Skip to content

Commit

Permalink
Add changes from explicit fsm compilation branch
Browse files Browse the repository at this point in the history
  • Loading branch information
parthsarkar17 committed Jan 17, 2025
1 parent 2e18d55 commit 40f8ec8
Show file tree
Hide file tree
Showing 40 changed files with 1,974 additions and 53 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions calyx-backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ smallvec.workspace = true
calyx-utils.workspace = true
calyx-frontend.workspace = true
calyx-ir.workspace = true
calyx-opt.workspace = true

csv = { version = "1.1", optional = true }
vast = "0.3.1"
Expand Down
2 changes: 2 additions & 0 deletions calyx-backend/src/mlir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ impl MlirBackend {
write!(f, "{}}}", " ".repeat(indent_level))
}
ir::Control::Empty(_) => writeln!(f),
ir::Control::FSMEnable(_) => todo!(),
}?;
let attr = control.get_attributes();
write!(f, "{}", Self::format_attributes(attr))?;
Expand All @@ -417,6 +418,7 @@ impl MlirBackend {
}
}
ir::PortParent::Group(_) => unimplemented!(),
ir::PortParent::FSM(_) => unimplemented!(),
ir::PortParent::StaticGroup(_) => unimplemented!(),
}
}
Expand Down
243 changes: 242 additions & 1 deletion calyx-backend/src/verilog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
use crate::traits::Backend;
use calyx_ir::{self as ir, Control, FlatGuard, Group, Guard, GuardRef, RRC};
use calyx_opt::passes::math_utilities::get_bit_width_from;
use calyx_utils::{CalyxResult, Error, OutputFile};
use ir::Nothing;
use itertools::Itertools;
use std::collections::{HashMap, HashSet};
use std::io;
use std::{collections::HashMap, rc::Rc};
use std::rc::Rc;
use std::{fs::File, time::Instant};
use vast::v17::ast as v;

Expand Down Expand Up @@ -230,6 +232,10 @@ fn emit_component<F: io::Write>(
flat_assign: bool,
f: &mut F,
) -> io::Result<()> {
for fsm in comp.fsms.iter() {
emit_fsm_module(fsm, comp.name, f)?;
}

writeln!(f, "module {}(", comp.name)?;

let sig = comp.signature.borrow();
Expand Down Expand Up @@ -295,6 +301,11 @@ fn emit_component<F: io::Write>(
.or_insert((Rc::clone(&asgn.dst), vec![asgn]));
}

// Emit FSMs
for fsm in comp.fsms.iter() {
emit_fsm(fsm, comp.name, f)?;
}

// Flatten all the guard expressions.
let mut pool = ir::GuardPool::new();
let grouped_asgns: Vec<_> = map
Expand Down Expand Up @@ -386,6 +397,7 @@ fn wire_decls(cell: &ir::Cell) -> Vec<(String, u64, ir::Direction)> {
}
}
ir::PortParent::Group(_) => unreachable!(),
ir::PortParent::FSM(_) => todo!(),
ir::PortParent::StaticGroup(_) => unreachable!(),
})
.collect()
Expand Down Expand Up @@ -443,6 +455,196 @@ fn cell_instance(cell: &ir::Cell) -> Option<v::Instance> {
}
}

/// Generates an inlined register representing the FSM, along with an always
/// block to transition the FSM and drive assignments that read from the FSM
/// register
fn emit_fsm<F: io::Write>(
fsm: &RRC<ir::FSM>,
comp_name: ir::Id,
f: &mut F,
) -> io::Result<()> {
// Initialize wires representing FSM internal state
let num_states = fsm.borrow().assignments.len();
let fsm_state_wires = (0..num_states)
.into_iter()
.map(|st| format!("{}_s{st}_out", fsm.borrow().name()))
.collect_vec();

for state_wire in fsm_state_wires.iter() {
writeln!(f, "logic {state_wire};")?;
}

// Instantiate an FSM module from the definition above
let fsm_name = fsm.borrow().name();
writeln!(f, "{fsm_name}_{comp_name}_def {fsm_name} (")?;
for (case, st_wire) in fsm_state_wires.into_iter().enumerate() {
writeln!(f, " .s{case}_out({st_wire}),")?;
}
writeln!(f, " .*")?;
writeln!(f, ");")?;

// Dump all assignments dependent on FSM state
emit_fsm_assignments(fsm, f)?;

io::Result::Ok(())
}

fn emit_fsm_assignments<F: io::Write>(
fsm: &RRC<ir::FSM>,
f: &mut F,
) -> io::Result<()> {
for collection in fsm.borrow().merge_assignments().iter() {
let dst_ref = &collection.first().unwrap().1.dst;
writeln!(f, "assign {} =", VerilogPortRef(dst_ref))?;
for (i, (case, assign)) in collection.iter().enumerate() {
// string representing the new guard on the assignment
let case_guard = format!("{}_s{case}_out", fsm.borrow().name());
let case_guarded_assign_guard = if assign.guard.is_true() {
case_guard
} else {
format!(
"({case_guard} & ({}))",
unflattened_guard(&assign.guard)
)
};

// value for the wire to take if either fsm is not in relevant state
// or if the assignment's original condition is not met
let guard_unmet_value = if is_data_port(dst_ref) {
format!("'x")
} else {
format!("{}'d0", dst_ref.borrow().width)
};

writeln!(
f,
" {} ? {} :",
case_guarded_assign_guard,
VerilogPortRef(&assign.src)
)?;

if i + 1 == collection.len() {
writeln!(f, " {guard_unmet_value};")?;
}
}
}
io::Result::Ok(())
}

fn emit_fsm_module<F: io::Write>(
fsm: &RRC<ir::FSM>,
comp_name: ir::Id,
f: &mut F,
) -> io::Result<()> {
let num_states = fsm.borrow().assignments.len();
let reg_bitwidth = get_bit_width_from(num_states as u64);

// Write module header. Inputs include ports checked during transitions, and
// outputs include one one-bit wire for every state
writeln!(f, "\nmodule {}_{comp_name}_def (", fsm.borrow().name())?;
writeln!(f, " input logic clk,")?;
writeln!(f, " input logic reset,")?;
let mut used_port_names: HashSet<ir::Canonical> = HashSet::new();
for transition in fsm.borrow().transitions.iter() {
if let ir::Transition::Conditional(guards) = transition {
for (guard, _) in guards.iter() {
for port in guard.all_ports().iter() {
if used_port_names.insert(port.borrow().canonical()) {
writeln!(f, " input logic {},", VerilogPortRef(port))?;
}
}
}
}
}
for state in (0..num_states).into_iter() {
writeln!(
f,
" output logic s{}_out{}",
state,
if state < num_states - 1 { "," } else { "" }
)?;
}
writeln!(f, ");\n")?;

// Write symbolic state variables and give them binary implementations
for state in (0..num_states).into_iter() {
writeln!(f, " parameter s{state} = {reg_bitwidth}'d{state};")?;
}

writeln!(f, "")?;

// State register logic variable
writeln!(f, " logic [{}:0] state_reg;", reg_bitwidth - 1)?;
writeln!(f, " logic [{}:0] state_next;\n", reg_bitwidth - 1)?;

// Generate sequential block representing the FSM
writeln!(f, " always @(posedge clk) begin")?;
writeln!(f, " if (reset) begin")?;
writeln!(f, " state_reg <= s0;")?;
writeln!(f, " end")?;
writeln!(f, " else begin")?;
writeln!(f, " state_reg <= state_next;")?;
writeln!(f, " end")?;
writeln!(f, " end\n")?;

// Begin emitting the FSM's transitions and updates
writeln!(f, " always @(*) begin")?;
writeln!(f, " case ( state_reg )")?;
// At each state, write the updates to the state and the outward-facing
// wires to make high / low
for (case, trans) in fsm.borrow().transitions.iter().enumerate() {
writeln!(f, " s{case}: begin")?;

// Outward-facing wires
for st in (0..num_states).into_iter() {
writeln!(
f,
"{}s{st}_out = 1'b{};",
" ".repeat(10),
if st == case { 1 } else { 0 }
)?;
}

// Updates to state
emit_fsm_transtions(trans, f)?;

writeln!(f, " end")?;
}

// Wrap up the module
writeln!(f, " endcase")?;
writeln!(f, " end")?;
writeln!(f, "endmodule\n")?;

io::Result::Ok(())
}

fn emit_fsm_transtions<F: io::Write>(
trans: &ir::Transition,
f: &mut F,
) -> io::Result<()> {
match trans {
ir::Transition::Unconditional(ns) => {
writeln!(f, "{}state_next = s{ns};", " ".repeat(10))?;
}
ir::Transition::Conditional(conds) => {
for (i, (g, ns)) in conds.iter().enumerate() {
let header = if i == 0 {
format!("if ({})", unflattened_guard(g))
} else if i == conds.len() - 1 {
"else".to_string()
} else {
format!("else if ({})", unflattened_guard(g))
};
writeln!(f, "{}{header} begin", " ".repeat(10))?;
writeln!(f, "{}state_next = s{ns};", " ".repeat(12))?;
writeln!(f, "{}end", " ".repeat(10))?;
}
}
}
io::Result::Ok(())
}

/// Generates an always block that checks of the guards are disjoint when the
/// length of assignments is greater than 1:
/// ```verilog
Expand Down Expand Up @@ -654,6 +856,7 @@ fn port_to_ref(port_ref: &RRC<ir::Port>) -> v::Expr {
}
}
ir::PortParent::Group(_) => unreachable!(),
ir::PortParent::FSM(_) => todo!(),
ir::PortParent::StaticGroup(_) => unreachable!(),
}
}
Expand Down Expand Up @@ -728,11 +931,49 @@ impl<'a> std::fmt::Display for VerilogPortRef<'a> {
}
}
ir::PortParent::Group(_) => unreachable!(),
ir::PortParent::FSM(_) => todo!(),
ir::PortParent::StaticGroup(_) => unreachable!(),
}
}
}

/// Given a (potentially nested) guard, generates a Verilog expression
/// representing that guard using nested parentheses.
fn unflattened_guard(guard: &ir::Guard<Nothing>) -> String {
match guard {
Guard::Or(left, right) => {
format!(
"({}) | ({})",
unflattened_guard(left),
unflattened_guard(right)
)
}
Guard::And(left, right) => {
format!(
"({}) & ({})",
unflattened_guard(left),
unflattened_guard(right)
)
}
Guard::CompOp(comp, left, right) => {
let op = match comp {
ir::PortComp::Eq => "==",
ir::PortComp::Neq => "!=",
ir::PortComp::Gt => ">",
ir::PortComp::Lt => "<",
ir::PortComp::Geq => ">=",
ir::PortComp::Leq => "<=",
};
format!("{} {} {}", VerilogPortRef(left), op, VerilogPortRef(right))
}
Guard::Not(inner) => format!("~({})", unflattened_guard(inner)),

Guard::Port(port) => format!("{}", VerilogPortRef(port)),
Guard::True => format!("1'd1"),
Guard::Info(_) => format!("1'd1"),
}
}

fn emit_guard<F: std::io::Write>(
guard: &ir::FlatGuard,
f: &mut F,
Expand Down
3 changes: 3 additions & 0 deletions calyx-frontend/src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ pub enum NumAttr {
/// dynamic.
/// Therefore, we only place if we can *guarantee* the interval of the component.
Interval,
#[strum(serialize = "state")]
State,
}
impl From<NumAttr> for Attribute {
fn from(attr: NumAttr) -> Self {
Expand All @@ -155,6 +157,7 @@ pub enum InternalAttr {
LOOP,
START,
END,
SCHEDULE_ID,
}
impl From<InternalAttr> for Attribute {
fn from(attr: InternalAttr) -> Self {
Expand Down
Loading

0 comments on commit 40f8ec8

Please sign in to comment.