vpi: add native structural-connectivity object model

Implement the IEEE 1364 structural VPI objects that Icarus previously left
unimplemented, so a single-source consumer (e.g. a dataflow/waveform viewer)
gets real data instead of empty fallbacks. Each object is sourced faithfully
from the compiler and threaded ivl -> tgt-vvp -> .vvp directive -> assembler
-> runtime __vpi* -> scope iterator -> vpi_user.h constants.

* vpiProcess (always/initial/final): .process directive from ivl_design_process;
  object reports vpiType/vpiLineNo/vpiFile/vpiScope.

* vpiPrimitive / vpiPrimTerm (gate/switch/UDP): .primitive directive from
  ivl_scope_logs; reports vpiPrimType, vpiSize (#inputs), name, location, and
  iterates terminals (vpiDirection, vpiTermIndex).

* vpiDriver: the tri resolver already retains each driver's value+strength;
  tgt-vvp tags each input with its driver scope (.resolv_drv), and
  vpi_iterate(vpiDriver, net) exposes per-driver value (vpiStrengthVal) and
  scope. signal->node->fun is the resolver, so no extra linkage is needed.

* vpiContAssign (+vpiLhs/vpiRhs): continuous assignments are preserved through
  the frontend (NetScope records each lval/rval/location/drive in
  PGAssign::elaborate; dll_target::end_design builds ivl_cont_assign_s, exposed
  via the new ivl_scope_cassigns/ivl_cassign_* API); tgt-vvp emits .contassign
  and the runtime materialises __vpiContAssign. vpiLhs/size/location are always
  faithful; vpiRhs resolves to the r-value net for simple assigns and is null
  for an expression r-value (anonymous synthesised temp).

* EVCD ($dumpports) inout conflict states: sys_evcd.c walks vpiDriver on an
  inout's net, separates module-side from external drives by scope, and maps
  the pair to the full IEEE 1364-2005 18.4.3 state characters (0/1/?/F, the
  A/a/B/b/C/c conflicts, and d/u/l/h by drive strength) instead of collapsing
  a genuine bus conflict to '?'. New ivtest evcd_inout covers it.

Full Verilog regression (regress-vlg.list) stays clean: the only failures are
pre-existing (gold-less Expected-Fail tests, a VHDL test, a test needing
-gno-io-range-error, and a pre-existing .port_info bug).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
William P. Moore 2026-06-19 18:53:47 -06:00
parent 96cf1674a4
commit f402e2142d
26 changed files with 1167 additions and 7 deletions

View File

@ -149,6 +149,16 @@ output ``L``/``H``, three-state, etc.) and the strength0/strength1 components.
The companion tasks $dumpportsall, $dumpportsoff, $dumpportson,
$dumpportsflush, and $dumpportslimit mirror the corresponding $dumpall family.
The extended VCD format is byte-compatible with the GHDL ``--evcd`` writer, so
the same waveform reader can consume port dumps from both Verilog and VHDL
designs.
For ``inout`` ports, Icarus separates the module-side drive from the external
(testbench-side) drive and emits the full IEEE 1364-2005 conflict-state
characters: ``0``/``1`` (both sides agree), ``A``/``a``/``B``/``b``/``C``/``c``
(the two sides drive conflicting values), ``d``/``u``/``l``/``h`` (same value
but differing drive strength), ``F`` (neither side drives), and the
directional ``D``/``U``/``H``/``L`` forms when only one side drives. This is
more precise than a plain resolved value, which would collapse a genuine bus
conflict to ``?``.
The extended VCD format is otherwise byte-compatible with the GHDL ``--evcd``
writer, so the same waveform reader can consume port dumps from both Verilog
and VHDL designs. (GHDL does not separate inout drive sides, so it emits the
resolved-value states only; Icarus is a strict superset there.)

View File

@ -174,6 +174,12 @@ void PGAssign::elaborate(Design*des, NetScope*scope) const
ivl_assert(*this, lval && rval);
ivl_assert(*this, rval->pin_count() == 1);
// Keep a faithful record of this continuous assignment (its
// l-value net, the r-value expression's net, location and drive
// strengths) so the target API can expose it as a vpiContAssign.
scope->add_cont_assign(lval, rval, get_file(), get_lineno(),
drive.drive0, drive.drive1);
// Detect the case that the rvalue-expression is a simple
// expression. In this case, we will need to create a driver
// (later) to carry strengths.

View File

@ -187,6 +187,7 @@ typedef struct ivl_net_probe_s*ivl_net_probe_t;
typedef struct ivl_nexus_s *ivl_nexus_t;
typedef struct ivl_nexus_ptr_s*ivl_nexus_ptr_t;
typedef struct ivl_parameter_s*ivl_parameter_t;
typedef struct ivl_cont_assign_s*ivl_cont_assign_t;
typedef struct ivl_process_s *ivl_process_t;
typedef struct ivl_scope_s *ivl_scope_t;
typedef struct ivl_signal_s *ivl_signal_t;
@ -1888,6 +1889,16 @@ extern unsigned ivl_scope_logs(ivl_scope_t net);
extern ivl_net_logic_t ivl_scope_log(ivl_scope_t net, unsigned idx);
extern unsigned ivl_scope_lpms(ivl_scope_t net);
extern ivl_lpm_t ivl_scope_lpm(ivl_scope_t, unsigned idx);
/* CONTINUOUS ASSIGNMENTS
* These methods enumerate the continuous assignments (`assign lhs = rhs;`)
* of a scope and access their l-value, r-value and source location. */
extern unsigned ivl_scope_cassigns(ivl_scope_t net);
extern ivl_cont_assign_t ivl_scope_cassign(ivl_scope_t net, unsigned idx);
extern ivl_signal_t ivl_cassign_lval(ivl_cont_assign_t net);
extern ivl_signal_t ivl_cassign_rval(ivl_cont_assign_t net);
extern const char* ivl_cassign_file(ivl_cont_assign_t net);
extern unsigned ivl_cassign_lineno(ivl_cont_assign_t net);
extern ivl_scope_t ivl_cassign_scope(ivl_cont_assign_t net);
extern const char* ivl_scope_name(ivl_scope_t net);
extern const char* ivl_scope_basename(ivl_scope_t net);
extern unsigned ivl_scope_params(ivl_scope_t net);

View File

@ -0,0 +1,42 @@
$date
Fri Jun 19 17:42:51 2026
$end
$version
Icarus Verilog
$end
$timescale
1s
$end
$scope module test.u $end
$var port 1 <0 io $end
$var port 1 <1 oe $end
$var port 1 <2 ov $end
$upscope $end
$enddefinitions $end
#0
$dumpports
pF 0 0 <0
pD 6 0 <1
pD 6 0 <2
$end
#5
pU 0 6 <1
pU 0 6 <2
pH 0 6 <0
#10
pD 6 0 <1
pD 6 0 <2
pF 0 0 <0
pU 0 6 <0
#15
pU 0 6 <1
pU 0 6 <2
pA 6 6 <0
#20
pD 6 0 <2
p0 6 0 <0
pB 6 6 <0
#25
pU 0 6 <2
p1 0 6 <0
#30

View File

@ -0,0 +1,32 @@
/*
* Extended-VCD ($dumpports) inout conflict-state test. The DUT and the
* testbench each drive the shared inout `io` through a conditional (tri)
* driver, so the resolver retains both sides and the writer emits the IEEE
* 1364-2005 18.4.3 state characters: F (neither drives), H (module drives,
* external hi-Z), U (external drives, module hi-Z), A (in 0 / out 1),
* B (in 1 / out 0) and 1 (both drive 1).
*/
module dut(inout wire io, input wire oe, input wire ov);
assign io = oe ? ov : 1'bz;
endmodule
module test;
reg oe, ov; // control the module-side (output) drive
reg te, tv; // control the external (testbench) drive
wire io;
assign io = te ? tv : 1'bz;
dut u(.io(io), .oe(oe), .ov(ov));
initial begin
$dumpports(u, "work/evcd_inout.evcd");
oe = 0; ov = 0; te = 0; tv = 0; #5; // F : neither side drives
oe = 1; ov = 1; te = 0; tv = 0; #5; // H : module 1, external hi-Z
oe = 0; ov = 0; te = 1; tv = 1; #5; // U : external 1, module hi-Z
oe = 1; ov = 1; te = 1; tv = 0; #5; // A : external 0, module 1
oe = 1; ov = 0; te = 1; tv = 1; #5; // B : external 1, module 0
oe = 1; ov = 1; te = 1; tv = 1; #5; // 1 : both drive 1
$finish;
end
endmodule

View File

@ -518,6 +518,7 @@ dumpvars normal ivltests # PR#174: dumpvars non-hierarchical arguments
evcd_basic normal ivltests diff=work/evcd_basic.evcd:gold/evcd_basic.evcd.gold:2
evcd_bus normal ivltests diff=work/evcd_bus.evcd:gold/evcd_bus.evcd.gold:2
evcd_onoff normal ivltests diff=work/evcd_onoff.evcd:gold/evcd_onoff.evcd.gold:2
evcd_inout normal ivltests diff=work/evcd_inout.evcd:gold/evcd_inout.evcd.gold:2
eeq normal ivltests # === and !== in structural context
else1 normal ivltests # ifdef with else
else2 normal ivltests # compound ifdef with else

View File

@ -174,6 +174,20 @@ NetScope::~NetScope()
/* name_ and module_name_ are perm-allocated. */
}
void NetScope::add_cont_assign(NetNet*lval, NetNet*rval, perm_string file,
unsigned lineno, ivl_drive_t drive0,
ivl_drive_t drive1)
{
cont_assign_t rec;
rec.lval = lval;
rec.rval = rval;
rec.file = file;
rec.lineno = lineno;
rec.drive0 = drive0;
rec.drive1 = drive1;
cont_assigns_.push_back(rec);
}
void NetScope::set_line(const LineInfo*info)
{
file_ = info->get_file();

View File

@ -1009,6 +1009,23 @@ class NetScope : public Definitions, public Attrib {
/* Search the scope hierarchy for the scope where 'type' was defined. */
NetScope*find_typedef_scope(const Design*des, const typedef_t*type_i);
/* Continuous assignments (the `assign lhs = rhs;` form) are lowered
to gates/LPM during elaboration. We keep a faithful record of each
one here so the target API can present them as vpiContAssign
objects with their l-value, r-value and source location. */
struct cont_assign_t {
NetNet*lval;
NetNet*rval;
perm_string file;
unsigned lineno;
ivl_drive_t drive0;
ivl_drive_t drive1;
};
void add_cont_assign(NetNet*lval, NetNet*rval, perm_string file,
unsigned lineno, ivl_drive_t drive0, ivl_drive_t drive1);
const std::vector<cont_assign_t>& cont_assigns() const { return cont_assigns_; }
const std::map<hname_t,NetScope*>& children() const { return children_; }
/* Parameters exist within a scope, and these methods allow
one to manipulate the set. In these cases, the name is the
*simple* name of the parameter, the hierarchy is implicit in
@ -1411,6 +1428,7 @@ class NetScope : public Definitions, public Attrib {
NetScope*unit_;
NetScope*up_;
std::map<hname_t,NetScope*> children_;
std::vector<cont_assign_t> cont_assigns_;
std::map<perm_string,interface_port_alias_t> interface_port_aliases_;
std::map<perm_string,std::map<long,interface_port_alias_t> > interface_port_alias_arrays_;

View File

@ -2222,6 +2222,49 @@ extern "C" ivl_lpm_t ivl_scope_lpm(ivl_scope_t net, unsigned idx)
return net->lpm_[idx];
}
extern "C" unsigned ivl_scope_cassigns(ivl_scope_t net)
{
assert(net);
return net->cassigns_.size();
}
extern "C" ivl_cont_assign_t ivl_scope_cassign(ivl_scope_t net, unsigned idx)
{
assert(net);
assert(idx < net->cassigns_.size());
return net->cassigns_[idx];
}
extern "C" ivl_signal_t ivl_cassign_lval(ivl_cont_assign_t net)
{
assert(net);
return net->lhs_;
}
extern "C" ivl_signal_t ivl_cassign_rval(ivl_cont_assign_t net)
{
assert(net);
return net->rhs_;
}
extern "C" const char* ivl_cassign_file(ivl_cont_assign_t net)
{
assert(net);
return net->file;
}
extern "C" unsigned ivl_cassign_lineno(ivl_cont_assign_t net)
{
assert(net);
return net->lineno;
}
extern "C" ivl_scope_t ivl_cassign_scope(ivl_cont_assign_t net)
{
assert(net);
return net->scope_;
}
static unsigned scope_name_len(ivl_scope_t net)
{
unsigned len = 0;

View File

@ -233,6 +233,54 @@ ivl_signal_t dll_target::find_signal(ivl_design_s &des, const NetNet*net)
return 0;
}
ivl_signal_t dll_target::find_signal_opt(ivl_design_s &des, const NetNet*net)
{
if (net == 0 || net->scope() == 0)
return 0;
perm_string nname = net->name();
if (nname.str() == 0) // unnamed net (e.g. some array elements)
return 0;
ivl_scope_t scop = find_scope(des, net->scope());
if (scop == 0)
return 0;
for (unsigned idx = 0 ; idx < scop->sigs_.size() ; idx += 1) {
const char*sn = scop->sigs_[idx]->name_.str();
if (sn && strcmp(sn, nname.str()) == 0)
return scop->sigs_[idx];
}
return 0;
}
void dll_target::add_cont_assigns(const NetScope*ns)
{
ivl_scope_t scope = find_scope(des_, ns);
const std::vector<NetScope::cont_assign_t>&list = ns->cont_assigns();
for (unsigned idx = 0 ; idx < list.size() ; idx += 1) {
ivl_signal_t lhs = find_signal_opt(des_, list[idx].lval);
ivl_signal_t rhs = find_signal_opt(des_, list[idx].rval);
if (scope == 0 || lhs == 0)
continue; // need at least the l-value net
// rhs may be 0 for an expression r-value (no single net).
ivl_cont_assign_t obj = new struct ivl_cont_assign_s;
obj->scope_ = scope;
obj->lhs_ = lhs;
obj->rhs_ = rhs;
obj->file = list[idx].file;
obj->lineno = list[idx].lineno;
obj->drive0 = list[idx].drive0;
obj->drive1 = list[idx].drive1;
scope->cassigns_.push_back(obj);
}
for (std::map<hname_t,NetScope*>::const_iterator it = ns->children().begin()
; it != ns->children().end() ; ++ it)
add_cont_assigns(it->second);
}
static ivl_nexus_t nexus_sig_make(ivl_signal_t net, unsigned pin)
{
ivl_nexus_t tmp = new struct ivl_nexus_s;
@ -658,10 +706,18 @@ bool dll_target::start_design(const Design*des)
* Here ivl is telling us that the design is scanned completely, and
* here is where we call the API to process the constructed design.
*/
int dll_target::end_design(const Design*)
int dll_target::end_design(const Design*des)
{
int rc;
if (errors == 0) {
/* All scopes and signals exist now, so attach the preserved
continuous assignments to their ivl_scope objects before
handing the design to the code-generator target. */
std::list<NetScope*>roots = des->find_root_scopes();
for (std::list<NetScope*>::const_iterator it = roots.begin()
; it != roots.end() ; ++ it)
add_cont_assigns(*it);
if (verbose_flag) {
cout << " ... invoking target_design" << endl;
}

22
t-dll.h
View File

@ -162,6 +162,11 @@ struct dll_target : public target_t, public expr_scan_t {
static ivl_scope_t find_scope(ivl_design_s &des, const NetScope*cur);
static ivl_signal_t find_signal(ivl_design_s &des, const NetNet*net);
/* Like find_signal but returns 0 instead of asserting when the net
has no matching ivl_signal (used when preserving cont assigns). */
static ivl_signal_t find_signal_opt(ivl_design_s &des, const NetNet*net);
/* Walk the NetScope tree building ivl_cont_assign_s records. */
void add_cont_assigns(const NetScope*ns);
static ivl_parameter_t scope_find_param(ivl_scope_t scope_i,
const char*name);
@ -653,6 +658,22 @@ struct ivl_process_s {
ivl_process_t next_;
};
/*
* A continuous assignment (the `assign lhs = rhs;` form), preserved through
* elaboration so it can be presented as a vpiContAssign. lhs_ and rhs_ are
* the l-value and r-value nets; the location and drive strengths come from
* the source statement.
*/
struct ivl_cont_assign_s {
ivl_scope_t scope_;
ivl_signal_t lhs_;
ivl_signal_t rhs_;
perm_string file;
unsigned lineno;
ivl_drive_t drive0;
ivl_drive_t drive1;
};
/*
* Scopes are kept in a tree. Each scope points to its first child,
* and also to any siblings. Thus a parent can scan all its children
@ -713,6 +734,7 @@ struct ivl_scope_s {
} u_;
std::vector<ivl_switch_t>switches;
std::vector<ivl_cont_assign_t>cassigns_;
signed int time_precision :8;
signed int time_units :8;

View File

@ -546,6 +546,23 @@ static void display_multi_driver_error(ivl_nexus_t nex, unsigned ndrivers,
static ivl_nexus_ptr_t *drivers = 0x0;
static unsigned adrivers = 0;
/* The scope a nexus driver lives in. Used to tag each resolver input so the
runtime can tell an inout's module-side drivers from its external ones,
which is what the extended-VCD ($dumpports) writer needs to emit the IEEE
1364 conflict-state characters. Every driver kind carries a scope. */
static ivl_scope_t driver_scope_of(ivl_nexus_ptr_t nptr)
{
ivl_net_logic_t log = ivl_nexus_ptr_log(nptr);
if (log) return ivl_logic_scope(log);
ivl_lpm_t lpm = ivl_nexus_ptr_lpm(nptr);
if (lpm) return ivl_lpm_scope(lpm);
ivl_net_const_t con = ivl_nexus_ptr_con(nptr);
if (con) return ivl_const_scope(con);
ivl_signal_t sig = ivl_nexus_ptr_sig(nptr);
if (sig) return ivl_signal_scope(sig);
return 0;
}
void EOC_cleanup_drivers(void)
{
free(drivers);
@ -758,6 +775,14 @@ static void draw_net_input_x(ivl_nexus_t nex,
display_multi_driver_error(nex, ndrivers, MDRV_REAL);
}
/* Capture each driver's scope now, before drawing the driver
inputs below: draw_net_input_drive() can recurse into
draw_net_input_x() and refill the shared static drivers[] array,
so it is not safe to read drivers[] after that loop. */
ivl_scope_t*driver_scopes = malloc(ndrivers * sizeof(ivl_scope_t));
for (idx = 0; idx < ndrivers; idx += 1)
driver_scopes[idx] = driver_scope_of(drivers[idx]);
driver_labels = malloc(ndrivers * sizeof(char*));
ivl_signal_t path_sig = find_modpath(nex);
if (path_sig) {
@ -782,6 +807,18 @@ static void draw_net_input_x(ivl_nexus_t nex,
fprintf(vvp_out, ";\n");
free(driver_labels);
/* Tag each resolver input with the scope of its driver, so the
runtime can expose vpiDriver and separate an inout's two drive
sides. Emitted as a companion directive keyed by the resolver. */
fprintf(vvp_out, " .resolv_drv RS_%p", nex);
for (idx = 0; idx < ndrivers; idx += 1) {
ivl_scope_t dsc = driver_scopes[idx];
if (dsc) fprintf(vvp_out, ", S_%p", dsc);
else fprintf(vvp_out, ", S_%p", nex); /* never expected */
}
fprintf(vvp_out, ";\n");
free(driver_scopes);
snprintf(result, sizeof result, "RS_%p", nex);
if (island)

View File

@ -2375,6 +2375,22 @@ int draw_process(ivl_process_t net, void*x)
local_count = 0;
fprintf(vvp_out, " .scope S_%p;\n", scope);
/* Emit a structural record so the runtime can expose this process
as a vpiProcess (always/initial/final) object on its scope, with
the source file/line of the process statement. The type code is
0=initial, 1=always(any flavour), 2=final. */
{
unsigned ptype;
switch (ivl_process_type(net)) {
case IVL_PR_INITIAL: ptype = 0; break;
case IVL_PR_FINAL: ptype = 2; break;
default: ptype = 1; break; /* always / always_* */
}
fprintf(vvp_out, " .process %u %u %u;\n", ptype,
ivl_file_table_index(ivl_stmt_file(stmt)),
ivl_stmt_lineno(stmt));
}
/* Generate the entry label. Just give the thread a number so
that we are certain the label is unique. */
fprintf(vvp_out, "T_%u ;\n", thread_count);

View File

@ -933,6 +933,57 @@ static void draw_equiv_impl_in_scope(ivl_net_logic_t lptr)
fprintf(vvp_out, "L_%p .functor %s 1, %s, %s, C4<0>, C4<0>;\n", lptr, ltype, lval, rval);
}
/* Map an Icarus gate type to the IEEE 1364 vpiPrimType subtype code, so the
runtime can answer vpi_get(vpiPrimType, prim_handle). Returns 0 for gate
types that have no 1364 primitive subtype. The numeric codes match the
vpi*Prim values in vpi_user.h. */
static int vpi_prim_type_code(ivl_logic_t t)
{
switch (t) {
case IVL_LO_AND: return 1; /* vpiAndPrim */
case IVL_LO_NAND: return 2; /* vpiNandPrim */
case IVL_LO_NOR: return 3; /* vpiNorPrim */
case IVL_LO_OR: return 4; /* vpiOrPrim */
case IVL_LO_XOR: return 5; /* vpiXorPrim */
case IVL_LO_XNOR: return 6; /* vpiXnorPrim */
case IVL_LO_BUF:
case IVL_LO_BUFZ:
case IVL_LO_BUFT: return 7; /* vpiBufPrim */
case IVL_LO_NOT: return 8; /* vpiNotPrim */
case IVL_LO_BUFIF0: return 9; /* vpiBufif0Prim */
case IVL_LO_BUFIF1: return 10; /* vpiBufif1Prim */
case IVL_LO_NOTIF0: return 11; /* vpiNotif0Prim */
case IVL_LO_NOTIF1: return 12; /* vpiNotif1Prim */
case IVL_LO_NMOS: return 13; /* vpiNmosPrim */
case IVL_LO_PMOS: return 14; /* vpiPmosPrim */
case IVL_LO_CMOS: return 15; /* vpiCmosPrim */
case IVL_LO_RNMOS: return 16; /* vpiRnmosPrim */
case IVL_LO_RPMOS: return 17; /* vpiRpmosPrim */
case IVL_LO_RCMOS: return 18; /* vpiRcmosPrim */
case IVL_LO_PULLUP: return 25; /* vpiPullupPrim */
case IVL_LO_PULLDOWN: return 26; /* vpiPulldownPrim */
case IVL_LO_UDP: return 28; /* vpiCombPrim (Icarus UDPs) */
default: return 0;
}
}
/* Emit a structural .primitive record for one gate so the runtime exposes it
as a vpiPrimitive on its scope (one-to-many from the module per 1364
26.6.1/26.6.13). This is independent of the simulation functor that
draw_logic_in_scope() draws; it carries the gate subtype, the number of
terminals (pin 0 is the output, the rest are inputs), the source location
and the gate's base name. */
static void draw_primitive_in_scope(ivl_net_logic_t lptr)
{
const char*bn = ivl_logic_basename(lptr);
fprintf(vvp_out, " .primitive %d %u %u %u, \"%s\";\n",
vpi_prim_type_code(ivl_logic_type(lptr)),
ivl_logic_pins(lptr),
ivl_file_table_index(ivl_logic_file(lptr)),
ivl_logic_lineno(lptr),
bn ? vvp_mangle_name(bn) : "");
}
static void draw_logic_in_scope(ivl_net_logic_t lptr)
{
unsigned pdx;
@ -2548,6 +2599,7 @@ int draw_scope(ivl_scope_t net, ivl_scope_t parent)
for (idx = 0 ; idx < ivl_scope_logs(net) ; idx += 1) {
ivl_net_logic_t lptr = ivl_scope_log(net, idx);
draw_primitive_in_scope(lptr);
draw_logic_in_scope(lptr);
}
@ -2583,6 +2635,28 @@ int draw_scope(ivl_scope_t net, ivl_scope_t parent)
draw_switch_in_scope(sw);
}
/* Emit the preserved continuous assignments so the runtime can
expose them as vpiContAssign objects on this scope, each with its
l-value and r-value nets (by signal label, word 0) and source
location. */
for (idx = 0 ; idx < ivl_scope_cassigns(net) ; idx += 1) {
ivl_cont_assign_t ca = ivl_scope_cassign(net, idx);
ivl_signal_t lval = ivl_cassign_lval(ca);
ivl_signal_t rval = ivl_cassign_rval(ca);
/* Only reference signals that are actually emitted as named
vpi objects. Local (synthesised/elided) nets have no
lookupable label, so skip a local l-value entirely and omit
a local r-value (vpiRhs is then null). */
if (ivl_signal_local(lval))
continue;
fprintf(vvp_out, " .contassign %u %u, v%p_0",
ivl_file_table_index(ivl_cassign_file(ca)),
ivl_cassign_lineno(ca), lval);
if (rval && !ivl_signal_local(rval))
fprintf(vvp_out, ", v%p_0", rval);
fprintf(vvp_out, ";\n");
}
if (ivl_scope_type(net) == IVL_SCT_TASK)
draw_task_definition(net);

View File

@ -53,6 +53,7 @@ static const char*units_names[] = { "s", "ms", "us", "ns", "ps", "fs" };
/* One tracked port. */
struct evcd_port {
vpiHandle net; /* value net (vpiLowConn of the port) */
vpiHandle inst; /* the dumped instance scope (module side) */
int dir; /* vpiInput / vpiOutput / vpiInout */
unsigned width;
int id; /* EVCD identifier integer */
@ -103,6 +104,126 @@ static void evcd_strengths(int logic, int*s0, int*s1)
}
}
/* Map (input-side value, output-side value) to the IEEE 1364-2005 18.4.3
extended-VCD state character for an inout (unknown-direction) port. Value
codes are 0, 1, 2=X, 3=Z, where Z means that side is not driving. A side
that is hi-Z lets the other side determine the input/output mnemonic; both
driving give the agreement (0/1) or conflict (A/a/B/b/C/c) characters. */
static char evcd_inout_char(int in_v, int out_v)
{
static const char tab[4][4] = {
/* out= 0 1 X Z */
/*in0*/ {'0', 'A', 'a', 'D'},
/*in1*/ {'B', '1', 'b', 'U'},
/*inX*/ {'C', 'c', '?', 'N'},
/*inZ*/ {'L', 'H', 'X', 'F'}
};
return tab[in_v & 3][out_v & 3];
}
/* Reduce a vpi logic value to the 0/1/2=X/3=Z code used above. */
static int evcd_vcode(int logic)
{
switch (logic) {
case vpi0: case vpiL: return 0;
case vpi1: case vpiH: return 1;
case vpiZ: return 3;
default: return 2;
}
}
/* Combine a new driver value into one side's accumulated value. Z means the
driver is not contributing; two real drivers that disagree make X. */
static int evcd_side_combine(int acc, int v)
{
if (v == 3) return acc;
if (acc == 3) return v;
if (acc == v) return acc;
return 2;
}
/* The drive strength level (0..7) encoded in a vpi strength bitmask
(1<<level). Per IEEE 1364-2005 18.4.3.2, levels 5..7 are "strong" and
1..4 are "weak"; this distinction selects the d/u/l/h conflict states. */
static int evcd_strlevel(unsigned mask)
{
int lvl = 0;
while (mask > 1) { mask >>= 1; lvl += 1; }
return lvl;
}
/* Non-zero if scope `s` is the instance `inst` or nested within it. */
static int evcd_scope_within(vpiHandle s, vpiHandle inst)
{
int guard = 0;
while (s && guard < 4096) {
if (vpi_compare_objects(s, inst)) return 1;
s = vpi_handle(vpiScope, s);
guard += 1;
}
return 0;
}
/* For a scalar inout port, separate the external (input-side) and module
(output-side) drives by walking the net's vpiDriver objects and
classifying each by whether its scope lies inside the dumped instance,
then map the pair to the EVCD state character. s0 and s1 receive the
resolved strengths. Returns 0 when the net exposes no drivers (the caller
then falls back to the plain resolved-value encoding). */
static char evcd_inout_state(struct evcd_port*p, int*s0, int*s1)
{
vpiHandle it = vpi_iterate(vpiDriver, p->net);
vpiHandle d;
int in_v = 3, out_v = 3; /* side values: 0,1,2=X,3=Z */
int in_lvl = 0, out_lvl = 0; /* strongest level seen per side */
int any = 0;
char c;
s_vpi_value rv;
if (it == NULL) return 0;
while ((d = vpi_scan(it))) {
s_vpi_value val;
vpiHandle dscope = vpi_handle(vpiScope, d);
int v, lvl;
any = 1;
val.format = vpiStrengthVal;
vpi_get_value(d, &val);
v = evcd_vcode((int)val.value.strength[0].logic);
/* Strength of the driven rail (s1 for a 1, s0 otherwise). */
lvl = evcd_strlevel((v == 1) ? val.value.strength[0].s1
: val.value.strength[0].s0);
if (dscope && evcd_scope_within(dscope, p->inst)) {
out_v = evcd_side_combine(out_v, v);
if (lvl > out_lvl) out_lvl = lvl;
} else {
in_v = evcd_side_combine(in_v, v);
if (lvl > in_lvl) in_lvl = lvl;
}
}
if (!any) return 0;
rv.format = vpiStrengthVal;
vpi_get_value(p->net, &rv);
evcd_strengths((int)rv.value.strength[0].logic, s0, s1);
c = evcd_inout_char(in_v, out_v);
/* When both sides drive the same level (0 or 1) but with different
strength ranges, the IEEE state is d/u (input stronger) or l/h
(output stronger) rather than the plain agreement 0/1. */
if (in_v == out_v && (in_v == 0 || in_v == 1)) {
int in_strong = in_lvl >= 5;
int out_strong = out_lvl >= 5;
if (in_strong && !out_strong)
c = (in_v == 0) ? 'd' : 'u';
else if (!in_strong && out_strong)
c = (in_v == 0) ? 'l' : 'h';
}
return c;
}
/* Emit one value record for a port:
scalar -> p<state> <s0> <s1> <<id>
@ -122,6 +243,19 @@ static void evcd_emit_value(struct evcd_port*p)
if (p->width <= 1) {
logic = (int)val.value.strength[0].logic;
evcd_strengths(logic, &s0, &s1);
/* For an inout, separate the two drive sides so genuine
conflicts become the IEEE conflict-state characters
(A/a/B/b/C/c) rather than a generic '?'. Falls back to the
resolved-value encoding when no per-driver info exists. */
if (p->dir == vpiInout) {
int cs0 = s0, cs1 = s1;
char cc = evcd_inout_state(p, &cs0, &cs1);
if (cc) {
fprintf(evcd_file, "p%c %d %d <%d\n",
cc, cs0, cs1, p->id);
return;
}
}
fprintf(evcd_file, "p%c %d %d <%d\n",
evcd_state_char(logic, p->dir), s0, s1, p->id);
return;
@ -243,6 +377,7 @@ static void evcd_scan_scope(vpiHandle scope)
p = (struct evcd_port*)calloc(1, sizeof *p);
p->net = net;
p->inst = scope;
p->dir = dir;
p->width = width;
p->id = evcd_next_id++;

View File

@ -333,6 +333,66 @@ typedef struct t_vpi_delay {
#define vpiGenScope 134
/******** Structural-connectivity object/relationship codes (Icarus) *********
These IEEE 1364/1800 object, relationship and property codes were not
previously implemented by Icarus. Standard values are used where they do
not collide with Icarus's existing numbering; where the standard value is
already taken by an unrelated Icarus object code (noted inline) an
Icarus-local value continues the 13x extension block used above for
vpiHighConn/vpiLowConn. The flowtracer-style single-source plugins guard
these with #ifndef, so they pick up whatever value each simulator's header
defines. */
#define vpiContAssign 8 /* continuous assignment (standard) */
#define vpiProcess 99 /* always/initial/final process (standard) */
#define vpiPrimitive 103 /* gate/switch/UDP primitive (standard) */
#define vpiPrimTerm 46 /* primitive terminal (standard) */
#define vpiInitial 24 /* initial process (standard) */
#define vpiAlways 137 /* always process (std 1 == vpiType; Icarus-local) */
#define vpiFinal 138 /* final process (no 1364 value; Icarus-local) */
/* Relationships / one-to-one navigations */
#define vpiStmt 104 /* statement of a process (standard) */
#define vpiLhs 77 /* left-hand side of an assignment (standard) */
#define vpiRhs 82 /* right-hand side of an assignment (standard) */
#define vpiDriver 91 /* driver of a net (standard) */
#define vpiLoad 93 /* load on a net or reg (standard) */
/* vpiPrimType is a property (additional type code) of a vpiPrimitive. The
standard value 33 is taken by vpiNamedBegin in Icarus, so this is an
Icarus-local value. Its *result* values (vpiAndPrim ... vpiCombPrim) are
the standard 1364 ones; they live in the additional-type-code namespace
and so legitimately share integer values with unrelated object/property
codes (exactly as the 1364 reference header does). */
#define vpiPrimType 139 /* primitive subtype (std 33 == vpiNamedBegin) */
#define vpiTermIndex 140 /* primitive terminal index (std 30 == vpiMemoryWord) */
#define vpiAndPrim 1
#define vpiNandPrim 2
#define vpiNorPrim 3
#define vpiOrPrim 4
#define vpiXorPrim 5
#define vpiXnorPrim 6
#define vpiBufPrim 7
#define vpiNotPrim 8
#define vpiBufif0Prim 9
#define vpiBufif1Prim 10
#define vpiNotif0Prim 11
#define vpiNotif1Prim 12
#define vpiNmosPrim 13
#define vpiPmosPrim 14
#define vpiCmosPrim 15
#define vpiRnmosPrim 16
#define vpiRpmosPrim 17
#define vpiRcmosPrim 18
#define vpiRtranPrim 19
#define vpiRtranif0Prim 20
#define vpiRtranif1Prim 21
#define vpiTranPrim 22
#define vpiTranif0Prim 23
#define vpiTranif1Prim 24
#define vpiPullupPrim 25
#define vpiPulldownPrim 26
#define vpiSeqPrim 27
#define vpiCombPrim 28
/* PROPERTIES */
#define vpiUndefined (-1)
#define vpiType 1

View File

@ -1683,6 +1683,37 @@ void compile_shiftr(char*label, long wid, bool signed_flag,
make_arith(arith, label, argc, argv);
}
/*
* Tag the inputs of an already-defined resolver with the scope each driver
* lives in. This lets the runtime expose vpiDriver and, in particular, lets
* the extended-VCD writer separate an inout port's module-side drivers from
* its external drivers when computing the IEEE 1364 conflict-state character.
* The resolver and the driver scopes are all defined earlier in the file, so
* the lookups resolve immediately.
*/
void compile_resolv_drv(char*resolver, unsigned argc, struct symb_s*argv)
{
vvp_net_t*net = lookup_functor_symbol(resolver);
resolv_core*core = net ? dynamic_cast<resolv_core*>(net->fun) : 0;
if (core) {
core->prepare_driver_scopes(argc);
for (unsigned idx = 0 ; idx < argc ; idx += 1) {
/* A driver scope may be declared later in the file than
the resolver, so resolve it through the deferred
vpi-handle lookup, which fills the (stable) slot once
the scope is defined. The lookup takes ownership of
the label string. */
compile_vpi_lookup((vpiHandle*)(void*)core->driver_scope_ref(idx),
argv[idx].text);
}
} else {
for (unsigned idx = 0 ; idx < argc ; idx += 1)
free(argv[idx].text);
}
free(resolver);
free(argv);
}
void compile_resolver(char*label, char*type, unsigned argc, struct symb_s*argv)
{
vvp_net_t*net = new vvp_net_t;
@ -2028,6 +2059,29 @@ void compile_thread(char*start_sym, char*flag)
free(flag);
}
/*
* Record an always/initial/final process as a vpiProcess object attached
* to the current scope. The scope was just selected by the preceding
* ".scope" directive, so vpip_attach_to_current_scope() lands it in the
* right module/instance.
*/
void compile_process(long type, long file_idx, long lineno)
{
vpiHandle obj = vpip_make_process(type, (unsigned)file_idx,
(unsigned)lineno);
vpip_attach_to_current_scope(obj);
}
void compile_primitive(long primtype, long npins,
long file_idx, long lineno, char*name)
{
vpiHandle obj = vpip_make_primitive((int)primtype, (unsigned)npins,
(unsigned)file_idx, (unsigned)lineno,
name);
vpip_attach_to_current_scope(obj);
free(name);
}
void compile_param_logic(char*label, const char*name, char*value, bool signed_flag,
bool local_flag,
long file_idx, long lineno)

View File

@ -114,6 +114,9 @@ extern void compile_functor(char*label, char*type, unsigned width,
extern void compile_resolver(char*label, char*type,
unsigned argc, struct symb_s*argv);
extern void compile_resolv_drv(char*resolver,
unsigned argc, struct symb_s*argv);
extern void compile_concat(char*label, unsigned w0, unsigned w1,
unsigned w2, unsigned w3,
unsigned argc, struct symb_s*argv);
@ -493,6 +496,32 @@ extern void compile_scope_recall(char*sym);
*/
extern void compile_thread(char*start_sym, char*flag);
/*
* The parser uses this to record an always/initial/final process as a
* vpiProcess object on the current scope. type is 0=initial, 1=always,
* 2=final; file_idx/lineno locate the process statement in the source.
*/
extern void compile_process(long type, long file_idx, long lineno);
/*
* The parser uses this to record a continuous assignment as a vpiContAssign
* on the current scope. lhs/rhs are the l-value and r-value signal symbols
* (resolved by deferred lookup); ownership of both strings passes to this
* function.
*/
extern void compile_contassign(long file_idx, long lineno,
char*lhs, char*rhs);
/*
* The parser uses this to record a gate/switch/UDP primitive as a
* vpiPrimitive object on the current scope. primtype is the vpiPrimType
* subtype code, npins the terminal count (pin 0 is the output), file_idx and
* lineno locate the gate, and name is its base name (parser-owned, freed
* here).
*/
extern void compile_primitive(long primtype, long npins,
long file_idx, long lineno, char*name);
/*
* This function is called to create a var vector with the given name.
*

View File

@ -175,6 +175,7 @@ inline uint64_t strtouint64(const char*str, char**endptr, int base)
".cmp/weq" { return K_CMP_WEQ; }
".cmp/wne" { return K_CMP_WNE; }
".concat" { return K_CONCAT; }
".contassign" { return K_CONTASSIGN; }
".concat8" { return K_CONCAT8; }
".delay" { return K_DELAY; }
".dff/n" { return K_DFF_N; }
@ -214,6 +215,8 @@ inline uint64_t strtouint64(const char*str, char**endptr, int base)
".part/v.s" { return K_PART_V_S; }
".port" { return K_PORT; }
".port_info" { return K_PORT_INFO; }
".primitive" { return K_PRIMITIVE; }
".process" { return K_PROCESS; }
".reduce/and" { return K_REDUCE_AND; }
".reduce/or" { return K_REDUCE_OR; }
".reduce/xor" { return K_REDUCE_XOR; }
@ -222,6 +225,7 @@ inline uint64_t strtouint64(const char*str, char**endptr, int base)
".reduce/xnor" { return K_REDUCE_XNOR; }
".repeat" { return K_REPEAT; }
".resolv" { return K_RESOLV; }
".resolv_drv" { return K_RESOLV_DRV; }
".rtran" { return K_RTRAN; }
".rtranif0" { return K_RTRANIF0; }
".rtranif1" { return K_RTRANIF1; }

View File

@ -86,16 +86,16 @@ static struct __vpiModPath*modpath_dst = 0;
%token K_CMP_EEQ K_CMP_EQ K_CMP_EQX K_CMP_EQZ K_CMP_WEQ K_CMP_WNE
%token K_CMP_EQ_R K_CMP_NEE K_CMP_NE K_CMP_NE_R
%token K_CMP_GE K_CMP_GE_R K_CMP_GE_S K_CMP_GT K_CMP_GT_R K_CMP_GT_S
%token K_CONCAT K_CONCAT8 K_DEBUG K_DELAY K_DFF_N K_DFF_N_ACLR
%token K_CONCAT K_CONCAT8 K_CONTASSIGN K_DEBUG K_DELAY K_DFF_N K_DFF_N_ACLR
%token K_DFF_N_ASET K_DFF_P K_DFF_P_ACLR K_DFF_P_ASET
%token K_ENUM2 K_ENUM2_S K_ENUM4 K_ENUM4_S K_EVENT K_EVENT_OR
%token K_EXPORT K_EXTEND_S K_FUNCTOR K_IMPORT K_ISLAND K_LATCH K_MODPATH
%token K_NET K_NET_S K_NET_R K_NET_2S K_NET_2U
%token K_NET8 K_NET8_2S K_NET8_2U K_NET8_S
%token K_PARAM_STR K_PARAM_L K_PARAM_REAL K_PART K_PART_PV
%token K_PART_V K_PART_V_S K_PORT K_PORT_INFO K_PV K_REDUCE_AND K_REDUCE_OR K_REDUCE_XOR
%token K_PART_V K_PART_V_S K_PORT K_PORT_INFO K_PRIMITIVE K_PROCESS K_PV K_REDUCE_AND K_REDUCE_OR K_REDUCE_XOR
%token K_REDUCE_NAND K_REDUCE_NOR K_REDUCE_XNOR K_REPEAT
%token K_RESOLV K_RTRAN K_RTRANIF0 K_RTRANIF1
%token K_RESOLV K_RESOLV_DRV K_RTRAN K_RTRANIF0 K_RTRANIF1
%token K_SCOPE K_SFUNC K_SFUNC_E K_SHIFTL K_SHIFTR K_SHIFTRS
%token K_SUBSTITUTE
%token K_THREAD K_TIMESCALE K_TRAN K_TRANIF0 K_TRANIF1 K_TRANVP
@ -279,6 +279,16 @@ statement
compile_resolver($1, $3, obj.cnt, obj.vect);
}
/* Tags the inputs of a resolver with the scope each driver lives in,
so the runtime can expose vpiDriver and separate an inout port's
module-side and external drives. First symbol is the resolver; the
rest are the per-input driver scopes, in input order. */
| K_RESOLV_DRV T_SYMBOL ',' symbols ';'
{ struct symbv_s obj = $4;
compile_resolv_drv($2, obj.cnt, obj.vect);
}
/* Part select statements take a single netlist input, and numbers
that define the part to be selected out of the input. */
@ -743,6 +753,34 @@ statement
starting address must already be defined. The .thread statement
may also take an optional flag word. */
/* A process statement records an always/initial/final process as a
structural object attached to the current scope, so the runtime can
expose it through vpi_iterate(vpiProcess, scope). Arguments are the
process type (0=initial, 1=always, 2=final), the file-table index and
the source line number of the process statement. */
| K_PROCESS T_NUMBER T_NUMBER T_NUMBER ';'
{ compile_process($2, $3, $4); }
/* A continuous-assignment statement records an `assign lhs = rhs;` as a
vpiContAssign on the current scope. Arguments are the file-table index,
the source line, and the l-value and r-value signal symbols. */
| K_CONTASSIGN T_NUMBER T_NUMBER ',' T_SYMBOL ',' T_SYMBOL ';'
{ compile_contassign($2, $3, $5, $7); }
| K_CONTASSIGN T_NUMBER T_NUMBER ',' T_SYMBOL ';'
{ compile_contassign($2, $3, $5, 0); }
/* A primitive statement records a gate/switch/UDP as a structural object
attached to the current scope, exposed through vpi_iterate(vpiPrimitive,
scope). Arguments are the vpiPrimType subtype code, the number of
terminals (pin 0 is the output), the file-table index, the source line,
and the gate's base name. */
| K_PRIMITIVE T_NUMBER T_NUMBER T_NUMBER T_NUMBER ',' T_STRING ';'
{ compile_primitive($2, $3, $4, $5, $7); }
| K_THREAD T_SYMBOL ';'
{ compile_thread($2, 0); }

View File

@ -122,6 +122,23 @@ resolv_tri::~resolv_tri()
delete[] val_;
}
__vpiScope* resolv_core::driver_scope(unsigned idx) const
{
if (idx < driver_scopes_.size())
return driver_scopes_[idx];
return 0;
}
bool resolv_tri::driver_value(unsigned idx, vvp_vector8_t&val) const
{
if (idx >= nports_)
return false;
if (val_[idx].size() == 0)
return false;
val = val_[idx];
return true;
}
void resolv_tri::recv_vec4_(unsigned port, const vvp_vector4_t&bit)
{
recv_vec8_(port, vvp_vector8_t(bit, 6,6 /* STRONG */));
@ -214,6 +231,18 @@ resolv_wired_logic::~resolv_wired_logic()
delete[] val_;
}
bool resolv_wired_logic::driver_value(unsigned idx, vvp_vector8_t&val) const
{
if (idx >= nports_)
return false;
if (val_[idx].size() == 0)
return false;
/* Wired-logic drivers carry plain 4-state values; present them as
strong for the per-driver view. */
val = vvp_vector8_t(val_[idx], 6, 6);
return true;
}
void resolv_wired_logic::recv_vec4_(unsigned port, const vvp_vector4_t&bit)
{
assert(port < nports_);

View File

@ -21,6 +21,9 @@
# include "config.h"
# include "vvp_net.h"
# include <vector>
class __vpiScope;
/*
* Resolver nodes are similar to wide functors, in that they may have
@ -36,6 +39,23 @@ class resolv_core : public vvp_net_fun_t {
explicit resolv_core(unsigned nports, vvp_net_t*net);
virtual ~resolv_core() override;
// Per-driver introspection, used to expose vpiDriver and to let
// the extended-VCD writer separate an inout's input-side and
// output-side drives. driver_count() is the number of leaf input
// drivers; driver_value() reads one driver's current contributed
// value (with strength); the scope each driver lives in is tagged
// from the .resolv_drv directive so callers can classify a driver
// as inside or outside a given module instance.
unsigned driver_count() const { return nports_; }
// Pre-size the per-driver scope table to a fixed size so the slot
// addresses are stable; the .resolv_drv directive then fills them
// through the deferred vpi-handle resolver (driver scopes can be
// declared later in the file than the resolver).
void prepare_driver_scopes(unsigned n) { driver_scopes_.assign(n, 0); }
__vpiScope** driver_scope_ref(unsigned idx) { return &driver_scopes_[idx]; }
__vpiScope* driver_scope(unsigned idx) const;
virtual bool driver_value(unsigned idx, vvp_vector8_t&val) const = 0;
void recv_vec4(vvp_net_ptr_t port, const vvp_vector4_t&bit,
vvp_context_t) override
{ recv_vec4_(port.port(), bit); }
@ -66,6 +86,7 @@ class resolv_core : public vvp_net_fun_t {
protected:
unsigned nports_;
vvp_net_t*net_;
std::vector<__vpiScope*> driver_scopes_;
};
class resolv_extend : public vvp_net_fun_t {
@ -116,6 +137,7 @@ class resolv_tri : public resolv_core {
~resolv_tri() override;
void count_drivers(unsigned bit_idx, unsigned counts[3]) override;
bool driver_value(unsigned idx, vvp_vector8_t&val) const override;
private:
void recv_vec4_(unsigned port, const vvp_vector4_t&bit) override;
@ -144,6 +166,7 @@ class resolv_wired_logic : public resolv_core {
virtual ~resolv_wired_logic() override;
void count_drivers(unsigned bit_idx, unsigned counts[3]) override;
bool driver_value(unsigned idx, vvp_vector8_t&val) const override;
protected:
virtual vvp_vector4_t wired_logic_math_(vvp_vector4_t&a, vvp_vector4_t&b) =0;

View File

@ -376,6 +376,12 @@ const char* vpi_type_as_string(PLI_INT32 code)
return "vpiMemoryWord";
case vpiModule:
return "vpiModule";
case vpiAlways:
return "vpiAlways";
case vpiInitial:
return "vpiInitial";
case vpiFinal:
return "vpiFinal";
case vpiNamedBegin:
return "vpiNamedBegin";
case vpiNamedEvent:

View File

@ -1000,6 +1000,17 @@ vpiHandle vpip_make_real_const(double value);
vpiHandle vpip_make_real_param(const char*name, double value, bool local_flag,
long file_idx, long lineno);
/* Make a vpiProcess object (always/initial/final). type is the encoding
emitted by tgt-vvp: 0=initial, 1=always, 2=final. */
vpiHandle vpip_make_process(long type, unsigned file_idx, unsigned lineno);
/* Make a vpiPrimitive object (gate/switch/UDP). primtype is the vpiPrimType
subtype code, npins the terminal count (pin 0 = output). The name is copied
(interned); the caller retains ownership of the passed string. */
vpiHandle vpip_make_primitive(int primtype, unsigned npins,
unsigned file_idx, unsigned lineno,
const char*name);
class __vpiNullConst : public __vpiHandle {
public:
explicit __vpiNullConst();

View File

@ -114,6 +114,16 @@ static void delete_sub_scopes(__vpiScope *scope)
case vpiPortBit:
port_bit_delete(item);
break;
case vpiAlways:
case vpiInitial:
case vpiFinal:
/* __vpiProcess holds no sub-objects of its own. */
delete item;
break;
case vpiContAssign:
/* __vpiContAssign references signals it does not own. */
delete item;
break;
case vpiStringVar:
string_delete(item);
break;
@ -287,6 +297,12 @@ static int compare_types(int code, int type)
type == vpiPackage) )
return 1;
if ( code == vpiProcess &&
(type == vpiAlways ||
type == vpiInitial ||
type == vpiFinal) )
return 1;
if ( code == vpiVariables &&
(type == vpiIntegerVar ||
type == vpiBitVar ||
@ -337,6 +353,303 @@ static vpiHandle module_iter(int code, vpiHandle obj)
return make_subset_iterator_(code, ref->intern);
}
/*
* vpiProcess: an always/initial/final process. It is attached to its scope
* (via vpip_attach_to_current_scope) so that vpi_iterate(vpiProcess, scope)
* enumerates the processes of a module. The object reports its kind through
* vpiType (vpiAlways / vpiInitial / vpiFinal) and carries the source
* file/line of the process statement.
*/
class __vpiProcess : public __vpiHandle {
public:
__vpiProcess(int type_code, unsigned file_idx, unsigned lineno)
: type_code_(type_code), scope_(vpip_peek_current_scope()),
file_idx_(file_idx), lineno_(lineno) { }
int get_type_code(void) const override { return type_code_; }
int vpi_get(int code) override;
char* vpi_get_str(int code) override;
vpiHandle vpi_handle(int code) override;
private:
int type_code_;
__vpiScope* scope_;
unsigned file_idx_;
unsigned lineno_;
};
int __vpiProcess::vpi_get(int code)
{
switch (code) {
case vpiType:
return type_code_;
case vpiLineNo:
return (int)lineno_;
case vpiAutomatic:
return 0;
default:
return vpiUndefined;
}
}
char* __vpiProcess::vpi_get_str(int code)
{
switch (code) {
case vpiType:
switch (type_code_) {
case vpiAlways: return simple_set_rbuf_str("vpiAlways");
case vpiInitial: return simple_set_rbuf_str("vpiInitial");
case vpiFinal: return simple_set_rbuf_str("vpiFinal");
default: return 0;
}
case vpiFile:
if (file_idx_ < file_names.size())
return simple_set_rbuf_str(file_names[file_idx_]);
return 0;
default:
return 0;
}
}
vpiHandle __vpiProcess::vpi_handle(int code)
{
switch (code) {
case vpiScope:
case vpiModule:
return scope_;
default:
return 0;
}
}
vpiHandle vpip_make_process(long type, unsigned file_idx, unsigned lineno)
{
int tc;
switch (type) {
case 0: tc = vpiInitial; break;
case 2: tc = vpiFinal; break;
default: tc = vpiAlways; break;
}
return new __vpiProcess(tc, file_idx, lineno);
}
/*
* vpiPrimitive (gate/switch/UDP) and its vpiPrimTerm terminals, per 1364
* 26.6.13. The primitive is attached to its scope so vpi_iterate(vpiPrimitive,
* scope) enumerates the gates of a module. vpiSize is the number of inputs
* (terminal count minus the single output, pin 0). Each terminal reports
* vpiDirection (pin 0 output, the rest inputs) and vpiTermIndex.
*/
class __vpiPrimitive;
class __vpiPrimTerm : public __vpiHandle {
public:
__vpiPrimTerm(__vpiPrimitive*prim, unsigned index)
: prim_(prim), index_(index) { }
int get_type_code(void) const override { return vpiPrimTerm; }
int vpi_get(int code) override;
vpiHandle vpi_handle(int code) override;
private:
__vpiPrimitive*prim_;
unsigned index_;
};
class __vpiPrimitive : public __vpiHandle {
public:
__vpiPrimitive(int primtype, unsigned npins, unsigned file_idx,
unsigned lineno, const char*name)
: primtype_(primtype), npins_(npins), scope_(vpip_peek_current_scope()),
file_idx_(file_idx), lineno_(lineno)
{
name_ = vpip_name_string(name ? name : "");
terms_ = npins_ ? new __vpiPrimTerm*[npins_] : 0;
for (unsigned i = 0 ; i < npins_ ; i += 1)
terms_[i] = new __vpiPrimTerm(this, i);
}
~__vpiPrimitive()
{
for (unsigned i = 0 ; i < npins_ ; i += 1) delete terms_[i];
delete[] terms_;
}
int get_type_code(void) const override { return vpiPrimitive; }
int vpi_get(int code) override;
char* vpi_get_str(int code) override;
vpiHandle vpi_handle(int code) override;
vpiHandle vpi_iterate(int code) override;
private:
int primtype_;
unsigned npins_;
__vpiScope*scope_;
unsigned file_idx_;
unsigned lineno_;
const char*name_;
__vpiPrimTerm**terms_;
};
int __vpiPrimitive::vpi_get(int code)
{
switch (code) {
case vpiPrimType:
return primtype_;
case vpiSize:
return (int)(npins_ ? npins_ - 1 : 0); /* number of inputs */
case vpiLineNo:
return (int)lineno_;
default:
return vpiUndefined;
}
}
char* __vpiPrimitive::vpi_get_str(int code)
{
switch (code) {
case vpiName:
case vpiDefName:
return simple_set_rbuf_str(name_);
case vpiFullName: {
char buf[4096];
buf[0] = 0;
if (scope_) {
construct_scope_fullname(scope_, buf);
strcat(buf, ".");
}
strcat(buf, name_);
return simple_set_rbuf_str(buf);
}
case vpiFile:
if (file_idx_ < file_names.size())
return simple_set_rbuf_str(file_names[file_idx_]);
return 0;
default:
return 0;
}
}
vpiHandle __vpiPrimitive::vpi_handle(int code)
{
switch (code) {
case vpiScope:
case vpiModule:
return scope_;
default:
return 0;
}
}
vpiHandle __vpiPrimitive::vpi_iterate(int code)
{
if (code == vpiPrimTerm && npins_ > 0) {
vpiHandle*args = (vpiHandle*)calloc(npins_, sizeof(vpiHandle));
for (unsigned i = 0 ; i < npins_ ; i += 1) args[i] = terms_[i];
return vpip_make_iterator(npins_, args, true);
}
return 0;
}
int __vpiPrimTerm::vpi_get(int code)
{
switch (code) {
case vpiTermIndex:
return (int)index_;
case vpiDirection:
return index_ == 0 ? vpiOutput : vpiInput;
default:
return vpiUndefined;
}
}
vpiHandle __vpiPrimTerm::vpi_handle(int code)
{
switch (code) {
case vpiParent:
case vpiPrimitive:
return prim_;
default:
return 0;
}
}
vpiHandle vpip_make_primitive(int primtype, unsigned npins, unsigned file_idx,
unsigned lineno, const char*name)
{
return new __vpiPrimitive(primtype, npins, file_idx, lineno, name);
}
/*
* vpiContAssign: a continuous assignment (`assign lhs = rhs;`), preserved
* through elaboration and attached to its scope so vpi_iterate(vpiContAssign,
* scope) enumerates them. vpiLhs / vpiRhs navigate to the l-value and r-value
* nets; vpiSize is the l-value width; vpiLineNo/vpiFile give the location.
*/
class __vpiContAssign : public __vpiHandle {
public:
__vpiContAssign(unsigned file_idx, unsigned lineno)
: lhs_(0), rhs_(0), scope_(vpip_peek_current_scope()),
file_idx_(file_idx), lineno_(lineno) { }
int get_type_code(void) const override { return vpiContAssign; }
int vpi_get(int code) override;
char* vpi_get_str(int code) override;
vpiHandle vpi_handle(int code) override;
// Filled in by the deferred vpi-handle lookup in compile_contassign.
vpiHandle lhs_;
vpiHandle rhs_;
private:
__vpiScope*scope_;
unsigned file_idx_;
unsigned lineno_;
};
int __vpiContAssign::vpi_get(int code)
{
switch (code) {
case vpiLineNo:
return (int)lineno_;
case vpiSize:
return lhs_ ? lhs_->vpi_get(vpiSize) : 0;
default:
return vpiUndefined;
}
}
char* __vpiContAssign::vpi_get_str(int code)
{
switch (code) {
case vpiFile:
if (file_idx_ < file_names.size())
return simple_set_rbuf_str(file_names[file_idx_]);
return 0;
default:
return 0;
}
}
vpiHandle __vpiContAssign::vpi_handle(int code)
{
switch (code) {
case vpiLhs: return lhs_;
case vpiRhs: return rhs_;
case vpiScope:
case vpiModule: return scope_;
default: return 0;
}
}
void compile_contassign(long file_idx, long lineno, char*lhs, char*rhs)
{
__vpiContAssign*obj = new __vpiContAssign((unsigned)file_idx,
(unsigned)lineno);
vpip_attach_to_current_scope(obj);
/* The l-value and r-value signals may be defined later in the file,
so resolve them through the deferred vpi-handle lookup, which
takes ownership of the label strings. The r-value is absent when
the assignment's r-value is an expression with no single net. */
compile_vpi_lookup(&obj->lhs_, lhs);
if (rhs)
compile_vpi_lookup(&obj->rhs_, rhs);
}
__vpiScope::__vpiScope(const char*nam, const char*tnam, bool auto_flag)
: is_automatic_(auto_flag)

View File

@ -24,6 +24,7 @@
# include "compile.h"
# include "vpi_priv.h"
# include "resolv.h"
# include "vvp_net_sig.h"
# include "vvp_island.h"
# include "schedule.h"
@ -680,6 +681,67 @@ static vpiHandle signal_get_handle(int code, vpiHandle ref)
return 0;
}
/*
* vpiDriver: one input driver of a resolved (tri/inout) net. It exposes the
* value (with strength) that this single driver contributes read from the
* resolver's retained per-input value and the scope the driver lives in.
* The extended-VCD ($dumpports) writer uses the scope to tell an inout's
* module-side drivers from its external ones and so emit the IEEE 1364
* conflict-state characters.
*/
class __vpiDriver : public __vpiHandle {
public:
__vpiDriver(resolv_core*core, unsigned idx) : core_(core), idx_(idx) { }
int get_type_code(void) const override { return vpiDriver; }
vpiHandle vpi_handle(int code) override;
void vpi_get_value(p_vpi_value vp) override;
private:
resolv_core*core_;
unsigned idx_;
};
vpiHandle __vpiDriver::vpi_handle(int code)
{
switch (code) {
case vpiScope:
case vpiModule:
return core_->driver_scope(idx_);
default:
return 0;
}
}
void __vpiDriver::vpi_get_value(p_vpi_value vp)
{
/* Only vpiStrengthVal is meaningful for a driver: it carries the
per-bit value and drive strength this driver contributes. */
vvp_vector8_t v8;
bool ok = core_->driver_value(idx_, v8);
unsigned wid = (ok && v8.size() > 0) ? v8.size() : 1;
s_vpi_strengthval*op = static_cast<s_vpi_strengthval*>
(need_result_buf(wid * sizeof(s_vpi_strengthval), RBUF_VAL));
for (unsigned idx = 0 ; idx < wid ; idx += 1) {
if (!ok) {
/* Driver not contributing -> three-state. */
op[idx].logic = vpiZ; op[idx].s0 = vpiHiZ; op[idx].s1 = vpiHiZ;
continue;
}
vvp_scalar_t val = v8.value(idx);
unsigned s0 = 1 << val.strength0();
unsigned s1 = 1 << val.strength1();
switch (val.value()) {
case BIT4_0: op[idx].logic = vpi0; op[idx].s0 = s0|s1; op[idx].s1 = 0; break;
case BIT4_1: op[idx].logic = vpi1; op[idx].s0 = 0; op[idx].s1 = s0|s1; break;
case BIT4_X: op[idx].logic = vpiX; op[idx].s0 = s0; op[idx].s1 = s1; break;
default: op[idx].logic = vpiZ; op[idx].s0 = vpiHiZ; op[idx].s1 = vpiHiZ; break;
}
}
vp->format = vpiStrengthVal;
vp->value.strength = op;
}
static vpiHandle signal_iterate(int code, vpiHandle ref)
{
struct __vpiSignal*rfp = dynamic_cast<__vpiSignal*>(ref);
@ -689,6 +751,20 @@ static vpiHandle signal_iterate(int code, vpiHandle ref)
return rfp->is_netarray ? rfp->id.index->vpi_iterate(code) : NULL;
}
/* Drivers of a resolved net (tri/inout): expose each input of the
resolver feeding this signal as a vpiDriver. */
if (code == vpiDriver) {
resolv_core*core = rfp->node
? dynamic_cast<resolv_core*>(rfp->node->fun) : 0;
if (core == 0 || core->driver_count() == 0)
return 0;
unsigned n = core->driver_count();
vpiHandle*args = static_cast<vpiHandle*>(calloc(n, sizeof(vpiHandle)));
for (unsigned idx = 0 ; idx < n ; idx += 1)
args[idx] = new __vpiDriver(core, idx);
return vpip_make_iterator(n, args, true);
}
return 0;
}