futilehdl/src/frontend.rs

219 lines
6.2 KiB
Rust

use std::cell::Cell;
use std::collections::BTreeMap;
use super::parser;
use crate::rtlil;
pub use callable::Callable;
pub use types::{Type, TypeStruct, TypingContext};
mod callable;
#[cfg(never)]
pub mod lowering;
pub mod typed_ir;
pub mod types;
#[cfg(never)]
use crate::builtin_cells::get_builtins;
// pub use lowering::lower_module;
/// 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 },
TodoError(String),
TypeError { expected: Type, found: Type },
}
#[derive(Debug)]
pub struct CompileError {
kind: CompileErrorKind,
}
impl CompileError {
fn new(kind: CompileErrorKind) -> Self {
Self { kind }
}
}
/// 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())
}
}
pub struct Context {
/// map callable name to callable
callables: BTreeMap<String, Callable>,
/// type names
typenames: BTreeMap<String, Type>,
types: TypingContext,
/// map signal name to Signal
signals: BTreeMap<String, typed_ir::Signal>,
/// incrementing counter for unique IDs
ids: Counter,
}
struct Counter(Cell<usize>);
impl Counter {
fn new() -> Counter {
Counter(Cell::new(0))
}
fn next(&self) -> usize {
let next = self.0.get() + 1;
self.0.set(next);
next
}
}
impl Context {
pub fn new() -> Self {
let tcx = TypingContext::new();
Context {
callables: BTreeMap::new(),
signals: BTreeMap::new(),
types: TypingContext::new(),
typenames: [("Logic".to_string(), tcx.primitives.logic)].into(),
ids: Counter::new(),
}
}
fn try_get_signal(&self, signame: &str) -> Result<&typed_ir::Signal, CompileError> {
self.signals.get(signame).ok_or_else(|| {
CompileError::new(CompileErrorKind::UndefinedReference(signame.to_owned()))
})
}
fn try_get_type(&self, typename: &str) -> Result<Type, CompileError> {
self.typenames.get(typename).copied().ok_or_else(|| {
CompileError::new(CompileErrorKind::UndefinedReference(typename.to_owned()))
})
}
fn try_get_callable(&self, callname: &str) -> Result<&Callable, CompileError> {
self.callables.get(callname).ok_or_else(|| {
CompileError::new(CompileErrorKind::UndefinedReference(callname.to_owned()))
})
}
fn type_expression(
&self,
expr: &parser::expression::Expression,
) -> Result<typed_ir::Expr, CompileError> {
use parser::expression::Expression;
let id = typed_ir::ExprId(self.ids.next() as u32);
let t_expr = match expr {
Expression::Path(name) => {
let signal = self.try_get_signal(name)?;
typed_ir::Expr {
id,
kind: typed_ir::ExprKind::Path(signal.id),
typ: signal.typ,
}
}
Expression::Literal(_) => todo!(),
Expression::UnOp(op) => self.type_expression(&op.a)?,
Expression::BinOp(op) => {
let (a, b) = (self.type_expression(&op.a)?, self.type_expression(&op.b)?);
typed_ir::Expr {
id,
kind: typed_ir::ExprKind::Call {
called: typed_ir::DefId(99),
args: vec![a, b],
},
typ: self.types.primitives.elabnum,
}
}
Expression::Call(call) => {
let args_resolved = call
.args
.iter()
.map(|expr| self.type_expression(expr))
.collect::<Result<Vec<_>, _>>()?;
typed_ir::Expr {
id,
kind: typed_ir::ExprKind::Call {
called: typed_ir::DefId(99),
args: args_resolved,
},
typ: self.types.primitives.elabnum,
}
}
};
Ok(t_expr)
}
fn type_comb(
&mut self,
comb: &parser::comb::CombBlock,
) -> Result<typed_ir::Block, CompileError> {
let mut signals = Vec::new();
for port in comb.ports.iter() {
let sig_id = self.ids.next();
let sig_typename = &port.net.typ;
let sig_type = self.try_get_type(sig_typename.name.fragment())?;
let sig = typed_ir::Signal {
id: typed_ir::DefId(sig_id as u32),
typ: sig_type,
};
signals.push(sig.clone());
self.signals.insert(port.net.name.to_string(), sig);
}
let ret_typename = &comb.ret.name;
let ret_type = self.try_get_type(ret_typename.fragment())?;
let root_expr = self.type_expression(&comb.expr)?;
// TODO: more sophisticated type compat check
if root_expr.typ != ret_type {
let expected = ret_type;
let found = root_expr.typ;
return Err(CompileError::new(CompileErrorKind::TypeError {
expected,
found,
}));
}
Ok(typed_ir::Block {
signals,
expr: root_expr,
})
}
pub fn type_module(&mut self, module: parser::Module) -> Result<typed_ir::Block, CompileError> {
for item in module.items {
let block = match &item {
parser::ModuleItem::Comb(comb) => self.type_comb(comb)?,
parser::ModuleItem::Proc(_) => todo!(),
parser::ModuleItem::State(_) => todo!(),
};
return Ok(block);
}
Err(CompileError::new(CompileErrorKind::TodoError(
"no blocks in module".to_string(),
)))
}
}