From bcc026c9026b813c61ca802e815e416286cf44d6 Mon Sep 17 00:00:00 2001 From: mjoekhan Date: Tue, 21 Jul 2026 17:12:16 +0500 Subject: [PATCH] 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. --- Makefile.in | 2 +- PExpr.cc | 9 +++ PExpr.h | 27 +++++++ PWire.h | 1 + design_dump.cc | 3 + docs/ROADMAP.md | 4 +- docs/STATUS.md | 4 +- docs/assoc-array.md | 38 ++++++++++ elab_expr.cc | 100 ++++++++++++++++++++++++ elab_lval.cc | 10 ++- elab_type.cc | 38 ++++++++++ elaborate.cc | 98 ++++++++++++++++++++++++ eval_tree.cc | 2 + examples/assoc_array/aa_string.sv | 83 ++++++++++++++++++++ ivl_target.h | 1 + netaarray.cc | 50 ++++++++++++ netaarray.h | 47 ++++++++++++ netlist.cc | 8 ++ netlist.h | 2 + netmisc.cc | 4 + parse.y | 10 ++- pform.cc | 23 +++++- pform.h | 1 + pform_dump.cc | 8 ++ pform_types.h | 10 +++ tgt-stub/stub.c | 3 + tgt-stub/types.c | 3 + tgt-vvp/eval_string.c | 9 ++- tgt-vvp/eval_vec4.c | 28 +++++++ tgt-vvp/stmt_assign.c | 57 ++++++++++++++ tgt-vvp/vvp_process.c | 13 ++++ tgt-vvp/vvp_scope.c | 9 +++ vpi/sys_darray.c | 5 +- vvp/Makefile.in | 2 +- vvp/codes.h | 7 ++ vvp/compile.cc | 7 ++ vvp/compile.h | 1 + vvp/lexor.lex | 1 + vvp/parse.y | 5 +- vvp/vpi_darray.cc | 44 +++++++++++ vvp/vpi_priv.h | 1 + vvp/vthread.cc | 121 ++++++++++++++++++++++++++++++ vvp/vvp_aarray.cc | 94 +++++++++++++++++++++++ vvp/vvp_aarray.h | 60 +++++++++++++++ vvp/words.cc | 31 ++++++++ 45 files changed, 1068 insertions(+), 16 deletions(-) create mode 100644 docs/assoc-array.md create mode 100644 examples/assoc_array/aa_string.sv create mode 100644 netaarray.cc create mode 100644 netaarray.h create mode 100644 vvp/vvp_aarray.cc create mode 100644 vvp/vvp_aarray.h diff --git a/Makefile.in b/Makefile.in index f97cd0743..d50073c72 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 \ diff --git a/PExpr.cc b/PExpr.cc index ad6168a65..6ee1887d4 100644 --- a/PExpr.cc +++ b/PExpr.cc @@ -391,6 +391,15 @@ PENull::~PENull() { } +PEAArrayKey::PEAArrayKey(key_kind_t kind) +: kind_(kind) +{ +} + +PEAArrayKey::~PEAArrayKey() +{ +} + PEFNumber::PEFNumber(verireal*v) : value_(v) { diff --git a/PExpr.h b/PExpr.h index c9d7ad94d..b9de5104a 100644 --- a/PExpr.h +++ b/PExpr.h @@ -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: diff --git a/PWire.h b/PWire.h index f7c8c51ea..fa3e5723d 100644 --- a/PWire.h +++ b/PWire.h @@ -80,6 +80,7 @@ class PWire : public PNamedItem { void set_range(const std::list&ranges, PWSRType type); void set_unpacked_idx(const std::list&ranges); + const std::list& get_unpacked_idx() const { return unpacked_; } void set_data_type(data_type_t*type); diff --git a/design_dump.cc b/design_dump.cc index ee9a1cb8e..47ba3052d 100644 --- a/design_dump.cc +++ b/design_dump.cc @@ -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; } diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 9416b3886..3d4b041c5 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -4,8 +4,8 @@ Each item should be a dedicated `feat/` 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) diff --git a/docs/STATUS.md b/docs/STATUS.md index 4c0c09235..6eeb8c995 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -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-man’s 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 | diff --git a/docs/assoc-array.md b/docs/assoc-array.md new file mode 100644 index 000000000..7a690d283 --- /dev/null +++ b/docs/assoc-array.md @@ -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`). + +## 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). diff --git a/elab_expr.cc b/elab_expr.cc index 83ee2a7eb..267297ca1 100644 --- a/elab_expr.cc +++ b/elab_expr.cc @@ -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; diff --git a/elab_lval.cc b/elab_lval.cc index 2251c186a..bd46d3d28 100644 --- a/elab_lval.cc +++ b/elab_lval.cc @@ -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); diff --git a/elab_type.cc b/elab_type.cc index c905791e1..ad44c2899 100644 --- a/elab_type.cc +++ b/elab_type.cc @@ -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(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(lidx) && dynamic_cast(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(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(lidx)) { + // `[string]` arrives as PETypename via `'[' expression ']'` + // (dedicated K_string rule conflicts with typename-as-expr). + if (dynamic_cast(tn->get_type()) && + (ridx == 0 || dynamic_cast(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(lidx)) { // Special case: Detect the mark for a QUEUE declaration, // which is the dimensions [null:max_idx]. diff --git a/elaborate.cc b/elaborate.cc index 5fb337569..4722b8b80 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -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 (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(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 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 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; iaarray_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. diff --git a/eval_tree.cc b/eval_tree.cc index cab465892..3ed161644 100644 --- a/eval_tree.cc +++ b/eval_tree.cc @@ -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; diff --git a/examples/assoc_array/aa_string.sv b/examples/assoc_array/aa_string.sv new file mode 100644 index 000000000..0e55012f8 --- /dev/null +++ b/examples/assoc_array/aa_string.sv @@ -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 diff --git a/ivl_target.h b/ivl_target.h index b4eec2c3b..7221aaed6 100644 --- a/ivl_target.h +++ b/ivl_target.h @@ -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; diff --git a/netaarray.cc b/netaarray.cc new file mode 100644 index 000000000..769d60f12 --- /dev/null +++ b/netaarray.cc @@ -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 + +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(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(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; +} diff --git a/netaarray.h b/netaarray.h new file mode 100644 index 000000000..ac8fe9b1e --- /dev/null +++ b/netaarray.h @@ -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 */ diff --git a/netlist.cc b/netlist.cc index dd7b4c261..557d29878 100644 --- a/netlist.cc +++ b/netlist.cc @@ -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 (net_type_); } +const netaarray_t* NetNet::aarray_type(void) const +{ + return dynamic_cast (net_type_); +} + const netqueue_t* NetNet::queue_type(void) const { return dynamic_cast (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(); } diff --git a/netlist.h b/netlist.h index d485a5be8..6d83c8d2f 100644 --- a/netlist.h +++ b/netlist.h @@ -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; diff --git a/netmisc.cc b/netmisc.cc index 39cc4540c..802e00ca8 100644 --- a/netmisc.cc +++ b/netmisc.cc @@ -1005,6 +1005,10 @@ NetExpr* elab_and_eval(Design*des, NetScope*scope, PExpr*pe, if (dynamic_cast(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(pe)) return tmp; diff --git a/parse.y b/parse.y index 5279d022f..7c5ed3297 100644 --- a/parse.y +++ b/parse.y @@ -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 *tmp = new std::list; + pform_range_t index (new PENull, new PENull); + pform_requires_sv(@$, "Associative array declaration"); + tmp->push_back(index); + $$ = tmp; + } | '[' '$' ']' { // SystemVerilog queue list *tmp = new std::list; diff --git a/pform.cc b/pform.cc index 39b42fbe7..5e5e7c5f8 100644 --- a/pform.cc +++ b/pform.cc @@ -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*loop_vars) { listassign_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&udims = wire->get_unpacked_idx(); + if (udims.empty()) break; + const pform_range_t&r = udims.front(); + if ((dynamic_cast(r.first) && dynamic_cast(r.second)) || + dynamic_cast(r.first) || + dynamic_cast(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, diff --git a/pform.h b/pform.h index b276b52e0..6c1ed7fc1 100644 --- a/pform.h +++ b/pform.h @@ -346,6 +346,7 @@ extern PCallTask* pform_make_call_task(const struct vlltype&loc, const std::list &parms); extern void pform_make_foreach_declarations(const struct vlltype&loc, + char*array_name, std::list*loop_vars); extern PForeach* pform_make_foreach(const struct vlltype&loc, char*ident, diff --git a/pform_dump.cc b/pform_dump.cc index 0361bec15..9bc4ad1ff 100644 --- a/pform_dump.cc +++ b/pform_dump.cc @@ -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(); diff --git a/pform_types.h b/pform_types.h index 021229e53..554dd4dca 100644 --- a/pform_types.h +++ b/pform_types.h @@ -93,6 +93,16 @@ typedef std::pair 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 pform_range_t; diff --git a/tgt-stub/stub.c b/tgt-stub/stub.c index 3d19f5bc7..95603a5aa 100644 --- a/tgt-stub/stub.c +++ b/tgt-stub/stub.c @@ -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; diff --git a/tgt-stub/types.c b/tgt-stub/types.c index 38a6b38c6..8d276af65 100644 --- a/tgt-stub/types.c +++ b/tgt-stub/types.c @@ -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; diff --git a/tgt-vvp/eval_string.c b/tgt-vvp/eval_string.c index 25ab2a180..9668fc81e 100644 --- a/tgt-vvp/eval_string.c +++ b/tgt-vvp/eval_string.c @@ -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; diff --git a/tgt-vvp/eval_vec4.c b/tgt-vvp/eval_vec4.c index 6d97567de..d21a19cd9 100644 --- a/tgt-vvp/eval_vec4.c +++ b/tgt-vvp/eval_vec4.c @@ -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 || diff --git a/tgt-vvp/stmt_assign.c b/tgt-vvp/stmt_assign.c index 9e3d3a5b2..1830b1560 100644 --- a/tgt-vvp/stmt_assign.c +++ b/tgt-vvp/stmt_assign.c @@ -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); } diff --git a/tgt-vvp/vvp_process.c b/tgt-vvp/vvp_process.c index cff3f90ef..0fe946ade 100644 --- a/tgt-vvp/vvp_process.c +++ b/tgt-vvp/vvp_process.c @@ -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); diff --git a/tgt-vvp/vvp_scope.c b/tgt-vvp/vvp_scope.c index 5600e1abd..b8b5b3543 100644 --- a/tgt-vvp/vvp_scope.c +++ b/tgt-vvp/vvp_scope.c @@ -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); diff --git a/vpi/sys_darray.c b/vpi/sys_darray.c index 41a045818..12f635f90 100644 --- a/vpi/sys_darray.c +++ b/vpi/sys_darray.c @@ -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); } diff --git a/vvp/Makefile.in b/vvp/Makefile.in index 32a6aa0a5..cf45e0a7b 100644 --- a/vvp/Makefile.in +++ b/vvp/Makefile.in @@ -147,7 +147,7 @@ CORE_OBJ = lib_main.o \ substitute.o \ symbols.o ufunc.o codes.o vthread.o schedule.o \ statistics.o tables.o udp.o vvp_island.o vvp_net.o vvp_net_sig.o \ - vvp_object.o vvp_cobject.o vvp_darray.o 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 \ diff --git a/vvp/codes.h b/vvp/codes.h index fa3c2e73f..a08d02659 100644 --- a/vvp/codes.h +++ b/vvp/codes.h @@ -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); diff --git a/vvp/compile.cc b/vvp/compile.cc index 8fdce1abf..8592cae6c 100644 --- a/vvp/compile.cc +++ b/vvp/compile.cc @@ -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} }, diff --git a/vvp/compile.h b/vvp/compile.h index a813db3b2..2ed86634c 100644 --- a/vvp/compile.h +++ b/vvp/compile.h @@ -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); diff --git a/vvp/lexor.lex b/vvp/lexor.lex index 122bcaf45..6ee8fcc12 100644 --- a/vvp/lexor.lex +++ b/vvp/lexor.lex @@ -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; } diff --git a/vvp/parse.y b/vvp/parse.y index b8af4e50f..2372151e2 100644 --- a/vvp/parse.y +++ b/vvp/parse.y @@ -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); } diff --git a/vvp/vpi_darray.cc b/vvp/vpi_darray.cc index a81dcd6ba..a577f252a 100644 --- a/vvp/vpi_darray.cc +++ b/vvp/vpi_darray.cc @@ -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(get_net()->fun); + if (!fun) return 0; + vvp_object_t val = fun->get_object(); + const vvp_aarray*aval = val.peek(); + 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) { diff --git a/vvp/vpi_priv.h b/vvp/vpi_priv.h index b0fc1e322..741b2fc83 100644 --- a/vvp/vpi_priv.h +++ b/vvp/vpi_priv.h @@ -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 { diff --git a/vvp/vthread.cc b/vvp/vthread.cc index b70a81334..7a3ad62e1 100644 --- a/vvp/vthread.cc +++ b/vvp/vthread.cc @@ -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(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(net->fun); + assert(obj); + return obj->get_object().peek(); +} + +/* + * %store/aar/vec4 + * 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(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(); + } + aa->set_word(key, value); + return true; +} + +/* + * %load/aar/vec4 + * 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(cp->net->fun); + word = vvp_vector4_t(obj ? obj->size() : 1, BIT4_X); + } + thr->push_vec4(word); + return true; +} + +/* + * %delete/aar — 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 — 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 — 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 — 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 — 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. */ diff --git a/vvp/vvp_aarray.cc b/vvp/vvp_aarray.cc new file mode 100644 index 000000000..c1c04f70c --- /dev/null +++ b/vvp/vvp_aarray.cc @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2026 Icarus UVM track + */ + +# include "vvp_aarray.h" +# include + +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::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::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(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; +} diff --git a/vvp/vvp_aarray.h b/vvp/vvp_aarray.h new file mode 100644 index 000000000..6114c777b --- /dev/null +++ b/vvp/vvp_aarray.h @@ -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 +# include +# include + +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 map_; + unsigned word_wid_; +}; + +#endif /* IVL_vvp_aarray_H */ diff --git a/vvp/words.cc b/vvp/words.cc index 6291a7646..4c2af5a42 100644 --- a/vvp/words.cc +++ b/vvp/words.cc @@ -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;