Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "3"
members = ["interp", "monitor", "protocols"]
members = ["filament-eval", "interp", "monitor", "protocols"]


[workspace.package]
Expand All @@ -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"
rustc-hash = "2.1.1"
16 changes: 16 additions & 0 deletions filament-eval/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
136 changes: 136 additions & 0 deletions filament-eval/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright 2026 Cornell University
// released under MIT License
// author: Ernest Ng <eyn5@cornell.edu>

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"
))
}
}
64 changes: 64 additions & 0 deletions filament-eval/src/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2026 Cornell University
// released under MIT License
// author: Ernest Ng <eyn5@cornell.edu>

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<Event>`
/// Rust doesn't allow us to do `impl Display for Vec<Event>` directly due to
/// the orphan rule (neither `Display` nor `Vec` are defined in this crate).
pub struct Events(pub Vec<Event>);

/// 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<String> = self.0.iter().map(|e| e.to_string()).collect();
write!(f, "[{}]", event_strs.join(", "))
}
}
12 changes: 12 additions & 0 deletions filament-eval/tests/add.fil
Original file line number Diff line number Diff line change
@@ -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;
}
12 changes: 12 additions & 0 deletions filament-eval/tests/comb.fil
Original file line number Diff line number Diff line change
@@ -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;
}
10 changes: 5 additions & 5 deletions protocols/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,8 @@ entity_impl!(StructId, "struct");

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Struct {
name: String,
pins: Vec<Field>,
pub name: String,
pub pins: Vec<Field>,
}

impl Struct {
Expand Down Expand Up @@ -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 {
Expand Down
18 changes: 18 additions & 0 deletions protocols/src/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// author: Nikil Shyamunder <nvs26@cornell.edu>
// author: Kevin Laeufer <laeufer@cornell.edu>
// author: Francis Pham <fdp25@cornell.edu>
// author: Ernest Ng <eyn5@cornell.edu>

use crate::serialize::Type::BitVec;
use crate::{interpreter::ExprValue, ir::*};
use baa::{BitVecOps, BitVecValue};
use itertools::Itertools;
Expand Down Expand Up @@ -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(
Expand Down