Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Calyx-FIRRTL backend] Guards and non-primitive Cells #1817

Merged
merged 30 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8c8d07b
Initial commit: added barebones firrtl.rs as a backend option.
ayakayorihiro Dec 9, 2023
7a5ce0a
Fix formatting errors and try to leverage VerilogBackend
ayakayorihiro Dec 9, 2023
0fc6b8c
Fix formatting
ayakayorihiro Dec 9, 2023
9cad3a9
first pass on inputs and outputs
ayakayorihiro Dec 12, 2023
d395802
fix CI formatting issue
ayakayorihiro Dec 12, 2023
24d3829
more CI formatting fixes
ayakayorihiro Dec 12, 2023
f7d00eb
temporary commit before fixing up starter code
ayakayorihiro Dec 13, 2023
29e6c73
Clean up starter firrtl translation code
ayakayorihiro Dec 13, 2023
eb6f883
Fix CI errors
ayakayorihiro Dec 13, 2023
d5770c7
Barebones test containing input/output ports and single assignment
ayakayorihiro Dec 14, 2023
e2d6800
Fix test failure caused by whitespace
ayakayorihiro Dec 14, 2023
e1bbefd
clean up unnecessary comments
ayakayorihiro Dec 14, 2023
34e6fbf
Port guard support and some refactoring for converting ports to FIRRTL
ayakayorihiro Dec 14, 2023
46660b2
First steps for recursive guard building + more refactoring
ayakayorihiro Dec 14, 2023
338c682
Guard support and tests
ayakayorihiro Dec 14, 2023
4667d68
Updating firrtl.rs to support guards
ayakayorihiro Dec 15, 2023
9f21d0c
Fix formatting issues
ayakayorihiro Dec 15, 2023
8729d39
additional formatting fix
ayakayorihiro Dec 15, 2023
7783487
Added default assignment to 0 for guard failure and fixed expected ou…
ayakayorihiro Dec 15, 2023
c9a4634
Added default initialization statements for assignments with guards
ayakayorihiro Dec 15, 2023
945c620
adding test to make sure that there's only one invalid initialization…
ayakayorihiro Dec 15, 2023
9dbf811
Merge with master
ayakayorihiro Dec 19, 2023
0d87744
fixing attributes and "is invalid" initialization
ayakayorihiro Dec 19, 2023
18289c4
First steps to support non-primitive cells
ayakayorihiro Dec 19, 2023
2de6771
Fixing hardcoding of main as the top level component
ayakayorihiro Dec 19, 2023
b408e14
Fix formatting errors
ayakayorihiro Dec 19, 2023
544e179
More formatting fixes
ayakayorihiro Dec 19, 2023
5517fd2
Remove unnecessary FIXME
ayakayorihiro Dec 19, 2023
ec01294
Fix indentation and add comment from PR feedback
ayakayorihiro Dec 21, 2023
602ab3d
Fix small format bug
ayakayorihiro Dec 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
ayakayorihiro marked this conversation as resolved.
Show resolved Hide resolved
}
}
ayakayorihiro marked this conversation as resolved.
Show resolved Hide resolved
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)?;
ayakayorihiro marked this conversation as resolved.
Show resolved Hide resolved

// 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!(),
ayakayorihiro marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

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();
ayakayorihiro marked this conversation as resolved.
Show resolved Hide resolved
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);
ayakayorihiro marked this conversation as resolved.
Show resolved Hide resolved
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);
ayakayorihiro marked this conversation as resolved.
Show resolved Hide resolved
}
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),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clone seems off to me?

Since get_port_string only requires a &ir::Port as the first argument I don't see a good reason to clone anything when you can just pass a reference to the port you have. See the invocations you have above for CompOp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the catch!! Indeed the cloning was unnecessary 😅

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 {}
}
Loading