diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 57bae2566..17feedeb7 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -8,7 +8,7 @@ Each item should be a dedicated `feat/` branch, with tests/examples and a 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` — **partial** (see [virtual-interface.md](virtual-interface.md)) 4. **Clocking blocks** — enough for `@(vif.cb)` — **partial** (interface-local `@(bif.cb)`; see [clocking.md](clocking.md)) -5. **`mailbox` / `semaphore` builtins** (or solid class equivalents with blocking put/get) +5. **`mailbox` / `semaphore` builtins** (or solid class equivalents with blocking put/get) — **partial** (int mailbox + semaphore; see [mailbox-semaphore.md](mailbox-semaphore.md)) 6. **Constraints + `randomize()` / `randomize() with`** — start unconstrained `rand`, then solver 7. **`$cast` / `$typename` hardening** for factory patterns 8. **Covergroups** — functional coverage diff --git a/docs/STATUS.md b/docs/STATUS.md index 1dde44418..7e0a14978 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -11,12 +11,13 @@ Last updated: 2026-07-21 | Area | Status | |------|--------| | 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 | +| [`uvm/`](../uvm/) | Seeded from IVL_UVM (VerifWorks) — messaging, CLP, stub phases, legacy poor-man’s mailbox/semaphore classes; **not** Accellera-compatible. Prefer compiler builtins for `mailbox`/`semaphore` (see STATUS row below). | | [`examples/hello_uvm`](../examples/hello_uvm) | Smoke TB for the seeded library (`Makefile` included) | | Parameterized classes | **Partial** on `feat/param-classes` (merged): ANSI `class C #(type T = int, parameter int W = 8);` parses into the class scope and elaborates with **defaults**. Explicit specializations `C#(byte)` / overrides not done yet. See [`examples/param_classes`](../examples/param_classes). | | Associative arrays | **Partial** on `feat/assoc-array`: string-keyed `int aa[string]` / `int aa[*]` with size/num/exists/delete/foreach/copy. See [`docs/assoc-array.md`](assoc-array.md) and [`examples/assoc_array`](../examples/assoc_array). | | Virtual interfaces | **Partial** on `feat/virtual-interface`: `virtual interface T` as class property / TF arg; assign interface instance; member R/W; `@(posedge vif.clk)`. See [`docs/virtual-interface.md`](virtual-interface.md) and [`examples/virtual_interface`](../examples/virtual_interface). | | Clocking blocks | **Partial** on `feat/clocking-blocks`: interface-local `clocking`; `@(bif.cb)`; `cb.sig` R/W with `#0` skew. See [`docs/clocking.md`](clocking.md) and [`examples/clocking`](../examples/clocking). | +| Mailbox / semaphore | **Partial** on `feat/mailbox-semaphore`: compiler builtins; `mailbox #(int)` + blocking/nonblocking put/get/num; `semaphore` get/put/try_get. See [`docs/mailbox-semaphore.md`](mailbox-semaphore.md) and [`examples/mailbox_sem`](../examples/mailbox_sem). | | Constraints / randomize | Missing | | Covergroups / DPI | Missing | diff --git a/docs/mailbox-semaphore.md b/docs/mailbox-semaphore.md new file mode 100644 index 000000000..431abfa20 --- /dev/null +++ b/docs/mailbox-semaphore.md @@ -0,0 +1,60 @@ +# Mailbox / semaphore (Tier A #5) + +Status: **partial** — compiler builtins for Accellera-style IPC (int messages). + +## Choice: compiler builtins (not library classes) + +IEEE `mailbox` / `semaphore` are built-in classes. A pure SV library cannot implement the required API here because: + +- Functions cannot take `output`/`ref` ports (`try_get(v)` needs a side-effecting write). +- Class `event` properties are not supported yet (needed for efficient wait/wakeup). + +This fork therefore implements **native builtins**, similar in shape to `string` / queue method sfuncs: empty `netclass_t` types named `mailbox` / `semaphore`, methods lowered to `$ivl_mailbox$*` / `$ivl_semaphore$*`, and vvp opcodes (`%mbx/*`, `%sem/*`) with real thread suspend/resume. + +The older IVL_UVM “poor-man’s” classes (`ivl_uvm_mbx`, `ivl_uvm_semaphore`) remain in `uvm/` for that library’s own tests; new Accellera-shaped code should use the builtins. + +## Supported in this slice + +```systemverilog +mailbox #(int) mb = new(); // #(T) accepted; element type is int/vec for v1 +mb.put(7); +mb.get(v); // blocking +ok = mb.try_put(8); +ok = mb.try_get(v); // nonblocking; writes v on success +n = mb.num(); + +semaphore sem = new(1); +sem.get(1); // blocking +sem.put(1); +ok = sem.try_get(1); +``` + +Also: unbounded `new()` / `new(0)`, bounded mailbox `new(N)`, blocking put when full, `peek` / `try_peek`. + +## Encoding + +| Layer | Role | +|-------|------| +| Parse / pform | Builtin typedefs `mailbox` / `semaphore`; `mailbox #(T) x` as var decl (T ignored for now) | +| Netlist | Empty `netclass_t` “mailbox” / “semaphore” | +| Elab | `$ivl_mailbox$new/put/get/...`, `$ivl_semaphore$new/get/put/try_get` | +| Codegen | `%mbx/*`, `%sem/*`, `%box/vec4` / `%unbox/vec4` for int payloads | +| Runtime | `vvp_mailbox` / `vvp_semaphore` (`vvp/vvp_mailbox.{h,cc}`) | + +## Deferred (do not claim) + +- True parameterized element types (`mailbox #(string)`, class handles) beyond boxing ints +- Explicit specialization of user param-classes interacting with mailbox +- `peek` stress / priority / typed message checking +- Replacing IVL_UVM’s `ivl_uvm_mbx` usages with the builtin + +## Example + +[`examples/mailbox_sem/mbx_sem_basic.sv`](../examples/mailbox_sem/mbx_sem_basic.sv) — prints `PASSED`. + +```bash +./install/bin/iverilog -g2012 -o /tmp/mbx_sem.vvp examples/mailbox_sem/mbx_sem_basic.sv +./install/bin/vvp /tmp/mbx_sem.vvp +``` + +See also [STATUS.md](STATUS.md) and [ROADMAP.md](ROADMAP.md). diff --git a/elab_expr.cc b/elab_expr.cc index d5eea8d8a..684f6ed32 100644 --- a/elab_expr.cc +++ b/elab_expr.cc @@ -2017,6 +2017,25 @@ unsigned PECallFunction::test_width_method_(Design*, NetScope*, if (search_results.net && search_results.net->data_type()==IVL_VT_CLASS) { const netclass_t *class_type = dynamic_cast(search_results.type); ivl_assert(*this, class_type); + perm_string cname = class_type->get_name(); + if (cname == perm_string::literal("mailbox")) { + if (method_name == "num" || method_name == "try_get" + || method_name == "try_peek" || method_name == "try_put") { + expr_type_ = IVL_VT_BOOL; + expr_width_ = (method_name == "num") ? 32 : 1; + min_width_ = expr_width_; + signed_flag_ = false; + return expr_width_; + } + } + if (cname == perm_string::literal("semaphore") + && method_name == "try_get") { + expr_type_ = IVL_VT_BOOL; + expr_width_ = 1; + min_width_ = 1; + signed_flag_ = false; + return expr_width_; + } NetScope*method = class_type->method_from_name(method_name); if (method == 0) { @@ -4126,6 +4145,59 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope, NetNet*net = search_results.net; const netclass_t*class_type = dynamic_cast(search_results.type); ivl_assert(*this, class_type); + + perm_string cname = class_type->get_name(); + if (cname == perm_string::literal("mailbox")) { + if (method_name == perm_string::literal("num")) { + NetESFunc*sys = new NetESFunc("$ivl_mailbox$num", + &netvector_t::atom2u32, 1); + sys->set_line(*this); + sys->parm(0, sub_expr); + return sys; + } + if (method_name == perm_string::literal("try_get") + || method_name == perm_string::literal("try_peek") + || method_name == perm_string::literal("try_put")) { + const char*sname = + method_name==perm_string::literal("try_get") + ? "$ivl_mailbox$try_get" + : (method_name==perm_string::literal("try_peek") + ? "$ivl_mailbox$try_peek" + : "$ivl_mailbox$try_put"); + unsigned nargs = parms_.empty() ? 0 : 1; + NetESFunc*sys = new NetESFunc(sname, + &netvector_t::atom2u32, + 1 + nargs); + sys->set_line(*this); + sys->parm(0, sub_expr); + if (nargs > 0 && parms_[0].parm) { + NetExpr*a = elab_and_eval(des, scope, + parms_[0].parm, + -1, false, false); + if (a) sys->parm(1, a); + } + return sys; + } + } + if (cname == perm_string::literal("semaphore") + && method_name == perm_string::literal("try_get")) { + unsigned nargs = parms_.empty() ? 0 : 1; + NetESFunc*sys = new NetESFunc("$ivl_semaphore$try_get", + &netvector_t::atom2u32, + 1 + nargs); + sys->set_line(*this); + sys->parm(0, sub_expr); + if (nargs > 0 && parms_[0].parm) { + NetExpr*narg = elab_and_eval(des, scope, + parms_[0].parm, + 32, false, false, + IVL_VT_LOGIC); + sys->parm(1, narg ? narg + : new NetEConst(verinum((uint64_t)1, 32))); + } + return sys; + } + return elaborate_class_method_net_(des, scope, net, class_type, method_name, nullptr); } @@ -7426,6 +7498,33 @@ NetExpr* PENewClass::elaborate_expr_constructor_(Design*des, NetScope*scope, NetScope *new_scope = ctype->get_constructor(); if (new_scope == 0) { + // Built-in mailbox/semaphore constructors map to runtime sfuncs. + if (gn_system_verilog() + && ctype->get_name() == perm_string::literal("mailbox")) { + unsigned nargs = parms_.empty() ? 0 : 1; + NetESFunc*mbx = new NetESFunc("$ivl_mailbox$new", ctype, nargs); + mbx->set_line(*this); + if (!parms_.empty() && parms_[0].parm) { + NetExpr*barg = elab_and_eval(des, scope, parms_[0].parm, 32, + false, false, IVL_VT_LOGIC); + mbx->parm(0, barg ? barg : new NetEConst(verinum((uint64_t)0, 32))); + } + delete obj; + return mbx; + } + if (gn_system_verilog() + && ctype->get_name() == perm_string::literal("semaphore")) { + unsigned nargs = parms_.empty() ? 0 : 1; + NetESFunc*sem = new NetESFunc("$ivl_semaphore$new", ctype, nargs); + sem->set_line(*this); + if (!parms_.empty() && parms_[0].parm) { + NetExpr*carg = elab_and_eval(des, scope, parms_[0].parm, 32, + false, false, IVL_VT_LOGIC); + sem->parm(0, carg ? carg : new NetEConst(verinum((uint64_t)0, 32))); + } + delete obj; + return sem; + } // No constructor. if (parms_.size() > 0) { cerr << get_fileline() << ": error: " diff --git a/elab_type.cc b/elab_type.cc index ef071907a..967c41863 100644 --- a/elab_type.cc +++ b/elab_type.cc @@ -40,6 +40,23 @@ using namespace std; +static netclass_t* make_builtin_semaphore_type_() +{ + static netclass_t*builtin_semaphore_type = 0; + if (!builtin_semaphore_type) + builtin_semaphore_type = new netclass_t(perm_string::literal("semaphore"), 0); + return builtin_semaphore_type; +} + +static netclass_t* make_builtin_mailbox_type_() +{ + static netclass_t*builtin_mailbox_type = 0; + if (!builtin_mailbox_type) + builtin_mailbox_type = new netclass_t(perm_string::literal("mailbox"), 0); + return builtin_mailbox_type; +} + + /* * Elaborations of types may vary depending on the scope that it is * done in, so keep a per-scope cache of the results. @@ -506,6 +523,11 @@ NetScope *typeref_t::find_scope(Design *des, NetScope *s) const ivl_type_t typedef_t::elaborate_type(Design *des, NetScope *scope) { + if (name == perm_string::literal("mailbox")) + return make_builtin_mailbox_type_(); + if (name == perm_string::literal("semaphore")) + return make_builtin_semaphore_type_(); + if (!data_type.get()) { cerr << get_fileline() << ": error: Undefined type `" << name << "`." << endl; diff --git a/elaborate.cc b/elaborate.cc index d585b5198..cacd86f37 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -4582,6 +4582,66 @@ NetProc* PCallTask::elaborate_method_(Design*des, NetScope*scope, } if (const netclass_t*class_type = dynamic_cast(sr.type)) { + perm_string cname = class_type->get_name(); + if (cname == perm_string::literal("mailbox")) { + if (method_name == perm_string::literal("put")) { + static const std::vector parm_names = { + perm_string::literal("message") + }; + return elaborate_sys_task_method_(des, scope, net, method_name, + "$ivl_mailbox$put", parm_names); + } + if (method_name == perm_string::literal("get") + || method_name == perm_string::literal("peek")) { + static const std::vector parm_names = { + perm_string::literal("message") + }; + const char*sname = method_name == perm_string::literal("get") + ? "$ivl_mailbox$get" : "$ivl_mailbox$peek"; + return elaborate_sys_task_method_(des, scope, net, method_name, + sname, parm_names); + } + if (method_name == perm_string::literal("try_put") + || method_name == perm_string::literal("try_get") + || method_name == perm_string::literal("try_peek")) { + static const std::vector parm_names = { + perm_string::literal("message") + }; + const char*sname = + method_name == perm_string::literal("try_put") + ? "$ivl_mailbox$try_put" + : (method_name == perm_string::literal("try_get") + ? "$ivl_mailbox$try_get" + : "$ivl_mailbox$try_peek"); + return elaborate_sys_task_method_(des, scope, net, method_name, + sname, parm_names); + } + if (method_name == perm_string::literal("num")) { + static const std::vector no_parm_names; + return elaborate_sys_task_method_(des, scope, net, method_name, + "$ivl_mailbox$num", no_parm_names); + } + } + if (cname == perm_string::literal("semaphore")) { + static const std::vector sem_no_parms; + static const std::vector sem_one_parm = { + perm_string::literal("keycount") + }; + if (method_name == perm_string::literal("get") + || method_name == perm_string::literal("put") + || method_name == perm_string::literal("try_get")) { + const char*sname = + method_name == perm_string::literal("get") + ? "$ivl_semaphore$get" + : (method_name == perm_string::literal("put") + ? "$ivl_semaphore$put" + : "$ivl_semaphore$try_get"); + return elaborate_sys_task_method_(des, scope, net, method_name, + sname, + parms_.empty() ? sem_no_parms + : sem_one_parm); + } + } NetScope*task = class_type->method_from_name(method_name); if (task == 0) { // If an implicit this was added it is not an error if we diff --git a/examples/mailbox_sem/mbx_sem_basic.sv b/examples/mailbox_sem/mbx_sem_basic.sv new file mode 100644 index 000000000..6a9c7484b --- /dev/null +++ b/examples/mailbox_sem/mbx_sem_basic.sv @@ -0,0 +1,82 @@ +// Tier A #5 smoke: built-in mailbox #(int) + semaphore API. +module mbx_sem_basic; + mailbox #(int) mb = new(); + semaphore sem = new(1); + int v, n, ok; + int pass; + + initial begin + pass = 1; + + mb.put(7); + mb.get(v); + if (v !== 7) begin + $display("FAIL: mb.get got %0d want 7", v); + pass = 0; + end + + ok = mb.try_put(8); + if (ok !== 1) begin + $display("FAIL: try_put expected 1 got %0d", ok); + pass = 0; + end + + ok = mb.try_get(v); + if (ok !== 1 || v !== 8) begin + $display("FAIL: try_get ok=%0d v=%0d", ok, v); + pass = 0; + end + + n = mb.num(); + if (n !== 0) begin + $display("FAIL: num expected 0 got %0d", n); + pass = 0; + end + + ok = mb.try_get(v); + if (ok !== 0) begin + $display("FAIL: empty try_get expected 0 got %0d", ok); + pass = 0; + end + + sem.get(1); + sem.put(1); + ok = sem.try_get(1); + if (ok !== 1) begin + $display("FAIL: sem.try_get expected 1 got %0d", ok); + pass = 0; + end + ok = sem.try_get(1); + if (ok !== 0) begin + $display("FAIL: sem.try_get empty expected 0 got %0d", ok); + pass = 0; + end + + fork + begin + #5; + mb.put(42); + end + begin + mb.get(v); + if (v !== 42) begin + $display("FAIL: blocking get got %0d", v); + pass = 0; + end + end + join + + fork + begin + #5; + sem.put(1); + end + begin + sem.get(1); + end + join + + if (pass) $display("PASSED"); + else $display("FAILED"); + end +endmodule diff --git a/netclass.cc b/netclass.cc index 278fceb43..34ed45a38 100644 --- a/netclass.cc +++ b/netclass.cc @@ -165,7 +165,9 @@ bool netclass_t::test_for_missing_initializers() const NetScope*netclass_t::method_from_name(perm_string name) const { - NetScope*task = class_scope_->child( hname_t(name) ); + NetScope*task = 0; + if (class_scope_) + task = class_scope_->child( hname_t(name) ); if ((task == 0) && super_) task = super_->method_from_name(name); return task; @@ -174,6 +176,9 @@ NetScope*netclass_t::method_from_name(perm_string name) const NetScope* netclass_t::get_constructor() const { + if (class_scope_ == 0) + return nullptr; + auto task = class_scope_->child(hname_t(perm_string::literal("new"))); if (task) return task; diff --git a/parse.y b/parse.y index 331a09717..c3125ba35 100644 --- a/parse.y +++ b/parse.y @@ -1102,7 +1102,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type, %type port_direction port_direction_opt clocking_direction %type clocking_skew_opt %type integer_vector_type integer_vector_type_no_reg -%type parameter_value_opt +%type parameter_value_opt type_parameter_value %type event_expression_list %type event_expression @@ -1609,6 +1609,29 @@ data_declaration /* IEEE1800-2005: A.2.1.3 */ $1, $2); var_lifetime = LexicalScope::INHERITED; } + /* Built-in / parameterized class types: `mailbox #(int) mb = new();` */ + | attribute_list_opt K_const_opt variable_lifetime_opt TYPE_IDENTIFIER type_parameter_value list_of_variable_decl_assignments ';' + { typeref_t*tmp = new typeref_t($4.type); + FILE_NAME(tmp, @4); + pform_make_var(@4, $6, tmp, $1, $2); + if ($5) { + if ($5->by_order) { + for (std::list::iterator i = $5->by_order->begin() + ; i != $5->by_order->end() ; ++i) + delete *i; + delete $5->by_order; + } + if ($5->by_name) { + for (std::list::iterator i = $5->by_name->begin() + ; i != $5->by_name->end() ; ++i) + delete i->parm; + delete $5->by_name; + } + delete $5; + } + delete[]$4.text; + var_lifetime = LexicalScope::INHERITED; + } | attribute_list_opt K_const_opt K_var variable_lifetime_opt list_of_variable_decl_assignments_with_type ';' { data_type_t*data_type = $5.type; if (!data_type) { @@ -2361,6 +2384,30 @@ type_identifier_variable_decl_assignments_with_type $$.decl_assignments = $3; $$.type = pform_make_parray_type(@2, tmp, $2); } + /* Built-in parameterized classes: `mailbox #(int) mb = new();` */ + | TYPE_IDENTIFIER type_parameter_value list_of_variable_decl_assignments + { pform_set_type_referenced(@1, $1.text); + auto tmp = new typeref_t($1.type); + FILE_NAME(tmp, @1); + delete[]$1.text; + if ($2) { + if ($2->by_order) { + for (std::list::iterator i = $2->by_order->begin() + ; i != $2->by_order->end() ; ++i) + delete *i; + delete $2->by_order; + } + if ($2->by_name) { + for (std::list::iterator i = $2->by_name->begin() + ; i != $2->by_name->end() ; ++i) + delete i->parm; + delete $2->by_name; + } + delete $2; + } + $$.decl_assignments = $3; + $$.type = tmp; + } | package_scope TYPE_IDENTIFIER dimensions_opt list_of_variable_decl_assignments { lex_in_package_scope(nullptr); auto tmp = new typeref_t($2.type, $1); @@ -5866,6 +5913,7 @@ module_item delete[]$2; } + | attribute_list_opt IDENTIFIER parameter_value_opt error ';' { yyerror(@2, "error: Invalid module instantiation"); @@ -6400,6 +6448,22 @@ from_exclude : K_from { $$ = false; } | K_exclude { $$ = true; } ; The parameter value by name syntax is OVI enhancement BTF-B06 as approved by WG1364 on 6/28/1998. */ +/* Required `#(...)` type/parameter override list (not optional). */ +type_parameter_value + : '#' '(' expression_list_with_nuls ')' + { struct parmvalue_t*tmp = new struct parmvalue_t; + tmp->by_order = $3; + tmp->by_name = 0; + $$ = tmp; + } + | '#' '(' parameter_value_byname_list ')' + { struct parmvalue_t*tmp = new struct parmvalue_t; + tmp->by_order = 0; + tmp->by_name = $3; + $$ = tmp; + } + ; + parameter_value_opt : '#' '(' expression_list_with_nuls ')' { struct parmvalue_t*tmp = new struct parmvalue_t; diff --git a/pform.cc b/pform.cc index 29cfda436..16615fcce 100644 --- a/pform.cc +++ b/pform.cc @@ -922,6 +922,28 @@ typedef_t* pform_test_type_identifier(const struct vlltype&loc, const char*txt) { perm_string name = lex_strings.make(txt); + // Built-in SystemVerilog classes (IEEE 1800 mailbox/semaphore). + if (gn_system_verilog()) { + if (name == lex_strings.make("semaphore")) { + static typedef_t*semaphore_type = 0; + if (!semaphore_type) { + semaphore_type = new typedef_t(lex_strings.make("semaphore")); + semaphore_type->set_data_type(new type_parameter_t(semaphore_type->name)); + semaphore_type->set_basic_type(type_restrict_t(type_restrict_t::CLASS)); + } + return semaphore_type; + } + if (name == lex_strings.make("mailbox")) { + static typedef_t*mailbox_type = 0; + if (!mailbox_type) { + mailbox_type = new typedef_t(lex_strings.make("mailbox")); + mailbox_type->set_data_type(new type_parameter_t(mailbox_type->name)); + mailbox_type->set_basic_type(type_restrict_t(type_restrict_t::CLASS)); + } + return mailbox_type; + } + } + LexicalScope*cur_scope = lexical_scope; do { LexicalScope::typedef_map_t::iterator cur; @@ -2558,6 +2580,59 @@ void pform_make_modgates(const struct vlltype&loc, std::vector*gates, std::list*attr) { + // Reinterpret `mailbox #(int) mb;` style no-port instantiations of a + // known type identifier as variable declarations (built-in classes). + if (gn_system_verilog()) { + typedef_t*decl_type = pform_test_type_identifier(loc, type); + if (decl_type) { + bool declaration_like = true; + std::list*decls = new std::list; + for (unsigned idx = 0 ; idx < gates->size() ; idx += 1) { + lgate&cur = (*gates)[idx]; + if (cur.parms || cur.parms_by_name) { + declaration_like = false; + break; + } + decl_assignment_t*decl = new decl_assignment_t; + decl->name = { lex_strings.make(cur.name), loc.lexical_pos }; + if (cur.ranges) { + decl->index.swap(*cur.ranges); + delete cur.ranges; + cur.ranges = 0; + } + decls->push_back(decl); + } + if (declaration_like) { + pform_set_type_referenced(loc, type); + typeref_t*dtype = new typeref_t(decl_type); + FILE_NAME(dtype, loc); + pform_make_var(loc, decls, dtype, attr, false); + if (overrides) { + // Built-in mailbox/semaphore ignore specialization args for now. + if (overrides->by_order) { + for (list::iterator i = overrides->by_order->begin() + ; i != overrides->by_order->end() ; ++i) + delete *i; + delete overrides->by_order; + } + if (overrides->by_name) { + for (list::iterator i = overrides->by_name->begin() + ; i != overrides->by_name->end() ; ++i) + delete i->parm; + delete overrides->by_name; + } + delete overrides; + } + delete gates; + return; + } + for (list::iterator cur = decls->begin() + ; cur != decls->end() ; ++cur) + delete *cur; + delete decls; + } + } + // The grammer should not allow module gates to happen outside // an active module. But if really bad input errors combine in // an ugly way with error recovery, then catch this diff --git a/tgt-vvp/eval_object.c b/tgt-vvp/eval_object.c index 98e1cd5d4..c42430c78 100644 --- a/tgt-vvp/eval_object.c +++ b/tgt-vvp/eval_object.c @@ -794,6 +794,30 @@ int draw_eval_object(ivl_expr_t ex) strstr(ivl_expr_name(ex), "_with") != 0) { if (draw_queue_method_find_sfunc(ex) == 0) return 0; } + /* Mailbox constructor: $ivl_mailbox$new([bound]) */ + if (strcmp(ivl_expr_name(ex), "$ivl_mailbox$new") == 0) { + long bound = 0; + unsigned parm_count = ivl_expr_parms(ex); + if (parm_count > 0) { + ivl_expr_t bexpr = ivl_expr_parm(ex, 0); + if (bexpr && ivl_expr_type(bexpr) == IVL_EX_NUMBER) + bound = (long)ivl_expr_uvalue(bexpr); + } + fprintf(vvp_out, " %%mbx/new %ld;\n", bound); + return 0; + } + /* Semaphore constructor: $ivl_semaphore$new([initial_count]) */ + if (strcmp(ivl_expr_name(ex), "$ivl_semaphore$new") == 0) { + long cnt = 0; + unsigned parm_count = ivl_expr_parms(ex); + if (parm_count > 0) { + ivl_expr_t cexpr = ivl_expr_parm(ex, 0); + if (cexpr && ivl_expr_type(cexpr) == IVL_EX_NUMBER) + cnt = (long)ivl_expr_uvalue(cexpr); + } + fprintf(vvp_out, " %%sem/new %ld;\n", cnt); + return 0; + } if (strcmp(ivl_expr_name(ex), "$ivl_vif_new") == 0) { unsigned n = ivl_expr_parms(ex); fprintf(vvp_out, " %%new/vif %u", n); diff --git a/tgt-vvp/eval_vec4.c b/tgt-vvp/eval_vec4.c index 93784bff9..e361bee86 100644 --- a/tgt-vvp/eval_vec4.c +++ b/tgt-vvp/eval_vec4.c @@ -1194,6 +1194,82 @@ static void draw_sfunc_vec4(ivl_expr_t expr) return; } + + /* Mailbox num: $ivl_mailbox$num(mbx) -> int on vec4 */ + if (strcmp(ivl_expr_name(expr), "$ivl_mailbox$num") == 0) { + ivl_expr_t mbx_e = (parm_count > 0) ? ivl_expr_parm(expr, 0) : 0; + if (mbx_e) draw_eval_object(mbx_e); + fprintf(vvp_out, " %%mbx/num;\n"); + if (ivl_expr_width(expr) > 1) + fprintf(vvp_out, " %%pad/u %u;\n", ivl_expr_width(expr)); + return; + } + + /* Mailbox try_put: $ivl_mailbox$try_put(mbx, item) -> bit on vec4 */ + if (strcmp(ivl_expr_name(expr), "$ivl_mailbox$try_put") == 0) { + ivl_expr_t mbx_e = (parm_count > 0) ? ivl_expr_parm(expr, 0) : 0; + ivl_expr_t item_e = (parm_count > 1) ? ivl_expr_parm(expr, 1) : 0; + if (mbx_e) draw_eval_object(mbx_e); + if (item_e) { + if (ivl_expr_value(item_e) == IVL_VT_CLASS) { + draw_eval_object(item_e); + } else { + unsigned wid = ivl_expr_width(item_e); + if (!wid) wid = 32; + draw_eval_vec4(item_e); + fprintf(vvp_out, " %%box/vec4 %u;\n", wid); + } + } else { + fprintf(vvp_out, " %%null;\n"); + } + fprintf(vvp_out, " %%mbx/try_put;\n"); + if (ivl_expr_width(expr) > 1) + fprintf(vvp_out, " %%pad/u %u;\n", ivl_expr_width(expr)); + return; + } + + /* Mailbox try_get/try_peek */ + if (strcmp(ivl_expr_name(expr), "$ivl_mailbox$try_get") == 0 || + strcmp(ivl_expr_name(expr), "$ivl_mailbox$try_peek") == 0) { + int is_peek = (strcmp(ivl_expr_name(expr), "$ivl_mailbox$try_peek") == 0); + ivl_expr_t mbx_e = (parm_count > 0) ? ivl_expr_parm(expr, 0) : 0; + ivl_expr_t item_e = (parm_count > 1) ? ivl_expr_parm(expr, 1) : 0; + if (mbx_e) draw_eval_object(mbx_e); + fprintf(vvp_out, " %s;\n", is_peek ? "%mbx/try_peek" : "%mbx/try_get"); + if (item_e && ivl_expr_type(item_e) == IVL_EX_SIGNAL + && ivl_expr_signal(item_e)) { + ivl_signal_t sig = ivl_expr_signal(item_e); + if (ivl_signal_data_type(sig) == IVL_VT_CLASS) { + fprintf(vvp_out, " %%store/obj v%p_0;\n", sig); + } else { + unsigned wid = ivl_signal_width(sig); + if (!wid) wid = 32; + fprintf(vvp_out, " %%unbox/vec4 %u;\n", wid); + fprintf(vvp_out, " %%store/vec4 v%p_0, 0, %u;\n", sig, wid); + } + } else { + fprintf(vvp_out, " %%pop/obj 1, 0;\n"); + } + if (ivl_expr_width(expr) > 1) + fprintf(vvp_out, " %%pad/u %u;\n", ivl_expr_width(expr)); + return; + } + + /* Semaphore try_get */ + if (strcmp(ivl_expr_name(expr), "$ivl_semaphore$try_get") == 0) { + ivl_expr_t sem_e = (parm_count > 0) ? ivl_expr_parm(expr, 0) : 0; + if (sem_e) draw_eval_object(sem_e); + if (parm_count >= 2 && ivl_expr_parm(expr, 1)) { + draw_eval_vec4(ivl_expr_parm(expr, 1)); + } else { + fprintf(vvp_out, " %%pushi/vec4 1, 0, 32;\n"); + } + fprintf(vvp_out, " %%sem/try_get;\n"); + if (ivl_expr_width(expr) > 1) + fprintf(vvp_out, " %%pad/u %u;\n", ivl_expr_width(expr)); + return; + } + draw_vpi_func_call(expr); } diff --git a/tgt-vvp/vvp_process.c b/tgt-vvp/vvp_process.c index b9acef727..7a99a9312 100644 --- a/tgt-vvp/vvp_process.c +++ b/tgt-vvp/vvp_process.c @@ -1945,6 +1945,146 @@ static int show_system_task_call(ivl_statement_t net) { const char*stmt_name = ivl_stmt_name(net); + +#define EMIT_MBX_PUSH_ITEM(item_expr) \ + do { \ + ivl_expr_t _item = (item_expr); \ + if (_item) { \ + if (ivl_expr_value(_item) == IVL_VT_CLASS) { \ + draw_eval_object(_item); \ + } else { \ + unsigned _wid = ivl_expr_width(_item); \ + if (!_wid) _wid = 32; \ + draw_eval_vec4(_item); \ + fprintf(vvp_out, " %%box/vec4 %u;\n", _wid); \ + } \ + } else { \ + fprintf(vvp_out, " %%null;\n"); \ + } \ + } while(0) + +#define EMIT_MBX_STORE_ITEM(net_arg_idx) \ + do { \ + if (ivl_stmt_parm_count(net) > (net_arg_idx)) { \ + ivl_expr_t _item = ivl_stmt_parm(net, net_arg_idx); \ + if (_item && ivl_expr_type(_item) == IVL_EX_SIGNAL) { \ + ivl_signal_t _sig = ivl_expr_signal(_item); \ + if (_sig && ivl_signal_data_type(_sig) == IVL_VT_CLASS) { \ + fprintf(vvp_out, " %%store/obj v%p_0;\n", _sig); \ + } else if (_sig) { \ + unsigned _wid = ivl_signal_width(_sig); \ + if (!_wid) _wid = 32; \ + fprintf(vvp_out, " %%unbox/vec4 %u;\n", _wid); \ + fprintf(vvp_out, " %%store/vec4 v%p_0, 0, %u;\n", _sig, _wid); \ + } else { \ + fprintf(vvp_out, " %%pop/obj 1, 0;\n"); \ + } \ + } else { \ + fprintf(vvp_out, " %%pop/obj 1, 0;\n"); \ + } \ + } else { \ + fprintf(vvp_out, " %%pop/obj 1, 0;\n"); \ + } \ + } while(0) + + if (strcmp(stmt_name, "$ivl_mailbox$put") == 0) { + ivl_expr_t mbx = ivl_stmt_parm(net, 0); + ivl_expr_t item = ivl_stmt_parm_count(net) >= 2 ? ivl_stmt_parm(net, 1) : 0; + if (mbx) draw_eval_object(mbx); + EMIT_MBX_PUSH_ITEM(item); + fprintf(vvp_out, " %%mbx/put;\n"); + return 0; + } + + if (strcmp(stmt_name, "$ivl_mailbox$get") == 0) { + ivl_expr_t mbx = ivl_stmt_parm(net, 0); + if (mbx) draw_eval_object(mbx); + fprintf(vvp_out, " %%mbx/get;\n"); + EMIT_MBX_STORE_ITEM(1); + return 0; + } + + if (strcmp(stmt_name, "$ivl_mailbox$peek") == 0) { + ivl_expr_t mbx = ivl_stmt_parm(net, 0); + if (mbx) draw_eval_object(mbx); + fprintf(vvp_out, " %%mbx/peek;\n"); + EMIT_MBX_STORE_ITEM(1); + return 0; + } + + if (strcmp(stmt_name, "$ivl_mailbox$try_put") == 0) { + ivl_expr_t mbx = ivl_stmt_parm(net, 0); + ivl_expr_t item = ivl_stmt_parm_count(net) >= 2 ? ivl_stmt_parm(net, 1) : 0; + if (mbx) draw_eval_object(mbx); + EMIT_MBX_PUSH_ITEM(item); + fprintf(vvp_out, " %%mbx/try_put;\n"); + fprintf(vvp_out, " %%pop/vec4 1;\n"); + return 0; + } + + if (strcmp(stmt_name, "$ivl_mailbox$try_get") == 0) { + ivl_expr_t mbx = ivl_stmt_parm(net, 0); + if (mbx) draw_eval_object(mbx); + fprintf(vvp_out, " %%mbx/try_get;\n"); + fprintf(vvp_out, " %%pop/vec4 1;\n"); + EMIT_MBX_STORE_ITEM(1); + return 0; + } + + if (strcmp(stmt_name, "$ivl_mailbox$try_peek") == 0) { + ivl_expr_t mbx = ivl_stmt_parm(net, 0); + if (mbx) draw_eval_object(mbx); + fprintf(vvp_out, " %%mbx/try_peek;\n"); + fprintf(vvp_out, " %%pop/vec4 1;\n"); + EMIT_MBX_STORE_ITEM(1); + return 0; + } + + if (strcmp(stmt_name, "$ivl_mailbox$num") == 0) { + ivl_expr_t mbx = ivl_stmt_parm(net, 0); + if (mbx) draw_eval_object(mbx); + fprintf(vvp_out, " %%mbx/num;\n"); + fprintf(vvp_out, " %%pop/vec4 1;\n"); + return 0; + } + + if (strcmp(stmt_name, "$ivl_semaphore$get") == 0) { + ivl_expr_t sem = ivl_stmt_parm(net, 0); + if (sem) draw_eval_object(sem); + if (ivl_stmt_parm_count(net) >= 2 && ivl_stmt_parm(net, 1)) { + draw_eval_vec4(ivl_stmt_parm(net, 1)); + } else { + fprintf(vvp_out, " %%pushi/vec4 1, 0, 32;\n"); + } + fprintf(vvp_out, " %%sem/get;\n"); + return 0; + } + + if (strcmp(stmt_name, "$ivl_semaphore$put") == 0) { + ivl_expr_t sem = ivl_stmt_parm(net, 0); + if (sem) draw_eval_object(sem); + if (ivl_stmt_parm_count(net) >= 2 && ivl_stmt_parm(net, 1)) { + draw_eval_vec4(ivl_stmt_parm(net, 1)); + } else { + fprintf(vvp_out, " %%pushi/vec4 1, 0, 32;\n"); + } + fprintf(vvp_out, " %%sem/put;\n"); + return 0; + } + + if (strcmp(stmt_name, "$ivl_semaphore$try_get") == 0) { + ivl_expr_t sem = ivl_stmt_parm(net, 0); + if (sem) draw_eval_object(sem); + if (ivl_stmt_parm_count(net) >= 2 && ivl_stmt_parm(net, 1)) { + draw_eval_vec4(ivl_stmt_parm(net, 1)); + } else { + fprintf(vvp_out, " %%pushi/vec4 1, 0, 32;\n"); + } + fprintf(vvp_out, " %%sem/try_get;\n"); + fprintf(vvp_out, " %%pop/vec4 1;\n"); + return 0; + } + if (strcmp(stmt_name,"$ivl_vif_wait") == 0) { ivl_expr_t vif = ivl_stmt_parm(net, 0); ivl_expr_t edge_ex = ivl_stmt_parm(net, 1); diff --git a/uvm/ivl_uvm_src/ivl_uvm_mbx.svh b/uvm/ivl_uvm_src/ivl_uvm_mbx.svh index f07f234ec..d3bdc146f 100644 --- a/uvm/ivl_uvm_src/ivl_uvm_mbx.svh +++ b/uvm/ivl_uvm_src/ivl_uvm_mbx.svh @@ -3,7 +3,10 @@ `define IVL_UVM_MBX_T int `endif -// Poor man's mailbox +// Poor man's mailbox (legacy IVL_UVM helper). +// Prefer the compiler builtin `mailbox #(int)` when using -g2012 on this fork +// (see docs/mailbox-semaphore.md). This class remains for IVL_UVM tests that +// still instantiate ivl_uvm_mbx. // Default size = 1 in SV // Here, only size-1 is supported as of now // Also no parameterized classes in Icarus (yet), so using a `define diff --git a/vvp/Makefile.in b/vvp/Makefile.in index 5049e0f23..41ef14252 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 vvp_aarray.o vvp_vif.o event.o logic.o delay.o \ + vvp_object.o vvp_cobject.o vvp_darray.o vvp_aarray.o vvp_vif.o vvp_mailbox.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 1a9e33d12..b702bb8d6 100644 --- a/vvp/codes.h +++ b/vvp/codes.h @@ -172,6 +172,20 @@ extern bool of_NANDR(vthread_t thr, vvp_code_t code); extern bool of_NEW_COBJ(vthread_t thr, vvp_code_t code); extern bool of_NEW_DARRAY(vthread_t thr, vvp_code_t code); extern bool of_NEW_VIF(vthread_t thr, vvp_code_t code); +extern bool of_MBX_NEW(vthread_t thr, vvp_code_t code); +extern bool of_MBX_PUT(vthread_t thr, vvp_code_t code); +extern bool of_MBX_GET(vthread_t thr, vvp_code_t code); +extern bool of_MBX_PEEK(vthread_t thr, vvp_code_t code); +extern bool of_MBX_TRY_PUT(vthread_t thr, vvp_code_t code); +extern bool of_MBX_TRY_GET(vthread_t thr, vvp_code_t code); +extern bool of_MBX_TRY_PEEK(vthread_t thr, vvp_code_t code); +extern bool of_MBX_NUM(vthread_t thr, vvp_code_t code); +extern bool of_SEM_NEW(vthread_t thr, vvp_code_t code); +extern bool of_SEM_GET(vthread_t thr, vvp_code_t code); +extern bool of_SEM_PUT(vthread_t thr, vvp_code_t code); +extern bool of_SEM_TRY_GET(vthread_t thr, vvp_code_t code); +extern bool of_BOX_VEC4(vthread_t thr, vvp_code_t code); +extern bool of_UNBOX_VEC4(vthread_t thr, vvp_code_t code); extern bool of_NOOP(vthread_t thr, vvp_code_t code); extern bool of_NOR(vthread_t thr, vvp_code_t code); extern bool of_NORR(vthread_t thr, vvp_code_t code); diff --git a/vvp/compile.cc b/vvp/compile.cc index c52aac891..82de8aac1 100644 --- a/vvp/compile.cc +++ b/vvp/compile.cc @@ -114,6 +114,7 @@ static const struct opcode_table_s opcode_table[] = { { "%assign/wr/e",of_ASSIGN_WRE,1,{OA_VPI_PTR, OA_NONE, OA_NONE} }, { "%blend", of_BLEND, 0, {OA_NONE, OA_NONE, OA_NONE} }, { "%blend/wr", of_BLEND_WR,0, {OA_NONE, OA_NONE, OA_NONE} }, + { "%box/vec4", of_BOX_VEC4, 1, {OA_NUMBER, OA_NONE, OA_NONE} }, { "%breakpoint", of_BREAKPOINT, 0, {OA_NONE, OA_NONE, OA_NONE} }, { "%callf/obj", of_CALLF_OBJ, 2,{OA_CODE_PTR2,OA_VPI_PTR, OA_NONE} }, { "%callf/real", of_CALLF_REAL, 2,{OA_CODE_PTR2,OA_VPI_PTR, OA_NONE} }, @@ -225,6 +226,14 @@ static const struct opcode_table_s opcode_table[] = { { "%load/vec4", of_LOAD_VEC4, 1,{OA_FUNC_PTR,OA_NONE, OA_NONE} }, { "%load/vec4a", of_LOAD_VEC4A,2,{OA_ARR_PTR, OA_BIT1, OA_NONE} }, { "%max/wr", of_MAX_WR, 0, {OA_NONE, OA_NONE, OA_NONE} }, + { "%mbx/get", of_MBX_GET, 0, {OA_NONE, OA_NONE, OA_NONE} }, + { "%mbx/new", of_MBX_NEW, 1, {OA_NUMBER, OA_NONE, OA_NONE} }, + { "%mbx/num", of_MBX_NUM, 0, {OA_NONE, OA_NONE, OA_NONE} }, + { "%mbx/peek", of_MBX_PEEK, 0, {OA_NONE, OA_NONE, OA_NONE} }, + { "%mbx/put", of_MBX_PUT, 0, {OA_NONE, OA_NONE, OA_NONE} }, + { "%mbx/try_get", of_MBX_TRY_GET, 0, {OA_NONE, OA_NONE, OA_NONE} }, + { "%mbx/try_peek", of_MBX_TRY_PEEK, 0, {OA_NONE, OA_NONE, OA_NONE} }, + { "%mbx/try_put", of_MBX_TRY_PUT, 0, {OA_NONE, OA_NONE, OA_NONE} }, { "%min/wr", of_MIN_WR, 0, {OA_NONE, OA_NONE, OA_NONE} }, { "%mod", of_MOD, 0, {OA_NONE, OA_NONE, OA_NONE} }, { "%mod/s", of_MOD_S, 0, {OA_NONE, OA_NONE, OA_NONE} }, @@ -335,6 +344,10 @@ static const struct opcode_table_s opcode_table[] = { { "%rsort/obj", of_RSORT_OBJ, 1, {OA_FUNC_PTR, OA_NONE, OA_NONE} }, { "%rsort/prop/obj", of_RSORT_PROP_OBJ, 1, {OA_NUMBER, OA_NONE, OA_NONE} }, { "%scopy", of_SCOPY, 0, {OA_NONE, OA_NONE, OA_NONE} }, + { "%sem/get", of_SEM_GET, 0, {OA_NONE, OA_NONE, OA_NONE} }, + { "%sem/new", of_SEM_NEW, 1, {OA_NUMBER, OA_NONE, OA_NONE} }, + { "%sem/put", of_SEM_PUT, 0, {OA_NONE, OA_NONE, OA_NONE} }, + { "%sem/try_get", of_SEM_TRY_GET, 0, {OA_NONE, OA_NONE, OA_NONE} }, { "%set/dar/obj/real",of_SET_DAR_OBJ_REAL,1,{OA_NUMBER,OA_NONE,OA_NONE} }, { "%set/dar/obj/str", of_SET_DAR_OBJ_STR, 1,{OA_NUMBER,OA_NONE,OA_NONE} }, { "%set/dar/obj/vec4",of_SET_DAR_OBJ_VEC4,1,{OA_NUMBER,OA_NONE,OA_NONE} }, @@ -389,6 +402,7 @@ static const struct opcode_table_s opcode_table[] = { { "%test_nul/a", of_TEST_NUL_A, 2,{OA_ARR_PTR, OA_BIT1, OA_NONE} }, { "%test_nul/obj", of_TEST_NUL_OBJ, 0,{OA_NONE, OA_NONE, OA_NONE} }, { "%test_nul/prop",of_TEST_NUL_PROP,2,{OA_NUMBER, OA_BIT1, OA_NONE} }, + { "%unbox/vec4",of_UNBOX_VEC4,1, {OA_NUMBER, OA_NONE, OA_NONE} }, { "%vif/load/vec4", of_VIF_LOAD_VEC4, 2,{OA_NUMBER, OA_BIT1, OA_NONE} }, { "%vif/store/vec4",of_VIF_STORE_VEC4,2,{OA_NUMBER, OA_BIT1, OA_NONE} }, { "%vif/wait", of_VIF_WAIT, 2,{OA_NUMBER, OA_BIT1, OA_NONE} }, diff --git a/vvp/vthread.cc b/vvp/vthread.cc index 1248db3ea..c422145bc 100644 --- a/vvp/vthread.cc +++ b/vvp/vthread.cc @@ -29,6 +29,7 @@ # include "vvp_darray.h" # include "vvp_aarray.h" # include "vvp_vif.h" +# include "vvp_mailbox.h" # include "class_type.h" #ifdef CHECK_WITH_VALGRIND # include "vvp_cleanup.h" @@ -8399,3 +8400,235 @@ bool of_REAP_UFUNC(vthread_t thr, vvp_code_t cp) return true; } + + +/* + * Push an object onto a thread's object stack (used when waking mailbox/semaphore waiters). + */ +void vthread_push_obj_item(vthread_t thr, const vvp_object_t& obj) +{ + thr->push_object(obj); +} + +/* ================================================================ + * Mailbox opcodes + * ================================================================ */ + +bool of_MBX_NEW(vthread_t thr, vvp_code_t cp) +{ + size_t bound = cp->number; + vvp_object_t mbx(new vvp_mailbox(bound)); + thr->push_object(mbx); + return true; +} + +bool of_MBX_PUT(vthread_t thr, vvp_code_t) +{ + vvp_object_t item_obj; + thr->pop_object(item_obj); + vvp_object_t mbx_obj; + thr->pop_object(mbx_obj); + + vvp_mailbox*mbx = mbx_obj.peek(); + if (!mbx) return true; + + bool done = mbx->put(thr, item_obj); + if (!done) return false; + return true; +} + +bool of_MBX_GET(vthread_t thr, vvp_code_t) +{ + vvp_object_t mbx_obj; + thr->pop_object(mbx_obj); + + vvp_mailbox*mbx = mbx_obj.peek(); + if (!mbx) { + thr->push_object(vvp_object_t()); + return true; + } + + vvp_object_t item; + bool done = mbx->get(thr, item); + if (!done) return false; + thr->push_object(item); + return true; +} + +bool of_MBX_PEEK(vthread_t thr, vvp_code_t) +{ + vvp_object_t mbx_obj; + thr->pop_object(mbx_obj); + + vvp_mailbox*mbx = mbx_obj.peek(); + if (!mbx) { + thr->push_object(vvp_object_t()); + return true; + } + + vvp_object_t item; + bool done = mbx->peek(thr, item); + if (!done) return false; + thr->push_object(item); + return true; +} + +bool of_MBX_TRY_PUT(vthread_t thr, vvp_code_t) +{ + vvp_object_t item_obj; + thr->pop_object(item_obj); + vvp_object_t mbx_obj; + thr->pop_object(mbx_obj); + + vvp_mailbox*mbx = mbx_obj.peek(); + bool ok = mbx ? mbx->try_put(item_obj) : false; + vvp_vector4_t res(1, ok ? BIT4_1 : BIT4_0); + thr->push_vec4(res); + return true; +} + +bool of_MBX_TRY_GET(vthread_t thr, vvp_code_t) +{ + vvp_object_t mbx_obj; + thr->pop_object(mbx_obj); + + vvp_mailbox*mbx = mbx_obj.peek(); + vvp_object_t item; + bool ok = mbx ? mbx->try_get(item) : false; + thr->push_object(ok ? item : vvp_object_t()); + vvp_vector4_t res(1, ok ? BIT4_1 : BIT4_0); + thr->push_vec4(res); + return true; +} + +bool of_MBX_TRY_PEEK(vthread_t thr, vvp_code_t) +{ + vvp_object_t mbx_obj; + thr->pop_object(mbx_obj); + + vvp_mailbox*mbx = mbx_obj.peek(); + vvp_object_t item; + bool ok = mbx ? mbx->try_peek(item) : false; + thr->push_object(ok ? item : vvp_object_t()); + vvp_vector4_t res(1, ok ? BIT4_1 : BIT4_0); + thr->push_vec4(res); + return true; +} + +bool of_MBX_NUM(vthread_t thr, vvp_code_t) +{ + vvp_object_t mbx_obj; + thr->pop_object(mbx_obj); + + vvp_mailbox*mbx = mbx_obj.peek(); + unsigned long cnt = mbx ? (unsigned long)mbx->num() : 0UL; + vvp_vector4_t res(32); + for (unsigned idx = 0 ; idx < 32 ; ++idx) + res.set_bit(idx, (cnt >> idx) & 1 ? BIT4_1 : BIT4_0); + thr->push_vec4(res); + return true; +} + +/* ================================================================ + * Semaphore opcodes + * ================================================================ */ + +bool of_SEM_NEW(vthread_t thr, vvp_code_t cp) +{ + size_t cnt = cp->number; + vvp_object_t sem(new vvp_semaphore(cnt)); + thr->push_object(sem); + return true; +} + +bool of_SEM_GET(vthread_t thr, vvp_code_t) +{ + unsigned long nval = 0; + { + const vvp_vector4_t& nv = thr->peek_vec4(0); + unsigned nbits = nv.size() < 32 ? nv.size() : 32; + for (unsigned i = 0 ; i < nbits ; ++i) + if (nv.value(i) == BIT4_1) nval |= (1UL << i); + } + thr->pop_vec4(1); + + vvp_object_t sem_obj; + thr->pop_object(sem_obj); + + vvp_semaphore*sem = sem_obj.peek(); + if (!sem) return true; + + bool done = sem->get(thr, nval ? nval : 1); + if (!done) return false; + return true; +} + +bool of_SEM_PUT(vthread_t thr, vvp_code_t) +{ + unsigned long nval = 0; + { + const vvp_vector4_t& nv = thr->peek_vec4(0); + unsigned nbits = nv.size() < 32 ? nv.size() : 32; + for (unsigned i = 0 ; i < nbits ; ++i) + if (nv.value(i) == BIT4_1) nval |= (1UL << i); + } + thr->pop_vec4(1); + + vvp_object_t sem_obj; + thr->pop_object(sem_obj); + + vvp_semaphore*sem = sem_obj.peek(); + if (sem) sem->put(nval ? nval : 1); + return true; +} + +bool of_SEM_TRY_GET(vthread_t thr, vvp_code_t) +{ + unsigned long nval = 0; + { + const vvp_vector4_t& nv = thr->peek_vec4(0); + unsigned nbits = nv.size() < 32 ? nv.size() : 32; + for (unsigned i = 0 ; i < nbits ; ++i) + if (nv.value(i) == BIT4_1) nval |= (1UL << i); + } + thr->pop_vec4(1); + + vvp_object_t sem_obj; + thr->pop_object(sem_obj); + + vvp_semaphore*sem = sem_obj.peek(); + bool ok = sem ? sem->try_get(nval ? nval : 1) : false; + vvp_vector4_t res(1, ok ? BIT4_1 : BIT4_0); + thr->push_vec4(res); + return true; +} + +bool of_BOX_VEC4(vthread_t thr, vvp_code_t cp) +{ + unsigned wid = cp->number; + vvp_vector4_t v(wid); + const vvp_vector4_t& top = thr->peek_vec4(0); + unsigned bits = top.size() < wid ? top.size() : wid; + for (unsigned i = 0 ; i < bits ; ++i) + v.set_bit(i, top.value(i)); + thr->pop_vec4(1); + vvp_object_t box(new vvp_boxed_vec4(v)); + thr->push_object(box); + return true; +} + +bool of_UNBOX_VEC4(vthread_t thr, vvp_code_t cp) +{ + unsigned wid = cp->number; + vvp_object_t obj; + thr->pop_object(obj); + vvp_vector4_t res(wid, BIT4_0); + if (vvp_boxed_vec4*box = obj.peek()) { + const vvp_vector4_t& v = box->get_value(); + unsigned bits = v.size() < wid ? v.size() : wid; + for (unsigned i = 0 ; i < bits ; ++i) + res.set_bit(i, v.value(i)); + } + thr->push_vec4(res); + return true; +} diff --git a/vvp/vthread.h b/vvp/vthread.h index 1658c6839..ec616020c 100644 --- a/vvp/vthread.h +++ b/vvp/vthread.h @@ -136,4 +136,8 @@ extern const vvp_vector4_t& vthread_get_vec4_stack(struct vthread_s*thr, unsigne /* This is used to actually delete a thread once we are done with it. */ extern void vthread_delete(vthread_t thr); +/* Push an object onto a (possibly suspended) thread object stack. */ +class vvp_object_t; +extern void vthread_push_obj_item(vthread_t thr, const vvp_object_t& obj); + #endif /* IVL_vthread_H */ diff --git a/vvp/vvp_mailbox.cc b/vvp/vvp_mailbox.cc new file mode 100644 index 000000000..ef252bafa --- /dev/null +++ b/vvp/vvp_mailbox.cc @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2026 Icarus UVM fork contributors + * + * 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. + * + * Runtime implementation of SystemVerilog built-in mailbox and semaphore. + */ + +#include "vvp_mailbox.h" +#include "vthread.h" +#include "schedule.h" +#include + +/* ================================================================ + * vvp_mailbox + * ================================================================ */ + +vvp_mailbox::vvp_mailbox(size_t bound) +: bound_(bound) +{ +} + +vvp_mailbox::~vvp_mailbox() +{ +} + +vvp_object* vvp_mailbox::duplicate() const +{ + vvp_mailbox* copy = new vvp_mailbox(bound_); + copy->items_ = items_; + /* Note: waiters are NOT copied – a duplicate mailbox has no + * pending waiters. */ + return copy; +} + +bool vvp_mailbox::try_put(const vvp_object_t& item) +{ + if (full()) + return false; + items_.push_back(item); + resume_get_waiters_(); + return true; +} + +bool vvp_mailbox::try_get(vvp_object_t& item) +{ + if (empty()) + return false; + item = items_.front(); + items_.pop_front(); + resume_put_waiters_(); + return true; +} + +bool vvp_mailbox::try_peek(vvp_object_t& item) +{ + if (empty()) + return false; + item = items_.front(); + return true; +} + +bool vvp_mailbox::put(vthread_t thr, const vvp_object_t& item) +{ + if (!full()) { + items_.push_back(item); + resume_get_waiters_(); + return true; /* completed immediately */ + } + /* Mailbox is full – suspend the thread. Store the item in the + * waiter record so it is available when space opens up. */ + put_waiter_t w; + w.thr = thr; + w.item = item; + put_waiters_.push_back(w); + return false; /* thread suspended */ +} + +bool vvp_mailbox::get(vthread_t thr, vvp_object_t& item_out) +{ + if (!empty()) { + item_out = items_.front(); + items_.pop_front(); + resume_put_waiters_(); + return true; + } + get_waiter_t w; + w.thr = thr; + w.is_peek = false; + get_waiters_.push_back(w); + return false; +} + +bool vvp_mailbox::peek(vthread_t thr, vvp_object_t& item_out) +{ + if (!empty()) { + item_out = items_.front(); + return true; + } + get_waiter_t w; + w.thr = thr; + w.is_peek = true; + get_waiters_.push_back(w); + return false; +} + +void vvp_mailbox::resume_get_waiters_() +{ + while (!get_waiters_.empty() && !empty()) { + get_waiter_t waiter = get_waiters_.front(); + get_waiters_.erase(get_waiters_.begin()); + /* Push the retrieved item onto the waiting thread's object + * stack so it is available when the thread resumes at the + * instruction following %mbx/get or %mbx/peek. */ + vthread_push_obj_item(waiter.thr, items_.front()); + if (!waiter.is_peek) { + items_.pop_front(); + resume_put_waiters_(); + } + schedule_vthread(waiter.thr, 0, true); + } +} + +void vvp_mailbox::resume_put_waiters_() +{ + while (!put_waiters_.empty() && !full()) { + put_waiter_t w = put_waiters_.front(); + put_waiters_.erase(put_waiters_.begin()); + items_.push_back(w.item); + schedule_vthread(w.thr, 0, true); + resume_get_waiters_(); + } +} + +/* ================================================================ + * vvp_semaphore + * ================================================================ */ + +vvp_semaphore::vvp_semaphore(size_t initial_count) +: count_(initial_count) +{ +} + +vvp_semaphore::~vvp_semaphore() +{ +} + +vvp_object* vvp_semaphore::duplicate() const +{ + return new vvp_semaphore(count_); +} + +bool vvp_semaphore::try_get(size_t n) +{ + if (count_ >= n) { + count_ -= n; + return true; + } + return false; +} + +void vvp_semaphore::put(size_t n) +{ + count_ += n; + resume_waiters_(); +} + +bool vvp_semaphore::get(vthread_t thr, size_t n) +{ + if (count_ >= n) { + count_ -= n; + return true; + } + waiter_t w; + w.thr = thr; + w.n = n; + waiters_.push_back(w); + return false; +} + +void vvp_semaphore::resume_waiters_() +{ + bool progress = true; + while (progress) { + progress = false; + for (size_t idx = 0; idx < waiters_.size(); ++idx) { + if (count_ >= waiters_[idx].n) { + count_ -= waiters_[idx].n; + vthread_t w = waiters_[idx].thr; + waiters_.erase(waiters_.begin() + (long)idx); + schedule_vthread(w, 0, true); + progress = true; + break; + } + } + } +} diff --git a/vvp/vvp_mailbox.h b/vvp/vvp_mailbox.h new file mode 100644 index 000000000..579b77bd2 --- /dev/null +++ b/vvp/vvp_mailbox.h @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2026 Icarus UVM fork contributors + * + * 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. + * + * Runtime objects for SystemVerilog built-in mailbox and semaphore. + */ +#ifndef VVP_MAILBOX_H +#define VVP_MAILBOX_H + +#include "vvp_object.h" +#include "vvp_net.h" +#include +#include + +struct vthread_s; +typedef vthread_s* vthread_t; + +/* + * vvp_mailbox -- runtime representation of SystemVerilog's + * built-in parameterized mailbox. + * + * A mailbox is an unbounded (or bounded) typed FIFO. When the FIFO + * is empty, get() and peek() suspend the calling thread. When the + * FIFO is at capacity, put() suspends the calling thread. + * + * We store contents as vvp_object_t (like queue_object). String and + * vec4 mailboxes would need separate classes; for UVM the mailbox + * element type is always an object. + */ +class vvp_mailbox : public vvp_object { + public: + explicit vvp_mailbox(size_t bound = 0); /* 0 = unbounded */ + ~vvp_mailbox() override; + + vvp_object* duplicate() const override; + void shallow_copy(const vvp_object*) override {} + + size_t bound() const { return bound_; } + size_t num() const { return items_.size(); } + bool full() const { return bound_ && items_.size() >= bound_; } + bool empty() const { return items_.empty(); } + + /* Non-blocking operations. Return true on success. */ + bool try_put(const vvp_object_t& item); + bool try_get(vvp_object_t& item); + bool try_peek(vvp_object_t& item); + + /* + * Blocking operations: return true if the operation completed + * immediately, or false if thr was suspended and will be + * rescheduled when the condition changes. + * + * For get/peek: when returning false the thread is added to + * get_waiters_. On resumption (via resume_get_waiters_) the + * retrieved item has already been pushed onto the thread's + * object stack by vthread_push_obj_item(). + * + * For put: when returning false the item is stored in the + * put_waiter_t record; it is NOT pushed to the thread stack. + */ + bool put(vthread_t thr, const vvp_object_t& item); + bool get(vthread_t thr, vvp_object_t& item_out); + bool peek(vthread_t thr, vvp_object_t& item_out); + + private: + void resume_get_waiters_(); + void resume_put_waiters_(); + + size_t bound_; /* 0 = unbounded */ + std::deque items_; + + /* Threads suspended waiting for items (get/peek). */ + struct get_waiter_t { + vthread_t thr; + bool is_peek; + }; + std::vector get_waiters_; + + /* Threads suspended waiting for space (put). + * The item to be put is stored here until space is available. */ + struct put_waiter_t { + vthread_t thr; + vvp_object_t item; + }; + std::vector put_waiters_; +}; + +/* + * vvp_semaphore -- runtime representation of SystemVerilog's + * built-in semaphore. + * + * A semaphore has an integer key count. get(N) blocks until at least + * N keys are available, then decrements by N. put(N) adds N keys + * and wakes any waiters. try_get(N) is non-blocking. + */ +class vvp_semaphore : public vvp_object { + public: + explicit vvp_semaphore(size_t initial_count = 0); + ~vvp_semaphore() override; + + vvp_object* duplicate() const override; + void shallow_copy(const vvp_object*) override {} + + size_t count() const { return count_; } + + /* try_get: if count >= n, decrement and return true; else false. */ + bool try_get(size_t n = 1); + + /* put: add n keys and wake suspended threads. */ + void put(size_t n = 1); + + /* get: block thr until count >= n, then decrement. + * Returns true if immediately satisfied, false if thr is suspended. */ + bool get(vthread_t thr, size_t n = 1); + + private: + void resume_waiters_(); + + size_t count_; + + struct waiter_t { + vthread_t thr; + size_t n; + }; + std::vector waiters_; +}; + +/* + * vvp_boxed_vec4 -- wraps a vec4 (integer/bit) value as a vvp_object_t so + * that it can be stored in a mailbox alongside class objects. Used when + * mailbox #(T) is instantiated with a non-class element type (e.g. int). + */ +class vvp_boxed_vec4 : public vvp_object { + public: + explicit vvp_boxed_vec4(const vvp_vector4_t& v) : value_(v) {} + ~vvp_boxed_vec4() override {} + + vvp_object* duplicate() const override { + return new vvp_boxed_vec4(value_); + } + void shallow_copy(const vvp_object* src) override { + if (const vvp_boxed_vec4* s = dynamic_cast(src)) + value_ = s->value_; + } + + const vvp_vector4_t& get_value() const { return value_; } + + private: + vvp_vector4_t value_; +}; + +#endif /* VVP_MAILBOX_H */