Skip to main content

marlin_veryl_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, 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                                            //match &*factor_type.width.expression fixed_type {
126                                            //    FixedType::U32(fixed_type_u32) => &fixed_type_u32.u32.u32_token,
127                                            //    FixedType::U64(fixed_type_u64) => &fixed_type_u64.u64.u64_token,
128                                            //    FixedType::I32(fixed_type_i32) => &fixed_type_i32.i32.i32_token,
129                                            //    FixedType::I64(fixed_type_i64) => &fixed_type_i64.i64.i64_token,
130                                            //    FixedType::F32(_)|
131                                            //    FixedType::F64(_) => todo!("Cannot use float as width"),
132                                            //    FixedType::Strin(_) => todo!("Cannot use string as width"),
133                                            //}.to_string().parse::<usize>().expect("Veryl bug: parsed number but cannot convert to usize")
134                                            } else {
135                                                1
136                                            }
137                                            //match &*factor_type_group_variable_type_factor_type_opt.variable_type {
138                                            //    VariableType::Logic(variable_type_logic) => {
139                                            //
140                                            //    },
141                                            //    _ => {
142                                            //        self.error = Some(syn::Error::new_spanned(
143                                            //            &self.args.name,
144                                            //            format!(
145                                            //                "`{port_name}` does not have logic type, and only logic is supported right now"
146                                            //            ),
147                                            //        ));
148                                            //        return;
149                                            //    }
150                                            //}
151                                        },
152                                        FactorTypeGroup::FixedType(_factor_type_group_fixed_type) => {
153                                            todo!("What is FactorTypeGroup::FixedType?")
154                                            //match &*factor_type_group_fixed_type.fixed_type {
155                                            //    FixedType::U32(fixed_type_u32) => &fixed_type_u32.u32.u32_token,
156                                            //    FixedType::U64(fixed_type_u64) => &fixed_type_u64.u64.u64_token,
157                                            //    FixedType::I32(fixed_type_i32) => &fixed_type_i32.i32.i32_token,
158                                            //    FixedType::I64(fixed_type_i64) => &fixed_type_i64.i64.i64_token,
159                                            //    FixedType::F32(_)|
160                                            //    FixedType::F64(_) => todo!("Cannot use float as width"),
161                                            //    FixedType::Strin(_) => todo!("Cannot use string as width"),
162                                            //}.to_string().parse::<usize>().expect("Veryl bug: parsed number but cannot convert to usize")
163                                        },
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 verilog_module_prefix = veryl_source_path
264    //    .file_stem()
265    //    .map(|stem| format!("{}_", stem))
266    //    .unwrap_or_default();
267    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}