From dc90a07a81e78b6f8639b1b61a3c2ae442b93122 Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Sun, 1 Feb 2026 15:54:34 -0500 Subject: [PATCH 1/7] New Cargo package --- Cargo.toml | 4 ++-- filament-eval/Cargo.toml | 13 +++++++++++++ filament-eval/src/main.rs | 3 +++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 filament-eval/Cargo.toml create mode 100644 filament-eval/src/main.rs 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..a9a69b72 --- /dev/null +++ b/filament-eval/Cargo.toml @@ -0,0 +1,13 @@ +[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] +protocols.workspace = true +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..e7a11a96 --- /dev/null +++ b/filament-eval/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 857484045ea0fb4eb1df9edc342f46b45b0e8647 Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Sun, 1 Feb 2026 16:19:03 -0500 Subject: [PATCH 2/7] Basic file IO --- filament-eval/Cargo.toml | 1 + filament-eval/src/main.rs | 24 ++++++++++++++++++++++-- filament-eval/tests/add.fil | 12 ++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 filament-eval/tests/add.fil diff --git a/filament-eval/Cargo.toml b/filament-eval/Cargo.toml index a9a69b72..4acea353 100644 --- a/filament-eval/Cargo.toml +++ b/filament-eval/Cargo.toml @@ -9,5 +9,6 @@ readme.workspace = true license.workspace = true [dependencies] +anyhow = "1.0.100" protocols.workspace = true serde_json = "1.0.149" diff --git a/filament-eval/src/main.rs b/filament-eval/src/main.rs index e7a11a96..83714325 100644 --- a/filament-eval/src/main.rs +++ b/filament-eval/src/main.rs @@ -1,3 +1,23 @@ -fn main() { - println!("Hello, world!"); +use std::{env, path::Path}; + +use serde_json::Value; + +fn main() -> anyhow::Result<()> { + let args: Vec = env::args().collect(); + if args.is_empty() { + panic!("Missing argument") + } else if args.len() >= 2 { + panic!("Too many arguments supplied") + } + let filepath_str = &args[0]; + 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}")); + Ok(()) + } else { + panic!("Invalid extension for file {filepath_str}, expected JSON file"); + } } 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 From 29c89836b83d1f92da884e71f674a9b2ce3af873 Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Sun, 1 Feb 2026 16:23:27 -0500 Subject: [PATCH 3/7] Add Clap --- filament-eval/Cargo.toml | 1 + filament-eval/src/main.rs | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/filament-eval/Cargo.toml b/filament-eval/Cargo.toml index 4acea353..c8e2b968 100644 --- a/filament-eval/Cargo.toml +++ b/filament-eval/Cargo.toml @@ -10,5 +10,6 @@ license.workspace = true [dependencies] anyhow = "1.0.100" +clap = { version = "4.5.56", features = ["derive"] } protocols.workspace = true serde_json = "1.0.149" diff --git a/filament-eval/src/main.rs b/filament-eval/src/main.rs index 83714325..16cba751 100644 --- a/filament-eval/src/main.rs +++ b/filament-eval/src/main.rs @@ -1,16 +1,23 @@ -use std::{env, path::Path}; - +use clap::Parser; use serde_json::Value; +use std::path::Path; + +#[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, +} fn main() -> anyhow::Result<()> { - let args: Vec = env::args().collect(); - if args.is_empty() { - panic!("Missing argument") - } else if args.len() >= 2 { - panic!("Too many arguments supplied") - } - let filepath_str = &args[0]; - let filepath = Path::new(filepath_str); + 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)?; From e05435e2d7ba9d894472f8cb9514e226745c51c4 Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Sun, 1 Feb 2026 16:44:47 -0500 Subject: [PATCH 4/7] Extract event variables --- filament-eval/Cargo.toml | 1 + filament-eval/src/main.rs | 48 ++++++++++++++++++++++++++++++++++-- filament-eval/tests/comb.fil | 12 +++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 filament-eval/tests/comb.fil diff --git a/filament-eval/Cargo.toml b/filament-eval/Cargo.toml index c8e2b968..c8f009f3 100644 --- a/filament-eval/Cargo.toml +++ b/filament-eval/Cargo.toml @@ -12,4 +12,5 @@ license.workspace = true 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 index 16cba751..feffa15f 100644 --- a/filament-eval/src/main.rs +++ b/filament-eval/src/main.rs @@ -1,7 +1,9 @@ use clap::Parser; +use serde::Deserialize; use serde_json::Value; -use std::path::Path; +use std::{fmt, path::Path}; +/// CLI arguments for the Filament to Protocols compiler #[derive(Parser)] #[command( version, @@ -14,6 +16,46 @@ struct Cli { json: String, } +/// A Filament event variable +#[derive(Debug, Deserialize, Clone, PartialEq)] +struct Event { + /// The name of the event + name: String, + + /// The delay associated with the event + delay: u32, +} + +impl fmt::Display for Event { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "<{}: {}>", self.name, self.delay) + } +} + +/// Extracts all the `Event`s from the Filament interface JSON +fn get_events(json: &Value) -> Vec { + 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 +} + +/// Main entry point for the executable fn main() -> anyhow::Result<()> { let cli = Cli::parse(); let filepath_str = cli.json; @@ -21,8 +63,10 @@ fn main() -> anyhow::Result<()> { 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) + 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); + println!("events: {:?}", events); Ok(()) } else { panic!("Invalid extension for file {filepath_str}, expected JSON 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 From 77af10630410c48527c3ad9f3bc8cd93bf191534 Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Sun, 1 Feb 2026 16:47:17 -0500 Subject: [PATCH 5/7] add example in comments --- filament-eval/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/filament-eval/src/main.rs b/filament-eval/src/main.rs index feffa15f..42b3a8e9 100644 --- a/filament-eval/src/main.rs +++ b/filament-eval/src/main.rs @@ -56,6 +56,8 @@ fn get_events(json: &Value) -> Vec { } /// 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; From 46848588fac96eb987ccd2c6f715beb33463843d Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Tue, 3 Feb 2026 12:15:29 -0500 Subject: [PATCH 6/7] Compute max time out of all output ports --- filament-eval/src/main.rs | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/filament-eval/src/main.rs b/filament-eval/src/main.rs index 42b3a8e9..3df5bca6 100644 --- a/filament-eval/src/main.rs +++ b/filament-eval/src/main.rs @@ -1,7 +1,7 @@ use clap::Parser; use serde::Deserialize; use serde_json::Value; -use std::{fmt, path::Path}; +use std::{cmp::max, fmt, path::Path}; /// CLI arguments for the Filament to Protocols compiler #[derive(Parser)] @@ -32,8 +32,21 @@ impl fmt::Display for Event { } } -/// Extracts all the `Event`s from the Filament interface JSON -fn get_events(json: &Value) -> Vec { +/// 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). +struct Events(Vec); + +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(", ")) + } +} + +/// 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"); @@ -52,7 +65,21 @@ fn get_events(json: &Value) -> Vec { }; events.push(event); } - events + 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 @@ -68,7 +95,8 @@ fn main() -> anyhow::Result<()> { 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); - println!("events: {:?}", events); + let max_time = find_max_time(&json); + println!("events: {}, max_time = {}", events, max_time); Ok(()) } else { panic!("Invalid extension for file {filepath_str}, expected JSON file"); From d0c8bc91a5e65cc88f2e683361b06e94517cf7de Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Tue, 3 Feb 2026 13:07:04 -0500 Subject: [PATCH 7/7] Produce Protocols struct type from args to Filament component --- filament-eval/src/main.rs | 86 ++++++++++++++++++++++++++------------ filament-eval/src/types.rs | 64 ++++++++++++++++++++++++++++ protocols/src/ir.rs | 10 ++--- protocols/src/serialize.rs | 18 ++++++++ 4 files changed, 146 insertions(+), 32 deletions(-) create mode 100644 filament-eval/src/types.rs diff --git a/filament-eval/src/main.rs b/filament-eval/src/main.rs index 3df5bca6..a8fddf2c 100644 --- a/filament-eval/src/main.rs +++ b/filament-eval/src/main.rs @@ -1,7 +1,23 @@ +// Copyright 2026 Cornell University +// released under MIT License +// author: Ernest Ng + +use anyhow::anyhow; use clap::Parser; -use serde::Deserialize; +use protocols::ir::Dir; +use protocols::ir::Struct; +use protocols::serialize::serialize_struct; use serde_json::Value; -use std::{cmp::max, fmt, path::Path}; +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)] @@ -16,31 +32,30 @@ struct Cli { json: String, } -/// A Filament event variable -#[derive(Debug, Deserialize, Clone, PartialEq)] -struct Event { - /// The name of the event - name: String, - - /// The delay associated with the event - delay: u32, -} - -impl fmt::Display for Event { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "<{}: {}>", self.name, self.delay) +/// 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); } -} - -/// 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). -struct Events(Vec); - -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(", ")) + 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, } } @@ -97,8 +112,25 @@ fn main() -> anyhow::Result<()> { 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 { - panic!("Invalid extension for file {filepath_str}, expected JSON file"); + 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/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(