Skip to content

Commit

Permalink
[Calyx-FIRRTL backend] Guards and non-primitive Cells (#1817)
Browse files Browse the repository at this point in the history
* Initial commit: added barebones firrtl.rs as a backend option.

* Fix formatting errors and try to leverage VerilogBackend

* Fix formatting

* first pass on inputs and outputs

* fix CI formatting issue

* more CI formatting fixes

* temporary commit before fixing up starter code

* Clean up starter firrtl translation code

* Fix CI errors

* Barebones test containing input/output ports and single assignment

* Fix test failure caused by whitespace

* clean up unnecessary comments

* Port guard support and some refactoring for converting ports to FIRRTL

* First steps for recursive guard building + more refactoring

* Guard support and tests

* Updating firrtl.rs to support guards

* Fix formatting issues

* additional formatting fix

* Added default assignment to 0 for guard failure and fixed expected output

* Added default initialization statements for assignments with guards

* adding test to make sure that there's only one invalid initialization per unique dest

* fixing attributes and "is invalid" initialization

* First steps to support non-primitive cells

* Fixing hardcoding of main as the top level component

* Fix formatting errors

* More formatting fixes

* Remove unnecessary FIXME

* Fix indentation and add comment from PR feedback

* Fix small format bug
  • Loading branch information
ayakayorihiro authored Dec 21, 2023
1 parent 4101392 commit 73dec70
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 23 deletions.
168 changes: 145 additions & 23 deletions calyx-backend/src/firrtl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = " ";
Expand Down Expand Up @@ -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)?
}
Expand All @@ -45,7 +54,6 @@ fn emit_component<F: io::Write>(
comp: &ir::Component,
f: &mut F,
) -> io::Result<()> {
writeln!(f, "circuit {}:", comp.name)?;
writeln!(f, "{}module {}:", SPACING, comp.name)?;

// Inputs and Outputs
Expand Down Expand Up @@ -84,41 +92,108 @@ fn emit_component<F: io::Write>(
// Add a COMPONENT START: <name> 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<String> = 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: <name> 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<F: io::Write>(
asgn: &ir::Assignment<ir::Nothing>,
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<ir::Nothing>) -> 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.
Expand Down Expand Up @@ -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<F: io::Write>(
port: &RRC<ir::Port>,
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<F: io::Write>(
asgn: &ir::Assignment<ir::Nothing>,
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(())
}
18 changes: 18 additions & 0 deletions tests/backend/firrtl/and-or-not-guard.expect
Original file line number Diff line number Diff line change
@@ -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

9 changes: 9 additions & 0 deletions tests/backend/firrtl/and-or-not-guard.futil
Original file line number Diff line number Diff line change
@@ -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 {}
}
16 changes: 16 additions & 0 deletions tests/backend/firrtl/basic-guard.expect
Original file line number Diff line number Diff line change
@@ -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

9 changes: 9 additions & 0 deletions tests/backend/firrtl/basic-guard.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// -b firrtl
component main(in : 32, cond: 1) -> (out : 32) {
cells {}
wires {
out = cond ? in;
done = 1'd1;
}
control {}
}
1 change: 1 addition & 0 deletions tests/backend/firrtl/basic-program.expect
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ circuit main:
done <= UInt(1)
out <= in
; COMPONENT END: main

18 changes: 18 additions & 0 deletions tests/backend/firrtl/comparison-guard.expect
Original file line number Diff line number Diff line change
@@ -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

9 changes: 9 additions & 0 deletions tests/backend/firrtl/comparison-guard.futil
Original file line number Diff line number Diff line change
@@ -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 {}
}
17 changes: 17 additions & 0 deletions tests/backend/firrtl/or-guard.expect
Original file line number Diff line number Diff line change
@@ -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

9 changes: 9 additions & 0 deletions tests/backend/firrtl/or-guard.futil
Original file line number Diff line number Diff line change
@@ -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 {}
}
21 changes: 21 additions & 0 deletions tests/backend/firrtl/two-or-guards.expect
Original file line number Diff line number Diff line change
@@ -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

10 changes: 10 additions & 0 deletions tests/backend/firrtl/two-or-guards.futil
Original file line number Diff line number Diff line change
@@ -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 {}
}

0 comments on commit 73dec70

Please sign in to comment.