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

Initial code and tests for Calyx-to-FIRRTL backend #1806

Merged
merged 15 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 {}
}