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 marlin_verilator::PortDirection;
8use proc_macro2::TokenStream;
9use quote::{format_ident, quote};
10
11pub struct MacroArgs {
12    pub source_path: syn::LitStr,
13    pub name: syn::LitStr,
14
15    pub clock_port: Option<syn::LitStr>,
16    pub reset_port: Option<syn::LitStr>,
17}
18
19impl syn::parse::Parse for MacroArgs {
20    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
21        syn::custom_keyword!(src);
22        syn::custom_keyword!(name);
23
24        syn::custom_keyword!(clock);
25        syn::custom_keyword!(reset);
26        input.parse::<src>()?;
27        input.parse::<syn::Token![=]>()?;
28        let source_path = input.parse::<syn::LitStr>()?;
29
30        input.parse::<syn::Token![,]>()?;
31
32        input.parse::<name>()?;
33        input.parse::<syn::Token![=]>()?;
34        let name = input.parse::<syn::LitStr>()?;
35
36        let mut clock_port = None;
37        let mut reset_port = None;
38        while input.peek(syn::Token![,]) {
39            input.parse::<syn::Token![,]>()?;
40
41            let lookahead = input.lookahead1();
42            if lookahead.peek(clock) {
43                input.parse::<clock>()?;
44                input.parse::<syn::Token![=]>()?;
45                clock_port = Some(input.parse::<syn::LitStr>()?);
46            } else if lookahead.peek(reset) {
47                input.parse::<reset>()?;
48                input.parse::<syn::Token![=]>()?;
49                reset_port = Some(input.parse::<syn::LitStr>()?);
50            } else {
51                return Err(lookahead.error());
52            }
53        }
54
55        Ok(Self {
56            source_path,
57            name,
58            clock_port,
59            reset_port,
60        })
61    }
62}
63
64pub fn build_verilated_struct(
65    macro_name: &str,
66    top_name: syn::LitStr,
67    source_path: syn::LitStr,
68    verilog_ports: Vec<(String, usize, usize, PortDirection)>,
69    clock_port: Option<syn::LitStr>,
70    reset_port: Option<syn::LitStr>,
71    item: TokenStream,
72) -> TokenStream {
73    let crate_name = format_ident!("{}", macro_name);
74    let item = match syn::parse::<syn::ItemStruct>(item.into()) {
75        Ok(item) => item,
76        Err(error) => {
77            return error.into_compile_error();
78        }
79    };
80
81    let mut struct_members = vec![];
82
83    let mut preeval_impl = vec![];
84    let mut posteval_impl = vec![];
85
86    let mut other_impl = vec![];
87
88    let mut verilated_model_ports_impl = vec![];
89    let mut verilated_model_init_impl = vec![];
90    let mut verilated_model_init_self = vec![];
91
92    verilated_model_init_impl.push(quote! {
93        let new_model: extern "C" fn() -> *mut #crate_name::__reexports::libc::c_void =
94            *unsafe { library.get(concat!("ffi_new_V", #top_name).as_bytes()) }
95                .expect("failed to get symbol");
96        let model = (new_model)();
97
98
99        let delete_model: extern "C" fn(*mut #crate_name::__reexports::libc::c_void) =
100            *unsafe { library.get(concat!("ffi_delete_V", #top_name).as_bytes()) }
101                .expect("failed to get symbol");
102
103        let eval_model: extern "C" fn(*mut #crate_name::__reexports::libc::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        drop_model: delete_model,
109        eval_model,
110        model,
111        _phantom: std::marker::PhantomData
112    });
113
114    for (port_name, port_msb, port_lsb, port_direction) in verilog_ports {
115        if port_name.chars().any(|c| c == '\\' || c == ' ') {
116            return syn::Error::new_spanned(
117                top_name,
118                "Escaped module names are not supported",
119            )
120            .into_compile_error();
121        }
122
123        let port_width = port_msb + 1 - port_lsb;
124
125        let port_type = if port_width <= 8 {
126            quote! { #crate_name::__reexports::verilator::types::CData }
127        } else if port_width <= 16 {
128            quote! { #crate_name::__reexports::verilator::types::SData }
129        } else if port_width <= 32 {
130            quote! { #crate_name::__reexports::verilator::types::IData }
131        } else if port_width <= 64 {
132            quote! { #crate_name::__reexports::verilator::types::QData }
133        } else {
134            return syn::Error::new_spanned(
135                source_path,
136                format!(
137                    "Port `{}` is wider than supported right now",
138                    port_name
139                ),
140            )
141            .into_compile_error();
142        };
143
144        let port_name_ident = format_ident!("{}", port_name);
145        struct_members.push(quote! {
146            pub #port_name_ident: #port_type
147        });
148        verilated_model_init_self.push(quote! {
149            #port_name_ident: 0 as _
150        });
151
152        match port_direction {
153            PortDirection::Input => {
154                let setter = format_ident!("pin_{}", port_name);
155                struct_members.push(quote! {
156                            #setter: extern "C" fn(*mut #crate_name::__reexports::libc::c_void, #port_type)
157                        });
158                preeval_impl.push(quote! {
159                    (self.#setter)(self.model, self.#port_name_ident);
160                });
161
162                if let Some(clock_port) = &clock_port {
163                    if clock_port.value().as_str() == port_name {
164                        other_impl.push(quote! {
165                            pub fn tick(&mut self) {
166                                self.#port_name = 1 as _;
167                                self.eval();
168                                self.#port_name = 0 as _;
169                                self.eval();
170                            }
171                        });
172                    }
173                }
174
175                if let Some(_reset_port) = &reset_port {
176                    todo!("reset ports");
177                }
178
179                verilated_model_init_impl.push(quote! {
180                            let #setter: extern "C" fn(*mut #crate_name::__reexports::libc::c_void, #port_type) =
181                                *unsafe { library.get(concat!("ffi_V", #top_name, "_pin_", #port_name).as_bytes()) }
182                                    .expect("failed to get symbol");
183                        });
184                verilated_model_init_self.push(quote! { #setter });
185            }
186            PortDirection::Output => {
187                let getter = format_ident!("read_{}", port_name);
188                struct_members.push(quote! {
189                            #getter: extern "C" fn(*mut #crate_name::__reexports::libc::c_void) -> #port_type
190                        });
191                posteval_impl.push(quote! {
192                    self.#port_name_ident = (self.#getter)(self.model);
193                });
194
195                verilated_model_init_impl.push(quote! {
196                            let #getter: extern "C" fn(*mut #crate_name::__reexports::libc::c_void) -> #port_type =
197                                *unsafe { library.get(concat!("ffi_V", #top_name, "_read_", #port_name).as_bytes()) }
198                                    .expect("failed to get symbol");
199                        });
200                verilated_model_init_self.push(quote! { #getter });
201            }
202            _ => todo!("Unhandled port direction"),
203        }
204
205        let verilated_model_port_direction = match port_direction {
206            PortDirection::Input => {
207                quote! { #crate_name::__reexports::verilator::PortDirection::Input }
208            }
209            PortDirection::Output => {
210                quote! { #crate_name::__reexports::verilator::PortDirection::Output }
211            }
212            _ => todo!("Other port directions"),
213        };
214
215        verilated_model_ports_impl.push(quote! {
216            (#port_name, #port_msb, #port_lsb, #verilated_model_port_direction)
217        });
218    }
219
220    struct_members.push(quote! {
221        drop_model: extern "C" fn(*mut #crate_name::__reexports::libc::c_void),
222        eval_model: extern "C" fn(*mut #crate_name::__reexports::libc::c_void)
223    });
224
225    let struct_name = item.ident;
226    let vis = item.vis;
227    let port_count = verilated_model_ports_impl.len();
228    quote! {
229        #vis struct #struct_name<'ctx> {
230            #(#struct_members),*,
231            #[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."]
232            model: *mut #crate_name::__reexports::libc::c_void,
233            _phantom: std::marker::PhantomData<&'ctx ()>
234        }
235
236        impl #struct_name<'_> {
237            pub fn eval(&mut self) {
238                #(#preeval_impl)*
239                (self.eval_model)(self.model);
240                #(#posteval_impl)*
241            }
242
243            #(#other_impl)*
244        }
245
246        impl<'ctx> std::ops::Drop for #struct_name<'ctx> {
247            fn drop(&mut self) {
248                (self.drop_model)(self.model);
249                self.model = std::ptr::null_mut();
250            }
251        }
252
253        impl<'ctx> #crate_name::__reexports::verilator::VerilatedModel for #struct_name<'ctx> {
254            fn name() -> &'static str {
255                #top_name
256            }
257
258            fn source_path() -> &'static str {
259                #source_path
260            }
261
262            fn ports() -> &'static [(&'static str, usize, usize, #crate_name::__reexports::verilator::PortDirection)] {
263                static PORTS: [(&'static str, usize, usize, #crate_name::__reexports::verilator::PortDirection); #port_count] = [#(#verilated_model_ports_impl),*];
264                &PORTS
265            }
266
267            fn init_from(library: &#crate_name::__reexports::libloading::Library) -> Self {
268                #(#verilated_model_init_impl)*
269                Self {
270                    #(#verilated_model_init_self),*
271                }
272            }
273        }
274    }
275}