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