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.
This commit is contained in:
parent
6fc7ac56c9
commit
207f9a9b09
|
|
@ -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 \
|
||||
|
|
|
|||
1
PWire.h
1
PWire.h
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
@ -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
|
||||
|
|
|
|||
95
elab_expr.cc
95
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<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) {
|
||||
|
|
|
|||
52
elab_lval.cc
52
elab_lval.cc
|
|
@ -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.
|
||||
|
|
|
|||
41
elab_type.cc
41
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<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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
47
elaborate.cc
47
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<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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
12
netmisc.h
12
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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
43
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 <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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
1
t-dll.h
1
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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} },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
133
vvp/vthread.cc
133
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 <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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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 */
|
||||
Loading…
Reference in New Issue