use std::collections::BTreeMap; use crate::builtin_cells::get_builtins; use crate::parser; use crate::rtlil; use crate::rtlil::RtlilWrite; /// lots of code is still not width-aware, this constant keeps track of that const TODO_WIDTH: u32 = 1; fn make_pubid(id: &str) -> String { "\\".to_owned() + id } #[derive(Debug)] pub enum CompileErrorKind { UndefinedReference(String), BadArgCount { received: usize, expected: usize }, } #[derive(Debug)] pub struct CompileError { kind: CompileErrorKind, } impl CompileError { fn new(kind: CompileErrorKind) -> Self { Self { kind } } } pub enum GenericParam { Unsolved, Solved(T), } pub enum Type { /// a wire of some width Wire(GenericParam), } impl Type { pub fn wire() -> Self { Self::Wire(GenericParam::Unsolved) } } pub struct CallArgument { pub name: String, pub atype: Type, } // module that can be instantiated like a function pub struct Callable { pub name: String, pub args: Vec, pub ret: Type, pub instantiate: Box rtlil::Cell>, } /// A user-defined signal pub struct Signal { /// the user-visible name of the signal pub name: String, /// the id of the signal in RTLIL pub il_id: String, /// the type of the signal pub typ: Type, // unique ID of the signal // pub uid: u64, } impl Signal { fn sigspec(&self) -> rtlil::SigSpec { rtlil::SigSpec::Wire(self.il_id.to_owned()) } } /// context used when generating processes struct ProcContext { updates: Vec<(rtlil::SigSpec, rtlil::SigSpec)>, next_sigs: BTreeMap, } struct Context { /// map callable name to callable callables: BTreeMap, /// map signal name to Signal signals: BTreeMap } impl Context { fn get_signal(&self, signame: &str) -> Option<&Signal> { self.signals.get(signame) } fn try_get_signal(&self, signame: &str) -> Result<&Signal, CompileError> { self.get_signal(signame) .ok_or(CompileError::new(CompileErrorKind::UndefinedReference(signame.to_owned()))) } } fn lower_process_statement( ctx: &Context, pctx: &mut ProcContext, module: &mut rtlil::Module, stmt: &parser::proc::ProcStatement, ) -> Result { 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.clone(), 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_operation<'a>(op: parser::Operation<'a>) -> parser::Call<'a> { match op { parser::Operation::And { a, b } => { let a = desugar_expression(a); let b = desugar_expression(b); parser::Call { name: "and".into(), args: vec![a, b] } }, parser::Operation::Or { a, b } => { let a = desugar_expression(a); let b = desugar_expression(b); parser::Call { name: "or".into(), args: vec![a, b] } } parser::Operation::Xor { a, b } => { let a = desugar_expression(a); let b = desugar_expression(b); parser::Call { name: "xor".into(), args: vec![a, b] } } parser::Operation::Not(a) => { let a = desugar_expression(a); parser::Call { name: "not".into(), args: vec![a] } } } } fn desugar_expression<'a>(expr: parser::Expression<'a>) -> parser::Expression<'a> { // TODO: allow ergonomic traversal of AST match expr { parser::Expression::Ident(_) => expr, parser::Expression::Literal(_) => expr, parser::Expression::Call(mut call) => { let new_args = call.args.into_iter().map(|argex| desugar_expression(argex)).collect(); call.args = new_args; parser::Expression::Call(call) }, parser::Expression::Operation(op) => { parser::Expression::Call(Box::new(desugar_operation(*op))) }, } } fn lower_expression( ctx: &Context, module: &mut rtlil::Module, expr: &parser::Expression, ) -> Result { let expr = desugar_expression(expr.clone()); match expr { parser::Expression::Ident(ident) => { let signal = ctx.try_get_signal(ident)?; Ok(signal.sigspec()) }, parser::Expression::Call(call) => { let args_resolved = call .args .iter() .map(|expr| lower_expression(ctx, module, expr)) .collect::, _>>()?; 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.args.len() { return Err(CompileError::new(CompileErrorKind::BadArgCount { expected: callable.args.len(), received: args_resolved.len(), })); } 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) } // TODO: instantiate operators directly here instead of desugaring, once the callable infrastructure improves // to get better errors parser::Expression::Operation(_op) => todo!("operators not yet implemented"), parser::Expression::Literal(lit) => Ok(rtlil::SigSpec::Const(lit as i64, TODO_WIDTH)), } } 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(()) } pub fn lower_module(pa_module: parser::Module) -> Result { let mut writer = rtlil::ILWriter::new(); let mut ir_module = rtlil::Module::new(make_pubid(pa_module.name)); let mut context = Context { callables: get_builtins() .into_iter() .map(|clb| (clb.name.to_owned(), clb)) .collect(), signals: BTreeMap::new(), }; writer.write_line("autoidx 1"); for (idx, port) in pa_module.ports.iter().enumerate() { let sig = Signal { name: port.net.name.to_owned(), il_id: make_pubid(port.net.name), typ: Type::Wire(GenericParam::Solved(port.net.width.unwrap_or(1) as u32)) }; let sig = context.signals.entry(port.net.name.to_owned()).or_insert(sig); let dir_option = match port.direction { parser::PortDirection::Input => rtlil::PortOption::Input(idx as i32 + 1), parser::PortDirection::Output => rtlil::PortOption::Output(idx as i32 + 1), }; let wire = rtlil::Wire::new( sig.il_id.to_owned(), port.net.width.unwrap_or(1) as u32, Some(dir_option), ); ir_module.add_wire(wire); } for item in pa_module.items { match item { parser::ModuleItem::Assign(assignment) => { lower_assignment(&context, &mut ir_module, assignment)? } parser::ModuleItem::Proc(proc) => lower_process(&context, &mut ir_module, &proc)?, } } ir_module.write_rtlil(&mut writer); Ok(writer.finish()) }