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