1use 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 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#[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(¶meter.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}