Skip to main content

marlin_verilog_macro/
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::{env, fmt, path::PathBuf};
8
9use marlin_verilog_macro_builder::{
10    MacroArgs, build_verilated_struct, parse_verilog_ports,
11};
12use proc_macro::TokenStream;
13use quote::{format_ident, quote};
14use syn::{parse_macro_input, spanned::Spanned};
15
16#[proc_macro_attribute]
17pub fn verilog(args: TokenStream, item: TokenStream) -> TokenStream {
18    let args = syn::parse_macro_input!(args as MacroArgs);
19
20    let manifest_directory = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("Please compile using `cargo` or set the `CARGO_MANIFEST_DIR` environment variable"));
21    let source_path = manifest_directory.join(args.source_path.value());
22
23    let ports = match parse_verilog_ports(
24        &args.name,
25        &args.source_path,
26        &source_path,
27    ) {
28        Ok(ports) => ports,
29        Err(error) => {
30            return error.into();
31        }
32    };
33
34    build_verilated_struct(
35        "verilog",
36        args.name,
37        syn::LitStr::new(
38            source_path.to_string_lossy().as_ref(),
39            args.source_path.span(),
40        ),
41        ports,
42        item.into(),
43    )
44    .into()
45}
46
47enum DPIPrimitiveType {
48    Bool,
49    U8,
50    U16,
51    U32,
52    U64,
53    I8,
54    I16,
55    I32,
56    I64,
57}
58
59impl fmt::Display for DPIPrimitiveType {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        match self {
62            DPIPrimitiveType::Bool => "bool",
63            DPIPrimitiveType::U8 => "u8",
64            DPIPrimitiveType::U16 => "u16",
65            DPIPrimitiveType::U32 => "u32",
66            DPIPrimitiveType::U64 => "u64",
67            DPIPrimitiveType::I8 => "i8",
68            DPIPrimitiveType::I16 => "i16",
69            DPIPrimitiveType::I32 => "i32",
70            DPIPrimitiveType::I64 => "i64",
71        }
72        .fmt(f)
73    }
74}
75
76impl DPIPrimitiveType {
77    fn as_c(&self) -> &'static str {
78        match self {
79            DPIPrimitiveType::Bool => "svBit",
80            DPIPrimitiveType::U8 => "uint8_t",
81            DPIPrimitiveType::U16 => "uint16_t",
82            DPIPrimitiveType::U32 => "uint32_t",
83            DPIPrimitiveType::U64 => "uint64_t",
84            DPIPrimitiveType::I8 => "int8_t",
85            DPIPrimitiveType::I16 => "int16_t",
86            DPIPrimitiveType::I32 => "int32_t",
87            DPIPrimitiveType::I64 => "int64_t",
88        }
89    }
90}
91
92fn parse_dpi_primitive_type(
93    ty: &syn::TypePath,
94) -> Result<DPIPrimitiveType, syn::Error> {
95    if let Some(qself) = &ty.qself {
96        return Err(syn::Error::new_spanned(
97            qself.lt_token,
98            "Primitive integer type should not be qualified in DPI function",
99        ));
100    }
101
102    match ty
103        .path
104        .require_ident()
105        .or(Err(syn::Error::new_spanned(
106            ty,
107            "Primitive integer type should not have multiple path segments",
108        )))?
109        .to_string()
110        .as_str()
111    {
112        "bool" => Ok(DPIPrimitiveType::Bool),
113        "u8" => Ok(DPIPrimitiveType::U8),
114        "u16" => Ok(DPIPrimitiveType::U16),
115        "u32" => Ok(DPIPrimitiveType::U32),
116        "u64" => Ok(DPIPrimitiveType::U64),
117        "i8" => Ok(DPIPrimitiveType::I8),
118        "i16" => Ok(DPIPrimitiveType::I16),
119        "i32" => Ok(DPIPrimitiveType::I32),
120        "i64" => Ok(DPIPrimitiveType::I64),
121        _ => Err(syn::Error::new_spanned(
122            ty,
123            "Unknown primitive integer type",
124        )),
125    }
126}
127
128enum DPIType {
129    Input(DPIPrimitiveType),
130    /// Veriltor handles output and inout types the same
131    Inout(DPIPrimitiveType),
132}
133
134fn parse_dpi_type(ty: &syn::Type) -> Result<DPIType, syn::Error> {
135    match ty {
136        syn::Type::Path(type_path) => {
137            Ok(DPIType::Input(parse_dpi_primitive_type(type_path)?))
138        }
139        syn::Type::Reference(syn::TypeReference {
140            and_token,
141            lifetime,
142            mutability,
143            elem,
144        }) => {
145            if mutability.is_none() {
146                return Err(syn::Error::new_spanned(
147                    and_token,
148                    "DPI output or inout type must be represented with a mutable reference",
149                ));
150            }
151            if let Some(lifetime) = lifetime {
152                return Err(syn::Error::new_spanned(
153                    lifetime,
154                    "DPI output or inout type cannot use lifetimes",
155                ));
156            }
157
158            let syn::Type::Path(type_path) = elem.as_ref() else {
159                return Err(syn::Error::new_spanned(
160                    elem,
161                    "DPI output or inout type must be a mutable reference to a primitive integer type",
162                ));
163            };
164            Ok(DPIType::Inout(parse_dpi_primitive_type(type_path)?))
165        }
166        other => Err(syn::Error::new_spanned(
167            other,
168            "This type is not supported in DPI. Please use primitive integers or mutable references to them",
169        )),
170    }
171}
172
173/// Marlin allows you to import Rust functions into (System)Verilog over DPI.
174/// The function must have "C" linkage and be imported into SystemVerilog with
175/// "DPI-C" linkage.
176///
177/// For example:
178/// ```ignore
179/// // in Rust
180/// #[verilog::dpi]
181/// pub extern "C" fn three(out: &mut u32) {
182///     *out = 3;
183/// }
184/// ```
185/// ```systemverilog
186/// // in SystemVerilog
187/// import "DPI-C" function void three(output int out);
188/// ```
189///
190/// The Rust function can only take in primitive integer types at or below
191/// 64-bit width and booleans. The order and count of parameters must correspond
192/// exactly with the SystemVerilog import declaration.
193///
194/// Any `input` parameter on the Verilog side should correspond to a plain
195/// argument on the Rust side. Any `output` or `inout` parmaeter on the Verilog
196/// side should corresponding to a mutable reference on the Rust side. Note that
197/// the names of parmaeters on either side are irrelevant and need not
198/// correspond.
199///
200/// Here are some examples:
201///
202/// | SystemVerilog parameter | Rust parameter |
203/// | --- | --- |
204/// | `output int foo` | `foo: &mut i32` |
205/// | `input bit bar` | `bar: bool` |
206///
207/// ## More
208///
209/// Please reference the [Verilator docs on DPI](https://verilator.org/guide/latest/connecting.html#direct-programming-interface-dpi) for further information.
210/// You can also see the [corresponding page in the Marlin handbook](https://www.ethanuppal.com/marlin/verilog/dpi.html).
211#[proc_macro_attribute]
212pub fn dpi(_args: TokenStream, item: TokenStream) -> TokenStream {
213    let item_fn = parse_macro_input!(item as syn::ItemFn);
214
215    if !matches!(item_fn.vis, syn::Visibility::Public(_)) {
216        return syn::Error::new_spanned(
217            item_fn.vis,
218            "Marking the function `pub` is required to expose this Rust function to C",
219        )
220        .into_compile_error()
221        .into();
222    }
223
224    let Some(abi) = &item_fn.sig.abi else {
225        return syn::Error::new_spanned(
226            item_fn,
227            "`extern \"C\"` is required to expose this Rust function to C",
228        )
229        .into_compile_error()
230        .into();
231    };
232
233    if !abi
234        .name
235        .as_ref()
236        .map(|name| name.value().as_str() == "C")
237        .unwrap_or(true)
238    {
239        return syn::Error::new_spanned(
240            item_fn,
241            "You must specify the C ABI for the `extern` marking",
242        )
243        .into_compile_error()
244        .into();
245    }
246
247    if item_fn.sig.generics.lt_token.is_some() {
248        return syn::Error::new_spanned(
249            item_fn.sig.generics,
250            "Generics are not supported for DPI functions",
251        )
252        .into_compile_error()
253        .into();
254    }
255
256    if let Some(asyncness) = &item_fn.sig.asyncness {
257        return syn::Error::new_spanned(
258            asyncness,
259            "DPI functions must be synchronous",
260        )
261        .into_compile_error()
262        .into();
263    }
264
265    if let syn::ReturnType::Type(_, return_type) = &item_fn.sig.output {
266        return syn::Error::new_spanned(
267            return_type,
268            "DPI functions cannot have a return value",
269        )
270        .into_compile_error()
271        .into();
272    }
273
274    let ports =
275        match item_fn
276            .sig
277            .inputs
278            .iter()
279            .try_fold(vec![], |mut ports, input| {
280                let syn::FnArg::Typed(parameter) = input else {
281                    return Err(syn::Error::new_spanned(
282                        input,
283                        "Invalid parameter on DPI function",
284                    ));
285                };
286
287                let syn::Pat::Ident(name) = &*parameter.pat else {
288                    return Err(syn::Error::new_spanned(
289                        parameter,
290                        "Function argument must be an identifier",
291                    ));
292                };
293
294                let attrs = parameter.attrs.clone();
295                ports.push((name, attrs, parse_dpi_type(&parameter.ty)?));
296                Ok(ports)
297            }) {
298            Ok(ports) => ports,
299            Err(error) => {
300                return error.into_compile_error().into();
301            }
302        };
303
304    let attributes = item_fn.attrs;
305    let function_name = item_fn.sig.ident;
306    let body = item_fn.block;
307
308    let struct_name = format_ident!("__DPI_{}", function_name);
309
310    let mut parameter_types = vec![];
311    let mut parameters = vec![];
312
313    for (name, attributes, dpi_type) in &ports {
314        let parameter_type = match dpi_type {
315            DPIType::Input(inner) => {
316                let type_ident = format_ident!("{}", inner.to_string());
317                quote! { #type_ident }
318            }
319            DPIType::Inout(inner) => {
320                let type_ident = format_ident!("{}", inner.to_string());
321                quote! { *mut #type_ident }
322            }
323        };
324        parameter_types.push(parameter_type.clone());
325        parameters.push(quote! {
326            #(#attributes)* #name: #parameter_type
327        });
328    }
329
330    let preamble =
331        ports
332            .iter()
333            .filter_map(|(name, _, dpi_type)| match dpi_type {
334                DPIType::Inout(_) => Some(quote! {
335                    let #name = unsafe { &mut *#name };
336                }),
337                _ => None,
338            });
339
340    let function_name_literal = syn::LitStr::new(
341        function_name.to_string().as_str(),
342        function_name.span(),
343    );
344
345    let c_signature = ports
346        .iter()
347        .map(|(name, _, dpi_type)| {
348            let c_type = match dpi_type {
349                DPIType::Input(inner) => inner.as_c().to_string(),
350                DPIType::Inout(inner) => format!("{}*", inner.as_c()),
351            };
352            let name_literal =
353                syn::LitStr::new(name.ident.to_string().as_str(), name.span());
354            let type_literal = syn::LitStr::new(&c_type, name.span());
355            quote! {
356                (#name_literal, #type_literal)
357            }
358        })
359        .collect::<Vec<_>>();
360
361    quote! {
362        #[allow(non_camel_case_types)]
363        struct #struct_name;
364
365        impl #struct_name {
366            #(#attributes)*
367            pub extern "C" fn call(#(#parameters),*) {
368                #(#preamble)*
369                #body
370            }
371        }
372
373        impl verilog::__reexports::verilator::dpi::DpiFunction for #struct_name {
374            fn name(&self) -> &'static str {
375                #function_name_literal
376            }
377
378            fn signature(&self) -> &'static [(&'static str, &'static str)] {
379                &[#(#c_signature),*]
380            }
381
382            fn pointer(&self) -> *const std::ffi::c_void {
383                #struct_name::call as extern "C" fn(#(#parameter_types),*) as *const std::ffi::c_void
384            }
385        }
386
387        #[allow(non_upper_case_globals)]
388        pub static #function_name: &'static dyn verilog::__reexports::verilator::dpi::DpiFunction = &#struct_name;
389    }
390    .into()
391}