SV: add string-keyed associative array vertical slice

Support int aa[string]/int aa[*] with assign, size/num, exists, delete,
foreach, and whole-array copy for UVM Tier A table patterns. Runtime uses
vvp_aarray_vec4 (std::map); int keys and richer element types deferred.
This commit is contained in:
mjoekhan 2026-07-21 17:12:16 +05:00
parent 39e757e975
commit bcc026c902
45 changed files with 1068 additions and 16 deletions

View File

@ -116,7 +116,7 @@ O = main.o async.o design_dump.o discipline.o dup_expr.o elaborate.o \
emit.o eval_attrib.o \
eval_tree.o expr_synth.o functor.o lexor.o lexor_keyword.o link_const.o \
load_module.o netlist.o netmisc.o nettypes.o net_analog.o net_assign.o \
net_design.o netclass.o netdarray.o \
net_design.o netclass.o netdarray.o netaarray.o \
netenum.o netparray.o netqueue.o netscalar.o netstruct.o netvector.o \
net_event.o net_expr.o net_func.o \
net_func_eval.o net_link.o net_modulo.o \

View File

@ -391,6 +391,15 @@ PENull::~PENull()
{
}
PEAArrayKey::PEAArrayKey(key_kind_t kind)
: kind_(kind)
{
}
PEAArrayKey::~PEAArrayKey()
{
}
PEFNumber::PEFNumber(verireal*v)
: value_(v)
{

27
PExpr.h
View File

@ -637,6 +637,33 @@ class PENull : public PExpr {
unsigned flags) const override;
};
/*
* Sentinel used in pform_range_t for associative-array dimensions.
* WILDCARD encodes `[*]` (string-keyed in the first vertical slice).
* STRING encodes `[string]`.
*/
class PEAArrayKey : public PExpr {
public:
enum key_kind_t { STRING, WILDCARD };
explicit PEAArrayKey(key_kind_t kind);
~PEAArrayKey() override;
inline key_kind_t key_kind() const { return kind_; }
virtual void dump(std::ostream&) const override;
virtual unsigned test_width(Design*des, NetScope*scope,
width_mode_t&mode) override;
virtual NetExpr*elaborate_expr(Design*des, NetScope*scope,
ivl_type_t type, unsigned flags) const override;
virtual NetExpr*elaborate_expr(Design*des, NetScope*,
unsigned expr_wid,
unsigned flags) const override;
private:
key_kind_t kind_;
};
class PENumber : public PExpr {
public:

View File

@ -80,6 +80,7 @@ class PWire : public PNamedItem {
void set_range(const std::list<pform_range_t>&ranges, PWSRType type);
void set_unpacked_idx(const std::list<pform_range_t>&ranges);
const std::list<pform_range_t>& get_unpacked_idx() const { return unpacked_; }
void set_data_type(data_type_t*type);

View File

@ -144,6 +144,9 @@ ostream& operator << (ostream&o, ivl_variable_type_t val)
case IVL_VT_QUEUE:
o << "queue";
break;
case IVL_VT_AARRAY:
o << "aarray";
break;
}
return o;
}

View File

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

View File

@ -13,8 +13,8 @@ Last updated: 2026-07-21
| Icarus Verilog tree | Fork of `steveicarus/iverilog` (`master` @ merge of SV array ordering work) |
| [`uvm/`](../uvm/) | Seeded from IVL_UVM (VerifWorks) — messaging, CLP, stub phases, poor-mans mailbox/semaphore; **not** Accellera-compatible |
| [`examples/hello_uvm`](../examples/hello_uvm) | Smoke TB for the seeded library (`Makefile` included) |
| Parameterized classes | **Partial** on `feat/param-classes`: ANSI `class C #(type T = int, parameter int W = 8);` parses into the class scope and elaborates with **defaults**. Explicit specializations `C#(byte)` / overrides not done yet. See [`examples/param_classes`](../examples/param_classes). |
| Associative arrays | Missing |
| Parameterized classes | **Partial** on `feat/param-classes` (merged): ANSI `class C #(type T = int, parameter int W = 8);` parses into the class scope and elaborates with **defaults**. Explicit specializations `C#(byte)` / overrides not done yet. See [`examples/param_classes`](../examples/param_classes). |
| Associative arrays | **Partial** on `feat/assoc-array`: string-keyed `int aa[string]` / `int aa[*]` with size/num/exists/delete/foreach/copy. See [`docs/assoc-array.md`](assoc-array.md) and [`examples/assoc_array`](../examples/assoc_array). |
| Virtual interfaces | Missing |
| Constraints / randomize | Missing |
| Covergroups / DPI | Missing |

38
docs/assoc-array.md Normal file
View File

@ -0,0 +1,38 @@
# Associative arrays (Tier A #2)
Status: **partial** — string-keyed vertical slice for UVM-like tables.
## Supported in this slice
```systemverilog
int aa[string];
int aa[*]; // accepted as string-keyed (not IEEE int-key wildcard yet)
aa["x"] = 1;
v = aa["x"];
n = aa.size(); // also aa.num()
aa.exists("x");
aa.delete("x");
aa.delete(); // clear all
foreach (aa[k]) ... // k is string
bb = aa; // whole-array copy
```
Element types: packed integral (`int` / `logic` vectors) only for now.
## Encoding
| Syntax | `pform_range_t` sentinel |
|--------|--------------------------|
| `[*]` | `(PENull, PENull)` — distinct from queue `(PENull, 0)` and darray `(0, 0)` |
| `[string]` | `(PETypename(string), 0)` via `'[' expression ']'` (avoids bison conflict with typename-as-expr) |
Elaborates to `netaarray_t` with `IVL_VT_AARRAY`. Runtime: `vvp_aarray_vec4` (`std::map<std::string, vvp_vector4_t>`).
## Deferred (do not claim)
- Integer / class / enum keys (true IEEE `[*]` integer keys)
- Class / string / real / nested AA elements
- `first` / `last` / `next` / `prev` methods (foreach uses internal `key_at`)
- Associative arrays as class properties
See also [STATUS.md](STATUS.md) and [ROADMAP.md](ROADMAP.md).

View File

@ -35,6 +35,7 @@
# include "discipline.h"
# include "netmisc.h"
# include "netdarray.h"
# include "netaarray.h"
# include "netqueue.h"
# include "netstruct.h"
# include "netscalar.h"
@ -414,6 +415,10 @@ NetExpr* elaborate_rval_expr(Design*des, NetScope*scope, ivl_type_t lv_net_type,
// all the types to this new form.
typed_elab = true;
break;
case IVL_VT_AARRAY:
// Whole-array assigns are handled as objects; indexed
// assigns use the element type via context_wid below.
break;
case IVL_VT_REAL:
case IVL_VT_STRING:
break;
@ -3939,6 +3944,65 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
ivl_assert(*this, sub_expr);
// Associative array methods (string-keyed vertical slice).
if (search_results.net &&
search_results.net->data_type()==IVL_VT_AARRAY &&
search_results.path_head.back().index.size()==0) {
perm_string method_name = search_results.path_tail.back().name;
if (method_name == "size" || method_name == "num") {
if (parms_.size() != 0) {
cerr << get_fileline() << ": error: " << method_name
<< "() method takes no arguments" << endl;
des->errors += 1;
}
NetESFunc*sys_expr = new NetESFunc("$size", &netvector_t::atom2u32, 1);
sys_expr->set_line(*this);
sys_expr->parm(0, sub_expr);
return sys_expr;
}
if (method_name == "exists") {
if (parms_.size() != 1) {
cerr << get_fileline() << ": error: exists() method "
<< "takes one argument" << endl;
des->errors += 1;
}
NetESFunc*sys_expr = new NetESFunc("$ivl_aarray_method$exists",
&netvector_t::atom2u32, 2);
sys_expr->set_line(*this);
sys_expr->parm(0, sub_expr);
if (parms_.size() >= 1 && parms_[0].parm) {
NetExpr*key = elab_and_eval(des, scope, parms_[0].parm, -1);
sys_expr->parm(1, key);
} else {
sys_expr->parm(1, 0);
}
return sys_expr;
}
if (method_name == "key_at") {
/* Internal helper for foreach lowering. */
if (parms_.size() != 1) {
cerr << get_fileline() << ": error: key_at() takes one argument"
<< endl;
des->errors += 1;
}
NetESFunc*sys_expr = new NetESFunc("$ivl_aarray_method$key_at",
&netstring_t::type_string, 2);
sys_expr->set_line(*this);
sys_expr->parm(0, sub_expr);
if (parms_.size() >= 1 && parms_[0].parm) {
NetExpr*idx = elab_and_eval(des, scope, parms_[0].parm, -1);
sys_expr->parm(1, idx);
} else {
sys_expr->parm(1, 0);
}
return sys_expr;
}
}
// Dynamic array or queue methods when there is no index.
if (search_results.net &&
(search_results.net->data_type()==IVL_VT_DARRAY ||
@ -4669,6 +4733,7 @@ bool PEIdent::calculate_packed_indices_(Design*des, NetScope*scope, const NetNet
case IVL_VT_STRING:
case IVL_VT_DARRAY:
case IVL_VT_QUEUE:
case IVL_VT_AARRAY:
dimensions += 1;
default:
break;
@ -6914,6 +6979,16 @@ NetExpr* PEIdent::elaborate_expr_net_bit_(Design*des, NetScope*scope,
return res;
}
if (const netaarray_t*aarray = net->sig()->aarray_type()) {
if (debug_elaborate) {
cerr << get_fileline() << ": debug: "
<< "Bit select of an associative array becomes NetESelect." << endl;
}
NetESelect*res = new NetESelect(net, mux, aarray->element_width(), aarray->element_type());
res->set_line(*net);
return res;
}
// If the bit select is constant, then treat it similar
// to the part select, so that I save the effort of
// making a mux part in the netlist.
@ -7455,6 +7530,31 @@ NetExpr* PENull::elaborate_expr(Design*, NetScope*, unsigned, unsigned) const
return tmp;
}
unsigned PEAArrayKey::test_width(Design*, NetScope*, width_mode_t&)
{
expr_type_ = IVL_VT_NO_TYPE;
expr_width_ = 1;
min_width_ = 1;
signed_flag_ = false;
return expr_width_;
}
NetExpr* PEAArrayKey::elaborate_expr(Design*des, NetScope*, ivl_type_t, unsigned) const
{
cerr << get_fileline() << ": internal error: "
<< "PEAArrayKey used as an expression." << endl;
des->errors += 1;
return 0;
}
NetExpr* PEAArrayKey::elaborate_expr(Design*des, NetScope*, unsigned, unsigned) const
{
cerr << get_fileline() << ": internal error: "
<< "PEAArrayKey used as an expression." << endl;
des->errors += 1;
return 0;
}
unsigned PENumber::test_width(Design*, NetScope*, width_mode_t&mode)
{
expr_type_ = IVL_VT_LOGIC;

View File

@ -27,6 +27,7 @@
# include "netstruct.h"
# include "netclass.h"
# include "netdarray.h"
# include "netaarray.h"
# include "netparray.h"
# include "netvector.h"
# include "netenum.h"
@ -340,7 +341,7 @@ NetAssign_*PEIdent::elaborate_lval_var_(Design *des, NetScope *scope,
if (use_sel == index_component_t::SEL_BIT) {
if (reg->darray_type()) {
if (reg->darray_type() || reg->aarray_type()) {
NetAssign_*lv = new NetAssign_(reg);
elaborate_lval_darray_bit_(des, scope, lv, is_force);
return lv;
@ -729,12 +730,13 @@ bool PEIdent::elaborate_lval_darray_bit_(Design*des,
const name_component_t&name_tail = path_.back();
ivl_assert(*this, !name_tail.index.empty());
// For now, only support single-dimension dynamic arrays.
// For now, only support single-dimension dynamic/associative arrays.
ivl_assert(*this, name_tail.index.size() == 1);
if ((lv->sig()->type()==NetNet::UNRESOLVED_WIRE) && !is_force) {
ivl_assert(*this, lv->sig()->coerced_to_uwire());
report_mixed_assignment_conflict_("darray word");
report_mixed_assignment_conflict_(lv->sig()->aarray_type()
? "aarray word" : "darray word");
des->errors += 1;
return false;
}
@ -743,7 +745,7 @@ bool PEIdent::elaborate_lval_darray_bit_(Design*des,
ivl_assert(*this, index_tail.msb != 0);
ivl_assert(*this, index_tail.lsb == 0);
// Evaluate the select expression...
// Evaluate the select expression (integer for darray, string for aarray).
NetExpr*mux = elab_and_eval(des, scope, index_tail.msb, -1);
lv->set_word(mux);

View File

@ -23,6 +23,7 @@
# include "netlist.h"
# include "netclass.h"
# include "netdarray.h"
# include "netaarray.h"
# include "netenum.h"
# include "netqueue.h"
# include "netparray.h"
@ -343,6 +344,12 @@ static ivl_type_t elaborate_static_array_type(Design *des, const LineInfo &li,
des->errors++;
// Recover
base_type = new netvector_t(IVL_VT_LOGIC);
} else if (dynamic_cast<const netaarray_t*>(base_type)) {
cerr << li.get_fileline() << ": sorry: "
<< "array of associative array type is not yet supported."
<< endl;
des->errors++;
base_type = new netvector_t(IVL_VT_LOGIC);
}
ivl_type_t type = new netuarray_t(dims, base_type);
@ -374,6 +381,37 @@ ivl_type_t elaborate_array_type(Design *des, NetScope *scope,
type = elaborate_darray_check_type(des, li, type, "Dynamic array");
type = new netdarray_t(type);
continue;
} else if (dynamic_cast<PENull*>(lidx) && dynamic_cast<PENull*>(ridx)) {
// Associative array wildcard `[*]` — string-keyed in this slice.
type = elaborate_static_array_type(des, li, type, dimensions);
type = elaborate_darray_check_type(des, li, type, "Associative array");
type = new netaarray_t(type);
continue;
} else if (dynamic_cast<PEAArrayKey*>(lidx)) {
// Associative array via PEAArrayKey sentinel.
type = elaborate_static_array_type(des, li, type, dimensions);
type = elaborate_darray_check_type(des, li, type, "Associative array");
type = new netaarray_t(type);
continue;
} else if (PETypename*tn = dynamic_cast<PETypename*>(lidx)) {
// `[string]` arrives as PETypename via `'[' expression ']'`
// (dedicated K_string rule conflicts with typename-as-expr).
if (dynamic_cast<string_type_t*>(tn->get_type()) &&
(ridx == 0 || dynamic_cast<PENull*>(ridx))) {
type = elaborate_static_array_type(des, li, type, dimensions);
type = elaborate_darray_check_type(des, li, type, "Associative array");
type = new netaarray_t(type);
continue;
}
cerr << li.get_fileline() << ": sorry: "
<< "associative array key type is not yet supported "
<< "(string keys only in this slice)."
<< endl;
des->errors++;
type = elaborate_static_array_type(des, li, type, dimensions);
type = elaborate_darray_check_type(des, li, type, "Associative array");
type = new netaarray_t(type);
continue;
} else if (dynamic_cast<PENull*>(lidx)) {
// Special case: Detect the mark for a QUEUE declaration,
// which is the dimensions [null:max_idx].

View File

@ -47,6 +47,7 @@
# include "netenum.h"
# include "netvector.h"
# include "netdarray.h"
# include "netaarray.h"
# include "netqueue.h"
# include "netparray.h"
# include "netscalar.h"
@ -3047,6 +3048,13 @@ NetProc* PAssign::elaborate(Design*des, NetScope*scope) const
rv = elaborate_rval_(des, scope, use_lv_type);
} else if (const netaarray_t*atype = dynamic_cast<const netaarray_t*> (lv_net_type)) {
ivl_assert(*this, lv->more==0);
ivl_type_t use_lv_type = lv_net_type;
if (lv->word())
use_lv_type = atype->element_type();
rv = elaborate_rval_(des, scope, use_lv_type);
} else if (const netuarray_t*utype = dynamic_cast<const netuarray_t*>(lv_net_type)) {
ivl_assert(*this, lv->more==0);
if (debug_elaborate) {
@ -4042,6 +4050,12 @@ NetProc* PCallTask::elaborate_sys_task_method_(Design*des, NetScope*scope,
<< "method takes zero or one argument." << endl;
des->errors += 1;
}
} else if (net->aarray_type()) {
if (nparms > 1) {
cerr << get_fileline() << ": error: associative array delete() "
<< "method takes zero or one argument." << endl;
des->errors += 1;
}
} else if (nparms > 0) {
cerr << get_fileline() << ": error: darray delete() "
<< "method takes no arguments." << endl;
@ -4507,6 +4521,22 @@ NetProc* PCallTask::elaborate_method_(Design*des, NetScope*scope,
}
}
// Associative array methods (string-keyed vertical slice).
if (net->aarray_type()) {
if (method_name == "delete") {
static const std::vector<perm_string> parm_names = {
perm_string::literal("index")
};
return elaborate_sys_task_method_(des, scope, net, method_name,
"$ivl_aarray_method$delete",
parm_names);
} else if (method_name == "size" || method_name == "num") {
return elaborate_method_func_(scope, net,
&netvector_t::atom2s32,
method_name, "$size");
}
}
if (net->queue_type()) {
if (method_name == "push_back") {
static const std::vector<perm_string> parm_names = {
@ -6048,6 +6078,74 @@ NetProc* PForeach::elaborate(Design*des, NetScope*scope) const
if (array_sig->unpacked_dimensions() >= index_vars_.size())
return elaborate_static_array_(des, scope, dims);
// Associative array foreach: for (i=0; i<size; i++) { k = key_at(i); body }
if (array_sig->aarray_type()) {
if (index_vars_.size() != 1) {
cerr << get_fileline() << ": sorry: "
<< "Multi-index foreach loops not supported." << endl;
des->errors += 1;
return 0;
}
pform_name_t index_name;
index_name.push_back(name_component_t(index_vars_[0]));
NetNet*idx_sig = des->find_signal(scope, index_name);
ivl_assert(*this, idx_sig);
// Hidden integer loop index
netvector_t*idx_vec = new netvector_t(IVL_VT_BOOL, 31, 0);
idx_vec->set_signed(true);
NetNet*loop_idx = new NetNet(scope, scope->local_symbol(),
NetNet::REG, idx_vec);
loop_idx->set_line(*this);
loop_idx->local_flag(true);
NetESignal*array_exp = new NetESignal(array_sig);
array_exp->set_line(*this);
NetESignal*loop_exp = new NetESignal(loop_idx);
loop_exp->set_line(*this);
NetEConst*zero = make_const_val(0);
zero->set_line(*this);
NetESFunc*size_expr = new NetESFunc("$size", &netvector_t::atom2u32, 1);
size_expr->set_line(*this);
size_expr->parm(0, array_exp);
NetEBComp*cond_expr = new NetEBComp('<', loop_exp, size_expr);
cond_expr->set_line(*this);
NetESFunc*key_at = new NetESFunc("$ivl_aarray_method$key_at",
&netstring_t::type_string, 2);
key_at->set_line(*this);
key_at->parm(0, new NetESignal(array_sig));
key_at->parm(1, new NetESignal(loop_idx));
NetAssign_*key_lv = new NetAssign_(idx_sig);
NetAssign*key_asgn = new NetAssign(key_lv, key_at);
key_asgn->set_line(*this);
NetProc*sub;
if (statement_)
sub = statement_->elaborate(des, scope);
else
sub = new NetBlock(NetBlock::SEQU, 0);
NetBlock*body = new NetBlock(NetBlock::SEQU, 0);
body->set_line(*this);
body->append(key_asgn);
body->append(sub);
NetAssign_*step_lv = new NetAssign_(loop_idx);
NetEConst*step_val = make_const_val(1);
NetAssign*step = new NetAssign(step_lv, '+', step_val);
step->set_line(*this);
NetForLoop*stmt = new NetForLoop(loop_idx, zero, cond_expr, body, step);
stmt->set_line(*this);
return stmt;
}
// At this point, we know that the array is dynamic so we
// handle that slightly differently, using run-time tests.

View File

@ -2195,6 +2195,7 @@ static bool get_array_info(const NetExpr*arg, long dim,
switch (ptype->base_type()) {
case IVL_VT_DARRAY:
case IVL_VT_QUEUE:
case IVL_VT_AARRAY:
case IVL_VT_STRING:
defer = true;
return true;
@ -2211,6 +2212,7 @@ static bool get_array_info(const NetExpr*arg, long dim,
switch (sig->data_type()) {
case IVL_VT_DARRAY:
case IVL_VT_QUEUE:
case IVL_VT_AARRAY:
case IVL_VT_STRING:
defer = true;
return true;

View File

@ -0,0 +1,83 @@
// Associative-array vertical slice smoke test (string keys).
// Also accepts int aa[*] as string-keyed AA in this first slice.
module aa_string;
int aa[string];
int bb[string];
int aa_star[*]; // documented: [*] is string-keyed in this slice
int v, n, e;
int pass;
string k;
int sum;
initial begin
pass = 1;
aa["x"] = 1;
aa["y"] = 2;
aa["z"] = 3;
v = aa["x"];
if (v !== 1) begin
$display("FAIL: aa[\"x\"] read got %0d", v);
pass = 0;
end
n = aa.size();
if (n !== 3) begin
$display("FAIL: size() expected 3 got %0d", n);
pass = 0;
end
n = aa.num();
if (n !== 3) begin
$display("FAIL: num() expected 3 got %0d", n);
pass = 0;
end
e = aa.exists("x");
if (e !== 1) begin
$display("FAIL: exists(\"x\") expected 1 got %0d", e);
pass = 0;
end
e = aa.exists("missing");
if (e !== 0) begin
$display("FAIL: exists(\"missing\") expected 0 got %0d", e);
pass = 0;
end
sum = 0;
foreach (aa[k]) begin
sum = sum + aa[k];
end
if (sum !== 6) begin
$display("FAIL: foreach sum expected 6 got %0d", sum);
pass = 0;
end
bb = aa;
if (bb.size() !== 3 || bb["y"] !== 2) begin
$display("FAIL: whole-array copy");
pass = 0;
end
aa.delete("x");
if (aa.exists("x") !== 0 || aa.size() !== 2) begin
$display("FAIL: delete(\"x\")");
pass = 0;
end
aa.delete();
if (aa.size() !== 0) begin
$display("FAIL: delete() clear all, size=%0d", aa.size());
pass = 0;
end
aa_star["hello"] = 42;
if (aa_star["hello"] !== 42 || aa_star.size() !== 1) begin
$display("FAIL: [*] string-keyed AA");
pass = 0;
end
if (pass) $display("PASSED");
else $display("FAILED");
$finish;
end
endmodule

View File

@ -467,6 +467,7 @@ typedef enum ivl_variable_type_e ENUM_UNSIGNED_INT {
IVL_VT_DARRAY = 6, /* Array (esp. dynamic array) */
IVL_VT_CLASS = 7, /* SystemVerilog class instances */
IVL_VT_QUEUE = 8, /* SystemVerilog queue instances */
IVL_VT_AARRAY = 9, /* SystemVerilog associative array */
IVL_VT_VECTOR = IVL_VT_LOGIC /* For compatibility */
} ivl_variable_type_t;

50
netaarray.cc Normal file
View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2026 Icarus UVM track
*
* This source code is free software; you can redistribute it
* and/or modify it in source code form under the terms of the GNU
* General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
# include "netaarray.h"
# include <iostream>
using namespace std;
netaarray_t::netaarray_t(ivl_type_t vec)
: netarray_t(vec)
{
}
netaarray_t::~netaarray_t()
{
}
ivl_variable_type_t netaarray_t::base_type(void) const
{
return IVL_VT_AARRAY;
}
bool netaarray_t::test_equivalence(ivl_type_t that) const
{
const netaarray_t* that_aa = dynamic_cast<const netaarray_t*>(that);
if (!that_aa) return false;
return test_compatibility(that);
}
bool netaarray_t::test_compatibility(ivl_type_t that) const
{
const netaarray_t *that_aa = dynamic_cast<const netaarray_t*>(that);
if (!that_aa)
return false;
return element_type()->type_equivalent(that_aa->element_type());
}
ostream& netaarray_t::debug_dump(ostream&out) const
{
out << "aarray<" << *element_type() << ">[string]";
return out;
}

47
netaarray.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef IVL_netaarray_H
#define IVL_netaarray_H
/*
* Copyright (c) 2026 Icarus UVM track
*
* This source code is free software; you can redistribute it
* and/or modify it in source code form under the terms of the GNU
* General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
# include "nettypes.h"
# include "ivl_target.h"
/*
* Associative array type.
*
* First vertical slice (UVM Tier A #2): string-keyed arrays only.
* Both `int aa[string];` and `int aa[*];` elaborate to this type with
* string keys. Integer keys, class elements, and nested AAs are deferred.
*/
class netaarray_t : public netarray_t {
public:
explicit netaarray_t(ivl_type_t vec);
~netaarray_t() override;
ivl_variable_type_t base_type() const override;
inline bool get_signed() const override
{ return element_type()->get_signed(); }
inline ivl_variable_type_t element_base_type() const
{ return element_type()->base_type(); }
inline unsigned long element_width(void) const
{ return element_type()->packed_width(); }
std::ostream& debug_dump(std::ostream&) const override;
private:
bool test_compatibility(ivl_type_t that) const override;
bool test_equivalence(ivl_type_t that) const override;
};
#endif /* IVL_netaarray_H */

View File

@ -30,6 +30,7 @@
# include "netmisc.h"
# include "netclass.h"
# include "netdarray.h"
# include "netaarray.h"
# include "netenum.h"
# include "netparray.h"
# include "netscalar.h"
@ -711,6 +712,11 @@ const netdarray_t* NetNet::darray_type(void) const
return dynamic_cast<const netdarray_t*> (net_type_);
}
const netaarray_t* NetNet::aarray_type(void) const
{
return dynamic_cast<const netaarray_t*> (net_type_);
}
const netqueue_t* NetNet::queue_type(void) const
{
return dynamic_cast<const netqueue_t*> (net_type_);
@ -2498,6 +2504,8 @@ ivl_variable_type_t NetESignal::expr_type() const
{
if (net_->darray_type())
return IVL_VT_DARRAY;
if (net_->aarray_type())
return IVL_VT_AARRAY;
else
return net_->data_type();
}

View File

@ -87,6 +87,7 @@ class data_type_t;
struct enum_type_t;
class netclass_t;
class netdarray_t;
class netaarray_t;
class netparray_t;
class netuarray_t;
class netqueue_t;
@ -756,6 +757,7 @@ class NetNet : public NetObj, public PortType {
const netenum_t*enumeration(void) const;
const netstruct_t*struct_type(void) const;
const netdarray_t*darray_type(void) const;
const netaarray_t*aarray_type(void) const;
const netqueue_t*queue_type(void) const;
const netclass_t*class_type(void) const;
const netarray_t*array_type(void) const;

View File

@ -1005,6 +1005,10 @@ NetExpr* elab_and_eval(Design*des, NetScope*scope, PExpr*pe,
if (dynamic_cast<PEAssignPattern*>(pe))
return tmp;
// fall through
case IVL_VT_AARRAY:
if (expr_type == IVL_VT_AARRAY)
return tmp;
break;
case IVL_VT_STRING:
if (dynamic_cast<PEConcat*>(pe))
return tmp;

10
parse.y
View File

@ -2252,7 +2252,7 @@ loop_statement /* IEEE1800-2005: A.6.8 */
PBlock*tmp = pform_push_block_scope(@1, for_block_name, PBlock::BL_SEQ);
current_block_stack.push(tmp);
pform_make_foreach_declarations(@1, $5);
pform_make_foreach_declarations(@1, $3, $5);
}
statement_or_null
{ PForeach*tmp_for = pform_make_foreach(@1, $3, $5, $9);
@ -3203,6 +3203,14 @@ variable_dimension /* IEEE1800-2005: A.2.5 */
tmp->push_back(index);
$$ = tmp;
}
| '[' '*' ']'
{ // Associative array wildcard key. First slice: string-keyed.
std::list<pform_range_t> *tmp = new std::list<pform_range_t>;
pform_range_t index (new PENull, new PENull);
pform_requires_sv(@$, "Associative array declaration");
tmp->push_back(index);
$$ = tmp;
}
| '[' '$' ']'
{ // SystemVerilog queue
list<pform_range_t> *tmp = new std::list<pform_range_t>;

View File

@ -1032,6 +1032,7 @@ void pform_make_var(const struct vlltype&loc,
}
void pform_make_foreach_declarations(const struct vlltype&loc,
char*array_name,
std::list<perm_string>*loop_vars)
{
list<decl_assignment_t*>assign_list;
@ -1044,7 +1045,27 @@ void pform_make_foreach_declarations(const struct vlltype&loc,
assign_list.push_back(tmp_assign);
}
pform_make_var(loc, &assign_list, &size_type);
/* Associative arrays (string / [*] keys in this slice) need string
foreach index variables. Look up the array in enclosing scopes. */
data_type_t*idx_type = &size_type;
perm_string aname = lex_strings.make(array_name);
for (LexicalScope*scp = lexical_scope ? lexical_scope->parent_scope() : 0;
scp; scp = scp->parent_scope()) {
PWire*wire = scp->wires_find(aname);
if (!wire) continue;
const list<pform_range_t>&udims = wire->get_unpacked_idx();
if (udims.empty()) break;
const pform_range_t&r = udims.front();
if ((dynamic_cast<PENull*>(r.first) && dynamic_cast<PENull*>(r.second)) ||
dynamic_cast<PEAArrayKey*>(r.first) ||
dynamic_cast<PETypename*>(r.first)) {
static string_type_t aa_string_idx;
idx_type = &aa_string_idx;
}
break;
}
pform_make_var(loc, &assign_list, idx_type);
}
PForeach* pform_make_foreach(const struct vlltype&loc,

View File

@ -346,6 +346,7 @@ extern PCallTask* pform_make_call_task(const struct vlltype&loc,
const std::list<named_pexpr_t> &parms);
extern void pform_make_foreach_declarations(const struct vlltype&loc,
char*array_name,
std::list<perm_string>*loop_vars);
extern PForeach* pform_make_foreach(const struct vlltype&loc,
char*ident,

View File

@ -506,6 +506,14 @@ void PENull::dump(ostream&out) const
out << "null";
}
void PEAArrayKey::dump(ostream&out) const
{
if (kind_ == STRING)
out << "string";
else
out << "*";
}
void PENumber::dump(ostream&out) const
{
out << value();

View File

@ -93,6 +93,16 @@ typedef std::pair<perm_string, unsigned> pform_ident_t;
* [ $ ] -- Queue type
* first = PENull
* second = 0
*
* [ * ] -- Associative array (wildcard key; this slice treats as string)
* first = PENull
* second = PENull
*
* [ string ] -- Associative array with string keys
* first = PETypename(string_type_t) (via '[' expression ']')
* second = 0
*
* PEAArrayKey may also appear as first for AA sentinels.
*/
typedef std::pair<PExpr*,PExpr*> pform_range_t;

View File

@ -178,6 +178,9 @@ const char*data_type_string(ivl_variable_type_t vtype)
case IVL_VT_QUEUE:
vt = "queue";
break;
case IVL_VT_AARRAY:
vt = "aarray";
break;
}
return vt;

View File

@ -69,6 +69,9 @@ void show_net_type(ivl_type_t net_type)
case IVL_VT_QUEUE:
show_net_type_queue(net_type);
break;
case IVL_VT_AARRAY:
fprintf(out, "aarray");
break;
case IVL_VT_VOID:
fprintf(out, "void");
break;

View File

@ -218,7 +218,14 @@ void draw_eval_string(ivl_expr_t expr)
string_ex_pop(expr);
else if (strcmp(ivl_expr_name(expr), "$ivl_queue_method$pop_front")==0)
string_ex_pop(expr);
else
else if (strcmp(ivl_expr_name(expr), "$ivl_aarray_method$key_at")==0) {
ivl_expr_t arr = ivl_expr_parm(expr, 0);
ivl_expr_t idx = ivl_expr_parm(expr, 1);
assert(ivl_expr_type(arr) == IVL_EX_SIGNAL);
draw_eval_expr_into_integer(idx, 3);
fprintf(vvp_out, " %%aar/key_at v%p_0;\n",
ivl_expr_signal(arr));
} else
draw_sfunc_string(expr);
break;

View File

@ -1013,6 +1013,19 @@ static void draw_select_vec4(ivl_expr_t expr)
return;
}
if (ivl_expr_value(subexpr) == IVL_VT_AARRAY) {
ivl_signal_t sig = ivl_expr_signal(subexpr);
assert(sig);
assert(ivl_signal_data_type(sig)==IVL_VT_AARRAY);
assert(base);
draw_eval_string(base);
fprintf(vvp_out, " %%load/aar/vec4 v%p_0;\n", sig);
if (ivl_expr_value(expr) == IVL_VT_BOOL) {
fprintf(vvp_out, " %%cast2;\n");
}
return;
}
if (test_immediate_vec4_ok(base)) {
unsigned long val0, valx;
unsigned base_wid;
@ -1110,6 +1123,16 @@ static void draw_sfunc_vec4(ivl_expr_t expr)
return;
}
if (strcmp(ivl_expr_name(expr), "$ivl_aarray_method$exists")==0 &&
parm_count == 2) {
ivl_expr_t arr = ivl_expr_parm(expr, 0);
ivl_expr_t key = ivl_expr_parm(expr, 1);
assert(ivl_expr_type(arr) == IVL_EX_SIGNAL);
draw_eval_string(key);
fprintf(vvp_out, " %%aar/exists v%p_0;\n", ivl_expr_signal(arr));
return;
}
/* find*_with has four parameters and is lowered in eval_object.c */
if (parm_count == 4 &&
strncmp(ivl_expr_name(expr), "$ivl_queue_method$",
@ -1128,6 +1151,11 @@ static void draw_sfunc_vec4(ivl_expr_t expr)
fprintf(vvp_out, " %%pop/obj 1, 0;\n");
return;
}
if (ivl_expr_type(arg) == IVL_EX_SIGNAL &&
ivl_signal_data_type(ivl_expr_signal(arg)) == IVL_VT_AARRAY) {
fprintf(vvp_out, " %%aar/size v%p_0;\n", ivl_expr_signal(arg));
return;
}
}
if ((strcmp(ivl_expr_name(expr), "$ivl_queue_method$sum") == 0 ||

View File

@ -1162,6 +1162,59 @@ static int show_stmt_assign_sig_darray(ivl_statement_t net)
return errors;
}
/*
* Associative array assignment (string-keyed vertical slice).
* Indexed: evaluate value then string key, %store/aar/vec4.
* Whole: duplicate object like darray.
*/
static int show_stmt_assign_sig_aarray(ivl_statement_t net)
{
int errors = 0;
ivl_lval_t lval = ivl_stmt_lval(net, 0);
ivl_expr_t rval = ivl_stmt_rval(net);
ivl_signal_t var= ivl_lval_sig(lval);
ivl_type_t var_type= ivl_signal_net_type(var);
assert(ivl_type_base(var_type) == IVL_VT_AARRAY);
ivl_type_t element_type = ivl_type_element(var_type);
assert(ivl_stmt_lvals(net) == 1);
assert(ivl_lval_part_off(lval) == 0);
if (ivl_lval_idx(lval)) {
ivl_expr_t mux = ivl_lval_idx(lval);
assert(ivl_stmt_opcode(net) == 0);
switch (ivl_type_base(element_type)) {
case IVL_VT_BOOL:
case IVL_VT_LOGIC:
draw_eval_vec4(rval);
resize_vec4_wid(rval, ivl_type_packed_width(element_type));
draw_eval_string(mux);
fprintf(vvp_out, " %%store/aar/vec4 v%p_0;\n", var);
break;
default:
fprintf(stderr, "%s:%u: sorry: associative array element "
"type not supported in this slice.\n",
ivl_stmt_file(net), ivl_stmt_lineno(net));
errors += 1;
break;
}
} else if (ivl_expr_type(rval) == IVL_EX_SIGNAL) {
assert(ivl_stmt_opcode(net) == 0);
errors += draw_eval_object(rval);
fprintf(vvp_out, " %%dup/obj;\n");
fprintf(vvp_out, " %%store/obj v%p_0; %s:%u: aarray copy\n",
var, ivl_stmt_file(net), ivl_stmt_lineno(net));
fprintf(vvp_out, " %%pop/obj 1, 0;\n");
} else {
assert(ivl_stmt_opcode(net) == 0);
errors += draw_eval_object(rval);
fprintf(vvp_out, " %%store/obj v%p_0; %s:%u: aarray assign\n",
var, ivl_stmt_file(net), ivl_stmt_lineno(net));
}
return errors;
}
/*
* This function handles the special case that we assign an array
* pattern to a queue. Handle this by assigning each element.
@ -1447,6 +1500,10 @@ int show_stmt_assign(ivl_statement_t net)
return show_stmt_assign_sig_darray(net);
}
if (sig && (ivl_signal_data_type(sig) == IVL_VT_AARRAY)) {
return show_stmt_assign_sig_aarray(net);
}
if (sig && (ivl_signal_data_type(sig) == IVL_VT_QUEUE)) {
return show_stmt_assign_sig_queue(net);
}

View File

@ -1684,6 +1684,16 @@ static int show_delete_method(ivl_statement_t net)
assert(ivl_expr_type(parm) == IVL_EX_SIGNAL);
ivl_signal_t var = ivl_expr_signal(parm);
if (ivl_signal_data_type(var) == IVL_VT_AARRAY) {
if (parm_count == 2) {
draw_eval_string(ivl_stmt_parm(net, 1));
fprintf(vvp_out, " %%delete/aar/str v%p_0;\n", var);
} else {
fprintf(vvp_out, " %%delete/aar v%p_0;\n", var);
}
return 0;
}
/* If this is a queue then it can have an element to delete. */
if (parm_count == 2) {
if (ivl_type_base(ivl_signal_net_type(var)) != IVL_VT_QUEUE)
@ -1938,6 +1948,9 @@ static int show_system_task_call(ivl_statement_t net)
if (strcmp(stmt_name,"$ivl_darray_method$delete") == 0)
return show_delete_method(net);
if (strcmp(stmt_name,"$ivl_aarray_method$delete") == 0)
return show_delete_method(net);
if (strcmp(stmt_name,"$ivl_darray_method$reverse") == 0)
return show_reverse_method(net);

View File

@ -538,6 +538,15 @@ static void draw_reg_in_scope(ivl_signal_t sig)
ivl_type_packed_width(element_type),
ivl_signal_local(sig)? " Local signal" : "");
} else if (ivl_signal_data_type(sig) == IVL_VT_AARRAY) {
ivl_type_t var_type = ivl_signal_net_type(sig);
ivl_type_t element_type = ivl_type_element(var_type);
fprintf(vvp_out, "v%p_0 .var/aarray \"%s\", %u;%s\n", sig,
vvp_mangle_name(ivl_signal_basename(sig)),
ivl_type_packed_width(element_type),
ivl_signal_local(sig)? " Local signal" : "");
} else if (ivl_signal_data_type(sig) == IVL_VT_QUEUE) {
ivl_type_t var_type = ivl_signal_net_type(sig);
ivl_type_t element_type = ivl_type_element(var_type);

View File

@ -50,12 +50,13 @@ static PLI_INT32 dobject_size_compiletf(ICARUS_VPI_CONST PLI_BYTE8*name)
switch(vpi_get(vpiArrayType, arg)) {
case vpiDynamicArray:
case vpiQueueArray:
case vpiAssocArray:
break;
default:
vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
(int)vpi_get(vpiLineNo, callh));
vpi_printf("%s argument must be a dynamic array, queue or "
"string.\n", name);
vpi_printf("%s argument must be a dynamic array, queue, "
"associative array or string.\n", name);
vpip_set_return_value(1);
vpi_control(vpiFinish, 1);
}

View File

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

View File

@ -150,6 +150,7 @@ extern bool of_LOAD_REAL(vthread_t thr, vvp_code_t code);
extern bool of_LOAD_DAR_R(vthread_t thr, vvp_code_t code);
extern bool of_LOAD_DAR_STR(vthread_t thr, vvp_code_t code);
extern bool of_LOAD_DAR_VEC4(vthread_t thr, vvp_code_t code);
extern bool of_LOAD_AAR_VEC4(vthread_t thr, vvp_code_t code);
extern bool of_LOAD_PROP_DAR_VEC4(vthread_t thr, vvp_code_t code);
extern bool of_LOAD_OBJ(vthread_t thr, vvp_code_t code);
extern bool of_LOAD_OBJA(vthread_t thr, vvp_code_t code);
@ -236,6 +237,12 @@ extern bool of_SPLIT_VEC4(vthread_t thr, vvp_code_t code);
extern bool of_STORE_DAR_R(vthread_t thr, vvp_code_t code);
extern bool of_STORE_DAR_STR(vthread_t thr, vvp_code_t code);
extern bool of_STORE_DAR_VEC4(vthread_t thr, vvp_code_t code);
extern bool of_STORE_AAR_VEC4(vthread_t thr, vvp_code_t code);
extern bool of_DELETE_AAR(vthread_t thr, vvp_code_t code);
extern bool of_DELETE_AAR_STR(vthread_t thr, vvp_code_t code);
extern bool of_AAR_EXISTS(vthread_t thr, vvp_code_t code);
extern bool of_AAR_KEY_AT(vthread_t thr, vvp_code_t code);
extern bool of_AAR_SIZE(vthread_t thr, vvp_code_t code);
extern bool of_STORE_OBJ(vthread_t thr, vvp_code_t code);
extern bool of_STORE_OBJA(vthread_t thr, vvp_code_t code);
extern bool of_STORE_PROP_OBJ(vthread_t thr, vvp_code_t code);

View File

@ -89,6 +89,9 @@ struct opcode_table_s {
};
static const struct opcode_table_s opcode_table[] = {
{ "%aar/exists", of_AAR_EXISTS, 1,{OA_FUNC_PTR, OA_NONE, OA_NONE} },
{ "%aar/key_at", of_AAR_KEY_AT, 1,{OA_FUNC_PTR, OA_NONE, OA_NONE} },
{ "%aar/size", of_AAR_SIZE, 1,{OA_FUNC_PTR, OA_NONE, OA_NONE} },
{ "%abs/wr", of_ABS_WR, 0, {OA_NONE, OA_NONE, OA_NONE} },
{ "%add", of_ADD, 0, {OA_NONE, OA_NONE, OA_NONE} },
{ "%add/wr", of_ADD_WR, 0, {OA_NONE, OA_NONE, OA_NONE} },
@ -155,6 +158,8 @@ static const struct opcode_table_s opcode_table[] = {
{ "%debug/thr", of_DEBUG_THR, 1,{OA_STRING, OA_NONE, OA_NONE} },
{ "%delay", of_DELAY, 2, {OA_BIT1, OA_BIT2, OA_NONE} },
{ "%delayx", of_DELAYX, 1, {OA_NUMBER, OA_NONE, OA_NONE} },
{ "%delete/aar", of_DELETE_AAR, 1,{OA_FUNC_PTR, OA_NONE, OA_NONE} },
{ "%delete/aar/str",of_DELETE_AAR_STR,1,{OA_FUNC_PTR, OA_NONE, OA_NONE} },
{ "%delete/elem",of_DELETE_ELEM,1,{OA_FUNC_PTR,OA_NONE,OA_NONE} },
{ "%delete/obj",of_DELETE_OBJ,1,{OA_FUNC_PTR,OA_NONE, OA_NONE} },
{ "%delete/prop/elem", of_DELETE_PROP_ELEM, 1, {OA_NUMBER, OA_NONE, OA_NONE} },
@ -206,6 +211,7 @@ static const struct opcode_table_s opcode_table[] = {
{ "%jmp/1xz",of_JMP1XZ, 2, {OA_CODE_PTR, OA_BIT1, OA_NONE} },
{ "%join", of_JOIN, 0, {OA_NONE, OA_NONE, OA_NONE} },
{ "%join/detach",of_JOIN_DETACH,1,{OA_NUMBER,OA_NONE, OA_NONE} },
{ "%load/aar/vec4",of_LOAD_AAR_VEC4,1, {OA_FUNC_PTR, OA_NONE, OA_NONE} },
{ "%load/ar",of_LOAD_AR,2, {OA_ARR_PTR, OA_BIT1, OA_NONE} },
{ "%load/dar/r", of_LOAD_DAR_R, 1, {OA_FUNC_PTR, OA_NONE, OA_NONE}},
{ "%load/dar/str",of_LOAD_DAR_STR, 1, {OA_FUNC_PTR, OA_NONE, OA_NONE} },
@ -340,6 +346,7 @@ static const struct opcode_table_s opcode_table[] = {
{ "%sort/obj", of_SORT_OBJ, 1, {OA_FUNC_PTR, OA_NONE, OA_NONE} },
{ "%sort/prop/obj", of_SORT_PROP_OBJ, 1, {OA_NUMBER, OA_NONE, OA_NONE} },
{ "%split/vec4", of_SPLIT_VEC4, 1,{OA_NUMBER, OA_NONE, OA_NONE} },
{ "%store/aar/vec4",of_STORE_AAR_VEC4,1,{OA_FUNC_PTR, OA_NONE, OA_NONE} },
{ "%store/dar/r", of_STORE_DAR_R, 1,{OA_FUNC_PTR, OA_NONE, OA_NONE} },
{ "%store/dar/str", of_STORE_DAR_STR, 1,{OA_FUNC_PTR, OA_NONE, OA_NONE} },
{ "%store/dar/vec4",of_STORE_DAR_VEC4,1,{OA_FUNC_PTR, OA_NONE, OA_NONE} },

View File

@ -511,6 +511,7 @@ extern void compile_variable(char*label, char*name,
extern void compile_var_real(char*label, char*name, bool local_flag);
extern void compile_var_string(char*label, char*name);
extern void compile_var_darray(char*label, char*name, unsigned size);
extern void compile_var_aarray(char*label, char*name, unsigned size);
extern void compile_var_cobject(char*label, char*name);
extern void compile_var_queue(char*label, char*name, unsigned size);

View File

@ -244,6 +244,7 @@ inline uint64_t strtouint64(const char*str, char**endptr, int base)
".var" { return K_VAR; }
".var/cobj" { return K_VAR_COBJECT; }
".var/darray" { return K_VAR_DARRAY; }
".var/aarray" { return K_VAR_AARRAY; }
".var/queue" { return K_VAR_QUEUE; }
".var/real" { return K_VAR_R; }
".var/s" { return K_VAR_S; }

View File

@ -100,7 +100,7 @@ static struct __vpiModPath*modpath_dst = 0;
%token K_SUBSTITUTE
%token K_THREAD K_TIMESCALE K_TRAN K_TRANIF0 K_TRANIF1 K_TRANVP
%token K_UFUNC_REAL K_UFUNC_VEC4 K_UFUNC_E K_UDP K_UDP_C K_UDP_S
%token K_VAR K_VAR_COBJECT K_VAR_DARRAY
%token K_VAR K_VAR_COBJECT K_VAR_DARRAY K_VAR_AARRAY
%token K_VAR_QUEUE
%token K_VAR_S K_VAR_STR K_VAR_I K_VAR_R K_VAR_2S K_VAR_2U
%token K_vpi_call K_vpi_call_w K_vpi_call_i
@ -772,6 +772,9 @@ statement
| T_LABEL K_VAR_DARRAY T_STRING ',' T_NUMBER ';'
{ compile_var_darray($1, $3, $5); }
| T_LABEL K_VAR_AARRAY T_STRING ',' T_NUMBER ';'
{ compile_var_aarray($1, $3, $5); }
| T_LABEL K_VAR_QUEUE T_STRING ',' T_NUMBER';'
{ compile_var_queue($1, $3, $5); }

View File

@ -22,6 +22,7 @@
# include "vpi_priv.h"
# include "vvp_net_sig.h"
# include "vvp_darray.h"
# include "vvp_aarray.h"
# include "vvp_cobject.h"
# include "array_common.h"
# include "schedule.h"
@ -294,6 +295,49 @@ vpiHandle vpip_make_darray_var(const char*name, vvp_net_t*net)
return obj;
}
class __vpiAarrayVar : public __vpiBaseVar {
public:
__vpiAarrayVar(__vpiScope*sc, const char*na, vvp_net_t*ne)
: __vpiBaseVar(sc, na, ne) { }
int get_type_code() const override { return vpiArrayVar; }
int vpi_get(int code) override {
switch (code) {
case vpiArrayType:
return vpiAssocArray;
case vpiSize: {
const vvp_fun_signal_object*fun =
dynamic_cast<const vvp_fun_signal_object*>(get_net()->fun);
if (!fun) return 0;
vvp_object_t val = fun->get_object();
const vvp_aarray*aval = val.peek<vvp_aarray>();
return aval ? (int)aval->get_size() : 0;
}
default:
return 0;
}
}
char* vpi_get_str(int code) override {
if (code == vpiFile)
return simple_set_rbuf_str(file_names[0]);
return generic_get_str(code, scope_, name_, NULL);
}
void vpi_get_value(p_vpi_value val) override {
val->format = vpiSuppressVal;
}
};
vpiHandle vpip_make_aarray_var(const char*name, vvp_net_t*net)
{
__vpiScope*scope = vpip_peek_current_scope();
const char*use_name = name ? vpip_name_string(name) : NULL;
return new __vpiAarrayVar(scope, use_name, net);
}
__vpiQueueVar::__vpiQueueVar(__vpiScope*sc, const char*na, vvp_net_t*ne)
: __vpiDarrayVar(sc, na, ne)
{

View File

@ -869,6 +869,7 @@ class __vpiDarrayVar : public __vpiBaseVar, public __vpiArrayBase {
};
extern vpiHandle vpip_make_darray_var(const char*name, vvp_net_t*net);
extern vpiHandle vpip_make_aarray_var(const char*name, vvp_net_t*net);
class __vpiQueueVar : public __vpiDarrayVar {

View File

@ -27,6 +27,7 @@
# include "vvp_net_sig.h"
# include "vvp_cobject.h"
# include "vvp_darray.h"
# include "vvp_aarray.h"
# include "class_type.h"
#ifdef CHECK_WITH_VALGRIND
# include "vvp_cleanup.h"
@ -6037,6 +6038,126 @@ bool of_STORE_DAR_VEC4(vthread_t thr, vvp_code_t cp)
return store_dar<vvp_vector4_t>(thr, cp);
}
static vvp_aarray* get_aarray(vvp_code_t cp)
{
vvp_net_t*net = cp->net;
assert(net);
vvp_fun_signal_object*obj = dynamic_cast<vvp_fun_signal_object*>(net->fun);
assert(obj);
return obj->get_object().peek<vvp_aarray>();
}
/*
* %store/aar/vec4 <var>
* string key on string stack, vec4 value on vec4 stack.
*/
bool of_STORE_AAR_VEC4(vthread_t thr, vvp_code_t cp)
{
string key = thr->pop_str();
vvp_vector4_t value = thr->pop_vec4();
vvp_aarray*aa = get_aarray(cp);
if (!aa) {
/* Lazily create if somehow missing. */
vvp_net_t*net = cp->net;
vvp_fun_signal_object*obj = dynamic_cast<vvp_fun_signal_object*>(net->fun);
unsigned wid = obj ? obj->size() : value.size();
vvp_object_t empty(new vvp_aarray_vec4(wid));
vvp_net_ptr_t ptr(net, 0);
vvp_send_object(ptr, empty, thr->wt_context);
aa = empty.peek<vvp_aarray>();
}
aa->set_word(key, value);
return true;
}
/*
* %load/aar/vec4 <var>
* string key on string stack; push vec4 value.
*/
bool of_LOAD_AAR_VEC4(vthread_t thr, vvp_code_t cp)
{
string key = thr->pop_str();
vvp_aarray*aa = get_aarray(cp);
vvp_vector4_t word;
if (aa)
aa->get_word(key, word);
else {
vvp_fun_signal_object*obj = dynamic_cast<vvp_fun_signal_object*>(cp->net->fun);
word = vvp_vector4_t(obj ? obj->size() : 1, BIT4_X);
}
thr->push_vec4(word);
return true;
}
/*
* %delete/aar <var> clear all entries
*/
bool of_DELETE_AAR(vthread_t thr, vvp_code_t cp)
{
(void) thr;
vvp_aarray*aa = get_aarray(cp);
if (aa) aa->clear();
return true;
}
/*
* %delete/aar/str <var> erase one key (string stack)
*/
bool of_DELETE_AAR_STR(vthread_t thr, vvp_code_t cp)
{
string key = thr->pop_str();
vvp_aarray*aa = get_aarray(cp);
if (aa) aa->erase(key);
return true;
}
/*
* %aar/exists <var> pop string key, push 32-bit 0/1
*/
bool of_AAR_EXISTS(vthread_t thr, vvp_code_t cp)
{
string key = thr->pop_str();
vvp_aarray*aa = get_aarray(cp);
int present = (aa && aa->exists(key)) ? 1 : 0;
vvp_vector4_t res(32, BIT4_0);
if (present)
res.set_bit(0, BIT4_1);
thr->push_vec4(res);
return true;
}
/*
* %aar/key_at <var> index in words[3], push string key
*/
bool of_AAR_KEY_AT(vthread_t thr, vvp_code_t cp)
{
int64_t adr = thr->words[3].w_int;
vvp_aarray*aa = get_aarray(cp);
string key;
if (aa && adr >= 0)
key = aa->key_at((size_t)adr);
thr->push_str(key);
return true;
}
/*
* %aar/size <var> push 32-bit entry count
*/
bool of_AAR_SIZE(vthread_t thr, vvp_code_t cp)
{
vvp_aarray*aa = get_aarray(cp);
size_t sz = aa ? aa->get_size() : 0;
vvp_vector4_t val(32, BIT4_0);
unsigned long ul = sz;
for (unsigned idx = 0; idx < 32; idx++) {
val.set_bit(idx, (ul & 1UL) ? BIT4_1 : BIT4_0);
ul >>= 1;
}
thr->push_vec4(val);
return true;
}
bool of_STORE_OBJ(vthread_t thr, vvp_code_t cp)
{
/* set the value into port 0 of the destination. */

94
vvp/vvp_aarray.cc Normal file
View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2026 Icarus UVM track
*/
# include "vvp_aarray.h"
# include <cassert>
using namespace std;
vvp_aarray::~vvp_aarray()
{
}
void vvp_aarray::set_word(const string&, const vvp_vector4_t&)
{
assert(0);
}
void vvp_aarray::get_word(const string&, vvp_vector4_t&) const
{
assert(0);
}
vvp_aarray_vec4::vvp_aarray_vec4(unsigned word_wid)
: word_wid_(word_wid)
{
}
vvp_aarray_vec4::~vvp_aarray_vec4()
{
}
size_t vvp_aarray_vec4::get_size() const
{
return map_.size();
}
void vvp_aarray_vec4::clear()
{
map_.clear();
}
bool vvp_aarray_vec4::exists(const string&key) const
{
return map_.find(key) != map_.end();
}
void vvp_aarray_vec4::erase(const string&key)
{
map_.erase(key);
}
void vvp_aarray_vec4::set_word(const string&key, const vvp_vector4_t&value)
{
vvp_vector4_t tmp = value;
if (tmp.size() != word_wid_)
tmp.resize(word_wid_);
map_[key] = tmp;
}
void vvp_aarray_vec4::get_word(const string&key, vvp_vector4_t&value) const
{
map<string,vvp_vector4_t>::const_iterator cur = map_.find(key);
if (cur == map_.end()) {
value = vvp_vector4_t(word_wid_, BIT4_X);
return;
}
value = cur->second;
}
string vvp_aarray_vec4::key_at(size_t idx) const
{
if (idx >= map_.size())
return string();
map<string,vvp_vector4_t>::const_iterator cur = map_.begin();
for (size_t i = 0; i < idx; i += 1)
++cur;
return cur->first;
}
void vvp_aarray_vec4::shallow_copy(const vvp_object*obj)
{
const vvp_aarray_vec4*that = dynamic_cast<const vvp_aarray_vec4*>(obj);
assert(that);
map_ = that->map_;
word_wid_ = that->word_wid_;
}
vvp_object* vvp_aarray_vec4::duplicate() const
{
vvp_aarray_vec4*obj = new vvp_aarray_vec4(word_wid_);
obj->map_ = map_;
return obj;
}

60
vvp/vvp_aarray.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef IVL_vvp_aarray_H
#define IVL_vvp_aarray_H
/*
* Copyright (c) 2026 Icarus UVM track
*
* Associative-array runtime (string-keyed vertical slice).
* Deferred: int keys, class elements, nested AAs.
*/
# include "vvp_object.h"
# include "vvp_net.h"
# include <map>
# include <string>
# include <vector>
class vvp_aarray : public vvp_object {
public:
inline vvp_aarray() { }
virtual ~vvp_aarray() override;
virtual size_t get_size(void) const =0;
virtual void clear(void) =0;
virtual bool exists(const std::string&key) const =0;
virtual void erase(const std::string&key) =0;
virtual void set_word(const std::string&key, const vvp_vector4_t&value);
virtual void get_word(const std::string&key, vvp_vector4_t&value) const;
virtual std::string key_at(size_t idx) const =0;
};
class vvp_aarray_vec4 : public vvp_aarray {
public:
explicit vvp_aarray_vec4(unsigned word_wid);
~vvp_aarray_vec4() override;
size_t get_size(void) const override;
void clear(void) override;
bool exists(const std::string&key) const override;
void erase(const std::string&key) override;
void set_word(const std::string&key, const vvp_vector4_t&value) override;
void get_word(const std::string&key, vvp_vector4_t&value) const override;
std::string key_at(size_t idx) const override;
void shallow_copy(const vvp_object*obj) override;
vvp_object* duplicate(void) const override;
unsigned word_wid() const { return word_wid_; }
private:
std::map<std::string, vvp_vector4_t> map_;
unsigned word_wid_;
};
#endif /* IVL_vvp_aarray_H */

View File

@ -21,6 +21,7 @@
# include "vpi_priv.h"
# include "array.h"
# include "vvp_net_sig.h"
# include "vvp_aarray.h"
# include "logic.h"
# include "schedule.h"
#ifdef CHECK_WITH_VALGRIND
@ -143,6 +144,36 @@ void compile_var_queue(char*label, char*name, unsigned size)
delete[] name;
}
void compile_var_aarray(char*label, char*name, unsigned size)
{
vvp_net_t*net = new vvp_net_t;
if (vpip_peek_current_scope()->is_automatic()) {
vvp_fun_signal_object_aa*tmp = new vvp_fun_signal_object_aa(size);
net->fil = tmp;
net->fun = tmp;
} else {
net->fil = 0;
net->fun = new vvp_fun_signal_object_sa(size);
}
define_functor_symbol(label, net);
/* Immediately bind an empty aarray so methods work without `new`. */
{
vvp_object_t empty(new vvp_aarray_vec4(size));
vvp_net_ptr_t ptr(net, 0);
vvp_send_object(ptr, empty, 0);
}
vpiHandle obj = vpip_make_aarray_var(name, net);
compile_vpi_symbol(label, obj);
vpip_attach_to_current_scope(obj);
free(label);
delete[] name;
}
void compile_var_cobject(char*label, char*name)
{
vvp_net_t*net = new vvp_net_t;