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