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;
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            // taken from https://github.com/dalance/sv-parser/blob/master/README.md
435            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}