marlin_verilog_macro_builder/
lib.rs

1// Copyright (C) 2024 Ethan Uppal.
2//
3// This Source Code Form is subject to the terms of the Mozilla Public License,
4// v. 2.0. If a copy of the MPL was not distributed with this file, You can
5// obtain one at https://mozilla.org/MPL/2.0/.
6
7use 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            // taken from https://github.com/dalance/sv-parser/blob/master/README.md
521            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}