marlin_verilog_macro_builder/
lib.rs1use std::{collections::HashMap, path::Path};
8
9use marlin_verilator::PortDirection;
10use proc_macro2::TokenStream;
11use quote::{format_ident, quote};
12use sv_parser::{self as sv, Locate, RefNode, unwrap_node};
13
14mod util;
15
16pub struct MacroArgs {
17 pub source_path: syn::LitStr,
18 pub name: syn::LitStr,
19
20 pub clock_port: Option<syn::LitStr>,
21 pub reset_port: Option<syn::LitStr>,
22}
23
24impl syn::parse::Parse for MacroArgs {
25 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
26 syn::custom_keyword!(src);
27 syn::custom_keyword!(name);
28
29 syn::custom_keyword!(clock);
30 syn::custom_keyword!(reset);
31 input.parse::<src>()?;
32 input.parse::<syn::Token![=]>()?;
33 let source_path = input.parse::<syn::LitStr>()?;
34
35 input.parse::<syn::Token![,]>()?;
36
37 input.parse::<name>()?;
38 input.parse::<syn::Token![=]>()?;
39 let name = input.parse::<syn::LitStr>()?;
40
41 let mut clock_port = None;
42 let mut reset_port = None;
43 while input.peek(syn::Token![,]) {
44 input.parse::<syn::Token![,]>()?;
45
46 let lookahead = input.lookahead1();
47 if lookahead.peek(clock) {
48 input.parse::<clock>()?;
49 input.parse::<syn::Token![=]>()?;
50 clock_port = Some(input.parse::<syn::LitStr>()?);
51 } else if lookahead.peek(reset) {
52 input.parse::<reset>()?;
53 input.parse::<syn::Token![=]>()?;
54 reset_port = Some(input.parse::<syn::LitStr>()?);
55 } else {
56 return Err(lookahead.error());
57 }
58 }
59
60 Ok(Self {
61 source_path,
62 name,
63 clock_port,
64 reset_port,
65 })
66 }
67}
68
69pub fn build_verilated_struct(
70 macro_name: &str,
71 top_name: syn::LitStr,
72 source_path: syn::LitStr,
73 verilog_ports: Vec<(String, usize, usize, PortDirection)>,
74 clock_port: Option<syn::LitStr>,
75 reset_port: Option<syn::LitStr>,
76 item: TokenStream,
77) -> TokenStream {
78 let crate_name = format_ident!("{}", macro_name);
79 let item = match syn::parse::<syn::ItemStruct>(item.into()) {
80 Ok(item) => item,
81 Err(error) => {
82 return error.into_compile_error();
83 }
84 };
85
86 let mut struct_members = vec![];
87
88 let mut preeval_impl = vec![];
89 let mut posteval_impl = vec![];
90
91 let mut other_impl = vec![];
92
93 let mut verilated_model_ports_impl = vec![];
94 let mut verilated_model_init_impl = vec![];
95 let mut verilated_model_init_self = vec![];
96
97 verilated_model_init_impl.push(quote! {
98 let new_model: extern "C" fn() -> *mut std::ffi::c_void =
99 *unsafe { library.get(concat!("ffi_new_V", #top_name).as_bytes()) }
100 .expect("failed to get symbol");
101 let model = (new_model)();
102
103 let eval_model: extern "C" fn(*mut std::ffi::c_void) =
104 *unsafe { library.get(concat!("ffi_V", #top_name, "_eval").as_bytes()) }
105 .expect("failed to get symbol");
106 });
107 verilated_model_init_self.push(quote! {
108 eval_model,
109 model,
110 _marker: std::marker::PhantomData
111 });
112
113 for (port_name, port_msb, port_lsb, port_direction) in verilog_ports {
114 if port_name.chars().any(|c| c == '\\' || c == ' ') {
115 return syn::Error::new_spanned(
116 top_name,
117 "Escaped module names are not supported",
118 )
119 .into_compile_error();
120 }
121
122 let port_width = port_msb + 1 - port_lsb;
123
124 let port_type = if port_width <= 8 {
125 quote! { #crate_name::__reexports::verilator::types::CData }
126 } else if port_width <= 16 {
127 quote! { #crate_name::__reexports::verilator::types::SData }
128 } else if port_width <= 32 {
129 quote! { #crate_name::__reexports::verilator::types::IData }
130 } else if port_width <= 64 {
131 quote! { #crate_name::__reexports::verilator::types::QData }
132 } else {
133 return syn::Error::new_spanned(
134 source_path,
135 format!(
136 "Port `{}` is wider than supported right now",
137 port_name
138 ),
139 )
140 .into_compile_error();
141 };
142
143 let port_name_ident = format_ident!("{}", port_name);
144 let port_documentation = syn::LitStr::new(
145 &format!(
146 "Corresponds to Verilog `{port_direction} {port_name}[{port_msb}:{port_lsb}]`."
147 ),
148 top_name.span(),
149 );
150 struct_members.push(quote! {
151 #[doc = #port_documentation]
152 pub #port_name_ident: #port_type
153 });
154 verilated_model_init_self.push(quote! {
155 #port_name_ident: 0 as _
156 });
157
158 match port_direction {
159 PortDirection::Input => {
160 let setter = format_ident!("pin_{}", port_name);
161 struct_members.push(quote! {
162 #[doc(hidden)]
163 #setter: extern "C" fn(*mut std::ffi::c_void, #port_type)
164 });
165 preeval_impl.push(quote! {
166 (self.#setter)(self.model, self.#port_name_ident);
167 });
168
169 if let Some(clock_port) = &clock_port {
170 if clock_port.value().as_str() == port_name {
171 other_impl.push(quote! {
172 pub fn tick(&mut self) {
173 self.#port_name = 1 as _;
174 self.eval();
175 self.#port_name = 0 as _;
176 self.eval();
177 }
178 });
179 }
180 }
181
182 if let Some(_reset_port) = &reset_port {
183 todo!("reset ports");
184 }
185
186 verilated_model_init_impl.push(quote! {
187 let #setter: extern "C" fn(*mut std::ffi::c_void, #port_type) =
188 *unsafe { library.get(concat!("ffi_V", #top_name, "_pin_", #port_name).as_bytes()) }
189 .expect("failed to get symbol");
190 });
191 verilated_model_init_self.push(quote! { #setter });
192 }
193 PortDirection::Output => {
194 let getter = format_ident!("read_{}", port_name);
195 struct_members.push(quote! {
196 #[doc(hidden)]
197 #getter: extern "C" fn(*mut std::ffi::c_void) -> #port_type
198 });
199 posteval_impl.push(quote! {
200 self.#port_name_ident = (self.#getter)(self.model);
201 });
202
203 verilated_model_init_impl.push(quote! {
204 let #getter: extern "C" fn(*mut std::ffi::c_void) -> #port_type =
205 *unsafe { library.get(concat!("ffi_V", #top_name, "_read_", #port_name).as_bytes()) }
206 .expect("failed to get symbol");
207 });
208 verilated_model_init_self.push(quote! { #getter });
209 }
210 _ => todo!("Unhandled port direction"),
211 }
212
213 let verilated_model_port_direction = match port_direction {
214 PortDirection::Input => {
215 quote! { #crate_name::__reexports::verilator::PortDirection::Input }
216 }
217 PortDirection::Output => {
218 quote! { #crate_name::__reexports::verilator::PortDirection::Output }
219 }
220 _ => todo!("Other port directions"),
221 };
222
223 verilated_model_ports_impl.push(quote! {
224 (#port_name, #port_msb, #port_lsb, #verilated_model_port_direction)
225 });
226 }
227
228 struct_members.push(quote! {
229 #[doc(hidden)]
230 eval_model: extern "C" fn(*mut std::ffi::c_void)
231 });
232
233 let struct_name = item.ident;
234 let vis = item.vis;
235 let port_count = verilated_model_ports_impl.len();
236 quote! {
237 #vis struct #struct_name<'ctx> {
238 #[doc(hidden)]
239 vcd_api: Option<#crate_name::__reexports::verilator::vcd::__private::VcdApi>,
240 #[doc(hidden)]
241 opened_vcd: bool,
242 #(#struct_members),*,
243 #[doc = "# Safety\nThe Rust binding to the model will not outlive the dynamic library context (with lifetime `'ctx`) and is dropped when this struct is."]
244 #[doc(hidden)]
245 model: *mut std::ffi::c_void,
246 #[doc(hidden)]
247 _marker: std::marker::PhantomData<&'ctx ()>,
248 #[doc(hidden)]
249 _unsend_unsync: std::marker::PhantomData<(std::cell::Cell<()>, std::sync::MutexGuard<'static, ()>)>
250 }
251
252 impl<'ctx> #struct_name<'ctx> {
253 pub fn eval(&mut self) {
254 #(#preeval_impl)*
255 (self.eval_model)(self.model);
256 #(#posteval_impl)*
257 }
258
259 pub fn open_vcd(
260 &mut self,
261 path: impl std::convert::AsRef<std::path::Path>,
262 ) -> #crate_name::__reexports::verilator::vcd::Vcd<'ctx> {
263 let path = path.as_ref();
264 if let Some(vcd_api) = &self.vcd_api {
265 if self.opened_vcd {
266 panic!("Verilator does not support opening multiple VCD traces (see issue #5813). You can instead split the already-opened VCD.");
267 }
268 let c_path = std::ffi::CString::new(path.as_os_str().as_encoded_bytes()).expect("Failed to convert provided VCD path to C string");
269 let vcd_ptr = (vcd_api.open_trace)(self.model, c_path.as_ptr());
270 self.opened_vcd = true;
271 #crate_name::__reexports::verilator::vcd::__private::new_vcd(
272 vcd_ptr,
273 vcd_api.dump,
274 vcd_api.open_next,
275 vcd_api.flush,
276 vcd_api.close_and_delete
277 )
278 } else {
279 #crate_name::__reexports::verilator::vcd::__private::new_vcd_useless()
280 }
281 }
282
283 #(#other_impl)*
284 }
285
286 impl<'ctx> #crate_name::__reexports::verilator::VerilatedModel<'ctx> for #struct_name<'ctx> {
287 fn name() -> &'static str {
288 #top_name
289 }
290
291 fn source_path() -> &'static str {
292 #source_path
293 }
294
295 fn ports() -> &'static [(&'static str, usize, usize, #crate_name::__reexports::verilator::PortDirection)] {
296 static PORTS: [(&'static str, usize, usize, #crate_name::__reexports::verilator::PortDirection); #port_count] = [#(#verilated_model_ports_impl),*];
297 &PORTS
298 }
299
300 fn init_from(library: &'ctx #crate_name::__reexports::libloading::Library, tracing_enabled: bool) -> Self {
301 #(#verilated_model_init_impl)*
302
303 let vcd_api =
304 if tracing_enabled {
305 use #crate_name::__reexports::verilator::vcd::__private::VcdApi;
306
307 let open_trace: extern "C" fn(*mut std::ffi::c_void, *const std::ffi::c_char) -> *mut std::ffi::c_void =
308 *unsafe { library.get(concat!("ffi_V", #top_name, "_open_trace").as_bytes()).expect("failed to get open_trace symbol") };
309 let dump: extern "C" fn(*mut std::ffi::c_void, u64) =
310 *unsafe { library.get(b"ffi_VerilatedVcdC_dump").expect("failed to get dump symbol") };
311 let open_next: extern "C" fn(*mut std::ffi::c_void, bool) =
312 *unsafe { library.get(b"ffi_VerilatedVcdC_open_next").expect("failed to get open_next symbol") };
313 let flush: extern "C" fn(*mut std::ffi::c_void) =
314 *unsafe { library.get(b"ffi_VerilatedVcdC_flush").expect("failed to get flush symbol") };
315 let close_and_delete: extern "C" fn(*mut std::ffi::c_void) =
316 *unsafe { library.get(b"ffi_VerilatedVcdC_close_and_delete").expect("failed to get close_and_delete symbol") };
317 Some(VcdApi { open_trace, dump, open_next, flush, close_and_delete })
318 } else {
319 None
320 };
321
322 Self {
323 vcd_api,
324 opened_vcd: false,
325 #(#verilated_model_init_self),*,
326 _unsend_unsync: std::marker::PhantomData
327 }
328 }
329
330 unsafe fn model(&self) -> *mut std::ffi::c_void {
331 self.model
332 }
333 }
334 }
335}
336
337pub fn parse_verilog_ports(
338 top_name: &syn::LitStr,
339 source_path: &syn::LitStr,
340 verilog_source_path: &Path,
341) -> Result<Vec<(String, usize, usize, PortDirection)>, proc_macro2::TokenStream>
342{
343 let defines = HashMap::new();
344 let (ast, _) =
345 match sv::parse_sv(verilog_source_path, &defines, &["."], false, false)
346 {
347 Ok(result) => result,
348 Err(error) => {
349 return Err(syn::Error::new_spanned(
350 source_path,
351 error.to_string(),
352 )
353 .into_compile_error());
354 }
355 };
356
357 let Some(module) = (&ast).into_iter().find_map(|node| match node {
358 RefNode::ModuleDeclarationAnsi(module) => {
359 fn get_identifier(node: RefNode) -> Option<Locate> {
361 match unwrap_node!(node, SimpleIdentifier, EscapedIdentifier) {
362 Some(RefNode::SimpleIdentifier(x)) => Some(x.nodes.0),
363 Some(RefNode::EscapedIdentifier(x)) => Some(x.nodes.0),
364 _ => None,
365 }
366 }
367
368 let id = unwrap_node!(module, ModuleIdentifier).unwrap();
369 let id = get_identifier(id).unwrap();
370 let id = ast.get_str_trim(&id).unwrap();
371 if id == top_name.value().as_str() {
372 Some(module)
373 } else {
374 None
375 }
376 }
377 _ => None,
378 }) else {
379 return Err(syn::Error::new_spanned(
380 top_name,
381 format!(
382 "Could not find module declaration for `{}` in {}",
383 top_name.value(),
384 source_path.value()
385 ),
386 )
387 .into_compile_error());
388 };
389
390 let port_declarations_list = module
391 .nodes
392 .0
393 .nodes
394 .6
395 .as_ref()
396 .and_then(|list| list.nodes.0.nodes.1.as_ref())
397 .map(|list| list.contents())
398 .unwrap_or(vec![]);
399
400 let mut ports = vec![];
401 for (_, port) in port_declarations_list {
402 match port {
403 sv::AnsiPortDeclaration::Net(net) => {
404 let port_name = ast.get_str_trim(&net.nodes.1.nodes.0).expect(
405 "Port identifier could not be traced back to source code",
406 );
407
408 if port_name.chars().any(|c| c == '\\' || c == ' ') {
409 return Err(syn::Error::new_spanned(
410 top_name,
411 "Escaped module names are not supported",
412 )
413 .into_compile_error());
414 }
415
416 let Some((port_direction, port_type ))= net.nodes.0.as_ref().and_then(|maybe_net_header| match maybe_net_header {
417 sv::NetPortHeaderOrInterfacePortHeader::NetPortHeader(net_port_header) => {
418 net_port_header.nodes.0.as_ref().map(|port_direction| (port_direction, &net_port_header.nodes.1))
419 },
420 _ => todo!("Other port header")
421 }) else {
422 return Err(syn::Error::new_spanned(
423 source_path,
424 format!(
425 "Port `{}` has no supported direction (`input` or `output`)",
426 port_name
427 ),
428 )
429 .into_compile_error())
430 };
431
432 let port_dimensions = match port_type {
433 sv::NetPortType::DataType(net_port_type_data_type) => {
434 match &net_port_type_data_type.nodes.1 {
435 sv::DataTypeOrImplicit::DataType(data_type) => {
436 match &**data_type {
437 sv::DataType::Vector(data_type_vector) => {
438 &data_type_vector.nodes.2
439 }
440 other => todo!(
441 "Unsupported data type {:?}",
442 other
443 ),
444 }
445 }
446 sv::DataTypeOrImplicit::ImplicitDataType(
447 implicit_data_type,
448 ) => &implicit_data_type.nodes.1,
449 }
450 }
451 sv::NetPortType::NetTypeIdentifier(
452 _net_type_identifier,
453 ) => todo!("bklk"),
454 sv::NetPortType::Interconnect(
455 _net_port_type_interconnect,
456 ) => todo!("ckl"),
457 };
458
459 let (port_msb, port_lsb) = match port_dimensions.len() {
460 0 => (0, 0),
461 1 => match &port_dimensions[0] {
462 sv::PackedDimension::Range(packed_dimension_range) => {
463 let range =
464 &packed_dimension_range.nodes.0.nodes.1.nodes;
465 (
466 util::evaluate_numeric_constant_expression(
467 &ast, &range.0,
468 ),
469 util::evaluate_numeric_constant_expression(
470 &ast, &range.2,
471 ),
472 )
473 }
474 _ => todo!(),
475 },
476 _ => todo!("Don't support multidimensional ports yet"),
477 };
478
479 let port_direction = match port_direction {
480 sv::PortDirection::Input(_) => PortDirection::Input,
481 sv::PortDirection::Output(_) => PortDirection::Output,
482 sv::PortDirection::Inout(_) => PortDirection::Inout,
483 sv::PortDirection::Ref(_) => todo!(),
484 };
485
486 ports.push((
487 port_name.to_string(),
488 port_msb,
489 port_lsb,
490 port_direction,
491 ));
492 }
493 _ => todo!("Other types of ports"),
494 }
495 }
496
497 Ok(ports)
498}