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    verilated_model_init_impl.push(quote! {
98        let new_model: extern "C" fn() -> *mut std::ffi::c_void =
99            *unsafe { library.get(concat!("ffi_new_V", #top_name).as_bytes()) }
100                .expect("failed to get symbol");
101        let model = (new_model)();
102
103        let eval_model: extern "C" fn(*mut std::ffi::c_void) =
104            *unsafe { library.get(concat!("ffi_V", #top_name, "_eval").as_bytes()) }
105                .expect("failed to get symbol");
106    });
107    verilated_model_init_self.push(quote! {
108        eval_model,
109        model,
110        _marker: std::marker::PhantomData
111    });
112
113    for (port_name, port_msb, port_lsb, port_direction) in verilog_ports {
114        if port_name.chars().any(|c| c == '\\' || c == ' ') {
115            return syn::Error::new_spanned(
116                top_name,
117                "Escaped module names are not supported",
118            )
119            .into_compile_error();
120        }
121
122        let port_width = port_msb + 1 - port_lsb;
123
124        let port_type = if port_width <= 8 {
125            quote! { #crate_name::__reexports::verilator::types::CData }
126        } else if port_width <= 16 {
127            quote! { #crate_name::__reexports::verilator::types::SData }
128        } else if port_width <= 32 {
129            quote! { #crate_name::__reexports::verilator::types::IData }
130        } else if port_width <= 64 {
131            quote! { #crate_name::__reexports::verilator::types::QData }
132        } else {
133            return syn::Error::new_spanned(
134                source_path,
135                format!(
136                    "Port `{}` is wider than supported right now",
137                    port_name
138                ),
139            )
140            .into_compile_error();
141        };
142
143        let port_name_ident = format_ident!("{}", port_name);
144        let port_documentation = syn::LitStr::new(
145            &format!(
146                "Corresponds to Verilog `{port_direction} {port_name}[{port_msb}:{port_lsb}]`."
147            ),
148            top_name.span(),
149        );
150        struct_members.push(quote! {
151            #[doc = #port_documentation]
152            pub #port_name_ident: #port_type
153        });
154        verilated_model_init_self.push(quote! {
155            #port_name_ident: 0 as _
156        });
157
158        match port_direction {
159            PortDirection::Input => {
160                let setter = format_ident!("pin_{}", port_name);
161                struct_members.push(quote! {
162                    #[doc(hidden)]
163                    #setter: extern "C" fn(*mut std::ffi::c_void, #port_type)
164                });
165                preeval_impl.push(quote! {
166                    (self.#setter)(self.model, self.#port_name_ident);
167                });
168
169                if let Some(clock_port) = &clock_port {
170                    if clock_port.value().as_str() == port_name {
171                        other_impl.push(quote! {
172                            pub fn tick(&mut self) {
173                                self.#port_name = 1 as _;
174                                self.eval();
175                                self.#port_name = 0 as _;
176                                self.eval();
177                            }
178                        });
179                    }
180                }
181
182                if let Some(_reset_port) = &reset_port {
183                    todo!("reset ports");
184                }
185
186                verilated_model_init_impl.push(quote! {
187                    let #setter: extern "C" fn(*mut std::ffi::c_void, #port_type) =
188                        *unsafe { library.get(concat!("ffi_V", #top_name, "_pin_", #port_name).as_bytes()) }
189                            .expect("failed to get symbol");
190                });
191                verilated_model_init_self.push(quote! { #setter });
192            }
193            PortDirection::Output => {
194                let getter = format_ident!("read_{}", port_name);
195                struct_members.push(quote! {
196                    #[doc(hidden)]
197                    #getter: extern "C" fn(*mut std::ffi::c_void) -> #port_type
198                });
199                posteval_impl.push(quote! {
200                    self.#port_name_ident = (self.#getter)(self.model);
201                });
202
203                verilated_model_init_impl.push(quote! {
204                    let #getter: extern "C" fn(*mut std::ffi::c_void) -> #port_type =
205                        *unsafe { library.get(concat!("ffi_V", #top_name, "_read_", #port_name).as_bytes()) }
206                            .expect("failed to get symbol");
207                });
208                verilated_model_init_self.push(quote! { #getter });
209            }
210            _ => todo!("Unhandled port direction"),
211        }
212
213        let verilated_model_port_direction = match port_direction {
214            PortDirection::Input => {
215                quote! { #crate_name::__reexports::verilator::PortDirection::Input }
216            }
217            PortDirection::Output => {
218                quote! { #crate_name::__reexports::verilator::PortDirection::Output }
219            }
220            _ => todo!("Other port directions"),
221        };
222
223        verilated_model_ports_impl.push(quote! {
224            (#port_name, #port_msb, #port_lsb, #verilated_model_port_direction)
225        });
226    }
227
228    struct_members.push(quote! {
229        #[doc(hidden)]
230        eval_model: extern "C" fn(*mut std::ffi::c_void)
231    });
232
233    let struct_name = item.ident;
234    let vis = item.vis;
235    let port_count = verilated_model_ports_impl.len();
236    quote! {
237        #vis struct #struct_name<'ctx> {
238            #[doc(hidden)]
239            vcd_api: Option<#crate_name::__reexports::verilator::vcd::__private::VcdApi>,
240            #[doc(hidden)]
241            opened_vcd: bool,
242            #(#struct_members),*,
243            #[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."]
244            #[doc(hidden)]
245            model: *mut std::ffi::c_void,
246            #[doc(hidden)]
247            _marker: std::marker::PhantomData<&'ctx ()>,
248            #[doc(hidden)]
249            _unsend_unsync: std::marker::PhantomData<(std::cell::Cell<()>, std::sync::MutexGuard<'static, ()>)>
250        }
251
252        impl<'ctx> #struct_name<'ctx> {
253            pub fn eval(&mut self) {
254                #(#preeval_impl)*
255                (self.eval_model)(self.model);
256                #(#posteval_impl)*
257            }
258
259            pub fn open_vcd(
260                &mut self,
261                path: impl std::convert::AsRef<std::path::Path>,
262            ) -> #crate_name::__reexports::verilator::vcd::Vcd<'ctx> {
263                let path = path.as_ref();
264                if let Some(vcd_api) = &self.vcd_api {
265                    if self.opened_vcd {
266                        panic!("Verilator does not support opening multiple VCD traces (see issue #5813). You can instead split the already-opened VCD.");
267                    }
268                    let c_path = std::ffi::CString::new(path.as_os_str().as_encoded_bytes()).expect("Failed to convert provided VCD path to C string");
269                    let vcd_ptr = (vcd_api.open_trace)(self.model, c_path.as_ptr());
270                    self.opened_vcd = true;
271                    #crate_name::__reexports::verilator::vcd::__private::new_vcd(
272                        vcd_ptr,
273                        vcd_api.dump,
274                        vcd_api.open_next,
275                        vcd_api.flush,
276                        vcd_api.close_and_delete
277                    )
278                } else {
279                    #crate_name::__reexports::verilator::vcd::__private::new_vcd_useless()
280                }
281            }
282
283            #(#other_impl)*
284        }
285
286        impl<'ctx> #crate_name::__reexports::verilator::VerilatedModel<'ctx> for #struct_name<'ctx> {
287            fn name() -> &'static str {
288                #top_name
289            }
290
291            fn source_path() -> &'static str {
292                #source_path
293            }
294
295            fn ports() -> &'static [(&'static str, usize, usize, #crate_name::__reexports::verilator::PortDirection)] {
296                static PORTS: [(&'static str, usize, usize, #crate_name::__reexports::verilator::PortDirection); #port_count] = [#(#verilated_model_ports_impl),*];
297                &PORTS
298            }
299
300            fn init_from(library: &'ctx #crate_name::__reexports::libloading::Library, tracing_enabled: bool) -> Self {
301                #(#verilated_model_init_impl)*
302
303                let vcd_api =
304                    if tracing_enabled {
305                        use #crate_name::__reexports::verilator::vcd::__private::VcdApi;
306
307                        let open_trace: extern "C" fn(*mut std::ffi::c_void, *const std::ffi::c_char) -> *mut std::ffi::c_void =
308                            *unsafe { library.get(concat!("ffi_V", #top_name, "_open_trace").as_bytes()).expect("failed to get open_trace symbol") };
309                        let dump: extern "C" fn(*mut std::ffi::c_void, u64) =
310                            *unsafe { library.get(b"ffi_VerilatedVcdC_dump").expect("failed to get dump symbol") };
311                        let open_next: extern "C" fn(*mut std::ffi::c_void, bool) =
312                            *unsafe { library.get(b"ffi_VerilatedVcdC_open_next").expect("failed to get open_next symbol") };
313                        let flush: extern "C" fn(*mut std::ffi::c_void) =
314                            *unsafe { library.get(b"ffi_VerilatedVcdC_flush").expect("failed to get flush symbol") };
315                        let close_and_delete: extern "C" fn(*mut std::ffi::c_void) =
316                            *unsafe { library.get(b"ffi_VerilatedVcdC_close_and_delete").expect("failed to get close_and_delete symbol") };
317                        Some(VcdApi { open_trace, dump, open_next, flush, close_and_delete })
318                    } else {
319                        None
320                    };
321
322                Self {
323                    vcd_api,
324                    opened_vcd: false,
325                    #(#verilated_model_init_self),*,
326                    _unsend_unsync: std::marker::PhantomData
327                }
328            }
329
330            unsafe fn model(&self) -> *mut std::ffi::c_void {
331                self.model
332            }
333        }
334    }
335}
336
337pub fn parse_verilog_ports(
338    top_name: &syn::LitStr,
339    source_path: &syn::LitStr,
340    verilog_source_path: &Path,
341) -> Result<Vec<(String, usize, usize, PortDirection)>, proc_macro2::TokenStream>
342{
343    let defines = HashMap::new();
344    let (ast, _) =
345        match sv::parse_sv(verilog_source_path, &defines, &["."], false, false)
346        {
347            Ok(result) => result,
348            Err(error) => {
349                return Err(syn::Error::new_spanned(
350                    source_path,
351                    error.to_string(),
352                )
353                .into_compile_error());
354            }
355        };
356
357    let Some(module) = (&ast).into_iter().find_map(|node| match node {
358        RefNode::ModuleDeclarationAnsi(module) => {
359            // taken from https://github.com/dalance/sv-parser/blob/master/README.md
360            fn get_identifier(node: RefNode) -> Option<Locate> {
361                match unwrap_node!(node, SimpleIdentifier, EscapedIdentifier) {
362                    Some(RefNode::SimpleIdentifier(x)) => Some(x.nodes.0),
363                    Some(RefNode::EscapedIdentifier(x)) => Some(x.nodes.0),
364                    _ => None,
365                }
366            }
367
368            let id = unwrap_node!(module, ModuleIdentifier).unwrap();
369            let id = get_identifier(id).unwrap();
370            let id = ast.get_str_trim(&id).unwrap();
371            if id == top_name.value().as_str() {
372                Some(module)
373            } else {
374                None
375            }
376        }
377        _ => None,
378    }) else {
379        return Err(syn::Error::new_spanned(
380            top_name,
381            format!(
382                "Could not find module declaration for `{}` in {}",
383                top_name.value(),
384                source_path.value()
385            ),
386        )
387        .into_compile_error());
388    };
389
390    let port_declarations_list = module
391        .nodes
392        .0
393        .nodes
394        .6
395        .as_ref()
396        .and_then(|list| list.nodes.0.nodes.1.as_ref())
397        .map(|list| list.contents())
398        .unwrap_or(vec![]);
399
400    let mut ports = vec![];
401    for (_, port) in port_declarations_list {
402        match port {
403            sv::AnsiPortDeclaration::Net(net) => {
404                let port_name = ast.get_str_trim(&net.nodes.1.nodes.0).expect(
405                    "Port identifier could not be traced back to source code",
406                );
407
408                if port_name.chars().any(|c| c == '\\' || c == ' ') {
409                    return Err(syn::Error::new_spanned(
410                        top_name,
411                        "Escaped module names are not supported",
412                    )
413                    .into_compile_error());
414                }
415
416                let Some((port_direction, port_type ))= net.nodes.0.as_ref().and_then(|maybe_net_header| match maybe_net_header {
417                    sv::NetPortHeaderOrInterfacePortHeader::NetPortHeader(net_port_header) => {
418                        net_port_header.nodes.0.as_ref().map(|port_direction| (port_direction, &net_port_header.nodes.1))
419                    },
420                    _ => todo!("Other port header")
421                }) else {
422                    return Err(syn::Error::new_spanned(
423                        source_path,
424                        format!(
425                            "Port `{}` has no supported direction (`input` or `output`)",
426                            port_name
427                        ),
428                    )
429                    .into_compile_error())
430                };
431
432                let port_dimensions = match port_type {
433                    sv::NetPortType::DataType(net_port_type_data_type) => {
434                        match &net_port_type_data_type.nodes.1 {
435                            sv::DataTypeOrImplicit::DataType(data_type) => {
436                                match &**data_type {
437                                    sv::DataType::Vector(data_type_vector) => {
438                                        &data_type_vector.nodes.2
439                                    }
440                                    other => todo!(
441                                        "Unsupported data type {:?}",
442                                        other
443                                    ),
444                                }
445                            }
446                            sv::DataTypeOrImplicit::ImplicitDataType(
447                                implicit_data_type,
448                            ) => &implicit_data_type.nodes.1,
449                        }
450                    }
451                    sv::NetPortType::NetTypeIdentifier(
452                        _net_type_identifier,
453                    ) => todo!("bklk"),
454                    sv::NetPortType::Interconnect(
455                        _net_port_type_interconnect,
456                    ) => todo!("ckl"),
457                };
458
459                let (port_msb, port_lsb) = match port_dimensions.len() {
460                    0 => (0, 0),
461                    1 => match &port_dimensions[0] {
462                        sv::PackedDimension::Range(packed_dimension_range) => {
463                            let range =
464                                &packed_dimension_range.nodes.0.nodes.1.nodes;
465                            (
466                                util::evaluate_numeric_constant_expression(
467                                    &ast, &range.0,
468                                ),
469                                util::evaluate_numeric_constant_expression(
470                                    &ast, &range.2,
471                                ),
472                            )
473                        }
474                        _ => todo!(),
475                    },
476                    _ => todo!("Don't support multidimensional ports yet"),
477                };
478
479                let port_direction = match port_direction {
480                    sv::PortDirection::Input(_) => PortDirection::Input,
481                    sv::PortDirection::Output(_) => PortDirection::Output,
482                    sv::PortDirection::Inout(_) => PortDirection::Inout,
483                    sv::PortDirection::Ref(_) => todo!(),
484                };
485
486                ports.push((
487                    port_name.to_string(),
488                    port_msb,
489                    port_lsb,
490                    port_direction,
491                ));
492            }
493            _ => todo!("Other types of ports"),
494        }
495    }
496
497    Ok(ports)
498}