Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions integration_tests/BUILTIN_PRIMITIVES/builtin_primitives.va
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
`include "constants.vams"
`include "disciplines.vams"

module builtin_primitives(p, n);
inout p, n;
electrical p, n, mid;

parameter real R1 = 1e3;
parameter real R2 = 2e3;
parameter real C1 = 1e-12;

// Use builtin primitives (lowered to contribution statements)
resistor #(.r(R1)) r1 (p, mid);
capacitor #(.c(C1)) c1 (p, mid);
resistor #(.r(R2)) r2 (mid, n);
endmodule
35 changes: 35 additions & 0 deletions integration_tests/MODULE_INST/module_inst.va
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Test module instantiation syntax support
// This tests hierarchical module instantiation with parameter overrides

`include "constants.vams"
`include "disciplines.vams"

// Simple resistor module that can be instantiated
module resistor(p, n);
inout p, n;
electrical p, n;
parameter real r = 1.0 from (0:inf);

analog begin
I(p, n) <+ V(p, n) / r;
end
endmodule

// Module that instantiates resistors
module module_inst(d, s);
inout d, s;
electrical d, s, mid;

parameter real rwire = 1e-3 from (0:inf);
parameter real rload = 1e3 from (0:inf);

// Module instantiation with parameter override
resistor #(.r(rwire)) r_wire (d, mid);
resistor #(.r(rload)) r_load (mid, s);

// Additional behavior
analog begin
// Add small leakage current
I(d, s) <+ V(d, s) * 1e-12;
end
endmodule
4 changes: 4 additions & 0 deletions openvaf/hir/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ pub struct CompilationOpts {
/// Allow analog operators (limexp, ddt, idt) in signal-dependent conditionals.
/// This is non-standard Verilog-A behavior required by some foundry models.
pub allow_analog_in_cond: bool,
/// Enable built-in primitive module support (resistor, capacitor, inductor).
/// When enabled, these primitives are lowered to equivalent contribution statements.
pub allow_builtin_primitives: bool,
}

#[salsa::database(BaseDatabase, InternDatabase, HirDefDatabase, HirTyDatabase)]
Expand Down Expand Up @@ -144,6 +147,7 @@ impl CompilationDB {

res.set_global_lint_overwrites(root_file, overwrites);
res.set_allow_analog_in_cond(opts.allow_analog_in_cond);
res.set_allow_builtin_primitives(opts.allow_builtin_primitives);
Ok(res)
}
}
Expand Down
2 changes: 1 addition & 1 deletion openvaf/hir/tests/data_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ fn ui_allow_analog_cond_test(file: &Path) -> Result {
&[],
&[],
&[],
&CompilationOpts { allow_analog_in_cond: true },
&CompilationOpts { allow_analog_in_cond: true, ..Default::default() },
)
.unwrap();
let actual = db.compilation_unit().test_diagnostics(&db);
Expand Down
16 changes: 15 additions & 1 deletion openvaf/hir_def/src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,21 @@ impl Body {
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()
// For non-initial analog blocks, also process primitive module instances
// and generate synthetic contribution statements (if enabled)
let primitive_stmts = if db.allow_builtin_primitives() {
let module_data = &tree[item_tree];
ctx.lower_primitive_instances(module_data, &tree)
} else {
Vec::new()
};
let behavior_stmts: Vec<StmtId> =
ast.analog_behaviour().map(|stmt| ctx.collect_stmt(stmt)).collect();

// Prepend primitive contributions to the analog block
let mut all_stmts = primitive_stmts;
all_stmts.extend(behavior_stmts);
all_stmts.into_boxed_slice()
};
}

Expand Down
212 changes: 210 additions & 2 deletions openvaf/hir_def/src/body/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,48 @@ use std::mem;

use basedb::lints::LintRegistry;
use basedb::{AstIdMap, ErasedAstId, LintAttrs};
use syntax::ast::{self, ArgListOwner, AttrIter, AttrsOwner, FunctionRef};
use syntax::name::AsName;
use syntax::ast::{self, ArgListOwner, AssignOp, AttrIter, AttrsOwner, BinaryOp, FunctionRef};
use syntax::name::{AsName, Name};
use syntax::AstPtr;

use crate::item_tree::{ItemTree, Module, ModuleInstItem, ModuleItem};

// use tracing::debug;
use super::{Body, BodySourceMap};
use crate::db::HirDefDB;

/// Built-in primitive modules that can be instantiated.
/// These are transformed into equivalent contribution statements during lowering.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuiltInPrimitive {
/// resistor #(.r(R)) name (hi, lo) -> I(hi,lo) <+ V(hi,lo) / R
Resistor,
/// capacitor #(.c(C)) name (hi, lo) -> I(hi,lo) <+ ddt(C * V(hi,lo))
Capacitor,
/// inductor #(.l(L)) name (hi, lo) -> V(hi,lo) <+ ddt(L * I(hi,lo))
Inductor,
}

impl BuiltInPrimitive {
/// Try to parse a primitive name from a string
pub fn from_name(name: &str) -> Option<Self> {
match name {
"resistor" => Some(Self::Resistor),
"capacitor" => Some(Self::Capacitor),
"inductor" => Some(Self::Inductor),
_ => None,
}
}

/// Get the expected parameter name for this primitive
pub fn param_name(&self) -> &'static str {
match self {
Self::Resistor => "r",
Self::Capacitor => "c",
Self::Inductor => "l",
}
}
}
use crate::expr::{CaseCond, Event, GlobalEvent};
use crate::nameres::DefMapSource;
use crate::{BlockLoc, Case, Expr, ExprId, Intern, Literal, Path, ScopeId, Stmt, StmtId};
Expand Down Expand Up @@ -271,6 +306,179 @@ impl LowerCtx<'_> {
self.source_map.stmt_map_back.insert(id, src);
id
}

// ============================================================================
// Primitive module instance lowering
// ============================================================================

/// Lower primitive module instances to synthetic contribution statements.
/// Called during module body lowering to transform instances like:
/// resistor #(.r(R)) r1 (a, b) -> I(a,b) <+ V(a,b) / R
pub fn lower_primitive_instances(&mut self, module: &Module, tree: &ItemTree) -> Vec<StmtId> {
let mut stmts = Vec::new();

for item in &module.items {
if let ModuleItem::ModuleInst(inst_id) = item {
let inst = &tree.data.module_insts[*inst_id];

if let Some(primitive) = BuiltInPrimitive::from_name(&inst.module_name) {
if let Some(stmt) = self.lower_primitive_instance(primitive, inst) {
stmts.push(stmt);
}
}
}
}
stmts
}

/// Lower a single primitive instance to a contribution statement
fn lower_primitive_instance(
&mut self,
primitive: BuiltInPrimitive,
inst: &ModuleInstItem,
) -> Option<StmtId> {
// Validate: must have exactly 2 ports
if inst.port_connections.len() != 2 {
return None;
}

// Get parameter value name from the parameter assignments
// param_assignments stores (Name, Name) pairs where the second is the value identifier
let param_name = primitive.param_name();
let param_value_name = inst
.param_assignments
.iter()
.find(|(name, _)| &**name == param_name)
.map(|(_, value_name)| value_name.clone())?;

// Create a path expression from the parameter value name
let param_expr = self.alloc_expr_desugared(Expr::Path {
path: Path::new_ident(param_value_name),
port: false,
});

let hi = &inst.port_connections[0];
let lo = &inst.port_connections[1];

match primitive {
BuiltInPrimitive::Resistor => self.create_resistor_contribution(hi, lo, param_expr),
BuiltInPrimitive::Capacitor => self.create_capacitor_contribution(hi, lo, param_expr),
BuiltInPrimitive::Inductor => self.create_inductor_contribution(hi, lo, param_expr),
}
}

/// Create: I(hi, lo) <+ V(hi, lo) / R
fn create_resistor_contribution(
&mut self,
hi: &Name,
lo: &Name,
r_expr: ExprId,
) -> Option<StmtId> {
// Create V(hi, lo) branch access
let v_access = self.create_branch_access(Name::new_inline("V"), hi, lo);

// Create V(hi,lo) / R
let div_expr = self.alloc_expr_desugared(Expr::BinaryOp {
lhs: v_access,
rhs: r_expr,
op: Some(BinaryOp::Division),
});

// Create I(hi, lo) branch access
let i_access = self.create_branch_access(Name::new_inline("I"), hi, lo);

// Create contribution statement: I(hi,lo) <+ V(hi,lo) / R
Some(self.alloc_stmt_desugared(Stmt::Assignment {
dst: i_access,
val: div_expr,
assignment_kind: AssignOp::Contribute,
}))
}

/// Create: I(hi, lo) <+ ddt(C * V(hi, lo))
fn create_capacitor_contribution(
&mut self,
hi: &Name,
lo: &Name,
c_expr: ExprId,
) -> Option<StmtId> {
// Create V(hi, lo) branch access
let v_access = self.create_branch_access(Name::new_inline("V"), hi, lo);

// Create C * V(hi,lo)
let mul_expr = self.alloc_expr_desugared(Expr::BinaryOp {
lhs: c_expr,
rhs: v_access,
op: Some(BinaryOp::Multiplication),
});

// Create ddt(C * V(hi,lo))
let ddt_expr = self.create_ddt_call(mul_expr);

// Create I(hi, lo) branch access
let i_access = self.create_branch_access(Name::new_inline("I"), hi, lo);

// Create contribution statement: I(hi,lo) <+ ddt(C * V(hi,lo))
Some(self.alloc_stmt_desugared(Stmt::Assignment {
dst: i_access,
val: ddt_expr,
assignment_kind: AssignOp::Contribute,
}))
}

/// Create: V(hi, lo) <+ ddt(L * I(hi, lo))
fn create_inductor_contribution(
&mut self,
hi: &Name,
lo: &Name,
l_expr: ExprId,
) -> Option<StmtId> {
// Create I(hi, lo) branch access
let i_access = self.create_branch_access(Name::new_inline("I"), hi, lo);

// Create L * I(hi,lo)
let mul_expr = self.alloc_expr_desugared(Expr::BinaryOp {
lhs: l_expr,
rhs: i_access,
op: Some(BinaryOp::Multiplication),
});

// Create ddt(L * I(hi,lo))
let ddt_expr = self.create_ddt_call(mul_expr);

// Create V(hi, lo) branch access
let v_access = self.create_branch_access(Name::new_inline("V"), hi, lo);

// Create contribution statement: V(hi,lo) <+ ddt(L * I(hi,lo))
Some(self.alloc_stmt_desugared(Stmt::Assignment {
dst: v_access,
val: ddt_expr,
assignment_kind: AssignOp::Contribute,
}))
}

/// Create a branch access expression like V(hi, lo) or I(hi, lo)
fn create_branch_access(&mut self, nature: Name, hi: &Name, lo: &Name) -> ExprId {
// Create path expressions for the nodes
let hi_path = self
.alloc_expr_desugared(Expr::Path { path: Path::new_ident(hi.clone()), port: false });
let lo_path = self
.alloc_expr_desugared(Expr::Path { path: Path::new_ident(lo.clone()), port: false });

// Create the function call: V(hi, lo) or I(hi, lo)
self.alloc_expr_desugared(Expr::Call {
fun: Some(Path::new_ident(nature)),
args: vec![hi_path, lo_path],
})
}

/// Create a ddt() function call
fn create_ddt_call(&mut self, arg: ExprId) -> ExprId {
self.alloc_expr_desugared(Expr::Call {
fun: Some(Path::new_ident(Name::new_inline("ddt"))),
args: vec![arg],
})
}
}

impl Literal {
Expand Down
4 changes: 4 additions & 0 deletions openvaf/hir_def/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ pub trait InternDB: BaseDB {

#[salsa::query_group(HirDefDatabase)]
pub trait HirDefDB: InternDB + Upcast<dyn BaseDB> {
/// Enable built-in primitive module support (resistor, capacitor, inductor).
#[salsa::input]
fn allow_builtin_primitives(&self) -> bool;

#[salsa::invoke(NDATable::nda_table_query)]
fn nda_table(&self, root_file: FileId) -> Arc<NDATable>;

Expand Down
Loading
Loading