Browse Source

fix rtlil generation

main
NotAFile 6 months ago
parent
commit
1de0846f96
  1. 4
      lib/builtins/main.hyd
  2. 61
      src/builtin_cells.rs
  3. 9
      src/frontend.rs
  4. 295
      src/frontend/lowering.rs
  5. 17
      src/frontend/types.rs
  6. 23
      src/main.rs
  7. 13
      src/parser/comb.rs
  8. 4
      src/rtlil.rs

4
lib/builtins/main.hyd

@ -1,7 +1,7 @@
comb reduce_or (
a: Logic
comb reduce_or<T> (
a: T
)
-> Logic<1> {
reduce_or(a)

61
src/builtin_cells.rs

@ -3,12 +3,7 @@ use crate::frontend::Callable;
use crate::rtlil;
use crate::rtlil::SigSpec;
fn instantiate_unop(celltype: &str, id: &str, args: &[SigSpec], ret: &SigSpec) -> rtlil::Cell {
let a = args
.get(0)
.expect("wrong argcount slipped through type check");
assert_eq!(args.len(), 1);
fn instantiate_unop(celltype: &str, id: &str, a: &SigSpec, ret: &SigSpec) -> rtlil::Cell {
let mut cell = rtlil::Cell::new(id, celltype);
cell.add_param("\\A_SIGNED", "0");
cell.add_param("\\A_WIDTH", "1");
@ -18,15 +13,13 @@ fn instantiate_unop(celltype: &str, id: &str, args: &[SigSpec], ret: &SigSpec) -
cell
}
fn instantiate_binop(celltype: &str, id: &str, args: &[SigSpec], ret: &SigSpec) -> rtlil::Cell {
let a = args
.get(0)
.expect("wrong argcount slipped through type check");
let b = args
.get(1)
.expect("wrong argcount slipped through type check");
assert_eq!(args.len(), 2);
fn instantiate_binop(
celltype: &str,
id: &str,
a: &SigSpec,
b: &SigSpec,
ret: &SigSpec,
) -> rtlil::Cell {
let mut cell = rtlil::Cell::new(id, celltype);
cell.add_param("\\A_SIGNED", "0");
cell.add_param("\\A_WIDTH", "1");
@ -38,41 +31,3 @@ fn instantiate_binop(celltype: &str, id: &str, args: &[SigSpec], ret: &SigSpec)
cell.add_connection("\\Y", ret);
cell
}
/*
fn make_binop_callable<'ctx>(name: &str, _celltype: &'static str) -> Callable {
// FIXME: CRIMES CRIMES CRIMES
let logic_type: &'static TypeStruct = Box::leak(Box::new(TypeStruct::logic_infer()));
let args = vec![
(Some("a".to_owned()), logic_type),
(Some("b".to_owned()), logic_type),
];
Callable {
name: name.to_owned(),
args,
ret_type: Some(logic_type),
}
}
fn make_unnop_callable<'ctx>(name: &str, _celltype: &'static str) -> Callable {
// FIXME: CRIMES CRIMES CRIMES
let logic_type: &'static TypeStruct = Box::leak(Box::new(TypeStruct::logic_infer()));
let args = vec![(Some("A".to_owned()), logic_type)];
Callable {
name: name.to_owned(),
args,
ret_type: Some(logic_type),
}
}
pub fn get_builtins<'ctx>() -> Vec<Callable> {
vec![
make_binop_callable("and", "$and"),
make_binop_callable("or", "$or"),
make_binop_callable("xor", "$xor"),
make_binop_callable("xnor", "$xnor"),
make_unnop_callable("not", "$not"),
make_unnop_callable("reduce_or", "$reduce_or"),
]
}
*/

9
src/frontend.rs

@ -8,7 +8,6 @@ pub use callable::{Callable, CallableContext, CallableId};
pub use types::{Type, TypeStruct, TypingContext};
mod callable;
#[cfg(never)]
pub mod lowering;
pub mod typed_ir;
pub mod types;
@ -234,6 +233,11 @@ impl Context {
) -> Result<typed_ir::Block, CompileError> {
let mut signals = Vec::new();
for (idx, tvarname) in comb.genparams.iter().enumerate() {
let tvar = self.types.make_typevar(0, idx as u32);
self.typenames.insert(tvarname.name.to_string(), tvar);
}
for port in comb.ports.iter() {
let sig_id = self.ids.next();
@ -289,7 +293,8 @@ impl Context {
}
match &expr.kind {
typed_ir::ExprKind::Literal(_) => todo!(),
typed_ir::ExprKind::Path(_) => todo!(),
// we can not see beyond this expression right now
typed_ir::ExprKind::Path(_) => expr.clone(),
typed_ir::ExprKind::Call {
called,
args,

295
src/frontend/lowering.rs

@ -1,255 +1,126 @@
use std::collections::BTreeMap;
use super::types::{make_primitives, TypeStruct};
use super::{make_pubid, CompileError, CompileErrorKind, Context, Signal, TODO_WIDTH};
use crate::builtin_cells::get_builtins;
use crate::parser;
use crate::parser::expression::Expression;
use super::typed_ir;
use super::typed_ir::ExprKind;
use super::{make_pubid, CompileError, Context, TODO_WIDTH};
use crate::rtlil;
use crate::rtlil::RtlilWrite;
/// context used when generating processes
struct ProcContext {
updates: Vec<(rtlil::SigSpec, rtlil::SigSpec)>,
next_sigs: BTreeMap<String, rtlil::SigSpec>,
}
fn lower_process_statement(
ctx: &Context,
pctx: &mut ProcContext,
module: &mut rtlil::Module,
stmt: &parser::proc::ProcStatement,
) -> Result<rtlil::CaseRule, CompileError> {
let rule = match stmt {
parser::proc::ProcStatement::IfElse(_) => todo!("if/else unimplemented"),
parser::proc::ProcStatement::Assign(assig) => {
// FIXME: actually store this
let next_sig;
if let Some(sig) = pctx.next_sigs.get(assig.lhs) {
next_sig = sig.clone();
} else {
let next_gen_id = format!("${}$next", assig.lhs);
module.add_wire(rtlil::Wire::new(&next_gen_id, TODO_WIDTH, None));
next_sig = rtlil::SigSpec::Wire(next_gen_id);
pctx.next_sigs
.insert(assig.lhs.to_owned(), next_sig.clone());
// trigger the modified value to update
pctx.updates
.push((ctx.try_get_signal(assig.lhs)?.sigspec(), next_sig.clone()));
};
let next_expr_wire = lower_expression(ctx, module, &assig.expr)?;
rtlil::CaseRule {
assign: vec![(next_sig, next_expr_wire)],
switches: vec![],
}
}
parser::proc::ProcStatement::Match(match_block) => {
let match_sig = lower_expression(ctx, module, &match_block.expr)?;
let mut cases = vec![];
for arm in &match_block.arms {
let case = lower_process_statement(ctx, pctx, module, &arm.1)?;
let compare_sig = lower_expression(ctx, module, &arm.0)?;
cases.push((compare_sig, case));
}
let switch_rule = rtlil::SwitchRule {
signal: match_sig,
cases,
};
rtlil::CaseRule {
assign: vec![],
switches: vec![switch_rule],
}
}
parser::proc::ProcStatement::Block(_) => todo!("blocks unimplemented"),
};
Ok(rule)
}
fn lower_process(
ctx: &Context,
module: &mut rtlil::Module,
process: &parser::proc::ProcBlock,
) -> Result<(), CompileError> {
let mut pctx = ProcContext {
updates: vec![],
next_sigs: BTreeMap::new(),
};
let mut cases = vec![];
for stmt in &process.items {
let case = lower_process_statement(ctx, &mut pctx, module, stmt)?;
cases.push(case);
}
let sync_sig = ctx.try_get_signal(process.net.fragment())?;
let sync_cond = rtlil::SyncCond::Posedge(sync_sig.sigspec());
let sync_rule = rtlil::SyncRule {
cond: sync_cond,
assign: pctx.updates,
};
if cases.len() != 1 {
panic!("only one expression per block, for now")
}
assert_eq!(cases.len(), 1);
let ir_proc = rtlil::Process {
id: module.make_genid("proc"),
root_case: cases.into_iter().next().unwrap(),
sync_rules: vec![sync_rule],
};
module.add_process(ir_proc);
Ok(())
}
fn desugar_binop<'a>(op: parser::expression::BinOp<'a>) -> parser::expression::Call<'a> {
let a = desugar_expression(op.a);
let b = desugar_expression(op.b);
let op_func = match op.kind {
parser::expression::BinOpKind::And => "and",
parser::expression::BinOpKind::Or => "or",
parser::expression::BinOpKind::Xor => "xor",
};
parser::expression::Call {
name: op_func.into(),
args: vec![a, b],
}
}
fn desugar_unop<'a>(op: parser::expression::UnOp<'a>) -> parser::expression::Call<'a> {
let a = desugar_expression(op.a);
let op_func = match op.kind {
parser::expression::UnOpKind::BitNot => "not",
parser::expression::UnOpKind::Not => todo!("bin not"),
};
parser::expression::Call {
name: op_func.into(),
args: vec![a],
}
}
fn desugar_expression<'a>(expr: Expression<'a>) -> Expression<'a> {
// TODO: allow ergonomic traversal of AST
match expr {
Expression::Path(_) => expr,
Expression::Literal(_) => expr,
Expression::Call(mut call) => {
let new_args = call.args.into_iter().map(desugar_expression).collect();
call.args = new_args;
Expression::Call(call)
}
Expression::BinOp(op) => Expression::Call(Box::new(desugar_binop(*op))),
Expression::UnOp(op) => Expression::Call(Box::new(desugar_unop(*op))),
}
}
fn lower_expression(
ctx: &Context,
module: &mut rtlil::Module,
expr: &Expression,
expr: &typed_ir::Expr,
) -> Result<rtlil::SigSpec, CompileError> {
let expr = desugar_expression(expr.clone());
match expr {
Expression::Path(ident) => {
let signal = ctx.try_get_signal(ident)?;
Ok(signal.sigspec())
}
Expression::Call(call) => {
let args_resolved = call
.args
let expr_width = ctx.types.get_width(expr.typ).expect("signal needs width");
let expr_wire_name = format!("$sig_{}", expr.id.0);
let expr_wire = rtlil::Wire::new(expr_wire_name.clone(), expr_width, None);
module.add_wire(expr_wire);
match &expr.kind {
ExprKind::Path(def) => Ok(rtlil::SigSpec::Wire(format!("$sig_{}", def.0))),
ExprKind::Call {
called,
args,
genargs,
} => {
let args_resolved = args
.iter()
.map(|expr| lower_expression(ctx, module, expr))
.collect::<Result<Vec<_>, _>>()?;
let callable = ctx
.callables
.get(call.name.fragment() as &str)
.ok_or_else(|| {
CompileError::new(CompileErrorKind::UndefinedReference(
call.name.fragment().to_string(),
))
})?;
if args_resolved.len() != callable.argcount() {
return Err(CompileError::new(CompileErrorKind::BadArgCount {
expected: callable.argcount(),
received: args_resolved.len(),
}));
}
let callable = ctx.callables.get(*called);
let cell_id = module.make_genid(callable.name());
let output_gen_id = format!("{}$out", &cell_id);
module.add_wire(rtlil::Wire::new(&output_gen_id, TODO_WIDTH, None));
let output_gen_wire = rtlil::SigSpec::Wire(output_gen_id);
// let cell =
// (*callable.instantiate)(&cell_id, args_resolved.as_slice(), &output_gen_wire);
// module.add_cell(cell);
Ok(output_gen_wire)
if *called == ctx.callables.builtins.xor {
let a_width = ctx.types.get_width(args[0].typ).unwrap();
let b_width = ctx.types.get_width(args[1].typ).unwrap();
let y_width = ctx.types.get_width(expr.typ).unwrap();
let mut cell = rtlil::Cell::new(&cell_id, "$xor");
cell.add_param("\\A_SIGNED", "0");
cell.add_param("\\A_WIDTH", &a_width.to_string());
cell.add_param("\\B_SIGNED", "0");
cell.add_param("\\B_WIDTH", &b_width.to_string());
cell.add_param("\\Y_WIDTH", &y_width.to_string());
cell.add_connection(
"\\A",
&rtlil::SigSpec::Wire(format!("$sig_{}", args[0].id.0)),
);
cell.add_connection(
"\\B",
&rtlil::SigSpec::Wire(format!("$sig_{}", args[1].id.0)),
);
cell.add_connection("\\Y", &rtlil::SigSpec::Wire(expr_wire_name.clone()));
module.add_cell(cell);
} else if *called == ctx.callables.builtins.reduce_or {
let a_width = ctx.types.get_width(args[0].typ).unwrap();
let y_width = ctx.types.get_width(expr.typ).unwrap();
let mut cell = rtlil::Cell::new(&cell_id, "$reduce_or");
cell.add_param("\\A_SIGNED", "0");
cell.add_param("\\A_WIDTH", &a_width.to_string());
cell.add_param("\\Y_WIDTH", &y_width.to_string());
cell.add_connection(
"\\A",
&rtlil::SigSpec::Wire(format!("$sig_{}", args[0].id.0)),
);
cell.add_connection("\\Y", &rtlil::SigSpec::Wire(expr_wire_name.clone()));
module.add_cell(cell);
} else if *called == ctx.callables.builtins.bitnot {
let a_width = ctx.types.get_width(args[0].typ).unwrap();
let y_width = ctx.types.get_width(expr.typ).unwrap();
let mut cell = rtlil::Cell::new(&cell_id, "$not");
cell.add_param("\\A_SIGNED", "0");
cell.add_param("\\A_WIDTH", &a_width.to_string());
cell.add_param("\\Y_WIDTH", &y_width.to_string());
cell.add_connection(
"\\A",
&rtlil::SigSpec::Wire(format!("$sig_{}", args[0].id.0)),
);
cell.add_connection("\\Y", &rtlil::SigSpec::Wire(expr_wire_name.clone()));
module.add_cell(cell);
}
// TODO: insert builtin cells here
Ok(rtlil::SigSpec::Wire(expr_wire_name))
}
// TODO: instantiate operators directly here instead of desugaring, once the callable infrastructure improves
// to get better errors
Expression::Literal(lit) => Ok(rtlil::SigSpec::Const(
lit.span().fragment().parse().unwrap(),
TODO_WIDTH,
ExprKind::Literal(lit) => Ok(rtlil::SigSpec::Const(
todo!(),
ctx.types.get_width(expr.typ).expect("signal has no size"),
)),
Expression::UnOp(_) => todo!("unary op"),
Expression::BinOp(_) => todo!("binary op"),
}
}
fn lower_assignment(
ctx: &Context,
module: &mut rtlil::Module,
assignment: parser::Assign,
) -> Result<(), CompileError> {
let target_id = ctx.try_get_signal(assignment.lhs)?.sigspec();
let return_wire = lower_expression(ctx, module, &assignment.expr)?;
module.add_connection(&target_id, &return_wire);
Ok(())
}
fn lower_comb(
ctx: &mut Context,
module: &mut rtlil::Module,
pa_comb: parser::comb::CombBlock,
block: typed_ir::Block,
) -> Result<(), CompileError> {
for (num, port) in pa_comb.ports.iter().enumerate() {
let port_id = make_pubid(port.net.name.fragment());
let port_tyname = &port.net.typ;
ctx.try_get_type(port_tyname.name.fragment())?;
for (num, sig) in block.signals.iter().enumerate() {
let sig_id = format!("$sig_{}", sig.id.0);
let port_width = ctx.types.get_width(sig.typ).expect("signal has no size");
module.add_wire(rtlil::Wire::new(
port_id.clone(),
TODO_WIDTH,
sig_id.clone(),
port_width,
Some(rtlil::PortOption::Input((num + 1) as i32)),
));
let typ = TypeStruct::logic_width(TODO_WIDTH);
let signal = Signal {
name: port.net.name.fragment().to_string(),
il_id: port_id,
// TODO: CRIMES CRIMES CRIMES
typ: Box::leak(Box::new(typ)),
};
ctx.signals
.insert(port.net.name.fragment().to_string(), signal);
}
let ret_id = module.make_genid("ret");
module.add_wire(rtlil::Wire::new(
ret_id.clone(),
TODO_WIDTH,
ctx.types
.get_width(block.expr.typ)
.expect("signal has no size"),
Some(rtlil::PortOption::Output(99)),
));
let out_sig = lower_expression(ctx, module, &pa_comb.expr)?;
let out_sig = lower_expression(ctx, module, &block.expr)?;
module.add_connection(&rtlil::SigSpec::Wire(ret_id), &out_sig);
Ok(())
}
pub fn lower_module(pa_module: parser::Module) -> Result<String, CompileError> {
pub fn lower_block(context: &mut Context, block: typed_ir::Block) -> Result<String, CompileError> {
let mut writer = rtlil::ILWriter::new();
let mut ir_module = rtlil::Module::new(make_pubid("test"));
lower_comb(context, &mut ir_module, block)?;
writer.write_line("autoidx 1");
ir_module.write_rtlil(&mut writer);
Ok(writer.finish())

17
src/frontend/types.rs

@ -154,6 +154,23 @@ impl TypingContext {
})
}
pub fn get_width(&self, typ: Type) -> Option<u32> {
match &self.get(typ).kind {
TypeKind::ElabType(_) => None,
TypeKind::Logic(data) => match &data.value {
ElabValue::Infer => None,
ElabValue::Concrete(val) => match val {
ElabValueData::U32(val) => Some(*val),
ElabValueData::Bytes(_) => None,
},
},
TypeKind::UInt(_) => todo!(),
TypeKind::Callable(_) => None,
TypeKind::Infer => None,
TypeKind::TypeVar(_, _) => None,
}
}
pub fn parameterize(&mut self, typ: Type, params: &[GenericArg]) -> Option<Type> {
// TODO: return proper error type here
match &self.get(typ).kind {

23
src/main.rs

@ -74,19 +74,20 @@ fn main() {
.pretty_typed_block(&mut pretty_block, &typed_inferred)
.unwrap();
println!("{}", &pretty_block);
let lowered =
frontend::lowering::lower_block(&mut frontendcontext, typed_inferred);
match lowered {
Ok(res) => {
let mut file =
File::create(opt.output.unwrap_or_else(|| "out.rtlil".into()))
.expect("could not open file");
file.write_all(res.as_bytes())
.expect("failed to write output file");
}
Err(err) => eprintln!("{:#?}", err),
}
}
}
/*
match lowered {
Ok(res) => {
let mut file = File::create(opt.output.unwrap_or_else(|| "out.rtlil".into()))
.expect("could not open file");
file.write_all(res.as_bytes())
.expect("failed to write output file");
}
Err(err) => eprintln!("{:#?}", err),
}
*/
}
}
}

13
src/parser/comb.rs

@ -10,14 +10,15 @@ use nom::sequence::delimited;
use nom::sequence::preceded;
use nom::sequence::tuple;
use nom::{
combinator::{cut, map},
multi::many0,
combinator::{cut, map, opt},
multi::many1,
};
#[derive(Debug)]
pub struct CombBlock<'a> {
pub name: Span<'a>,
pub ports: Vec<PortDecl<'a>>,
pub genparams: Vec<TypeName<'a>>,
pub ret: TypeName<'a>,
pub expr: Expression<'a>,
}
@ -28,15 +29,21 @@ pub fn comb_block(input: TokenSpan) -> IResult<TokenSpan, CombBlock> {
token(tk::Comb),
cut(tuple((
token(tk::Ident),
opt(delimited(
token(tk::LAngle),
many1(typename),
token(tk::RAngle),
)),
delimited(token(tk::LParen), inputs_list, token(tk::RParen)),
preceded(token(tk::RArrow), typename),
delimited(token(tk::LBrace), expression, token(tk::RBrace)),
))),
),
|(name, inputs, ret, expr)| CombBlock {
|(name, genparams, inputs, ret, expr)| CombBlock {
// TODO: bring back returns
name: name.span(),
ports: inputs,
genparams: genparams.into_iter().flatten().collect(),
ret,
expr,
},

4
src/rtlil.rs

@ -94,6 +94,10 @@ impl Wire {
port_info,
}
}
pub fn id(&self) -> &str {
&self.id
}
}
impl RtlilWrite for Wire {

Loading…
Cancel
Save