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                )
427                .into_compile_error());
428            }
429        };
430
431    let Some(module) = (&ast).into_iter().find_map(|node| match node {
432        RefNode::ModuleDeclarationAnsi(module) => {
433            // taken from https://github.com/dalance/sv-parser/blob/master/README.md
434            fn get_identifier(node: RefNode) -> Option<Locate> {
435                match unwrap_node!(node, SimpleIdentifier, EscapedIdentifier) {
436                    Some(RefNode::SimpleIdentifier(x)) => Some(x.nodes.0),
437                    Some(RefNode::EscapedIdentifier(x)) => Some(x.nodes.0),
438                    _ => None,
439                }
440            }
441
442            let id = unwrap_node!(module, ModuleIdentifier).unwrap();
443            let id = get_identifier(id).unwrap();
444            let id = ast.get_str_trim(&id).unwrap();
445            if id == top_name.value().as_str() {
446                Some(module)
447            } else {
448                None
449            }
450        }
451        _ => None,
452    }) else {
453        return Err(syn::Error::new_spanned(
454            top_name,
455            format!(
456                "Could not find module declaration for `{}` in {}",
457                top_name.value(),
458                source_path.value()
459            ),
460        )
461        .into_compile_error());
462    };
463
464    let port_declarations_list = module
465        .nodes
466        .0
467        .nodes
468        .6
469        .as_ref()
470        .and_then(|list| list.nodes.0.nodes.1.as_ref())
471        .map(|list| list.contents())
472        .unwrap_or(vec![]);
473
474    let mut ports = vec![];
475    for (_, port) in port_declarations_list {
476        match port {
477            sv::AnsiPortDeclaration::Net(net) => {
478                let port_name = ast.get_str_trim(&net.nodes.1.nodes.0).expect(
479                    "Port identifier could not be traced back to source code",
480                );
481
482                if port_name.chars().any(|c| c == '\\' || c == ' ') {
483                    return Err(syn::Error::new_spanned(
484                        top_name,
485                        "Escaped module names are not supported",
486                    )
487                    .into_compile_error());
488                }
489
490                let Some((port_direction, port_type ))= net.nodes.0.as_ref().and_then(|maybe_net_header| match maybe_net_header {
491                    sv::NetPortHeaderOrInterfacePortHeader::NetPortHeader(net_port_header) => {
492                        net_port_header.nodes.0.as_ref().map(|port_direction| (port_direction, &net_port_header.nodes.1))
493                    },
494                    _ => todo!("Other port header")
495                }) else {
496                    return Err(syn::Error::new_spanned(
497                        source_path,
498                        format!(
499                            "Port `{}` has no supported direction (`input` or `output`)",
500                            port_name
501                        ),
502                    )
503                    .into_compile_error())
504                };
505
506                let port_dimensions = match port_type {
507                    sv::NetPortType::DataType(net_port_type_data_type) => {
508                        match &net_port_type_data_type.nodes.1 {
509                            sv::DataTypeOrImplicit::DataType(data_type) => {
510                                match &**data_type {
511                                    sv::DataType::Vector(data_type_vector) => {
512                                        &data_type_vector.nodes.2
513                                    }
514                                    other => todo!(
515                                        "Unsupported data type {:?}",
516                                        other
517                                    ),
518                                }
519                            }
520                            sv::DataTypeOrImplicit::ImplicitDataType(
521                                implicit_data_type,
522                            ) => &implicit_data_type.nodes.1,
523                        }
524                    }
525                    sv::NetPortType::NetTypeIdentifier(
526                        _net_type_identifier,
527                    ) => todo!("bklk"),
528                    sv::NetPortType::Interconnect(
529                        _net_port_type_interconnect,
530                    ) => todo!("ckl"),
531                };
532
533                let (port_msb, port_lsb) = match port_dimensions.len() {
534                    0 => (0, 0),
535                    1 => match &port_dimensions[0] {
536                        sv::PackedDimension::Range(packed_dimension_range) => {
537                            let range =
538                                &packed_dimension_range.nodes.0.nodes.1.nodes;
539                            (
540                                util::evaluate_numeric_constant_expression(
541                                    &ast, &range.0,
542                                ),
543                                util::evaluate_numeric_constant_expression(
544                                    &ast, &range.2,
545                                ),
546                            )
547                        }
548                        _ => todo!(),
549                    },
550                    _ => todo!("Don't support multidimensional ports yet"),
551                };
552
553                let port_direction = match port_direction {
554                    sv::PortDirection::Input(_) => PortDirection::Input,
555                    sv::PortDirection::Output(_) => PortDirection::Output,
556                    sv::PortDirection::Inout(_) => PortDirection::Inout,
557                    sv::PortDirection::Ref(_) => todo!(),
558                };
559
560                ports.push((
561                    port_name.to_string(),
562                    port_msb,
563                    port_lsb,
564                    port_direction,
565                ));
566            }
567            _ => todo!("Other types of ports"),
568        }
569    }
570
571    Ok(ports)
572}