From 207f9a9b09defe0c8c28dede04c5d0453a94556f Mon Sep 17 00:00:00 2001 From: mjoekhan Date: Tue, 21 Jul 2026 17:58:35 +0500 Subject: [PATCH] SV: add virtual-interface vertical slice for UVM agents Support virtual interface class properties/TF args, instance bind, member R/W, and @(posedge vif.clk) so basic driver connectivity works. --- Makefile.in | 4 +- PWire.h | 1 + docs/ROADMAP.md | 2 +- docs/STATUS.md | 2 +- docs/virtual-interface.md | 66 ++++++++++++ dup_expr.cc | 6 +- elab_expr.cc | 95 ++++++++++++++++- elab_lval.cc | 52 +++++++++ elab_type.cc | 41 ++++++++ elab_vif.cc | 96 +++++++++++++++++ elaborate.cc | 47 ++++++++- examples/virtual_interface/vif_basic.sv | 34 ++++++ ivl_target.h | 1 + lexor.lex | 4 + net_assign.cc | 13 +++ netlist.h | 6 ++ netmisc.h | 12 +++ netvif.cc | 66 ++++++++++++ netvif.h | 46 ++++++++ parse.y | 43 +++++++- pform_dump.cc | 8 ++ pform_types.h | 15 +++ t-dll-api.cc | 6 ++ t-dll-proc.cc | 1 + t-dll.h | 1 + tgt-vvp/eval_object.c | 11 ++ tgt-vvp/eval_vec4.c | 14 +++ tgt-vvp/stmt_assign.c | 19 ++++ tgt-vvp/vvp_process.c | 14 +++ vvp/Makefile.in | 2 +- vvp/codes.h | 7 ++ vvp/compile.cc | 3 + vvp/compile.h | 14 +++ vvp/lexor.lex | 1 + vvp/parse.y | 9 ++ vvp/vthread.cc | 133 ++++++++++++++++++++++++ vvp/vvp_vif.cc | 118 +++++++++++++++++++++ vvp/vvp_vif.h | 78 ++++++++++++++ 38 files changed, 1078 insertions(+), 13 deletions(-) create mode 100644 docs/virtual-interface.md create mode 100644 elab_vif.cc create mode 100644 examples/virtual_interface/vif_basic.sv create mode 100644 netvif.cc create mode 100644 netvif.h create mode 100644 vvp/vvp_vif.cc create mode 100644 vvp/vvp_vif.h diff --git a/Makefile.in b/Makefile.in index d50073c72..c2befb0bc 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 \ diff --git a/PWire.h b/PWire.h index fa3e5723d..0fbf32ddc 100644 --- a/PWire.h +++ b/PWire.h @@ -83,6 +83,7 @@ class PWire : public PNamedItem { const std::list& 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; diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 3d4b041c5..3fc24f586 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -6,7 +6,7 @@ Each item should be a dedicated `feat/` 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 diff --git a/docs/STATUS.md b/docs/STATUS.md index 6eeb8c995..3c4fc6a50 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -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 | diff --git a/docs/virtual-interface.md b/docs/virtual-interface.md new file mode 100644 index 000000000..18b3d35f2 --- /dev/null +++ b/docs/virtual-interface.md @@ -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 `. 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). diff --git a/dup_expr.cc b/dup_expr.cc index 5f9ae0ba3..2780716a6 100644 --- a/dup_expr.cc +++ b/dup_expr.cc @@ -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 diff --git a/elab_expr.cc b/elab_expr.cc index 267297ca1..d5eea8d8a 100644 --- a/elab_expr.cc +++ b/elab_expr.cc @@ -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(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(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(sr.type)) { return elaborate_expr_class_field_(des, scope, sr, 0, flags); + } else if (const netvif_t*vif_type = dynamic_cast(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(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) { diff --git a/elab_lval.cc b/elab_lval.cc index bd46d3d28..08f35d973 100644 --- a/elab_lval.cc +++ b/elab_lval.cc @@ -31,6 +31,7 @@ # include "netparray.h" # include "netvector.h" # include "netenum.h" +# include "netvif.h" # include "compiler.h" # include # include @@ -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(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(midx)); + NetAssign_*lv = new NetAssign_(reg); + lv->set_vif_member(static_cast(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(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(midx)); + lv->set_vif_member(static_cast(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. diff --git a/elab_type.cc b/elab_type.cc index ad44c2899..ef071907a 100644 --- a/elab_type.cc +++ b/elab_type.cc @@ -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::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::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 diff --git a/elab_vif.cc b/elab_vif.cc new file mode 100644 index 000000000..75d3bd5b5 --- /dev/null +++ b/elab_vif.cc @@ -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(vif_type), + static_cast(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(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(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 parms(3); + parms[0] = vif_base; + parms[1] = new NetEConst(verinum(static_cast(edge_code), 32)); + parms[1]->set_line(loc); + parms[2] = new NetEConst(verinum(static_cast(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; +} diff --git a/elaborate.cc b/elaborate.cc index 4722b8b80..3ae2e13ff 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -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 (lv_net_type)) { + if (dynamic_cast (lv_net_type) || + dynamic_cast (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(expr_[0]->expr())) { + NetExpr*tmp = elab_and_eval(des, scope, expr_[0]->expr(), -1); + if (NetESFunc*sf = dynamic_cast(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(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 diff --git a/examples/virtual_interface/vif_basic.sv b/examples/virtual_interface/vif_basic.sv new file mode 100644 index 000000000..13c49d68d --- /dev/null +++ b/examples/virtual_interface/vif_basic.sv @@ -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 diff --git a/ivl_target.h b/ivl_target.h index 7221aaed6..7c0b35fd2 100644 --- a/ivl_target.h +++ b/ivl_target.h @@ -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); diff --git a/lexor.lex b/lexor.lex index e55675919..017dac31b 100644 --- a/lexor.lex +++ b/lexor.lex @@ -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 diff --git a/net_assign.cc b/net_assign.cc index 35b6b6ae6..682c4f900 100644 --- a/net_assign.cc +++ b/net_assign.cc @@ -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(ntype); + ivl_assert(*this, vif_type); + ntype = vif_type->get_member_type(static_cast(vif_member_idx_)); + } + if (word_) { if (const netdarray_t *darray = dynamic_cast(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(idx); + lwid_ = wid; +} + /* */ void NetAssign_::turn_sig_to_wire_on_release() diff --git a/netlist.h b/netlist.h index 6d83c8d2f..f876794bc 100644 --- a/netlist.h +++ b/netlist.h @@ -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_; diff --git a/netmisc.h b/netmisc.h index ff9edc1ec..e8eed8b12 100644 --- a/netmisc.h +++ b/netmisc.h @@ -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); diff --git a/netvif.cc b/netvif.cc new file mode 100644 index 000000000..77263a0c6 --- /dev/null +++ b/netvif.cc @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2026 Icarus UVM track + */ + +# include "netvif.h" +# include + +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(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(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); +} diff --git a/netvif.h b/netvif.h new file mode 100644 index 000000000..d80e0c47f --- /dev/null +++ b/netvif.h @@ -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 + +/* + * 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. + */ +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 members_; + std::vector member_types_; +}; + +#endif /* IVL_netvif_H */ diff --git a/parse.y b/parse.y index 7c5ed3297..5d0915747 100644 --- a/parse.y +++ b/parse.y @@ -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 implicit_type %type reg_prefixed_atomic_type simple_type_or_string let_formal_type %type packed_array_data_type atomic_type +%type virtual_interface_type %type ps_type_identifier ps_type_identifier_dim %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*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 diff --git a/pform_dump.cc b/pform_dump.cc index 9bc4ad1ff..d96a1d1f9 100644 --- a/pform_dump.cc +++ b/pform_dump.cc @@ -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::const_iterator cur = initialize.begin() diff --git a/pform_types.h b/pform_types.h index 554dd4dca..ae3451590 100644 --- a/pform_types.h +++ b/pform_types.h @@ -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 &dims); diff --git a/t-dll-api.cc b/t-dll-api.cc index 4e586bb0d..2ea585ee8 100644 --- a/t-dll-api.cc +++ b/t-dll-api.cc @@ -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); diff --git a/t-dll-proc.cc b/t-dll-proc.cc index 6bfe77ff9..f7965c002 100644 --- a/t-dll-proc.cc +++ b/t-dll-proc.cc @@ -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; } diff --git a/t-dll.h b/t-dll.h index 44d5c3724..1b8bd90f2 100644 --- a/t-dll.h +++ b/t-dll.h @@ -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 diff --git a/tgt-vvp/eval_object.c b/tgt-vvp/eval_object.c index abf21480b..98e1cd5d4 100644 --- a/tgt-vvp/eval_object.c +++ b/tgt-vvp/eval_object.c @@ -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; diff --git a/tgt-vvp/eval_vec4.c b/tgt-vvp/eval_vec4.c index d21a19cd9..93784bff9 100644 --- a/tgt-vvp/eval_vec4.c +++ b/tgt-vvp/eval_vec4.c @@ -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; diff --git a/tgt-vvp/stmt_assign.c b/tgt-vvp/stmt_assign.c index 1830b1560..da44a53d8 100644 --- a/tgt-vvp/stmt_assign.c +++ b/tgt-vvp/stmt_assign.c @@ -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); diff --git a/tgt-vvp/vvp_process.c b/tgt-vvp/vvp_process.c index 0fe946ade..b9acef727 100644 --- a/tgt-vvp/vvp_process.c +++ b/tgt-vvp/vvp_process.c @@ -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); diff --git a/vvp/Makefile.in b/vvp/Makefile.in index cf45e0a7b..5049e0f23 100644 --- a/vvp/Makefile.in +++ b/vvp/Makefile.in @@ -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 \ diff --git a/vvp/codes.h b/vvp/codes.h index a08d02659..1a9e33d12 100644 --- a/vvp/codes.h +++ b/vvp/codes.h @@ -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 { diff --git a/vvp/compile.cc b/vvp/compile.cc index 8592cae6c..c52aac891 100644 --- a/vvp/compile.cc +++ b/vvp/compile.cc @@ -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} }, diff --git a/vvp/compile.h b/vvp/compile.h index 2ed86634c..b73872260 100644 --- a/vvp/compile.h +++ b/vvp/compile.h @@ -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 , , , ... + * + * Compiles the virtual-interface constructor instruction. The + * 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 diff --git a/vvp/lexor.lex b/vvp/lexor.lex index 6ee8fcc12..06b83f22b 100644 --- a/vvp/lexor.lex +++ b/vvp/lexor.lex @@ -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. */ diff --git a/vvp/parse.y b/vvp/parse.y index 2372151e2..5c7998080 100644 --- a/vvp/parse.y +++ b/vvp/parse.y @@ -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 diff --git a/vvp/vthread.cc b/vvp/vthread.cc index 7a3ad62e1..1248db3ea 100644 --- a/vvp/vthread.cc +++ b/vvp/vthread.cc @@ -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 , + * + * Read virtual-interface member 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(); + assert(vif); + + vvp_net_t*net = vif->signal(idx); + assert(net); + + vvp_signal_value*sig = dynamic_cast (net->fil); + assert(sig); + + vvp_vector4_t val; + sig->vec4_value(val); + assert(val.size() == wid); + + thr->push_vec4(val); + + return true; +} + /* * %load/vec4a , */ @@ -4858,6 +4891,28 @@ bool of_NEW_DARRAY(vthread_t thr, vvp_code_t cp) return true; } +/* + * %new/vif , , , ... + * + * 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 , + * + * Pop a vector from the vec4 stack and drive it onto virtual + * interface member 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(); + 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 , , */ @@ -8091,6 +8179,51 @@ bool of_WAIT(vthread_t thr, vvp_code_t cp) return false; } +/* + * %vif/wait , + * + * Suspend the current thread until virtual interface member of + * the vvp_vif object on top of the object stack sees the requested + * (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(); + assert(vif); + + vvp_vif::edge_t kind = vvp_vif::EDGE_ANYEDGE; + if (edge == static_cast(vvp_vif::EDGE_POSEDGE)) + kind = vvp_vif::EDGE_POSEDGE; + else if (edge == static_cast(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 (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. diff --git a/vvp/vvp_vif.cc b/vvp/vvp_vif.cc new file mode 100644 index 000000000..181f8d809 --- /dev/null +++ b/vvp/vvp_vif.cc @@ -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 + +using namespace std; + +vvp_vif::vvp_vif(size_t nmembers) +: nmembers_(nmembers), + signals_(nmembers, static_cast(0)), + pos_events_(nmembers, static_cast(0)), + neg_events_(nmembers, static_cast(0)), + any_events_(nmembers, static_cast(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 , , , ... + * + * 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 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); +} diff --git a/vvp/vvp_vif.h b/vvp/vvp_vif.h new file mode 100644 index 000000000..4d4c73080 --- /dev/null +++ b/vvp/vvp_vif.h @@ -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 + +/* + * 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 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 . + vvp_net_t* signal(size_t idx) const; + + // The edge-detector net for member /, 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 signals_; + std::vector pos_events_; + std::vector neg_events_; + std::vector any_events_; +}; + +#endif /* IVL_vvp_vif_H */