Skip to content

Commit

Permalink
Add attributes for components and implement counter_fsm attribute (#…
Browse files Browse the repository at this point in the history
…453)

* add attributes to frontend

* add invalid test

* add attributes to IR

* move fsm type decision to a pass

* add pass to main pass set

* add better error message

* fix syntax for multiple attributes

* disable fsm attrs for externals

* restructure attributes to work with

* transfer attribute changes

* remove unused attributes and replace with dummy

* re-add doc comments

* small doc prettying
  • Loading branch information
UnsignedByte authored Sep 4, 2024
1 parent bb771f1 commit c5e18c3
Show file tree
Hide file tree
Showing 23 changed files with 320 additions and 78 deletions.
4 changes: 4 additions & 0 deletions crates/ast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ pest_consume.workspace = true

fil-utils.workspace = true
fil-gen.workspace = true
strum = "0.26.3"
strum_macros = "0.26.3"
enumflags2 = "0.7.10"
enum-map = "2.7.3"
77 changes: 77 additions & 0 deletions crates/ast/src/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use enum_map::{Enum, EnumMap};
use fil_utils::GPosIdx;
use strum_macros::EnumString;

/// An attribute that accepts a numeric value
#[derive(Enum, Clone, Copy, PartialEq, EnumString)]
pub enum NumAttr {}

/// An flag attribute
#[derive(Enum, Clone, Copy, PartialEq, EnumString)]
pub enum BoolAttr {
/// Use a counter based FSM design
#[strum(serialize = "counter_fsm")]
CounterFSM,
/// Dummy attribute because the [Enum] trait derivation throws errors if there is only one varianat
#[strum(disabled)]
Dummy,
}

/// Represents a single attribute. This is a private enum that is used during
/// parsing to collect all attributes before creating the [Attributes] struct.
#[derive(Enum, Clone, Copy)]
pub enum Attr {
Bool(BoolAttr),
Num(NumAttr),
}

impl From<BoolAttr> for Attr {
fn from(attr: BoolAttr) -> Self {
Attr::Bool(attr)
}
}

impl From<NumAttr> for Attr {
fn from(attr: NumAttr) -> Self {
Attr::Num(attr)
}
}

/// A set of attributes attached to a component
#[derive(Default, Clone)]
pub struct Attributes {
attrs: EnumMap<Attr, Option<(u64, GPosIdx)>>,
}

impl Attributes {
pub fn new(attrs: impl Iterator<Item = (Attr, GPosIdx, u64)>) -> Self {
Self {
attrs: attrs.map(|(attr, l, v)| (attr, Some((v, l)))).collect(),
}
}

/// Get the value of an attribute.
pub fn get(&self, attr: impl Into<Attr>) -> Option<u64> {
self.attrs[attr.into()].map(|(v, _)| v)
}

/// Get the location of an attribute.
pub fn get_loc(&self, attr: impl Into<Attr>) -> Option<GPosIdx> {
self.attrs[attr.into()].map(|(_, l)| l)
}

/// Set the value of an attribute
pub fn set(&mut self, attr: impl Into<Attr>, value: u64) {
self.attrs[attr.into()] = Some((value, GPosIdx::UNKNOWN));
}

/// Remove an attribute
pub fn remove(&mut self, attr: impl Into<Attr>) {
self.attrs[attr.into()] = None;
}

/// Check if the attribute set is empty
pub fn is_empty(&self) -> bool {
self.attrs.iter().all(|(_, v)| v.is_none())
}
}
3 changes: 1 addition & 2 deletions crates/ast/src/component.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use std::path::PathBuf;

use super::{Command, Id, Signature};
use fil_gen as gen;
use gen::GenConfig;
use std::path::PathBuf;

#[derive(Default)]
/// A external or generate definition in Filament
Expand Down
2 changes: 2 additions & 0 deletions crates/ast/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod attribute;
mod bind_map;
mod component;
mod constraint;
Expand All @@ -11,6 +12,7 @@ mod port;
mod signature;
mod time;

pub use attribute::{Attr, Attributes, BoolAttr, NumAttr};
pub use bind_map::Binding;
pub use component::{Component, Extern, Namespace};
pub use constraint::{Constraint, OrderConstraint, OrderOp};
Expand Down
36 changes: 35 additions & 1 deletion crates/ast/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
#![allow(clippy::type_complexity)]

//! Parser for Filament programs.
use crate::{self as ast, Loc, TimeSub};
use crate::{self as ast, Attributes, Loc, TimeSub};
use fil_utils::{self as utils, FilamentResult};
use fil_utils::{FileIdx, GPosIdx, GlobalPositionTable};
use itertools::Itertools;
use pest::pratt_parser::{Assoc, Op, PrattParser};
use pest_consume::{match_nodes, Error, Parser};
use std::fs;
use std::path::Path;
use std::str::FromStr;

/// Data associated with parsing the file.
#[derive(Clone)]
Expand Down Expand Up @@ -626,6 +627,7 @@ impl FilamentParser {
Ok(match_nodes!(
input.into_children();
[
attributes(attributes),
identifier(name),
params(params),
abstract_var(abstract_vars),
Expand All @@ -636,6 +638,7 @@ impl FilamentParser {
let (inputs, outputs, interface_signals, unannotated_ports) = io;
ast::Signature::new(
name,
attributes,
params,
abstract_vars,
unannotated_ports,
Expand All @@ -648,6 +651,7 @@ impl FilamentParser {
)
},
[
attributes(attributes),
identifier(name),
params(params),
io(io),
Expand All @@ -657,6 +661,7 @@ impl FilamentParser {
let (inputs, outputs, interface_signals, unannotated_ports) = io;
ast::Signature::new(
name,
attributes,
params,
vec![],
unannotated_ports,
Expand Down Expand Up @@ -820,6 +825,35 @@ impl FilamentParser {
))
}

fn not(input: Node) -> ParseResult<()> {
Ok(())
}

fn attr_bind(input: Node) -> ParseResult<Vec<(ast::Attr, GPosIdx, u64)>> {
match_nodes!(
input.clone().into_children();
[identifier(name)] =>
ast::BoolAttr::from_str(name.as_ref()).map(
|attr| vec![(ast::Attr::Bool(attr), name.pos(), 1)]).map_err(
|_| input.error(format!("Found unknown attribute flag \"{name}\""))),
[not(_), identifier(name)] =>
ast::BoolAttr::from_str(name.as_ref()).map(
|attr| vec![(ast::Attr::Bool(attr), name.pos(), 0)]).map_err(
|_| input.error(format!("Found unknown attribute flag \"{name}\""))),
[identifier(name), bitwidth(val)] =>
ast::NumAttr::from_str(name.as_ref()).map(
|attr| vec![(ast::Attr::Num(attr), name.pos(), val)]).map_err(
|_| input.error(format!("Found unknown numeric attribute \"{name}\""))),
)
}

fn attributes(input: Node) -> ParseResult<Attributes> {
Ok(match_nodes!(
input.into_children();
[attr_bind(attr)..] => ast::Attributes::new(attr.into_iter().flatten())
))
}

fn component(input: Node) -> ParseResult<ast::Component> {
match_nodes!(
input.into_children();
Expand Down
5 changes: 5 additions & 0 deletions crates/ast/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::{
Binding, Expr, Id, InterfaceDef, Loc, OrderConstraint, PortDef, Time,
TimeSub,
};
use crate::Attributes;
use fil_utils::GPosIdx;

#[derive(Clone)]
Expand Down Expand Up @@ -111,6 +112,8 @@ impl SigBind {
pub struct Signature {
/// Name of the component
pub name: Loc<Id>,
/// Attributes associated with this component
pub attributes: Attributes,
/// Parameters for the Signature
pub params: Vec<Loc<ParamBind>>,
/// Parameters bound in the signature binding. These always have a default value.
Expand All @@ -136,6 +139,7 @@ impl Signature {
#[allow(clippy::too_many_arguments)]
pub fn new(
name: Loc<Id>,
attributes: Attributes,
params: Vec<Loc<ParamBind>>,
events: Vec<Loc<EventBind>>,
unannotated_ports: Vec<(Id, u64)>,
Expand All @@ -150,6 +154,7 @@ impl Signature {
inputs.append(&mut outputs);
Self {
name,
attributes,
params,
sig_bindings,
events,
Expand Down
23 changes: 19 additions & 4 deletions crates/ast/src/syntax.pest
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,32 @@ param_bind = {
"?" ~ param_var ~ "=" ~ expr |
param_var
}

signature = {
identifier ~ params ~ abstract_var? ~ io ~ sig_bindings ~ constraints
attributes ~ "comp" ~ identifier ~ params ~ abstract_var? ~ io ~ sig_bindings ~ constraints
}

attributes = {
("#[" ~ attr_bind ~ ("," ~ attr_bind)* ~ "]")?
}

not = { "not" }

attr_bind = {
not ~ "(" ~ identifier ~ ")"
| identifier ~ "=" ~ bitwidth
| identifier
}

component = {
"comp" ~ signature ~ "{" ~ command* ~ "}"
signature ~ "{" ~ command* ~ "}"
}

external = {
"extern" ~ string_lit ~ "{" ~ ("comp" ~ signature ~ ";")* ~ "}"
"extern" ~ string_lit ~ "{" ~ (signature ~ ";")* ~ "}"
}
generate = {
"generate" ~ "(" ~ identifier ~ ")" ~ "using" ~ string_lit ~ "{" ~ ("comp" ~ signature ~ ";")* ~ "}"
"generate" ~ "(" ~ identifier ~ ")" ~ "using" ~ string_lit ~ "{" ~ (signature ~ ";")* ~ "}"
}

comp_or_ext = {
Expand Down
9 changes: 6 additions & 3 deletions crates/filament/src/cmdline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,12 @@ pub struct Opts {
/// backend to use (default: verilog): calyx, verilog
#[argh(option, long = "backend", default = "Backend::Verilog")]
pub backend: Backend,
/// disable generation of slow FSMs in the backend
#[argh(switch, long = "disable-slow-fsms")]
pub disable_slow_fsms: bool,
/// disable generation of counter-based FSMs in the backend. The default (non-counter) FSM
/// is represented by a single bit Shift Register counting through the number of states.
/// However, for components with a large number of states or a large II, it may be more efficient to use a
/// counter-based FSM, where one counter loops every II states, at which point it increments the state counter.
#[argh(switch, long = "no-counter-fsms")]
pub no_counter_fsms: bool,
/// preserves original port names during compilation.
#[argh(switch, long = "preserve-names")]
pub preserve_names: bool,
Expand Down
2 changes: 1 addition & 1 deletion crates/filament/src/ir_passes/bundle_elim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ impl BundleElim {
}

impl Construct for BundleElim {
fn from(_opts: &cmdline::Opts, ctx: &mut ir::Context) -> Self {
fn from(_: &cmdline::Opts, ctx: &mut ir::Context) -> Self {
let mut visitor = Self {
context: DenseIndexInfo::default(),
local_map: HashMap::new(),
Expand Down
47 changes: 47 additions & 0 deletions crates/filament/src/ir_passes/fsm_attributes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use crate::ir_visitor::{Visitor, VisitorData};
use fil_ast as ast;
use fil_ir::TimeSub;

/// Sets the proper FSM Attributes for every component
#[derive(Default)]
pub struct FSMAttributes;

impl Visitor for FSMAttributes {
fn name() -> &'static str {
"fsm-attributes"
}

fn visit(&mut self, mut data: VisitorData) {
let attrs = &data.comp.attrs;

// Check if the component already has FSM attributes
if attrs.get(ast::BoolAttr::CounterFSM).is_some() {
return;
}

// If the component is external or generated, do not add any slow FSM attributes
if data.comp.is_ext() || data.comp.is_gen() {
return;
}

// Get the delay of the component if it is a single event component
if !data.opts.no_counter_fsms && data.comp.events().len() == 1 {
let delay = &data.comp.events().iter().next().unwrap().1.delay;
let TimeSub::Unit(delay) = delay else {
data.comp.internal_error(
"Non-unit delays should have been compiled away.",
);
};
let delay = delay.concrete(&data.comp);

// If the delay is > 1, add a slow FSM attribute
// TODO(UnsignedByte): Find a better heuristic for slow FSMs
if delay > 1 {
data.comp.attrs.set(ast::BoolAttr::CounterFSM, 1);
return;
}
}

data.comp.attrs.set(ast::BoolAttr::CounterFSM, 0);
}
}
14 changes: 9 additions & 5 deletions crates/filament/src/ir_passes/lower/build_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::fsm::{FsmBind, FsmType};
use super::utils::{cell_to_port_def, NameGenerator};
use super::Fsm;
use calyx_ir::{self as calyx, RRC};
use fil_ast as ast;
use fil_ir::{self as ir, Ctx, DenseIndexInfo, DisplayCtx};
use itertools::Itertools;
use std::{collections::HashMap, rc::Rc};
Expand Down Expand Up @@ -34,8 +35,6 @@ pub(super) struct BuildCtx<'a> {
pub comp: &'a ir::Component,
ctx: &'a ir::Context,
lib: &'a calyx::LibrarySignatures,
/// Disable generation of slow FSMs
disable_slow_fsms: bool,
/// Helper to generate names
ng: &'a NameGenerator,
/// Mapping from events to the FSM that reify them.
Expand All @@ -51,14 +50,12 @@ impl<'a> BuildCtx<'a> {
ctx: &'a ir::Context,
idx: ir::CompIdx,
binding: &'a mut Binding,
disable_slow_fsms: bool,
ng: &'a NameGenerator,
builder: calyx::Builder<'a>,
lib: &'a calyx::LibrarySignatures,
) -> Self {
BuildCtx {
ctx,
disable_slow_fsms,
ng,
comp: ctx.get(idx),
binding,
Expand Down Expand Up @@ -237,7 +234,14 @@ impl<'a> BuildCtx<'a> {
);
};
let delay = delay.concrete(self.comp);
let typ = FsmType::new(states, delay, self.disable_slow_fsms);
let typ =
match self.comp.attrs.get(ast::BoolAttr::CounterFSM).unwrap() {
0 => FsmType::Simple(states),
1 => {
FsmType::CounterChain(states, delay)
}
v => unreachable!("Encountered boolean attribute counter_fsm with non-boolean value {}", v),
};
self.implement_fsm(&typ);

// Construct the FSM
Expand Down
Loading

0 comments on commit c5e18c3

Please sign in to comment.