SV: add mailbox/semaphore builtins for UVM IPC

Implement native SystemVerilog mailbox and semaphore with blocking and
nonblocking put/get, so Accellera-style code can use the standard API
without depending on the legacy IVL_UVM poor-man's classes.
This commit is contained in:
mjoekhan 2026-07-21 18:37:08 +05:00
parent 8504b349f8
commit 7379ff6530
21 changed files with 1340 additions and 6 deletions

View File

@ -8,7 +8,7 @@ Each item should be a dedicated `feat/<name>` 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

View File

@ -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-mans mailbox/semaphore; **not** Accellera-compatible |
| [`uvm/`](../uvm/) | Seeded from IVL_UVM (VerifWorks) — messaging, CLP, stub phases, legacy poor-mans 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 |

60
docs/mailbox-semaphore.md Normal file
View File

@ -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-mans” classes (`ivl_uvm_mbx`, `ivl_uvm_semaphore`) remain in `uvm/` for that librarys 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_UVMs `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).

View File

@ -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<const netclass_t*>(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<const netclass_t*>(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: "

View File

@ -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;

View File

@ -4582,6 +4582,66 @@ NetProc* PCallTask::elaborate_method_(Design*des, NetScope*scope,
}
if (const netclass_t*class_type = dynamic_cast<const netclass_t*>(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<perm_string> 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<perm_string> 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<perm_string> 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<perm_string> 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<perm_string> sem_no_parms;
static const std::vector<perm_string> 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

View File

@ -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

View File

@ -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;

66
parse.y
View File

@ -1102,7 +1102,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type,
%type <porttype> port_direction port_direction_opt clocking_direction
%type <expr> clocking_skew_opt
%type <vartype> integer_vector_type integer_vector_type_no_reg
%type <parmvalue> parameter_value_opt
%type <parmvalue> parameter_value_opt type_parameter_value
%type <event_exprs> event_expression_list
%type <event_expr> 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<PExpr*>::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<named_pexpr_t>::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<PExpr*>::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<named_pexpr_t>::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;

View File

@ -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<lgate>*gates,
std::list<named_pexpr_t>*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<decl_assignment_t*>*decls = new std::list<decl_assignment_t*>;
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<PExpr*>::iterator i = overrides->by_order->begin()
; i != overrides->by_order->end() ; ++i)
delete *i;
delete overrides->by_order;
}
if (overrides->by_name) {
for (list<named_pexpr_t>::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<decl_assignment_t*>::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

View File

@ -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);

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -147,7 +147,7 @@ CORE_OBJ = lib_main.o \
substitute.o \
symbols.o ufunc.o codes.o vthread.o schedule.o \
statistics.o tables.o udp.o vvp_island.o vvp_net.o vvp_net_sig.o \
vvp_object.o vvp_cobject.o vvp_darray.o 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 \

View File

@ -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);

View File

@ -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} },

View File

@ -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<vvp_mailbox>();
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<vvp_mailbox>();
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<vvp_mailbox>();
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<vvp_mailbox>();
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_mailbox>();
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_mailbox>();
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<vvp_mailbox>();
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<vvp_semaphore>();
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<vvp_semaphore>();
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<vvp_semaphore>();
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<vvp_boxed_vec4>()) {
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;
}

View File

@ -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 */

201
vvp/vvp_mailbox.cc Normal file
View File

@ -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 <cassert>
/* ================================================================
* 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;
}
}
}
}

157
vvp/vvp_mailbox.h Normal file
View File

@ -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 <deque>
#include <vector>
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<vvp_object_t> items_;
/* Threads suspended waiting for items (get/peek). */
struct get_waiter_t {
vthread_t thr;
bool is_peek;
};
std::vector<get_waiter_t> 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_waiter_t> 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<waiter_t> 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<const vvp_boxed_vec4*>(src))
value_ = s->value_;
}
const vvp_vector4_t& get_value() const { return value_; }
private:
vvp_vector4_t value_;
};
#endif /* VVP_MAILBOX_H */