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
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
24 changes: 23 additions & 1 deletion openvaf/hir_def/src/item_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ impl ItemTree {
ports,
branches,
functions,
module_insts,
} = &mut self.data;
modules.shrink_to_fit();
disciplines.shrink_to_fit();
Expand All @@ -87,6 +88,7 @@ impl ItemTree {
functions.shrink_to_fit();
nature_attrs.shrink_to_fit();
discipline_attrs.shrink_to_fit();
module_insts.shrink_to_fit();
}

pub fn block_scope(&self, block: AstId<BlockStmt>) -> &Block {
Expand All @@ -109,6 +111,7 @@ pub struct ItemTreeData {
pub ports: Arena<Port>,
pub branches: Arena<Branch>,
pub functions: Arena<Function>,
pub module_insts: Arena<ModuleInstItem>,
}

/// Trait implemented by all item nodes in the item tree.
Expand Down Expand Up @@ -230,6 +233,7 @@ item_tree_nodes! {
Function in functions -> ast::Function,
NatureAttr in nature_attrs -> ast::NatureAttr,
DisciplineAttr in discipline_attrs -> ast::DisciplineAttr,
ModuleInstItem in module_insts -> ast::ModuleInst,
}

#[derive(Debug, Eq, PartialEq, Clone)]
Expand All @@ -250,6 +254,7 @@ pub enum ModuleItem {
Branch(ItemTreeId<Branch>),
Node(LocalNodeId),
Function(ItemTreeId<Function>),
ModuleInst(ItemTreeId<ModuleInstItem>),
}

impl_from_typed! (
Expand All @@ -259,7 +264,8 @@ impl_from_typed! (
Variable(ItemTreeId<Var>),
Branch(ItemTreeId<Branch>),
Node(LocalNodeId),
Function(ItemTreeId<Function>) for ModuleItem
Function(ItemTreeId<Function>),
ModuleInst(ItemTreeId<ModuleInstItem>) for ModuleItem
);

#[derive(Debug, Eq, PartialEq, Clone)]
Expand Down Expand Up @@ -418,6 +424,22 @@ impl_from_typed! (
FunctionArg(LocalFunctionArgId) for FunctionItem
);

/// A module instantiation (hierarchical instance)
/// Example: `resistor #(.r(rwire)) r1 (d, de0);`
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ModuleInstItem {
/// Instance name (e.g., "r1")
pub name: Name,
/// Module type being instantiated (e.g., "resistor")
pub module_name: Name,
/// Parameter overrides (e.g., [("r", "rwire")])
pub param_assignments: Vec<(Name, Name)>,
/// Port connections (node names in order)
pub port_connections: Vec<Name>,
/// AST node reference
pub ast_id: AstId<ast::ModuleInst>,
}

#[derive(Debug, Eq, PartialEq, Clone)]
pub struct FunctionArg {
pub name: Name,
Expand Down
60 changes: 58 additions & 2 deletions openvaf/hir_def/src/item_tree/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use typed_index_collections::TiVec;

use super::{
Block, Branch, BranchKind, Discipline, DisciplineAttr, DisciplineAttrKind, Domain, Function,
FunctionArg, FunctionItem, ItemTree, ItemTreeId, Module, ModuleItem, Nature, NatureAttr,
NatureRef, NatureRefKind, Net, Node, Param, Port, RootItem, Var,
FunctionArg, FunctionItem, ItemTree, ItemTreeId, Module, ModuleInstItem, ModuleItem, Nature,
NatureAttr, NatureRef, NatureRefKind, Net, Node, Param, Port, RootItem, Var,
};
// use tracing::trace;
use crate::db::HirDefDB;
Expand Down Expand Up @@ -300,6 +300,7 @@ impl Ctx {
}
ast::ModuleItem::BranchDecl(branch) => self.lower_branch(branch, dst),
ast::ModuleItem::AliasParam(alias) => self.lower_alias_param(alias, dst),
ast::ModuleItem::ModuleInst(inst) => self.lower_module_inst(inst, dst),
};
}
}
Expand Down Expand Up @@ -610,4 +611,59 @@ impl Ctx {
dst.push(param.into())
}
}

fn lower_module_inst(&mut self, inst: ast::ModuleInst, dst: &mut Vec<ModuleItem>) {
let inst_name = match inst.inst_name() {
Some(name) => name.as_name(),
None => return,
};
let module_name = match inst.module_name() {
Some(name) => name.as_name(),
None => return,
};

// Extract parameter assignments: .param(value) -> (param_name, value_expr_as_name)
let param_assignments = inst
.param_assignments()
.map(|pas| {
pas.param_assignments()
.filter_map(|pa| {
let param_name = pa.param()?.as_name();
// Try to extract the value as a simple name/identifier
// For complex expressions, we just skip for now
let value_name = pa.value().and_then(|e| e.as_ident())?;
Some((param_name, value_name))
})
.collect()
})
.unwrap_or_default();

// Extract port connections - for positional connections, extract the node name
let port_connections = inst
.port_connections()
.map(|pcs| {
pcs.port_connections()
.filter_map(|pc| {
// For named connections: .port(expr) - use the connection expr
// For positional connections: just expr - use the expr
let expr =
if pc.dot_token().is_some() { pc.connection() } else { pc.expr() };
expr.and_then(|e| e.as_ident())
})
.collect()
})
.unwrap_or_default();

let ast_id = self.source_ast_id_map.ast_id(&inst);

let module_inst = ModuleInstItem {
name: inst_name,
module_name,
param_assignments,
port_connections,
ast_id,
};
let id = self.tree.data.module_insts.push_and_get_key(module_inst);
dst.push(id.into());
}
}
11 changes: 11 additions & 0 deletions openvaf/hir_def/src/item_tree/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ impl<'a> Printer<'a> {
let param = &self.tree[param];
wln!(self, "aliasparam {} = {:?}", param.name, param.src);
}
ModuleItem::ModuleInst(inst) => {
let inst = &self.tree[inst];
wln!(
self,
"module_inst {} {} #({:?}) ({:?})",
inst.module_name,
inst.name,
inst.param_assignments,
inst.port_connections
);
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions openvaf/hir_def/src/nameres/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ impl DefCollector<'_> {
ModuleItem::AliasParameter(id) => {
self.insert_item_decl(scope, self.tree[id].name.clone(), id)
}
ModuleItem::ModuleInst(_) => {
// Module instances are recorded but don't participate in name resolution
// They will be processed later during elaboration/flattening
}
}
}
}
Expand Down
96 changes: 95 additions & 1 deletion openvaf/parser/src/grammar/items/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,15 @@ fn module_items(p: &mut Parser) {
net_decl::<true>(p, m);
}
IDENT => {
net_decl::<false>(p, m);
// Distinguish between net declaration and module instantiation:
// Net declaration: `discipline name, name2;` - IDENT followed by IDENT, comma, or semicolon
// Module instantiation: `module_name #(params) inst_name (ports);` or `module_name inst_name (ports);`
// Key distinction: module inst has IDENT followed by #( or IDENT followed by IDENT then (
if p.nth(1) == POUND || (p.nth(1) == IDENT && p.nth(2) == L_PAREN) {
module_inst(p, m);
} else {
net_decl::<false>(p, m);
}
}
PARAMETER_KW | LOCALPARAM_KW => {
parameter_decl(p, m);
Expand Down Expand Up @@ -232,3 +240,89 @@ fn branch_decl(p: &mut Parser, m: Marker) {
p.eat(T![;]);
m.complete(p, BRANCH_DECL);
}

/// Parse module instantiation: `module_name #(.param(value), ...) instance_name (port1, port2, ...);`
/// Example: `resistor #(.r(rwire)) r1 (d, de0);`
fn module_inst(p: &mut Parser, m: Marker) {
// Module name (the type being instantiated)
name_ref_r(p, TokenSet::new(&[POUND, IDENT]));

// Optional parameter assignments: #(.param(value), ...)
if p.at(POUND) {
param_assignments(p);
}

// Instance name
name_r(p, TokenSet::new(&[T!['('], T![;]]));

// Port connections: (port1, port2, ...) or (.port(connection), ...)
if p.at(T!['(']) {
port_connections(p);
} else {
p.error(p.unexpected_token_msg(T!['(']));
}

p.eat(T![;]);
m.complete(p, MODULE_INST);
}

/// Parse parameter assignments: #(.param(value), ...)
fn param_assignments(p: &mut Parser) {
let m = p.start();
p.bump(POUND);
p.expect(T!['(']);

if !p.at(T![')']) {
param_assignment(p);
while p.eat(T![,]) {
param_assignment(p);
}
}

p.expect(T![')']);
m.complete(p, PARAM_ASSIGNMENTS);
}

/// Parse a single parameter assignment: .param(value)
fn param_assignment(p: &mut Parser) {
let m = p.start();
p.expect(T![.]);
name_r(p, TokenSet::new(&[T!['(']]));
p.expect(T!['(']);
expr(p);
p.expect(T![')']);
m.complete(p, PARAM_ASSIGNMENT);
}

/// Parse port connections: (expr, expr, ...) or (.port(expr), ...)
fn port_connections(p: &mut Parser) {
let m = p.start();
p.bump(T!['(']);

if !p.at(T![')']) {
port_connection(p);
while p.eat(T![,]) {
port_connection(p);
}
}

p.expect(T![')']);
m.complete(p, PORT_CONNECTIONS);
}

/// Parse a single port connection: either positional (expr) or named (.port(expr))
fn port_connection(p: &mut Parser) {
let m = p.start();
if p.at(T![.]) {
// Named port connection: .port(expr)
p.bump(T![.]);
name_r(p, TokenSet::new(&[T!['(']]));
p.expect(T!['(']);
expr(p);
p.expect(T![')']);
} else {
// Positional port connection: just an expression (usually a name)
expr(p);
}
m.complete(p, PORT_CONNECTION);
}
Loading
Loading