diff --git a/Cargo.lock b/Cargo.lock index cc8e9135..90c227c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1058,7 +1058,9 @@ dependencies = [ "expect-test", "float-cmp", "hir", + "hir_lower", "indexmap 2.14.0", + "lasso", "libc", "libloading", "linker", @@ -1068,6 +1070,8 @@ dependencies = [ "llvm-sys 211.0.1", "md5", "mini_harness", + "mir", + "mir_interpret", "mir_llvm", "osdi", "paths", @@ -1076,6 +1080,7 @@ dependencies = [ "target", "termcolor", "tokens", + "typed-index-collections", ] [[package]] diff --git a/openvaf/hir/src/body.rs b/openvaf/hir/src/body.rs index 36f39a50..9b7765c6 100644 --- a/openvaf/hir/src/body.rs +++ b/openvaf/hir/src/body.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use hir_def::db::HirDefDB; -pub use hir_def::expr::Event; +pub use hir_def::expr::{Event, GlobalEvent}; use hir_def::DefWithBodyId; pub use hir_def::{/*expr::CaseCond,*/ BuiltIn, Case, ExprId, Literal, ParamSysFun, StmtId, Type,}; use hir_ty::db::HirTyDB; @@ -167,6 +167,7 @@ impl<'a> BodyRef<'a> { Expr::Call { fun, args } } hir_def::Expr::Array(ref args) => Expr::Array(args), + hir_def::Expr::Index { base, index } => Expr::Index { base, index }, hir_def::Expr::Literal(ref literal) => Expr::Literal(literal), _ => panic!("invalid HIR: {:?}", self.body.exprs[expr]), } @@ -187,11 +188,16 @@ impl<'a> BodyRef<'a> { hir_def::Stmt::EventControl { ref event, body } => { Some(Stmt::EventControl { event, body }) } - hir_def::Stmt::Assignment { val, .. } => { + hir_def::Stmt::Assignment { val, assignment_kind, .. } => { + let indirect = assignment_kind == syntax::ast::AssignOp::Indirect; let stmt = match self.infere.assignment_destination[&stmnt] { inference::AssignDst::Var(id) => { Stmt::Assignment { lhs: AssignmentLhs::Variable(Variable { id }), rhs: val } } + inference::AssignDst::VarElement { var, index } => Stmt::Assignment { + lhs: AssignmentLhs::ArrayElement { var: Variable { id: var }, index }, + rhs: val, + }, inference::AssignDst::FunVar { fun, arg: None } => Stmt::Assignment { lhs: AssignmentLhs::FunctionReturn(Function { id: fun }), rhs: val, @@ -201,12 +207,20 @@ impl<'a> BodyRef<'a> { rhs: val, }, inference::AssignDst::Flow(branch) => Stmt::Contribute { - kind: ContributeKind::Flow, + kind: if indirect { + ContributeKind::IndirectFlow + } else { + ContributeKind::Flow + }, branch: branch.into(), rhs: val, }, inference::AssignDst::Potential(branch) => Stmt::Contribute { - kind: ContributeKind::Potential, + kind: if indirect { + ContributeKind::IndirectPotential + } else { + ContributeKind::Potential + }, branch: branch.into(), rhs: val, }, @@ -231,12 +245,22 @@ pub enum AssignmentLhs { Variable(Variable), FunctionReturn(Function), FunctionArg(FunctionArg), + /// `arr[index] = …` — assignment to an array element. + ArrayElement { var: Variable, index: ExprId }, } #[derive(Debug, Clone, Eq, PartialEq)] pub enum ContributeKind { Flow, Potential, + /// Indirect branch assignment `I(out) : f(...) == 0` — `out` becomes a current + /// source whose value is solved so the constraint `f == 0` holds. `rhs` is the + /// constraint equation. + IndirectFlow, + /// Indirect branch assignment `V(out) : f(...) == 0` — `out` becomes a voltage + /// source whose value is solved so the constraint `f == 0` holds. `rhs` is the + /// constraint equation. + IndirectPotential, } #[derive(Debug, Clone, Eq, PartialEq)] @@ -270,6 +294,8 @@ pub enum Expr<'a> { Select { cond: ExprId, then_val: ExprId, else_val: ExprId }, Call { fun: ResolvedFun, args: &'a [ExprId] }, Array(&'a [ExprId]), + /// Array element access `base[index]`. + Index { base: ExprId, index: ExprId }, Literal(&'a Literal), } impl Expr<'_> { diff --git a/openvaf/hir/src/diagnostics.rs b/openvaf/hir/src/diagnostics.rs index 5686a846..154c135e 100644 --- a/openvaf/hir/src/diagnostics.rs +++ b/openvaf/hir/src/diagnostics.rs @@ -4,7 +4,7 @@ pub use basedb::{BaseDB, FileId}; use hir_def::db::HirDefDB; use hir_def::nameres::diagnostics::DefDiagnosticWrapped; use hir_def::nameres::{DefMap, LocalScopeId, ScopeDefItem, ScopeOrigin}; -use hir_def::DefWithBodyId; +use hir_def::{DefWithBodyId, ModuleBodyKind}; use hir_ty::diagnostics::InferenceDiagnosticWrapped; use hir_ty::validation::{ self, BodyValidationDiagnostic, BodyValidationDiagnosticWrapped, @@ -48,7 +48,7 @@ pub(crate) fn collect(db: &CompilationDB, root_file: FileId, sink: &mut impl Dia collect_body_diagnostcs( db, sink, - DefWithBodyId::ModuleId { initial: true, module }, + DefWithBodyId::ModuleId { kind: ModuleBodyKind::AnalogInitial, module }, &parse, &sm, root_file, @@ -57,7 +57,16 @@ pub(crate) fn collect(db: &CompilationDB, root_file: FileId, sink: &mut impl Dia collect_body_diagnostcs( db, sink, - DefWithBodyId::ModuleId { initial: false, module }, + DefWithBodyId::ModuleId { kind: ModuleBodyKind::Analog, module }, + &parse, + &sm, + root_file, + &ast_id_map, + ); + collect_body_diagnostcs( + db, + sink, + DefWithBodyId::ModuleId { kind: ModuleBodyKind::Procedural, module }, &parse, &sm, root_file, diff --git a/openvaf/hir/src/lib.rs b/openvaf/hir/src/lib.rs index d446d48c..f10f9538 100644 --- a/openvaf/hir/src/lib.rs +++ b/openvaf/hir/src/lib.rs @@ -22,8 +22,8 @@ pub use hir_def::nameres::diagnostics::PathResolveError; use hir_def::nameres::{DefMap, LocalScopeId, ScopeDefItem}; use hir_def::{ AliasParamId, BlockId, BlockLoc, BranchId, DefWithBodyId, DisciplineId, FunctionId, - LocalFunctionArgId, Lookup, ModuleId, ModuleLoc, NatureAttrId, NatureId, NodeId, ParamId, - VarId, + LocalFunctionArgId, Lookup, ModuleBodyKind, ModuleId, ModuleLoc, NatureAttrId, NatureId, + NodeId, ParamId, VarId, }; pub use hir_def::{BuiltIn, Case, Literal, ParamSysFun, Path, Type}; pub use hir_ty::builtin; @@ -37,7 +37,8 @@ pub use syntax::name::Name; pub use crate::attributes::AstCache; pub use crate::body::{ - AssignmentLhs, Body, BodyRef, ContributeKind, Expr, ExprId, Ref, ResolvedFun, Stmt, StmtId, + AssignmentLhs, Body, BodyRef, ContributeKind, Event, Expr, ExprId, GlobalEvent, Ref, + ResolvedFun, Stmt, StmtId, }; pub use crate::db::CompilationDB; @@ -162,11 +163,23 @@ impl Module { } pub fn analog_initial_block(&self, db: &CompilationDB) -> Body { - Body::new(DefWithBodyId::ModuleId { initial: true, module: self.id }, db) + Body::new( + DefWithBodyId::ModuleId { kind: ModuleBodyKind::AnalogInitial, module: self.id }, + db, + ) } pub fn analog_block(&self, db: &CompilationDB) -> Body { - Body::new(DefWithBodyId::ModuleId { initial: false, module: self.id }, db) + Body::new(DefWithBodyId::ModuleId { kind: ModuleBodyKind::Analog, module: self.id }, db) + } + + /// The imperative `initial`/`final` procedural body executed by the standalone + /// VerilogA runner (`openvaf-r run`). Empty for ordinary device models. + pub fn procedural_block(&self, db: &CompilationDB) -> Body { + Body::new( + DefWithBodyId::ModuleId { kind: ModuleBodyKind::Procedural, module: self.id }, + db, + ) } // todo: just temporary for VAE, this needs to be cleaned up diff --git a/openvaf/hir_def/src/body.rs b/openvaf/hir_def/src/body.rs index 4847977f..0a1c671a 100644 --- a/openvaf/hir_def/src/body.rs +++ b/openvaf/hir_def/src/body.rs @@ -7,6 +7,7 @@ use basedb::lints::{Lint, LintSrc}; use basedb::{AttrDiagnostic, LintAttrs}; use lower::LowerCtx; use stdx::Ieee64; +use syntax::name::AsName; use syntax::{ast, AstNode, AstPtr}; use crate::db::HirDefDB; @@ -14,7 +15,8 @@ use crate::item_tree::{DisciplineAttr, ItemTreeId, ItemTreeNode, NatureAttr}; use crate::nameres::{DefMapSource, LocalScopeId}; use crate::{ DefWithBodyId, DisciplineAttrLoc, DisciplineLoc, Expr, ExprId, FunctionLoc, Literal, Lookup, - ModuleLoc, NatureAttrLoc, NatureLoc, ParamId, ParamLoc, ScopeId, Stmt, StmtId, Type, VarLoc, + ModuleBodyKind, ModuleLoc, NatureAttrLoc, NatureLoc, ParamId, ParamLoc, ScopeId, Stmt, StmtId, + Type, VarLoc, }; mod lower; @@ -67,7 +69,7 @@ impl Body { let (body, sm, _) = db.param_body_with_sourcemap(param); return (body, sm); } - DefWithBodyId::ModuleId { initial, module } => { + DefWithBodyId::ModuleId { kind, module } => { let ModuleLoc { scope, id: item_tree } = module.lookup(db); let ast_id = tree[item_tree].ast_id(); @@ -81,11 +83,39 @@ impl Body { ast_id_map: &ast_id_map, curr_scope, registry: ®istry, + genvar_names: ast + .module_items() + .filter_map(|it| match it { + ast::ModuleItem::GenvarDecl(g) => Some(g), + _ => None, + }) + .flat_map(|g| g.names().map(|n| n.as_name())) + .collect(), + bus_names: ast + .module_items() + .filter_map(|it| match it { + ast::ModuleItem::NetDecl(net) if net.dimension().is_some() => Some(net), + _ => None, + }) + .flat_map(|net| net.names().map(|n| n.as_name())) + .collect(), + module: Some(ast.clone()), + genvars: Vec::new(), }; - body.entry_stmts = if initial { - ast.analog_initial_behaviour().map(|stmt| ctx.collect_stmt(stmt)).collect() - } else { - ast.analog_behaviour().map(|stmt| ctx.collect_stmt(stmt)).collect() + body.entry_stmts = match kind { + ModuleBodyKind::AnalogInitial => { + ast.analog_initial_behaviour().map(|stmt| ctx.collect_stmt(stmt)).collect() + } + ModuleBodyKind::Analog => { + ast.analog_behaviour().map(|stmt| ctx.collect_stmt(stmt)).collect() + } + // Procedural runner lane: all `initial` blocks (source order) then + // all `final` blocks, as one imperative sequence. + ModuleBodyKind::Procedural => ast + .initial_behaviour() + .chain(ast.final_behaviour()) + .map(|stmt| ctx.collect_stmt(stmt)) + .collect(), }; } @@ -110,6 +140,10 @@ impl Body { ast_id_map: &ast_id_map, curr_scope, registry: ®istry, + genvar_names: Vec::new(), + bus_names: Vec::new(), + module: None, + genvars: Vec::new(), }; body.entry_stmts = ast.body().map(|stmt| ctx.collect_stmt(stmt)).collect(); } @@ -127,15 +161,23 @@ impl Body { ast_id_map: &ast_id_map, curr_scope, registry: ®istry, + genvar_names: Vec::new(), + bus_names: Vec::new(), + module: None, + genvars: Vec::new(), }; let expr = if let Some(expr) = ast.default() { ctx.collect_expr(expr) } else { let default_val = match db.var_data(var).ty { - Type::Real => Literal::Float(Ieee64::with_float(0.0)), Type::Integer => Literal::Int(0), - _ => unreachable!("invalid var type (TODO arrays)"), + // Arrays have no scalar default (their elements are managed + // per-element during lowering); use 0.0 as a placeholder. + Type::Real | Type::Array { .. } => { + Literal::Float(Ieee64::with_float(0.0)) + } + _ => unreachable!("invalid var type"), }; ctx.alloc_expr_desugared(Expr::Literal(default_val)) }; @@ -160,6 +202,10 @@ impl Body { ast_id_map: &ast_id_map, curr_scope, registry: ®istry, + genvar_names: Vec::new(), + bus_names: Vec::new(), + module: None, + genvars: Vec::new(), }; let expr = ctx.collect_opt_expr(ast.val()); let stmt = ctx.alloc_stmt_desugared(Stmt::Expr(expr)); @@ -182,6 +228,10 @@ impl Body { ast_id_map: &ast_id_map, curr_scope, registry: ®istry, + genvar_names: Vec::new(), + bus_names: Vec::new(), + module: None, + genvars: Vec::new(), }; let expr = ctx.collect_opt_expr(ast.val()); let stmt = ctx.alloc_stmt_desugared(Stmt::Expr(expr)); @@ -216,6 +266,10 @@ impl Body { ast_id_map: &ast_id_map, curr_scope: (scope, ast_id.into()), registry: ®istry, + genvar_names: Vec::new(), + bus_names: Vec::new(), + module: None, + genvars: Vec::new(), }; let default = ctx.collect_opt_expr(ast.default()); diff --git a/openvaf/hir_def/src/body/lower.rs b/openvaf/hir_def/src/body/lower.rs index 1bacc01a..f8798ec9 100644 --- a/openvaf/hir_def/src/body/lower.rs +++ b/openvaf/hir_def/src/body/lower.rs @@ -4,7 +4,7 @@ use basedb::lints::LintRegistry; use basedb::{AstIdMap, ErasedAstId, LintAttrs}; use syntax::ast::{self, ArgListOwner, AttrIter, AttrsOwner, FunctionRef}; use syntax::name::AsName; -use syntax::AstPtr; +use syntax::{AstNode, AstPtr}; // use tracing::debug; use super::{Body, BodySourceMap}; @@ -20,6 +20,15 @@ pub(super) struct LowerCtx<'a> { pub(super) ast_id_map: &'a AstIdMap, pub(super) curr_scope: (ScopeId, ErasedAstId), pub(super) registry: &'a LintRegistry, + /// Enclosing module (for compile-time constant evaluation of genvar/bus + /// expressions against module parameters). `None` for function/var/param bodies. + pub(super) module: Option, + /// Names declared `genvar` in the enclosing module. + pub(super) genvar_names: Vec, + /// Net names declared as a vectored/bus (`electrical [0:n] inode;`). + pub(super) bus_names: Vec, + /// Currently-bound genvar values during compile-time loop unrolling. + pub(super) genvars: Vec<(syntax::name::Name, i64)>, } impl LowerCtx<'_> { @@ -77,9 +86,24 @@ impl LowerCtx<'_> { Expr::Select { cond, then_val, else_val } } + ast::Expr::IndexExpr(e) => { + // Vectored/bus node element `inode[i]` with a compile-time-constant + // index resolves to the expanded scalar node `inode[]`. + if let Some(id) = self.try_bus_index(e, &expr) { + return id; + } + let base = self.collect_opt_expr(e.base()); + let index = self.collect_opt_expr(e.index()); + Expr::Index { base, index } + } + // TODO refactor with if let binding and default case is missing expression // BLOCK ast::Expr::PathExpr(path) => { + // A reference to a bound genvar folds to its current constant value. + if let Some(id) = self.try_genvar_path(path, &expr) { + return id; + } if let Some(path) = path.path().and_then(Path::resolve) { Expr::Path { path, port: false } } else { @@ -138,6 +162,11 @@ impl LowerCtx<'_> { Stmt::WhileLoop { cond, body } } ast::Stmt::ForStmt(stmt) => { + // A `for` loop over a genvar with compile-time bounds is unrolled into + // a flat block of body copies (one per iteration, genvar substituted). + if let Some(id) = self.try_unroll_genvar_for(stmt) { + return id; + } let cond = self.collect_opt_expr(stmt.condition()); let init = self.collect_opt_stmt(stmt.init()); let incr = self.collect_opt_stmt(stmt.incr()); @@ -157,7 +186,15 @@ impl LowerCtx<'_> { } else if event_stmt.final_step_token().is_some() { GlobalEvent::FinalStep } else { - return self.collect_opt_stmt(event_stmt.stmt()); + // Monitored event (`@(cross(...))` / `@(timer(...))`): preserve it so MIR + // lowering can give the variables it assigns cross-timestep retention. + let body = self.collect_opt_stmt(event_stmt.stmt()); + let stmt = Stmt::EventControl { event: Event::Cross, body }; + return self.alloc_stmt( + stmt, + AstPtr::new(event_stmt).cast().unwrap(), + event_stmt.attrs(), + ); }; let phases = event_stmt.sim_phases().map(|lit| lit.unescaped_value()).collect(); @@ -213,6 +250,173 @@ impl LowerCtx<'_> { Stmt::Block { body } } + /// Evaluate a compile-time integer expression in the current genvar/parameter + /// environment (literals, integer arithmetic, bound genvars and module + /// parameter defaults). Returns `None` if it is not a compile-time constant. + fn eval_genvar_const(&self, expr: &ast::Expr) -> Option { + use syntax::ast::{BinaryOp, LiteralKind, UnaryOp}; + match expr { + ast::Expr::Literal(lit) => match lit.kind() { + LiteralKind::IntNumber(i) => Some(i.value() as i64), + _ => None, + }, + ast::Expr::PrefixExpr(p) => { + let v = self.eval_genvar_const(&p.expr()?)?; + match p.op_kind()? { + UnaryOp::Neg => Some(-v), + UnaryOp::Identity => Some(v), + _ => None, + } + } + ast::Expr::ParenExpr(p) => self.eval_genvar_const(&p.expr()?), + ast::Expr::BinExpr(b) => { + let l = self.eval_genvar_const(&b.lhs()?)?; + let r = self.eval_genvar_const(&b.rhs()?)?; + match b.op_kind()? { + BinaryOp::Addition => Some(l.wrapping_add(r)), + BinaryOp::Subtraction => Some(l.wrapping_sub(r)), + BinaryOp::Multiplication => Some(l.wrapping_mul(r)), + BinaryOp::Division if r != 0 => Some(l / r), + BinaryOp::Remainder if r != 0 => Some(l % r), + _ => None, + } + } + ast::Expr::PathExpr(pe) => { + let ident = pe.path()?.as_raw_ident()?; + let tname = ident.text(); + // genvar binding takes precedence over parameters + if let Some((_, val)) = self.genvars.iter().rev().find(|(gv, _)| tname == &**gv) { + return Some(*val); + } + let module = self.module.as_ref()?; + for pdecl in module.syntax().descendants().filter_map(ast::ParamDecl::cast) { + for para in pdecl.paras() { + if para.name().map_or(false, |n| n.text() == tname) { + return self.eval_genvar_const(¶.default()?); + } + } + } + None + } + _ => None, + } + } + + /// Evaluate a compile-time boolean loop condition (a comparison of two + /// compile-time integers). Returns `None` if it cannot be evaluated. + fn eval_genvar_cond(&self, expr: &ast::Expr) -> Option { + use syntax::ast::BinaryOp; + match expr { + ast::Expr::ParenExpr(p) => self.eval_genvar_cond(&p.expr()?), + ast::Expr::BinExpr(b) => { + let l = self.eval_genvar_const(&b.lhs()?)?; + let r = self.eval_genvar_const(&b.rhs()?)?; + match b.op_kind()? { + BinaryOp::LesserTest => Some(l < r), + BinaryOp::GreaterTest => Some(l > r), + BinaryOp::LesserEqualTest => Some(l <= r), + BinaryOp::GreaterEqualTest => Some(l >= r), + BinaryOp::EqualityTest => Some(l == r), + BinaryOp::NegatedEqualityTest => Some(l != r), + _ => None, + } + } + _ => None, + } + } + + /// If `expr` is a single identifier path, return its name. + fn single_ident(expr: &ast::Expr) -> Option { + match expr { + ast::Expr::PathExpr(pe) => { + let ident = pe.path()?.as_raw_ident()?; + Some(syntax::name::Name::resolve(ident.text().as_ref())) + } + _ => None, + } + } + + /// Fold a reference to a bound genvar into its current constant value. + fn try_genvar_path(&mut self, path: &ast::PathExpr, expr: &ast::Expr) -> Option { + let ident = path.path()?.as_raw_ident()?; + let tname = ident.text(); + let val = self.genvars.iter().rev().find(|(gv, _)| tname == &**gv).map(|(_, v)| *v)?; + Some(self.alloc_expr(Expr::Literal(Literal::Int(val as i32)), AstPtr::new(expr))) + } + + /// Resolve `inode[i]` (bus net element, compile-time-constant index) to the + /// expanded scalar node `inode[]`. + fn try_bus_index(&mut self, e: &ast::IndexExpr, expr: &ast::Expr) -> Option { + let base = e.base()?; + let pe = match &base { + ast::Expr::PathExpr(pe) => pe, + _ => return None, + }; + let ident = pe.path()?.as_raw_ident()?; + let bname = ident.text(); + if !self.bus_names.iter().any(|b| bname == &**b) { + return None; + } + let k = self.eval_genvar_const(&e.index()?)?; + let synth = syntax::name::Name::resolve(&format!("{}[{}]", bname, k)); + let path = Path::new_ident(synth); + Some(self.alloc_expr(Expr::Path { path, port: false }, AstPtr::new(expr))) + } + + /// Unroll a genvar `for` loop with compile-time bounds into a flat block of + /// body copies (genvar substituted per iteration). Returns `None` for ordinary + /// runtime loops, which are lowered normally. + fn try_unroll_genvar_for(&mut self, stmt: &ast::ForStmt) -> Option { + let init = stmt.init()?; + let init_assign = match &init { + ast::Stmt::AssignStmt(a) => a.assign()?, + _ => return None, + }; + let gv = Self::single_ident(&init_assign.lval()?)?; + if !self.genvar_names.contains(&gv) { + return None; + } + let start = self.eval_genvar_const(&init_assign.rval()?)?; + let cond = stmt.condition()?; + let incr = stmt.incr()?; + let incr_assign = match &incr { + ast::Stmt::AssignStmt(a) => a.assign()?, + _ => return None, + }; + let incr_rval = incr_assign.rval()?; + + let mut bodies = Vec::new(); + let mut val = start; + let mut guard = 0u64; + loop { + self.genvars.push((gv.clone(), val)); + match self.eval_genvar_cond(&cond) { + Some(true) => {} + Some(false) => { + self.genvars.pop(); + break; + } + None => { + self.genvars.pop(); + return None; + } + } + let body_id = self.collect_opt_stmt(stmt.for_body()); + bodies.push(body_id); + let next = self.eval_genvar_const(&incr_rval); + self.genvars.pop(); + match next { + Some(n) => val = n, + None => return None, + } + guard += 1; + if guard > 1_000_000 { + break; + } + } + Some(self.alloc_stmt_desugared(Stmt::Block { body: bodies })) + } + fn alloc_expr(&mut self, expr: Expr, ptr: AstPtr) -> ExprId { let id = self.make_expr(expr, Some(ptr.clone())); self.source_map.expr_map.insert(ptr, id); diff --git a/openvaf/hir_def/src/body/pretty.rs b/openvaf/hir_def/src/body/pretty.rs index a5aca0e0..9e153b99 100644 --- a/openvaf/hir_def/src/body/pretty.rs +++ b/openvaf/hir_def/src/body/pretty.rs @@ -183,6 +183,12 @@ impl Printer<'_> { } w!(self, "}}"); } + Expr::Index { base, index } => { + self.pretty_print_expr(base); + w!(self, "["); + self.pretty_print_expr(index); + w!(self, "]"); + } Expr::Literal(ref lit) => w!(self, "{:?}", lit), } } diff --git a/openvaf/hir_def/src/builtin.rs b/openvaf/hir_def/src/builtin.rs index 5729cfb5..10424a05 100644 --- a/openvaf/hir_def/src/builtin.rs +++ b/openvaf/hir_def/src/builtin.rs @@ -183,13 +183,11 @@ impl BuiltIn { | BuiltIn::zi_np | BuiltIn::zi_zd | BuiltIn::zi_zp - | BuiltIn::laplace_nd | BuiltIn::laplace_np | BuiltIn::laplace_zd | BuiltIn::laplace_zp | BuiltIn::last_crossing | BuiltIn::slew - | BuiltIn::transition | BuiltIn::fclose | BuiltIn::fopen | BuiltIn::fdisplay diff --git a/openvaf/hir_def/src/expr.rs b/openvaf/hir_def/src/expr.rs index 1fe73c5e..7ac41aeb 100644 --- a/openvaf/hir_def/src/expr.rs +++ b/openvaf/hir_def/src/expr.rs @@ -82,6 +82,11 @@ pub enum Expr { args: Vec, }, Array(Vec), + /// Array element / bus node access `base[index]`. + Index { + base: ExprId, + index: ExprId, + }, Literal(Literal), } @@ -100,6 +105,10 @@ impl Expr { f(then_val); f(else_val); } + Expr::Index { base, index } => { + f(base); + f(index); + } Expr::Call { args: ref exprs, .. } | Expr::Array(ref exprs) => { for e in exprs { f(*e) @@ -144,6 +153,9 @@ pub enum GlobalEvent { #[non_exhaustive] pub enum Event { Global { kind: GlobalEvent, phases: Vec }, + /// A monitored analog event such as `@(cross(...))` / `@(timer(...))`. Variables + /// assigned inside its body are given cross-timestep retention during lowering. + Cross, } #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/openvaf/hir_def/src/item_tree/lower.rs b/openvaf/hir_def/src/item_tree/lower.rs index 05e5e668..40c8efa7 100644 --- a/openvaf/hir_def/src/item_tree/lower.rs +++ b/openvaf/hir_def/src/item_tree/lower.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use arena::IdxRange; use basedb::{AstId, AstIdMap, FileId}; use syntax::ast::{self, ParamRef, PathSegmentKind}; -use syntax::name::{kw, AsIdent, AsName}; +use syntax::name::{kw, AsIdent, AsName, Name}; use syntax::{match_ast, AstNode, ConstExprValue, WalkEvent}; use typed_index_collections::TiVec; @@ -289,6 +289,11 @@ impl Ctx { self.lower_stmt(stmt, dst); } } + ast::ModuleItem::ProceduralBlock(block) => { + if let Some(stmt) = block.stmt() { + self.lower_stmt(stmt, dst); + } + } ast::ModuleItem::VarDecl(var) => { self.lower_var(var, dst); } @@ -300,6 +305,10 @@ impl Ctx { } ast::ModuleItem::BranchDecl(branch) => self.lower_branch(branch, dst), ast::ModuleItem::AliasParam(alias) => self.lower_alias_param(alias, dst), + // Genvars are compile-time loop variables; they carry no item-tree + // entity. The genvar `for` loop is unrolled during body lowering, so + // there is nothing to lower here. + ast::ModuleItem::GenvarDecl(_) => {} }; } } @@ -384,17 +393,97 @@ impl Ctx { } } + /// Collect bus base names declared with a vectored range anywhere in the module + /// body (`input [msb:lsb] x;` or `electrical [msb:lsb] x;`), mapping each name to + /// its constant `(msb, lsb)` bounds. Used to expand bare head port names. + fn collect_bus_ranges(&self, module: &ast::ModuleDecl) -> ahash::AHashMap { + let mut ranges = ahash::AHashMap::new(); + let mut add = |dim: Option, names: ast::AstChildren| { + if let Some(dim) = dim { + if let (Some(msb), Some(lsb)) = ( + dim.msb().and_then(|e| eval_const_int(&e, module)), + dim.lsb().and_then(|e| eval_const_int(&e, module)), + ) { + for name in names { + ranges.insert(name.as_name(), (msb, lsb)); + } + } + } + }; + for item in module.module_items() { + match item { + ast::ModuleItem::BodyPortDecl(bpd) => { + if let Some(decl) = bpd.port_decl() { + add(decl.dimension(), decl.names()); + } + } + ast::ModuleItem::NetDecl(decl) => add(decl.dimension(), decl.names()), + _ => {} + } + } + ranges + } + fn lower_module_ports( &mut self, ports: ast::ModulePorts, nodes: &mut TiVec, dst: &mut Vec, ) { + let module = ports.syntax().ancestors().find_map(ast::ModuleDecl::cast); + + // A port may be listed in the head as a bare name (`module m(in, out);`) and + // given a bus range only in the body (`input [0:3] in;`). Pre-scan the body's + // declarations so such a head name expands to the same `in[0]..in[3]` scalar + // nodes the body decl produces, letting the two reconcile by name. + let bus_ranges = module.as_ref().map(|m| self.collect_bus_ranges(m)).unwrap_or_default(); + for port in ports.ports() { let ast_id = self.source_ast_id_map.ast_id(&port); match port.kind() { ast::ModulePortKind::Name(name) => { - let name = name.as_name(); + let base = name.as_name(); + let indices: Vec> = match bus_ranges.get(&base) { + Some(&(msb, lsb)) => { + let (lo, hi) = if msb <= lsb { (msb, lsb) } else { (lsb, msb) }; + (lo..=hi).map(Some).collect() + } + None => vec![None], + }; + for idx in indices { + let name = match idx { + Some(idx) => Name::resolve(&format!("{}[{}]", base, idx)), + None => base.clone(), + }; + if nodes.iter().all(|node| node.name != name) { + let node = nodes.push_and_get_key(Node { + name, + is_port: true, + ast_id: ast_id.into(), + decls: Vec::new(), + }); + dst.push(node.into()) + } + } + } + // Vectored/bus port reference `inode[k]` in the module header. The + // index is a compile-time constant; the referenced element is one of + // the scalar nodes expanded from the bus net declaration (named + // `inode[k]`), which we mark as a port here. + ast::ModulePortKind::PortRef(port_ref) => { + let base = match port_ref.name() { + Some(name) => name.as_name(), + None => continue, + }; + let idx = port_ref + .expr() + .as_ref() + .zip(module.as_ref()) + .and_then(|(e, m)| eval_const_int(e, m)); + let name = match idx { + Some(idx) => Name::resolve(&format!("{}[{}]", base, idx)), + None => continue, + }; if nodes.iter().all(|node| node.name != name) { let node = nodes.push_and_get_key(Node { name, @@ -423,28 +512,51 @@ impl Ctx { let is_gnd = decl.net_type_token().map_or(false, |it| it.text() == kw::raw::ground); let ast_id = self.source_ast_id_map.ast_id(&decl); + + // Vectored/bus port declaration `input [msb:lsb] in;` expands into one scalar + // port/node per index, named `in[msb]`..`in[lsb]`, exactly as a bus net does. + let bus_range = decl.dimension().and_then(|dim| { + let module = decl.syntax().ancestors().find_map(ast::ModuleDecl::cast)?; + let msb = eval_const_int(&dim.msb()?, &module)?; + let lsb = eval_const_int(&dim.lsb()?, &module)?; + Some((msb, lsb)) + }); + for (name_idx, name) in decl.names().enumerate() { - let name = name.as_name(); - let id = self.tree.data.ports.push_and_get_key(Port { - name: name.clone(), - discipline: discipline.clone(), - is_input: is_input(&direction), - is_output: is_output(&direction), - ast_id, - name_idx, - is_gnd, - }); - - match nodes.iter_mut().find(|node| node.name == name) { - Some(node) => node.decls.push(id.into()), - None => { - let node = nodes.push_and_get_key(Node { - name, - is_port: true, - ast_id: ast_id.into(), - decls: vec![id.into()], - }); - dst.push(node.into()) + let base = name.as_name(); + let indices: Vec> = match bus_range { + Some((msb, lsb)) => { + let (lo, hi) = if msb <= lsb { (msb, lsb) } else { (lsb, msb) }; + (lo..=hi).map(Some).collect() + } + None => vec![None], + }; + for idx in indices { + let name = match idx { + Some(idx) => Name::resolve(&format!("{}[{}]", base, idx)), + None => base.clone(), + }; + let id = self.tree.data.ports.push_and_get_key(Port { + name: name.clone(), + discipline: discipline.clone(), + is_input: is_input(&direction), + is_output: is_output(&direction), + ast_id, + name_idx, + is_gnd, + }); + + match nodes.iter_mut().find(|node| node.name == name) { + Some(node) => node.decls.push(id.into()), + None => { + let node = nodes.push_and_get_key(Node { + name, + is_port: true, + ast_id: ast_id.into(), + decls: vec![id.into()], + }); + dst.push(node.into()) + } } } } @@ -460,26 +572,50 @@ impl Ctx { let ast_id = self.source_ast_id_map.ast_id(&decl); let is_gnd = decl.net_type_token().map_or(false, |it| it.text() == kw::raw::ground); + + // Vectored/bus net declaration `electrical [msb:lsb] inode;` expands into one + // scalar node per index, named `inode[msb]`..`inode[lsb]`. Indices are + // compile-time constants. All later access goes through those scalar nodes. + let bus_range = decl.dimension().and_then(|dim| { + let module = decl.syntax().ancestors().find_map(ast::ModuleDecl::cast)?; + let msb = eval_const_int(&dim.msb()?, &module)?; + let lsb = eval_const_int(&dim.lsb()?, &module)?; + Some((msb, lsb)) + }); + for (name_idx, name) in decl.names().enumerate() { - let name = name.as_name(); - let id = self.tree.data.nets.push_and_get_key(Net { - name: name.clone(), - discipline: discipline.clone(), - ast_id, - is_gnd, - name_idx, - }); - - match nodes.iter_mut().find(|node| node.name == name) { - Some(node) => node.decls.push(id.into()), - None => { - let node = nodes.push_and_get_key(Node { - name, - is_port: false, - ast_id: ast_id.into(), - decls: vec![id.into()], - }); - dst.push(node.into()); + let base = name.as_name(); + let indices: Vec> = match bus_range { + Some((msb, lsb)) => { + let (lo, hi) = if msb <= lsb { (msb, lsb) } else { (lsb, msb) }; + (lo..=hi).map(Some).collect() + } + None => vec![None], + }; + for idx in indices { + let name = match idx { + Some(idx) => Name::resolve(&format!("{}[{}]", base, idx)), + None => base.clone(), + }; + let id = self.tree.data.nets.push_and_get_key(Net { + name: name.clone(), + discipline: discipline.clone(), + ast_id, + is_gnd, + name_idx, + }); + + match nodes.iter_mut().find(|node| node.name == name) { + Some(node) => node.decls.push(id.into()), + None => { + let node = nodes.push_and_get_key(Node { + name, + is_port: false, + ast_id: ast_id.into(), + decls: vec![id.into()], + }); + dst.push(node.into()); + } } } } @@ -557,13 +693,29 @@ impl Ctx { } fn lower_var>>(&mut self, decl: ast::VarDecl, dst: &mut Vec) { - let ty = decl.ty().as_type(); + let base_ty = decl.ty().as_type(); + let module = decl.syntax().ancestors().find_map(ast::ModuleDecl::cast); for var in decl.vars() { if let Some(name) = var.name() { + // `real den[msb:lsb];` -> a fixed-size array. The bounds are + // compile-time integer constants (literals, arithmetic, or parameter + // references resolved against the enclosing module). + let ty = match (var.dimension(), module.as_ref()) { + (Some(dim), Some(module)) => { + let len = dim + .msb() + .and_then(|m| eval_const_int(&m, module)) + .zip(dim.lsb().and_then(|l| eval_const_int(&l, module))) + .map(|(msb, lsb)| (msb - lsb).unsigned_abs() as u32 + 1) + .unwrap_or(0); + Type::Array { ty: Box::new(base_ty.clone()), len } + } + _ => base_ty.clone(), + }; let var = Var { name: name.as_name(), ast_id: self.source_ast_id_map.ast_id(&var), - ty: ty.clone(), + ty, }; let id = self.tree.data.variables.push_and_get_key(var); dst.push(id.into()) @@ -611,3 +763,49 @@ impl Ctx { } } } + +/// Best-effort compile-time evaluation of an integer constant expression, resolving +/// parameter references against the enclosing module's parameter declarations. Used +/// to size array/bus dimensions like `real den[order:0]` / `electrical [0:n] x`. +fn eval_const_int(expr: &ast::Expr, module: &ast::ModuleDecl) -> Option { + use syntax::ast::{BinaryOp, LiteralKind, UnaryOp}; + match expr { + ast::Expr::Literal(lit) => match lit.kind() { + LiteralKind::IntNumber(i) => Some(i.value() as i64), + _ => None, + }, + ast::Expr::PrefixExpr(p) => { + let v = eval_const_int(&p.expr()?, module)?; + match p.op_kind()? { + UnaryOp::Neg => Some(-v), + UnaryOp::Identity => Some(v), + _ => None, + } + } + ast::Expr::ParenExpr(p) => eval_const_int(&p.expr()?, module), + ast::Expr::BinExpr(b) => { + let l = eval_const_int(&b.lhs()?, module)?; + let r = eval_const_int(&b.rhs()?, module)?; + match b.op_kind()? { + BinaryOp::Addition => Some(l.wrapping_add(r)), + BinaryOp::Subtraction => Some(l.wrapping_sub(r)), + BinaryOp::Multiplication => Some(l.wrapping_mul(r)), + BinaryOp::Division if r != 0 => Some(l / r), + _ => None, + } + } + ast::Expr::PathExpr(pe) => { + let ident = pe.path()?.as_raw_ident()?; + let name = ident.text(); + for pdecl in module.syntax().descendants().filter_map(ast::ParamDecl::cast) { + for para in pdecl.paras() { + if para.name().map_or(false, |n| n.text() == name) { + return eval_const_int(¶.default()?, module); + } + } + } + None + } + _ => None, + } +} diff --git a/openvaf/hir_def/src/lib.rs b/openvaf/hir_def/src/lib.rs index a65473c2..42e16a28 100644 --- a/openvaf/hir_def/src/lib.rs +++ b/openvaf/hir_def/src/lib.rs @@ -405,10 +405,21 @@ impl NatureAttrLoc { impl_intern!(NatureAttrId, NatureAttrLoc, intern_nature_attr, lookup_intern_nature_attr); +/// Which behavioural body of a module is being lowered. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ModuleBodyKind { + /// `analog` block (the DAE device behaviour). + Analog, + /// `analog initial` block. + AnalogInitial, + /// Standalone `initial`/`final` procedural blocks (imperative runner lane). + Procedural, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DefWithBodyId { ParamId(ParamId), - ModuleId { initial: bool, module: ModuleId }, + ModuleId { kind: ModuleBodyKind, module: ModuleId }, FunctionId(FunctionId), VarId(VarId), NatureAttrId(NatureAttrId), diff --git a/openvaf/hir_def/src/types.rs b/openvaf/hir_def/src/types.rs index 4c6fd533..4831d6f9 100644 --- a/openvaf/hir_def/src/types.rs +++ b/openvaf/hir_def/src/types.rs @@ -95,7 +95,7 @@ impl Type { pub fn base_type(&self) -> &Type { let mut curr = self; - while let Type::Array { ty, .. } = self { + while let Type::Array { ty, .. } = curr { curr = ty } curr diff --git a/openvaf/hir_def/tests/data_tests.rs b/openvaf/hir_def/tests/data_tests.rs index 1946e71d..26d71514 100644 --- a/openvaf/hir_def/tests/data_tests.rs +++ b/openvaf/hir_def/tests/data_tests.rs @@ -6,7 +6,7 @@ use basedb::{AbsPathBuf, BaseDB, BaseDatabase, FileId, Vfs, VfsEntry, VfsPath, V use expect_test::expect_file; use hir_def::db::{HirDefDB, HirDefDatabase, InternDatabase}; use hir_def::nameres::{DefMap, LocalScopeId, ScopeDefItem, ScopeOrigin}; -use hir_def::DefWithBodyId; +use hir_def::{DefWithBodyId, ModuleBodyKind}; use mini_harness::{harness, Result}; use parking_lot::RwLock; use stdx::{ignore_dev_tests, ignore_never, is_va_file, openvaf_test_data, project_root, Upcast}; @@ -108,7 +108,8 @@ fn body_test(file: &Path) -> Result { let mut actual = String::new(); for (_, scope) in &def_map[def_map.entry()].children { if let ScopeOrigin::Module(module) = def_map[*scope].origin { - let analog_block = DefWithBodyId::ModuleId { initial: false, module }; + let analog_block = + DefWithBodyId::ModuleId { kind: ModuleBodyKind::Analog, module }; actual.push_str(&db.body(analog_block).dump(&db)); for (_, scope) in &def_map[*scope].children { if let ScopeOrigin::Function(func) = def_map[*scope].origin { diff --git a/openvaf/hir_lower/src/body.rs b/openvaf/hir_lower/src/body.rs index 6a5b04e8..c386dc31 100644 --- a/openvaf/hir_lower/src/body.rs +++ b/openvaf/hir_lower/src/body.rs @@ -1,10 +1,10 @@ -use hir::{BodyRef, ExprId, Node}; +use hir::{AssignmentLhs, BodyRef, Event, ExprId, Node, Stmt, StmtId, Type, Variable}; use mir::builder::InstBuilder; use mir::{Block, Value}; use stdx::iter::zip; use crate::ctx::LoweringCtx; -use crate::ParamKind; +use crate::{CallBackKind, ParamKind, PlaceKind}; pub struct BodyLoweringCtx<'a, 'c1, 'c2> { pub ctx: &'a mut LoweringCtx<'c1, 'c2>, @@ -14,9 +14,113 @@ pub struct BodyLoweringCtx<'a, 'c1, 'c2> { impl<'c1, 'c2> BodyLoweringCtx<'_, 'c1, 'c2> { pub fn lower_entry_stmts(&mut self) { + // Pre-pass: find variables assigned inside `@(cross)` handlers. Each is backed + // by retained limit-state slots so it holds its value across timesteps (true + // latch/event semantics, e.g. a Schmitt trigger). The variable starts each + // evaluation at its previous accepted value. An array variable retains every + // element (one slot each), so e.g. an ADC's sampled bit vector survives. + let mut retained: Vec = Vec::new(); + for &stmnt in self.body.entry() { + self.collect_cross_assigned(stmnt, false, &mut retained); + } + let mut seen = ahash::AHashSet::new(); + retained.retain(|v| seen.insert(*v)); + + // (place, state, element type) for every retained slot, in store order. + let mut slots: Vec<(PlaceKind, crate::LimitState, Type)> = Vec::new(); + + if !self.ctx.no_equations { + for &var in &retained { + let (elem_ty, places) = self.retained_layout(var); + let mut states = Vec::with_capacity(places.len()); + for place in places { + let state = self.ctx.alloc_retained_state(); + let init = self.retained_load(state, &elem_ty); + self.ctx.def_place(place, init); + states.push(state); + slots.push((place, state, elem_ty.clone())); + } + self.ctx.retained_states.insert(var, states); + } + } + for &stmnt in self.body.entry() { self.lower_stmt(stmnt) } + + // Post-pass: store each retained slot's final value for the next timestep. + for (place, state, elem_ty) in slots { + let v_final = self.ctx.use_place(place); + self.retained_save(state, v_final, &elem_ty); + } + } + + /// The element type and the per-element places that back a retained variable: a + /// scalar has one `Var` place, an array one `VarElement` place per index. + fn retained_layout(&self, var: Variable) -> (Type, Vec) { + match var.ty(self.ctx.db) { + Type::Array { ty, len } => { + let places = (0..len).map(|i| PlaceKind::VarElement(var, i)).collect(); + (*ty, places) + } + ty => (ty, vec![PlaceKind::Var(var)]), + } + } + + /// Read a retained slot's previous-timestep value (stored as real) back into the + /// variable's element type. A real element needs no cast. + fn retained_load(&mut self, state: crate::LimitState, elem_ty: &Type) -> Value { + let prev = self.ctx.retained_prev(state); + match elem_ty { + Type::Real => prev, + _ => self.ctx.insert_cast(prev, &Type::Real, elem_ty), + } + } + + /// Store a retained slot's final value (cast to real) for the next timestep. + fn retained_save(&mut self, state: crate::LimitState, val: Value, elem_ty: &Type) { + let as_real = match elem_ty { + Type::Real => val, + _ => self.ctx.insert_cast(val, elem_ty, &Type::Real), + }; + self.ctx.store_retained(state, as_real); + } + + /// Recursively collect variables assigned inside `@(cross)` event handlers. + fn collect_cross_assigned(&self, stmnt: StmtId, in_cross: bool, dst: &mut Vec) { + let stmt = match self.body.get_stmt(stmnt) { + Some(stmt) => stmt, + None => return, + }; + match stmt { + Stmt::Assignment { lhs, .. } if in_cross => match lhs { + AssignmentLhs::Variable(var) => dst.push(var), + AssignmentLhs::ArrayElement { var, .. } => dst.push(var), + _ => {} + }, + Stmt::Assignment { .. } | Stmt::Expr(_) | Stmt::Contribute { .. } => {} + Stmt::EventControl { event, body } => { + let inner = in_cross || matches!(event, Event::Cross); + self.collect_cross_assigned(body, inner, dst); + } + Stmt::Block { body } => { + for &s in body { + self.collect_cross_assigned(s, in_cross, dst); + } + } + Stmt::If { then_branch, else_branch, .. } => { + self.collect_cross_assigned(then_branch, in_cross, dst); + self.collect_cross_assigned(else_branch, in_cross, dst); + } + Stmt::ForLoop { body, .. } | Stmt::WhileLoop { body, .. } => { + self.collect_cross_assigned(body, in_cross, dst); + } + Stmt::Case { case_arms, .. } => { + for arm in case_arms { + self.collect_cross_assigned(arm.body, in_cross, dst); + } + } + } } pub fn nodes_from_args( diff --git a/openvaf/hir_lower/src/callbacks.rs b/openvaf/hir_lower/src/callbacks.rs index 466e4149..82720f75 100644 --- a/openvaf/hir_lower/src/callbacks.rs +++ b/openvaf/hir_lower/src/callbacks.rs @@ -17,7 +17,7 @@ pub enum ParamInfoKind { MaxExclusive, } -#[derive(Debug, Clone, Hash, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] pub enum RetFlag { Abort, Finish, @@ -119,7 +119,10 @@ impl CallBackKind { name: format!("$store[{state:?}]"), params: 1, returns: 1, - has_sideeffects: false, + // Writing `next_state` is a side effect: when the stored value is not + // otherwise used (retained `@(cross)` state) the call must not be + // eliminated. The limit path still uses the return value as before. + has_sideeffects: true, }, CallBackKind::LimDiscontinuity => FunctionSignature { name: "$discontinuty[-1]".to_owned(), diff --git a/openvaf/hir_lower/src/ctx.rs b/openvaf/hir_lower/src/ctx.rs index 958c6132..bf51deaf 100644 --- a/openvaf/hir_lower/src/ctx.rs +++ b/openvaf/hir_lower/src/ctx.rs @@ -1,4 +1,4 @@ -use ahash::AHashSet; +use ahash::{AHashMap, AHashSet}; use hir::{CompilationDB, Node, Type, Variable}; use mir::builder::{InsertBuilder, InstBuilder}; use mir::{ @@ -25,8 +25,22 @@ pub struct LoweringCtx<'a, 'c> { /// but necessary to avoid accidental correlation/opimization. /// For example white_noise(x) - white_noise(x) is not zero. pub num_noise_sources: u32, + /// Variables assigned inside `@(cross)` handlers, each mapped to the limit-state + /// slot that stores its value across timesteps (latch / event retention). + /// Variables assigned inside `@(cross)` handlers that must retain their value + /// across timesteps. Each is backed by one retained limit-state slot per scalar, + /// or one per element for an array variable (in element order). + pub retained_states: AHashMap>, + /// True while lowering an `@(initial_step)` body: resets of retained variables + /// there are their initial value (read from the retained state), not a + /// per-evaluation reset. + pub in_initial_step: bool, } +/// Synthetic constant base used as the (non-parameter) `lim_state` key for retained +/// `@(cross)` slots, chosen to not collide with ordinary integer literals. +const RETAINED_STATE_KEY_BASE: i32 = 0x5E7A_0000; + impl<'a, 'c> LoweringCtx<'a, 'c> { pub fn new( db: &'a CompilationDB, @@ -43,6 +57,8 @@ impl<'a, 'c> LoweringCtx<'a, 'c> { inside_lim: false, intern, num_noise_sources: 0, + retained_states: AHashMap::default(), + in_initial_step: false, } } @@ -80,6 +96,9 @@ impl<'a, 'c> LoweringCtx<'a, 'c> { | PlaceKind::ParamMax(_) => return place, PlaceKind::Var(var) => self.use_param(ParamKind::HiddenState(var)), + // Array elements default to 0 (typically written before read in the + // initial block; a per-element HiddenState would be more precise). + PlaceKind::VarElement(..) => F_ZERO, PlaceKind::ImplicitResidual { .. } | PlaceKind::Contribute { .. } => F_ZERO, PlaceKind::CollapseImplicitEquation(_) => TRUE, PlaceKind::IsVoltageSrc(_) => FALSE, @@ -220,6 +239,30 @@ impl<'a, 'c> LoweringCtx<'a, 'c> { val } + /// Allocate a limit-state slot used purely to retain a value across timesteps + /// (the latch state of an `@(cross)` variable). It reuses the limit state-array + /// machinery (`prev_state`/`next_state`) but is keyed on a synthetic constant and + /// marked retained, so the limit-specific passes skip it. + pub fn alloc_retained_state(&mut self) -> LimitState { + let idx = self.intern.lim_state.len() as i32; + let key = self.iconst(RETAINED_STATE_KEY_BASE.wrapping_add(idx)); + let dst = self.intern.lim_state.raw.entry(key); + let state = LimitState::from(dst.index()); + dst.or_default().push((F_ZERO, false)); + self.intern.retained_lim_states.insert(state); + state + } + + /// Read the value retained from the previous accepted timestep. + pub fn retained_prev(&mut self, state: LimitState) -> Value { + self.use_param(ParamKind::PrevState(state)) + } + + /// Store `val` as the retained value for the next timestep. + pub fn store_retained(&mut self, state: LimitState, val: Value) { + self.call1(CallBackKind::StoreLimit(state), &[val]); + } + pub fn implicit_equation(&mut self, kind: ImplicitEquationKind) -> (ImplicitEquation, Value) { let equation = self.intern.implicit_equations.push_and_get_key(kind); let place = self.dec_place(PlaceKind::CollapseImplicitEquation(equation)); diff --git a/openvaf/hir_lower/src/expr.rs b/openvaf/hir_lower/src/expr.rs index 92f43fe0..2c27437e 100644 --- a/openvaf/hir_lower/src/expr.rs +++ b/openvaf/hir_lower/src/expr.rs @@ -49,6 +49,7 @@ impl BodyLoweringCtx<'_, '_, '_> { self.ctx.ins().phi(&[then_src, else_src]) } + Expr::Index { base, index } => self.lower_index(base, index), Expr::Call { args, fun } => match fun { ResolvedFun::User { func, limit } => self.lower_user_fun(func, limit, args), ResolvedFun::BuiltIn(builtin) => self.lower_builtin(expr, builtin, args), @@ -260,6 +261,43 @@ impl BodyLoweringCtx<'_, '_, '_> { self.ctx.use_place(PlaceKind::FunctionReturn(fun)) } + /// The number of elements of an array-typed variable (0 if not an array). + pub(crate) fn array_len(&self, var: hir::Variable) -> u32 { + match var.ty(self.ctx.db) { + Type::Array { len, .. } => len, + _ => 0, + } + } + + /// Lower an array element read `base[index]`. A fixed-size array is one MIR place + /// per element; a constant index reads it directly, a runtime index builds a + /// select chain over all elements. + fn lower_index(&mut self, base: ExprId, index: ExprId) -> Value { + let var = match self.body.get_expr(base) { + Expr::Read(Ref::Variable(var)) => var, + // only array-variable indexing is supported + _ => return F_ZERO, + }; + let len = self.array_len(var); + if len == 0 { + return F_ZERO; + } + if let Some(c) = self.body.as_literalint(&index) { + let c = (c.max(0) as u32).min(len - 1); + return self.ctx.use_place(PlaceKind::VarElement(var, c)); + } + let idx_val = self.lower_expr(index); + let mut res = self.ctx.use_place(PlaceKind::VarElement(var, 0)); + for i in 1..len { + let elem = self.ctx.use_place(PlaceKind::VarElement(var, i)); + let i_const = self.ctx.iconst(i as i32); + let cond = self.ctx.ins().ieq(idx_val, i_const); + let prev = res; + res = self.ctx.make_select(cond, |_s, branch| if branch { elem } else { prev }); + } + res + } + fn lower_builtin(&mut self, expr: ExprId, builtin: BuiltIn, args: &[ExprId]) -> Value { let signature = self.body.get_call_signature(expr); match builtin { @@ -530,6 +568,10 @@ impl BodyLoweringCtx<'_, '_, '_> { } } + // Without equation lowering (e.g. op-var contexts) a filter is a no-op. + BuiltIn::laplace_nd if self.ctx.no_equations => F_ZERO, + BuiltIn::laplace_nd => self.lower_laplace_nd(args), + BuiltIn::idt => { let kind = match_signature! { signature: @@ -716,9 +758,42 @@ impl BodyLoweringCtx<'_, '_, '_> { res }*/ - BuiltIn::slew | BuiltIn::transition | BuiltIn::limit | BuiltIn::absdelay => { - self.lower_expr(args[0]) + BuiltIn::transition => { + // `transition` accepts an integer or real first argument; the + // builtin signature types it as Real, so `lower_expr` already widens + // an integer/bool argument to Real for us (the operator returns Real). + let target = self.lower_expr(args[0]); + if self.ctx.no_equations { + // No DAE context (AC/noise setup, op-vars): pass the target through. + target + } else { + // Continuous (slew-limited) realization. The ideal `transition` is a + // piecewise-linear ramp from the old value to the new one over the + // rise/fall time; emitting it as an instantaneous jump produces a + // time discontinuity the transient integrator cannot step across + // ("timestep too small"). We realize it as a first-order lag whose + // time constant is the rise time when the target is increasing and + // the fall time when decreasing — a continuous output the solver + // integrates through, with the requested transition speed. + let eps = self.ctx.fconst(1e-12); + let rise = if args.len() > 2 { self.lower_expr(args[2]) } else { eps }; + let fall = if args.len() > 3 { self.lower_expr(args[3]) } else { rise }; + let (eq, x) = + self.ctx.implicit_equation(ImplicitEquationKind::Idt(IdtKind::Basic)); + // tau = (target >= x) ? rise : fall, floored to eps to avoid /0. + let rising = self.ctx.ins().fge(target, x); + let tau = self.ctx.make_select(rising, |_s, b| if b { rise } else { fall }); + let tau_ok = self.ctx.ins().fge(tau, eps); + let tau = self.ctx.make_select(tau_ok, |_s, b| if b { tau } else { eps }); + // dx/dt = (target - x)/tau -> react = x, resist = (x - target)/tau. + let diff = self.ctx.ins().fsub(x, target); + let resist = self.ctx.ins().fdiv(diff, tau); + self.ctx.def_resist_residual(resist, eq); + self.ctx.def_react_residual(x, eq); + x + } } + BuiltIn::slew | BuiltIn::limit | BuiltIn::absdelay => self.lower_expr(args[0]), _ => unreachable!(), } @@ -786,6 +861,106 @@ impl BodyLoweringCtx<'_, '_, '_> { val } + /// Read the coefficient values of an array-valued argument (an array variable's + /// elements or an array literal's entries), lowest index first. + fn array_coeffs(&mut self, arg: ExprId) -> Vec { + // Laplace coefficients feed real-valued state-space arithmetic, but an + // anonymous array literal of integer constants (the LRM's own examples use + // `'{-1,0,1}`) lowers to integer values. Widen each coefficient to real so + // the residual math stays well-typed. + match self.body.get_expr(arg) { + Expr::Read(Ref::Variable(var)) => { + let len = self.array_len(var); + let elem_ty = match var.ty(self.ctx.db) { + Type::Array { ty, .. } => *ty, + other => other, + }; + (0..len) + .map(|i| { + let v = self.ctx.use_place(PlaceKind::VarElement(var, i)); + self.coeff_to_real(v, &elem_ty) + }) + .collect() + } + Expr::Array(elems) => elems + .iter() + .map(|&e| { + let v = self.lower_expr(e); + let ty = self.body.expr_type(e); + self.coeff_to_real(v, &ty) + }) + .collect(), + _ => { + let v = self.lower_expr(arg); + let ty = self.body.expr_type(arg); + vec![self.coeff_to_real(v, &ty)] + } + } + } + + /// Widen an integer/bool coefficient value to real; reals pass through. + fn coeff_to_real(&mut self, v: Value, ty: &Type) -> Value { + match ty { + Type::Integer | Type::Bool => self.ctx.insert_cast(v, ty, &Type::Real), + _ => v, + } + } + + /// Lower `laplace_nd(input, num, den)` (coefficients in ascending powers of `s`) + /// as a controllable-canonical-form state space using `den.len()-1` integrator + /// states (implicit equations), reusing the existing DAE machinery. Coefficients + /// may be runtime values. + fn lower_laplace_nd(&mut self, args: &[ExprId]) -> Value { + let input = self.lower_expr(args[0]); + let num = self.array_coeffs(args[1]); + let den = self.array_coeffs(args[2]); + let n = den.len().saturating_sub(1); // filter order + if n == 0 { + if num.is_empty() || den.is_empty() { + return input; + } + let g = self.ctx.ins().fdiv(num[0], den[0]); + return self.ctx.ins().fmul(g, input); + } + + // States x_0..x_{n-1} with x_i = s^i w where D(s) w = input. + let mut states = Vec::with_capacity(n); + for _ in 0..n { + states.push(self.ctx.implicit_equation(ImplicitEquationKind::Idt(IdtKind::Basic))); + } + + // dx_i/dt = x_{i+1} for i in 0..n-1. + for i in 0..n - 1 { + let eq = states[i].0; + let next = states[i + 1].1; + let neg = self.ctx.ins().fneg(next); + self.ctx.def_resist_residual(neg, eq); + self.ctx.def_react_residual(states[i].1, eq); + } + + // dx_{n-1}/dt = (input - Σ_{i Type { self.body .needs_cast(expr) diff --git a/openvaf/hir_lower/src/lib.rs b/openvaf/hir_lower/src/lib.rs index 1f54f72e..d4917d03 100644 --- a/openvaf/hir_lower/src/lib.rs +++ b/openvaf/hir_lower/src/lib.rs @@ -45,6 +45,10 @@ pub enum ImplicitEquationKind { Ddt, NoiseSrc, Idt(IdtKind), + /// Auxiliary unknown introduced by an indirect branch assignment + /// (`V(out) : f(...) == 0`): the source value of the target branch, solved so the + /// constraint residual is zero. + IndirectBranch, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -154,6 +158,9 @@ impl IdtKind { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum PlaceKind { Var(Variable), + /// Element `idx` of a fixed-size array variable (compile-time array lowered to + /// one place per element). + VarElement(Variable, u32), FunctionReturn(hir::Function), FunctionArg(hir::FunctionArg), Contribute { @@ -178,6 +185,10 @@ impl PlaceKind { pub fn ty(&self, db: &CompilationDB) -> Type { match *self { PlaceKind::Var(var) => var.ty(db), + PlaceKind::VarElement(var, _) => match var.ty(db) { + Type::Array { ty, .. } => *ty, + ty => ty, + }, PlaceKind::FunctionReturn(fun) => fun.return_ty(db), PlaceKind::FunctionArg(arg) => arg.ty(db), @@ -202,6 +213,9 @@ impl From for PlaceKind { hir::AssignmentLhs::Variable(var) => PlaceKind::Var(var), hir::AssignmentLhs::FunctionReturn(fun) => PlaceKind::FunctionReturn(fun), hir::AssignmentLhs::FunctionArg(arg) => PlaceKind::FunctionArg(arg), + hir::AssignmentLhs::ArrayElement { .. } => { + unreachable!("array element assignment is lowered directly, not via PlaceKind") + } } } } @@ -231,6 +245,11 @@ pub struct HirInterner { pub tagged_reads: IndexMap>, pub implicit_equations: TiVec, pub lim_state: TiMap>, + /// Limit-state slots that actually back `@(cross)` retained variables (latch + /// state stored across timesteps). They reuse the limit state-array machinery + /// but carry no limit function, so the limit-specific derivative/value passes + /// must skip them. + pub retained_lim_states: ahash::AHashSet, } pub type LiveParams<'a> = FilterMap< @@ -248,6 +267,7 @@ impl Default for HirInterner { tagged_reads: IndexMap::with_hasher(BuildHasherDefault::::default()), implicit_equations: TiVec::default(), lim_state: TiMap::default(), + retained_lim_states: ahash::AHashSet::default(), } } } @@ -319,7 +339,12 @@ impl HirInterner { } } - for (param, vals) in self.lim_state.iter() { + for (state, (param, vals)) in self.lim_state.iter_enumerated() { + // Retained `@(cross)` slots are not limited node voltages; their key is a + // synthetic constant, so skip the limit derivative handling for them. + if self.retained_lim_states.contains(&state) { + continue; + } for &(val, neg) in vals { let param = func.dfg.value_def(*param).unwrap_param(); @@ -421,6 +446,9 @@ pub struct MirBuilder<'a> { tag_writes: bool, ctx: Option<&'a mut FunctionBuilderContext>, lower_equations: bool, + /// When set, lower the module's imperative `initial`/`final` procedural body + /// (the standalone runner lane) instead of the analog DAE bodies. + procedural: bool, } impl<'a> MirBuilder<'a> { @@ -439,9 +467,17 @@ impl<'a> MirBuilder<'a> { ctx: None, lower_equations: false, tag_writes: false, + procedural: false, } } + /// Lower the module's standalone `initial`/`final` procedural body instead of the + /// analog DAE bodies. Used by the VerilogA runner (`openvaf-r run`). + pub fn with_procedural(mut self) -> Self { + self.procedural = true; + self + } + pub fn tag_reads(&mut self, var: Variable) -> bool { self.tagged_reads.insert(var) } @@ -494,19 +530,29 @@ impl<'a> MirBuilder<'a> { let builder: FunctionBuilder<'_> = FunctionBuilder::new(&mut func, literals, ctx, self.tag_writes); let path = self.module.name(self.db); - let analog_initial_body = self.module.analog_initial_block(self.db); - let analog_body = self.module.analog_block(self.db); let mut ctx = LoweringCtx::new(self.db, builder, !self.lower_equations, &mut interner) .with_tagged_vars(self.tagged_reads); - let mut body_ctx = - BodyLoweringCtx { ctx: &mut ctx, body: analog_initial_body.borrow(), path: &path }; - - // lower analog initial blocks first - body_ctx.lower_entry_stmts(); - // ... and normal analog blocks afterwards - body_ctx.body = analog_body.borrow(); - body_ctx.lower_entry_stmts(); + + if self.procedural { + // Runner lane: lower only the imperative procedural body (all `initial` + // blocks in source order, then all `final` blocks). No analog/DAE bodies. + let procedural_body = self.module.procedural_block(self.db); + let mut body_ctx = + BodyLoweringCtx { ctx: &mut ctx, body: procedural_body.borrow(), path: &path }; + body_ctx.lower_entry_stmts(); + } else { + let analog_initial_body = self.module.analog_initial_block(self.db); + let analog_body = self.module.analog_block(self.db); + let mut body_ctx = + BodyLoweringCtx { ctx: &mut ctx, body: analog_initial_body.borrow(), path: &path }; + + // lower analog initial blocks first + body_ctx.lower_entry_stmts(); + // ... and normal analog blocks afterwards + body_ctx.body = analog_body.borrow(); + body_ctx.lower_entry_stmts(); + } for var in self.required_vars { ctx.dec_place(PlaceKind::Var(var)); diff --git a/openvaf/hir_lower/src/stmt.rs b/openvaf/hir_lower/src/stmt.rs index 6407f43c..0a496bce 100644 --- a/openvaf/hir_lower/src/stmt.rs +++ b/openvaf/hir_lower/src/stmt.rs @@ -1,9 +1,10 @@ -use hir::{BranchWrite, Case, CaseCond, ContributeKind, ExprId, Node, Stmt, StmtId, Type}; +use hir::{BranchWrite, Case, CaseCond, ContributeKind, Expr, ExprId, Node, Stmt, StmtId, Type}; use mir::builder::InstBuilder; -use mir::{Opcode, F_ZERO}; +use mir::{Opcode, Value, F_ZERO}; +use syntax::ast::BinaryOp; use crate::body::BodyLoweringCtx; -use crate::{CallBackKind, CurrentKind, ParamKind, PlaceKind}; +use crate::{CallBackKind, CurrentKind, ImplicitEquationKind, ParamKind, PlaceKind}; impl BodyLoweringCtx<'_, '_, '_> { pub(super) fn lower_stmt(&mut self, stmnt: StmtId) { @@ -17,17 +18,53 @@ impl BodyLoweringCtx<'_, '_, '_> { Stmt::Expr(expr) => { self.lower_expr(expr); } - Stmt::EventControl { body, .. } => { - // TODO handle porperly - self.lower_stmt(body); + Stmt::EventControl { event, body } => { + // Track `@(initial_step)` so resets of retained (`@cross`) variables + // inside it are treated as initial values (read from the retained + // state) rather than per-evaluation resets. Other events lower their + // body directly; their effect is gated by guards in the body. + if matches!( + event, + hir::Event::Global { kind: hir::GlobalEvent::InitialStep, .. } + ) { + let prev = self.ctx.in_initial_step; + self.ctx.in_initial_step = true; + self.lower_stmt(body); + self.ctx.in_initial_step = prev; + } else { + self.lower_stmt(body); + } } Stmt::Assignment { lhs, rhs } => { + // A retained variable's `@(initial_step)` reset is its initial value + // (already loaded from the retained state); skip it so it is not + // re-applied on every evaluation. + if self.ctx.in_initial_step { + let retained = match &lhs { + hir::AssignmentLhs::Variable(var) + | hir::AssignmentLhs::ArrayElement { var, .. } => { + self.ctx.retained_states.contains_key(var) + } + _ => false, + }; + if retained { + return; + } + } let val_ = self.lower_expr(rhs); - self.ctx.def_place(lhs.into(), val_); - } - Stmt::Contribute { kind, branch, rhs } => { - self.contribute(kind == ContributeKind::Potential, branch, rhs) + match lhs { + hir::AssignmentLhs::ArrayElement { var, index } => { + self.assign_array_element(var, index, val_) + } + _ => self.ctx.def_place(lhs.into(), val_), + } } + Stmt::Contribute { kind, branch, rhs } => match kind { + ContributeKind::Potential => self.contribute(true, branch, rhs), + ContributeKind::Flow => self.contribute(false, branch, rhs), + ContributeKind::IndirectPotential => self.indirect_contribute(true, branch, rhs), + ContributeKind::IndirectFlow => self.indirect_contribute(false, branch, rhs), + }, Stmt::Block { body } => { for stmt in body { @@ -119,6 +156,29 @@ impl BodyLoweringCtx<'_, '_, '_> { self.ctx.switch_to_block(end); } + /// Lower `arr[index] = val`. A constant index writes the element place directly; + /// a runtime index conditionally rewrites every element (`elem_i = (index==i) ? + /// val : elem_i`), keeping the array in pure SSA. + fn assign_array_element(&mut self, var: hir::Variable, index: ExprId, val: mir::Value) { + let len = self.array_len(var); + if len == 0 { + return; + } + if let Some(c) = self.body.as_literalint(&index) { + let c = (c.max(0) as u32).min(len - 1); + self.ctx.def_place(PlaceKind::VarElement(var, c), val); + return; + } + let idx_val = self.lower_expr(index); + for i in 0..len { + let current = self.ctx.use_place(PlaceKind::VarElement(var, i)); + let i_const = self.ctx.iconst(i as i32); + let cond = self.ctx.ins().ieq(idx_val, i_const); + let new = self.ctx.make_select(cond, |_s, branch| if branch { val } else { current }); + self.ctx.def_place(PlaceKind::VarElement(var, i), new); + } + } + fn lower_loop(&mut self, cond: ExprId, lower_body: impl FnOnce(&mut Self)) { let loop_cond_head = self.ctx.create_block(); let loop_body_head = self.ctx.create_block(); @@ -141,7 +201,22 @@ impl BodyLoweringCtx<'_, '_, '_> { self.ctx.switch_to_block(loop_end); } - fn contribute(&mut self, voltage_src: bool, mut write: BranchWrite, rhs: ExprId) { + fn contribute(&mut self, voltage_src: bool, write: BranchWrite, rhs: ExprId) { + let is_zero = self.body.get_expr(rhs).is_zero(); + self.contribute_with(voltage_src, write, is_zero, |s| s.lower_expr(rhs)); + } + + /// Shared body of a branch contribution. `lower_rhs` is invoked to produce the + /// contributed value at the exact point the old direct lowering did, so ordinary + /// contributions keep byte-identical MIR; indirect assignments supply an + /// already-computed implicit unknown instead. + fn contribute_with( + &mut self, + voltage_src: bool, + mut write: BranchWrite, + rhs_is_zero: bool, + lower_rhs: impl FnOnce(&mut Self) -> Value, + ) { let mut negate = false; if let BranchWrite::Unnamed { hi, lo } = &mut write { self.lower_contribute_unnamed_branch(&mut negate, hi, lo, voltage_src) @@ -149,8 +224,7 @@ impl BodyLoweringCtx<'_, '_, '_> { self.ctx.def_place(PlaceKind::IsVoltageSrc(write), voltage_src.into()); let (mut hi, mut lo) = write.nodes(self.ctx.db); - let is_zero = self.body.get_expr(rhs).is_zero(); - if voltage_src && is_zero { + if voltage_src && rhs_is_zero { if matches!(write, BranchWrite::Named(_)) { self.lower_contribute_unnamed_branch(&mut negate, &mut hi, &mut lo, voltage_src) } @@ -163,7 +237,7 @@ impl BodyLoweringCtx<'_, '_, '_> { F_ZERO, ); - let rhs = self.lower_expr(rhs); + let rhs = lower_rhs(self); if rhs == F_ZERO { return; } @@ -180,6 +254,49 @@ impl BodyLoweringCtx<'_, '_, '_> { self.ctx.def_place(place, new); } + /// Lower an indirect branch assignment `V(out) : f(...) == 0` (or the `I(out)` + /// flow form). The target branch becomes a source whose value is a fresh implicit + /// unknown `u`; an auxiliary equation pins `u` so the constraint residual is zero. + /// This reuses exactly the implicit-equation/DAE machinery behind `idt`. + fn indirect_contribute(&mut self, voltage_src: bool, write: BranchWrite, constraint: ExprId) { + let (eq, unknown) = self.ctx.implicit_equation(ImplicitEquationKind::IndirectBranch); + // Drive the branch as a source whose value is the implicit unknown. + self.contribute_with(voltage_src, write, false, |_| unknown); + // Residual of the auxiliary equation: `lhs - rhs` of the `==` constraint (== 0). + let residual = self.lower_constraint_residual(constraint); + self.ctx.def_resist_residual(residual, eq); + } + + /// Lower the constraint of an indirect branch assignment to its residual value. + /// The canonical form is `lhs == rhs`, whose residual is `lhs - rhs`; a bare + /// expression is treated leniently as `expr == 0`. + fn lower_constraint_residual(&mut self, constraint: ExprId) -> Value { + if let Expr::BinaryOp { lhs, rhs, op: BinaryOp::EqualityTest } = + self.body.get_expr(constraint) + { + let lhs = self.lower_real_operand(lhs); + let rhs = self.lower_real_operand(rhs); + self.ctx.ins().fsub(lhs, rhs) + } else { + self.lower_real_operand(constraint) + } + } + + /// Lower an expression and coerce the result to `Real` (integer/bool constraint + /// operands are widened so the residual is a floating-point quantity). + fn lower_real_operand(&mut self, expr: ExprId) -> Value { + let val = self.lower_expr(expr); + let ty = match self.body.needs_cast(expr) { + Some((_, dst)) => dst.clone(), + None => self.body.expr_type(expr), + }; + match ty { + Type::Real => val, + Type::Integer | Type::Bool => self.ctx.insert_cast(val, &ty, &Type::Real), + _ => val, + } + } + fn lower_contribute_unnamed_branch( &mut self, negate: &mut bool, diff --git a/openvaf/hir_ty/src/builtin.rs b/openvaf/hir_ty/src/builtin.rs index 1cf2cef3..51f5abef 100644 --- a/openvaf/hir_ty/src/builtin.rs +++ b/openvaf/hir_ty/src/builtin.rs @@ -263,11 +263,11 @@ bultins! { TRANSITION = const { - fn TRANSITION_NO_ARGS(Val(Integer)) -> Real; - fn TRANSITION_DELAY(Val(Integer),Val(Real)) -> Real; - fn TRANSITION_DELAY_RISET(Val(Integer),Val(Real)) -> Real; - fn TRANSITION_DELAY_RISET_FALLT(Val(Integer),Val(Real),Val(Real)) -> Real; - fn TRANSITION_DELAY_RISET_FALLT_TOL(Val(Integer),Val(Real),Val(Real), Val(Real)) -> Real; + fn TRANSITION_NO_ARGS(Val(Real)) -> Real; + fn TRANSITION_DELAY(Val(Real),Val(Real)) -> Real; + fn TRANSITION_DELAY_RISET(Val(Real),Val(Real)) -> Real; + fn TRANSITION_DELAY_RISET_FALLT(Val(Real),Val(Real),Val(Real)) -> Real; + fn TRANSITION_DELAY_RISET_FALLT_TOL(Val(Real),Val(Real),Val(Real), Val(Real)) -> Real; } diff --git a/openvaf/hir_ty/src/diagnostics.rs b/openvaf/hir_ty/src/diagnostics.rs index 29f29590..4a7ec740 100644 --- a/openvaf/hir_ty/src/diagnostics.rs +++ b/openvaf/hir_ty/src/diagnostics.rs @@ -86,6 +86,11 @@ impl Diagnostic for InferenceDiagnosticWrapped<'_> { AssignOp::Assign => res .with_message("invalid destination for assignment") .with_notes(vec!["help: expected a variable".to_owned()]), + AssignOp::Indirect => res + .with_message("invalid destination for indirect branch assignment") + .with_notes(vec![ + "help: expected nature access such as V(foo) or I(foo)".to_owned() + ]), }; match maybe_different_operand { @@ -97,7 +102,7 @@ impl Diagnostic for InferenceDiagnosticWrapped<'_> { "help: found a variable\nperhaps you meant to assign (=) a value" .to_owned(), ]), - None => res, + Some(ast::AssignOp::Indirect) | None => res, } } InferenceDiagnostic::PathResolveError { ref err, expr } => { diff --git a/openvaf/hir_ty/src/inference.rs b/openvaf/hir_ty/src/inference.rs index 5c134b31..a26a2ef9 100755 --- a/openvaf/hir_ty/src/inference.rs +++ b/openvaf/hir_ty/src/inference.rs @@ -42,6 +42,8 @@ pub enum ResolvedFun { #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum AssignDst { Var(VarId), + /// `arr[index] = …` — assignment to an array element. + VarElement { var: VarId, index: ExprId }, FunVar { fun: FunctionId, arg: Option }, Flow(BranchWrite), Potential(BranchWrite), @@ -92,7 +94,12 @@ impl InferenceResult { .infere_expr(body.entry_stmts[0], db.param_exprs(param).default) .and_then(|ty| ty.to_value()), }, - DefWithBodyId::VarId(var) => Some(db.var_data(var).ty.clone()), + DefWithBodyId::VarId(var) => Some(match db.var_data(var).ty.clone() { + // An array variable's desugared default is a scalar placeholder; check + // it against the element type rather than the array type. + Type::Array { ty, .. } => *ty, + ty => ty, + }), _ => None, }; @@ -124,7 +131,15 @@ impl Ctx<'_> { } Stmt::Assignment { dst, val, assignment_kind } => { let dst_ty = self.infere_assignment_dst(stmt, dst, assignment_kind); - self.infere_assignment(stmt, val, dst_ty); + if assignment_kind == ast::AssignOp::Indirect { + // `V(out) : f(...) == 0` — the rhs is the constraint equation, not a + // value to assign to the branch. Infer it on its own terms (an + // equality test producing bool, with its operands coerced as usual); + // MIR lowering turns it into an implicit-equation residual. + self.infere_expr(stmt, val); + } else { + self.infere_assignment(stmt, val, dst_ty); + } } Stmt::ForLoop { cond, .. } | Stmt::If { cond, .. } | Stmt::WhileLoop { cond, .. } => { self.infere_cond(stmt, cond) @@ -196,6 +211,25 @@ impl Ctx<'_> { ) -> Option { let e = self.infere_expr(stmt, expr); + // Array element assignment `den[index] = …`. The base must be an array variable. + if let Expr::Index { base, index } = self.body.exprs[expr] { + if let Ty::Var(Type::Array { ty, .. }, var) = self.result.expr_types[base].clone() { + let elem = *ty; + if matches!(assignment_kind, ast::AssignOp::Contribute | ast::AssignOp::Indirect) { + self.result.diagnostics.push(InferenceDiagnostic::InvalidAssignDst { + e: expr, + maybe_different_operand: Some(ast::AssignOp::Assign), + assignment_kind, + }); + } else { + self.result + .assignment_destination + .insert(stmt, AssignDst::VarElement { var, index }); + } + return Some(elem); + } + } + let (dst, ty) = match e? { Ty::Var(ty, var) => (AssignDst::Var(var), ty), Ty::FunctionVar { fun, ty, arg } => (AssignDst::FunVar { fun, arg }, ty), @@ -262,6 +296,15 @@ impl Ctx<'_> { assignment_kind, }); } + // Indirect branch assignment (`V(out) : …`) requires a branch destination, + // exactly like a contribution. + (AssignDst::Var(_) | AssignDst::FunVar { .. }, ast::AssignOp::Indirect) => { + self.result.diagnostics.push(InferenceDiagnostic::InvalidAssignDst { + e: expr, + maybe_different_operand: Some(ast::AssignOp::Assign), + assignment_kind, + }); + } _ => { self.result.assignment_destination.insert(stmt, dst); } @@ -391,6 +434,16 @@ impl Ctx<'_> { } Expr::Array(ref args) if args.is_empty() => Ty::Val(Type::EmptyArray), Expr::Array(ref args) => self.infere_array(stmt, args)?, + Expr::Index { base, index } => { + // The index is an integer value. + self.infere_expr(stmt, index); + // The result is the element type of the indexed array. + let base_ty = self.infere_expr(stmt, base)?; + match base_ty.to_value() { + Some(Type::Array { ty, .. }) => Ty::Val(*ty), + _ => Ty::Val(Type::Err), + } + } Expr::Literal(Literal::Float(_)) => Ty::Literal(Type::Real), Expr::Literal(Literal::Int(_)) => Ty::Literal(Type::Integer), // +/- inf can only appear in param bounds. @@ -967,7 +1020,8 @@ impl Ctx<'_> { } } - Some(Ty::Val(ty)) + // An array literal `{e0, e1, ...}` has an array type (element type `ty`). + Some(Ty::Val(Type::Array { ty: Box::new(ty), len: args.len() as u32 })) } fn infere_bin_op( diff --git a/openvaf/hir_ty/src/types.rs b/openvaf/hir_ty/src/types.rs index bd267f60..cb803403 100644 --- a/openvaf/hir_ty/src/types.rs +++ b/openvaf/hir_ty/src/types.rs @@ -203,7 +203,8 @@ impl Ty { // TODO merge these match arms when there are box/deref patterns (not any time soon) ( - Ty::Val(Type::Array { ty: ref ty1, .. }), + Ty::Val(Type::Array { ty: ref ty1, .. }) + | Ty::Var(Type::Array { ty: ref ty1, .. }, _), TyRequirement::ArrayAnyLength { ty: ty2 }, ) => equiv.compare_ty(ty1, ty2), diff --git a/openvaf/hir_ty/src/validation.rs b/openvaf/hir_ty/src/validation.rs index 37410907..c2ff481a 100644 --- a/openvaf/hir_ty/src/validation.rs +++ b/openvaf/hir_ty/src/validation.rs @@ -392,7 +392,7 @@ impl Diagnostic for BodyValidationDiagnosticWrapped<'_> { }]); res = res.with_notes(vec![ - "This function is part of the Verilog-A standard but currently not implemented by OpenVAF\nIf this function is important to your application, create an issue:\nhttps://github.com/pascalkuthe/openvaf/issues/new".to_owned(), + "This function is part of the Verilog-A standard but currently not implemented by OpenVAF\nIf this function is important to your application, create an issue:\nhttps://github.com/arpadbuermen/OpenVAF/issues/new".to_owned(), ]); res diff --git a/openvaf/hir_ty/src/validation/body.rs b/openvaf/hir_ty/src/validation/body.rs index 25517352..5ed80c18 100644 --- a/openvaf/hir_ty/src/validation/body.rs +++ b/openvaf/hir_ty/src/validation/body.rs @@ -4,7 +4,7 @@ use ahash::{HashMap, HashSet}; use hir_def::body::Body; use hir_def::{ BranchId, BuiltIn, DefWithBodyId, DisciplineId, Expr, ExprId, FunctionArgLoc, Literal, Lookup, - NatureId, NodeId, ParamId, Path, Stmt, StmtId, VarId, + ModuleBodyKind, NatureId, NodeId, ParamId, Path, Stmt, StmtId, VarId, }; use stdx::impl_display; use syntax::ast::AssignOp; @@ -104,8 +104,13 @@ impl BodyValidationDiagnostic { let infere = db.inference_result(def); let ctx = match def { - DefWithBodyId::ModuleId { initial: false, .. } => BodyCtx::AnalogBlock, - DefWithBodyId::ModuleId { initial: true, .. } => BodyCtx::AnalogInitialBlock, + DefWithBodyId::ModuleId { kind: ModuleBodyKind::Analog, .. } => BodyCtx::AnalogBlock, + DefWithBodyId::ModuleId { kind: ModuleBodyKind::AnalogInitial, .. } => { + BodyCtx::AnalogInitialBlock + } + DefWithBodyId::ModuleId { kind: ModuleBodyKind::Procedural, .. } => { + BodyCtx::ProceduralBlock + } DefWithBodyId::FunctionId(_) => BodyCtx::Function, _ => BodyCtx::Const, }; @@ -144,6 +149,7 @@ impl BodyValidationDiagnostic { pub enum BodyCtx { AnalogBlock, AnalogInitialBlock, + ProceduralBlock, Conditional, EventControl, Function, @@ -177,6 +183,7 @@ impl_display! { match BodyCtx{ BodyCtx::AnalogBlock => "analog block"; BodyCtx::AnalogInitialBlock => "analog initial block"; + BodyCtx::ProceduralBlock => "procedural block"; BodyCtx::Conditional => "conditions"; BodyCtx::EventControl => "events"; BodyCtx::Function => "analog functions"; @@ -203,7 +210,9 @@ impl BodyValidator<'_> { Stmt::Assignment { dst, val, assignment_kind } => { self.validate_expr(val, stmt); - if assignment_kind == AssignOp::Contribute && !self.ctx.allow_contribute() { + if matches!(assignment_kind, AssignOp::Contribute | AssignOp::Indirect) + && !self.ctx.allow_contribute() + { self.diagnostics .push(BodyValidationDiagnostic::IllegalContribute { stmt, ctx: self.ctx }) } @@ -727,8 +736,9 @@ impl ExprValidator<'_, '_> { } ( - BuiltIn::laplace_nd - | BuiltIn::laplace_np + // laplace_nd accepts runtime-computed coefficient arrays (realized as + // a state-space filter), so its coefficient args are not const-checked. + BuiltIn::laplace_np | BuiltIn::laplace_zp | BuiltIn::laplace_zd | BuiltIn::zi_nd diff --git a/openvaf/mir_interpret/src/lib.rs b/openvaf/mir_interpret/src/lib.rs index c41b13e3..ddb9f47f 100644 --- a/openvaf/mir_interpret/src/lib.rs +++ b/openvaf/mir_interpret/src/lib.rs @@ -13,6 +13,9 @@ pub struct InterpreterState { vals: TiVec, prev_bb: Block, next_inst: Option, + /// Set by a callback (e.g. `$finish`/`$stop`/`$fatal` in the runner) to request + /// early termination of `run()` with the given process exit code. + exit: Option, } impl InterpreterState { @@ -23,6 +26,19 @@ impl InterpreterState { pub fn read>(&self, val: Value) -> T { self.vals[val].into() } + + /// Request early termination of the interpreter with `code`. The first request + /// wins (mirrors `$finish` semantics — later tasks don't override it). + pub fn request_exit(&mut self, code: i32) { + if self.exit.is_none() { + self.exit = Some(code); + } + } + + /// The requested exit code, if any. + pub fn exit_code(&self) -> Option { + self.exit + } } pub type Func<'a> = fn(&mut InterpreterState, &[Value], &[Value], *mut c_void); @@ -56,15 +72,23 @@ impl<'a> Interpreter<'a> { let entry = func.layout.entry_block().expect("Function without entry block can not be interpreted"); - let state = - InterpreterState { vals, prev_bb: entry, next_inst: func.layout.first_inst(entry) }; + let state = InterpreterState { + vals, + prev_bb: entry, + next_inst: func.layout.first_inst(entry), + exit: None, + }; Interpreter { state, calls, func } } pub fn run(&mut self) { while let Some(inst) = self.state.next_inst { - self.eval(inst) + self.eval(inst); + // A callback (e.g. `$finish`/`$fatal`) may request early termination. + if self.state.exit.is_some() { + break; + } } } diff --git a/openvaf/openvaf-driver/src/cli_def.rs b/openvaf/openvaf-driver/src/cli_def.rs index 1aeb7738..d8215f04 100644 --- a/openvaf/openvaf-driver/src/cli_def.rs +++ b/openvaf/openvaf-driver/src/cli_def.rs @@ -38,6 +38,7 @@ pub fn main_command() -> Command { codegen_opts(), interface(), expand(), + run_mode(), dump_json(), input(), ]) @@ -64,6 +65,7 @@ pub const CACHE_DIR: &str = "cache-dir"; pub const OPT_LVL: &str = "opt_lvl"; pub const DEFINE: &str = "define"; pub const PRINT_EXPANSION: &str = "print-expansion"; +pub const RUN: &str = "run"; pub const DUMP_JSON: &str = "dump-json"; pub const ALLOW: &str = "allow"; pub const WARN: &str = "warn"; @@ -289,6 +291,16 @@ fn opt_lvl() -> Arg { .default_value("3").required(false) } +fn run_mode() -> Arg { + flag(RUN, "run") + .help("Run the module's imperative initial/final procedural blocks.") + .long_help( + "Lower the module's standalone `initial`/`final` procedural blocks to MIR and +interpret them, executing system tasks such as $display/$strobe/$finish. +No shared library is produced and no circuit is simulated; this is the +standalone VerilogA runner lane.", + ) +} fn expand() -> Arg { flag(PRINT_EXPANSION, "print-expansion") .help("Abort after preprocessing and print expanded sourcecode.") diff --git a/openvaf/openvaf-driver/src/crash_report.rs b/openvaf/openvaf-driver/src/crash_report.rs index 5dc56b1c..0b42a6a2 100644 --- a/openvaf/openvaf-driver/src/crash_report.rs +++ b/openvaf/openvaf-driver/src/crash_report.rs @@ -78,8 +78,8 @@ pub fn print_msg>(file_path: Option

) -> io::Result<()> { writeln!( stderr, "\nA log file has been generated at \"{}\". -To help us fix the problem, please open an issue at https://github.com/pascalkuthe/OpenVAF/ -or send an email to pascal.kuthe@semimod.de and attach the log file. +To help us fix the problem, please open an issue at https://github.com/arpadbuermen/OpenVAF/ +and attach the log file. If possible please also attach the source file that OpenVAF was compiling.", match file_path { Some(fp) => format!("{}", fp.as_ref().display()), diff --git a/openvaf/openvaf-driver/src/main.rs b/openvaf/openvaf-driver/src/main.rs index 5c6fbc98..88cb7ff7 100644 --- a/openvaf/openvaf-driver/src/main.rs +++ b/openvaf/openvaf-driver/src/main.rs @@ -7,10 +7,10 @@ use camino::Utf8PathBuf; use clap::ArgMatches; use cli_def::{main_command, INPUT}; use mimalloc::MiMalloc; -use openvaf::{compile, expand, CompilationDestination, CompilationTermination, Opts}; +use openvaf::{compile, expand, run, CompilationDestination, CompilationTermination, Opts}; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; -use crate::cli_def::{DUMP_JSON, PRINT_EXPANSION}; +use crate::cli_def::{DUMP_JSON, PRINT_EXPANSION, RUN}; use crate::cli_process::matches_to_opts; mod cli_def; @@ -61,8 +61,14 @@ pub const DATA_ERROR: i32 = 65; fn wrapped_main(matches: ArgMatches) -> Result { let print_expansion = matches.get_flag(PRINT_EXPANSION); let dump_json_ = matches.get_flag(DUMP_JSON); + let run_mode = matches.get_flag(RUN); let opts = matches_to_opts(matches)?; *ARGS.lock().unwrap() = Some(opts.clone()); + if run_mode { + // Standalone VerilogA runner: interpret the procedural blocks and propagate + // the program's own exit code (e.g. from $finish/$fatal). + return run(&opts); + } if print_expansion { let res = match expand(&opts)? { CompilationTermination::Compiled { .. } => 0, diff --git a/openvaf/openvaf/Cargo.toml b/openvaf/openvaf/Cargo.toml index 7cf64846..2bf90c89 100644 --- a/openvaf/openvaf/Cargo.toml +++ b/openvaf/openvaf/Cargo.toml @@ -28,6 +28,9 @@ llvm-sys-191 = { package = "llvm-sys", version = "191.0.0", optional = true } llvm-sys-201 = { package = "llvm-sys", version = "201.0.1", optional = true } llvm-sys-211 = { package = "llvm-sys", version = "211.0.0", optional = true } mir_llvm = { version = "0.0.0", path = "../mir_llvm", default-features = false } +mir = { version = "0.0.0", path = "../mir" } +hir_lower = { version = "0.0.0", path = "../hir_lower" } +mir_interpret = { version = "0.0.0", path = "../mir_interpret" } hir = { version = "0.0.0", path = "../hir" } target = { version = "0.0.0", path = "../target" } linker = { version = "0.0.0", path = "../linker" } @@ -40,6 +43,8 @@ md5 = "0.8" anyhow = "1" termcolor = "1.2" camino = "1.1.4" +lasso = { version = "0.7", features = ["ahash"] } +typed-index-collections = "3.1" [dev-dependencies] libloading = "0.9" diff --git a/openvaf/openvaf/src/lib.rs b/openvaf/openvaf/src/lib.rs index a2bb15f2..fdc69269 100644 --- a/openvaf/openvaf/src/lib.rs +++ b/openvaf/openvaf/src/lib.rs @@ -28,6 +28,9 @@ pub use target::spec::{get_target_names, Target}; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; mod cache; +mod run; + +pub use run::run; #[derive(Debug, Clone)] pub enum CompilationDestination { diff --git a/openvaf/openvaf/src/run.rs b/openvaf/openvaf/src/run.rs new file mode 100644 index 00000000..5b445226 --- /dev/null +++ b/openvaf/openvaf/src/run.rs @@ -0,0 +1,241 @@ +//! Standalone VerilogA runner (`openvaf-r run `). +//! +//! This is a separate execution lane from the OSDI/DAE compiler: instead of +//! emitting a shared library for a circuit simulator, it lowers a module's +//! imperative `initial`/`final` procedural blocks to MIR and *interprets* them +//! with `mir_interpret`, providing host implementations of the output/control +//! system tasks (`$display`, `$strobe`, `$finish`, `$fatal`, ...). +//! +//! No LLVM, no linking, no simulator is involved. + +use std::ffi::c_void; +use std::io::Write; + +use anyhow::{bail, Context, Result}; +use basedb::diagnostics::ConsoleSink; +use hir::CompilationDB; +use hir_lower::fmt::{DisplayKind, FmtArg, FmtArgKind}; +use hir_lower::{CallBackKind, HirInterner, MirBuilder, PlaceKind, RetFlag}; +use lasso::{Rodeo, Spur}; +use mir::{FuncRef, Value}; +use mir_interpret::{Data, Func, Interpreter, InterpreterState}; +use paths::AbsPathBuf; +use sim_back::collect_modules; +use typed_index_collections::{TiSlice, TiVec}; + +use crate::Opts; + +/// Host context for a single interpreter callback, stored behind the `*mut c_void` +/// the interpreter hands back on every call. +struct CbCtx { + kind: CbCtxKind, + /// Borrowed for the whole interpreter run (the `Rodeo` outlives the run). + literals: *const Rodeo, +} + +enum CbCtxKind { + Print { kind: DisplayKind, arg_tys: Box<[FmtArg]> }, + SetRetFlag(RetFlag), + /// A callback the runner does not implement (e.g. simulator-only tasks). It is + /// ignored at runtime after a one-line warning. + Unsupported(String), +} + +/// Run a module's behaviour and return the process exit code (0 unless +/// `$finish`/`$fatal`/`$stop` requested otherwise). +/// +/// Two bodies are executed, in order: the `analog` behaviour (so the idiomatic +/// `analog begin @(initial_step) $strobe(...) end` form prints — `@(initial_step)` +/// is currently lowered unconditionally), then any standalone `initial`/`final` +/// procedural blocks. A `$finish`/`$fatal` in the first stops before the second. +pub fn run(opts: &Opts) -> Result { + let input = + opts.input.canonicalize().with_context(|| format!("failed to resolve {}", opts.input))?; + let input = AbsPathBuf::assert(input); + let db = CompilationDB::new_fs(input, &opts.include, &opts.defines, &opts.lints)?; + + let modules = match collect_modules(&db, false, &mut ConsoleSink::new(&db)) { + Some(modules) => modules, + // Front-end emitted fatal diagnostics already. + None => return Ok(1), + }; + let module = match modules.first() { + Some(module) => module, + None => bail!("no module found to run in `{}`", opts.input), + }; + + let mut literals = Rodeo::new(); + let is_output = |_: PlaceKind| false; + + // Lower both behavioural bodies up front (both extend `literals`). + let (analog_func, analog_intern) = + MirBuilder::new(&db, module.module, &is_output, &mut std::iter::empty()) + .build(&mut literals); + let (proc_func, proc_intern) = + MirBuilder::new(&db, module.module, &is_output, &mut std::iter::empty()) + .with_procedural() + .build(&mut literals); + + // analog behaviour first, then procedural blocks; an early-exit request from the + // first body skips the second. + if let Some(code) = interpret_body(&analog_func, &analog_intern, &literals) { + return Ok(code); + } + if let Some(code) = interpret_body(&proc_func, &proc_intern, &literals) { + return Ok(code); + } + Ok(0) +} + +/// Interpret one lowered MIR body, wiring its callbacks to host implementations. +/// Returns `Some(code)` if the body requested early termination (`$finish`/`$fatal`), +/// `None` otherwise. +fn interpret_body( + func: &mir::Function, + intern: &hir_lower::HirInterner, + literals: &Rodeo, +) -> Option { + // Build the interpreter callback table: one host context per MIR callback. + let mut ctxs: Vec> = Vec::with_capacity(intern.callbacks.len()); + let mut calls: TiVec = + TiVec::with_capacity(intern.callbacks.len()); + for (_func_ref, kind) in intern.callbacks.iter_enumerated() { + let cb_kind = match kind { + CallBackKind::Print { kind, arg_tys } => { + CbCtxKind::Print { kind: *kind, arg_tys: arg_tys.clone() } + } + CallBackKind::SetRetFlag(flag) => CbCtxKind::SetRetFlag(*flag), + other => CbCtxKind::Unsupported(format!("{other:?}")), + }; + let mut boxed = Box::new(CbCtx { kind: cb_kind, literals }); + let ptr: *mut c_void = (&mut *boxed as *mut CbCtx).cast(); + ctxs.push(boxed); + calls.push((host_callback as Func, ptr)); + } + + // Provide an entry value for every MIR parameter. The runner has no circuit, so + // all inputs (node voltages, temperature, module-variable entry state, ...) default + // to 0. (Module `parameter` defaults are not yet evaluated here — a v1 limitation.) + let zero = Data::from(0.0f64); + let args: TiVec = std::iter::repeat(zero).take(intern.params.len()).collect(); + let mut interpreter = Interpreter::new(func, calls.as_slice(), args.as_slice()); + interpreter.run(); + + // `ctxs` (and the raw pointers in `calls`) stay alive until here. + let code = interpreter.state.exit_code(); + drop(ctxs); + code +} + +/// The single `fn` dispatched for every interpreter callback; behaviour is selected +/// by the [`CbCtx`] behind `data`. +fn host_callback(state: &mut InterpreterState, args: &[Value], _rets: &[Value], data: *mut c_void) { + // SAFETY: `data` points at the `CbCtx` we stored for this `FuncRef`, which lives + // for the whole `run()`; `literals` likewise outlives the interpreter. + let ctx = unsafe { &*(data as *const CbCtx) }; + let literals = unsafe { &*ctx.literals }; + + match &ctx.kind { + CbCtxKind::Print { kind, arg_tys } => { + let fmt = literals.resolve(&state.read::(args[0])); + let rendered = render_format(fmt, &args[1..], arg_tys, state, literals); + match kind { + // Diagnostic-flavoured tasks go to stderr, the rest to stdout. + DisplayKind::Error | DisplayKind::Fatal | DisplayKind::Warn => { + let _ = write!(std::io::stderr(), "{rendered}"); + } + _ => { + let _ = write!(std::io::stdout(), "{rendered}"); + } + } + } + CbCtxKind::SetRetFlag(flag) => { + // `$finish`/`$stop` are normal termination; `$fatal` is an error. + let code = match flag { + RetFlag::Abort => 1, + _ => 0, + }; + state.request_exit(code); + } + CbCtxKind::Unsupported(name) => { + let _ = writeln!( + std::io::stderr(), + "openvaf run: system task/function `{name}` is not supported by the runner; ignored" + ); + } + } +} + +/// Minimal C-`printf`-style renderer. `fmt` has already been normalised to C +/// conversions by `hir_lower::fmt::ins_display`, and `arg_tys`/`args` line up in +/// order with the `%` conversions in `fmt`. +fn render_format( + fmt: &str, + args: &[Value], + arg_tys: &[FmtArg], + state: &InterpreterState, + literals: &Rodeo, +) -> String { + let mut out = String::with_capacity(fmt.len()); + let mut chars = fmt.chars().peekable(); + let mut ai = 0usize; + + while let Some(c) = chars.next() { + if c != '%' { + out.push(c); + continue; + } + // Consume the conversion specifier: flags/width/precision/length up to the + // terminating conversion character. + if chars.peek() == Some(&'%') { + chars.next(); + out.push('%'); + continue; + } + let mut conv = '\0'; + for cc in chars.by_ref() { + if cc.is_ascii_alphabetic() { + conv = cc; + break; + } + } + // A dangling conversion with no matching argument (e.g. the engineering `%c` + // suffix produced for `%r`): emit nothing. + if ai >= arg_tys.len() { + continue; + } + let arg = args[ai]; + let ty = &arg_tys[ai]; + ai += 1; + out.push_str(&render_arg(conv, ty, arg, state, literals)); + } + out +} + +fn render_arg( + conv: char, + ty: &FmtArg, + arg: Value, + state: &InterpreterState, + literals: &Rodeo, +) -> String { + if ty.kind == FmtArgKind::Binary { + return format!("{:b}", state.read::(arg)); + } + match conv { + 'd' | 'i' => format!("{}", state.read::(arg)), + 'x' => format!("{:x}", state.read::(arg)), + 'X' => format!("{:X}", state.read::(arg)), + 'o' => format!("{:o}", state.read::(arg)), + 'c' => char::from_u32(state.read::(arg) as u32).map_or(String::new(), String::from), + 's' => literals.resolve(&state.read::(arg)).to_owned(), + 'e' | 'E' => format!("{:e}", state.read::(arg)), + 'f' | 'F' | 'g' | 'G' => format!("{}", state.read::(arg)), + // Best-effort fallback driven by the inferred argument type. + _ => match ty.ty { + hir::Type::Integer => format!("{}", state.read::(arg)), + hir::Type::String => literals.resolve(&state.read::(arg)).to_owned(), + _ => format!("{}", state.read::(arg)), + }, + } +} diff --git a/openvaf/openvaf/tests/integration.rs b/openvaf/openvaf/tests/integration.rs index df94470b..ed97f72a 100644 --- a/openvaf/openvaf/tests/integration.rs +++ b/openvaf/openvaf/tests/integration.rs @@ -10,7 +10,7 @@ use openvaf::{CompilationDestination, CompilationTermination, LLVMCodeGenOptLeve use stdx::{ignore_dev_tests, openvaf_test_data, project_root}; use target::spec::Target; -use crate::load::{load_osdi_lib, EvalFlags, OsdiDescriptor}; +use crate::load::{load_osdi_lib, EvalFlags, OsdiDescriptor, OsdiInstance, OsdiModel}; use crate::mock_sim::{MockSimulation, ALPHA}; mod load; @@ -255,6 +255,223 @@ fn test_noise() -> Result<()> { Ok(()) } +/// Fixed-size arrays: declaration, constant- and runtime-index read/write all +/// feed a single conductance. See `arrays.va`; with the default `sel=1` the +/// assembled conductance is G = 21, so the loaded DAE residual/Jacobian must +/// match exactly if every array access lowered correctly. +fn test_arrays() -> Result<()> { + if stdx::IS_CI && cfg!(windows) { + return Ok(()); + } + + let desc = test_descriptor(&openvaf_test_data("osdi").join("arrays.va"))?; + let model = desc.new_model(); + model.process_params()?; + let mut instance = model.new_instance(); + let mut sim = instance.mock_simulation(&model, desc.num_terminals, 300.0)?; + + sim.set_voltage("p", 1.0); + sim.set_voltage("n", 0.0); + instance.eval(&model, &mut sim, EvalFlags::empty()); + instance.load_dae(&model, &mut sim); + + // G = gsum (13) + gx (8) = 21, with I(p,n) = G * V(p,n). + float_cmp::assert_approx_eq!(f64, sim.read_residual("p").0, 21.0, epsilon = 1e-9); + float_cmp::assert_approx_eq!(f64, sim.read_residual("n").0, -21.0, epsilon = 1e-9); + float_cmp::assert_approx_eq!(f64, sim.read_jacobian("p", "p").0, 21.0, epsilon = 1e-9); + Ok(()) +} + +/// `@(cross)` state retention: `state` is assigned only inside cross handlers, so +/// it must hold across timesteps. The mock simulator's `next_iter` swaps the +/// prev/next state arrays, exactly as a real simulator advances a timestep. We +/// drive the input high/low/dead-band and check the latched output is retained. +/// See `cross_latch.va`; residual at q equals `-state` (with V(q)=0). +fn test_cross_latch() -> Result<()> { + if stdx::IS_CI && cfg!(windows) { + return Ok(()); + } + + let desc = test_descriptor(&openvaf_test_data("osdi").join("cross_latch.va"))?; + let model = desc.new_model(); + model.process_params()?; + let mut instance = model.new_instance(); + let mut sim = instance.mock_simulation(&model, desc.num_terminals, 300.0)?; + + // Advance one timestep: swap prev/next state, re-apply the node voltages + // (next_iter zeroes the solution), evaluate, and load the DAE residual. + let step = |instance: &OsdiInstance, model: &OsdiModel, sim: &mut MockSimulation, vd: f64, first: bool| { + if !first { + sim.next_iter(); + } + sim.set_voltage("q", 0.0); + sim.set_voltage("d", vd); + instance.eval(model, sim, EvalFlags::ENABLE_LIM | EvalFlags::INIT_LIM); + instance.load_dae(model, sim); + sim.read_residual("q").0 + }; + + // d high -> latch sets state=1 (residual = -1). + float_cmp::assert_approx_eq!(f64, step(&instance, &model, &mut sim, 1.0, true), -1.0, epsilon = 1e-9); + // dead-band -> state 1 retained. + float_cmp::assert_approx_eq!(f64, step(&instance, &model, &mut sim, 0.5, false), -1.0, epsilon = 1e-9); + // d low -> latch clears state=0 (residual = 0). + float_cmp::assert_approx_eq!(f64, step(&instance, &model, &mut sim, 0.0, false), 0.0, epsilon = 1e-9); + // dead-band -> state 0 retained. + float_cmp::assert_approx_eq!(f64, step(&instance, &model, &mut sim, 0.5, false), 0.0, epsilon = 1e-9); + // d high again -> latch flips back to state=1. + float_cmp::assert_approx_eq!(f64, step(&instance, &model, &mut sim, 1.0, false), -1.0, epsilon = 1e-9); + Ok(()) +} + +/// Regression: `laplace_nd` with anonymous integer coefficient literals (the form +/// the LRM examples use) must compile without the optimizer panicking on mixed +/// int/float arithmetic. Compiling + loading the descriptor is enough to guard the +/// crash. See `laplace_nd_int.va`. +fn test_laplace_nd_int() -> Result<()> { + if stdx::IS_CI && cfg!(windows) { + return Ok(()); + } + test_descriptor(&openvaf_test_data("osdi").join("laplace_nd_int.va"))?; + Ok(()) +} + +/// Vectored/bus ports: a port declared bare in the head and ranged in the body +/// (`input [0:3] in`) must expand to in[0]..in[3] and index correctly. The output +/// sums the four bits with distinct weights, so the loaded residual proves each bit +/// is a distinct node. See `vector_ports.va`. +fn test_vector_ports() -> Result<()> { + if stdx::IS_CI && cfg!(windows) { + return Ok(()); + } + let desc = test_descriptor(&openvaf_test_data("osdi").join("vector_ports.va"))?; + let model = desc.new_model(); + model.process_params()?; + let mut instance = model.new_instance(); + let mut sim = instance.mock_simulation(&model, desc.num_terminals, 300.0)?; + + sim.set_voltage("out", 0.0); + sim.set_voltage("in[0]", 1.0); + sim.set_voltage("in[1]", 1.0); + sim.set_voltage("in[2]", 1.0); + sim.set_voltage("in[3]", 1.0); + instance.eval(&model, &mut sim, EvalFlags::empty()); + instance.load_dae(&model, &mut sim); + + // residual(out) = V(out) - (1+2+3+4) = -10. + float_cmp::assert_approx_eq!(f64, sim.read_residual("out").0, -10.0, epsilon = 1e-9); + Ok(()) +} + +/// LRM 2.4 transition() Example 1 (QAM modulator): vectored input ports declared +/// bare in the head and ranged in the body, bus indexing, transition, $abstime. +/// Compile+load guard. +fn test_qam16() -> Result<()> { + if stdx::IS_CI && cfg!(windows) { + return Ok(()); + } + test_descriptor(&openvaf_test_data("osdi").join("qam16.va"))?; + Ok(()) +} + +/// Retained `@(cross)` ARRAY state: each array element assigned inside a cross +/// handler must retain independently across timesteps. Drives the input +/// high/dead-band/low and checks both elements hold and flip via the prev/next +/// state swap. See `cross_array.va`; residual at q0/q1 equals -s[0]/-s[1]. +fn test_cross_array() -> Result<()> { + if stdx::IS_CI && cfg!(windows) { + return Ok(()); + } + + let desc = test_descriptor(&openvaf_test_data("osdi").join("cross_array.va"))?; + let model = desc.new_model(); + model.process_params()?; + let mut instance = model.new_instance(); + let mut sim = instance.mock_simulation(&model, desc.num_terminals, 300.0)?; + + let step = |instance: &OsdiInstance, model: &OsdiModel, sim: &mut MockSimulation, vd: f64, first: bool| { + if !first { + sim.next_iter(); + } + sim.set_voltage("q0", 0.0); + sim.set_voltage("q1", 0.0); + sim.set_voltage("d", vd); + instance.eval(model, sim, EvalFlags::ENABLE_LIM | EvalFlags::INIT_LIM); + instance.load_dae(model, sim); + (sim.read_residual("q0").0, sim.read_residual("q1").0) + }; + + let check = |(a, b): (f64, f64), ea: f64, eb: f64| { + float_cmp::assert_approx_eq!(f64, a, ea, epsilon = 1e-9); + float_cmp::assert_approx_eq!(f64, b, eb, epsilon = 1e-9); + }; + + check(step(&instance, &model, &mut sim, 1.0, true), -1.0, -2.0); // set s=[1,2] + check(step(&instance, &model, &mut sim, 0.5, false), -1.0, -2.0); // dead-band: retained + check(step(&instance, &model, &mut sim, 0.0, false), 0.0, 0.0); // clear s=[0,0] + check(step(&instance, &model, &mut sim, 0.5, false), 0.0, 0.0); // dead-band: retained + check(step(&instance, &model, &mut sim, 1.0, false), -1.0, -2.0); // flips back + Ok(()) +} + +/// Indirect branch assignment `V(out) : V(pin,nin) == 0` (ideal op-amp, issue #80). +/// Lowers to an implicit equation whose unknown drives `out` as a voltage source and +/// whose residual is the constraint `V(pin) - V(nin)`. The constraint residual (and +/// its Jacobian) is independent of the unknown, so we can check it on the isolated +/// device: with V(pin)=0.3, V(nin)=0.1 the `implicit_equation_0` row carries 0.2 with +/// d/dV(pin)=+1, d/dV(nin)=-1. See `opamp_indirect.va`. +fn test_indirect_opamp() -> Result<()> { + if stdx::IS_CI && cfg!(windows) { + return Ok(()); + } + + let desc = test_descriptor(&openvaf_test_data("osdi").join("opamp_indirect.va"))?; + let model = desc.new_model(); + model.process_params()?; + let mut instance = model.new_instance(); + let mut sim = instance.mock_simulation(&model, desc.num_terminals, 300.0)?; + + sim.set_voltage("out", 0.0); + sim.set_voltage("pin", 0.3); + sim.set_voltage("nin", 0.1); + sim.set_voltage("implicit_equation_0", 0.7); // unknown; residual must not depend on it + instance.eval(&model, &mut sim, EvalFlags::empty()); + instance.load_dae(&model, &mut sim); + + // constraint residual = V(pin) - V(nin) = 0.2, regardless of the unknown. + float_cmp::assert_approx_eq!( + f64, + sim.read_residual("implicit_equation_0").0, + 0.2, + epsilon = 1e-9 + ); + float_cmp::assert_approx_eq!( + f64, + sim.read_jacobian("pin", "implicit_equation_0").0, + 1.0, + epsilon = 1e-9 + ); + float_cmp::assert_approx_eq!( + f64, + sim.read_jacobian("nin", "implicit_equation_0").0, + -1.0, + epsilon = 1e-9 + ); + Ok(()) +} + +/// LRM 2.4 transition() Example 2 (N-bit A/D converter), legal form (continuous +/// contributions outside the discrete @(cross) sampler). Exercises the whole new +/// stack at once: vector ports + genvar unroll + retained @(cross) array + +/// transition. Compile+load guard. +fn test_adc() -> Result<()> { + if stdx::IS_CI && cfg!(windows) { + return Ok(()); + } + test_descriptor(&openvaf_test_data("osdi").join("adc.va"))?; + Ok(()) +} + harness! { // TODO: run this in CI, somehow this test is flakey tough regarding the linker invocation (and really slow) Test::from_dir("integration", &integration_test, &ignore_dev_tests, &project_root().join("integration_tests")), @@ -264,5 +481,5 @@ harness! { Test::from_dir_filtered("vacask_spice", &vacask_spice_test, &is_va_file, &ignore_dev_tests, &vacask_devices().join("spice")), // VACASK simplified SPICE models Test::from_dir_filtered("vacask_spice_sn", &vacask_spice_sn_test, &is_va_file, &ignore_dev_tests, &vacask_devices().join("spice/sn")), - [Test::new("$limit", &test_limit),Test::new("noise", &test_noise)] + [Test::new("$limit", &test_limit),Test::new("noise", &test_noise),Test::new("arrays", &test_arrays),Test::new("cross_latch", &test_cross_latch),Test::new("laplace_nd_int", &test_laplace_nd_int),Test::new("vector_ports", &test_vector_ports),Test::new("qam16", &test_qam16),Test::new("cross_array", &test_cross_array),Test::new("adc", &test_adc),Test::new("indirect_opamp", &test_indirect_opamp)] } diff --git a/openvaf/openvaf/tests/mock_sim/mod.rs b/openvaf/openvaf/tests/mock_sim/mod.rs index 8fb354fb..03d05433 100644 --- a/openvaf/openvaf/tests/mock_sim/mod.rs +++ b/openvaf/openvaf/tests/mock_sim/mod.rs @@ -178,6 +178,18 @@ impl OsdiInstance { sim.state_1.resize(self.descriptor.num_states as usize, 0.0); sim.state_2.resize(self.descriptor.num_states as usize, 0.0); sim.noise_dense.resize(self.descriptor.num_noise_src as usize, 0.0); + + // Initialize the per-instance state_idx map (logical limit-state -> physical + // slot in prev_state/next_state). A real simulator assigns these; with a + // single state the default 0 happens to work, but with several they would all + // alias slot 0. Use the identity mapping. + unsafe { + let data = self.data as *mut u8; + let state_idx = data.add(self.descriptor.state_idx_off as usize).cast::(); + for i in 0..self.descriptor.num_states { + state_idx.add(i as usize).write(i); + } + } Ok(sim) } diff --git a/openvaf/parser/src/grammar/expressions.rs b/openvaf/parser/src/grammar/expressions.rs index 35282c50..ed087607 100644 --- a/openvaf/parser/src/grammar/expressions.rs +++ b/openvaf/parser/src/grammar/expressions.rs @@ -101,7 +101,7 @@ fn atom_expr(p: &mut Parser) -> Option { let done = match p.current() { T!['('] => paren_expr(p), - // T!["'{"] => array_expr(p), TODO properly implement arrays + T!["'{"] | T!['{'] => array_expr(p), T![~] | T![!] | T![-] | T![+] => { let m = p.start(); p.bump_ts(TokenSet::new(&[T![~], T![!], T![-], T![+]])); @@ -112,6 +112,10 @@ fn atom_expr(p: &mut Parser) -> Option { let m = path(p); if p.at(T!('(')) { call(p, m) + } else if p.at(T!['[']) { + // Index expression `base[index]` (array element or bus node access). + let base = m.precede(p).complete(p, PATH_EXPR); + index_expr(p, base) } else { let m = m.precede(p); m.complete(p, PATH_EXPR) @@ -159,21 +163,29 @@ fn paren_expr(p: &mut Parser) -> CompletedMarker { m.complete(p, PAREN_EXPR) } -// fn array_expr(p: &mut Parser) -> CompletedMarker { -// let m = p.start(); -// p.bump(T!["'{"]); -// while !p.at(EOF) && !p.at(T![']']) { -// // test array_attrs -// // const A: &[i64] = &[1, #[cfg(test)] 2]; -// if expr(p).is_none() { -// break; -// } - -// if !p.at(T!['}']) && !p.expect(T![,]) { -// break; -// } -// } -// p.expect(T!['}']); - -// m.complete(p, ARRAY_EXPR) -// } +/// `'{ e0, e1, ... }` (or plain `{ ... }`) array literal. +fn array_expr(p: &mut Parser) -> CompletedMarker { + let m = p.start(); + // Accept both the SystemVerilog-style `'{` and the plain `{` Verilog-A spelling. + p.bump_ts(TokenSet::new(&[T!["'{"], T!['{']])); + while !p.at(EOF) && !p.at(T!['}']) { + if expr(p).is_none() { + break; + } + if !p.at(T!['}']) && !p.expect(T![,]) { + break; + } + } + p.expect(T!['}']); + + m.complete(p, ARRAY_EXPR) +} + +/// `base[index]` — `base` is the already-parsed/completed base expression. +fn index_expr(p: &mut Parser, base: CompletedMarker) -> CompletedMarker { + let m = base.precede(p); + p.bump(T!['[']); + expr(p); + p.expect(T![']']); + m.complete(p, INDEX_EXPR) +} diff --git a/openvaf/parser/src/grammar/items.rs b/openvaf/parser/src/grammar/items.rs index 28152a7b..ca30756c 100755 --- a/openvaf/parser/src/grammar/items.rs +++ b/openvaf/parser/src/grammar/items.rs @@ -86,7 +86,17 @@ pub(super) fn var_decl(p: &mut Parser, m: Marker) { fn var(p: &mut Parser) -> bool { let m = p.start(); - name_r(p, TokenSet::new(&[T![,], T![=], T![;]])); + name_r(p, TokenSet::new(&[T!['['], T![,], T![=], T![;]])); + // Optional array dimension, e.g. `real den[order:0];`. + if p.at(T!['[']) { + let dim = p.start(); + p.bump(T!['[']); + expr(p); + p.expect(T![:]); + expr(p); + p.expect(T![']']); + dim.complete(p, DIMENSION); + } if p.eat(T![=]) { expr(p); } diff --git a/openvaf/parser/src/grammar/items/module.rs b/openvaf/parser/src/grammar/items/module.rs index 3ca4e230..9f3d91fe 100644 --- a/openvaf/parser/src/grammar/items/module.rs +++ b/openvaf/parser/src/grammar/items/module.rs @@ -5,10 +5,12 @@ const MODULE_ITEM_RECOVERY: TokenSet = DIRECTION_TS.union(TokenSet::new(&[ NET_TYPE, ANALOG_KW, INITIAL_KW, + FINAL_KW, BRANCH_KW, STRING_KW, REAL_KW, INTEGER_KW, + GENVAR_KW, PARAMETER_KW, LOCALPARAM_KW, ENDMODULE_KW, @@ -39,12 +41,33 @@ const MODULE_PORTS_RECOVERY: TokenSet = TokenSet::new(&[T![;], T![')'], ENDMODUL fn module_ports(p: &mut Parser) { while !p.at_ts(MODULE_PORTS_RECOVERY) { let m = p.start(); - if !eat_name(p) { - let m = p.start(); + if p.at(IDENT) && p.nth_at(1, T!['[']) { + // Vectored/bus port reference in the module header, e.g. + // `rc_ladder(inode[0], inode[n])`. The referenced element is resolved + // against the expanded scalar nodes of the bus net declaration. + let pr = p.start(); + name(p); + p.bump(T!['[']); + expr(p); + p.expect(T![']']); + pr.complete(p, PORT_REF); + m.complete(p, MODULE_PORT); + } else if eat_name(p) { + m.complete(p, MODULE_PORT); + } else if p.at_ts(DIRECTION_TS) || p.at(T!["(*"]) { + let inner = p.start(); attrs(p, MODULE_PORTS_RECOVERY.union(DIRECTION_TS)); - port_decl::(p, m) + port_decl::(p, inner); + m.complete(p, MODULE_PORT); + } else { + // Neither a port name nor a port direction. This happens e.g. for the + // currently unsupported vectored-node port syntax `inode[0]`. Abandon the + // (empty) port node, report an error and recover instead of asserting + // inside `port_decl` or producing a childless MODULE_PORT. + m.abandon(p); + let err = p.unexpected_tokens_msg(vec![IDENT, INPUT_KW, OUTPUT_KW, INOUT_KW]); + p.err_recover(err, MODULE_PORTS_RECOVERY.union(TokenSet::unique(T![,]))); } - m.complete(p, MODULE_PORT); if !p.at(T![')']) { p.expect_with(T![,], &[T![,], T![')']]); } @@ -83,6 +106,17 @@ fn port_decl(p: &mut Parser, m: Marker) { } p.eat(NET_TYPE); + // Optional vectored/bus range, e.g. `input [0:3] in;` / `output [0:bits-1] out;`. + if p.at(T!['[']) { + let dim = p.start(); + p.bump(T!['[']); + expr(p); + p.expect(T![:]); + expr(p); + p.expect(T![']']); + dim.complete(p, DIMENSION); + } + if MODULE_HEAD { decl_list(p, T![')'], module_port, MODULE_PORT_RECOVERY); } else { @@ -116,6 +150,13 @@ fn module_items(p: &mut Parser) { stmt_with_attrs(p); m.complete(p, ANALOG_BEHAVIOUR); } + // Standalone (non-analog) procedural blocks: `initial ` / `final `. + // These are imperative blocks executed by the standalone VerilogA runner. + INITIAL_KW | FINAL_KW => { + p.bump_any(); + stmt_with_attrs(p); + m.complete(p, PROCEDURAL_BLOCK); + } NET_TYPE => { net_decl::(p, m); } @@ -132,6 +173,7 @@ fn module_items(p: &mut Parser) { branch_decl(p, m); } INTEGER_KW | REAL_KW | STRING_KW => var_decl(p, m), + GENVAR_KW => genvar_decl(p, m), INPUT_KW | OUTPUT_KW | INOUT_KW => port_decl::(p, m), _ => { error_range = if let Some(error_range) = error_range { @@ -147,6 +189,7 @@ fn module_items(p: &mut Parser) { PORT_DECL, NET_DECL, ANALOG_BEHAVIOUR, + PROCEDURAL_BLOCK, ]); p.error(err); p.bump_any(); @@ -160,6 +203,13 @@ fn module_items(p: &mut Parser) { } } +fn genvar_decl(p: &mut Parser, m: Marker) { + p.bump(GENVAR_KW); + decl_list(p, T![;], decl_name, MODULE_ITEM_OR_ATTR_RECOVERY); + p.eat(T![;]); + m.complete(p, GENVAR_DECL); +} + fn net_decl(p: &mut Parser, m: Marker) { //direction and type ar both optional since only one is required if NET_TYPE_FIRST { @@ -171,6 +221,17 @@ fn net_decl(p: &mut Parser, m: Marker) { name_ref_r(p, MODULE_ITEM_OR_ATTR_RECOVERY.union(TokenSet::unique(T![;]))) } + // Optional vectored/bus range, e.g. `electrical [0:n] inode;`. + if p.at(T!['[']) { + let dim = p.start(); + p.bump(T!['[']); + expr(p); + p.expect(T![:]); + expr(p); + p.expect(T![']']); + dim.complete(p, DIMENSION); + } + net_dec_list(p); p.eat(T![;]); m.complete(p, NET_DECL); diff --git a/openvaf/parser/src/grammar/stmts.rs b/openvaf/parser/src/grammar/stmts.rs index 1c569b0f..42a89380 100644 --- a/openvaf/parser/src/grammar/stmts.rs +++ b/openvaf/parser/src/grammar/stmts.rs @@ -47,7 +47,10 @@ fn expr_or_assign_stmt(p: &mut Parser, m: Marker) { fn assign_or_expr(p: &mut Parser) -> bool { let m = p.start(); expr(p); - if p.eat_ts(TokenSet::new(&[T![<+], T![=]])) { + // `:` is the indirect branch assignment operator (`V(out) : f(...) == 0`). The + // lval expression is fully parsed above; a `:` here is unambiguous (any ternary + // `?:` was already consumed inside `expr`). + if p.eat_ts(TokenSet::new(&[T![<+], T![=], T![:]])) { expr(p); m.complete(p, ASSIGN); true @@ -60,21 +63,27 @@ fn assign_or_expr(p: &mut Parser) -> bool { fn event_stmt(p: &mut Parser, m: Marker) { p.bump(T![@]); p.expect(T!['(']); - p.expect_ts_r( - TokenSet::new(&[INITIAL_STEP_KW, FINAL_STEP_KW]), - TokenSet::new(&[T![')'], T!['(']]), - ); - if p.eat(T!['(']) { - while !p.at_ts(TokenSet::new(&[T![')'], T![begin], ENDMODULE_KW])) { - let mut succ = p.expect(STR_LIT); - if !p.at(T![')']) { - succ |= p.expect_with(T![,], &[T![')'], T![,]]); - if !succ { - p.bump_any() + if p.at_ts(TokenSet::new(&[INITIAL_STEP_KW, FINAL_STEP_KW])) { + // Global events: `@(initial_step)` / `@(final_step)` with optional sim phases. + p.bump_any(); + if p.eat(T!['(']) { + while !p.at_ts(TokenSet::new(&[T![')'], T![begin], ENDMODULE_KW])) { + let mut succ = p.expect(STR_LIT); + if !p.at(T![')']) { + succ |= p.expect_with(T![,], &[T![')'], T![,]]); + if !succ { + p.bump_any() + } } } + p.eat(T![')']); } - p.eat(T![')']); + } else { + // Monitored events: `@(cross(expr, dir, tol))`, `@(timer(...))`, ... parsed as + // a call expression. Currently the event condition is not used for scheduling + // (the guarded body is always evaluated, see hir_lower EventControl), so we + // only need to accept and consume it. + expr(p); } p.expect(T![')']); stmt_with_attrs(p); diff --git a/openvaf/sim_back/src/context.rs b/openvaf/sim_back/src/context.rs index eb518e01..3bbdf0d6 100644 --- a/openvaf/sim_back/src/context.rs +++ b/openvaf/sim_back/src/context.rs @@ -118,7 +118,13 @@ impl<'a> Context<'a> { if matches!(kind, PlaceKind::Var(var) if self.module.op_vars.contains_key(var)) || matches!(kind, PlaceKind::CollapseImplicitEquation(_) | PlaceKind::BoundStep) { - self.output_values.insert(val.unwrap_unchecked()); + // The output value may be `None` if it was never materialized (e.g. a + // `$bound_step()` whose value got eliminated). `unwrap_unchecked()` would + // otherwise yield the reserved sentinel `Value(u32::MAX)` and blow up the + // bitset insert (issue #10). + if let Some(val) = val.expand() { + self.output_values.insert(val); + } } } } diff --git a/openvaf/sim_back/src/dae/builder.rs b/openvaf/sim_back/src/dae/builder.rs index 3c6b6d4b..847ffa65 100644 --- a/openvaf/sim_back/src/dae/builder.rs +++ b/openvaf/sim_back/src/dae/builder.rs @@ -224,6 +224,11 @@ impl<'a> Builder<'a> { ) { for residual in &mut self.system.residual { for (state, (unchanged, lim_vals)) in self.intern.lim_state.iter_enumerated() { + // Retained `@(cross)` slots reuse the state array but carry no limit + // function; they take no part in limit-rhs construction. + if self.intern.retained_lim_states.contains(&state) { + continue; + } for &(val, neg) in lim_vals { let unknown = if let Some(unknown) = derivative_info.unknowns.index(&val) { unknown diff --git a/openvaf/syntax/src/ast/expr_ext.rs b/openvaf/syntax/src/ast/expr_ext.rs index 3bf62c58..ee1bc277 100644 --- a/openvaf/syntax/src/ast/expr_ext.rs +++ b/openvaf/syntax/src/ast/expr_ext.rs @@ -366,6 +366,30 @@ impl ast::SelectExpr { } } +impl ast::IndexExpr { + /// The indexed expression (e.g. the array/bus name in `den[k]`). + pub fn base(&self) -> Option { + support::children(self.syntax()).next() + } + + /// The index expression (e.g. `k` in `den[k]`). + pub fn index(&self) -> Option { + support::children(self.syntax()).nth(1) + } +} + +impl ast::Dimension { + /// The most-significant bound of `[msb:lsb]`. + pub fn msb(&self) -> Option { + support::children(self.syntax()).next() + } + + /// The least-significant bound of `[msb:lsb]`. + pub fn lsb(&self) -> Option { + support::children(self.syntax()).nth(1) + } +} + pub enum AsssigmentOp { /// a variable assignment stmt /// lhs must be an identifier (example `I = V(a,c)/R;`) diff --git a/openvaf/syntax/src/ast/generated/nodes.rs b/openvaf/syntax/src/ast/generated/nodes.rs index cc2f471d..127af9e8 100644 --- a/openvaf/syntax/src/ast/generated/nodes.rs +++ b/openvaf/syntax/src/ast/generated/nodes.rs @@ -251,6 +251,15 @@ impl ArrayExpr { pub fn r_curly_token(&self) -> Option { support::token(&self.syntax, T!['}']) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct IndexExpr { + pub(crate) syntax: SyntaxNode, +} +impl IndexExpr { + pub fn expr(&self) -> Option { support::child(&self.syntax) } + pub fn l_brack_token(&self) -> Option { support::token(&self.syntax, T!['[']) } + pub fn r_brack_token(&self) -> Option { support::token(&self.syntax, T![']']) } +} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Call { pub(crate) syntax: SyntaxNode, } @@ -402,6 +411,7 @@ impl NetDecl { pub fn net_type_token(&self) -> Option { support::token(&self.syntax, T![net_type]) } + pub fn dimension(&self) -> Option { support::child(&self.syntax) } pub fn names(&self) -> AstChildren { support::children(&self.syntax) } pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) } } @@ -416,6 +426,16 @@ impl AnalogBehaviour { pub fn stmt(&self) -> Option { support::child(&self.syntax) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ProceduralBlock { + pub(crate) syntax: SyntaxNode, +} +impl ast::AttrsOwner for ProceduralBlock {} +impl ProceduralBlock { + pub fn initial_token(&self) -> Option { support::token(&self.syntax, T![initial]) } + pub fn final_token(&self) -> Option { support::token(&self.syntax, T![final]) } + pub fn stmt(&self) -> Option { support::child(&self.syntax) } +} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Function { pub(crate) syntax: SyntaxNode, } @@ -459,6 +479,16 @@ impl AliasParam { pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct GenvarDecl { + pub(crate) syntax: SyntaxNode, +} +impl ast::AttrsOwner for GenvarDecl {} +impl GenvarDecl { + pub fn genvar_token(&self) -> Option { support::token(&self.syntax, T![genvar]) } + pub fn names(&self) -> AstChildren { support::children(&self.syntax) } + pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) } +} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ModulePort { pub(crate) syntax: SyntaxNode, } @@ -474,18 +504,40 @@ impl PortDecl { pub fn net_type_token(&self) -> Option { support::token(&self.syntax, T![net_type]) } + pub fn dimension(&self) -> Option { support::child(&self.syntax) } pub fn names(&self) -> AstChildren { support::children(&self.syntax) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PortRef { + pub(crate) syntax: SyntaxNode, +} +impl PortRef { + pub fn name(&self) -> Option { support::child(&self.syntax) } + pub fn l_brack_token(&self) -> Option { support::token(&self.syntax, T!['[']) } + pub fn expr(&self) -> Option { support::child(&self.syntax) } + pub fn r_brack_token(&self) -> Option { support::token(&self.syntax, T![']']) } +} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Var { pub(crate) syntax: SyntaxNode, } impl Var { pub fn name(&self) -> Option { support::child(&self.syntax) } + pub fn dimension(&self) -> Option { support::child(&self.syntax) } pub fn eq_token(&self) -> Option { support::token(&self.syntax, T![=]) } pub fn default(&self) -> Option { support::child(&self.syntax) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Dimension { + pub(crate) syntax: SyntaxNode, +} +impl Dimension { + pub fn l_brack_token(&self) -> Option { support::token(&self.syntax, T!['[']) } + pub fn expr(&self) -> Option { support::child(&self.syntax) } + pub fn colon_token(&self) -> Option { support::token(&self.syntax, T![:]) } + pub fn r_brack_token(&self) -> Option { support::token(&self.syntax, T![']']) } +} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Param { pub(crate) syntax: SyntaxNode, } @@ -541,6 +593,7 @@ pub enum Expr { BinExpr(BinExpr), ParenExpr(ParenExpr), ArrayExpr(ArrayExpr), + IndexExpr(IndexExpr), Call(Call), SelectExpr(SelectExpr), PathExpr(PathExpr), @@ -584,16 +637,19 @@ pub enum ModuleItem { BodyPortDecl(BodyPortDecl), NetDecl(NetDecl), AnalogBehaviour(AnalogBehaviour), + ProceduralBlock(ProceduralBlock), Function(Function), BranchDecl(BranchDecl), VarDecl(VarDecl), ParamDecl(ParamDecl), AliasParam(AliasParam), + GenvarDecl(GenvarDecl), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ModulePortKind { PortDecl(PortDecl), Name(Name), + PortRef(PortRef), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ParamRef { @@ -883,6 +939,17 @@ impl AstNode for ArrayExpr { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for IndexExpr { + fn can_cast(kind: SyntaxKind) -> bool { kind == INDEX_EXPR } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for Call { fn can_cast(kind: SyntaxKind) -> bool { kind == CALL } fn cast(syntax: SyntaxNode) -> Option { @@ -1059,6 +1126,17 @@ impl AstNode for AnalogBehaviour { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for ProceduralBlock { + fn can_cast(kind: SyntaxKind) -> bool { kind == PROCEDURAL_BLOCK } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for Function { fn can_cast(kind: SyntaxKind) -> bool { kind == FUNCTION } fn cast(syntax: SyntaxNode) -> Option { @@ -1092,6 +1170,17 @@ impl AstNode for AliasParam { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for GenvarDecl { + fn can_cast(kind: SyntaxKind) -> bool { kind == GENVAR_DECL } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for ModulePort { fn can_cast(kind: SyntaxKind) -> bool { kind == MODULE_PORT } fn cast(syntax: SyntaxNode) -> Option { @@ -1114,6 +1203,17 @@ impl AstNode for PortDecl { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for PortRef { + fn can_cast(kind: SyntaxKind) -> bool { kind == PORT_REF } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for Var { fn can_cast(kind: SyntaxKind) -> bool { kind == VAR } fn cast(syntax: SyntaxNode) -> Option { @@ -1125,6 +1225,17 @@ impl AstNode for Var { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for Dimension { + fn can_cast(kind: SyntaxKind) -> bool { kind == DIMENSION } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for Param { fn can_cast(kind: SyntaxKind) -> bool { kind == PARAM } fn cast(syntax: SyntaxNode) -> Option { @@ -1192,6 +1303,9 @@ impl From for Expr { impl From for Expr { fn from(node: ArrayExpr) -> Expr { Expr::ArrayExpr(node) } } +impl From for Expr { + fn from(node: IndexExpr) -> Expr { Expr::IndexExpr(node) } +} impl From for Expr { fn from(node: Call) -> Expr { Expr::Call(node) } } @@ -1210,8 +1324,8 @@ impl From for Expr { impl AstNode for Expr { fn can_cast(kind: SyntaxKind) -> bool { match kind { - PREFIX_EXPR | BIN_EXPR | PAREN_EXPR | ARRAY_EXPR | CALL | SELECT_EXPR | PATH_EXPR - | PORT_FLOW => true, + PREFIX_EXPR | BIN_EXPR | PAREN_EXPR | ARRAY_EXPR | INDEX_EXPR | CALL | SELECT_EXPR + | PATH_EXPR | PORT_FLOW => true, _ => Literal::can_cast(kind), } } @@ -1221,6 +1335,7 @@ impl AstNode for Expr { BIN_EXPR => Expr::BinExpr(BinExpr { syntax }), PAREN_EXPR => Expr::ParenExpr(ParenExpr { syntax }), ARRAY_EXPR => Expr::ArrayExpr(ArrayExpr { syntax }), + INDEX_EXPR => Expr::IndexExpr(IndexExpr { syntax }), CALL => Expr::Call(Call { syntax }), SELECT_EXPR => Expr::SelectExpr(SelectExpr { syntax }), PATH_EXPR => Expr::PathExpr(PathExpr { syntax }), @@ -1235,6 +1350,7 @@ impl AstNode for Expr { Expr::BinExpr(it) => &it.syntax, Expr::ParenExpr(it) => &it.syntax, Expr::ArrayExpr(it) => &it.syntax, + Expr::IndexExpr(it) => &it.syntax, Expr::Call(it) => &it.syntax, Expr::SelectExpr(it) => &it.syntax, Expr::PathExpr(it) => &it.syntax, @@ -1409,6 +1525,9 @@ impl From for ModuleItem { impl From for ModuleItem { fn from(node: AnalogBehaviour) -> ModuleItem { ModuleItem::AnalogBehaviour(node) } } +impl From for ModuleItem { + fn from(node: ProceduralBlock) -> ModuleItem { ModuleItem::ProceduralBlock(node) } +} impl From for ModuleItem { fn from(node: Function) -> ModuleItem { ModuleItem::Function(node) } } @@ -1424,11 +1543,14 @@ impl From for ModuleItem { impl From for ModuleItem { fn from(node: AliasParam) -> ModuleItem { ModuleItem::AliasParam(node) } } +impl From for ModuleItem { + fn from(node: GenvarDecl) -> ModuleItem { ModuleItem::GenvarDecl(node) } +} impl AstNode for ModuleItem { fn can_cast(kind: SyntaxKind) -> bool { match kind { - BODY_PORT_DECL | NET_DECL | ANALOG_BEHAVIOUR | FUNCTION | BRANCH_DECL | VAR_DECL - | PARAM_DECL | ALIAS_PARAM => true, + BODY_PORT_DECL | NET_DECL | ANALOG_BEHAVIOUR | PROCEDURAL_BLOCK | FUNCTION + | BRANCH_DECL | VAR_DECL | PARAM_DECL | ALIAS_PARAM | GENVAR_DECL => true, _ => false, } } @@ -1437,11 +1559,13 @@ impl AstNode for ModuleItem { BODY_PORT_DECL => ModuleItem::BodyPortDecl(BodyPortDecl { syntax }), NET_DECL => ModuleItem::NetDecl(NetDecl { syntax }), ANALOG_BEHAVIOUR => ModuleItem::AnalogBehaviour(AnalogBehaviour { syntax }), + PROCEDURAL_BLOCK => ModuleItem::ProceduralBlock(ProceduralBlock { syntax }), FUNCTION => ModuleItem::Function(Function { syntax }), BRANCH_DECL => ModuleItem::BranchDecl(BranchDecl { syntax }), VAR_DECL => ModuleItem::VarDecl(VarDecl { syntax }), PARAM_DECL => ModuleItem::ParamDecl(ParamDecl { syntax }), ALIAS_PARAM => ModuleItem::AliasParam(AliasParam { syntax }), + GENVAR_DECL => ModuleItem::GenvarDecl(GenvarDecl { syntax }), _ => return None, }; Some(res) @@ -1451,11 +1575,13 @@ impl AstNode for ModuleItem { ModuleItem::BodyPortDecl(it) => &it.syntax, ModuleItem::NetDecl(it) => &it.syntax, ModuleItem::AnalogBehaviour(it) => &it.syntax, + ModuleItem::ProceduralBlock(it) => &it.syntax, ModuleItem::Function(it) => &it.syntax, ModuleItem::BranchDecl(it) => &it.syntax, ModuleItem::VarDecl(it) => &it.syntax, ModuleItem::ParamDecl(it) => &it.syntax, ModuleItem::AliasParam(it) => &it.syntax, + ModuleItem::GenvarDecl(it) => &it.syntax, } } } @@ -1465,10 +1591,13 @@ impl From for ModulePortKind { impl From for ModulePortKind { fn from(node: Name) -> ModulePortKind { ModulePortKind::Name(node) } } +impl From for ModulePortKind { + fn from(node: PortRef) -> ModulePortKind { ModulePortKind::PortRef(node) } +} impl AstNode for ModulePortKind { fn can_cast(kind: SyntaxKind) -> bool { match kind { - PORT_DECL | NAME => true, + PORT_DECL | NAME | PORT_REF => true, _ => false, } } @@ -1476,6 +1605,7 @@ impl AstNode for ModulePortKind { let res = match syntax.kind() { PORT_DECL => ModulePortKind::PortDecl(PortDecl { syntax }), NAME => ModulePortKind::Name(Name { syntax }), + PORT_REF => ModulePortKind::PortRef(PortRef { syntax }), _ => return None, }; Some(res) @@ -1484,6 +1614,7 @@ impl AstNode for ModulePortKind { match self { ModulePortKind::PortDecl(it) => &it.syntax, ModulePortKind::Name(it) => &it.syntax, + ModulePortKind::PortRef(it) => &it.syntax, } } } @@ -1722,6 +1853,11 @@ impl std::fmt::Display for ArrayExpr { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for IndexExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for Call { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -1802,6 +1938,11 @@ impl std::fmt::Display for AnalogBehaviour { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for ProceduralBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for Function { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -1817,6 +1958,11 @@ impl std::fmt::Display for AliasParam { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for GenvarDecl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for ModulePort { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -1827,11 +1973,21 @@ impl std::fmt::Display for PortDecl { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for PortRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for Var { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for Dimension { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for Param { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/openvaf/syntax/src/ast/node_ext.rs b/openvaf/syntax/src/ast/node_ext.rs index 7176af57..bec308e2 100644 --- a/openvaf/syntax/src/ast/node_ext.rs +++ b/openvaf/syntax/src/ast/node_ext.rs @@ -7,7 +7,7 @@ use stdx::impl_debug; use super::{ AnalogBehaviour, ArgListOwner, Assign, AstChildTokens, AstChildren, Constraint, EventStmt, - Expr, ForStmt, Function, ModulePortKind, Path, PortFlow, Range, Stmt, StrLit, + Expr, ForStmt, Function, ModulePortKind, Path, PortFlow, ProceduralBlock, Range, Stmt, StrLit, }; use crate::ast::{self, support, AstNode}; use crate::SyntaxKind::{IDENT, ROOT_KW}; @@ -163,6 +163,21 @@ impl ast::ModuleDecl { pub fn body_ports(&self) -> AstChildren { support::children(self.syntax()) } + + /// Statements of standalone `initial` procedural blocks (the imperative runner + /// lane), in source order. Distinct from `analog initial` blocks. + pub fn initial_behaviour(&self) -> impl Iterator { + support::children::(self.syntax()) + .filter(|it| it.initial_token().is_some()) + .filter_map(|it| it.stmt()) + } + + /// Statements of standalone `final` procedural blocks, in source order. + pub fn final_behaviour(&self) -> impl Iterator { + support::children::(self.syntax()) + .filter(|it| it.final_token().is_some()) + .filter_map(|it| it.stmt()) + } } impl ast::ModulePort { @@ -185,12 +200,15 @@ impl ast::ModulePorts { pub enum AssignOp { Contribute, Assign, + /// Indirect branch assignment `V(out) : constraint == 0;` (Verilog-AMS LRM). + Indirect, } impl_debug! { match AssignOp{ AssignOp::Contribute => "<+"; AssignOp::Assign => "="; + AssignOp::Indirect => ":"; } } @@ -200,6 +218,8 @@ impl Assign { Some(AssignOp::Assign) } else if support::token(self.syntax(), T![<+]).is_some() { Some(AssignOp::Contribute) + } else if support::token(self.syntax(), T![:]).is_some() { + Some(AssignOp::Indirect) } else { None } diff --git a/openvaf/syntax/veriloga.ungram b/openvaf/syntax/veriloga.ungram index 0fccf808..cc50fd56 100644 --- a/openvaf/syntax/veriloga.ungram +++ b/openvaf/syntax/veriloga.ungram @@ -108,11 +108,15 @@ Expr = | BinExpr | ParenExpr | ArrayExpr +| IndexExpr | Call | SelectExpr | PathExpr | PortFlow +IndexExpr = + Expr '[' Expr ']' + PathExpr = Path // Required to allow PortFlow = '<' port: Path '>' @@ -190,24 +194,38 @@ ModuleItem = BodyPortDecl | NetDecl | AnalogBehaviour +| ProceduralBlock | Function | BranchDecl | VarDecl | ParamDecl | AliasParam +| GenvarDecl ModulePorts = '('ports: (ModulePort (',' ModulePort)*)? ')' ModulePort = kind: ModulePortKind -ModulePortKind = PortDecl| Name +ModulePortKind = PortDecl| Name | PortRef + +PortRef = + Name '[' Expr ']' + +GenvarDecl = + AttrList* 'genvar' (Name (',' Name)*) ';' AnalogBehaviour = AttrList* 'analog' 'initial'? Stmt +ProceduralBlock = + AttrList* ('initial' | 'final') Stmt + VarDecl = AttrList* Type (Var (',' Var)*) ';' Var = - Name ('=' default:Expr)? + Name Dimension? ('=' default:Expr)? + +Dimension = + '[' Expr ':' Expr ']' @@ -230,13 +248,13 @@ Range = NetDecl = - AttrList* discipline:NameRef? 'net_type'? (Name (',' Name)*)';' + AttrList* discipline:NameRef? 'net_type'? Dimension? (Name (',' Name)*)';' BodyPortDecl = PortDecl ';' PortDecl = - AttrList* Direction discipline:NameRef? 'net_type'? (Name (',' Name)*) + AttrList* Direction discipline:NameRef? 'net_type'? Dimension? (Name (',' Name)*) Direction = 'inout' | 'input' | 'output' diff --git a/openvaf/test_data/dae/lim_rhs_mir.snap b/openvaf/test_data/dae/lim_rhs_mir.snap index 6297143b..cfc218f6 100644 --- a/openvaf/test_data/dae/lim_rhs_mir.snap +++ b/openvaf/test_data/dae/lim_rhs_mir.snap @@ -1,6 +1,6 @@ function %(v16, v17, v18, v19, v20, v36, v46) { inst0 = const fn %$limit[Spur(1)](2) -> 1 - inst1 = const fn %$store[lim_state0](1) -> 1 + inst1 = fn %$store[lim_state0](1) -> 1 block5: @0009 br v20, block2, block4 diff --git a/openvaf/test_data/dae/lim_rhs_react_mir.snap b/openvaf/test_data/dae/lim_rhs_react_mir.snap index 9b9753c2..cd6d3264 100644 --- a/openvaf/test_data/dae/lim_rhs_react_mir.snap +++ b/openvaf/test_data/dae/lim_rhs_react_mir.snap @@ -1,6 +1,6 @@ function %(v16, v17, v18, v19, v20, v37, v45) { inst0 = const fn %$limit[Spur(1)](2) -> 1 - inst1 = const fn %$store[lim_state0](1) -> 1 + inst1 = fn %$store[lim_state0](1) -> 1 inst2 = const fn %ddt(1) -> 1 block5: diff --git a/openvaf/test_data/dae/lim_rhs_sign_mir.snap b/openvaf/test_data/dae/lim_rhs_sign_mir.snap index 99723dbf..f2f82af9 100644 --- a/openvaf/test_data/dae/lim_rhs_sign_mir.snap +++ b/openvaf/test_data/dae/lim_rhs_sign_mir.snap @@ -1,6 +1,6 @@ function %(v16, v19, v20, v21, v25, v30, v44, v64) { inst0 = const fn %$limit[Spur(1)](2) -> 1 - inst1 = const fn %$store[lim_state0](1) -> 1 + inst1 = fn %$store[lim_state0](1) -> 1 v3 = fconst 0.0 v6 = fconst 0x1.0000000000000p0 diff --git a/openvaf/test_data/osdi/adc.snap b/openvaf/test_data/osdi/adc.snap new file mode 100644 index 00000000..b2ea60d4 --- /dev/null +++ b/openvaf/test_data/osdi/adc.snap @@ -0,0 +1,72 @@ +param "$mfactor" +units = "", desc = "Multiplier (Verilog-A $mfactor)", flags = ParameterFlags(PARA_KIND_INST) +param "bits" +units = "", desc = "", flags = ParameterFlags(PARA_TY_INT) +param "fullscale" +units = "", desc = "", flags = ParameterFlags(0x0) +param "dly" +units = "", desc = "", flags = ParameterFlags(PARA_TY_INT) +param "ttime" +units = "", desc = "", flags = ParameterFlags(0x0) + +10 terminals +node "in" units = "V", runits = "A" +node "clk" units = "V", runits = "A" +node "out[0]" units = "V", runits = "A" +node "out[1]" units = "V", runits = "A" +node "out[2]" units = "V", runits = "A" +node "out[3]" units = "V", runits = "A" +node "out[4]" units = "V", runits = "A" +node "out[5]" units = "V", runits = "A" +node "out[6]" units = "V", runits = "A" +node "out[7]" units = "V", runits = "A" +node(flow) "flow(out[0])" units = "A", runits = "V" +node(flow) "flow(out[1])" units = "A", runits = "V" +node(flow) "flow(out[2])" units = "A", runits = "V" +node(flow) "flow(out[3])" units = "A", runits = "V" +node(flow) "flow(out[4])" units = "A", runits = "V" +node(flow) "flow(out[5])" units = "A", runits = "V" +node(flow) "flow(out[6])" units = "A", runits = "V" +node(flow) "flow(out[7])" units = "A", runits = "V" +node "implicit_equation_0" units = "", runits = "" +node "implicit_equation_1" units = "", runits = "" +node "implicit_equation_2" units = "", runits = "" +node "implicit_equation_3" units = "", runits = "" +node "implicit_equation_4" units = "", runits = "" +node "implicit_equation_5" units = "", runits = "" +node "implicit_equation_6" units = "", runits = "" +node "implicit_equation_7" units = "", runits = "" +jacobian (out[0], flow(out[0])) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (out[1], flow(out[1])) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (out[2], flow(out[2])) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (out[3], flow(out[3])) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (out[4], flow(out[4])) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (out[5], flow(out[5])) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (out[6], flow(out[6])) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (out[7], flow(out[7])) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[0]), out[0]) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[0]), implicit_equation_0) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[1]), out[1]) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[1]), implicit_equation_1) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[2]), out[2]) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[2]), implicit_equation_2) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[3]), out[3]) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[3]), implicit_equation_3) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[4]), out[4]) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[4]), implicit_equation_4) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[5]), out[5]) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[5]), implicit_equation_5) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[6]), out[6]) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[6]), implicit_equation_6) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[7]), out[7]) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out[7]), implicit_equation_7) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_0, implicit_equation_0) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_1, implicit_equation_1) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_2, implicit_equation_2) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_3, implicit_equation_3) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_4, implicit_equation_4) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_5, implicit_equation_5) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_6, implicit_equation_6) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_7, implicit_equation_7) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT | JACOBIAN_ENTRY_REACT_CONST) +10 states +has bound_step false diff --git a/openvaf/test_data/osdi/adc.va b/openvaf/test_data/osdi/adc.va new file mode 100644 index 00000000..b1a5b01a --- /dev/null +++ b/openvaf/test_data/osdi/adc.va @@ -0,0 +1,31 @@ +`include "constants.vams" +`include "disciplines.vams" +module adc(in, clk, out); + parameter bits = 8, fullscale = 1.0, dly = 0, ttime = 10n; + input in, clk; + output [0:bits-1] out; + electrical in, clk; + electrical [0:bits-1] out; + real sample, thresh; + integer result[0:bits-1]; + genvar i; + analog begin + @(cross(V(clk)-2.5, +1)) begin + sample = V(in); + thresh = fullscale/2.0; + for (i = bits - 1; i >= 0; i = i - 1) begin + if (sample > thresh) begin + result[i] = 1.0; + sample = sample - thresh; + end + else begin + result[i] = 0.0; + end + sample = 2.0*sample; + end + end + for (i = 0; i < bits; i = i + 1) begin + V(out[i]) <+ transition(result[i], dly, ttime); + end + end +endmodule diff --git a/openvaf/test_data/osdi/arrays.snap b/openvaf/test_data/osdi/arrays.snap new file mode 100644 index 00000000..496ec49f --- /dev/null +++ b/openvaf/test_data/osdi/arrays.snap @@ -0,0 +1,14 @@ +param "$mfactor" +units = "", desc = "Multiplier (Verilog-A $mfactor)", flags = ParameterFlags(PARA_KIND_INST) +param "sel" +units = "", desc = "", flags = ParameterFlags(PARA_TY_INT) + +2 terminals +node "p" units = "V", runits = "A" +node "n" units = "V", runits = "A" +jacobian (p, p) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (p, n) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (n, p) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (n, n) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +0 states +has bound_step false diff --git a/openvaf/test_data/osdi/arrays.va b/openvaf/test_data/osdi/arrays.va new file mode 100644 index 00000000..1398e1fc --- /dev/null +++ b/openvaf/test_data/osdi/arrays.va @@ -0,0 +1,34 @@ +`include "disciplines.vams" + +// Exercises fixed-size array support: declaration, constant-index read/write, +// and runtime-index (parameter-driven) read/write. The module behaves as a +// linear conductance whose value is assembled entirely through array accesses, +// so the loaded DAE residual/Jacobian directly reflect that the array lowering +// computed the right number. +// +// With sel = 1 the conductance works out to: +// g = [1, 2, 4] (constant-index writes) +// g[sel] = 8 -> g[1]=8 (runtime-index write) => g = [1, 8, 4] +// gsum = g[0]+g[1]+g[2] = 13 (constant-index reads) +// gx = g[sel] = g[1] = 8 (runtime-index read) +// G = gsum + gx = 21 +module arrays(p, n); + inout p, n; + electrical p, n; + + parameter integer sel = 1; + + real g[0:2]; + real gsum; + real gx; + + analog begin + g[0] = 1.0; + g[1] = 2.0; + g[2] = 4.0; + g[sel] = 8.0; + gsum = g[0] + g[1] + g[2]; + gx = g[sel]; + I(p, n) <+ (gsum + gx) * V(p, n); + end +endmodule diff --git a/openvaf/test_data/osdi/cross_array.snap b/openvaf/test_data/osdi/cross_array.snap new file mode 100644 index 00000000..0ba47cb7 --- /dev/null +++ b/openvaf/test_data/osdi/cross_array.snap @@ -0,0 +1,11 @@ +param "$mfactor" +units = "", desc = "Multiplier (Verilog-A $mfactor)", flags = ParameterFlags(PARA_KIND_INST) + +3 terminals +node "d" units = "V", runits = "A" +node "q0" units = "V", runits = "A" +node "q1" units = "V", runits = "A" +jacobian (q0, q0) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (q1, q1) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +2 states +has bound_step false diff --git a/openvaf/test_data/osdi/cross_array.va b/openvaf/test_data/osdi/cross_array.va new file mode 100644 index 00000000..9d446ee3 --- /dev/null +++ b/openvaf/test_data/osdi/cross_array.va @@ -0,0 +1,25 @@ +`include "disciplines.vams" + +// Retained `@(cross)` ARRAY state: s[0]/s[1] are assigned only inside cross +// handlers, so each element must hold its value across timesteps independently +// (the ADC sampled-bit-vector pattern, minimal form). Residual at q0/q1 is +// V(q) - s[k], so with V(q)=0 it reads back -s[0] / -s[1]. +module cross_array(d, q0, q1); + inout d, q0, q1; + electrical d, q0, q1; + + integer s[0:1]; + + analog begin + @(cross(V(d) - 0.7, +1)) if (V(d) > 0.7) begin + s[0] = 1; + s[1] = 2; + end + @(cross(V(d) - 0.3, -1)) if (V(d) < 0.3) begin + s[0] = 0; + s[1] = 0; + end + I(q0) <+ V(q0) - s[0]; + I(q1) <+ V(q1) - s[1]; + end +endmodule diff --git a/openvaf/test_data/osdi/cross_latch.snap b/openvaf/test_data/osdi/cross_latch.snap new file mode 100644 index 00000000..5f48ec8f --- /dev/null +++ b/openvaf/test_data/osdi/cross_latch.snap @@ -0,0 +1,9 @@ +param "$mfactor" +units = "", desc = "Multiplier (Verilog-A $mfactor)", flags = ParameterFlags(PARA_KIND_INST) + +2 terminals +node "d" units = "V", runits = "A" +node "q" units = "V", runits = "A" +jacobian (q, q) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +1 states +has bound_step false diff --git a/openvaf/test_data/osdi/cross_latch.va b/openvaf/test_data/osdi/cross_latch.va new file mode 100644 index 00000000..fe0c0da0 --- /dev/null +++ b/openvaf/test_data/osdi/cross_latch.va @@ -0,0 +1,21 @@ +`include "disciplines.vams" + +// Exercises `@(cross)` state retention across timesteps. `state` is assigned only +// inside cross handlers, so it must hold its value between evaluations (a latch). +// A rising crossing past 0.7 sets it; a falling crossing past 0.3 clears it; in +// the 0.3..0.7 dead-band neither guard fires and the previous value is retained. +// +// The output drives q toward `state` (residual at q is V(q) - state), so reading +// the q residual with V(q)=0 yields exactly -state -- a direct, retained readout. +module cross_latch(d, q); + inout d, q; + electrical d, q; + + integer state; + + analog begin + @(cross(V(d) - 0.7, +1)) if (V(d) > 0.7) state = 1; + @(cross(V(d) - 0.3, -1)) if (V(d) < 0.3) state = 0; + I(q) <+ V(q) - state; + end +endmodule diff --git a/openvaf/test_data/osdi/laplace_nd_int.snap b/openvaf/test_data/osdi/laplace_nd_int.snap new file mode 100644 index 00000000..577828d5 --- /dev/null +++ b/openvaf/test_data/osdi/laplace_nd_int.snap @@ -0,0 +1,15 @@ +param "$mfactor" +units = "", desc = "Multiplier (Verilog-A $mfactor)", flags = ParameterFlags(PARA_KIND_INST) + +2 terminals +node "in" units = "V", runits = "A" +node "out" units = "V", runits = "A" +node(flow) "flow(out)" units = "A", runits = "V" +node "implicit_equation_0" units = "", runits = "" +jacobian (out, flow(out)) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out), out) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out), implicit_equation_0) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_0, in) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_0, implicit_equation_0) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +0 states +has bound_step false diff --git a/openvaf/test_data/osdi/laplace_nd_int.va b/openvaf/test_data/osdi/laplace_nd_int.va new file mode 100644 index 00000000..d9150554 --- /dev/null +++ b/openvaf/test_data/osdi/laplace_nd_int.va @@ -0,0 +1,18 @@ +`include "disciplines.vams" + +// Regression: `laplace_nd` with anonymous *integer* coefficient literals. The +// Verilog-AMS LRM's own examples write coefficients as bare integers (e.g. +// `'{-1,0,1}`), which lower to integer MIR values. The state-space realization +// does real arithmetic on them, so the coefficients must be widened to real -- +// otherwise the optimizer hits `fsub Float, Int` and the compiler panics. +// +// Here H(s) = 1 / (1 + s): a stable first-order low-pass with integer +// coefficients, exercising exactly that widening path. +module laplace_nd_int(in, out); + inout in, out; + electrical in, out; + + analog begin + V(out) <+ laplace_nd(V(in), '{1}, '{1, 1}); + end +endmodule diff --git a/openvaf/test_data/osdi/opamp_indirect.snap b/openvaf/test_data/osdi/opamp_indirect.snap new file mode 100644 index 00000000..40d93090 --- /dev/null +++ b/openvaf/test_data/osdi/opamp_indirect.snap @@ -0,0 +1,16 @@ +param "$mfactor" +units = "", desc = "Multiplier (Verilog-A $mfactor)", flags = ParameterFlags(PARA_KIND_INST) + +3 terminals +node "out" units = "V", runits = "A" +node "pin" units = "V", runits = "A" +node "nin" units = "V", runits = "A" +node(flow) "flow(out)" units = "A", runits = "V" +node "implicit_equation_0" units = "", runits = "" +jacobian (out, flow(out)) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out), out) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out), implicit_equation_0) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_0, pin) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_0, nin) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +0 states +has bound_step false diff --git a/openvaf/test_data/osdi/opamp_indirect.va b/openvaf/test_data/osdi/opamp_indirect.va new file mode 100644 index 00000000..6400621a --- /dev/null +++ b/openvaf/test_data/osdi/opamp_indirect.va @@ -0,0 +1,9 @@ +// Ideal op-amp via indirect branch assignment (Verilog-AMS LRM, issue #80). +// `V(out):V(pin,nin) == 0` solves the output so the differential input is zero. +`include "disciplines.vams" +module opamp_indirect(out, pin, nin); + inout out, pin, nin; + electrical out, pin, nin; + analog + V(out) : V(pin,nin) == 0; +endmodule diff --git a/openvaf/test_data/osdi/qam16.snap b/openvaf/test_data/osdi/qam16.snap new file mode 100644 index 00000000..ec043f9c --- /dev/null +++ b/openvaf/test_data/osdi/qam16.snap @@ -0,0 +1,30 @@ +param "$mfactor" +units = "", desc = "Multiplier (Verilog-A $mfactor)", flags = ParameterFlags(PARA_KIND_INST) +param "freq" +units = "", desc = "", flags = ParameterFlags(0x0) +param "ampl" +units = "", desc = "", flags = ParameterFlags(0x0) +param "thresh" +units = "", desc = "", flags = ParameterFlags(0x0) +param "tdelay" +units = "", desc = "", flags = ParameterFlags(0x0) +param "ttransit" +units = "", desc = "", flags = ParameterFlags(0x0) + +5 terminals +node "in[0]" units = "V", runits = "" +node "in[1]" units = "V", runits = "" +node "in[2]" units = "V", runits = "" +node "in[3]" units = "V", runits = "" +node "out" units = "V", runits = "" +node(flow) "flow(out)" units = "", runits = "V" +node "implicit_equation_0" units = "", runits = "" +node "implicit_equation_1" units = "", runits = "" +jacobian (out, flow(out)) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out), out) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out), implicit_equation_0) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (flow(out), implicit_equation_1) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_0, implicit_equation_0) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT | JACOBIAN_ENTRY_REACT_CONST) +jacobian (implicit_equation_1, implicit_equation_1) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT | JACOBIAN_ENTRY_REACT_CONST) +0 states +has bound_step false diff --git a/openvaf/test_data/osdi/qam16.va b/openvaf/test_data/osdi/qam16.va new file mode 100644 index 00000000..45542fa7 --- /dev/null +++ b/openvaf/test_data/osdi/qam16.va @@ -0,0 +1,28 @@ +`include "constants.vams" +`include "disciplines.vams" + +// Verilog-AMS LRM 2.4 section 4.5.8, transition() Example 1 (QAM modulator). +// Exercises vectored input ports declared bare in the head and ranged in the body +// (`input [0:3] in; voltage [0:3] in;`), bus indexing `V(in[k])`, transition(), and +// $abstime. Kept as a compile+load regression for vector ports. +module qam16(in, out); + input [0:3] in; + output out; + voltage [0:3] in; + voltage out; + parameter real freq = 1.0 from (0:inf); + parameter real ampl = 1.0; + parameter real thresh = 2.5; + parameter real tdelay = 0 from [0:inf); + localparam real ttransit = 1/freq; + real x, y, phi; + integer row, col; + analog begin + row = 2 * (V(in[3]) > thresh) + (V(in[2]) > thresh); + col = 2 * (V(in[1]) > thresh) + (V(in[0]) > thresh); + x = transition(row - 1.5, tdelay, ttransit); + y = transition(col - 1.5, tdelay, ttransit); + phi = `M_TWO_PI * freq * $abstime; + V(out) <+ ampl * (x * cos(phi) + y * sin(phi)); + end +endmodule diff --git a/openvaf/test_data/osdi/vector_ports.snap b/openvaf/test_data/osdi/vector_ports.snap new file mode 100644 index 00000000..b1c4b323 --- /dev/null +++ b/openvaf/test_data/osdi/vector_ports.snap @@ -0,0 +1,16 @@ +param "$mfactor" +units = "", desc = "Multiplier (Verilog-A $mfactor)", flags = ParameterFlags(PARA_KIND_INST) + +5 terminals +node "in[0]" units = "V", runits = "A" +node "in[1]" units = "V", runits = "A" +node "in[2]" units = "V", runits = "A" +node "in[3]" units = "V", runits = "A" +node "out" units = "V", runits = "A" +jacobian (out, in[0]) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (out, in[1]) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (out, in[2]) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (out, in[3]) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (out, out) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_RESIST_CONST | JACOBIAN_ENTRY_REACT_CONST) +0 states +has bound_step false diff --git a/openvaf/test_data/osdi/vector_ports.va b/openvaf/test_data/osdi/vector_ports.va new file mode 100644 index 00000000..8f4fa327 --- /dev/null +++ b/openvaf/test_data/osdi/vector_ports.va @@ -0,0 +1,20 @@ +`include "disciplines.vams" + +// Exercises vectored/bus PORTS: `in` is declared as a bare name in the module head +// and given its [0:3] range only in the body (the LRM style), so the head name must +// reconcile with the body bus expansion into in[0]..in[3]. The output current sums +// the four bus bits with distinct weights, so the loaded residual/Jacobian prove +// each bit expanded and indexes to a distinct node. +// +// I(out) = V(out) - (1*V(in0) + 2*V(in1) + 3*V(in2) + 4*V(in3)) +// With V(out)=0 and V(in)= [1,1,1,1], residual(out) = -(1+2+3+4) = -10. +module vbus(in, out); + input [0:3] in; + output out; + electrical [0:3] in; + electrical out; + analog begin + I(out) <+ V(out) + - (1.0*V(in[0]) + 2.0*V(in[1]) + 3.0*V(in[2]) + 4.0*V(in[3])); + end +endmodule diff --git a/openvaf/tokens/src/parser/generated.rs b/openvaf/tokens/src/parser/generated.rs index 0a59b3ad..f230276e 100644 --- a/openvaf/tokens/src/parser/generated.rs +++ b/openvaf/tokens/src/parser/generated.rs @@ -72,6 +72,7 @@ pub enum SyntaxKind { FOR_KW, FROM_KW, FUNCTION_KW, + GENVAR_KW, IF_KW, INF_KW, INOUT_KW, @@ -89,6 +90,7 @@ pub enum SyntaxKind { INITIAL_STEP_KW, INITIAL_KW, FINAL_STEP_KW, + FINAL_KW, ALIASPARAM_KW, INT_NUMBER, STD_REAL_NUMBER, @@ -101,6 +103,9 @@ pub enum SyntaxKind { WHITESPACE, COMMENT, ANALOG_BEHAVIOUR, + PROCEDURAL_BLOCK, + INDEX_EXPR, + DIMENSION, ARG, ARG_LIST, ARRAY_EXPR, @@ -129,6 +134,8 @@ pub enum SyntaxKind { MODULE_DECL, MODULE_PORT, MODULE_PORTS, + PORT_REF, + GENVAR_DECL, NAME, NAME_REF, SYS_FUN, @@ -165,10 +172,11 @@ impl SyntaxKind { match self { ANALOG_KW | BEGIN_KW | BRANCH_KW | CASE_KW | DEFAULT_KW | DISABLE_KW | DISCIPLINE_KW | ELSE_KW | END_KW | ENDCASE_KW | ENDDISCIPLINE_KW | ENDFUNCTION_KW - | ENDMODULE_KW | ENDNATURE_KW | EXCLUDE_KW | FOR_KW | FROM_KW | FUNCTION_KW | IF_KW - | INF_KW | INOUT_KW | INPUT_KW | INTEGER_KW | MODULE_KW | NATURE_KW | OUTPUT_KW - | PARAMETER_KW | LOCALPARAM_KW | REAL_KW | STRING_KW | WHILE_KW | ROOT_KW - | INITIAL_STEP_KW | INITIAL_KW | FINAL_STEP_KW | ALIASPARAM_KW => true, + | ENDMODULE_KW | ENDNATURE_KW | EXCLUDE_KW | FOR_KW | FROM_KW | FUNCTION_KW + | GENVAR_KW | IF_KW | INF_KW | INOUT_KW | INPUT_KW | INTEGER_KW | MODULE_KW + | NATURE_KW | OUTPUT_KW | PARAMETER_KW | LOCALPARAM_KW | REAL_KW | STRING_KW + | WHILE_KW | ROOT_KW | INITIAL_STEP_KW | INITIAL_KW | FINAL_STEP_KW | FINAL_KW + | ALIASPARAM_KW => true, _ => false, } } @@ -208,6 +216,7 @@ impl SyntaxKind { "for" => FOR_KW, "from" => FROM_KW, "function" => FUNCTION_KW, + "genvar" => GENVAR_KW, "if" => IF_KW, "inf" => INF_KW, "inout" => INOUT_KW, @@ -225,6 +234,7 @@ impl SyntaxKind { "initial_step" => INITIAL_STEP_KW, "initial" => INITIAL_KW, "final_step" => FINAL_STEP_KW, + "final" => FINAL_KW, "aliasparam" => ALIASPARAM_KW, "reg" | "wreal" | "wire" | "uwire" | "wand" | "wor" | "ground" => NET_TYPE, _ => return None, @@ -332,6 +342,7 @@ impl std::fmt::Display for SyntaxKind { Self::FOR_KW => "'for'", Self::FROM_KW => "'from'", Self::FUNCTION_KW => "'function'", + Self::GENVAR_KW => "'genvar'", Self::IF_KW => "'if'", Self::INF_KW => "'inf'", Self::INOUT_KW => "'inout'", @@ -349,6 +360,7 @@ impl std::fmt::Display for SyntaxKind { Self::INITIAL_STEP_KW => "'initial_step'", Self::INITIAL_KW => "'initial'", Self::FINAL_STEP_KW => "'final_step'", + Self::FINAL_KW => "'final'", Self::ALIASPARAM_KW => "'aliasparam'", Self::INT_NUMBER => "integer", Self::STD_REAL_NUMBER | Self::SI_REAL_NUMBER => "real number", @@ -368,4 +380,4 @@ impl std::fmt::Display for SyntaxKind { } } #[macro_export] -macro_rules ! T { [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; ["_"] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<<] => { $ crate :: SyntaxKind :: ASHL } ; [>>>] => { $ crate :: SyntaxKind :: ASHR } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; ["(*"] => { $ crate :: SyntaxKind :: L_ATTR_PAREN } ; ["*)"] => { $ crate :: SyntaxKind :: R_ATTR_PAREN } ; ["'{"] => { $ crate :: SyntaxKind :: ARR_START } ; [<+] => { $ crate :: SyntaxKind :: CONTR } ; [**] => { $ crate :: SyntaxKind :: POW } ; [~^] => { $ crate :: SyntaxKind :: L_NXOR } ; [^~] => { $ crate :: SyntaxKind :: R_NXOR } ; [analog] => { $ crate :: SyntaxKind :: ANALOG_KW } ; [begin] => { $ crate :: SyntaxKind :: BEGIN_KW } ; [branch] => { $ crate :: SyntaxKind :: BRANCH_KW } ; [case] => { $ crate :: SyntaxKind :: CASE_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [disable] => { $ crate :: SyntaxKind :: DISABLE_KW } ; [discipline] => { $ crate :: SyntaxKind :: DISCIPLINE_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [end] => { $ crate :: SyntaxKind :: END_KW } ; [endcase] => { $ crate :: SyntaxKind :: ENDCASE_KW } ; [enddiscipline] => { $ crate :: SyntaxKind :: ENDDISCIPLINE_KW } ; [endfunction] => { $ crate :: SyntaxKind :: ENDFUNCTION_KW } ; [endmodule] => { $ crate :: SyntaxKind :: ENDMODULE_KW } ; [endnature] => { $ crate :: SyntaxKind :: ENDNATURE_KW } ; [exclude] => { $ crate :: SyntaxKind :: EXCLUDE_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [from] => { $ crate :: SyntaxKind :: FROM_KW } ; [function] => { $ crate :: SyntaxKind :: FUNCTION_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [inf] => { $ crate :: SyntaxKind :: INF_KW } ; [inout] => { $ crate :: SyntaxKind :: INOUT_KW } ; [input] => { $ crate :: SyntaxKind :: INPUT_KW } ; [integer] => { $ crate :: SyntaxKind :: INTEGER_KW } ; [module] => { $ crate :: SyntaxKind :: MODULE_KW } ; [nature] => { $ crate :: SyntaxKind :: NATURE_KW } ; [output] => { $ crate :: SyntaxKind :: OUTPUT_KW } ; [parameter] => { $ crate :: SyntaxKind :: PARAMETER_KW } ; [localparam] => { $ crate :: SyntaxKind :: LOCALPARAM_KW } ; [real] => { $ crate :: SyntaxKind :: REAL_KW } ; [string] => { $ crate :: SyntaxKind :: STRING_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [root] => { $ crate :: SyntaxKind :: ROOT_KW } ; [initial_step] => { $ crate :: SyntaxKind :: INITIAL_STEP_KW } ; [initial] => { $ crate :: SyntaxKind :: INITIAL_KW } ; [final_step] => { $ crate :: SyntaxKind :: FINAL_STEP_KW } ; [aliasparam] => { $ crate :: SyntaxKind :: ALIASPARAM_KW } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [net_type] => { $ crate :: SyntaxKind :: NET_TYPE } ; [sysfun] => { $ crate :: SyntaxKind :: SYSFUN } ; } +macro_rules ! T { [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; ["_"] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<<] => { $ crate :: SyntaxKind :: ASHL } ; [>>>] => { $ crate :: SyntaxKind :: ASHR } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; ["(*"] => { $ crate :: SyntaxKind :: L_ATTR_PAREN } ; ["*)"] => { $ crate :: SyntaxKind :: R_ATTR_PAREN } ; ["'{"] => { $ crate :: SyntaxKind :: ARR_START } ; [<+] => { $ crate :: SyntaxKind :: CONTR } ; [**] => { $ crate :: SyntaxKind :: POW } ; [~^] => { $ crate :: SyntaxKind :: L_NXOR } ; [^~] => { $ crate :: SyntaxKind :: R_NXOR } ; [analog] => { $ crate :: SyntaxKind :: ANALOG_KW } ; [begin] => { $ crate :: SyntaxKind :: BEGIN_KW } ; [branch] => { $ crate :: SyntaxKind :: BRANCH_KW } ; [case] => { $ crate :: SyntaxKind :: CASE_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [disable] => { $ crate :: SyntaxKind :: DISABLE_KW } ; [discipline] => { $ crate :: SyntaxKind :: DISCIPLINE_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [end] => { $ crate :: SyntaxKind :: END_KW } ; [endcase] => { $ crate :: SyntaxKind :: ENDCASE_KW } ; [enddiscipline] => { $ crate :: SyntaxKind :: ENDDISCIPLINE_KW } ; [endfunction] => { $ crate :: SyntaxKind :: ENDFUNCTION_KW } ; [endmodule] => { $ crate :: SyntaxKind :: ENDMODULE_KW } ; [endnature] => { $ crate :: SyntaxKind :: ENDNATURE_KW } ; [exclude] => { $ crate :: SyntaxKind :: EXCLUDE_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [from] => { $ crate :: SyntaxKind :: FROM_KW } ; [function] => { $ crate :: SyntaxKind :: FUNCTION_KW } ; [genvar] => { $ crate :: SyntaxKind :: GENVAR_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [inf] => { $ crate :: SyntaxKind :: INF_KW } ; [inout] => { $ crate :: SyntaxKind :: INOUT_KW } ; [input] => { $ crate :: SyntaxKind :: INPUT_KW } ; [integer] => { $ crate :: SyntaxKind :: INTEGER_KW } ; [module] => { $ crate :: SyntaxKind :: MODULE_KW } ; [nature] => { $ crate :: SyntaxKind :: NATURE_KW } ; [output] => { $ crate :: SyntaxKind :: OUTPUT_KW } ; [parameter] => { $ crate :: SyntaxKind :: PARAMETER_KW } ; [localparam] => { $ crate :: SyntaxKind :: LOCALPARAM_KW } ; [real] => { $ crate :: SyntaxKind :: REAL_KW } ; [string] => { $ crate :: SyntaxKind :: STRING_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [root] => { $ crate :: SyntaxKind :: ROOT_KW } ; [initial_step] => { $ crate :: SyntaxKind :: INITIAL_STEP_KW } ; [initial] => { $ crate :: SyntaxKind :: INITIAL_KW } ; [final_step] => { $ crate :: SyntaxKind :: FINAL_STEP_KW } ; [final] => { $ crate :: SyntaxKind :: FINAL_KW } ; [aliasparam] => { $ crate :: SyntaxKind :: ALIASPARAM_KW } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [net_type] => { $ crate :: SyntaxKind :: NET_TYPE } ; [sysfun] => { $ crate :: SyntaxKind :: SYSFUN } ; } diff --git a/sourcegen/src/ast/src.rs b/sourcegen/src/ast/src.rs index c1880e2c..5b233fa6 100644 --- a/sourcegen/src/ast/src.rs +++ b/sourcegen/src/ast/src.rs @@ -77,6 +77,7 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc { "for", "from", "function", + "genvar", "if", "inf", "inout", @@ -94,12 +95,16 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc { "initial_step", "initial", "final_step", + "final", "aliasparam", ], literals: &["INT_NUMBER", "STD_REAL_NUMBER", "SI_REAL_NUMBER", "STR_LIT"], tokens: &["ERROR", "IDENT", "SYSFUN", "NET_TYPE", "WHITESPACE", "COMMENT"], nodes: &[ "ANALOG_BEHAVIOUR", + "PROCEDURAL_BLOCK", + "INDEX_EXPR", + "DIMENSION", "ARG", "ARG_LIST", "ARRAY_EXPR", @@ -128,6 +133,8 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc { "MODULE_DECL", "MODULE_PORT", "MODULE_PORTS", + "PORT_REF", + "GENVAR_DECL", "NAME", "NAME_REF", "SYS_FUN", diff --git a/sourcegen/src/hir_builtins.rs b/sourcegen/src/hir_builtins.rs index 79e5637b..a7ab25db 100644 --- a/sourcegen/src/hir_builtins.rs +++ b/sourcegen/src/hir_builtins.rs @@ -25,7 +25,7 @@ const ANALOG_OPERATORS: [&str; 17] = [ "transition", ]; -const UNSUPPORTED: [&str; 50] = [ +const UNSUPPORTED: [&str; 48] = [ "simprobe", "analog_node_alias", "analog_port_alias", @@ -35,13 +35,11 @@ const UNSUPPORTED: [&str; 50] = [ "zi_np", "zi_zd", "zi_zp", - "laplace_nd", "laplace_np", "laplace_zd", "laplace_zp", "last_crossing", "slew", - "transition", "fclose", "fopen", "fdisplay",