1use 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 let mut dynamic_read_arms = vec![];
98 let mut dynamic_pin_arms = vec![];
99
100 verilated_model_init_impl.push(quote! {
101 let new_model: extern "C" fn() -> *mut std::ffi::c_void =
102 *unsafe { library.get(concat!("ffi_new_V", #top_name).as_bytes()) }
103 .expect("failed to get symbol");
104 let model = (new_model)();
105
106 let eval_model: extern "C" fn(*mut std::ffi::c_void) =
107 *unsafe { library.get(concat!("ffi_V", #top_name, "_eval").as_bytes()) }
108 .expect("failed to get symbol");
109 });
110 verilated_model_init_self.push(quote! {
111 eval_model,
112 model,
113 _marker: std::marker::PhantomData
114 });
115
116 for (port_name, port_msb, port_lsb, port_direction) in verilog_ports {
117 if port_name.chars().any(|c| c == '\\' || c == ' ') {
118 return syn::Error::new_spanned(
119 top_name,
120 "Escaped module names are not supported",
121 )
122 .into_compile_error();
123 }
124
125 let port_width = port_msb + 1 - port_lsb;
126
127 let port_type_name = if port_width <= 8 {
128 quote! { CData }
129 } else if port_width <= 16 {
130 quote! { SData }
131 } else if port_width <= 32 {
132 quote! { IData }
133 } else if port_width <= 64 {
134 quote! { QData }
135 } else {
136 return syn::Error::new_spanned(
137 source_path,
138 format!(
139 "Port `{}` is wider than supported right now",
140 port_name
141 ),
142 )
143 .into_compile_error();
144 };
145 let port_type = quote! { #crate_name::__reexports::verilator::types::#port_type_name };
146
147 let port_name_ident = format_ident!("{}", port_name);
148 let port_documentation = syn::LitStr::new(
149 &format!(
150 "Corresponds to Verilog `{port_direction} {port_name}[{port_msb}:{port_lsb}]`."
151 ),
152 top_name.span(),
153 );
154 struct_members.push(quote! {
155 #[doc = #port_documentation]
156 pub #port_name_ident: #port_type
157 });
158 verilated_model_init_self.push(quote! {
159 #port_name_ident: 0 as _
160 });
161
162 let port_name_literal = syn::LitStr::new(&port_name, top_name.span());
163
164 match port_direction {
165 PortDirection::Input => {
166 let setter = format_ident!("pin_{}", port_name);
167 struct_members.push(quote! {
168 #[doc(hidden)]
169 #setter: extern "C" fn(*mut std::ffi::c_void, #port_type)
170 });
171 preeval_impl.push(quote! {
172 (self.#setter)(self.model, self.#port_name_ident);
173 });
174
175 if let Some(clock_port) = &clock_port {
176 if clock_port.value().as_str() == port_name {
177 other_impl.push(quote! {
178 pub fn tick(&mut self) {
179 self.#port_name = 1 as _;
180 self.eval();
181 self.#port_name = 0 as _;
182 self.eval();
183 }
184 });
185 }
186 }
187
188 if let Some(_reset_port) = &reset_port {
189 todo!("reset ports");
190 }
191
192 verilated_model_init_impl.push(quote! {
193 let #setter: extern "C" fn(*mut std::ffi::c_void, #port_type) =
194 *unsafe { library.get(concat!("ffi_V", #top_name, "_pin_", #port_name).as_bytes()) }
195 .expect("failed to get symbol");
196 });
197 verilated_model_init_self.push(quote! { #setter });
198
199 dynamic_pin_arms.push(quote! {
200 #port_name_literal => {
201 if let #crate_name::__reexports::verilator::dynamic::VerilatorValue::#port_type_name(inner) = value {
202 self.#port_name_ident = inner;
203 } else {
204 return Err(
205 #crate_name::__reexports::verilator::dynamic::DynamicVerilatedModelError::InvalidPortWidth {
206 top_module: Self::name().to_string(),
207 port: port,
208 width: #port_width as _,
209 attempted_lower: 0,
210 attempted_higher: value.width()
211 },
212 );
213 }
214 }
215 });
216 }
217 PortDirection::Output => {
218 let getter = format_ident!("read_{}", port_name);
219 struct_members.push(quote! {
220 #[doc(hidden)]
221 #getter: extern "C" fn(*mut std::ffi::c_void) -> #port_type
222 });
223 posteval_impl.push(quote! {
224 self.#port_name_ident = (self.#getter)(self.model);
225 });
226
227 verilated_model_init_impl.push(quote! {
228 let #getter: extern "C" fn(*mut std::ffi::c_void) -> #port_type =
229 *unsafe { library.get(concat!("ffi_V", #top_name, "_read_", #port_name).as_bytes()) }
230 .expect("failed to get symbol");
231 });
232 verilated_model_init_self.push(quote! { #getter });
233
234 dynamic_read_arms.push(quote! {
235 #port_name_literal => Ok(self.#port_name_ident.into())
236 });
237 }
238 _ => todo!("Unhandled port direction"),
239 }
240
241 let verilated_model_port_direction = match port_direction {
242 PortDirection::Input => {
243 quote! { #crate_name::__reexports::verilator::PortDirection::Input }
244 }
245 PortDirection::Output => {
246 quote! { #crate_name::__reexports::verilator::PortDirection::Output }
247 }
248 _ => todo!("Other port directions"),
249 };
250
251 verilated_model_ports_impl.push(quote! {
252 (#port_name, #port_msb, #port_lsb, #verilated_model_port_direction)
253 });
254 }
255
256 struct_members.push(quote! {
257 #[doc(hidden)]
258 eval_model: extern "C" fn(*mut std::ffi::c_void)
259 });
260
261 let struct_name = item.ident;
262 let vis = item.vis;
263 let port_count = verilated_model_ports_impl.len();
264 quote! {
265 #vis struct #struct_name<'ctx> {
266 #[doc(hidden)]
267 vcd_api: Option<#crate_name::__reexports::verilator::vcd::__private::VcdApi>,
268 #[doc(hidden)]
269 opened_vcd: bool,
270 #(#struct_members),*,
271 #[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."]
272 #[doc(hidden)]
273 model: *mut std::ffi::c_void,
274 #[doc(hidden)]
275 _marker: std::marker::PhantomData<&'ctx ()>,
276 #[doc(hidden)]
277 _unsend_unsync: std::marker::PhantomData<(std::cell::Cell<()>, std::sync::MutexGuard<'static, ()>)>
278 }
279
280 impl<'ctx> #struct_name<'ctx> {
281 #[doc = "Equivalent to the Verilator `eval` method."]
282 pub fn eval(&mut self) {
283 #(#preeval_impl)*
284 (self.eval_model)(self.model);
285 #(#posteval_impl)*
286 }
287
288 pub fn open_vcd(
289 &mut self,
290 path: impl std::convert::AsRef<std::path::Path>,
291 ) -> #crate_name::__reexports::verilator::vcd::Vcd<'ctx> {
292 let path = path.as_ref();
293 if let Some(vcd_api) = &self.vcd_api {
294 if self.opened_vcd {
295 panic!("Verilator does not support opening multiple VCD traces (see issue #5813). You can instead split the already-opened VCD.");
296 }
297 let c_path = std::ffi::CString::new(path.as_os_str().as_encoded_bytes()).expect("Failed to convert provided VCD path to C string");
298 let vcd_ptr = (vcd_api.open_trace)(self.model, c_path.as_ptr());
299 self.opened_vcd = true;
300 #crate_name::__reexports::verilator::vcd::__private::new_vcd(
301 vcd_ptr,
302 vcd_api.dump,
303 vcd_api.open_next,
304 vcd_api.flush,
305 vcd_api.close_and_delete
306 )
307 } else {
308 #crate_name::__reexports::verilator::vcd::__private::new_vcd_useless()
309 }
310 }
311
312 #(#other_impl)*
313 }
314
315 impl<'ctx> #crate_name::__reexports::verilator::AsVerilatedModel<'ctx> for #struct_name<'ctx> {
316 fn name() -> &'static str {
317 #top_name
318 }
319
320 fn source_path() -> &'static str {
321 #source_path
322 }
323
324 fn ports() -> &'static [(&'static str, usize, usize, #crate_name::__reexports::verilator::PortDirection)] {
325 static PORTS: [(&'static str, usize, usize, #crate_name::__reexports::verilator::PortDirection); #port_count] = [#(#verilated_model_ports_impl),*];
326 &PORTS
327 }
328
329 fn init_from(library: &'ctx #crate_name::__reexports::libloading::Library, tracing_enabled: bool) -> Self {
330 #(#verilated_model_init_impl)*
331
332 let vcd_api =
333 if tracing_enabled {
334 use #crate_name::__reexports::verilator::vcd::__private::VcdApi;
335
336 let open_trace: extern "C" fn(*mut std::ffi::c_void, *const std::ffi::c_char) -> *mut std::ffi::c_void =
337 *unsafe { library.get(concat!("ffi_V", #top_name, "_open_trace").as_bytes()).expect("failed to get open_trace symbol") };
338 let dump: extern "C" fn(*mut std::ffi::c_void, u64) =
339 *unsafe { library.get(b"ffi_VerilatedVcdC_dump").expect("failed to get dump symbol") };
340 let open_next: extern "C" fn(*mut std::ffi::c_void, bool) =
341 *unsafe { library.get(b"ffi_VerilatedVcdC_open_next").expect("failed to get open_next symbol") };
342 let flush: extern "C" fn(*mut std::ffi::c_void) =
343 *unsafe { library.get(b"ffi_VerilatedVcdC_flush").expect("failed to get flush symbol") };
344 let close_and_delete: extern "C" fn(*mut std::ffi::c_void) =
345 *unsafe { library.get(b"ffi_VerilatedVcdC_close_and_delete").expect("failed to get close_and_delete symbol") };
346 Some(VcdApi { open_trace, dump, open_next, flush, close_and_delete })
347 } else {
348 None
349 };
350
351 Self {
352 vcd_api,
353 opened_vcd: false,
354 #(#verilated_model_init_self),*,
355 _unsend_unsync: std::marker::PhantomData
356 }
357 }
358
359 unsafe fn model(&self) -> *mut std::ffi::c_void {
360 self.model
361 }
362 }
363
364 impl<'ctx> #crate_name::__reexports::verilator::AsDynamicVerilatedModel<'ctx> for #struct_name<'ctx> {
365 fn read(
366 &self,
367 port: impl Into<String>,
368 ) -> Result<#crate_name::__reexports::verilator::dynamic::VerilatorValue, #crate_name::__reexports::verilator::dynamic::DynamicVerilatedModelError> {
369 use #crate_name::__reexports::verilator::AsVerilatedModel;
370
371 let port = port.into();
372
373 match port.as_str() {
374 #(#dynamic_read_arms,)*
375 _ => Err(#crate_name::__reexports::verilator::dynamic::DynamicVerilatedModelError::NoSuchPort {
376 top_module: Self::name().to_string(),
377 port,
378 source: None,
379 })
380 }
381 }
382
383 fn pin(
384 &mut self,
385 port: impl Into<String>,
386 value: impl Into<#crate_name::__reexports::verilator::dynamic::VerilatorValue>,
387 ) -> Result<(), #crate_name::__reexports::verilator::dynamic::DynamicVerilatedModelError> {
388 use #crate_name::__reexports::verilator::AsVerilatedModel;
389
390 let port = port.into();
391 let value = value.into();
392
393 match port.as_str() {
394 #(#dynamic_pin_arms,)*
395 _ => {
396 return Err(#crate_name::__reexports::verilator::dynamic::DynamicVerilatedModelError::NoSuchPort {
397 top_module: Self::name().to_string(),
398 port,
399 source: None,
400 });
401 }
402 }
403
404 #[allow(unreachable_code)]
405 Ok(())
406 }
407 }
408 }
409}
410
411pub fn parse_verilog_ports(
412 top_name: &syn::LitStr,
413 source_path: &syn::LitStr,
414 verilog_source_path: &Path,
415) -> Result<Vec<(String, usize, usize, PortDirection)>, proc_macro2::TokenStream>
416{
417 let defines = HashMap::new();
418 let (ast, _) =
419 match sv::parse_sv(verilog_source_path, &defines, &["."], false, false)
420 {
421 Ok(result) => result,
422 Err(error) => {
423 return Err(syn::Error::new_spanned(
424 source_path,
425 error.to_string()
426 + " (Try checking, for instance, that the file exists.)",
427 )
428 .into_compile_error());
429 }
430 };
431
432 let Some(module) = (&ast).into_iter().find_map(|node| match node {
433 RefNode::ModuleDeclarationAnsi(module) => {
434 fn get_identifier(node: RefNode) -> Option<Locate> {
436 match unwrap_node!(node, SimpleIdentifier, EscapedIdentifier) {
437 Some(RefNode::SimpleIdentifier(x)) => Some(x.nodes.0),
438 Some(RefNode::EscapedIdentifier(x)) => Some(x.nodes.0),
439 _ => None,
440 }
441 }
442
443 let id = unwrap_node!(module, ModuleIdentifier).unwrap();
444 let id = get_identifier(id).unwrap();
445 let id = ast.get_str_trim(&id).unwrap();
446 if id == top_name.value().as_str() {
447 Some(module)
448 } else {
449 None
450 }
451 }
452 _ => None,
453 }) else {
454 return Err(syn::Error::new_spanned(
455 top_name,
456 format!(
457 "Could not find module declaration for `{}` in {}",
458 top_name.value(),
459 source_path.value()
460 ),
461 )
462 .into_compile_error());
463 };
464
465 let port_declarations_list = module
466 .nodes
467 .0
468 .nodes
469 .6
470 .as_ref()
471 .and_then(|list| list.nodes.0.nodes.1.as_ref())
472 .map(|list| list.contents())
473 .unwrap_or(vec![]);
474
475 let mut ports = vec![];
476 for (_, port) in port_declarations_list {
477 match port {
478 sv::AnsiPortDeclaration::Net(net) => {
479 let port_name = ast.get_str_trim(&net.nodes.1.nodes.0).expect(
480 "Port identifier could not be traced back to source code",
481 );
482
483 let (port_direction_node, port_type) = net
484 .nodes
485 .0
486 .as_ref()
487 .and_then(|maybe_net_header| match maybe_net_header {
488 sv::NetPortHeaderOrInterfacePortHeader::NetPortHeader(net_port_header) => {
489 net_port_header.nodes.0.as_ref().map(|d| (d, &net_port_header.nodes.1))
490 }
491 _ => todo!("Other port header"),
492 })
493 .ok_or_else(|| {
494 syn::Error::new_spanned(
495 source_path,
496 format!(
497 "Port `{}` has no supported direction (`input` or `output`)",
498 port_name
499 ),
500 )
501 .into_compile_error()
502 })?;
503
504 let dimensions: &[sv::PackedDimension] = match port_type {
505 sv::NetPortType::DataType(net_port_type_data_type) => {
506 match &net_port_type_data_type.nodes.1 {
507 sv::DataTypeOrImplicit::DataType(data_type) => {
508 match &**data_type {
509 sv::DataType::Vector(data_type_vector) => {
510 &data_type_vector.nodes.2
511 }
512 other => todo!(
513 "Unsupported data type {:?}",
514 other
515 ),
516 }
517 }
518 sv::DataTypeOrImplicit::ImplicitDataType(
519 implicit_data_type,
520 ) => &implicit_data_type.nodes.1,
521 }
522 }
523 sv::NetPortType::NetTypeIdentifier(_)
524 | sv::NetPortType::Interconnect(_) => {
525 todo!("Port type not yet implemented for net ports")
526 }
527 };
528
529 let port_info = match process_port_common(
530 &ast,
531 top_name,
532 port_name,
533 dimensions,
534 port_direction_node,
535 ) {
536 Ok(port_info) => port_info,
537 Err(error) => {
538 return Err(error.into_compile_error());
539 }
540 };
541 ports.push(port_info);
542 }
543
544 sv::AnsiPortDeclaration::Variable(var) => {
545 let port_name = ast.get_str_trim(&var.nodes.1.nodes.0).expect(
546 "Port identifier could not be traced back to source code",
547 );
548
549 let (port_direction_node, port_type) = var
550 .nodes
551 .0
552 .as_ref()
553 .and_then(|header| {
554 header.nodes.0.as_ref().map(|d| (d, &header.nodes.1))
555 })
556 .ok_or_else(|| {
557 syn::Error::new_spanned(
558 source_path,
559 format!(
560 "Port `{}` has no supported direction (`input` or `output`)",
561 port_name
562 ),
563 )
564 .into_compile_error()
565 })?;
566
567 let dimensions: &[sv::PackedDimension] = match &port_type
568 .nodes
569 .0
570 {
571 sv::VarDataType::DataType(data_type) => {
572 match &**data_type {
573 sv::DataType::Vector(data_type_vector) => {
574 &data_type_vector.nodes.2
575 }
576 other => todo!("Unsupported data type {:?}", other),
577 }
578 }
579 sv::VarDataType::Var(var_data_type_var) => {
580 match &var_data_type_var.nodes.1 {
581 sv::DataTypeOrImplicit::DataType(data_type) => {
582 match &**data_type {
583 sv::DataType::Vector(data_type_vector) => {
584 &data_type_vector.nodes.2
585 }
586 other => todo!(
587 "Unsupported data type (in the VarDataType>DataTypeOrImplicit>DataType branch) {:?}",
588 other
589 ),
590 }
591 }
592 sv::DataTypeOrImplicit::ImplicitDataType(
593 implicit_data_type,
594 ) => &implicit_data_type.nodes.1,
595 }
596 }
597 };
598
599 let port_info = match process_port_common(
600 &ast,
601 top_name,
602 port_name,
603 dimensions,
604 port_direction_node,
605 ) {
606 Ok(port_info) => port_info,
607 Err(error) => {
608 return Err(error.into_compile_error());
609 }
610 };
611 ports.push(port_info);
612 }
613 _ => todo!("Other types of ports"),
614 }
615 }
616
617 Ok(ports)
618}
619
620fn process_port_common(
621 ast: &sv::SyntaxTree,
622 top_name: &syn::LitStr,
623 port_name: &str,
624 dimensions: &[sv::PackedDimension],
625 port_direction_node: &sv::PortDirection,
626) -> Result<(String, usize, usize, PortDirection), syn::Error> {
627 if port_name.chars().any(|c| c == '\\' || c == ' ') {
628 return Err(syn::Error::new_spanned(
629 top_name,
630 "Escaped module names are not supported",
631 ));
632 }
633
634 let (port_msb, port_lsb) = match dimensions.len() {
635 0 => (0, 0),
636 1 => match &dimensions[0] {
637 sv::PackedDimension::Range(packed_dimension_range) => {
638 let range = &packed_dimension_range.nodes.0.nodes.1.nodes;
639 (
640 util::evaluate_numeric_constant_expression(ast, &range.0),
641 util::evaluate_numeric_constant_expression(ast, &range.2),
642 )
643 }
644 _ => todo!("Unsupported dimension type"),
645 },
646 _ => todo!("Don't support multidimensional ports yet"),
647 };
648
649 let port_direction = match port_direction_node {
650 sv::PortDirection::Input(_) => PortDirection::Input,
651 sv::PortDirection::Output(_) => PortDirection::Output,
652 sv::PortDirection::Inout(_) => PortDirection::Inout,
653 sv::PortDirection::Ref(_) => {
654 todo!("Reference port direction is not supported")
655 }
656 };
657
658 Ok((port_name.to_string(), port_msb, port_lsb, port_direction))
659}