diff --git a/Cargo.toml b/Cargo.toml index e324a8d6..64d5114b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = ["interp", "monitor", "protocols"] +members = ["filament-eval", "interp", "monitor", "protocols"] [workspace.package] @@ -15,4 +15,4 @@ license = "MIT" protocols = { path = "protocols" } baa = { version = "0.17.1", features = ["rand1"] } patronus = "0.34.1" -rustc-hash = "2.1.1" \ No newline at end of file +rustc-hash = "2.1.1" diff --git a/filament-eval/Cargo.toml b/filament-eval/Cargo.toml new file mode 100644 index 00000000..c8f009f3 --- /dev/null +++ b/filament-eval/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "filament_eval" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +readme.workspace = true +license.workspace = true + +[dependencies] +anyhow = "1.0.100" +clap = { version = "4.5.56", features = ["derive"] } +protocols.workspace = true +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" diff --git a/filament-eval/src/main.rs b/filament-eval/src/main.rs new file mode 100644 index 00000000..a8fddf2c --- /dev/null +++ b/filament-eval/src/main.rs @@ -0,0 +1,136 @@ +// Copyright 2026 Cornell University +// released under MIT License +// author: Ernest Ng + +use anyhow::anyhow; +use clap::Parser; +use protocols::ir::Dir; +use protocols::ir::Struct; +use protocols::serialize::serialize_struct; +use serde_json::Value; +use std::io::stdout; +use std::{cmp::max, path::Path}; + +use types::RawParameter; + +use types::Events; + +use crate::types::Event; + +mod types; + +/// CLI arguments for the Filament to Protocols compiler +#[derive(Parser)] +#[command( + version, + about = "Compiles Filament types to Protocols", + disable_version_flag = true +)] +struct Cli { + /// Path to a JSON file for a Filament interface + #[arg(short, long, value_name = "FILAMENT_INTERFACE_JSON_FILE")] + json: String, +} + +/// Produces a Protocols struct definition based on the Filament interface +fn generate_struct(json: &Value, name: String) -> Struct { + let inputs = json["inputs"] + .as_array() + .expect("Expected `inputs` to be a JSON array"); + let mut struct_fields = vec![]; + for input in inputs { + let raw_param: RawParameter = serde_json::from_value(input.clone()) + .expect("Unable to convert JSON object into input `RawParameter`"); + let input_field = raw_param.into_field(Dir::In); + struct_fields.push(input_field); + } + let outputs = json["outputs"] + .as_array() + .expect("Expected `outputs` to be a JSON array"); + for output in outputs { + let raw_param: RawParameter = serde_json::from_value(output.clone()) + .expect("Unable to convert JSON object into output `RawParameter"); + let output_field = raw_param.into_field(Dir::Out); + struct_fields.push(output_field); + } + Struct { + name, + pins: struct_fields, + } +} + +/// Extracts all the `Event`s from the Filament interface JSON contained +/// in the `json` argument +fn get_events(json: &Value) -> Events { + let interface_ports = json["interfaces"] + .as_array() + .expect("Expected `interfaces` to be a JSON array"); + let mut events = vec![]; + for port in interface_ports { + let event_name = port["event"] + .as_str() + .expect("Expected `event` to be a string") + .to_string(); + let delay = port["delay"] + .as_i64() + .expect("Expected `delay` to be an integer") as u32; + let event = Event { + name: event_name, + delay, + }; + events.push(event); + } + Events(events) +} + +/// Computes the max time interval out of all the output ports in the Filament type +/// The argument `json` is the JSON representing the Filament signature. +fn find_max_time(json: &Value) -> u32 { + let outputs = json["outputs"] + .as_array() + .expect("Expected `outputs` to be a JSON array"); + let mut max_end_time = 0; + for output in outputs { + let end_time = output["end"].as_i64().expect("Expected `end` to be an int") as u32; + max_end_time = max(max_end_time, end_time); + } + max_end_time +} + +/// Main entry point for the executable +/// Example: cargo run -- --json tests/add.json +/// TODO: add Turnt tests +fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + let filepath_str = cli.json; + let filepath = Path::new(&filepath_str); + + if let Some("json") = filepath.extension().and_then(|s| s.to_str()) { + let file_contents = std::fs::read_to_string(filepath)?; + let json: Value = serde_json::from_str(&file_contents) + .unwrap_or_else(|_| panic!("Unable to read from JSON file {filepath_str}")); + let events = get_events(&json); + let max_time = find_max_time(&json); + println!("events: {}, max_time = {}", events, max_time); + + // Treat the file-stem of the JSON filepath as the name of the + // Protocols struct (this involves converting from `OsStr` to `String`) + let dut_name: String = filepath.file_stem().map_or_else( + || panic!("Unable to extract file stem from filepath"), + |os_str| { + os_str + .to_str() + .expect("Unable to convert `OsStr` to `&str`") + .to_uppercase() + }, + ); + let protocols_struct = generate_struct(&json, dut_name); + + serialize_struct(&mut stdout(), &protocols_struct)?; + Ok(()) + } else { + Err(anyhow!( + "Invalid extension for file {filepath_str}, expected JSON file" + )) + } +} diff --git a/filament-eval/src/types.rs b/filament-eval/src/types.rs new file mode 100644 index 00000000..0f192d73 --- /dev/null +++ b/filament-eval/src/types.rs @@ -0,0 +1,64 @@ +// Copyright 2026 Cornell University +// released under MIT License +// author: Ernest Ng + +use std::fmt; + +use protocols::ir::{Dir, Field, Type}; +use serde::Deserialize; + +/// A Filament event variable +#[derive(Debug, Deserialize, Clone, PartialEq)] +pub struct Event { + /// The name of the event + pub name: String, + + /// The delay associated with the event + pub delay: u32, +} + +/// Tuple struct so that we can implement `Display` for `Vec` +/// Rust doesn't allow us to do `impl Display for Vec` directly due to +/// the orphan rule (neither `Display` nor `Vec` are defined in this crate). +pub struct Events(pub Vec); + +/// A raw Filament parameter (the fields of this struct exactly match +/// what is in the Filament interface JSON) +#[derive(Debug, Deserialize)] +#[allow(dead_code)] +pub struct RawParameter { + name: String, + width: u32, + event: String, + start: u32, + end: u32, +} + +impl RawParameter { + /// Converts a `RawParameter` into a Protocols `Field` based on the + /// supplied direction `dir` + pub fn into_field(self, dir: Dir) -> Field { + Field { + name: self.name, + dir, + tpe: Type::BitVec(self.width), + } + } +} + +/* -------------------------------------------------------------------------- */ +/* Trait implementations */ +/* -------------------------------------------------------------------------- */ + +impl fmt::Display for Event { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "<{}: {}>", self.name, self.delay) + } +} + +impl fmt::Display for Events { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let event_strs: Vec = self.0.iter().map(|e| e.to_string()).collect(); + write!(f, "[{}]", event_strs.join(", ")) + } +} diff --git a/filament-eval/tests/add.fil b/filament-eval/tests/add.fil new file mode 100644 index 00000000..0c2c0502 --- /dev/null +++ b/filament-eval/tests/add.fil @@ -0,0 +1,12 @@ +import "primitives/comb.fil"; + +comp main<'G:1>( + go: interface['G], + left: ['G, 'G+1] 32, + right: ['G, 'G+1] 32 +) -> ( + out: ['G,'G+1] 32 +) { + a0 := new Add[32]<'G>(left, right); + out = a0.out; +} \ No newline at end of file diff --git a/filament-eval/tests/comb.fil b/filament-eval/tests/comb.fil new file mode 100644 index 00000000..396c6006 --- /dev/null +++ b/filament-eval/tests/comb.fil @@ -0,0 +1,12 @@ +import "primitives/comb.fil"; + +comp main<'G: 1>( + go: interface['G], + left: ['G, 'G+1] 32, + right: ['G, 'G+1] 32 +) -> ( + out: ['G, 'G+1] 32 +) { + m0 := new MultComb[32]<'G>(left, right); + out = m0.out; +} \ No newline at end of file diff --git a/protocols/src/ir.rs b/protocols/src/ir.rs index 42dab8fa..b9a9e50f 100644 --- a/protocols/src/ir.rs +++ b/protocols/src/ir.rs @@ -410,8 +410,8 @@ entity_impl!(StructId, "struct"); #[derive(Debug, Clone, PartialEq, Eq)] pub struct Struct { - name: String, - pins: Vec, + pub name: String, + pub pins: Vec, } impl Struct { @@ -448,9 +448,9 @@ impl Struct { /// - The `Type` of the field #[derive(Debug, Clone, PartialEq, Eq)] pub struct Field { - name: String, - dir: Dir, - tpe: Type, + pub name: String, + pub dir: Dir, + pub tpe: Type, } impl Field { diff --git a/protocols/src/serialize.rs b/protocols/src/serialize.rs index 683cba96..d6ff5fd6 100644 --- a/protocols/src/serialize.rs +++ b/protocols/src/serialize.rs @@ -3,7 +3,9 @@ // author: Nikil Shyamunder // author: Kevin Laeufer // author: Francis Pham +// author: Ernest Ng +use crate::serialize::Type::BitVec; use crate::{interpreter::ExprValue, ir::*}; use baa::{BitVecOps, BitVecValue}; use itertools::Itertools; @@ -278,6 +280,22 @@ pub fn serialize_structs( Ok(()) } +/// Pretty-prints a struct definition (realized as a value of the `Struct` +/// datatype) to the output buffer `out` +pub fn serialize_struct(out: &mut impl Write, st: &Struct) -> std::io::Result<()> { + writeln!(out, "struct {} {{", st.name())?; + + for field in st.pins() { + if let BitVec(width) = field.tpe() { + writeln!(out, " {} {}: u{},", field.dir(), field.name(), width)?; + } else { + panic!("Cannot serialize struct with non-BitVec field types"); + } + } + writeln!(out, "}}\n")?; + Ok(()) +} + /// Serializes a `Vec` of `(SymbolTable, Transaction)` pairs to the provided /// output buffer `out` pub fn serialize(