diff --git a/bi/src/main.rs b/bi/src/main.rs index f95550b3..8ff25d81 100644 --- a/bi/src/main.rs +++ b/bi/src/main.rs @@ -24,9 +24,13 @@ use crate::signal_trace::*; #[derive(Parser, Debug)] #[command(version, about, long_about = None, disable_version_flag = true)] struct Cli { - /// Path to a Protocol (.prot) file - #[arg(short, long, value_name = "PROTOCOLS_FILE")] - protocol: String, + #[arg( + short, + long, + value_name = "PROTOCOLS_FILE", + help = "One or several protocol files." + )] + protocol: Vec, /// Path to a waveform trace (.fst, .vcd, .ghw) file #[arg(short, long, value_name = "WAVE_FILE")] @@ -94,9 +98,9 @@ fn main() -> Result<(), Box> { let show_warnings = false; let skip_static_step_fork_checks = false; let mut d = DiagnosticHandler::new(ColorChoice::Auto, false, show_warnings, false); - let (st, protos) = frontend(cli.protocol, &mut d, skip_static_step_fork_checks)?; + let ast = frontend(&cli.protocol, &mut d, skip_static_step_fork_checks)?; - let designs = find_designs(&st, &protos); + let designs = find_designs(&ast); // try to find instances that we care about if cli.instances.is_empty() { @@ -124,7 +128,12 @@ fn main() -> Result<(), Box> { let exclude_from_trace = if cli.include_idle { FxHashSet::default() } else { - FxHashSet::from_iter(protos.iter().filter(|p| p.is_idle).map(|p| p.name.clone())) + FxHashSet::from_iter( + ast.protos + .iter() + .filter(|p| p.is_idle) + .map(|p| p.name.clone()), + ) }; let bi_protos: Vec> = instances @@ -133,7 +142,7 @@ fn main() -> Result<(), Box> { designs[&inst.design] .protocols .iter() - .map(|(id, _)| protos[*id].clone()) + .map(|(id, _)| ast.protos[*id].clone()) .collect() }) .collect(); @@ -143,7 +152,7 @@ fn main() -> Result<(), Box> { .enumerate() .zip(bi_protos.iter()) .map(|((inst_id, inst), protos)| { - BackwardsInterpreter::new(&st, protos, inst_id as u32, cli.include_in_progress) + BackwardsInterpreter::new(&ast.st, protos, inst_id as u32, cli.include_in_progress) }) .collect(); diff --git a/cli/src/main.rs b/cli/src/main.rs index 2d299aa9..327617f4 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -6,15 +6,19 @@ use std::path::Path; use clap::*; use protocols::backends::{PinAnnotation, to_verilog}; -use protocols::frontend::ast::Protocol; +use protocols::frontend::ast::Ast; use protocols::frontend::diagnostic::DiagnosticHandler; -use protocols::frontend::symbol::SymbolTable; use protocols::{Value, frontend, transaction_frontend}; #[derive(Parser, Debug)] struct Args { - #[arg(short, long, value_name = "PROTOCOLS_FILE")] - protocol: String, + #[arg( + short, + long, + value_name = "PROTOCOLS_FILE", + help = "One or several protocol files." + )] + protocol: Vec, #[command(subcommand)] command: Option, @@ -45,14 +49,10 @@ enum Cmds { }, } -fn load_trace( - st: &SymbolTable, - protos: &[Protocol], - transactions: Option<&str>, -) -> Vec<(String, Vec)> { +fn load_trace(ast: &Ast, transactions: Option<&str>) -> Vec<(String, Vec)> { if let Some(filename) = transactions { let mut d = DiagnosticHandler::new(ColorChoice::Auto, false, true, false); - let traces = transaction_frontend(filename, st, protos, &mut d).unwrap(); + let traces = transaction_frontend(filename, ast, &mut d).unwrap(); if !traces.is_empty() { if traces.len() > 1 { log::warn!("More than 1 trace in {filename}. Picking first one."); @@ -67,14 +67,13 @@ fn load_trace( } fn make_verilog_tb( - st: &SymbolTable, - protos: &[Protocol], + ast: &Ast, verilog_tb: String, transactions: Option, vcd_out: Option, clock: Option, ) { - let trace = load_trace(st, protos, transactions.as_deref()); + let trace = load_trace(ast, transactions.as_deref()); let mut pins = vec![]; if let Some(clock) = clock { pins.push((clock, PinAnnotation::Clock)); @@ -84,8 +83,7 @@ fn make_verilog_tb( let tb_name = "tb"; to_verilog( tb_name, - st, - protos, + ast, &pins, vcd_out.as_deref(), &trace, @@ -95,8 +93,7 @@ fn make_verilog_tb( } fn run_verilog_tb( - st: &SymbolTable, - protos: &[Protocol], + ast: &Ast, run_dir: String, transactions: Option, clock: Option, @@ -124,8 +121,7 @@ fn run_verilog_tb( let verilog_tb_str = abs_cwd.join(verilog_tb).to_str().unwrap().to_string(); let vcd_out_rel = "dump.vcd"; make_verilog_tb( - st, - protos, + ast, verilog_tb_str, transactions, Some(vcd_out_rel.to_string()), @@ -178,12 +174,12 @@ fn main() { // we always parse and type check the protocol file let skip_static_step_fork_checks = false; let mut d = DiagnosticHandler::new(ColorChoice::Auto, false, true, false); - let (st, protos) = frontend(args.protocol, &mut d, skip_static_step_fork_checks).unwrap(); + let ast = frontend(&args.protocol, &mut d, skip_static_step_fork_checks).unwrap(); match args.command { None => {} Some(Cmds::Constructs) => { - for p in &protos { + for p in &ast.protos { println!("{}: {}", p.name, p.used_constructs()); } } @@ -193,7 +189,7 @@ fn main() { vcd_out, clock, }) => { - make_verilog_tb(&st, &protos, verilog_tb, transactions, vcd_out, clock); + make_verilog_tb(&ast, verilog_tb, transactions, vcd_out, clock); } Some(Cmds::RunVerilog { run_dir, @@ -201,7 +197,7 @@ fn main() { clock, verilog, }) => { - run_verilog_tb(&st, &protos, run_dir, transactions, clock, verilog); + run_verilog_tb(&ast, run_dir, transactions, clock, verilog); } } } diff --git a/examples/wishbone/antmicro_litex.prot b/examples/wishbone/antmicro_litex.prot new file mode 100644 index 00000000..6523faf4 --- /dev/null +++ b/examples/wishbone/antmicro_litex.prot @@ -0,0 +1,19 @@ +// Map common wishbone protocol pins to the Antmicro/Litex implementation + +module LitexWishbone : Wishbone { + in clk : clock @posedge, + // "interesting" mappings + in reset : u1 = !Wishbone.RST, + in wishbone_adr : u30 = Wishbone.ADR[31:2] with Wishbone.ADR[1:0] == 2'b00, + // all other pins just map 1:1 + out wishbone_dat_r : u32 = Wishbone.DAT_I, + in wishbone_dat_w : u32 = Wishbone.DAT_O, + in wishbone_cyc : u1 = Wishbone.CYC, + in wishbone_stb : u1 = Wishbone.STB, + out wishbone_ack : u1 = Wishbone.ACK, + in wishbone_we : u1 = Wishbone.WE, + in wishbone_sel : u4 = Wishbone.SEL, + in wishbone_cti : u3 = Wishbone.CTI, + in wishbone_bte : u2 = Wishbone.BTE, + out wishbone_err : u1 = Wishbone.ERR, +} \ No newline at end of file diff --git a/examples/wishbone/wishbone.prot b/examples/wishbone/wishbone.prot index 260f3352..d97eb31a 100644 --- a/examples/wishbone/wishbone.prot +++ b/examples/wishbone/wishbone.prot @@ -1,5 +1,5 @@ // Wishbone pins for this project from the server (the one servicing requests) perspective -struct Wishbone { +interface Wishbone { // high active RST pin (the wishbone spec requires all signals to be high active) in RST: u1, // acknowledge (from server) @@ -28,7 +28,7 @@ struct Wishbone { // 2: 8-beat wrap burst // 3: 16-beat wrap burst in BTE: u2, - // cycle type indentifier + // cycle type identifier // 0: classic cycle // 1: constant address burst cycle // 2: incrementing burst cycle diff --git a/graph-interp/src/main.rs b/graph-interp/src/main.rs index 56e3a83e..4d75dded 100644 --- a/graph-interp/src/main.rs +++ b/graph-interp/src/main.rs @@ -2,7 +2,7 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use clap::Parser; use protocols::ascii_waveform::print_ascii_waveform; -use protocols::frontend::ast::Protocol; +use protocols::frontend::ast::{Ast, Protocol}; use protocols::frontend::design::{Design, find_a_single_design}; use protocols::frontend::diagnostic::DiagnosticHandler; use protocols::frontend::symbol::SymbolTable; @@ -22,9 +22,13 @@ struct Cli { #[arg(long, value_name = "VERILOG_FILES", value_delimiter = ' ', num_args = 1..)] verilog: Vec, - /// Path to a Protocol (.prot) file - #[arg(short, long, value_name = "PROTOCOLS_FILE")] - protocol: String, + #[arg( + short, + long, + value_name = "PROTOCOLS_FILE", + help = "One or several protocol files." + )] + protocol: Vec, /// Path to a Transactions (.tx) file #[arg(short, long, value_name = "TRANSACTIONS_FILE")] @@ -59,7 +63,7 @@ struct Cli { ascii_waveform: bool, } -fn load_protocols(cli: &Cli) -> (SymbolTable, Vec) { +fn load_protocols(cli: &Cli) -> Ast { let mut handler = DiagnosticHandler::new(clap::ColorChoice::Never, false, true, false); frontend( &cli.protocol, @@ -69,9 +73,9 @@ fn load_protocols(cli: &Cli) -> (SymbolTable, Vec) { .unwrap() } -fn load_traces(cli: &Cli, st: &SymbolTable, protos: &[Protocol]) -> Vec)>> { +fn load_traces(cli: &Cli, ast: &Ast) -> Vec)>> { let mut handler = DiagnosticHandler::new(clap::ColorChoice::Never, false, true, false); - transaction_frontend(&cli.transactions, st, protos, &mut handler).unwrap() + transaction_frontend(&cli.transactions, ast, &mut handler).unwrap() } fn build_arg_map<'a>( @@ -191,17 +195,18 @@ fn run_respect_forks( fn main() { let cli = Cli::parse(); - let (st, protos) = load_protocols(&cli); - let traces = load_traces(&cli, &st, &protos); - let design = find_a_single_design(&st, &protos, &cli.protocol).unwrap(); + let ast = load_protocols(&cli); + let st = &ast.st; + let traces = load_traces(&cli, &ast); + let design = find_a_single_design(&ast, &cli.protocol).unwrap(); let old_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(|_| {})); let result = catch_unwind(AssertUnwindSafe(|| { if cli.respect_forks { - run_respect_forks(&cli, &st, &protos, &design, &traces); + run_respect_forks(&cli, st, &ast.protos, &design, &traces); } else { - run_classic(&cli, &st, &protos, &design, traces); + run_classic(&cli, st, &ast.protos, &design, traces); } })); std::panic::set_hook(old_hook); diff --git a/interp/src/main.rs b/interp/src/main.rs index 04e03efb..68f10a74 100644 --- a/interp/src/main.rs +++ b/interp/src/main.rs @@ -19,9 +19,13 @@ struct Cli { #[arg(long, value_name = "VERILOG_FILES", value_delimiter = ' ', num_args = 1..)] verilog: Vec, - /// Path to a Protocol (.prot) file - #[arg(short, long, value_name = "PROTOCOLS_FILE")] - protocol: String, + #[arg( + short, + long, + value_name = "PROTOCOLS_FILE", + help = "One or several protocol files." + )] + protocol: Vec, /// Path to a Transactions (.tx) file #[arg(short, long, value_name = "TRANSACTIONS_FILE")] @@ -128,7 +132,7 @@ fn main() -> anyhow::Result<()> { cli.display_hex, ); - let (st, protos) = match frontend( + let ast = match frontend( &cli.protocol, &mut protocols_handler, cli.skip_static_step_fork_checks, @@ -136,7 +140,7 @@ fn main() -> anyhow::Result<()> { Ok(result) => result, Err(error) => exit_after_setup_error(error, !protocols_handler.error_string().is_empty()), }; - let design = find_a_single_design(&st, &protos, &cli.protocol)?; + let design = find_a_single_design(&ast, &cli.protocol)?; // Create a separate `DiagnosticHandler` when parsing the transactions file let mut transactions_handler = DiagnosticHandler::new( @@ -145,13 +149,12 @@ fn main() -> anyhow::Result<()> { emit_warnings, cli.display_hex, ); - let traces = - match transaction_frontend(cli.transactions, &st, &protos, &mut transactions_handler) { - Ok(result) => result, - Err(error) => { - exit_after_setup_error(error, !transactions_handler.error_string().is_empty()) - } - }; + let traces = match transaction_frontend(cli.transactions, &ast, &mut transactions_handler) { + Ok(result) => result, + Err(error) => { + exit_after_setup_error(error, !transactions_handler.error_string().is_empty()) + } + }; let mut any_failed = false; for (trace_index, todos) in traces.into_iter().enumerate() { @@ -169,8 +172,8 @@ fn main() -> anyhow::Result<()> { )?; let mut scheduler = Scheduler::new( - &st, - &protos, + &ast.st, + &ast.protos, todos, sim, &mut protocols_handler, diff --git a/monitor/src/main.rs b/monitor/src/main.rs index 8f29ebe3..ae5a5891 100644 --- a/monitor/src/main.rs +++ b/monitor/src/main.rs @@ -39,9 +39,13 @@ use crate::signal_trace::WaveSignalTrace; #[derive(Parser, Debug)] #[command(version, about, long_about = None, disable_version_flag = true)] struct Cli { - /// Path to a Protocol (.prot) file - #[arg(short, long, value_name = "PROTOCOLS_FILE")] - protocol: String, + #[arg( + short, + long, + value_name = "PROTOCOLS_FILE", + help = "One or several protocol files." + )] + protocol: Vec, /// Path to a waveform trace (.fst, .vcd, .ghw) file #[arg(short, long, value_name = "WAVE_FILE")] @@ -152,13 +156,13 @@ fn main() -> anyhow::Result<()> { DiagnosticHandler::new(cli.color, false, emit_warnings, cli.display_hex); // Parse protocols file - let (st, protos) = frontend( + let ast = frontend( &cli.protocol, &mut protocols_handler, cli.skip_static_step_fork_checks, )?; - let designs = find_designs(&st, &protos); + let designs = find_designs(&ast); // Try to find instances that we care about if cli.instances.is_empty() { @@ -206,7 +210,7 @@ fn main() -> anyhow::Result<()> { let design_transactions: Vec = design .protocols .iter() - .map(|&(idx, _)| protos[idx].clone()) + .map(|&(idx, _)| ast.protos[idx].clone()) .collect(); if design_transactions.is_empty() { @@ -215,7 +219,7 @@ fn main() -> anyhow::Result<()> { // Create a scheduler for this design, using the design name as the struct name let scheduler = Scheduler::initialize( - &st, + &ast.st, &design_transactions, &trace, design.name.clone(), diff --git a/protocols/src/backends/verilog.rs b/protocols/src/backends/verilog.rs index 132896b7..4eff354b 100644 --- a/protocols/src/backends/verilog.rs +++ b/protocols/src/backends/verilog.rs @@ -13,14 +13,14 @@ use crate::scheduler::Invocation; // todo: add `interface` and `module` to protocol language and remove pin argument pub fn to_verilog( testbench_name: &str, - st: &SymbolTable, - protos: &[Protocol], + ast: &Ast, pins: &[(String, PinAnnotation)], vcd_out: Option<&str>, invocs: &[Invocation], out: &mut impl std::io::Write, ) -> std::io::Result<()> { - let modules = find_designs(st, protos); + let modules = find_designs(ast); + let st = &ast.st; assert_eq!( modules.len(), 1, @@ -30,7 +30,7 @@ pub fn to_verilog( // derive the instance name from the first protocol let first_proto_id = module.protocols[0].0; - let instance_name_id = protos[first_proto_id].type_param.unwrap(); + let instance_name_id = ast.protos[first_proto_id].type_param.unwrap(); let instance_name = st[instance_name_id].name().to_string(); // header @@ -135,7 +135,7 @@ pub fn to_verilog( // one task for each protocol for &(proto_id, _) in module.protocols.iter() { - let proto = &protos[proto_id]; + let proto = &ast.protos[proto_id]; let sym_verilog = gen_sym_to_verilog_map(st, proto, &module, &instance_name); proto_to_verilog(st, proto, &sym_verilog, out)?; } @@ -413,21 +413,16 @@ pub mod tests { use crate::frontend; use crate::frontend::diagnostic::DiagnosticHandler; - fn backend( - st: &SymbolTable, - protos: &[Protocol], - pins: &[(String, PinAnnotation)], - invocs: &[Invocation], - ) -> String { + fn backend(ast: &Ast, pins: &[(String, PinAnnotation)], invocs: &[Invocation]) -> String { let mut out = vec![]; - to_verilog("tb", st, protos, pins, Some("dump.vcd"), invocs, &mut out).unwrap(); + to_verilog("tb", ast, pins, Some("dump.vcd"), invocs, &mut out).unwrap(); String::from_utf8(out).unwrap() } #[test] fn alu_d1_to_verilog() { - let (st, protos) = frontend( - "../tests/alus/alu_d1.prot", + let ast = frontend( + &["../tests/alus/alu_d1.prot"], &mut DiagnosticHandler::default(), false, ) @@ -450,12 +445,7 @@ pub mod tests { ], ), ]; - let verilog = backend( - &st, - &protos, - &[("clk".to_string(), PinAnnotation::Clock)], - &tx, - ); + let verilog = backend(&ast, &[("clk".to_string(), PinAnnotation::Clock)], &tx); println!("{verilog}"); // note: this "test" just runs the Verilog backend, but it isn't really possible to // assert meaningful properties about the output. That needs to be done by an diff --git a/protocols/src/frontend/ast.rs b/protocols/src/frontend/ast.rs index eb3f2121..d9cb72e5 100644 --- a/protocols/src/frontend/ast.rs +++ b/protocols/src/frontend/ast.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Cornell University +// Copyright 2024-26 Cornell University // released under MIT License // author: Nikil Shyamunder // author: Kevin Laeufer @@ -13,7 +13,15 @@ use rustc_hash::{FxHashMap, FxHashSet}; use strum::IntoEnumIterator; use crate::frontend::serialize::{build_statements, serialize_expr}; -use crate::frontend::symbol::{Arg, ScopeId, SymbolId, SymbolTable, Type}; +use crate::frontend::symbol::{Arg, Dir, ScopeId, StructId, SymbolId, SymbolTable, Type}; + +/// Frontend representation of parsed protocol files. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Ast { + pub st: SymbolTable, + pub protos: Vec, + pub remaps: Vec, +} #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProtocolContext { @@ -35,6 +43,8 @@ pub struct ProtocolContext { /// The distinguished `ExprId` corresponding to `DontCare` dont_care_id: ExprId, + true_expr_id: ExprId, + expr_loc: SecondaryMap, /// The scope in the [[SymbolTable]] associated with this protocol. @@ -45,6 +55,7 @@ impl ProtocolContext { pub fn new(name: String, scope: ScopeId) -> Self { let mut exprs = PrimaryMap::new(); let dont_care_id = exprs.push(Expr::DontCare); + let true_expr_id = exprs.push(Expr::Const(BitVecValue::new_true())); let expr_loc: SecondaryMap = SecondaryMap::new(); Self { name, @@ -53,6 +64,7 @@ impl ProtocolContext { is_idle: false, exprs, dont_care_id, + true_expr_id, expr_loc, scope, } @@ -66,6 +78,10 @@ impl ProtocolContext { self.dont_care_id } + pub fn expr_true(&self) -> ExprId { + self.true_expr_id + } + pub fn expr_ids(&self) -> Vec { self.exprs.keys().collect() } @@ -91,6 +107,39 @@ impl ProtocolContext { } } +// #[derive(Debug, Clone, PartialEq, Eq)] +// pub struct Module { +// clock: Clock, +// protocols: Vec, +// ports: Vec, +// } +// +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub enum Clock { + #[default] + None, + Posedge(String), +} + +/// Represents an unresolved `module`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RemapModule { + pub ctx: ProtocolContext, + pub name: String, + pub clock: Clock, + pub implements: Vec, + pub mappings: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Mapping { + pub name: String, + pub dir: Dir, + pub tpe: Type, + pub rhs: ExprId, + pub cond: ExprId, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Protocol { pub ctx: ProtocolContext, @@ -486,7 +535,7 @@ mod tests { ); // 1) declare symbols that are local to the add protocol - let add_scope = symbols.add_protocol_scope("add"); + let add_scope = symbols.enter_scope("add"); let a = symbols.add_without_parent("a".to_string(), Type::BitVec(32), SymbolKind::Arg(0)); let b: SymbolId = symbols.add_without_parent("b".to_string(), Type::BitVec(32), SymbolKind::Arg(1)); @@ -550,7 +599,7 @@ mod tests { ); // 1) declare symbols local to the calyx_go_done protocol - let scope = symbols.add_protocol_scope("calyx_go_done"); + let scope = symbols.enter_scope("calyx_go_done"); let ii = symbols.add_without_parent("ii".to_string(), Type::BitVec(32), SymbolKind::Arg(0)); let oo = symbols.add_without_parent("oo".to_string(), Type::BitVec(32), SymbolKind::Arg(1)); assert_eq!(symbols["oo"], symbols[oo]); diff --git a/protocols/src/frontend/design.rs b/protocols/src/frontend/design.rs index 0f20ccdd..3cf57e49 100644 --- a/protocols/src/frontend/design.rs +++ b/protocols/src/frontend/design.rs @@ -6,7 +6,7 @@ //! # Design extraction //! This module contains code to extract/infer Verilog designs from `struct` and protocol declarations. -use crate::frontend::ast::Protocol; +use crate::frontend::ast::Ast; use crate::frontend::serialize::serialize_field; use crate::frontend::symbol::{Field, SymbolId, SymbolTable, Type}; use anyhow::bail; @@ -47,19 +47,29 @@ pub fn serialize_design(symbol_table: &SymbolTable, design: &Design) -> String { /// Succeeds iff there is only a single `struct` with protocols in the file. pub fn find_a_single_design( - st: &SymbolTable, - protos: &[Protocol], - filename: &str, + ast: &Ast, + filenames: &[impl AsRef], ) -> anyhow::Result { - let designs = find_designs(st, protos); + let designs = find_designs(ast); if designs.is_empty() { - bail!("No protocols found in {}", filename); + bail!( + "No protocols found in {}", + filenames + .iter() + .map(|f| f.as_ref().to_string_lossy()) + .collect::>() + .join(", ") + ); } if designs.len() > 1 { let design_names = designs.keys().cloned().collect::>(); bail!( "There are multiple structs in {}: {}.\nWe need to add a way to select which one we want to use.", - filename, + filenames + .iter() + .map(|f| f.as_ref().to_string_lossy()) + .collect::>() + .join(", "), design_names.join(", ") ); } @@ -68,11 +78,12 @@ pub fn find_a_single_design( /// Finds all the protocols associated with a given `struct` (called a "design" since its a DUT), /// returning a `HashMap` from struct names to the actual `Design` -pub fn find_designs(st: &SymbolTable, protos: &[Protocol]) -> FxHashMap { +pub fn find_designs(ast: &Ast) -> FxHashMap { // Maps the name of the protocol to metadata about the struct (design) // We use `FxHashMap` because its a bit faster than the usual `HashMap` let mut designs: FxHashMap = FxHashMap::default(); - for (proto_id, proto) in protos.iter().enumerate() { + let st = &ast.st; + for (proto_id, proto) in ast.protos.iter().enumerate() { if let Some(dut_symbol_id) = proto.type_param { // We assume type parameters have to be structs let struct_id = match st[dut_symbol_id].tpe() { diff --git a/protocols/src/frontend/diagnostic.rs b/protocols/src/frontend/diagnostic.rs index f6afa0f4..e4f67a65 100644 --- a/protocols/src/frontend/diagnostic.rs +++ b/protocols/src/frontend/diagnostic.rs @@ -186,7 +186,7 @@ impl DiagnosticHandler { pub fn emit_diagnostic_expr( &mut self, - tr: &Protocol, + tr: &ProtocolContext, expr_id: &ExprId, message: &str, level: Level, @@ -561,7 +561,7 @@ mod tests { #[test] fn test_emit_diagnostic() { let mut symbols = SymbolTable::default(); - let scope = symbols.add_protocol_scope("test_transaction"); + let scope = symbols.enter_scope("test_transaction"); let a = symbols.add_without_parent("a".to_string(), Type::BitVec(32), SymbolKind::InPort); let b = symbols.add_without_parent("b".to_string(), Type::BitVec(32), SymbolKind::InPort); diff --git a/protocols/src/frontend/mod.rs b/protocols/src/frontend/mod.rs index 5d3ef0a3..8dd03e35 100644 --- a/protocols/src/frontend/mod.rs +++ b/protocols/src/frontend/mod.rs @@ -14,26 +14,36 @@ pub mod typecheck; /// Simple frontend which loads a single protocols file, type checks and returns the AST. pub fn frontend( - filename: impl AsRef + Clone, + filenames: &[impl AsRef], diag: &mut DiagnosticHandler, skip_static_step_fork_checks: bool, -) -> anyhow::Result<(symbol::SymbolTable, Vec)> { +) -> anyhow::Result { // Parse protocols file - let (mut st, protos) = parser::parse_file(filename.clone(), diag) - .map_err(|e| anyhow!("{}: {}", e, filename.as_ref().to_string_lossy()))?; + let err = |e: String| { + anyhow!( + "{}: {}", + e, + filenames + .iter() + .map(|f| f.as_ref().to_string_lossy()) + .collect::>() + .join(", ") + ) + }; + let mut ast = parser::parse_files(filenames, diag).map_err(err)?; // Type-check the parsed transactions - typecheck::type_check(&mut st, &protos, diag)?; + typecheck::type_check(&mut ast, diag)?; // check for fork and step errors let error_count = if skip_static_step_fork_checks { 0 } else { - static_fork_step_check::check_step_and_fork(&st, &protos, diag) + static_fork_step_check::check_step_and_fork(&ast, diag) }; if error_count > 0 { Err(anyhow!("step or fork errors")) } else { - Ok((st, protos)) + Ok(ast) } } diff --git a/protocols/src/frontend/parser.rs b/protocols/src/frontend/parser.rs index 3c1f6ec8..9efb1df1 100644 --- a/protocols/src/frontend/parser.rs +++ b/protocols/src/frontend/parser.rs @@ -36,11 +36,281 @@ lazy_static::lazy_static! { }; } +fn parse_expr( + st: &SymbolTable, + diag: &mut DiagnosticHandler, + fileid: usize, + ctx: &mut ProtocolContext, + pairs: Pairs, +) -> Result { + let boxed_expr = parse_boxed_expr(st, diag, fileid, pairs)?; + let expr_id = boxed_expr_to_expr_id(ctx, fileid, boxed_expr)?; + Ok(expr_id) +} +// Helper method for expected rule errors +fn expect_rule( + diag: &mut DiagnosticHandler, + fileid: usize, + option: Option, + context_pair: &pest::iterators::Pair, + message: &str, +) -> Result { + option.ok_or_else(|| { + let msg = message.to_string(); + diag.emit_diagnostic_parsing(&msg, fileid, context_pair, Level::Error); + msg + }) +} + +fn parse_boxed_expr( + st: &SymbolTable, + diag: &mut DiagnosticHandler, + fileid: usize, + pairs: Pairs, +) -> Result { + PRATT_PARSER + .map_primary(|primary| { + let start = primary.as_span().start(); + let end = primary.as_span().end(); + + match primary.as_rule() { + Rule::integer => { + // unwrap into width, radix, and then value + let mut inner = primary.clone().into_inner(); + let width: u32 = inner.next().unwrap().as_str().parse::().unwrap(); + let radix = inner.next().unwrap().as_rule(); + let value_str = inner.next().unwrap().as_str(); + + let value = match radix { + Rule::bin => u64::from_str_radix(value_str, 2), + Rule::oct => u64::from_str_radix(value_str, 8), + Rule::hex => u64::from_str_radix(value_str, 16), + Rule::dec => value_str.parse::(), + _ => unreachable!("Unexpected radix rule: {:?}", radix), + }; + + let bvv = BitVecValue::from_u64(value.unwrap(), width); + + Ok(BoxedExpr::Const(bvv, start, end)) + } + Rule::path_id => { + let path_id = primary.as_str(); + let symbol_id = st.symbol_id_from_name_in_active_scope(path_id); + match symbol_id { + Some(id) => Ok(BoxedExpr::Sym(id, start, end)), + None => { + let msg = format!("Referencing undefined symbol: {}", path_id); + diag.emit_diagnostic_parsing(&msg, fileid, &primary, Level::Error); + Err(msg) + } + } + } + Rule::dont_care => Ok(BoxedExpr::DontCare(start, end)), + Rule::slice => { + let mut inner_rules = primary.clone().into_inner(); + let path_rule = expect_rule( + diag, + fileid, + inner_rules.next(), + &primary, + "Expected path rule in slice expression", + )?; + let path_id = parse_boxed_expr(st, diag, fileid, Pairs::single(path_rule))?; + let idx1_rule = inner_rules.next().unwrap(); + let idx1 = idx1_rule.as_str().parse::().unwrap(); + let idx2_rule = inner_rules.next(); + let idx2 = match idx2_rule { + Some(rule) => rule.as_str().parse::().unwrap(), + None => idx1, + }; + Ok(BoxedExpr::Slice(Box::new(path_id), idx1, idx2, start, end)) + } + Rule::expr => parse_boxed_expr(st, diag, fileid, primary.into_inner()), + Rule::is_last_expr => Ok(BoxedExpr::IsLastIteration(start, end)), + Rule::iter_count_expr => { + let mut inner = primary.clone().into_inner(); + if let Some(width_str) = inner.next().unwrap().as_str().strip_prefix('u') { + let width: WidthInt = width_str.parse().unwrap(); + Ok(BoxedExpr::IterCount(width, start, end)) + } else { + unreachable!("width should always start with u") + } + } + rule => unreachable!("Expr::parse expected atom, found {:?}", rule), + } + }) + .map_infix(|lhs, op, rhs| { + let lhs_unwrap = lhs?; + let rhs_unwrap = rhs?; + let start = lhs_unwrap.start(); + let end = lhs_unwrap.end(); + let op = match op.as_rule() { + Rule::eq => BinOp::Equal, + Rule::concat => BinOp::Concat, + Rule::add => BinOp::Add, + rule => unreachable!("Expr::parse expected infix operation, found {:?}", rule), + }; + Ok(BoxedExpr::Binary( + op, + Box::new(lhs_unwrap), + Box::new(rhs_unwrap), + start, + end, + )) + }) + .map_prefix(|op, arg| { + let arg_unwrapped = arg?; + let start = op.as_span().start(); + let end = arg_unwrapped.end(); + let op = match op.as_rule() { + Rule::not => UnaryOp::Not, + rule => unreachable!("Expr::parse expected prefix operation, found {:?}", rule), + }; + Ok(BoxedExpr::Unary(op, Box::new(arg_unwrapped), start, end)) + }) + .parse(pairs) +} + +fn boxed_expr_to_expr_id( + ctx: &mut ProtocolContext, + fileid: usize, + expr: BoxedExpr, +) -> Result { + let expr_id = match expr { + BoxedExpr::Const(value, start, end) => { + let expr_id = ctx.e(Expr::Const(value)); + ctx.add_expr_loc(expr_id, start, end, fileid); + expr_id + } + BoxedExpr::Sym(symbol_id, start, end) => { + let expr_id = ctx.e(Expr::Sym(symbol_id)); + ctx.add_expr_loc(expr_id, start, end, fileid); + expr_id + } + BoxedExpr::DontCare(start, end) => { + let expr_id = ctx.e(Expr::DontCare); + ctx.add_expr_loc(expr_id, start, end, fileid); + expr_id + } + BoxedExpr::IsLastIteration(start, end) => { + let expr_id = ctx.e(Expr::IsLastIteration); + ctx.add_expr_loc(expr_id, start, end, fileid); + expr_id + } + BoxedExpr::IterCount(width, start, end) => { + let expr_id = ctx.e(Expr::IterCount(width)); + ctx.add_expr_loc(expr_id, start, end, fileid); + expr_id + } + BoxedExpr::Binary(op, lhs, rhs, start, end) => { + let lhs_id = boxed_expr_to_expr_id(ctx, fileid, *lhs)?; + let rhs_id = boxed_expr_to_expr_id(ctx, fileid, *rhs)?; + let expr_id = ctx.e(Expr::Binary(op, lhs_id, rhs_id)); + ctx.add_expr_loc(expr_id, start, end, fileid); + expr_id + } + BoxedExpr::Unary(op, arg, start, end) => { + let arg_id = boxed_expr_to_expr_id(ctx, fileid, *arg)?; + let expr_id = ctx.e(Expr::Unary(op, arg_id)); + ctx.add_expr_loc(expr_id, start, end, fileid); + expr_id + } + BoxedExpr::Slice(expr, idx1, idx2, start, end) => { + let sym_id = boxed_expr_to_expr_id(ctx, fileid, *expr)?; + let expr_id = ctx.e(Expr::Slice(sym_id, idx1, idx2)); + ctx.add_expr_loc(expr_id, start, end, fileid); + expr_id + } + }; + Ok(expr_id) +} + +fn parse_dir(diag: &mut DiagnosticHandler, fileid: usize, pair: Pair) -> Result { + match pair.as_rule() { + Rule::dir => { + let dir_str = pair.as_str(); + match dir_str { + "in" => Ok(Dir::In), + "out" => Ok(Dir::Out), + _ => { + let msg = format!("Unexpected direction string: {:?}", dir_str); + diag.emit_diagnostic_parsing(&msg, fileid, &pair, Level::Error); + Err(msg) + } + } + } + _ => { + let msg = format!( + "Unexpected rule while parsing direction: {:?}", + pair.as_rule() + ); + diag.emit_diagnostic_parsing(&msg, fileid, &pair, Level::Error); + Err(msg) + } + } +} + +fn parse_type( + diag: &mut DiagnosticHandler, + fileid: usize, + st: &mut SymbolTable, + pair: Pair, +) -> Result { + if pair.as_rule() == Rule::tpe { + let inner_type = expect_rule( + diag, + fileid, + pair.clone().into_inner().next(), + &pair, + "Expected type", + )?; + match inner_type.as_rule() { + Rule::bv_tpe => { + let width_str = inner_type.into_inner().next().unwrap().as_str(); + let size = width_str.parse::().unwrap(); + Ok(Type::BitVec(size)) + } + Rule::uint_tpe => Ok(Type::UnsignedInt), + Rule::seq_tpe => { + let inner_tpe = + parse_type(diag, fileid, st, inner_type.into_inner().next().unwrap())?; + let seq_id = st.add_seq(inner_tpe, 0); + Ok(Type::Seq(seq_id)) + } + Rule::seq_plus_tpe => { + let inner_tpe = + parse_type(diag, fileid, st, inner_type.into_inner().next().unwrap())?; + let seq_id = st.add_seq(inner_tpe, 1); + Ok(Type::Seq(seq_id)) + } + _ => { + let msg = format!("Unexpected rule while parsing type: {:?}", pair.as_rule()); + diag.emit_diagnostic_parsing(&msg, fileid, &pair, Level::Error); + Err(msg) + } + } + } else { + let msg = format!("Unexpected rule while parsing type: {:?}", pair.as_rule()); + diag.emit_diagnostic_parsing(&msg, fileid, &pair, Level::Error); + Err(msg) + } +} + +fn declare_struct_instance(st: &mut SymbolTable, struct_id: StructId, name: &str) -> SymbolId { + let sym_id = st.add_without_parent(name.to_string(), Type::Struct(struct_id), SymbolKind::Dut); + let pins = st[struct_id].pins().clone(); + for pin in pins { + let pin_name = pin.name().to_string(); + st.add_with_parent(pin_name, sym_id); + } + sym_id +} + pub struct ParserContext<'a> { pub st: &'a mut SymbolTable, pub fileid: usize, pub proto: Protocol, - pub handler: &'a mut DiagnosticHandler, + pub diag: &'a mut DiagnosticHandler, } impl ParserContext<'_> { @@ -51,12 +321,7 @@ impl ParserContext<'_> { context_pair: &pest::iterators::Pair, message: &str, ) -> Result { - option.ok_or_else(|| { - let msg = message.to_string(); - self.handler - .emit_diagnostic_parsing(&msg, self.fileid, context_pair, Level::Error); - msg - }) + expect_rule(self.diag, self.fileid, option, context_pair, message) } // Helper for getting symbol id from name with error handling @@ -70,169 +335,12 @@ impl ParserContext<'_> { .symbol_id_from_name_in_active_scope(name) .ok_or_else(|| { let msg = format!("{}: {}", message, name); - self.handler + self.diag .emit_diagnostic_parsing(&msg, self.fileid, context_pair, Level::Error); msg }) } - pub fn parse_boxed_expr(&mut self, pairs: Pairs) -> Result { - PRATT_PARSER - .map_primary(|primary| { - let start = primary.as_span().start(); - let end = primary.as_span().end(); - - match primary.as_rule() { - Rule::integer => { - // unwrap into width, radix, and then value - let mut inner = primary.clone().into_inner(); - let width: u32 = inner.next().unwrap().as_str().parse::().unwrap(); - let radix = inner.next().unwrap().as_rule(); - let value_str = inner.next().unwrap().as_str(); - - let value = match radix { - Rule::bin => u64::from_str_radix(value_str, 2), - Rule::oct => u64::from_str_radix(value_str, 8), - Rule::hex => u64::from_str_radix(value_str, 16), - Rule::dec => value_str.parse::(), - _ => unreachable!("Unexpected radix rule: {:?}", radix), - }; - - let bvv = BitVecValue::from_u64(value.unwrap(), width); - - Ok(BoxedExpr::Const(bvv, start, end)) - } - Rule::path_id => { - let path_id = primary.as_str(); - let symbol_id = self.st.symbol_id_from_name_in_active_scope(path_id); - match symbol_id { - Some(id) => Ok(BoxedExpr::Sym(id, start, end)), - None => { - let msg = format!("Referencing undefined symbol: {}", path_id); - self.handler.emit_diagnostic_parsing( - &msg, - self.fileid, - &primary, - Level::Error, - ); - Err(msg) - } - } - } - Rule::dont_care => Ok(BoxedExpr::DontCare(start, end)), - Rule::slice => { - let mut inner_rules = primary.clone().into_inner(); - let path_rule = self.expect_rule( - inner_rules.next(), - &primary, - "Expected path rule in slice expression", - )?; - let path_id = self.parse_boxed_expr(Pairs::single(path_rule))?; - let idx1_rule = inner_rules.next().unwrap(); - let idx1 = idx1_rule.as_str().parse::().unwrap(); - let idx2_rule = inner_rules.next(); - let idx2 = match idx2_rule { - Some(rule) => rule.as_str().parse::().unwrap(), - None => idx1, - }; - Ok(BoxedExpr::Slice(Box::new(path_id), idx1, idx2, start, end)) - } - Rule::expr => self.parse_boxed_expr(primary.into_inner()), - Rule::is_last_expr => Ok(BoxedExpr::IsLastIteration(start, end)), - Rule::iter_count_expr => { - let mut inner = primary.clone().into_inner(); - if let Some(width_str) = inner.next().unwrap().as_str().strip_prefix('u') { - let width: WidthInt = width_str.parse().unwrap(); - Ok(BoxedExpr::IterCount(width, start, end)) - } else { - unreachable!("width should always start with u") - } - } - rule => unreachable!("Expr::parse expected atom, found {:?}", rule), - } - }) - .map_infix(|lhs, op, rhs| { - let lhs_unwrap = lhs?; - let rhs_unwrap = rhs?; - let start = lhs_unwrap.start(); - let end = lhs_unwrap.end(); - let op = match op.as_rule() { - Rule::eq => BinOp::Equal, - Rule::concat => BinOp::Concat, - Rule::add => BinOp::Add, - rule => unreachable!("Expr::parse expected infix operation, found {:?}", rule), - }; - Ok(BoxedExpr::Binary( - op, - Box::new(lhs_unwrap), - Box::new(rhs_unwrap), - start, - end, - )) - }) - .map_prefix(|op, arg| { - let arg_unwrapped = arg?; - let start = op.as_span().start(); - let end = arg_unwrapped.end(); - let op = match op.as_rule() { - Rule::not => UnaryOp::Not, - rule => unreachable!("Expr::parse expected prefix operation, found {:?}", rule), - }; - Ok(BoxedExpr::Unary(op, Box::new(arg_unwrapped), start, end)) - }) - .parse(pairs) - } - - fn boxed_expr_to_expr_id(&mut self, expr: BoxedExpr) -> Result { - let expr_id = match expr { - BoxedExpr::Const(value, start, end) => { - let expr_id = self.proto.e(Expr::Const(value)); - self.proto.add_expr_loc(expr_id, start, end, self.fileid); - expr_id - } - BoxedExpr::Sym(symbol_id, start, end) => { - let expr_id = self.proto.e(Expr::Sym(symbol_id)); - self.proto.add_expr_loc(expr_id, start, end, self.fileid); - expr_id - } - BoxedExpr::DontCare(start, end) => { - let expr_id = self.proto.e(Expr::DontCare); - self.proto.add_expr_loc(expr_id, start, end, self.fileid); - expr_id - } - BoxedExpr::IsLastIteration(start, end) => { - let expr_id = self.proto.e(Expr::IsLastIteration); - self.proto.add_expr_loc(expr_id, start, end, self.fileid); - expr_id - } - BoxedExpr::IterCount(width, start, end) => { - let expr_id = self.proto.e(Expr::IterCount(width)); - self.proto.add_expr_loc(expr_id, start, end, self.fileid); - expr_id - } - BoxedExpr::Binary(op, lhs, rhs, start, end) => { - let lhs_id = self.boxed_expr_to_expr_id(*lhs)?; - let rhs_id = self.boxed_expr_to_expr_id(*rhs)?; - let expr_id = self.proto.e(Expr::Binary(op, lhs_id, rhs_id)); - self.proto.add_expr_loc(expr_id, start, end, self.fileid); - expr_id - } - BoxedExpr::Unary(op, arg, start, end) => { - let arg_id = self.boxed_expr_to_expr_id(*arg)?; - let expr_id = self.proto.e(Expr::Unary(op, arg_id)); - self.proto.add_expr_loc(expr_id, start, end, self.fileid); - expr_id - } - BoxedExpr::Slice(expr, idx1, idx2, start, end) => { - let sym_id = self.boxed_expr_to_expr_id(*expr)?; - let expr_id = self.proto.e(Expr::Slice(sym_id, idx1, idx2)); - self.proto.add_expr_loc(expr_id, start, end, self.fileid); - expr_id - } - }; - Ok(expr_id) - } - fn parse_struct(&mut self, pair: pest::iterators::Pair) -> Result { let mut inner_rules = pair.clone().into_inner(); let struct_name = self @@ -283,25 +391,17 @@ impl ParserContext<'_> { let dut_struct = self.st[struct_id].clone(); // now that we know the DUT parameter name, we can construct the scope let scope_name = format!("{}::{}", dut_struct.name(), self.proto.name); - self.proto.ctx.scope = self.st.add_protocol_scope(&scope_name); - - let dut_symbol_id = self.st.add_without_parent( - path_id_1.to_string(), - Type::Struct(struct_id), - SymbolKind::Dut, - ); + self.proto.ctx.scope = self.st.enter_scope(&scope_name); + let dut_symbol_id = + declare_struct_instance(self.st, struct_id, path_id_1); self.proto.type_param = Some(dut_symbol_id); - for pin in dut_struct.pins() { - let pin_name = pin.name().to_string(); - self.st.add_with_parent(pin_name, dut_symbol_id); - } } _ => { let msg = format!( "Attempted to parse DUT type param. Unexpected rule: {:?}", inner_pair.as_rule() ); - self.handler.emit_diagnostic_parsing( + self.diag.emit_diagnostic_parsing( &msg, self.fileid, &inner_pair, @@ -312,7 +412,7 @@ impl ParserContext<'_> { } } else { // create scope without DUT struct prefix - self.proto.ctx.scope = self.st.add_protocol_scope(&self.proto.name); + self.proto.ctx.scope = self.st.enter_scope(&self.proto.name); } if let Some(arglist_pair) = inner_rules.peek() { @@ -353,7 +453,7 @@ impl ParserContext<'_> { "Unexpected rule while parsing transaction: {:?}", pair.as_rule() ); - self.handler + self.diag .emit_diagnostic_parsing(&msg, self.fileid, &pair, Level::Error); Err(msg) } @@ -361,9 +461,7 @@ impl ParserContext<'_> { } fn parse_expr(&mut self, pairs: Pairs) -> Result { - let boxed_expr = self.parse_boxed_expr(pairs)?; - let expr_id = self.boxed_expr_to_expr_id(boxed_expr)?; - Ok(expr_id) + parse_expr(self.st, self.diag, self.fileid, &mut self.proto.ctx, pairs) } fn parse_stmt_block(&mut self, stmt_pairs: Pairs) -> Result { @@ -400,12 +498,8 @@ impl ParserContext<'_> { "Unexpected rule while parsing statement block: {:?}", inner_pair.as_rule() ); - self.handler.emit_diagnostic_parsing( - &msg, - self.fileid, - &inner_pair, - Level::Error, - ); + self.diag + .emit_diagnostic_parsing(&msg, self.fileid, &inner_pair, Level::Error); return Err(msg); } }; @@ -452,7 +546,7 @@ impl ParserContext<'_> { "Step call expected single positive integer as argument, got {}.", num_steps ); - self.handler + self.diag .emit_diagnostic_parsing(&msg, self.fileid, &pair, Level::Error); return Err(msg); } @@ -583,12 +677,8 @@ impl ParserContext<'_> { "Received unexpected rule while parsing arglist: {:?}", inner_pair.as_rule() ); - self.handler.emit_diagnostic_parsing( - &msg, - self.fileid, - &inner_pair, - Level::Error, - ); + self.diag + .emit_diagnostic_parsing(&msg, self.fileid, &inner_pair, Level::Error); return Err(msg); } } @@ -621,7 +711,7 @@ impl ParserContext<'_> { &inner_pair, "Expected type in field", )?; - let dir = self.parse_dir(dir_pair)?; + let dir = parse_dir(self.diag, self.fileid, dir_pair)?; let id = id_pair.as_str(); let tpe = self.parse_type(tpe_pair)?; let field = Field::new(id.to_string(), dir, tpe); @@ -638,12 +728,8 @@ impl ParserContext<'_> { "Unexpected rule while parsing fields: {:?}", inner_pair.as_rule() ); - self.handler.emit_diagnostic_parsing( - &msg, - self.fileid, - &inner_pair, - Level::Error, - ); + self.diag + .emit_diagnostic_parsing(&msg, self.fileid, &inner_pair, Level::Error); return Err(msg); } } @@ -651,89 +737,247 @@ impl ParserContext<'_> { Ok((fields, symbols)) } - fn parse_dir(&mut self, pair: pest::iterators::Pair) -> Result { - match pair.as_rule() { - Rule::dir => { - let dir_str = pair.as_str(); - match dir_str { - "in" => Ok(Dir::In), - "out" => Ok(Dir::Out), - _ => { - let msg = format!("Unexpected direction string: {:?}", dir_str); - self.handler.emit_diagnostic_parsing( - &msg, - self.fileid, - &pair, - Level::Error, - ); - Err(msg) - } - } + fn parse_type(&mut self, pair: pest::iterators::Pair) -> Result { + parse_type(self.diag, self.fileid, self.st, pair) + } +} + +struct ModuleCtx<'a> { + st: &'a mut SymbolTable, + fileid: usize, + diag: &'a mut DiagnosticHandler, + m: RemapModule, +} + +type Pair<'a> = pest::iterators::Pair<'a, Rule>; + +enum EntryTpe { + T(Type), + PosedgeClock, +} + +impl ModuleCtx<'_> { + // Helper method for expected rule errors + fn expect_rule( + &mut self, + option: Option, + context_pair: &Pair, + message: &str, + ) -> Result { + expect_rule(self.diag, self.fileid, option, context_pair, message) + } + + fn on_module_impl_list(pair: Pair, out: &mut Vec) -> Result<(), String> { + for inner_pair in pair.into_inner() { + match inner_pair.as_rule() { + Rule::non_path_id => out.push(inner_pair.as_str().to_string()), + Rule::module_impl_list => Self::on_module_impl_list(inner_pair, out)?, + other => unreachable!("Unexpected rule: {:?}", other), } - _ => { - let msg = format!( - "Unexpected rule while parsing direction: {:?}", - pair.as_rule() - ); - self.handler - .emit_diagnostic_parsing(&msg, self.fileid, &pair, Level::Error); - Err(msg) + } + Ok(()) + } + + fn on_module_entries(&mut self, pair: Pair) -> Result<(), String> { + for inner_pair in pair.into_inner() { + match inner_pair.as_rule() { + Rule::module_entry => self.on_module_entry(inner_pair)?, + Rule::module_entries => self.on_module_entries(inner_pair)?, + other => unreachable!("Unexpected rule: {:?}", other), } } + Ok(()) } - fn parse_type(&mut self, pair: pest::iterators::Pair) -> Result { - if pair.as_rule() == Rule::tpe { - let inner_type = - self.expect_rule(pair.clone().into_inner().next(), &pair, "Expected type")?; - match inner_type.as_rule() { - Rule::bv_tpe => { - let width_str = inner_type.into_inner().next().unwrap().as_str(); - let size = width_str.parse::().unwrap(); - Ok(Type::BitVec(size)) - } - Rule::uint_tpe => Ok(Type::UnsignedInt), - Rule::seq_tpe => { - let inner_tpe = self.parse_type(inner_type.into_inner().next().unwrap())?; - let seq_id = self.st.add_seq(inner_tpe, 0); - Ok(Type::Seq(seq_id)) - } - Rule::seq_plus_tpe => { - let inner_tpe = self.parse_type(inner_type.into_inner().next().unwrap())?; - let seq_id = self.st.add_seq(inner_tpe, 1); - Ok(Type::Seq(seq_id)) - } - _ => { - let msg = format!("Unexpected rule while parsing type: {:?}", pair.as_rule()); - self.handler - .emit_diagnostic_parsing(&msg, self.fileid, &pair, Level::Error); - Err(msg) + fn on_module_entry(&mut self, pair: Pair) -> Result<(), String> { + let mut field_inner = pair.clone().into_inner(); + let dir_pair = + self.expect_rule(field_inner.next(), &pair, "Expected direction in mapping")?; + let id_pair = + self.expect_rule(field_inner.next(), &pair, "Expected identifier in mapping")?; + let tpe_pair = self.expect_rule(field_inner.next(), &pair, "Expected type in mapping")?; + let dir = parse_dir(self.diag, self.fileid, dir_pair)?; + let id = id_pair.as_str(); + let tpe = self.parse_entry_type(tpe_pair)?; + + match tpe { + EntryTpe::T(tpe) => { + let remap_rule = + self.expect_rule(field_inner.next(), &pair, "Pin remap expected.")?; + self.on_pin_remap(dir, id.into(), tpe, &pair, remap_rule) + } + EntryTpe::PosedgeClock => { + if matches!(self.m.clock, Clock::None) { + if dir == Dir::In { + self.m.clock = Clock::Posedge(id.to_string()); + Ok(()) + } else { + let msg = "A clock must be an input, not an output".into(); + self.err(msg, &pair) + } + } else { + let msg = format!( + "Module {} already has a clock declared: {:?}", + self.m.name, self.m.clock + ); + self.err(msg, &pair) } } + } + } + + fn on_pin_remap( + &mut self, + dir: Dir, + name: String, + tpe: Type, + context: &Pair, + rule: Pair, + ) -> Result<(), String> { + assert_eq!(rule.as_rule(), Rule::pin_remap); + let mut in_remap = rule.into_inner(); + let expr_rule = + self.expect_rule(in_remap.next(), context, "pin remaps require an expression")?; + let rhs = parse_expr( + self.st, + self.diag, + self.fileid, + &mut self.m.ctx, + expr_rule.into_inner(), + )?; + let cond = if let Some(cond_rule) = in_remap.next() { + parse_expr( + self.st, + self.diag, + self.fileid, + &mut self.m.ctx, + cond_rule.into_inner(), + )? } else { - let msg = format!("Unexpected rule while parsing type: {:?}", pair.as_rule()); - self.handler - .emit_diagnostic_parsing(&msg, self.fileid, &pair, Level::Error); - Err(msg) + self.m.ctx.expr_true() + }; + self.m.mappings.push(Mapping { + name, + dir, + tpe, + rhs, + cond, + }); + + Ok(()) + } + + fn err(&mut self, msg: String, pair: &Pair) -> Result<(), String> { + self.diag + .emit_diagnostic_parsing(&msg, self.fileid, pair, Level::Error); + Err(msg) + } + + fn parse_entry_type(&mut self, pair: pest::iterators::Pair) -> Result { + let pair = pair.into_inner().next().unwrap(); + match pair.as_rule() { + Rule::clock_tpe => Ok(EntryTpe::PosedgeClock), + Rule::tpe => Ok(EntryTpe::T(parse_type( + self.diag, + self.fileid, + self.st, + pair, + )?)), + other => unreachable!("Unexpected rule: {:?}", other), } } + + fn on_module_def(&mut self, pair: Pair) -> Result<(), String> { + let mut inner_rules = pair.clone().into_inner(); + let name = self + .expect_rule(inner_rules.next(), &pair, "Expected struct name")? + .as_str(); + self.m.name = name.to_string(); + self.m.ctx.name = name.to_string(); + self.m.ctx.scope = self.st.enter_scope(name); + + // interfaces / structs implemented + let impl_lst = self.expect_rule( + inner_rules.next(), + &pair, + "Expected list of implemented interfaces/structs", + )?; + let mut impl_struct_names = vec![]; + Self::on_module_impl_list(impl_lst, &mut impl_struct_names)?; + self.m.implements = impl_struct_names + .iter() + .map(|name| { + self.st + .struct_id_from_name(name) + .ok_or_else(|| format!("Undefined struct: {}", name)) + }) + .collect::>()?; + // declare interface pins so that they can be used in the body + for &struct_id in &self.m.implements { + let instance_name = self.st[struct_id].name().to_string(); + declare_struct_instance(self.st, struct_id, &instance_name); + } + + // body + if let Some(body) = inner_rules.next() { + self.on_module_entries(body)?; + } + self.st.exit_scope(); + Ok(()) + } } -pub fn parse_file( - filename: impl AsRef, +pub(crate) fn parse_files( + filenames: &[impl AsRef], handler: &mut DiagnosticHandler, -) -> Result<(SymbolTable, Vec), String> { - parse_file_with_name(filename.as_ref(), filename.as_ref(), handler) +) -> Result { + let mut st = SymbolTable::default(); + let mut protos = vec![]; + let mut remaps = vec![]; + for filename in filenames { + let display_name = filename.as_ref().to_str().unwrap(); + parse_file_internal( + filename, + display_name, + &mut st, + &mut protos, + &mut remaps, + handler, + )?; + } + Ok(Ast { st, protos, remaps }) } -pub fn parse_file_with_name( +#[cfg(test)] +pub(crate) fn parse_file_with_name( filename: impl AsRef, display_name: impl AsRef, handler: &mut DiagnosticHandler, -) -> Result<(SymbolTable, Vec), String> { - let name = display_name.as_ref().to_str().unwrap().to_string(); +) -> Result { + let mut st = SymbolTable::default(); + let mut protos = vec![]; + let mut remaps = vec![]; + parse_file_internal( + filename, + display_name.as_ref().to_str().unwrap(), + &mut st, + &mut protos, + &mut remaps, + handler, + )?; + Ok(Ast { st, protos, remaps }) +} + +fn parse_file_internal( + filename: impl AsRef, + display_name: &str, + st: &mut SymbolTable, + protos: &mut Vec, + remaps: &mut Vec, + diag: &mut DiagnosticHandler, +) -> Result<(), String> { let input = std::fs::read_to_string(filename).map_err(|e| format!("failed to load: {}", e))?; - let fileid = handler.add_file(name, input.clone()); + let fileid = diag.add_file(display_name.into(), input.clone()); let res = ProtocolParser::parse(Rule::file, &input); match res { @@ -744,58 +988,64 @@ pub fn parse_file_with_name( InputLocation::Span(span) => span, }; let msg: String = format!("Lexing failed: {}", err.variant.message()); - handler.emit_diagnostic_lexing(&msg, fileid, start, end, Level::Error); + diag.emit_diagnostic_lexing(&msg, fileid, start, end, Level::Error); return Err(msg); } } let pairs = ProtocolParser::parse(Rule::file, &input).unwrap(); let inner = pairs.clone().next().unwrap().into_inner(); - let mut st = SymbolTable::default(); - let mut protos = vec![]; for pair in inner { - if pair.as_rule() == Rule::struct_def { - // dummy context to set up the symbol table with the struct; the transaction here is irrelevant - let mut context = ParserContext { - st: &mut st, - fileid, - proto: Protocol::new("".to_string(), ROOT_SCOPE), - handler, - }; - if let Err(e) = context.parse_struct(pair) { - let msg = format!("Error parsing struct: {}", e); - eprintln!("{}", msg); - return Err(msg); + match pair.as_rule() { + Rule::struct_def => { + // dummy context to set up the symbol table with the struct; the transaction here is irrelevant + let mut context = ParserContext { + st, + fileid, + proto: Protocol::new("".to_string(), ROOT_SCOPE), + diag, + }; + context.parse_struct(pair)?; } - } else if pair.as_rule() == Rule::fun { - // set up an base symbol table containing the struct, and an empty transaction for the parser to parse into - let mut context: ParserContext<'_> = ParserContext { - st: &mut st, - fileid, - proto: Protocol::new("".to_string(), ROOT_SCOPE), - handler, - }; - context.parse_transaction(pair)?; - let proto = context.proto; - assert!(!proto.name.is_empty()); - assert_ne!(proto.ctx.scope, ROOT_SCOPE); - protos.push(proto); + Rule::fun => { + // set up an base symbol table containing the struct, and an empty transaction for the parser to parse into + let mut context = ParserContext { + st, + fileid, + proto: Protocol::new("".to_string(), ROOT_SCOPE), + diag, + }; + context.parse_transaction(pair)?; + let proto = context.proto; + assert!(!proto.name.is_empty()); + assert_ne!(proto.ctx.scope, ROOT_SCOPE); + protos.push(proto); + } + Rule::module_def => { + let m = RemapModule { + ctx: ProtocolContext::new("".to_string(), ROOT_SCOPE), + name: "".to_string(), + clock: Default::default(), + implements: vec![], + mappings: vec![], + }; + let mut ctx = ModuleCtx { + st, + fileid, + diag, + m, + }; + ctx.on_module_def(pair)?; + let m = ctx.m; + assert!(!m.name.is_empty()); + assert_ne!(m.ctx.scope, ROOT_SCOPE); + remaps.push(m); + } + Rule::EOI => {} // OK + other => todo!("Add support for {other:?}"), } } - Ok((st, protos)) -} -pub fn parsing_helper( - transaction_filename: &str, - handler: &mut DiagnosticHandler, -) -> (SymbolTable, Vec) { - let result = parse_file(transaction_filename, handler); - match result { - Ok(success_vec) => success_vec, - Err(err) => panic!( - "Failed to parse file: {}\nError: {}", - transaction_filename, err - ), - } + Ok(()) } diff --git a/protocols/src/frontend/protocols.pest b/protocols/src/frontend/protocols.pest index e44f69cb..01e143f2 100644 --- a/protocols/src/frontend/protocols.pest +++ b/protocols/src/frontend/protocols.pest @@ -43,6 +43,7 @@ expr = { atom ~ (bin_op ~ atom)*} id = _{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_" )* } path_id = @{ id ~ ("." ~ id)* } +non_path_id = @{ id } tpe = { seq_plus_tpe | seq_tpe | uint_tpe | bv_tpe } uint_tpe = { "uint" } bv_tpe = { "u" ~ dec_integer } @@ -83,7 +84,17 @@ annotation_name = { "idle" } // Structs field = {dir ~ path_id ~ ":" ~ tpe } field_list = { (field ~ "," ~ field_list) | field ~ ","? } -struct_def = { "struct" ~ path_id ~ "{" ~ field_list? ~ "}" } +struct_def = { ("struct" | "interface") ~ path_id ~ "{" ~ field_list? ~ "}" } + +// modules +module_impl_list = { (non_path_id ~ "+" ~ module_impl_list) | non_path_id } +clock_tpe = { "clock" ~ "@posedge" } +module_entry_tpe = { clock_tpe | tpe } +pin_remap_constraint = {"with" ~ expr} +pin_remap = { "=" ~ expr ~ pin_remap_constraint?} +module_entry = {dir ~ path_id ~ ":" ~ module_entry_tpe ~ pin_remap? } +module_entries = { (module_entry ~ "," ~ module_entries) | module_entry ~ ","? } +module_def = { "module" ~ non_path_id ~ ":" ~ module_impl_list ~ "{" ~ module_entries ~ "}"} fun = { annotation? ~ "prot" ~ path_id ~ type_param ~ "(" ~ arglist? ~ ")" ~ "{" ~ stmt* ~ "}" } -file = { SOI ~ (fun | struct_def)* ~ EOI } \ No newline at end of file +file = { SOI ~ (fun | struct_def | module_def)* ~ EOI } \ No newline at end of file diff --git a/protocols/src/frontend/serialize.rs b/protocols/src/frontend/serialize.rs index 18920eb1..34f4c06b 100644 --- a/protocols/src/frontend/serialize.rs +++ b/protocols/src/frontend/serialize.rs @@ -17,9 +17,9 @@ use crate::frontend::symbol::*; use crate::interpreter::ExprValue; /// Serializes a `Vec` of `(Transaction, SymbolTable)` pairs to a `String` -pub fn serialize_to_string(st: &SymbolTable, protos: &[Protocol]) -> std::io::Result { +pub fn serialize_to_string(ast: &Ast) -> std::io::Result { let mut out = Vec::new(); - serialize(&mut out, st, protos)?; + serialize(&mut out, ast)?; let out = String::from_utf8(out).unwrap(); Ok(out) } @@ -353,16 +353,13 @@ pub fn serialize_structs( /// Serializes a `Vec` of `(SymbolTable, Transaction)` pairs to the provided /// output buffer `out` -pub fn serialize( - out: &mut impl Write, - st: &SymbolTable, - protos: &[Protocol], -) -> std::io::Result<()> { +pub fn serialize(out: &mut impl Write, ast: &Ast) -> std::io::Result<()> { + let st = &ast.st; if !st.struct_ids().is_empty() { serialize_structs(out, st, st.struct_ids())?; } - for proto in protos { + for proto in &ast.protos { write!(out, "fn {}", proto.name)?; if let Some(type_param) = proto.type_param { @@ -412,13 +409,13 @@ pub fn serialize( pub mod tests { use std::path::Path; - use baa::BitVecValue; - use insta::Settings; - use strip_ansi_escapes::strip_str; - use super::*; use crate::frontend::diagnostic::DiagnosticHandler; use crate::frontend::parser::parse_file_with_name; + use baa::BitVecValue; + use clap::ColorChoice; + use insta::Settings; + use strip_ansi_escapes::strip_str; fn snap(name: &str, content: String) { let mut settings = Settings::clone_current(); @@ -429,11 +426,11 @@ pub mod tests { } fn test_helper(filename: &str, snap_name: &str) { - let mut handler = DiagnosticHandler::default(); + let mut handler = DiagnosticHandler::new(ColorChoice::Never, false, false, false); let result = parse_file_with_name(filename, display_filename(filename), &mut handler); let content = match result { - Ok((st, protos)) => serialize_to_string(&st, &protos).unwrap(), + Ok(ast) => serialize_to_string(&ast).unwrap(), Err(_) => strip_str(handler.error_string()), }; println!("{}", content); @@ -481,6 +478,7 @@ pub mod tests { } #[test] + #[ignore] // protocol has an error fn test_mul_invalid_prot() { test_helper("../tests/multipliers/mul_invalid.prot", "mul_invalid"); } @@ -590,7 +588,7 @@ pub mod tests { Field::new("b".to_string(), Dir::In, Type::BitVec(32)), ], ); - let scope = symbols.add_protocol_scope("easycond"); + let scope = symbols.enter_scope("easycond"); let dut = symbols.add_without_parent( "dut".to_string(), Type::Struct(dut_struct), @@ -624,7 +622,12 @@ pub mod tests { easycond.s(Stmt::Assign(b, one_expr)), ]; easycond.body = easycond.s(Stmt::Block(body)); - println!("{}", serialize_to_string(&symbols, &[easycond]).unwrap()); symbols.exit_scope(); + let ast = Ast { + st: symbols, + protos: vec![easycond], + remaps: vec![], + }; + println!("{}", serialize_to_string(&ast).unwrap()); } } diff --git a/protocols/src/frontend/static_fork_step_check.rs b/protocols/src/frontend/static_fork_step_check.rs index bb5be74c..1b6a63fb 100644 --- a/protocols/src/frontend/static_fork_step_check.rs +++ b/protocols/src/frontend/static_fork_step_check.rs @@ -1,21 +1,23 @@ use crate::frontend::ast::*; use crate::frontend::diagnostic::{DiagnosticHandler, Level}; -use crate::frontend::symbol::SymbolTable; /// Conservative static analysis for our step and fork rules: /// 1. fork must come after a step /// 2. any execution of a protocol must fork exactly 0 or 1 time /// 3. protocols must end in a step /// 4. a loop must contain at least one step, otherwise the protocol can never terminate -pub fn check_step_and_fork( - _st: &SymbolTable, - protocols: &[Protocol], - diag: &mut DiagnosticHandler, -) -> u32 { +pub fn check_step_and_fork(ast: &Ast, diag: &mut DiagnosticHandler) -> u32 { let mut error_count = 0; - for proto in protocols { - let final_stmts = - analyze_stmt(diag, &mut error_count, proto, proto.body, State::default()).final_stmts; + for proto in &ast.protos { + let final_stmts = analyze_stmt( + diag, + &mut error_count, + proto, + proto.body, + State::default(), + false, + ) + .final_stmts; match final_stmts.as_slice() { [one] if !is_step(proto, *one) => { let msg = format!("Final statement in `{}` is not a step.", proto.name); @@ -38,11 +40,12 @@ fn analyze_stmt( proto: &Protocol, stmt: StmtId, mut state: State, + maybe_unreachable: bool, ) -> State { match &proto[stmt] { Stmt::Block(stmts) => { for &stmt in stmts { - state = analyze_stmt(diag, error_count, proto, stmt, state); + state = analyze_stmt(diag, error_count, proto, stmt, state, maybe_unreachable); } state } @@ -63,16 +66,23 @@ fn analyze_stmt( state.forks = match state.forks { ForkCount::Unknown => ForkCount::Unknown, ForkCount::Zero => ForkCount::One(stmt), - ForkCount::One(prev) => { - diag.emit_diagnostic_multi_stmt( - proto, - &[prev, stmt], - &["first fork", "second fork"], - "A single protocol may only fork once during its execution.", - Level::Error, - ); - *error_count += 1; - ForkCount::One(stmt) + ForkCount::One(prev) | ForkCount::MoreThanOne(prev, _) => { + if !maybe_unreachable { + diag.emit_diagnostic_multi_stmt( + proto, + &[prev, stmt], + &["first fork", "second fork"], + "A single protocol may only fork once during its execution.", + Level::Error, + ); + *error_count += 1; + } + let new_count = if let ForkCount::MoreThanOne(_, old) = state.forks { + old + 1 + } else { + 2 + }; + ForkCount::MoreThanOne(prev, new_count) } }; state.final_stmts = vec![stmt]; @@ -84,7 +94,8 @@ fn analyze_stmt( } // control flow Stmt::While(_, body) | Stmt::RepeatLoop(_, body) | Stmt::ForInLoop(_, _, body) => { - let mut body_state = analyze_stmt(diag, error_count, proto, *body, State::default()); + let mut body_state = + analyze_stmt(diag, error_count, proto, *body, State::default(), true); if body_state.steps == StepCount::Bounded(0) { diag.emit_diagnostic_stmt( proto, @@ -116,8 +127,8 @@ fn analyze_stmt( state } Stmt::IfElse(_, tru, fals) => { - let after_tru = analyze_stmt(diag, error_count, proto, *tru, state.clone()); - let mut after_fals = analyze_stmt(diag, error_count, proto, *fals, state); + let after_tru = analyze_stmt(diag, error_count, proto, *tru, state.clone(), true); + let mut after_fals = analyze_stmt(diag, error_count, proto, *fals, state, true); let steps = if after_tru.steps == after_fals.steps { after_tru.steps } else { @@ -169,4 +180,5 @@ enum ForkCount { Unknown, Zero, One(StmtId), + MoreThanOne(StmtId, u64), } diff --git a/protocols/src/frontend/symbol.rs b/protocols/src/frontend/symbol.rs index 0a8f1ad1..0e3aedc3 100644 --- a/protocols/src/frontend/symbol.rs +++ b/protocols/src/frontend/symbol.rs @@ -282,7 +282,7 @@ impl Default for SymbolTable { } impl SymbolTable { - pub fn add_protocol_scope(&mut self, name: &str) -> ScopeId { + pub fn enter_scope(&mut self, name: &str) -> ScopeId { assert_eq!( self.active_scope, ROOT_SCOPE, "nested scopes are not supported" @@ -408,7 +408,7 @@ impl SymbolTable { id } - pub fn struct_id_from_name(&mut self, name: &str) -> Option { + pub fn struct_id_from_name(&self, name: &str) -> Option { self.by_name_struct.get(name).copied() } diff --git a/protocols/src/frontend/typecheck.rs b/protocols/src/frontend/typecheck.rs index 2aa3dcc1..122e1ed6 100644 --- a/protocols/src/frontend/typecheck.rs +++ b/protocols/src/frontend/typecheck.rs @@ -5,14 +5,14 @@ // author: Francis Pham // author: Ernest Ng -use anyhow::{Context, anyhow}; -use baa::BitVecOps; - use crate::frontend::ast::*; use crate::frontend::diagnostic::*; use crate::frontend::serialize::*; use crate::frontend::static_checks::{check_assertion_wf, check_assignment_wf, check_condition_wf}; use crate::frontend::symbol::*; +use anyhow::{Context, anyhow, bail}; +use baa::BitVecOps; +use rustc_hash::FxHashSet; /// Helper function for emitting error messages related to invalid bit-slices fn emit_bitslice_type_error( @@ -20,7 +20,7 @@ fn emit_bitslice_type_error( end_idx: u32, expr_width: u32, handler: &mut DiagnosticHandler, - tr: &Protocol, + tr: &ProtocolContext, expr_id: &ExprId, ) -> anyhow::Result { let error_msg = format!( @@ -34,7 +34,7 @@ fn emit_bitslice_type_error( /// Typechecks an expression (identified by its `ExprId`) with respect to /// `Transaction` `tr`, `SymbolTable` `st` & the associated `DiagnosticHandler` fn type_check_expr( - tr: &Protocol, + tr: &ProtocolContext, st: &SymbolTable, handler: &mut DiagnosticHandler, expr_id: &ExprId, @@ -349,20 +349,147 @@ fn type_check_stmt( } } +fn find_symbols(ctx: &ProtocolContext, e: ExprId) -> FxHashSet { + let mut out = FxHashSet::default(); + let mut todo = vec![e]; + while let Some(e) = todo.pop() { + match ctx[e].clone() { + Expr::Const(_) => {} + Expr::Sym(s) => { + out.insert(s); + } + Expr::DontCare => {} + Expr::Binary(_, a, b) => { + todo.push(a); + todo.push(b); + } + Expr::Unary(_, a) => { + todo.push(a); + } + Expr::Slice(a, _, _) => { + todo.push(a); + } + Expr::IsLastIteration => {} + Expr::IterCount(_) => {} + } + } + out +} + +/// For input pins, +fn is_input_map_rhs_ok( + ctx: &ProtocolContext, + st: &SymbolTable, + diag: &mut DiagnosticHandler, + name: &str, + rhs: ExprId, + cond: ExprId, +) -> bool { + let rhs_syms: Vec<_> = find_symbols(ctx, rhs).into_iter().collect(); + let cond_syms: Vec<_> = find_symbols(ctx, cond).into_iter().collect(); + + if let &[other] = rhs_syms.as_slice() + && st[other].is_in_port() + { + if cond_syms.is_empty() || cond_syms == rhs_syms { + true + } else { + let msg = format!( + "The condition for the mapping of input pin `{name}` can only depend on the same pin as the mapping." + ); + diag.emit_diagnostic_expr(ctx, &cond, &msg, Level::Error); + false + } + } else { + let msg = format!("Input pin `{name}` must be mapped to a single other pin."); + diag.emit_diagnostic_expr(ctx, &rhs, &msg, Level::Error); + false + } +} + +/// For output pins, only a direct equivalence to another output pin is allowed. +fn is_output_map_rhs_ok( + ctx: &ProtocolContext, + st: &SymbolTable, + diag: &mut DiagnosticHandler, + name: &str, + rhs: ExprId, + cond: ExprId, +) -> bool { + if let Expr::Sym(sym_id) = ctx[rhs] + && st[sym_id].is_out_port() + { + if cond == ctx.expr_true() { + true + } else { + let msg = format!("Output pins are not allowed to have a `with` condition ({name})."); + diag.emit_diagnostic_expr(ctx, &cond, &msg, Level::Error); + false + } + } else { + let msg = format!("Output pin `{name}` can only be mapped directly to another output pin."); + diag.emit_diagnostic_expr(ctx, &rhs, &msg, Level::Error); + false + } +} + /// Typechecks every function contained in the argument `Vec` /// of `(Transaction, SymbolTable)` pairs -pub fn type_check( - st: &mut SymbolTable, - protos: &[Protocol], - handler: &mut DiagnosticHandler, -) -> anyhow::Result<()> { - for proto in protos { +pub(crate) fn type_check(ast: &mut Ast, diag: &mut DiagnosticHandler) -> anyhow::Result<()> { + for proto in &ast.protos { // debug sanity check to make sure the symbol table and the argument list are in sync for (index, arg) in proto.args.iter().enumerate() { - debug_assert_eq!(st[arg].as_arg_index(), Some(index), "{}", st[arg].name()); + debug_assert_eq!( + ast.st[arg].as_arg_index(), + Some(index), + "{}", + ast.st[arg].name() + ); } - type_check_stmt(proto, st, handler, &proto.body)?; + type_check_stmt(proto, &mut ast.st, diag, &proto.body)?; + } + + for remap in &ast.remaps { + let mut found_err = false; + let ctx = &remap.ctx; + for m in &remap.mappings { + // check that the rhs is of the correct type + let rhs_tpe = type_check_expr(ctx, &ast.st, diag, &m.rhs)?; + if !rhs_tpe.is_equivalent(&m.tpe) { + let error_msg = format!( + "Pin remapping has incompatible type: {} : {} vs. {} : {}", + m.name, + serialize_type(&ast.st, m.tpe), + serialize_expr(ctx, &ast.st, &m.rhs), + serialize_type(&ast.st, rhs_tpe) + ); + diag.emit_diagnostic_expr(&remap.ctx, &m.rhs, &error_msg, Level::Error); + found_err = true; + } + + // the condition must be boolean + let cond_type = type_check_expr(ctx, &ast.st, diag, &m.cond)?; + if !cond_type.is_equivalent(&Type::BitVec(1)) { + let error_msg = format!( + "Pin remapping condition for {} does not have bv<1> type: {} : {}", + m.name, + serialize_expr(ctx, &ast.st, &m.cond), + serialize_type(&ast.st, cond_type) + ); + diag.emit_diagnostic_expr(&remap.ctx, &m.cond, &error_msg, Level::Error); + found_err = true; + } + + let rhs_ok = match m.dir { + Dir::In => is_input_map_rhs_ok(ctx, &ast.st, diag, &m.name, m.rhs, m.cond), + Dir::Out => is_output_map_rhs_ok(ctx, &ast.st, diag, &m.name, m.rhs, m.cond), + }; + found_err |= !rhs_ok; + } + if found_err { + bail!("Type error in {}", remap.name); + } } Ok(()) } @@ -371,13 +498,12 @@ pub fn type_check( mod tests { use std::path::Path; + use super::*; + use crate::frontend::parser::parse_file_with_name; use baa::BitVecValue; use insta::Settings; use strip_ansi_escapes::strip_str; - use super::*; - use crate::frontend::parser::parse_file_with_name; - fn snap(name: &str, content: String) { let mut settings = Settings::clone_current(); settings.set_snapshot_path(Path::new("../tests/snapshots")); @@ -390,8 +516,8 @@ mod tests { let mut handler = DiagnosticHandler::default(); let result = parse_file_with_name(file_name, display_filename(file_name), &mut handler); let content = match result { - Ok((mut st, protos)) => { - let _ = type_check(&mut st, &protos, &mut handler); + Ok(mut ast) => { + let _ = type_check(&mut ast, &mut handler); strip_str(handler.error_string()) } Err(_) => strip_str(handler.error_string()), @@ -477,7 +603,7 @@ mod tests { fn function_argument_test() { let mut handler = DiagnosticHandler::default(); let mut symbols = SymbolTable::default(); - let scope = symbols.add_protocol_scope("func_arg_invalid"); + let scope = symbols.enter_scope("func_arg_invalid"); let a = symbols.add_without_parent("a".to_string(), Type::BitVec(1), SymbolKind::Arg(0)); let b: SymbolId = symbols.add_without_parent("b".to_string(), Type::BitVec(1), SymbolKind::Arg(1)); @@ -512,6 +638,11 @@ mod tests { let body = vec![a_assign, fork, c_assign, step, s_assign]; prot.body = prot.s(Stmt::Block(body)); symbols.exit_scope(); - let _ = type_check(&mut symbols, &[prot], &mut handler); + let mut ast = Ast { + st: symbols, + protos: vec![prot], + remaps: vec![], + }; + let _ = type_check(&mut ast, &mut handler); } } diff --git a/protocols/src/ir/graphviz.rs b/protocols/src/ir/graphviz.rs index a3f63857..b8b5f2fa 100644 --- a/protocols/src/ir/graphviz.rs +++ b/protocols/src/ir/graphviz.rs @@ -174,35 +174,35 @@ fn escape_label(label: &str) -> String { mod tests { use std::path::Path; + use super::*; + use crate::frontend; use crate::frontend::diagnostic::DiagnosticHandler; - use crate::frontend::parser::parse_file; use crate::ir::edge_contract::{contract_edges, normalize_assignments}; use crate::ir::lowering::lower_ast_to_ir; use insta::Settings; - use super::*; - fn snap(name: &str, filename: &str) { let mut handler = DiagnosticHandler::default(); - let (symbols, protocols) = parse_file(filename, &mut handler).unwrap(); + let ast = frontend(&[filename], &mut handler, false).unwrap(); let mut content = String::new(); - for ast in protocols { - let ir: ProtoGraph = lower_ast_to_ir(ast, &symbols); + let st = &ast.st; + for proto_ast in ast.protos { + let ir: ProtoGraph = lower_ast_to_ir(proto_ast, st); content += "== pre-contract ==\n"; - content += &to_dot_string(&ir, &symbols); + content += &to_dot_string(&ir, st); content += "\n"; // println!("post contract"); let mut contracted_ir = ir.clone(); - contract_edges(&mut contracted_ir, &symbols); + contract_edges(&mut contracted_ir, st); content += "== post-contract ==\n"; - content += &to_dot_string(&contracted_ir, &symbols); + content += &to_dot_string(&contracted_ir, st); content += "\n"; let mut assignment_normalized_ir = contracted_ir.clone(); - normalize_assignments(&mut assignment_normalized_ir, &symbols); + normalize_assignments(&mut assignment_normalized_ir, st); content += "== post-normalize ==\n"; - content += &to_dot_string(&assignment_normalized_ir, &symbols); + content += &to_dot_string(&assignment_normalized_ir, st); content += "\n"; } diff --git a/protocols/src/ir/proto_graph.rs b/protocols/src/ir/proto_graph.rs index d6484cb1..65270dc4 100644 --- a/protocols/src/ir/proto_graph.rs +++ b/protocols/src/ir/proto_graph.rs @@ -305,8 +305,8 @@ impl Index<&OpId> for ProtoGraph { #[cfg(test)] mod tests { use super::*; + use crate::frontend; use crate::frontend::diagnostic::DiagnosticHandler; - use crate::frontend::parser::parse_file; use crate::ir::lowering::lower_ast_to_ir; use std::path::Path; use tempfile::NamedTempFile; @@ -320,18 +320,20 @@ mod tests { fn parse_and_lower_file(path: impl AsRef, protocol_name: Option<&str>) -> ProtoGraph { let mut handler = DiagnosticHandler::default(); - let (symbol_table, protocols) = parse_file(path, &mut handler).unwrap(); - let ast = match protocol_name { - Some(protocol_name) => protocols + let skip_static_step_fork_checks = true; + let ast = frontend(&[path], &mut handler, skip_static_step_fork_checks).unwrap(); + let proto = match protocol_name { + Some(protocol_name) => ast + .protos .into_iter() .find(|ast| ast.name == protocol_name) .unwrap(), None => { - assert_eq!(protocols.len(), 1); - protocols.into_iter().next().unwrap() + assert_eq!(ast.protos.len(), 1); + ast.protos.into_iter().next().unwrap() } }; - lower_ast_to_ir(ast, &symbol_table) + lower_ast_to_ir(proto, &ast.st) } #[test] diff --git a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__calyx_go_done_struct.snap b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__calyx_go_done_struct.snap index 0d9008d5..4e110ee9 100644 --- a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__calyx_go_done_struct.snap +++ b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__calyx_go_done_struct.snap @@ -17,5 +17,6 @@ fn calyx_go_done(ii: u32, oo: u32) { } dut.go := 0; dut.ii := X; - oo := dut.oo; + assert_eq(dut.oo, oo); + step(); } diff --git a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__cond.snap b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__cond.snap index 68d32fe2..957a7fed 100644 --- a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__cond.snap +++ b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__cond.snap @@ -23,6 +23,7 @@ fn mul(a: u32, b: u32, s: u32) { } else { fork(); } - dut.c := 1; - s := dut.s; + assert_eq(dut.c, 1); + assert_eq(s, dut.s); + step(); } diff --git a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__if_without_else.snap b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__if_without_else.snap index ce28df87..e0ff14cc 100644 --- a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__if_without_else.snap +++ b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__if_without_else.snap @@ -8,8 +8,9 @@ struct DualIdentity { } fn if_without_else(a: u64, b: u64) { - if dut.b == b { + if dut.b == 1 { dut.a := a; } else { } + step(); } diff --git a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__mul.snap b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__mul.snap index b28b4fb4..6511a77c 100644 --- a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__mul.snap +++ b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__mul.snap @@ -18,4 +18,5 @@ fn mul(a: u32, b: u32, s: u32) { dut.b := X; step(); assert_eq(s, dut.s); + step(); } diff --git a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__mul_ignore.snap b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__mul_ignore.snap index a1261885..7aece18a 100644 --- a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__mul_ignore.snap +++ b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__mul_ignore.snap @@ -17,7 +17,8 @@ fn mul(a: u32, b: u32, s: u32) { dut.a := X; dut.b := X; step(); - s := dut.s; + assert_eq(s, dut.s); + step(); } fn ignore(a: u32, b: u32, s: u32) { @@ -29,5 +30,6 @@ fn ignore(a: u32, b: u32, s: u32) { dut.a := X; dut.b := X; step(); - s := dut.s; + assert_eq(s, dut.s); + step(); } diff --git a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__simple_bounded_loop.snap b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__simple_bounded_loop.snap index 385007ff..412a8505 100644 --- a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__simple_bounded_loop.snap +++ b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__simple_bounded_loop.snap @@ -13,4 +13,5 @@ fn simple_bounded_loop(a: u64, num_iterations: uint, s: u64) { step(); } assert_eq(dut.s, s); + step(); } diff --git a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__simple_while.snap b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__simple_while.snap index 53a0827b..f97defeb 100644 --- a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__simple_while.snap +++ b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__simple_while.snap @@ -9,10 +9,11 @@ struct Counter { fn simple_while(a: u64, b: u64, s: u64) { dut.a := a; - while !(dut.s == b) { + while !(dut.s == 1) { step(); } step(); step(); assert_eq(dut.s, s); + step(); } diff --git a/protocols/src/tests/snapshots/protocols__frontend__typecheck__tests__calyx_go_done_struct.snap b/protocols/src/tests/snapshots/protocols__frontend__typecheck__tests__calyx_go_done_struct.snap index a4d0366c..0b210dba 100644 --- a/protocols/src/tests/snapshots/protocols__frontend__typecheck__tests__calyx_go_done_struct.snap +++ b/protocols/src/tests/snapshots/protocols__frontend__typecheck__tests__calyx_go_done_struct.snap @@ -7,9 +7,3 @@ warning: Inferred RHS type as u32 from LHS type u32. │ 15 │ dut.ii := X; │ ^^^^^^^^^^^^ Inferred RHS type as u32 from LHS type u32. - -error: Cannot assign to function argument oo. Try using assert_eq if you want to check the value of a transaction output. - ┌─ tests/calyx_go_done/calyx_go_done_struct.prot:16:3 - │ -16 │ oo := dut.oo; - │ ^^^^^^^^^^^^^ Cannot assign to function argument oo. Try using assert_eq if you want to check the value of a transaction output. diff --git a/protocols/src/tests/snapshots/protocols__frontend__typecheck__tests__cond.snap b/protocols/src/tests/snapshots/protocols__frontend__typecheck__tests__cond.snap index 0c6115aa..7514d621 100644 --- a/protocols/src/tests/snapshots/protocols__frontend__typecheck__tests__cond.snap +++ b/protocols/src/tests/snapshots/protocols__frontend__typecheck__tests__cond.snap @@ -13,9 +13,3 @@ warning: Inferred RHS type as u32 from LHS type u32. │ 20 │ dut.b := X; │ ^^^^^^^^^^^ Inferred RHS type as u32 from LHS type u32. - -error: `dut.c` is a output field of the struct `dut`, but only input fields are allowed to appear in assignments) - ┌─ tests/multipliers/mult_cond.prot:30:3 - │ -30 │ dut.c := 1'b1; - │ ^^^^^^^^^^^^^^ `dut.c` is a output field of the struct `dut`, but only input fields are allowed to appear in assignments) diff --git a/protocols/src/tests/snapshots/protocols__frontend__typecheck__tests__simple_while.snap b/protocols/src/tests/snapshots/protocols__frontend__typecheck__tests__simple_while.snap index 2cd2f925..0c17594c 100644 --- a/protocols/src/tests/snapshots/protocols__frontend__typecheck__tests__simple_while.snap +++ b/protocols/src/tests/snapshots/protocols__frontend__typecheck__tests__simple_while.snap @@ -2,8 +2,4 @@ source: protocols/src/frontend/typecheck.rs expression: content --- -error: b is a function argument, but conditions in if-statements / while-loops cannot mention function arguments - ┌─ tests/counters/simple_while.prot:10:22 - │ -10 │ while !(dut.s == b) { - │ ^ b is a function argument, but conditions in if-statements / while-loops cannot mention function arguments + diff --git a/protocols/src/transactions/mod.rs b/protocols/src/transactions/mod.rs index 6d80e25f..d68ec234 100644 --- a/protocols/src/transactions/mod.rs +++ b/protocols/src/transactions/mod.rs @@ -1,21 +1,19 @@ use rustc_hash::FxHashMap; use crate::Value; -use crate::frontend::ast::Protocol; +use crate::frontend::ast::Ast; use crate::frontend::diagnostic::DiagnosticHandler; -use crate::frontend::symbol::SymbolTable; pub mod parser; pub type Traces = Vec)>>; /// Simple frontend for loading a transaction trace file (*.tx) -pub fn transaction_frontend<'a>( +pub fn transaction_frontend( filename: impl AsRef, - st: &'a SymbolTable, - protos: &'a [Protocol], + ast: &Ast, diag: &mut DiagnosticHandler, ) -> anyhow::Result { - let protos: FxHashMap = protos.iter().map(|p| (p.name.clone(), p)).collect(); - parser::parse_transactions_file(filename, diag, st, &protos) + let protos: FxHashMap = ast.protos.iter().map(|p| (p.name.clone(), p)).collect(); + parser::parse_transactions_file(filename, diag, &ast.st, &protos) } diff --git a/tests/calyx_go_done/calyx_go_done_struct.prot b/tests/calyx_go_done/calyx_go_done_struct.prot index 2b6d9b2d..84e5ac3e 100644 --- a/tests/calyx_go_done/calyx_go_done_struct.prot +++ b/tests/calyx_go_done/calyx_go_done_struct.prot @@ -13,5 +13,6 @@ prot calyx_go_done(ii: u32, oo: u32) { } dut.go := 32'b0; dut.ii := X; - oo := dut.oo; + assert_eq(dut.oo, oo); + step(); } diff --git a/tests/counters/simple_bounded_loop.prot b/tests/counters/simple_bounded_loop.prot index 251968bb..f9bd28a1 100644 --- a/tests/counters/simple_bounded_loop.prot +++ b/tests/counters/simple_bounded_loop.prot @@ -9,4 +9,5 @@ prot simple_bounded_loop(a: u64, num_iterations: uint, s: u64) { step(); } assert_eq(dut.s, s); + step(); } diff --git a/tests/counters/simple_while.prot b/tests/counters/simple_while.prot index a185338c..e9e3f0f5 100644 --- a/tests/counters/simple_while.prot +++ b/tests/counters/simple_while.prot @@ -7,11 +7,13 @@ prot simple_while(a: u64, b: u64, s: u64) { // Loading dut with values dut.a := a; - while !(dut.s == b) { + while !(dut.s == 64'd1) { step(1); } step(2); assert_eq(dut.s, s); + step(); } + diff --git a/tests/identities/dual_identity_d1/if_without_else.prot b/tests/identities/dual_identity_d1/if_without_else.prot index 56b035d2..6e73572e 100644 --- a/tests/identities/dual_identity_d1/if_without_else.prot +++ b/tests/identities/dual_identity_d1/if_without_else.prot @@ -4,7 +4,9 @@ struct DualIdentity { } prot if_without_else(a: u64, b: u64) { - if (dut.b == b) { + if (dut.b == 64'd1) { dut.a := a; } + step(); } + diff --git a/tests/multipliers/mul.prot b/tests/multipliers/mul.prot index 3eac00ec..b2331307 100644 --- a/tests/multipliers/mul.prot +++ b/tests/multipliers/mul.prot @@ -19,4 +19,5 @@ prot mul(a: u32, b: u32, s: u32) { dut.b := X; step(); assert_eq(s, dut.s); + step(); } diff --git a/tests/multipliers/mul_ignore.prot b/tests/multipliers/mul_ignore.prot index 5dafbc96..c42a4b86 100644 --- a/tests/multipliers/mul_ignore.prot +++ b/tests/multipliers/mul_ignore.prot @@ -19,7 +19,8 @@ prot mul(a: u32, b: u32, s: u32) { dut.a := X; dut.b := X; step(); - s := dut.s; + assert_eq(s, dut.s); + step(); } prot ignore(a: u32, b: u32, s: u32) { @@ -34,5 +35,7 @@ prot ignore(a: u32, b: u32, s: u32) { dut.a := X; dut.b := X; step(); - s := dut.s; + assert_eq(s, dut.s); + step(); } + diff --git a/tests/multipliers/mult_cond.prot b/tests/multipliers/mult_cond.prot index 44efb2e1..59be42cc 100644 --- a/tests/multipliers/mult_cond.prot +++ b/tests/multipliers/mult_cond.prot @@ -27,7 +27,8 @@ prot mul(a: u32, b: u32, s: u32) { else { fork(); } - dut.c := 1'b1; + assert_eq(dut.c, 1'b1); - s := dut.s; + assert_eq(s, dut.s); + step(); }