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