1use 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
42pub struct VerylRuntimeOptions {
45 pub veryl_executable: OsString,
48
49 pub call_veryl_build: bool,
53
54 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
68pub struct VerylRuntime {
70 verilator_runtime: VerilatorRuntime,
71}
72
73impl VerylRuntime {
74 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 pub fn create_model<'ctx, M: AsVerilatedModel<'ctx>>(
165 &'ctx self,
166 ) -> Result<M, Whatever> {
167 self.verilator_runtime.create_model_simple()
168 }
169}