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] Primitive Cells #1835

Merged
merged 39 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 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
a5024f5
Create extmodule name from primitive information. Need to extract int…
ayakayorihiro Dec 20, 2023
83f0dfb
extracted function to get a FIRRTL module name given internal primiti…
ayakayorihiro Dec 20, 2023
aa1fefa
emit extmodule declarations
ayakayorihiro Dec 20, 2023
82c1176
Merge with current master
ayakayorihiro Dec 21, 2023
d1fed08
Fix formatting errors
ayakayorihiro Dec 21, 2023
8c52906
Add test for instantiating cells and creating extmodules for primitiv…
ayakayorihiro Dec 21, 2023
4ff08f4
merged main and made changes based on PR comments
ayakayorihiro Dec 23, 2023
9c00e94
Fix typo
ayakayorihiro Dec 27, 2023
5612b5b
add another simple test
ayakayorihiro Dec 27, 2023
8878f39
Fixes from PR comments
ayakayorihiro Jan 3, 2024
9e7d7fd
Fixed bug of misinterpreting the output of insert
ayakayorihiro Jan 3, 2024
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
122 changes: 77 additions & 45 deletions calyx-backend/src/firrtl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
//! valid FIRRTL program.

use crate::{traits::Backend, VerilogBackend};
use calyx_ir::{self as ir, RRC};
use calyx_utils::{CalyxResult, OutputFile};
use calyx_ir::{self as ir, Binding, RRC};
use calyx_utils::{CalyxResult, Id, OutputFile};
use std::collections::HashSet;
use std::io;

Expand Down Expand Up @@ -34,14 +34,31 @@ 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 {}:", ctx.entrypoint)?;
// Pass to output any necessary extmodule statements (for primitive calls)
let mut extmodule_set: HashSet<String> = HashSet::new();
for comp in &ctx.components {
for cell in comp.cells.iter() {
let cell_borrowed = cell.as_ref().borrow();
if let ir::CellType::Primitive {
name,
param_binding,
..
} = &cell_borrowed.prototype
{
let curr_module_name =
get_primitive_module_name(name, param_binding);
if extmodule_set.insert(curr_module_name.clone()) {
emit_primitive_extmodule(
&curr_module_name,
name,
param_binding,
out,
)?;
}
};
}
}
writeln!(out, "circuit {}:", top_level_component)?;
for comp in ctx.components.iter() {
emit_component(comp, out)?
}
Expand Down Expand Up @@ -96,37 +113,27 @@ fn emit_component<F: io::Write>(
for cell in comp.cells.iter() {
let cell_borrowed = cell.as_ref().borrow();
if cell_borrowed.type_name().is_some() {
match cell_borrowed.prototype {
let module_name = match &cell_borrowed.prototype {
ir::CellType::Primitive {
name: _,
param_binding: _,
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!(),
}
} => get_primitive_module_name(name, param_binding),
ir::CellType::Component { name } => name.to_string(),
_ => unreachable!(),
};
writeln!(
f,
"{}inst {} of {}",
SPACING.repeat(2),
cell_borrowed.name(),
module_name
)?;
}
}

let mut dst_set: HashSet<String> = HashSet::new();
let mut dst_set: HashSet<ir::Canonical> = HashSet::new();
// Emit assignments
for asgn in &comp.continuous_assignments {
match asgn.guard.as_ref() {
Expand All @@ -135,19 +142,18 @@ fn emit_component<F: io::Write>(
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) {
let dst_canonical = asgn.dst.as_ref().borrow().canonical();
if !dst_set.contains(&dst_canonical) {
// 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);
write_invalid_initialization(&asgn.dst, f)?;
dst_set.insert(dst_canonical);
}
// 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);
write_assignment(asgn, f, 3)?;
}
}
}
Expand All @@ -158,6 +164,33 @@ fn emit_component<F: io::Write>(
Ok(())
}

// creates a FIRRTL module name given the internal information of a primitive.
fn get_primitive_module_name(name: &Id, param_binding: &Binding) -> String {
let mut primitive_string = name.to_string();
for (_, size) in param_binding.as_ref().iter() {
primitive_string.push('_');
primitive_string.push_str(&size.to_string());
}
primitive_string
}

fn emit_primitive_extmodule<F: io::Write>(
curr_module_name: &String,
name: &Id,
param_binding: &Binding,
f: &mut F,
) -> io::Result<()> {
writeln!(f, "{}extmodule {}:", SPACING, curr_module_name)?;
writeln!(f, "{}defname = {}", SPACING.repeat(2), name)?;
for (id, size) in param_binding.as_ref().iter() {
writeln!(f, "{}parameter {} = {}", SPACING.repeat(2), id, size)?;
}
writeln!(f)?;
Ok(())
}

// fn create_primitive_extmodule() {}

// recursive function that writes the FIRRTL representation for a guard.
fn get_guard_string(guard: &ir::Guard<ir::Nothing>) -> String {
match guard {
Expand Down Expand Up @@ -189,7 +222,7 @@ fn get_guard_string(guard: &ir::Guard<ir::Nothing>) -> String {
};
format!("{}({}, {})", op_str, l_str, r_str)
}
ir::Guard::Port(port) => get_port_string(&port.borrow().clone(), false),
ir::Guard::Port(port) => get_port_string(&port.borrow(), false),
ir::Guard::Info(_) => {
panic!("guard should not have info")
}
Expand Down Expand Up @@ -227,7 +260,7 @@ fn get_port_string(port: &calyx_ir::Port, is_dst: bool) -> String {
fn write_invalid_initialization<F: io::Write>(
port: &RRC<ir::Port>,
f: &mut F,
) -> CalyxResult<()> {
) -> io::Result<()> {
let default_initialization_str = "; default initialization";
let dst_string = get_port_string(&port.borrow(), true);
if port.borrow().attributes.has(ir::BoolAttr::Control) {
Expand All @@ -237,25 +270,24 @@ fn write_invalid_initialization<F: io::Write>(
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<()> {
) -> io::Result<()> {
let dest_port = asgn.dst.borrow();
let dest_string = get_port_string(&dest_port, true);
let source_port = asgn.src.borrow();
Expand Down
41 changes: 41 additions & 0 deletions tests/backend/firrtl/basic-cell.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
circuit main:
extmodule std_wire_1:
defname = std_wire
parameter WIDTH = 1

module identity:
input in: UInt<32>
output out: UInt<32>
input go: UInt<1>
input clk: Clock
input reset: UInt<1>
output done: UInt<1>
; COMPONENT START: identity
done <= UInt(1)
out <= in
; COMPONENT END: identity

module main:
input go: UInt<1>
input clk: Clock
input reset: UInt<1>
output done: UInt<1>
; COMPONENT START: main
inst id of identity
inst invoke0_go of std_wire_1
inst invoke0_done of std_wire_1
done is invalid ; default initialization
when invoke0_done.out:
done <= UInt(1)
id.clk <= clk
id.go is invalid ; default initialization
when invoke0_go.out:
id.go <= UInt(1)
id.reset <= reset
id.in is invalid ; default initialization
when invoke0_go.out:
id.in <= UInt(5)
invoke0_go.in <= go
invoke0_done.in <= id.done
; COMPONENT END: main

26 changes: 26 additions & 0 deletions tests/backend/firrtl/basic-cell.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// -b firrtl
import "primitives/core.futil";
component identity(in : 32) -> (out : 32) {
cells {}
wires {
out = in;
done = 1'd1;
}
control {}
}

component main() -> () {
cells {
id = identity();
}
wires {
group run_id {
id.in = 32'd5;
id.go = 1'd1;
run_id[done] = id.done;
}
}
control {
seq { run_id; }
}
}
48 changes: 48 additions & 0 deletions tests/backend/firrtl/primitive-cells.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
circuit main:
extmodule std_add_32:
defname = std_add
parameter WIDTH = 32

extmodule std_wire_1:
defname = std_wire
parameter WIDTH = 1

module plus_one:
input in: UInt<32>
output out: UInt<32>
input go: UInt<1>
input clk: Clock
input reset: UInt<1>
output done: UInt<1>
; COMPONENT START: plus_one
inst add of std_add_32
done <= UInt(1)
out <= add.out
add.left <= UInt(1)
add.right <= in
; COMPONENT END: plus_one

module main:
input go: UInt<1>
input clk: Clock
input reset: UInt<1>
output done: UInt<1>
; COMPONENT START: main
inst po of plus_one
inst invoke0_go of std_wire_1
inst invoke0_done of std_wire_1
done is invalid ; default initialization
when invoke0_done.out:
done <= UInt(1)
invoke0_go.in <= go
invoke0_done.in <= po.done
po.clk <= clk
po.go is invalid ; default initialization
when invoke0_go.out:
po.go <= UInt(1)
po.reset <= reset
po.in is invalid ; default initialization
when invoke0_go.out:
po.in <= UInt(5)
; COMPONENT END: main

30 changes: 30 additions & 0 deletions tests/backend/firrtl/primitive-cells.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// -b firrtl
import "primitives/core.futil";
component plus_one(in : 32) -> (out : 32) {
cells {
add = std_add(32);
}
wires {
add.left = 32'd1;
add.right = in;
out = add.out;
done = 1'd1;
}
control {}
}

component main() -> () {
cells {
po = plus_one();
}
wires {
group run_po {
po.in = 32'd5;
po.go = 1'd1;
run_po[done] = po.done;
}
}
control {
seq { run_po; }
}
}