marlin_veryl_macro/
lib.rs1use std::{env, fs, iter, str};
8
9use camino::Utf8PathBuf;
10use marlin_verilator::PortDirection;
11use marlin_verilog_macro_builder::{MacroArgs, build_verilated_struct};
12use proc_macro::TokenStream;
13use veryl_parser::{
14 Parser,
15 veryl_grammar_trait::{
16 FactorTypeGroup, FirstToken, ModuleDeclaration,
17 PortDeclarationGroupGroup, PortDeclarationItemGroup, ScalarTypeGroup,
18 },
19 veryl_walker::VerylWalker,
20};
21
22fn search_for_veryl_toml(mut start: Utf8PathBuf) -> Option<Utf8PathBuf> {
23 while start.parent().is_some() {
24 if start.join("Veryl.toml").is_file() {
25 return Some(start.join("Veryl.toml"));
26 }
27 start.pop();
28 }
29 None
30}
31
32struct ModuleFinder<'args, 'source> {
33 args: &'args MacroArgs,
34 source_code: &'source str,
35 look_for: String,
36 found: Option<Vec<(String, usize, usize, PortDirection)>>,
37 error: Option<syn::Error>,
38}
39
40impl VerylWalker for ModuleFinder<'_, '_> {
41 fn module_declaration(&mut self, module: &ModuleDeclaration) {
42 let name_token = &module.identifier.identifier_token.token;
43 if &self.source_code.as_bytes()[name_token.pos as usize
44 ..(name_token.pos + name_token.length) as usize]
45 == self.look_for.as_bytes()
46 {
47 if let Some(port_declarations) = module
48 .module_declaration_opt2
49 .as_ref()
50 .and_then(|opt2| {
51 opt2.port_declaration.port_declaration_opt.as_ref()
52 })
53 .map(|opt| &opt.port_declaration_list)
54 {
55 let veryl_ports =
56 iter::once(&port_declarations.port_declaration_group)
57 .chain(
58 port_declarations
59 .port_declaration_list_list
60 .iter()
61 .map(|after_first| {
62 &after_first.port_declaration_group
63 }),
64 ).filter_map(|group|
65 match &*group.port_declaration_group_group {
66 PortDeclarationGroupGroup::LBracePortDeclarationListRBrace(_) => None,
67 PortDeclarationGroupGroup::PortDeclarationItem(port_declaration_group_group_port_declaration_item) => Some(port_declaration_group_group_port_declaration_item),
68 }
69 ).map(|item| {
70
71 let item = &item.port_declaration_item;
72 let port_name_token = item.identifier.identifier_token.token;
73 let port_name_bytes = &self.source_code.as_bytes()[port_name_token.pos as usize..(port_name_token.pos + port_name_token.length) as usize];
74 let port_name_str = str::from_utf8(port_name_bytes).expect("Veryl bug: Veryl identifier had invalid byte range (invalid UTF-8)");
75
76 (port_name_str, &item.port_declaration_item_group)
77 });
78
79 let mut ports = vec![];
80 for (port_name, port_type) in veryl_ports {
81 match &**port_type {
82 PortDeclarationItemGroup::PortTypeConcrete(
83 port_declaration_item_group_port_type_concrete,
84 ) => {
85 let concrete_type =
86 &port_declaration_item_group_port_type_concrete
87 .port_type_concrete;
88
89 let port_direction = match &*concrete_type.direction {
90 veryl_parser::veryl_grammar_trait::Direction::Input(_) => PortDirection::Input,
91 veryl_parser::veryl_grammar_trait::Direction::Output(_) => PortDirection::Output,
92 veryl_parser::veryl_grammar_trait::Direction::Inout(_) => PortDirection::Inout,
93 veryl_parser::veryl_grammar_trait::Direction::Ref(_) => {
94 self.error = Some(syn::Error::new_spanned(&self.args.name, format!("`{port_name}` is a ref port, which is currently not supported")));
95 return;
96 },
97 veryl_parser::veryl_grammar_trait::Direction::Modport(_) => {
98 self.error = Some(syn::Error::new_spanned(&self.args.name, format!("`{port_name}` is a modport, which is currently not supported")));
99 return;
100 }
101 veryl_parser::veryl_grammar_trait::Direction::Import(_) => {
102 self.error = Some(syn::Error::new_spanned(&self.args.name, format!("`{port_name}` is an import port, which is currently not supported")));
103 return;
104 }
105 };
106
107 if concrete_type.array_type.array_type_opt.is_some()
108 {
109 self.error = Some(syn::Error::new_spanned(
110 &self.args.name,
111 format!(
112 "`{port_name}` is an array, which is currently not supported"
113 ),
114 ));
115 return;
116 }
117
118 let port_width = match &*concrete_type.array_type.scalar_type.scalar_type_group {
119 ScalarTypeGroup::UserDefinedTypeScalarTypeOpt(_scalar_type_group_user_defined_type_scalar_type_opt) => todo!("What is UserDefinedTypeScalarTypeOpt"),
120 ScalarTypeGroup::FactorType(scalar_type_group_factor_type) => {
121 match &*scalar_type_group_factor_type.factor_type.factor_type_group {
122 FactorTypeGroup::VariableTypeFactorTypeOpt(factor_type_group_variable_type_factor_type_opt) => {
123 if let Some(factor_type) = factor_type_group_variable_type_factor_type_opt.factor_type_opt.as_ref() {
124 factor_type.width.expression.token().to_string().parse::<usize>().expect("Veryl bug: parsed number but cannot convert to usize") - 1
125 } else {
135 1
136 }
137 },
152 FactorTypeGroup::FixedType(_factor_type_group_fixed_type) => {
153 todo!("What is FactorTypeGroup::FixedType?")
154 },
164 }
165 }
166 };
167
168 ports.push((
169 port_name.to_string(),
170 port_width,
171 0,
172 port_direction,
173 ));
174 }
175 PortDeclarationItemGroup::PortTypeAbstract(_) => {
176 self.error = Some(syn::Error::new_spanned(
177 &self.args.name,
178 format!(
179 "Port `{port_name}` has abstract type and therefore cannot be interfaced with"
180 ),
181 ));
182 return;
183 }
184 }
185 }
186
187 self.found = Some(ports);
188 }
189 }
190 }
191}
192
193#[proc_macro_attribute]
194pub fn veryl(args: TokenStream, item: TokenStream) -> TokenStream {
195 let args = syn::parse_macro_input!(args as MacroArgs);
196
197 let manifest_directory = Utf8PathBuf::from(
198 env::var("CARGO_MANIFEST_DIR").expect("Please use CARGO"),
199 );
200 let Some(veryl_toml_path) = search_for_veryl_toml(manifest_directory)
201 else {
202 return syn::Error::new_spanned(
203 args.source_path,
204 "Could not find Veryl.toml",
205 )
206 .into_compile_error()
207 .into();
208 };
209
210 let veryl_source_path = {
211 let mut veryl_source_path = veryl_toml_path.clone();
212 veryl_source_path.pop();
213 veryl_source_path.join(args.source_path.value())
214 };
215 let source_code = match fs::read_to_string(&veryl_source_path) {
216 Ok(contents) => contents,
217 Err(error) => {
218 return syn::Error::new_spanned(
219 &args.source_path,
220 format!(
221 "Failed to read source code file at {veryl_source_path}: {error}"
222 ),
223 )
224 .into_compile_error()
225 .into();
226 }
227 };
228
229 let parser = match Parser::parse(&source_code, &veryl_source_path) {
230 Ok(parser) => parser,
231 Err(error) => {
232 return syn::Error::new_spanned(
233 &args.source_path,
234 format!(
235 "[veryl-parser] Failed to parser source code file at {veryl_source_path}: {error}"
236 ),
237 )
238 .into_compile_error()
239 .into();
240 }
241 };
242
243 let mut module_finder = ModuleFinder {
244 args: &args,
245 source_code: &source_code,
246 look_for: args.name.value(),
247 found: None,
248 error: None,
249 };
250 module_finder.veryl(&parser.veryl);
251
252 let ports = if let Some(ports) = module_finder.found {
253 ports
254 } else {
255 return module_finder.error.expect("Marlin bug for Veryl integration: ModuleFinder exited without ports or error").into_compile_error().into();
256 };
257
258 let verilog_source_path = syn::LitStr::new(
259 veryl_source_path.with_extension("sv").as_str(),
260 args.source_path.span(),
261 );
262
263 let veryl_toml_contents = match fs::read_to_string(&veryl_toml_path) {
268 Ok(contents) => contents,
269 Err(error) => {
270 return syn::Error::new_spanned(&args.source_path, format!("Could not read contents of Veryl.toml at project root {veryl_toml_path}: {error}")).into_compile_error().into();
271 }
272 };
273
274 let veryl_toml: toml::Value = match toml::from_str(&veryl_toml_contents) {
275 Ok(toml) => toml,
276 Err(error) => {
277 return syn::Error::new_spanned(&args.source_path, format!("Could not parse contents of Veryl.toml at project root {veryl_toml_path} as a TOML file: {error}")).into_compile_error().into();
278 }
279 };
280
281 let Some(project_name) = veryl_toml
282 .get("project")
283 .and_then(|project| project.get("name"))
284 .and_then(|name| name.as_str())
285 else {
286 return syn::Error::new_spanned(&args.source_path, format!("Could not read the project.name field of Veryl.toml at project root {veryl_toml_path}")).into_compile_error().into();
287 };
288
289 let verilog_module_name = syn::LitStr::new(
290 &format!("{}_{}", project_name, args.name.value()),
291 args.name.span(),
292 );
293
294 build_verilated_struct(
295 "veryl",
296 verilog_module_name,
297 verilog_source_path,
298 ports,
299 item.into(),
300 )
301 .into()
302}