pub mod attr {
pub use ferrilator_macros::ferrilate;
}
use ferrilator_core::DataType;
use ferrilator_core::Module;
use ferrilator_core::err;
use std::io::Read;
use std::io::Write;
pub fn build(name: &str, rust_file: &str, verilog_files: &[&str]) -> err::Result<()> {
for fname in verilog_files {
if !std::fs::exists(fname)? {
return err::input!("file {fname} does not exist");
}
}
let item = load_struct(name, rust_file)?;
let module_name = read_module_name(&item)?;
let module = Module::from_struct(module_name.clone(), item)?;
let out_dir = std::env::var("OUT_DIR").unwrap();
let verilated_dir = format!("{out_dir}/{module_name}_verilated");
let binding_src = format!("{verilated_dir}/{module_name}_binding.cc");
write_binding_file(&module_name, &binding_src, &module)?;
check_process_output(
"verilator",
std::process::Command::new("verilator")
.arg("--cc")
.arg("--build")
.args(["--top-module", &module_name])
.args(["--Mdir", &verilated_dir])
.args(verilog_files)
.arg(&binding_src)
.output()
.unwrap(),
);
let verilator_root = std::env::var("VERILATOR_ROOT").unwrap_or("/usr/share/verilator".into());
let verilator_include = format!("{verilator_root}/include");
let binding_obj = format!("{verilated_dir}/{module_name}_binding.o");
check_process_output(
"build binding file",
std::process::Command::new("g++")
.arg(&format!("-I{verilator_include}"))
.arg(&format!("-I{verilated_dir}"))
.args(&["-c", &binding_src])
.args(&["-o", &binding_obj])
.output()
.unwrap(),
);
let all_path = format!("{verilated_dir}/V{module_name}__ALL.a");
let module_path = format!("{verilated_dir}/libV{module_name}.a");
std::fs::copy(&all_path, &module_path).unwrap();
check_process_output(
"archive verilator runtime",
std::process::Command::new("ar")
.arg("rcs")
.arg(&module_path)
.arg(&format!("{verilated_dir}/{module_name}_binding.o"))
.arg(&binding_obj)
.output()
.unwrap(),
);
let verilated_src = format!("{verilator_include}/verilated.cpp");
let verilated_obj = format!("{verilated_dir}/verilated.o");
let runtime_path = format!("{verilated_dir}/libverilated.a");
if is_older(&runtime_path, &verilated_src) {
check_process_output(
"build verilator runtime",
std::process::Command::new("g++")
.arg(&format!("-I{verilator_include}"))
.args(&["-c", &verilated_src])
.args(&["-o", &verilated_obj])
.output()
.unwrap(),
);
check_process_output(
"archive verilator runtime",
std::process::Command::new("ar")
.arg("rcs")
.arg(&runtime_path)
.arg(&verilated_obj)
.output()
.unwrap(),
);
}
println!("cargo:rustc-link-search=native={verilated_dir}");
println!("cargo:rustc-link-lib=static=V{module_name}");
println!("cargo:rustc-link-lib=static=verilated");
println!("cargo:rustc-link-lib=dylib=stdc++");
for fname in verilog_files {
println!("cargo:rerun-if-changed={fname}");
}
Ok(())
}
fn is_older(lhs: &str, rhs: &str) -> bool {
if let Ok(lhs) = std::fs::metadata(lhs)
&& let Ok(rhs) = std::fs::metadata(rhs)
&& let Ok(lhs) = lhs.modified()
&& let Ok(rhs) = rhs.modified()
{
lhs < rhs
} else {
true
}
}
fn check_process_output(task: &str, out: std::process::Output) {
if !out.status.success() {
println!("{task} failed");
println!("--- stdout ---");
std::io::copy(&mut &out.stdout[..], &mut std::io::stdout()).unwrap();
println!("--- stderr ---");
std::io::copy(&mut &out.stderr[..], &mut std::io::stderr()).unwrap();
panic!("cannot proceed");
}
}
fn load_struct(name: &str, rust_file: &str) -> err::Result<syn::ItemStruct> {
let mut content = String::new();
let mut file = std::fs::File::open(rust_file)?;
file.read_to_string(&mut content)?;
let file = syn::parse_file(&content)?;
for item in file.items {
if let syn::Item::Struct(item) = item {
if item.ident.to_string() == name {
return Ok(item);
}
}
}
err::input!("failed to find struct defn for {name}")
}
fn read_module_name(item: &syn::ItemStruct) -> err::Result<String> {
for attr in &item.attrs {
match &attr.meta {
syn::Meta::List(attr) => match attr.path.segments.last() {
Some(seg) => {
if seg.ident.to_string() == "ferrilate" {
return Ok(attr.tokens.to_string());
}
}
None => {}
},
syn::Meta::Path(_) => {}
syn::Meta::NameValue(_) => {}
}
}
err::input!(
"struct {} has no 'ferrilate' attribute",
item.ident.to_string()
)
}
fn write_binding_file(module_name: &str, fname: &str, module: &Module) -> err::Result<()> {
if let Some(dir) = std::path::Path::new(fname).parent() {
std::fs::create_dir_all(dir)?;
}
let mut file = std::fs::File::create(fname)?;
writeln!(file, "#include <V{module_name}.h>")?;
writeln!(file)?;
writeln!(file, "extern \"C\" {{")?;
writeln!(file, "V{module_name}* {module_name}_new() {{")?;
writeln!(file, " return new V{module_name};")?;
writeln!(file, "}}")?;
writeln!(file, "void {module_name}_del(V{module_name}* dut) {{")?;
writeln!(file, " delete dut;")?;
writeln!(file, "}}")?;
writeln!(file, "void {module_name}_eval(V{module_name}* dut) {{")?;
writeln!(file, " dut->eval();")?;
writeln!(file, "}}")?;
for port in module.ports() {
let port_name = &port.name();
if port.data_type() == DataType::U128 {
if port.input() {
writeln!(
file,
"void {module_name}_set_{port_name}(V{module_name}* dut, const uint32_t (&arr)[4]) {{"
)?;
writeln!(file, " dut->{port_name}.at(0) = arr[0];")?;
writeln!(file, " dut->{port_name}.at(1) = arr[1];")?;
writeln!(file, " dut->{port_name}.at(2) = arr[2];")?;
writeln!(file, " dut->{port_name}.at(3) = arr[3];")?;
writeln!(file, "}}")?;
}
if port.output() {
writeln!(
file,
"void {module_name}_get_{port_name}(V{module_name}* dut, uint32_t (&arr)[4]) {{"
)?;
writeln!(file, " arr[0] = dut->{port_name}.at(0);")?;
writeln!(file, " arr[1] = dut->{port_name}.at(1);")?;
writeln!(file, " arr[2] = dut->{port_name}.at(2);")?;
writeln!(file, " arr[3] = dut->{port_name}.at(3);")?;
writeln!(file, "}}")?;
}
} else {
let type_name = c_type_name(port.data_type());
if port.input() {
writeln!(
file,
"void {module_name}_set_{port_name}(V{module_name}* dut, {type_name} value) {{"
)?;
writeln!(file, " dut->{port_name} = value;")?;
writeln!(file, "}}")?;
}
if port.output() {
writeln!(
file,
"{type_name} {module_name}_get_{port_name}(V{module_name}* dut) {{"
)?;
writeln!(file, " return dut->{port_name};")?;
writeln!(file, "}}")?;
}
}
}
writeln!(file, "}}")?;
Ok(())
}
fn c_type_name(data_type: DataType) -> &'static str {
match data_type {
DataType::Bool => "uint8_t",
DataType::U8 => "uint8_t",
DataType::U16 => "uint16_t",
DataType::U32 => "uint32_t",
DataType::U64 => "uint64_t",
DataType::I8 => "int8_t",
DataType::I16 => "int16_t",
DataType::I32 => "int32_t",
DataType::I64 => "int64_t",
DataType::U128 => panic!("cannot directly represent u128 in C"),
}
}