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:
parent
96cf1674a4
commit
f402e2142d
|
|
@ -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.)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
11
ivl_target.h
11
ivl_target.h
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
14
net_scope.cc
14
net_scope.cc
|
|
@ -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();
|
||||
|
|
|
|||
18
netlist.h
18
netlist.h
|
|
@ -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_;
|
||||
|
||||
|
|
|
|||
43
t-dll-api.cc
43
t-dll-api.cc
|
|
@ -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;
|
||||
|
|
|
|||
58
t-dll.cc
58
t-dll.cc
|
|
@ -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
22
t-dll.h
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
135
vpi/sys_evcd.c
135
vpi/sys_evcd.c
|
|
@ -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++;
|
||||
|
|
|
|||
60
vpi_user.h
60
vpi_user.h
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
44
vvp/parse.y
44
vvp/parse.y
|
|
@ -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); }
|
||||
|
||||
|
|
|
|||
|
|
@ -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_);
|
||||
|
|
|
|||
23
vvp/resolv.h
23
vvp/resolv.h
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
313
vvp/vpi_scope.cc
313
vvp/vpi_scope.cc
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue