Skip to main content

marlin_veryl/
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::current_dir, ffi::OsString, fs, process::Command};
8
9use camino::Utf8PathBuf;
10use marlin_verilator::{
11    AsVerilatedModel, VerilatorRuntime, VerilatorRuntimeOptions,
12    eprintln_nocapture,
13};
14use owo_colors::OwoColorize;
15use snafu::{OptionExt, ResultExt, Whatever, whatever};
16
17#[doc(hidden)]
18pub mod __reexports {
19    pub use libloading;
20    pub use marlin_verilator as verilator;
21}
22
23pub mod prelude {
24    pub use crate as veryl;
25    pub use crate::{VerylRuntime, VerylRuntimeOptions};
26    pub use marlin_verilator::{AsDynamicVerilatedModel, AsVerilatedModel};
27    pub use marlin_veryl_macro::veryl;
28}
29
30const VERYL_TOML: &str = "Veryl.toml";
31
32fn search_for_veryl_toml(mut start: Utf8PathBuf) -> Option<Utf8PathBuf> {
33    while start.parent().is_some() {
34        if start.join(VERYL_TOML).is_file() {
35            return Some(start.join(VERYL_TOML));
36        }
37        start.pop();
38    }
39    None
40}
41
42/// Optional configuration for creating a [`VerylRuntime`]. Usually, you can
43/// just use [`VerylRuntimeOptions::default()`].
44pub struct VerylRuntimeOptions {
45    /// The name of the `veryl` executable, interpreted in some way by the
46    /// OS/shell.
47    pub veryl_executable: OsString,
48
49    /// Whether `veryl build` should be automatically called. This switch is
50    /// useful to disable when, for example, another tool has already
51    /// called `veryl build`.
52    pub call_veryl_build: bool,
53
54    /// See [`VerilatorRuntimeOptions`].
55    pub verilator_options: VerilatorRuntimeOptions,
56}
57
58impl Default for VerylRuntimeOptions {
59    fn default() -> Self {
60        Self {
61            veryl_executable: "veryl".into(),
62            call_veryl_build: false,
63            verilator_options: VerilatorRuntimeOptions::default(),
64        }
65    }
66}
67
68/// Runtime for Veryl code.
69pub struct VerylRuntime {
70    verilator_runtime: VerilatorRuntime,
71}
72
73impl VerylRuntime {
74    /// Creates a new runtime for instantiating Veryl units as Rust objects.
75    /// Does NOT call `veryl build` by defaul because `veryl build` is not
76    /// thread safe. You can enable this with [`VerylRuntimeOptions`] or just
77    /// run it beforehand.
78    pub fn new(options: VerylRuntimeOptions) -> Result<Self, Whatever> {
79        if options.verilator_options.log {
80            log::info!("Searching for Veryl project root");
81        }
82        let Some(veryl_toml_path) = search_for_veryl_toml(
83            current_dir()
84                .whatever_context("Failed to get current directory")?
85                .try_into()
86                .whatever_context(
87                    "Failed to convert current directory to UTF-8",
88                )?,
89        ) else {
90            whatever!(
91                "Failed to find {VERYL_TOML} searching from current directory"
92            );
93        };
94        let mut veryl_project_path = veryl_toml_path.clone();
95        veryl_project_path.pop();
96
97        if options.call_veryl_build {
98            if options.verilator_options.log {
99                log::info!("Invoking `veryl build` (this may take a while)");
100            }
101
102            let veryl_toml_contents = fs::read_to_string(&veryl_toml_path)
103                .whatever_context(format!(
104                "Failed to read contents of {VERYL_TOML} at {veryl_toml_path}"
105            ))?;
106            let veryl_toml: toml::Value = toml::from_str(&veryl_toml_contents)
107                .whatever_context(format!(
108                    "Failed to parse {VERYL_TOML} as a valid TOML file"
109                ))?;
110            let veryl_project_name = veryl_toml
111                .get("project")
112                .and_then(|project| project.as_table())
113                .and_then(|project| project.get("name"))
114                .and_then(|name| name.as_str())
115                .whatever_context(format!(
116                    "{VERYL_TOML} missing `project.name` field"
117                ))?;
118
119            eprintln_nocapture!(
120                "{} {veryl_project_name} ({veryl_project_path})",
121                "   Compiling".bold().green()
122            )?;
123
124            let veryl_output = Command::new(options.veryl_executable)
125                .arg("build")
126                .current_dir(&veryl_project_path)
127                .output()
128                .whatever_context("Invocation of veryl failed")?;
129
130            if !veryl_output.status.success() {
131                whatever!(
132                    "Invocation of veryl failed with {}\n\n--- STDOUT ---\n{}\n\n--- STDERR ---\n{}",
133                    veryl_output.status,
134                    String::from_utf8(veryl_output.stdout).unwrap_or_default(),
135                    String::from_utf8(veryl_output.stderr).unwrap_or_default()
136                );
137            }
138        }
139
140        let mut verilog_source_files = vec![];
141        for file in veryl_project_path.join("src").read_dir_utf8().whatever_context("Failed to read contents of the src/ folder under the Veryl project root")?.flatten() {
142            if file.path().extension().map(|extension| extension == "sv").unwrap_or(false) {
143               verilog_source_files.push(file.path().to_path_buf());
144            }
145        }
146        let verilog_source_files_ref = verilog_source_files
147            .iter()
148            .map(|path_buf| path_buf.as_path())
149            .collect::<Vec<_>>();
150
151        Ok(Self {
152            verilator_runtime: VerilatorRuntime::new(
153                &veryl_project_path.join("dependencies/whatever"),
154                &verilog_source_files_ref,
155                &[],
156                [],
157                options.verilator_options,
158            )?,
159        })
160    }
161
162    /// Instantiates a new Veryl module. This function simply wraps
163    /// [`VerilatorRuntime::create_model`].
164    pub fn create_model<'ctx, M: AsVerilatedModel<'ctx>>(
165        &'ctx self,
166    ) -> Result<M, Whatever> {
167        self.verilator_runtime.create_model_simple()
168    }
169}