diff --git a/calyx-backend/src/firrtl.rs b/calyx-backend/src/firrtl.rs index 06903f198..cce0181a1 100644 --- a/calyx-backend/src/firrtl.rs +++ b/calyx-backend/src/firrtl.rs @@ -4,8 +4,9 @@ //! valid FIRRTL program. use crate::{traits::Backend, VerilogBackend}; -use calyx_ir::{self as ir}; +use calyx_ir::{self as ir, RRC}; use calyx_utils::{CalyxResult, OutputFile}; +use std::collections::HashSet; use std::io; pub(super) const SPACING: &str = " "; @@ -33,6 +34,14 @@ impl Backend for FirrtlBackend { fn emit(ctx: &ir::Context, file: &mut OutputFile) -> CalyxResult<()> { let out = &mut file.get_write(); + let mut top_level_component = String::from("main"); + // Quick pass to check whether there exists a top-level component that we should replace main with. + for comp in ctx.components.iter() { + if comp.attributes.has(ir::BoolAttr::TopLevel) { + top_level_component = comp.name.to_string().clone(); + } + } + writeln!(out, "circuit {}:", top_level_component)?; for comp in ctx.components.iter() { emit_component(comp, out)? } @@ -45,7 +54,6 @@ fn emit_component( comp: &ir::Component, f: &mut F, ) -> io::Result<()> { - writeln!(f, "circuit {}:", comp.name)?; writeln!(f, "{}module {}:", SPACING, comp.name)?; // Inputs and Outputs @@ -84,41 +92,108 @@ fn emit_component( // Add a COMPONENT START: anchor before any code in the component writeln!(f, "{}; COMPONENT START: {}", SPACING.repeat(2), comp.name)?; - // TODO: Cells. NOTE: leaving this one for last + // Cells + for cell in comp.cells.iter() { + let cell_borrowed = cell.as_ref().borrow(); + if cell_borrowed.type_name().is_some() { + match cell_borrowed.prototype { + ir::CellType::Primitive { + name: _, + param_binding: _, + is_comb: _, + latency: _, + } => { + // TODO: use extmodules + writeln!( + f, + "{}; FIXME: attempting to instantiate primitive cell {}", + SPACING.repeat(2), + cell_borrowed.name() + )?; + } + ir::CellType::Component { name } => { + writeln!( + f, + "{}inst {} of {}", + SPACING.repeat(2), + cell_borrowed.name(), + name + )?; + } + ir::CellType::ThisComponent => unreachable!(), + ir::CellType::Constant { val: _, width: _ } => unreachable!(), + } + } + } + let mut dst_set: HashSet = HashSet::new(); + // Emit assignments for asgn in &comp.continuous_assignments { - // TODO: guards match asgn.guard.as_ref() { - ir::Guard::Or(_, _) => todo!(), - ir::Guard::And(_, _) => todo!(), - ir::Guard::Not(_) => todo!(), ir::Guard::True => { // Simple assignment with no guard - let _ = write_assignment(asgn, f); + let _ = write_assignment(asgn, f, 2); + } + _ => { + let dst_canonical = &asgn.dst.as_ref().borrow().canonical(); + let dst_canonical_str = dst_canonical.to_string(); + if !dst_set.contains(&dst_canonical_str) { + // if we don't have a "is invalid" statement yet, then we have to write one. + // an alternative "eager" approach would be to instantiate all possible ports + // (our output ports + all children's input ports) up front. + let _ = write_invalid_initialization(&asgn.dst, f); + dst_set.insert(dst_canonical_str); + } + // need to write out the guard. + let guard_string = get_guard_string(asgn.guard.as_ref()); + writeln!(f, "{}when {}:", SPACING.repeat(2), guard_string)?; + let _ = write_assignment(asgn, f, 3); } - ir::Guard::CompOp(_, _, _) => todo!(), - ir::Guard::Port(_) => {} - ir::Guard::Info(_) => todo!(), } } // Add COMPONENT END: anchor - writeln!(f, "{}; COMPONENT END: {}", SPACING.repeat(2), comp.name)?; + writeln!(f, "{}; COMPONENT END: {}\n", SPACING.repeat(2), comp.name)?; Ok(()) } -// Writes a FIRRTL assignment -fn write_assignment( - asgn: &ir::Assignment, - f: &mut F, -) -> CalyxResult<()> { - let dest_port = asgn.dst.borrow(); - let dest_string = get_port_string(&dest_port, true); - let source_port = asgn.src.borrow(); - let src_string = get_port_string(&source_port, false); - writeln!(f, "{}{} <= {}", SPACING.repeat(2), dest_string, src_string)?; - Ok(()) +// recursive function that writes the FIRRTL representation for a guard. +fn get_guard_string(guard: &ir::Guard) -> String { + match guard { + ir::Guard::Or(l, r) => { + let l_str = get_guard_string(l.as_ref()); + let r_str = get_guard_string(r.as_ref()); + format!("or({}, {})", l_str, r_str) + } + ir::Guard::And(l, r) => { + let l_str = get_guard_string(l.as_ref()); + let r_str = get_guard_string(r.as_ref()); + format!("and({}, {})", l_str, r_str) + } + ir::Guard::Not(g) => { + let g_str = get_guard_string(g); + format!("not({})", g_str) + } + ir::Guard::True => String::from(""), + ir::Guard::CompOp(op, l, r) => { + let l_str = get_port_string(&l.borrow(), false); + let r_str = get_port_string(&r.borrow(), false); + let op_str = match op { + ir::PortComp::Eq => "eq", + ir::PortComp::Neq => "neq", + ir::PortComp::Gt => "gt", + ir::PortComp::Lt => "lt", + ir::PortComp::Geq => "geq", + ir::PortComp::Leq => "leq", + }; + format!("{}({}, {})", op_str, l_str, r_str) + } + ir::Guard::Port(port) => get_port_string(&port.borrow().clone(), false), + ir::Guard::Info(_) => { + panic!("guard should not have info") + } + } } // returns the FIRRTL translation of a port. @@ -147,3 +222,50 @@ fn get_port_string(port: &calyx_ir::Port, is_dst: bool) -> String { } } } + +// variables that get set in assignments should get initialized to avoid the FIRRTL compiler from erroring. +fn write_invalid_initialization( + port: &RRC, + f: &mut F, +) -> CalyxResult<()> { + let default_initialization_str = "; default initialization"; + let dst_string = get_port_string(&port.borrow(), true); + if port.borrow().attributes.has(ir::BoolAttr::Control) { + writeln!( + f, + "{}{} <= UInt(0) {}", + SPACING.repeat(2), + dst_string, + default_initialization_str + )?; + } else { + writeln!( + f, + "{}{} is invalid {}", + SPACING.repeat(2), + dst_string, + default_initialization_str + )?; + } + Ok(()) +} + +// Writes a FIRRTL assignment +fn write_assignment( + asgn: &ir::Assignment, + f: &mut F, + num_indent: usize, +) -> CalyxResult<()> { + let dest_port = asgn.dst.borrow(); + let dest_string = get_port_string(&dest_port, true); + let source_port = asgn.src.borrow(); + let src_string = get_port_string(&source_port, false); + writeln!( + f, + "{}{} <= {}", + SPACING.repeat(num_indent), + dest_string, + src_string + )?; + Ok(()) +} diff --git a/tests/backend/firrtl/and-or-not-guard.expect b/tests/backend/firrtl/and-or-not-guard.expect new file mode 100644 index 000000000..e0dea86d9 --- /dev/null +++ b/tests/backend/firrtl/and-or-not-guard.expect @@ -0,0 +1,18 @@ +circuit main: + module main: + input in: UInt<32> + input cond: UInt<1> + input cond2: UInt<1> + input cond3: UInt<1> + output out: UInt<32> + input go: UInt<1> + input clk: Clock + input reset: UInt<1> + output done: UInt<1> + ; COMPONENT START: main + done <= UInt(1) + out is invalid ; default initialization + when and(or(not(cond), cond2), cond3): + out <= in + ; COMPONENT END: main + diff --git a/tests/backend/firrtl/and-or-not-guard.futil b/tests/backend/firrtl/and-or-not-guard.futil new file mode 100644 index 000000000..14356fc03 --- /dev/null +++ b/tests/backend/firrtl/and-or-not-guard.futil @@ -0,0 +1,9 @@ +// -b firrtl +component main(in : 32, cond: 1, cond2 : 1, cond3 : 1) -> (out : 32) { + cells {} + wires { + out = (!cond | cond2) & cond3 ? in; + done = 1'd1; + } + control {} +} diff --git a/tests/backend/firrtl/basic-guard.expect b/tests/backend/firrtl/basic-guard.expect new file mode 100644 index 000000000..1545e6ccb --- /dev/null +++ b/tests/backend/firrtl/basic-guard.expect @@ -0,0 +1,16 @@ +circuit main: + module main: + input in: UInt<32> + input cond: UInt<1> + output out: UInt<32> + input go: UInt<1> + input clk: Clock + input reset: UInt<1> + output done: UInt<1> + ; COMPONENT START: main + done <= UInt(1) + out is invalid ; default initialization + when cond: + out <= in + ; COMPONENT END: main + diff --git a/tests/backend/firrtl/basic-guard.futil b/tests/backend/firrtl/basic-guard.futil new file mode 100644 index 000000000..36fedfdb6 --- /dev/null +++ b/tests/backend/firrtl/basic-guard.futil @@ -0,0 +1,9 @@ +// -b firrtl +component main(in : 32, cond: 1) -> (out : 32) { + cells {} + wires { + out = cond ? in; + done = 1'd1; + } + control {} +} diff --git a/tests/backend/firrtl/basic-program.expect b/tests/backend/firrtl/basic-program.expect index 73af9081f..caabab6c9 100644 --- a/tests/backend/firrtl/basic-program.expect +++ b/tests/backend/firrtl/basic-program.expect @@ -10,3 +10,4 @@ circuit main: done <= UInt(1) out <= in ; COMPONENT END: main + diff --git a/tests/backend/firrtl/comparison-guard.expect b/tests/backend/firrtl/comparison-guard.expect new file mode 100644 index 000000000..ba15ed71c --- /dev/null +++ b/tests/backend/firrtl/comparison-guard.expect @@ -0,0 +1,18 @@ +circuit main: + module main: + input in: UInt<32> + input var: UInt<32> + input var2: UInt<32> + input cond3: UInt<1> + output out: UInt<32> + input go: UInt<1> + input clk: Clock + input reset: UInt<1> + output done: UInt<1> + ; COMPONENT START: main + done <= UInt(1) + out is invalid ; default initialization + when and(leq(var, var2), cond3): + out <= in + ; COMPONENT END: main + diff --git a/tests/backend/firrtl/comparison-guard.futil b/tests/backend/firrtl/comparison-guard.futil new file mode 100644 index 000000000..0e817516c --- /dev/null +++ b/tests/backend/firrtl/comparison-guard.futil @@ -0,0 +1,9 @@ +// -b firrtl +component main(in : 32, var: 32, var2 : 32, cond3 : 1) -> (out : 32) { + cells {} + wires { + out = (var <= var2) & cond3 ? in; + done = 1'd1; + } + control {} +} diff --git a/tests/backend/firrtl/or-guard.expect b/tests/backend/firrtl/or-guard.expect new file mode 100644 index 000000000..adb0e8311 --- /dev/null +++ b/tests/backend/firrtl/or-guard.expect @@ -0,0 +1,17 @@ +circuit main: + module main: + input in: UInt<32> + input cond: UInt<1> + input cond2: UInt<1> + output out: UInt<32> + input go: UInt<1> + input clk: Clock + input reset: UInt<1> + output done: UInt<1> + ; COMPONENT START: main + done <= UInt(1) + out is invalid ; default initialization + when or(cond, cond2): + out <= in + ; COMPONENT END: main + diff --git a/tests/backend/firrtl/or-guard.futil b/tests/backend/firrtl/or-guard.futil new file mode 100644 index 000000000..15cc0b126 --- /dev/null +++ b/tests/backend/firrtl/or-guard.futil @@ -0,0 +1,9 @@ +// -b firrtl +component main(in : 32, cond: 1, cond2 : 1) -> (out : 32) { + cells {} + wires { + out = cond | cond2 ? in; + done = 1'd1; + } + control {} +} diff --git a/tests/backend/firrtl/two-or-guards.expect b/tests/backend/firrtl/two-or-guards.expect new file mode 100644 index 000000000..cbd7c2e54 --- /dev/null +++ b/tests/backend/firrtl/two-or-guards.expect @@ -0,0 +1,21 @@ +circuit main: + module main: + input in: UInt<32> + input in2: UInt<32> + input cond: UInt<1> + input cond2: UInt<1> + input cond3: UInt<1> + output out: UInt<32> + input go: UInt<1> + input clk: Clock + input reset: UInt<1> + output done: UInt<1> + ; COMPONENT START: main + done <= UInt(1) + out is invalid ; default initialization + when or(cond, cond2): + out <= in + when or(cond2, cond3): + out <= in2 + ; COMPONENT END: main + diff --git a/tests/backend/firrtl/two-or-guards.futil b/tests/backend/firrtl/two-or-guards.futil new file mode 100644 index 000000000..07c388d77 --- /dev/null +++ b/tests/backend/firrtl/two-or-guards.futil @@ -0,0 +1,10 @@ +// -b firrtl +component main(in : 32, in2 : 32, cond: 1, cond2 : 1, cond3 : 1) -> (out : 32) { + cells {} + wires { + out = cond | cond2 ? in; + out = cond2 | cond3 ? in2; + done = 1'd1; + } + control {} +}