Merge pull request #8 from muhammadjawadkhan/feat/virtual-interface

SV: virtual-interface vertical slice (Tier A #3)
This commit is contained in:
muhammadjawadkhan 2026-07-21 17:59:30 +05:00 committed by GitHub
commit 05d4c76593
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 1078 additions and 13 deletions

View File

@ -112,11 +112,11 @@ FF = cprop.o exposenodes.o nodangle.o synth.o synth2.o syn-rules.o
O = main.o async.o design_dump.o discipline.o dup_expr.o elaborate.o \
elab_expr.o elaborate_analog.o elab_lval.o elab_net.o \
elab_scope.o elab_sig.o elab_sig_analog.o elab_type.o \
elab_scope.o elab_sig.o elab_sig_analog.o elab_type.o elab_vif.o \
emit.o eval_attrib.o \
eval_tree.o expr_synth.o functor.o lexor.o lexor_keyword.o link_const.o \
load_module.o netlist.o netmisc.o nettypes.o net_analog.o net_assign.o \
net_design.o netclass.o netdarray.o netaarray.o \
net_design.o netclass.o netdarray.o netaarray.o netvif.o \
netenum.o netparray.o netqueue.o netscalar.o netstruct.o netvector.o \
net_event.o net_expr.o net_func.o \
net_func_eval.o net_link.o net_modulo.o \

View File

@ -83,6 +83,7 @@ class PWire : public PNamedItem {
const std::list<pform_range_t>& get_unpacked_idx() const { return unpacked_; }
void set_data_type(data_type_t*type);
data_type_t* get_data_type() const { return set_data_type_.get(); }
void set_discipline(ivl_discipline_t);
ivl_discipline_t get_discipline(void) const;

View File

@ -6,7 +6,7 @@ Each item should be a dedicated `feat/<name>` branch, with tests/examples and a
1. **Parameterized classes**`class C #(type T = int);` / `C#(byte)` — needed for `uvm_*#(T)`, `config_db`**partial** (defaults; see STATUS)
2. **Associative arrays**`int aa[string];`**in progress / partial** (string keys only; see [assoc-array.md](assoc-array.md))
3. **Virtual interfaces** + eventing on `vif.clk`
3. **Virtual interfaces** + eventing on `vif.clk`**partial** (see [virtual-interface.md](virtual-interface.md))
4. **Clocking blocks** — enough for `@(vif.cb)`
5. **`mailbox` / `semaphore` builtins** (or solid class equivalents with blocking put/get)
6. **Constraints + `randomize()` / `randomize() with`** — start unconstrained `rand`, then solver

View File

@ -15,7 +15,7 @@ Last updated: 2026-07-21
| [`examples/hello_uvm`](../examples/hello_uvm) | Smoke TB for the seeded library (`Makefile` included) |
| Parameterized classes | **Partial** on `feat/param-classes` (merged): ANSI `class C #(type T = int, parameter int W = 8);` parses into the class scope and elaborates with **defaults**. Explicit specializations `C#(byte)` / overrides not done yet. See [`examples/param_classes`](../examples/param_classes). |
| Associative arrays | **Partial** on `feat/assoc-array`: string-keyed `int aa[string]` / `int aa[*]` with size/num/exists/delete/foreach/copy. See [`docs/assoc-array.md`](assoc-array.md) and [`examples/assoc_array`](../examples/assoc_array). |
| Virtual interfaces | Missing |
| Virtual interfaces | **Partial** on `feat/virtual-interface`: `virtual interface T` as class property / TF arg; assign interface instance; member R/W; `@(posedge vif.clk)`. See [`docs/virtual-interface.md`](virtual-interface.md) and [`examples/virtual_interface`](../examples/virtual_interface). |
| Constraints / randomize | Missing |
| Covergroups / DPI | Missing |

66
docs/virtual-interface.md Normal file
View File

@ -0,0 +1,66 @@
# Virtual interfaces (Tier A #3)
Status: **partial** — enough for basic UVM-agent style connectivity.
## Supported in this slice
```systemverilog
interface bus_if;
logic clk;
logic [7:0] data;
endinterface
class driver;
virtual interface bus_if vif; // class property
function new(virtual interface bus_if i); // TF argument
vif = i; // assign interface instance
endfunction
task drive(logic [7:0] d);
@(posedge vif.clk); // edge wait via VI
vif.data = d; // member write
endtask
endclass
module top;
bus_if bif();
driver drv;
initial begin
drv = new(bif);
// ...
end
endmodule
```
Also: member reads (`v = vif.data`) and anyedge / negedge waits on VI members.
**Syntax note:** the type must be written as `virtual interface <name>`. Bare `virtual bus_if` is deferred (parser conflicts with `virtual class` / `virtual function`).
## Encoding
| Layer | Role |
|-------|------|
| Parse | `K_virtual_interface` + `virtual_interface_type_t` (class props / TF ports) |
| Netlist | `netvif_t` (`IVL_VT_CLASS` so object load/store reuse works) |
| Elab | `$ivl_vif_new` / `$ivl_vif_get` / `$ivl_vif_wait`; VI member lvals |
| Runtime | `vvp_vif` + `%new/vif`, `%vif/load/vec4`, `%vif/store/vec4`, `%vif/wait` |
A virtual interface is a handle to an interface instance: member access and waits go through the bound signals.
## Example
[`examples/virtual_interface/vif_basic.sv`](../examples/virtual_interface/vif_basic.sv) — prints `PASSED`.
```bash
./install/bin/iverilog -g2012 -o /tmp/vif_basic.vvp examples/virtual_interface/vif_basic.sv
./install/bin/vvp /tmp/vif_basic.vvp
```
## Deferred (do not claim)
- Bare `virtual bus_if` (without the `interface` keyword)
- Virtual interface arrays
- Modport-qualified virtual interfaces (type enforcement)
- Clocking blocks on VI (Tier A #4)
- Parameterized interfaces
See also [STATUS.md](STATUS.md) and [ROADMAP.md](ROADMAP.md).

View File

@ -212,8 +212,10 @@ NetENull* NetENull::dup_expr() const
NetEProperty* NetEProperty::dup_expr() const
{
ivl_assert(*this, 0);
return 0;
NetEProperty*tmp = new NetEProperty(net_, pidx_,
index_ ? index_->dup_expr() : 0);
tmp->set_line(*this);
return tmp;
}
NetEScope* NetEScope::dup_expr() const

View File

@ -39,6 +39,7 @@
# include "netqueue.h"
# include "netstruct.h"
# include "netscalar.h"
# include "netvif.h"
# include "util.h"
# include "ivl_assert.h"
# include "map_named_args.h"
@ -3104,11 +3105,39 @@ NetExpr* PEIdent::elaborate_expr_class_field_(Design*des, NetScope*scope,
<< " look for property " << comp << endl;
}
/* Nested path: property is a virtual interface, then member. */
if (sr.path_tail.size() > 1) {
cerr << get_fileline() << ": sorry: "
<< "Nested member path not yet supported for class properties."
<< endl;
return nullptr;
int pidx = class_type->property_idx_from_name(comp.name);
if (pidx < 0) {
cerr << get_fileline() << ": error: "
<< "Class " << class_type->get_name()
<< " has no property " << comp.name << "." << endl;
des->errors += 1;
return 0;
}
ivl_type_t ptype = class_type->get_prop_type(pidx);
const netvif_t*vif_type = dynamic_cast<const netvif_t*>(ptype);
if (!vif_type || sr.path_tail.size() != 2) {
cerr << get_fileline() << ": sorry: "
<< "Nested member path not yet supported for class properties."
<< endl;
des->errors += 1;
return nullptr;
}
pform_name_t::const_iterator it = sr.path_tail.begin();
++it;
const name_component_t&mcomp = *it;
int midx = vif_type->member_idx_from_name(mcomp.name);
if (midx < 0) {
cerr << get_fileline() << ": error: "
<< "Virtual interface has no member `"
<< mcomp.name << "'." << endl;
des->errors += 1;
return 0;
}
NetEProperty*prop = new NetEProperty(sr.net, pidx, nullptr);
prop->set_line(*this);
return elab_vif_member_get(*this, prop, vif_type, midx);
}
ivl_type_t par_type;
@ -5196,6 +5225,22 @@ NetExpr* PEIdent::elaborate_expr(Design*des, NetScope*scope,
symbol_search_results sr;
symbol_search(this, des, scope, path_, lexical_pos_, &sr);
/* Assigning an interface instance to a virtual interface. */
if (const netvif_t*vif_type = dynamic_cast<const netvif_t*>(ntype)) {
if (sr.is_scope() && sr.scope && sr.scope->is_interface()
&& sr.path_tail.empty()) {
return elab_vif_new_from_scope(des, *this, sr.scope, vif_type);
}
/* Also allow a relative child scope name that is an interface. */
if (!sr.net && path_.size() == 1) {
hname_t use_name(peek_tail_name(path_));
if (NetScope*nsc = scope->child(use_name)) {
if (nsc->is_interface())
return elab_vif_new_from_scope(des, *this, nsc, vif_type);
}
}
}
if (!sr.net) {
cerr << get_fileline() << ": error: Unable to bind variable `"
<< path_ << "' in `" << scope_path(scope) << "'" << endl;
@ -5217,6 +5262,25 @@ NetExpr* PEIdent::elaborate_expr(Design*des, NetScope*scope,
sr.path_tail);
} else if (dynamic_cast<const netclass_t*>(sr.type)) {
return elaborate_expr_class_field_(des, scope, sr, 0, flags);
} else if (const netvif_t*vif_type = dynamic_cast<const netvif_t*>(sr.type)) {
if (sr.path_tail.size() != 1) {
cerr << get_fileline() << ": error: "
<< "Invalid virtual interface member path." << endl;
des->errors += 1;
return 0;
}
const name_component_t&comp = sr.path_tail.front();
int midx = vif_type->member_idx_from_name(comp.name);
if (midx < 0) {
cerr << get_fileline() << ": error: "
<< "Virtual interface has no member `"
<< comp.name << "'." << endl;
des->errors += 1;
return 0;
}
NetESignal*base = new NetESignal(net);
base->set_line(*this);
return elab_vif_member_get(*this, base, vif_type, midx);
}
}
@ -5833,6 +5897,29 @@ NetExpr* PEIdent::elaborate_expr_(Design*des, NetScope*scope,
expr_wid, flags);
}
if (const netvif_t*vif_type = dynamic_cast<const netvif_t*>(sr.type)) {
if (!sr.path_tail.empty()) {
if (sr.path_tail.size() != 1) {
cerr << get_fileline() << ": error: "
<< "Invalid virtual interface member path." << endl;
des->errors += 1;
return 0;
}
const name_component_t&comp = sr.path_tail.front();
int midx = vif_type->member_idx_from_name(comp.name);
if (midx < 0) {
cerr << get_fileline() << ": error: "
<< "Virtual interface has no member `"
<< comp.name << "'." << endl;
des->errors += 1;
return 0;
}
NetESignal*base = new NetESignal(sr.net);
base->set_line(*this);
return elab_vif_member_get(*this, base, vif_type, midx);
}
}
if (sr.net->enumeration() && !sr.path_tail.empty()) {
const netenum_t*netenum = sr.net->enumeration();
if (debug_elaborate) {

View File

@ -31,6 +31,7 @@
# include "netparray.h"
# include "netvector.h"
# include "netenum.h"
# include "netvif.h"
# include "compiler.h"
# include <cstdlib>
# include <iostream>
@ -295,6 +296,31 @@ NetAssign_*PEIdent::elaborate_lval_var_(Design *des, NetScope *scope,
if (class_type && !tail_path.empty() && gn_system_verilog())
return elaborate_lval_net_class_member_(des, scope, class_type, reg, tail_path);
/* Virtual interface member write: vif.member = ... */
if (const netvif_t*vif_type = dynamic_cast<const netvif_t*>(data_type)) {
if (!tail_path.empty() && gn_system_verilog()) {
if (tail_path.size() != 1) {
cerr << get_fileline() << ": error: "
<< "Invalid virtual interface member path." << endl;
des->errors += 1;
return 0;
}
const name_component_t&comp = tail_path.front();
int midx = vif_type->member_idx_from_name(comp.name);
if (midx < 0) {
cerr << get_fileline() << ": error: "
<< "Virtual interface has no member `"
<< comp.name << "'." << endl;
des->errors += 1;
return 0;
}
ivl_type_t mtype = vif_type->get_member_type(static_cast<size_t>(midx));
NetAssign_*lv = new NetAssign_(reg);
lv->set_vif_member(static_cast<unsigned>(midx),
ivl_type_packed_width(mtype));
return lv;
}
}
// Past this point, we should have taken care of the cases
// where the name is a member/method of a struct/class.
@ -1218,6 +1244,32 @@ NetAssign_* PEIdent::elaborate_lval_net_class_member_(Design*des, NetScope*scope
if (canon_index)
lv->set_word(canon_index);
/* Virtual interface property: remaining path is a VI member. */
if (const netvif_t*vif_type = dynamic_cast<const netvif_t*>(ptype)) {
if (member_path.empty())
return lv;
if (member_path.size() != 1) {
cerr << get_fileline() << ": error: "
<< "Invalid virtual interface member path." << endl;
des->errors += 1;
return 0;
}
const name_component_t&mcomp = member_path.front();
member_path.pop_front();
int midx = vif_type->member_idx_from_name(mcomp.name);
if (midx < 0) {
cerr << get_fileline() << ": error: "
<< "Virtual interface has no member `"
<< mcomp.name << "'." << endl;
des->errors += 1;
return 0;
}
ivl_type_t member_type = vif_type->get_member_type(static_cast<size_t>(midx));
lv->set_vif_member(static_cast<unsigned>(midx),
ivl_type_packed_width(member_type));
return lv;
}
// If the current member is a class object, then get the
// type. We may wind up iterating, and need the proper
// class type.

View File

@ -19,11 +19,15 @@
# include "PExpr.h"
# include "PScope.h"
# include "PWire.h"
# include "Module.h"
# include "parse_api.h"
# include "pform_types.h"
# include "netlist.h"
# include "netclass.h"
# include "netdarray.h"
# include "netaarray.h"
# include "netvif.h"
# include "netenum.h"
# include "netqueue.h"
# include "netparray.h"
@ -131,6 +135,43 @@ ivl_type_t class_type_t::elaborate_type_raw(Design*des, NetScope*scope) const
return scope->find_class(des, name);
}
ivl_type_t virtual_interface_type_t::elaborate_type_raw(Design*des, NetScope*scope) const
{
map<perm_string,Module*>::const_iterator cur = pform_modules.find(name);
if (cur == pform_modules.end() || !cur->second->is_interface) {
cerr << get_fileline() << ": error: `" << name
<< "' is not an interface type." << endl;
des->errors += 1;
return 0;
}
if (modport) {
cerr << get_fileline() << ": warning: Modport-qualified virtual "
<< "interfaces (`virtual " << name << "." << modport
<< "') are accepted but modport rules are not enforced yet."
<< endl;
}
netvif_t*vif = new netvif_t(name);
Module*mod = cur->second;
for (map<perm_string,PWire*>::const_iterator wt = mod->wires.begin();
wt != mod->wires.end(); ++wt) {
PWire*pw = wt->second;
if (!pw)
continue;
// Skip non-net/variable declarations if any.
data_type_t*dt = pw->get_data_type();
ivl_type_t mt = 0;
if (dt)
mt = dt->elaborate_type(des, scope);
if (!mt)
mt = &netvector_t::scalar_logic;
vif->add_member(wt->first, mt);
}
return vif;
}
/*
* elaborate_type_raw for enumerations is actually mostly performed
* during scope elaboration so that the enumeration literals are

96
elab_vif.cc Normal file
View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2026 Icarus UVM track
*
* Helpers for SystemVerilog virtual-interface elaboration.
*/
# include "config.h"
# include "PExpr.h"
# include "netlist.h"
# include "netvif.h"
# include "netclass.h"
# include "netmisc.h"
# include "compiler.h"
# include "ivl_assert.h"
using namespace std;
/*
* Build `$ivl_vif_new(sig0, sig1, ...)` from a concrete interface scope.
*/
NetExpr* elab_vif_new_from_scope(Design*des, const LineInfo&loc,
NetScope*iface_scope, const netvif_t*vif_type)
{
if (!iface_scope || !iface_scope->is_interface() || !vif_type) {
cerr << loc.get_fileline() << ": error: "
<< "Virtual interface assignment requires an interface instance."
<< endl;
des->errors += 1;
return 0;
}
if (iface_scope->module_name() != vif_type->interface_name()) {
cerr << loc.get_fileline() << ": error: "
<< "Interface type mismatch: expected `"
<< vif_type->interface_name() << "', got `"
<< iface_scope->module_name() << "'." << endl;
des->errors += 1;
return 0;
}
size_t n = vif_type->get_members();
NetESFunc*fun = new NetESFunc("$ivl_vif_new",
static_cast<ivl_type_t>(vif_type),
static_cast<unsigned>(n));
fun->set_line(loc);
for (size_t idx = 0; idx < n; idx += 1) {
perm_string mname = vif_type->get_member_name(idx);
NetNet*sig = iface_scope->find_signal(mname);
if (!sig) {
cerr << loc.get_fileline() << ": error: "
<< "Interface `" << iface_scope->module_name()
<< "' has no member `" << mname
<< "' for virtual interface." << endl;
des->errors += 1;
delete fun;
return 0;
}
NetESignal*es = new NetESignal(sig);
es->set_line(loc);
fun->parm(idx, es);
}
return fun;
}
/*
* `$ivl_vif_get(vif_handle, member_idx)` reading a VI member.
*/
NetExpr* elab_vif_member_get(const LineInfo&loc, NetExpr*vif_base,
const netvif_t*vif_type, int member_idx)
{
ivl_assert(loc, vif_base && vif_type && member_idx >= 0);
ivl_type_t mtype = vif_type->get_member_type(static_cast<size_t>(member_idx));
NetESFunc*fun = new NetESFunc("$ivl_vif_get", mtype, 2);
fun->set_line(loc);
fun->parm(0, vif_base);
NetEConst*idx = new NetEConst(verinum(static_cast<uint64_t>(member_idx), 32));
idx->set_line(loc);
fun->parm(1, idx);
return fun;
}
NetSTask* elab_vif_wait_task(const LineInfo&loc, NetExpr*vif_base,
int edge_code, int member_idx)
{
vector<NetExpr*> parms(3);
parms[0] = vif_base;
parms[1] = new NetEConst(verinum(static_cast<uint64_t>(edge_code), 32));
parms[1]->set_line(loc);
parms[2] = new NetEConst(verinum(static_cast<uint64_t>(member_idx), 32));
parms[2]->set_line(loc);
NetSTask*task = new NetSTask("$ivl_vif_wait", IVL_SFUNC_AS_TASK_IGNORE, parms);
task->set_line(loc);
return task;
}

View File

@ -48,6 +48,7 @@
# include "netvector.h"
# include "netdarray.h"
# include "netaarray.h"
# include "netvif.h"
# include "netqueue.h"
# include "netparray.h"
# include "netscalar.h"
@ -3028,7 +3029,8 @@ NetProc* PAssign::elaborate(Design*des, NetScope*scope) const
/* If the l-value is a compound type of some sort, then use
the newer net_type form of the elaborate_rval_ method to
handle the new types. */
if (dynamic_cast<const netclass_t*> (lv_net_type)) {
if (dynamic_cast<const netclass_t*> (lv_net_type) ||
dynamic_cast<const netvif_t*> (lv_net_type)) {
ivl_assert(*this, lv->more==0);
rv = elaborate_rval_(des, scope, lv_net_type);
@ -5405,6 +5407,49 @@ NetProc* PEventStatement::elaborate_st(Design*des, NetScope*scope,
return 0;
}
/* Vertical-slice support: @(posedge vif.clk) where vif is a
virtual interface. Emit $ivl_vif_wait then the continuation. */
if (expr_.size() == 1 && dynamic_cast<PEIdent*>(expr_[0]->expr())) {
NetExpr*tmp = elab_and_eval(des, scope, expr_[0]->expr(), -1);
if (NetESFunc*sf = dynamic_cast<NetESFunc*>(tmp)) {
if (strcmp(sf->name(), "$ivl_vif_get") == 0 && sf->nparms() == 2) {
int edge_code = 2; /* anyedge */
switch (expr_[0]->type()) {
case PEEvent::POSEDGE: edge_code = 0; break;
case PEEvent::NEGEDGE: edge_code = 1; break;
case PEEvent::ANYEDGE: edge_code = 2; break;
case PEEvent::EDGE: edge_code = 2; break;
default: break;
}
NetExpr*idx_ex = sf->parm(1);
long midx = 0;
if (!eval_as_long(midx, idx_ex)) {
cerr << get_fileline() << ": error: "
<< "Virtual interface wait member index "
<< "must be constant." << endl;
des->errors += 1;
delete tmp;
return 0;
}
/* Dup the VI base — NetESFunc::parm(idx,0) deletes
the old expression, so we cannot steal in place. */
NetExpr*vif_base = sf->parm(0)->dup_expr();
delete tmp;
NetSTask*wait_task = elab_vif_wait_task(*this, vif_base,
edge_code,
static_cast<int>(midx));
if (!enet)
return wait_task;
NetBlock*blk = new NetBlock(NetBlock::SEQU, 0);
blk->set_line(*this);
blk->append(wait_task);
blk->append(enet);
return blk;
}
}
delete tmp;
}
/* Create a single NetEvent and NetEvWait. Then, create a
NetEvProbe for each conjunctive event in the event
list. The NetEvProbe objects all refer back to the NetEvent

View File

@ -0,0 +1,34 @@
// Virtual-interface vertical slice (UVM Tier A #3).
// Note: this slice requires the `interface` keyword in the type
// (`virtual interface bus_if`) to avoid parser conflicts with `virtual class`.
interface bus_if;
logic clk;
logic [7:0] data;
endinterface
class driver;
virtual interface bus_if vif;
function new(virtual interface bus_if i);
vif = i;
endfunction
task drive(logic [7:0] d);
@(posedge vif.clk);
vif.data = d;
endtask
endclass
module top;
bus_if bif();
driver drv;
initial begin
bif.clk = 0;
forever #5 bif.clk = ~bif.clk;
end
initial begin
drv = new(bif);
#12 drv.drive(8'hA5);
if (bif.data !== 8'hA5) $fatal(1, "FAILED");
$display("PASSED");
$finish;
end
endmodule

View File

@ -1561,6 +1561,7 @@ extern ivl_expr_t ivl_lval_idx(ivl_lval_t net);
extern ivl_expr_t ivl_lval_part_off(ivl_lval_t net);
extern ivl_select_type_t ivl_lval_sel_type(ivl_lval_t net);
extern int ivl_lval_property_idx(ivl_lval_t net);
extern int ivl_lval_vif_member_idx(ivl_lval_t net);
extern ivl_signal_t ivl_lval_sig(ivl_lval_t net);
extern ivl_lval_t ivl_lval_nest(ivl_lval_t net);

View File

@ -264,6 +264,10 @@ TU [munpf]
"'{" { return K_LP; }
"::" { return K_SCOPE_RES; }
/* Combine `virtual interface` into one token so LALR can distinguish
virtual-interface types from `virtual class` / `virtual function`. */
"virtual"{W}+"interface" { return K_virtual_interface; }
/* This is horrible. The Verilog systax uses "->" in a lot of places.
The trickiest is in constraints, where it is not an operator at all
but a constraint implication. This only turns up as a problem when

View File

@ -24,6 +24,7 @@
# include "netdarray.h"
# include "netparray.h"
# include "netenum.h"
# include "netvif.h"
# include "ivl_assert.h"
using namespace std;
@ -161,6 +162,12 @@ ivl_type_t NetAssign_::net_type() const
ntype = class_type->get_prop_type(member_idx_);
}
if (vif_member_idx_ >= 0) {
const netvif_t*vif_type = dynamic_cast<const netvif_t*>(ntype);
ivl_assert(*this, vif_type);
ntype = vif_type->get_member_type(static_cast<size_t>(vif_member_idx_));
}
if (word_) {
if (const netdarray_t *darray = dynamic_cast<const netdarray_t*>(ntype))
ntype = darray->element_type();
@ -206,6 +213,12 @@ void NetAssign_::set_property(const perm_string&mname, unsigned idx)
member_idx_ = idx;
}
void NetAssign_::set_vif_member(unsigned idx, unsigned wid)
{
vif_member_idx_ = static_cast<int>(idx);
lwid_ = wid;
}
/*
*/
void NetAssign_::turn_sig_to_wire_on_release()

View File

@ -2964,6 +2964,10 @@ class NetAssign_ {
void set_property(const perm_string&name, unsigned int idx);
inline int get_property_idx(void) const { return member_idx_; }
// Virtual-interface member select (after a VI handle lval).
void set_vif_member(unsigned int idx, unsigned wid);
inline int get_vif_member_idx(void) const { return vif_member_idx_; }
// Determine if the assigned object is signed or unsigned.
// This is used when determining the expression type for
// a compressed assignment statement.
@ -3017,6 +3021,8 @@ class NetAssign_ {
// member/property if signal is a class.
perm_string member_;
int member_idx_ = -1;
// Virtual-interface member when assigning through a VI handle.
int vif_member_idx_ = -1;
bool signed_;
bool turn_sig_to_wire_on_release_;

View File

@ -408,6 +408,18 @@ extern NetExpr* elaborate_rval_expr(Design *des, NetScope *scope,
bool need_const = false,
bool force_unsigned = false);
class netvif_t;
class NetSTask;
/* Virtual-interface helpers (elab_vif.cc). */
extern NetExpr* elab_vif_new_from_scope(Design*des, const LineInfo&loc,
NetScope*iface_scope,
const netvif_t*vif_type);
extern NetExpr* elab_vif_member_get(const LineInfo&loc, NetExpr*vif_base,
const netvif_t*vif_type, int member_idx);
extern NetSTask* elab_vif_wait_task(const LineInfo&loc, NetExpr*vif_base,
int edge_code, int member_idx);
extern bool evaluate_range(Design*des, NetScope*scope, const LineInfo*li,
const pform_range_t&range,
long&index_l, long&index_r);

66
netvif.cc Normal file
View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2026 Icarus UVM track
*/
# include "netvif.h"
# include <iostream>
using namespace std;
netvif_t::netvif_t(perm_string iface_name)
: iface_name_(iface_name)
{
}
netvif_t::~netvif_t()
{
}
ivl_variable_type_t netvif_t::base_type() const
{
return IVL_VT_CLASS;
}
void netvif_t::add_member(perm_string name, ivl_type_t type)
{
members_.push_back(name);
member_types_.push_back(type);
}
int netvif_t::member_idx_from_name(perm_string name) const
{
for (size_t idx = 0; idx < members_.size(); idx += 1) {
if (members_[idx] == name)
return static_cast<int>(idx);
}
return -1;
}
perm_string netvif_t::get_member_name(size_t idx) const
{
return members_[idx];
}
ivl_type_t netvif_t::get_member_type(size_t idx) const
{
return member_types_[idx];
}
ostream& netvif_t::debug_dump(ostream&o) const
{
o << "virtual " << iface_name_;
return o;
}
bool netvif_t::test_compatibility(ivl_type_t that) const
{
const netvif_t*oth = dynamic_cast<const netvif_t*>(that);
if (oth == 0)
return false;
return iface_name_ == oth->iface_name_;
}
bool netvif_t::test_equivalence(ivl_type_t that) const
{
return test_compatibility(that);
}

46
netvif.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef IVL_netvif_H
#define IVL_netvif_H
/*
* Copyright (c) 2026 Icarus UVM track
*
* Virtual interface type (Tier A #3 vertical slice).
*/
# include "nettypes.h"
# include "StringHeap.h"
# include <vector>
/*
* A virtual interface is a handle to a concrete interface instance.
* base_type() is IVL_VT_CLASS so existing object load/store plumbing
* ( .var/cobj, %load/obj, %store/obj, class properties ) can be reused.
* Distinguish with dynamic_cast<const netvif_t*>.
*/
class netvif_t : public ivl_type_s {
public:
explicit netvif_t(perm_string iface_name);
~netvif_t() override;
ivl_variable_type_t base_type() const override;
inline perm_string interface_name() const { return iface_name_; }
void add_member(perm_string name, ivl_type_t type);
int member_idx_from_name(perm_string name) const;
size_t get_members() const { return members_.size(); }
perm_string get_member_name(size_t idx) const;
ivl_type_t get_member_type(size_t idx) const;
std::ostream& debug_dump(std::ostream&) const override;
private:
bool test_compatibility(ivl_type_t that) const override;
bool test_equivalence(ivl_type_t that) const override;
perm_string iface_name_;
std::vector<perm_string> members_;
std::vector<ivl_type_t> member_types_;
};
#endif /* IVL_netvif_H */

43
parse.y
View File

@ -959,7 +959,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type,
%token K_randcase K_randsequence K_ref K_return K_sequence K_shortint
%token K_shortreal K_solve K_static K_string K_struct K_super
%token K_tagged K_this K_throughout K_timeprecision K_timeunit K_type
%token K_typedef K_union K_unique K_var K_virtual K_void K_wait_order
%token K_typedef K_union K_unique K_var K_virtual K_virtual_interface K_void K_wait_order
%token K_wildcard K_with K_within
/* The new tokens from 1800-2009. */
@ -1077,6 +1077,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type,
%type <data_type> implicit_type
%type <data_type> reg_prefixed_atomic_type simple_type_or_string let_formal_type
%type <data_type> packed_array_data_type atomic_type
%type <data_type> virtual_interface_type
%type <data_type> ps_type_identifier ps_type_identifier_dim
%type <data_type> simple_packed_type
@ -1340,6 +1341,9 @@ class_item /* IEEE1800-2005: A.1.8 */
| property_qualifier_opt list_of_variable_decl_assignments_with_type ';'
{ pform_class_property(@2, $1, $2.type, $2.decl_assignments); }
| property_qualifier_opt virtual_interface_type list_of_variable_decl_assignments ';'
{ pform_class_property(@2, $1, $2, $3); }
| K_const class_item_qualifier_opt list_of_variable_decl_assignments_with_type ';'
{ pform_class_property(@1, $2 | property_qualifier_t::make_const(), $3.type, $3.decl_assignments); }
@ -1756,6 +1760,26 @@ data_type /* IEEE1800-2005: A.2.2.1 */
| ps_type_identifier_dim { $$ = $1; }
;
/* Keep virtual_interface_type out of general data_type to avoid LALR
* conflicts with `virtual class` / `virtual function`. The lexer returns
* K_virtual_interface for the two-word keyword sequence. */
virtual_interface_type
: K_virtual_interface IDENTIFIER
{ virtual_interface_type_t*tmp = new virtual_interface_type_t(lex_strings.make($2));
FILE_NAME(tmp, @1);
delete[] $2;
$$ = tmp;
}
| K_virtual_interface IDENTIFIER '.' IDENTIFIER
{ virtual_interface_type_t*tmp = new virtual_interface_type_t(lex_strings.make($2),
lex_strings.make($4));
FILE_NAME(tmp, @1);
delete[] $2;
delete[] $4;
$$ = tmp;
}
;
/* Data type or nothing, but not implicit */
data_type_opt
: data_type { $$ = $1; }
@ -3095,6 +3119,23 @@ tf_port_item /* IEEE1800-2005: A.2.7 */
}
}
| port_direction_opt K_var_opt virtual_interface_type identifier_name initializer_opt
{ NetNet::PortType use_port_type = $1;
if (use_port_type == NetNet::PIMPLICIT)
use_port_type = NetNet::PINPUT;
struct pform_port_list port_list = make_port_list($3, $4, 0, nullptr, nullptr);
port_declaration_context.port_type = use_port_type;
port_declaration_context.data_type = $3;
std::vector<pform_tf_port_t>*tmp = pform_make_task_ports(@4, use_port_type, $3,
port_list.ports);
$$ = tmp;
if ($5) {
pform_requires_sv(@5, "Task/function default argument");
assert(tmp->size()==1);
tmp->front().defe = $5;
}
}
/* Rules to match error cases... */
| port_direction_opt K_var_opt data_type_or_implicit_plus_id_dim error

View File

@ -359,6 +359,14 @@ void class_type_t::pform_dump(ostream&out, unsigned indent) const
if (base_type) base_type->pform_dump(out, indent+4);
}
void virtual_interface_type_t::pform_dump(ostream&out, unsigned indent) const
{
out << setw(indent) << "" << "virtual interface " << name;
if (modport)
out << "." << modport;
out << endl;
}
void class_type_t::pform_dump_init(ostream&out, unsigned indent) const
{
for (vector<Statement*>::const_iterator cur = initialize.begin()

View File

@ -436,6 +436,21 @@ struct class_type_t : public data_type_t {
virtual SymbolType symbol_type() const override;
};
/*
* virtual [interface] interface_identifier
* Optional modport is parsed but deferred (not used in this slice).
*/
struct virtual_interface_type_t : public data_type_t {
explicit virtual_interface_type_t(perm_string n, perm_string mp = perm_string())
: name(n), modport(mp) { }
void pform_dump(std::ostream&out, unsigned indent) const override;
ivl_type_t elaborate_type_raw(Design*des, NetScope*scope) const override;
perm_string name;
perm_string modport;
};
ivl_type_t elaborate_array_type(Design *des, NetScope *scope,
const LineInfo &li, ivl_type_t base_type,
const std::list<pform_range_t> &dims);

View File

@ -1733,6 +1733,12 @@ extern "C" int ivl_lval_property_idx(ivl_lval_t net)
return net->property_idx;
}
extern "C" int ivl_lval_vif_member_idx(ivl_lval_t net)
{
assert(net);
return net->vif_member_idx;
}
extern "C" ivl_signal_t ivl_lval_sig(ivl_lval_t net)
{
assert(net);

View File

@ -205,6 +205,7 @@ bool dll_target::make_single_lval_(const LineInfo*li, struct ivl_lval_s*cur, con
}
cur->property_idx = asn->get_property_idx();
cur->vif_member_idx = asn->get_vif_member_idx();
return flag;
}

View File

@ -479,6 +479,7 @@ struct ivl_lval_s {
unsigned width_;
unsigned type_ : 8; /* values from ivl_lval_type_t */
int property_idx;
int vif_member_idx;
union {
ivl_signal_t sig;
ivl_lval_t nest; // type_ == IVL_LVAL_LVAL

View File

@ -794,6 +794,17 @@ int draw_eval_object(ivl_expr_t ex)
strstr(ivl_expr_name(ex), "_with") != 0) {
if (draw_queue_method_find_sfunc(ex) == 0) return 0;
}
if (strcmp(ivl_expr_name(ex), "$ivl_vif_new") == 0) {
unsigned n = ivl_expr_parms(ex);
fprintf(vvp_out, " %%new/vif %u", n);
for (unsigned idx = 0; idx < n; idx += 1) {
ivl_expr_t p = ivl_expr_parm(ex, idx);
assert(ivl_expr_type(p) == IVL_EX_SIGNAL);
fprintf(vvp_out, ", v%p_0", ivl_expr_signal(p));
}
fprintf(vvp_out, ";\n");
return 0;
}
if (ivl_expr_value(ex) == IVL_VT_QUEUE ||
ivl_expr_value(ex) == IVL_VT_DARRAY) {
if (eval_queue_method_unique(ex) == 0) return 0;

View File

@ -1114,6 +1114,20 @@ static void draw_sfunc_vec4(ivl_expr_t expr)
return;
}
if (strcmp(ivl_expr_name(expr), "$ivl_vif_get") == 0 && parm_count == 2) {
ivl_expr_t vif = ivl_expr_parm(expr, 0);
ivl_expr_t idx = ivl_expr_parm(expr, 1);
unsigned long midx;
assert(ivl_expr_type(idx) == IVL_EX_NUMBER);
assert(number_is_immediate(idx, IMM_WID, 0) && !number_is_unknown(idx));
midx = get_number_immediate64(idx);
draw_eval_object(vif);
fprintf(vvp_out, " %%vif/load/vec4 %lu, %u;\n",
midx, ivl_expr_width(expr));
fprintf(vvp_out, " %%pop/obj 1, 0;\n");
return;
}
if (strcmp(ivl_expr_name(expr), "$ivl_queue_method$pop_back")==0) {
draw_darray_pop(expr);
return;

View File

@ -1364,7 +1364,26 @@ static int show_stmt_assign_sig_cobject(ivl_statement_t net)
ivl_expr_t rval = ivl_stmt_rval(net);
unsigned lwid = ivl_lval_width(lval);
int prop_idx = ivl_lval_property_idx(lval);
int vif_midx = ivl_lval_vif_member_idx(lval);
/* Assign through a virtual-interface member (possibly nested
under a class property that holds the VI handle). */
if (vif_midx >= 0) {
if (prop_idx >= 0) {
ivl_type_t sig_type = draw_lval_expr(lval);
fprintf(vvp_out, " %%prop/obj %d, 0; VI handle property\n",
prop_idx);
fprintf(vvp_out, " %%pop/obj 1, 1;\n");
(void)sig_type;
} else {
ivl_signal_t sig = ivl_lval_sig(lval);
assert(sig);
fprintf(vvp_out, " %%load/obj v%p_0;\n", sig);
}
draw_eval_vec4(rval);
fprintf(vvp_out, " %%vif/store/vec4 %d, %u;\n", vif_midx, lwid);
return errors;
}
if (prop_idx >= 0) {
ivl_type_t sig_type = draw_lval_expr(lval);

View File

@ -1945,6 +1945,20 @@ static int show_system_task_call(ivl_statement_t net)
{
const char*stmt_name = ivl_stmt_name(net);
if (strcmp(stmt_name,"$ivl_vif_wait") == 0) {
ivl_expr_t vif = ivl_stmt_parm(net, 0);
ivl_expr_t edge_ex = ivl_stmt_parm(net, 1);
ivl_expr_t idx_ex = ivl_stmt_parm(net, 2);
unsigned long edge, midx;
assert(ivl_expr_type(edge_ex) == IVL_EX_NUMBER);
assert(ivl_expr_type(idx_ex) == IVL_EX_NUMBER);
edge = get_number_immediate64(edge_ex);
midx = get_number_immediate64(idx_ex);
draw_eval_object(vif);
fprintf(vvp_out, " %%vif/wait %lu, %lu;\n", edge, midx);
return 0;
}
if (strcmp(stmt_name,"$ivl_darray_method$delete") == 0)
return show_delete_method(net);

View File

@ -147,7 +147,7 @@ CORE_OBJ = lib_main.o \
substitute.o \
symbols.o ufunc.o codes.o vthread.o schedule.o \
statistics.o tables.o udp.o vvp_island.o vvp_net.o vvp_net_sig.o \
vvp_object.o vvp_cobject.o vvp_darray.o vvp_aarray.o event.o logic.o delay.o \
vvp_object.o vvp_cobject.o vvp_darray.o vvp_aarray.o vvp_vif.o event.o logic.o delay.o \
words.o island_tran.o
VPI_OBJ = vpi_modules.o vpi_bit.o vpi_callback.o vpi_cobject.o vpi_const.o vpi_darray.o \

View File

@ -171,6 +171,7 @@ extern bool of_NAND(vthread_t thr, vvp_code_t code);
extern bool of_NANDR(vthread_t thr, vvp_code_t code);
extern bool of_NEW_COBJ(vthread_t thr, vvp_code_t code);
extern bool of_NEW_DARRAY(vthread_t thr, vvp_code_t code);
extern bool of_NEW_VIF(vthread_t thr, vvp_code_t code);
extern bool of_NOOP(vthread_t thr, vvp_code_t code);
extern bool of_NOR(vthread_t thr, vvp_code_t code);
extern bool of_NORR(vthread_t thr, vvp_code_t code);
@ -332,6 +333,9 @@ extern bool of_TEST_NUL(vthread_t thr, vvp_code_t code);
extern bool of_TEST_NUL_A(vthread_t thr, vvp_code_t code);
extern bool of_TEST_NUL_OBJ(vthread_t thr, vvp_code_t code);
extern bool of_TEST_NUL_PROP(vthread_t thr, vvp_code_t code);
extern bool of_VIF_LOAD_VEC4(vthread_t thr, vvp_code_t code);
extern bool of_VIF_STORE_VEC4(vthread_t thr, vvp_code_t code);
extern bool of_VIF_WAIT(vthread_t thr, vvp_code_t code);
extern bool of_VPI_CALL(vthread_t thr, vvp_code_t code);
extern bool of_WAIT(vthread_t thr, vvp_code_t code);
extern bool of_WAIT_FORK(vthread_t thr, vvp_code_t code);
@ -362,6 +366,9 @@ struct vvp_code_s {
class __vpiHandle*handle;
__vpiScope*scope;
const char*text;
// Used by %new/vif: a compile-time array of the vvp_net_t
// pointers for the virtual interface's member signals.
vvp_net_t **net_list;
};
union {

View File

@ -389,6 +389,9 @@ static const struct opcode_table_s opcode_table[] = {
{ "%test_nul/a", of_TEST_NUL_A, 2,{OA_ARR_PTR, OA_BIT1, OA_NONE} },
{ "%test_nul/obj", of_TEST_NUL_OBJ, 0,{OA_NONE, OA_NONE, OA_NONE} },
{ "%test_nul/prop",of_TEST_NUL_PROP,2,{OA_NUMBER, OA_BIT1, OA_NONE} },
{ "%vif/load/vec4", of_VIF_LOAD_VEC4, 2,{OA_NUMBER, OA_BIT1, OA_NONE} },
{ "%vif/store/vec4",of_VIF_STORE_VEC4,2,{OA_NUMBER, OA_BIT1, OA_NONE} },
{ "%vif/wait", of_VIF_WAIT, 2,{OA_NUMBER, OA_BIT1, OA_NONE} },
{ "%wait", of_WAIT, 1, {OA_FUNC_PTR, OA_NONE, OA_NONE} },
{ "%wait/fork",of_WAIT_FORK,0,{OA_NONE, OA_NONE, OA_NONE} },
{ "%xnor", of_XNOR, 0, {OA_NONE, OA_NONE, OA_NONE} },

View File

@ -426,6 +426,20 @@ extern void compile_event(char*label, char*type,
unsigned argc, struct symb_s*argv);
extern void compile_named_event(char*label, char*type, bool local_flag=false);
/*
* %new/vif <n>, <sig0>, <sig1>, ...
*
* Compiles the virtual-interface constructor instruction. The <n>
* symbols name the member signal nets of the interface, in member
* order; they are resolved to vvp_net_t pointers at compile time and
* handed to of_NEW_VIF() (see vvp_vif.cc), which builds the vvp_vif
* object at run time. This needs its own compile function (rather
* than going through compile_code()/opcode_table) because, like
* %vpi_call, it takes a variable-length operand list.
*/
extern void compile_new_vif(char*label, uint64_t n,
unsigned argc, struct symb_s*argv);
/*
* A code statement is a label, an opcode and up to 3 operands. There

View File

@ -268,6 +268,7 @@ inline uint64_t strtouint64(const char*str, char**endptr, int base)
"%vpi_func/r" { return K_vpi_func_r; }
"%vpi_func/s" { return K_vpi_func_s; }
"%file_line" { return K_file_line; }
"%new/vif" { return K_new_vif; }
/* Handle the specialized variable access functions. */

View File

@ -105,6 +105,7 @@ static struct __vpiModPath*modpath_dst = 0;
%token K_VAR_S K_VAR_STR K_VAR_I K_VAR_R K_VAR_2S K_VAR_2U
%token K_vpi_call K_vpi_call_w K_vpi_call_i
%token K_vpi_func K_vpi_func_r K_vpi_func_s
%token K_new_vif
%token K_ivl_version K_ivl_delay_selection
%token K_vpi_module K_vpi_time_precision K_file_names K_file_line
%token K_PORT_INPUT K_PORT_OUTPUT K_PORT_INOUT K_PORT_MIXED K_PORT_NODIR
@ -646,6 +647,14 @@ statement
{ assert($5 == 0);
compile_file_line($1, $3, $4, 0); }
/* %new/vif takes a variable-length list of member-signal symbols
(one per interface member), so like %vpi_call it cannot go
through the generic label_opt T_INSTR operands_opt rule, which
only accepts a fixed, small number of operands. */
| label_opt K_new_vif T_NUMBER ',' symbols ';'
{ compile_new_vif($1, $3, $5.cnt, $5.vect); }
/* %vpi_call statements are instructions that have unusual operand
requirements so are handled by their own rules. The %vpi_func
statement is a variant of %vpi_call that includes a thread vector

View File

@ -28,6 +28,7 @@
# include "vvp_cobject.h"
# include "vvp_darray.h"
# include "vvp_aarray.h"
# include "vvp_vif.h"
# include "class_type.h"
#ifdef CHECK_WITH_VALGRIND
# include "vvp_cleanup.h"
@ -4277,6 +4278,38 @@ bool of_LOAD_VEC4(vthread_t thr, vvp_code_t cp)
return true;
}
/*
* %vif/load/vec4 <idx>, <wid>
*
* Read virtual-interface member <idx> from the vvp_vif object on top
* of the object stack, and push its current value onto the vec4
* stack. The object stack is left alone (this mirrors %prop/v); the
* caller issues its own %pop/obj once it is done with the handle.
*/
bool of_VIF_LOAD_VEC4(vthread_t thr, vvp_code_t cp)
{
unsigned idx = cp->number;
unsigned wid = cp->bit_idx[0];
vvp_object_t&top = thr->peek_object();
vvp_vif*vif = top.peek<vvp_vif>();
assert(vif);
vvp_net_t*net = vif->signal(idx);
assert(net);
vvp_signal_value*sig = dynamic_cast<vvp_signal_value*> (net->fil);
assert(sig);
vvp_vector4_t val;
sig->vec4_value(val);
assert(val.size() == wid);
thr->push_vec4(val);
return true;
}
/*
* %load/vec4a <arr>, <adrx>
*/
@ -4858,6 +4891,28 @@ bool of_NEW_DARRAY(vthread_t thr, vvp_code_t cp)
return true;
}
/*
* %new/vif <n>, <sig0>, <sig1>, ...
*
* Build a fresh vvp_vif object with one member per operand net (see
* compile_new_vif() in vvp_vif.cc for how those nets got resolved and
* attached to this instruction), and push the new object onto the
* object stack.
*/
bool of_NEW_VIF(vthread_t thr, vvp_code_t cp)
{
unsigned n = cp->bit_idx[0];
vvp_net_t**list = cp->net_list;
vvp_vif*vif = new vvp_vif(n);
for (unsigned idx = 0 ; idx < n ; idx += 1)
vif->set_member(idx, list[idx]);
thr->push_object(vvp_object_t(vif));
return true;
}
bool of_NOOP(vthread_t, vvp_code_t)
{
return true;
@ -7833,6 +7888,39 @@ bool of_STORE_VEC4(vthread_t thr, vvp_code_t cp)
return true;
}
/*
* %vif/store/vec4 <idx>, <wid>
*
* Pop a vector from the vec4 stack and drive it onto virtual
* interface member <idx> of the vvp_vif object on top of the object
* stack. Unlike %vif/load/vec4, this pops both the value and the
* object handle: there is nothing left to chain another vif access
* onto once the store has happened.
*/
bool of_VIF_STORE_VEC4(vthread_t thr, vvp_code_t cp)
{
unsigned idx = cp->number;
unsigned wid = cp->bit_idx[0];
vvp_vector4_t val = thr->pop_vec4();
assert(val.size() >= wid);
if (val.size() > wid)
val.resize(wid);
vvp_object_t obj;
thr->pop_object(obj);
vvp_vif*vif = obj.peek<vvp_vif>();
assert(vif);
vvp_net_t*net = vif->signal(idx);
assert(net);
vvp_net_ptr_t ptr(net, 0);
vvp_send_vec4(ptr, val, thr->wt_context);
return true;
}
/*
* %store/vec4a <var-label>, <addr>, <offset>
*/
@ -8091,6 +8179,51 @@ bool of_WAIT(vthread_t thr, vvp_code_t cp)
return false;
}
/*
* %vif/wait <edge>, <idx>
*
* Suspend the current thread until virtual interface member <idx> of
* the vvp_vif object on top of the object stack sees the requested
* <edge> (0=posedge, 1=negedge, 2=anyedge; 3 is also accepted and
* treated as anyedge, since vvp_vif only keeps a single "any change"
* detector per member). The member's edge-detector net is created
* (and wired to the member signal) the first time it is needed; see
* vvp_vif::edge_event(). The vif handle is popped off the object
* stack once the wait has been armed, mirroring the way %vif/store/vec4
* consumes the handle.
*/
bool of_VIF_WAIT(vthread_t thr, vvp_code_t cp)
{
unsigned edge = cp->number;
unsigned idx = cp->bit_idx[0];
vvp_object_t&top = thr->peek_object();
vvp_vif*vif = top.peek<vvp_vif>();
assert(vif);
vvp_vif::edge_t kind = vvp_vif::EDGE_ANYEDGE;
if (edge == static_cast<unsigned>(vvp_vif::EDGE_POSEDGE))
kind = vvp_vif::EDGE_POSEDGE;
else if (edge == static_cast<unsigned>(vvp_vif::EDGE_NEGEDGE))
kind = vvp_vif::EDGE_NEGEDGE;
vvp_net_t*event_net = vif->edge_event(idx, kind);
assert(! thr->i_am_in_function);
assert(! thr->waiting_for_event);
thr->waiting_for_event = 1;
waitable_hooks_s*ep = dynamic_cast<waitable_hooks_s*> (event_net->fun);
assert(ep);
thr->wait_next = ep->add_waiting_thread(thr);
vvp_object_t tmp;
thr->pop_object(tmp);
/* Return false to suspend this thread. */
return false;
}
/*
* Implement the %wait/fork (SystemVerilog) instruction by suspending
* the current thread until all the detached children have finished.

118
vvp/vvp_vif.cc Normal file
View File

@ -0,0 +1,118 @@
/*
* Copyright (c) 2026 Stephen Williams (steve@icarus.com)
*
* This source code is free software; you can redistribute it
* and/or modify it in source code form under the terms of the GNU
* General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
# include "vvp_vif.h"
# include "event.h"
# include "compile.h"
# include "codes.h"
# include <cassert>
using namespace std;
vvp_vif::vvp_vif(size_t nmembers)
: nmembers_(nmembers),
signals_(nmembers, static_cast<vvp_net_t*>(0)),
pos_events_(nmembers, static_cast<vvp_net_t*>(0)),
neg_events_(nmembers, static_cast<vvp_net_t*>(0)),
any_events_(nmembers, static_cast<vvp_net_t*>(0))
{
}
vvp_vif::~vvp_vif()
{
}
void vvp_vif::set_member(size_t idx, vvp_net_t*sig)
{
assert(idx < nmembers_);
signals_[idx] = sig;
}
vvp_net_t* vvp_vif::signal(size_t idx) const
{
assert(idx < nmembers_);
return signals_[idx];
}
vvp_net_t* vvp_vif::edge_event(size_t idx, edge_t edge)
{
assert(idx < nmembers_);
vvp_net_t*sig = signals_[idx];
assert(sig);
vvp_net_t*&cache = (edge==EDGE_POSEDGE) ? pos_events_[idx]
: (edge==EDGE_NEGEDGE)? neg_events_[idx]
: any_events_[idx];
if (cache != 0)
return cache;
vvp_net_t*ep = new vvp_net_t;
if (edge == EDGE_ANYEDGE)
ep->fun = new vvp_fun_anyedge_sa;
else
ep->fun = new vvp_fun_edge_sa(edge==EDGE_POSEDGE
? vvp_edge_posedge
: vvp_edge_negedge);
// Wire the member signal's output into the new edge
// detector's only input, so it sees every change of the
// signal from now on.
sig->link(vvp_net_ptr_t(ep, 0));
cache = ep;
return ep;
}
/*
* %new/vif <n>, <sig0>, <sig1>, ...
*
* This is compiled much like a .event statement: the operand list is
* a variable-length list of symbols, so it cannot go through the
* generic, fixed-arity %-opcode table (compile_code()/opcode_table).
* Instead it gets its own lexer/grammar rule (see the K_new_vif token
* in lexor.lex/parse.y), the same way %vpi_call does.
*
* The <n> member signals are resolved (immediately, or postponed if
* they are forward references) to their vvp_net_t, and that list is
* attached to the emitted %new/vif instruction. At run time,
* of_NEW_VIF() uses that list to build a fresh vvp_vif object with
* one member per net.
*/
void compile_new_vif(char*label, uint64_t n, unsigned argc, struct symb_s*argv)
{
if (label)
compile_codelabel(label);
assert(n == argc);
vvp_net_t**list = new vvp_net_t*[argc];
for (unsigned idx = 0 ; idx < argc ; idx += 1) {
list[idx] = 0;
functor_ref_lookup(&list[idx], argv[idx].text);
}
vvp_code_t code = codespace_allocate();
code->opcode = of_NEW_VIF;
code->net_list = list;
code->bit_idx[0] = argc;
free(argv);
}

78
vvp/vvp_vif.h Normal file
View File

@ -0,0 +1,78 @@
#ifndef IVL_vvp_vif_H
#define IVL_vvp_vif_H
/*
* Copyright (c) 2026 Stephen Williams (steve@icarus.com)
*
* This source code is free software; you can redistribute it
* and/or modify it in source code form under the terms of the GNU
* General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
# include "vvp_object.h"
# include "vvp_net.h"
# include <vector>
/*
* A vvp_vif is the runtime representation of a SystemVerilog virtual
* interface handle. Elaboration gives the virtual interface type the
* IVL_VT_CLASS base type, so a virtual interface variable is a
* .var/cobj-style variable and virtual interface handles are passed
* around with the same %load/obj, %store/obj, %test_nul/obj, etc.,
* opcodes used for class handles. vvp_vif is a vvp_object so that it
* plugs directly into that machinery.
*
* A vvp_vif remembers, for each member signal of the interface, the
* vvp_net_t that carries the member's value. It also lazily builds
* edge-detector nets (posedge/negedge/anyedge) wired to those member
* signals, so that %vif/wait can suspend a thread until a particular
* member of a particular virtual interface handle changes.
*
* The signal nets are shared, elaboration-time objects (there is one
* per member of the interface *instance*, not per virtual interface
* handle) so many vvp_vif objects (e.g. many handles that were all
* assigned from the same interface instance) end up pointing at the
* same underlying nets. The edge-detector nets, however, are owned by
* this vvp_vif and are created (at most once each) the first time
* they are needed.
*/
class vvp_vif : public vvp_object {
public:
enum edge_t { EDGE_POSEDGE = 0, EDGE_NEGEDGE = 1, EDGE_ANYEDGE = 2 };
explicit vvp_vif(size_t nmembers);
~vvp_vif() override;
size_t size() const { return nmembers_; }
// Bind member <idx> to the given signal net. This is called
// once, right after construction, by %new/vif.
void set_member(size_t idx, vvp_net_t*sig);
// The net that carries the value of member <idx>.
vvp_net_t* signal(size_t idx) const;
// The edge-detector net for member <idx>/<edge>, created (and
// wired to the member signal) the first time it is asked for.
vvp_net_t* edge_event(size_t idx, edge_t edge);
private:
size_t nmembers_;
std::vector<vvp_net_t*> signals_;
std::vector<vvp_net_t*> pos_events_;
std::vector<vvp_net_t*> neg_events_;
std::vector<vvp_net_t*> any_events_;
};
#endif /* IVL_vvp_vif_H */