Skip to content

Commit

Permalink
Initial code and tests for Calyx-to-FIRRTL backend (#1806)
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

* Address PR comments

* Add error message for ports that have groups as parents
  • Loading branch information
ayakayorihiro committed Dec 18, 2023
1 parent bd0609d commit 156f64d
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 2 deletions.
3 changes: 3 additions & 0 deletions calyx-backend/src/backend_opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub enum BackendOpt {
Resources,
Sexp,
Yxi,
Firrtl,
None,
}

Expand All @@ -28,6 +29,7 @@ fn backends() -> Vec<(&'static str, BackendOpt)> {
("resources", BackendOpt::Resources),
("sexp", BackendOpt::Sexp),
("yxi", BackendOpt::Yxi),
("firrtl", BackendOpt::Firrtl),
("none", BackendOpt::None),
]
}
Expand Down Expand Up @@ -71,6 +73,7 @@ impl ToString for BackendOpt {
Self::XilinxXml => "xilinx-xml",
Self::Yxi => "yxi",
Self::Calyx => "calyx",
Self::Firrtl => "firrtl",
Self::None => "none",
}
.to_string()
Expand Down
149 changes: 149 additions & 0 deletions calyx-backend/src/firrtl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//! FIRRTL backend for the Calyx compiler.
//!
//! Transforms an [`ir::Context`](crate::ir::Context) into a formatted string that represents a
//! valid FIRRTL program.

use crate::{traits::Backend, VerilogBackend};
use calyx_ir::{self as ir};
use calyx_utils::{CalyxResult, OutputFile};
use std::io;

pub(super) const SPACING: &str = " ";

/// Implements a simple FIRRTL backend. The backend only accepts Calyx programs with no control
/// and no groups.
#[derive(Default)]
pub struct FirrtlBackend;

impl Backend for FirrtlBackend {
fn name(&self) -> &'static str {
"firrtl"
}

fn link_externs(
_prog: &calyx_ir::Context,
_write: &mut calyx_utils::OutputFile,
) -> calyx_utils::CalyxResult<()> {
Ok(()) // FIXME: Need to implement
}

fn validate(prog: &calyx_ir::Context) -> calyx_utils::CalyxResult<()> {
VerilogBackend::validate(prog) // FIXME: would this work if we wanted to check for the same things?
}

fn emit(ctx: &ir::Context, file: &mut OutputFile) -> CalyxResult<()> {
let out = &mut file.get_write();
for comp in ctx.components.iter() {
emit_component(comp, out)?
}
Ok(())
}
}

// TODO: Ask about the other backend configurations in verilog.rs and see if I need any of it
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
let sig = comp.signature.borrow();
for (_idx, port_ref) in sig.ports.iter().enumerate() {
let port = port_ref.borrow();
let direction_string =
// NOTE: The signature port definitions are reversed inside the component.
match port.direction {
ir::Direction::Input => {"output"}
ir::Direction::Output => {"input"}
ir::Direction::Inout => {
panic!("Unexpected Inout port on Component: {}", port.name)
}
};
if port.has_attribute(ir::BoolAttr::Clk) {
writeln!(
f,
"{}{} {}: Clock",
SPACING.repeat(2),
direction_string,
port.name
)?;
} else {
writeln!(
f,
"{}{} {}: UInt<{}>",
SPACING.repeat(2),
direction_string,
port.name,
port.width
)?;
}
}

// 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

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);
}
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)?;

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(())
}

// returns the FIRRTL translation of a port.
// if is_dst is true, then the port is a destination of an assignment, and shouldn't be a constant.
fn get_port_string(port: &calyx_ir::Port, is_dst: bool) -> String {
match &port.parent {
ir::PortParent::Cell(cell) => {
let parent_ref = cell.upgrade();
let parent = parent_ref.borrow();
match parent.prototype {
ir::CellType::Constant { val, width: _ } => {
if !is_dst {
format!("UInt({})", val)
} else {
unreachable!()
}
}
ir::CellType::ThisComponent => String::from(port.name.as_ref()),
_ => {
format!("{}.{}", parent.name().as_ref(), port.name.as_ref())
}
}
}
_ => {
unreachable!("Groups should not be parents as this backend takes place after compiler passes.")
}
}
}
2 changes: 2 additions & 0 deletions calyx-backend/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! Backends for the Calyx compiler.
mod backend_opt;
mod firrtl;
mod traits;
mod verilog;
mod yxi;

pub use backend_opt::BackendOpt;
pub use firrtl::FirrtlBackend;
pub use traits::Backend;
pub use verilog::VerilogBackend;
pub use yxi::YxiBackend;
Expand Down
8 changes: 6 additions & 2 deletions src/cmdline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use argh::FromArgs;
use calyx_backend::SexpBackend;
use calyx_backend::{
xilinx::{XilinxInterfaceBackend, XilinxXmlBackend},
Backend, BackendOpt, MlirBackend, ResourcesBackend, VerilogBackend,
YxiBackend,
Backend, BackendOpt, FirrtlBackend, MlirBackend, ResourcesBackend,
VerilogBackend, YxiBackend,
};
use calyx_ir as ir;
use calyx_utils::{CalyxResult, Error, OutputFile};
Expand Down Expand Up @@ -150,6 +150,10 @@ impl Opts {
let backend = YxiBackend;
backend.run(context, self.output)
}
BackendOpt::Firrtl => {
let backend = FirrtlBackend;
backend.run(context, self.output)
}
BackendOpt::Calyx => {
ir::Printer::write_context(
&context,
Expand Down
12 changes: 12 additions & 0 deletions tests/backend/firrtl/basic-program.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
circuit main:
module main:
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: main
done <= UInt(1)
out <= in
; COMPONENT END: main
9 changes: 9 additions & 0 deletions tests/backend/firrtl/basic-program.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// -b firrtl
component main(in : 32) -> (out : 32) {
cells {}
wires {
out = in;
done = 1'd1;
}
control {}
}

0 comments on commit 156f64d

Please sign in to comment.