SystemVerilog: darray unique methods and min/max with(predicate)
Add dynamic-array unique() and unique_index() support and extend queue/darray min() and max() to accept locator with(...) predicates. Generate dedicated _with sfuns for min/max, emit VVP code for filtered scans, and reduce through new object-stack min/max opcodes. Cover the new behavior with ivtest regressions for darray unique and queue/darray min/max with predicates, and update locator method documentation. Made-with: Cursor
This commit is contained in:
parent
30b4afa7d2
commit
3ccba27edb
81
elab_expr.cc
81
elab_expr.cc
|
|
@ -123,6 +123,10 @@ static NetExpr* elab_queue_locator_with_predicate(
|
|||
sfunc_name = lex_strings.make("$ivl_queue_method$find_last_with");
|
||||
else if (method_suffix == "find_last_index")
|
||||
sfunc_name = lex_strings.make("$ivl_queue_method$find_last_index_with");
|
||||
else if (method_suffix == "min")
|
||||
sfunc_name = lex_strings.make("$ivl_queue_method$min_with");
|
||||
else if (method_suffix == "max")
|
||||
sfunc_name = lex_strings.make("$ivl_queue_method$max_with");
|
||||
else
|
||||
ivl_assert(loc, 0);
|
||||
|
||||
|
|
@ -1737,6 +1741,7 @@ unsigned PECallFunction::test_width_method_(Design*, NetScope*,
|
|||
}
|
||||
|
||||
if (method_name == "find" || method_name == "find_index" ||
|
||||
method_name == "unique" || method_name == "unique_index" ||
|
||||
method_name == "min" || method_name == "max") {
|
||||
expr_type_ = IVL_VT_QUEUE;
|
||||
expr_width_ = 1;
|
||||
|
|
@ -4131,6 +4136,56 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
|
|||
ivl_type_t queue_rtype = queue ? static_cast<ivl_type_t>(queue)
|
||||
: static_cast<ivl_type_t>(dar);
|
||||
|
||||
if (method_name == "unique") {
|
||||
if (parms_.size() != 0) {
|
||||
cerr << get_fileline() << ": error: unique() method "
|
||||
<< "takes no arguments" << endl;
|
||||
des->errors += 1;
|
||||
}
|
||||
if (!queue_method_element_is_integral_vec4(element_type)) {
|
||||
cerr << get_fileline() << ": sorry: dynamic array unique() for this "
|
||||
<< "element type is not yet supported." << endl;
|
||||
des->errors += 1;
|
||||
return 0;
|
||||
}
|
||||
if (with_expr_) {
|
||||
cerr << get_fileline() << ": sorry: dynamic array unique() with a "
|
||||
<< "`with` clause is not yet supported." << endl;
|
||||
des->errors += 1;
|
||||
return 0;
|
||||
}
|
||||
NetESFunc*sys_expr = new NetESFunc(
|
||||
"$ivl_queue_method$unique", queue_rtype, 1);
|
||||
sys_expr->set_line(*this);
|
||||
sys_expr->parm(0, sub_expr);
|
||||
return sys_expr;
|
||||
}
|
||||
if (method_name == "unique_index") {
|
||||
if (parms_.size() != 0) {
|
||||
cerr << get_fileline() << ": error: unique_index() method "
|
||||
<< "takes no arguments" << endl;
|
||||
des->errors += 1;
|
||||
}
|
||||
if (!queue_method_element_is_integral_vec4(element_type)) {
|
||||
cerr << get_fileline() << ": sorry: dynamic array unique_index() for this "
|
||||
<< "element type is not yet supported." << endl;
|
||||
des->errors += 1;
|
||||
return 0;
|
||||
}
|
||||
if (with_expr_) {
|
||||
cerr << get_fileline() << ": sorry: dynamic array unique_index() with a "
|
||||
<< "`with` clause is not yet supported." << endl;
|
||||
des->errors += 1;
|
||||
return 0;
|
||||
}
|
||||
NetESFunc*sys_expr = new NetESFunc(
|
||||
"$ivl_queue_method$unique_index",
|
||||
static_cast<ivl_type_t>(&ivl_queue_unique_index_ret), 1);
|
||||
sys_expr->set_line(*this);
|
||||
sys_expr->parm(0, sub_expr);
|
||||
return sys_expr;
|
||||
}
|
||||
|
||||
if (method_name == "find") {
|
||||
if (!queue_method_element_is_integral_vec4(element_type)) {
|
||||
cerr << get_fileline() << ": sorry: dynamic array find() for this "
|
||||
|
|
@ -4324,10 +4379,15 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
|
|||
return 0;
|
||||
}
|
||||
if (with_expr_) {
|
||||
cerr << get_fileline() << ": sorry: dynamic array " << method_name
|
||||
<< "() with a `with` clause is not yet supported." << endl;
|
||||
des->errors += 1;
|
||||
return 0;
|
||||
if (!parms_.empty()) {
|
||||
cerr << get_fileline() << ": error: array locator `with` clause "
|
||||
"cannot be combined with a method argument." << endl;
|
||||
des->errors += 1;
|
||||
return 0;
|
||||
}
|
||||
return elab_queue_locator_with_predicate(
|
||||
des, scope, *this, with_expr_, sub_expr, element_type,
|
||||
queue_rtype, method_name);
|
||||
}
|
||||
NetESFunc*sys_expr = new NetESFunc(
|
||||
method_name == "min" ? "$ivl_queue_method$min"
|
||||
|
|
@ -4442,10 +4502,15 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
|
|||
return 0;
|
||||
}
|
||||
if (with_expr_) {
|
||||
cerr << get_fileline() << ": sorry: queue " << method_name
|
||||
<< "() with a `with` clause is not yet supported." << endl;
|
||||
des->errors += 1;
|
||||
return 0;
|
||||
if (!parms_.empty()) {
|
||||
cerr << get_fileline() << ": error: array locator `with` clause "
|
||||
"cannot be combined with a method argument." << endl;
|
||||
des->errors += 1;
|
||||
return 0;
|
||||
}
|
||||
return elab_queue_locator_with_predicate(
|
||||
des, scope, *this, with_expr_, sub_expr, element_type,
|
||||
static_cast<ivl_type_t>(queue), method_name);
|
||||
}
|
||||
NetESFunc*sys_expr = new NetESFunc(
|
||||
method_name == "min" ? "$ivl_queue_method$min"
|
||||
|
|
|
|||
|
|
@ -40,5 +40,8 @@ Regression tests (see ivtest/vvp_tests/*.json and regress-vvp.list):
|
|||
sv_queue_find_locators_ext.v Longer queue, compound predicates.
|
||||
sv_queue_unique.v unique / unique_index.
|
||||
sv_darray_find_locators.v Same locator patterns on int[] dynamic array.
|
||||
sv_darray_unique.v unique() and unique_index() on int[] dynamic array.
|
||||
sv_queue_min_max.v min() and max() on queue values.
|
||||
sv_darray_min_max.v min() and max() on dynamic array values.
|
||||
sv_queue_min_max_with.v min()/max() with predicate on queue values.
|
||||
sv_darray_min_max_with.v min()/max() with predicate on dynamic arrays.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
// Regression: dynamic array min/max with(predicate) locator methods.
|
||||
|
||||
module top;
|
||||
|
||||
bit failed = 0;
|
||||
|
||||
`define CHK(cond) if (!(cond)) begin $display("FAILED line %0d", `__LINE__); failed = 1; end
|
||||
|
||||
int a[] = '{4, 7, 2, 5, 7, 1, 6, 3, 1};
|
||||
int r[$];
|
||||
|
||||
initial begin
|
||||
r = a.min() with (item > 3);
|
||||
`CHK(r.size == 1);
|
||||
`CHK(r[0] == 4);
|
||||
|
||||
r = a.max() with (item < 7);
|
||||
`CHK(r.size == 1);
|
||||
`CHK(r[0] == 6);
|
||||
|
||||
r = a.min() with (item > 99);
|
||||
`CHK(r.size == 0);
|
||||
|
||||
r = a.max() with (item > 99);
|
||||
`CHK(r.size == 0);
|
||||
|
||||
if (!failed)
|
||||
$display("PASSED");
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Regression: dynamic array unique() and unique_index().
|
||||
|
||||
module top;
|
||||
|
||||
bit failed = 0;
|
||||
|
||||
`define CHK(cond) if (!(cond)) begin $display("FAILED line %0d", `__LINE__); failed = 1; end
|
||||
|
||||
int a[] = '{1, 2, 1, 3, 2};
|
||||
int u[$];
|
||||
int ix[$];
|
||||
|
||||
initial begin
|
||||
u = a.unique();
|
||||
`CHK(u.size == 3);
|
||||
`CHK(u[0] == 1);
|
||||
`CHK(u[1] == 2);
|
||||
`CHK(u[2] == 3);
|
||||
|
||||
ix = a.unique_index();
|
||||
`CHK(ix.size == 3);
|
||||
`CHK(ix[0] == 0);
|
||||
`CHK(ix[1] == 1);
|
||||
`CHK(ix[2] == 3);
|
||||
|
||||
if (!failed)
|
||||
$display("PASSED");
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// Regression: queue min/max with(predicate) locator methods.
|
||||
|
||||
module top;
|
||||
|
||||
bit failed = 0;
|
||||
|
||||
`define CHK(cond) if (!(cond)) begin $display("FAILED line %0d", `__LINE__); failed = 1; end
|
||||
|
||||
int q[$];
|
||||
int r[$];
|
||||
|
||||
initial begin
|
||||
q.delete();
|
||||
q.push_back(4);
|
||||
q.push_back(7);
|
||||
q.push_back(2);
|
||||
q.push_back(5);
|
||||
q.push_back(7);
|
||||
q.push_back(1);
|
||||
q.push_back(6);
|
||||
q.push_back(3);
|
||||
q.push_back(1);
|
||||
|
||||
r = q.min() with (item > 3);
|
||||
`CHK(r.size == 1);
|
||||
`CHK(r[0] == 4);
|
||||
|
||||
r = q.max() with (item < 7);
|
||||
`CHK(r.size == 1);
|
||||
`CHK(r[0] == 6);
|
||||
|
||||
r = q.min() with (item > 99);
|
||||
`CHK(r.size == 0);
|
||||
|
||||
r = q.max() with (item > 99);
|
||||
`CHK(r.size == 0);
|
||||
|
||||
if (!failed)
|
||||
$display("PASSED");
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -254,6 +254,8 @@ sv_const_fail9 vvp_tests/sv_const_fail9.json
|
|||
sv_darray_assign_op vvp_tests/sv_darray_assign_op.json
|
||||
sv_darray_find_locators vvp_tests/sv_darray_find_locators.json
|
||||
sv_darray_min_max vvp_tests/sv_darray_min_max.json
|
||||
sv_darray_min_max_with vvp_tests/sv_darray_min_max_with.json
|
||||
sv_darray_unique vvp_tests/sv_darray_unique.json
|
||||
sv_default_port_value1 vvp_tests/sv_default_port_value1.json
|
||||
sv_default_port_value2 vvp_tests/sv_default_port_value2.json
|
||||
sv_default_port_value3 vvp_tests/sv_default_port_value3.json
|
||||
|
|
@ -278,6 +280,7 @@ sv_queue_find vvp_tests/sv_queue_find.json
|
|||
sv_queue_find_locators_ext vvp_tests/sv_queue_find_locators_ext.json
|
||||
sv_queue_find_with vvp_tests/sv_queue_find_with.json
|
||||
sv_queue_min_max vvp_tests/sv_queue_min_max.json
|
||||
sv_queue_min_max_with vvp_tests/sv_queue_min_max_with.json
|
||||
sv_wildcard_import8 vvp_tests/sv_wildcard_import8.json
|
||||
sdf_header vvp_tests/sdf_header.json
|
||||
task_return1 vvp_tests/task_return1.json
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"type" : "normal",
|
||||
"source" : "sv_darray_min_max_with.v",
|
||||
"iverilog-args" : [ "-g2005-sv" ],
|
||||
"vlog95" : {
|
||||
"__comment" : "SystemVerilog dynamic array min/max with predicate",
|
||||
"type" : "CE"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"type" : "normal",
|
||||
"source" : "sv_darray_unique.v",
|
||||
"iverilog-args" : [ "-g2005-sv" ],
|
||||
"vlog95" : {
|
||||
"__comment" : "SystemVerilog dynamic array unique methods",
|
||||
"type" : "CE"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"type" : "normal",
|
||||
"source" : "sv_queue_min_max_with.v",
|
||||
"iverilog-args" : [ "-g2005-sv" ],
|
||||
"vlog95" : {
|
||||
"__comment" : "SystemVerilog queue min/max with predicate",
|
||||
"type" : "CE"
|
||||
}
|
||||
}
|
||||
|
|
@ -373,6 +373,10 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
|
|||
mode = 4;
|
||||
else if (strcmp(name, "$ivl_queue_method$find_last_index_with") == 0)
|
||||
mode = 5;
|
||||
else if (strcmp(name, "$ivl_queue_method$min_with") == 0)
|
||||
mode = 6;
|
||||
else if (strcmp(name, "$ivl_queue_method$max_with") == 0)
|
||||
mode = 7;
|
||||
else
|
||||
return 1;
|
||||
|
||||
|
|
@ -392,7 +396,7 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
|
|||
fprintf(vvp_out, " %%load/obj v%p_0;\n", cl);
|
||||
fprintf(vvp_out, " %%prop/queue/size %u;\n", pidx);
|
||||
fprintf(vvp_out, " %%ix/vec4/s %u;\n", n_reg);
|
||||
if (mode == 0 || mode == 1) {
|
||||
if (mode == 0 || mode == 1 || mode == 6 || mode == 7) {
|
||||
fprintf(vvp_out, " %%queue/new_empty/v;\n");
|
||||
}
|
||||
if (!reverse) {
|
||||
|
|
@ -436,6 +440,10 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
|
|||
fprintf(vvp_out, " %%push/ix/vec4 %u, 32, 1;\n", i_reg);
|
||||
fprintf(vvp_out, " %%queue/append_word/v 32;\n");
|
||||
fprintf(vvp_out, " %%jmp T_%u.%u;\n", thread_count, lab_nom);
|
||||
} else if (mode == 6 || mode == 7) {
|
||||
fprintf(vvp_out, " %%load/vec4 v%p_0;\n", item_sig);
|
||||
fprintf(vvp_out, " %%queue/append_word/v %u;\n", elem_wid);
|
||||
fprintf(vvp_out, " %%jmp T_%u.%u;\n", thread_count, lab_nom);
|
||||
} else if (mode == 2) {
|
||||
fprintf(vvp_out, " %%queue/new_empty/v;\n");
|
||||
fprintf(vvp_out, " %%load/vec4 v%p_0;\n", item_sig);
|
||||
|
|
@ -476,6 +484,10 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
|
|||
lab_loop_end);
|
||||
if (mode == 0 || mode == 1) {
|
||||
fprintf(vvp_out, " %%pop/obj 1, 0;\n");
|
||||
} else if (mode == 6 || mode == 7) {
|
||||
fprintf(vvp_out, " %%queue/%s/obj/v %u;\n",
|
||||
mode == 6 ? "min" : "max", elem_wid);
|
||||
fprintf(vvp_out, " %%pop/obj 1, 1;\n");
|
||||
} else {
|
||||
fprintf(vvp_out, " %%queue/new_empty/v;\n");
|
||||
fprintf(vvp_out, " %%pop/obj 1, 1;\n");
|
||||
|
|
@ -485,7 +497,7 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
|
|||
ivl_signal_t sig = ivl_expr_signal(qarg);
|
||||
fprintf(vvp_out, " %%queue/size/v v%p_0;\n", sig);
|
||||
fprintf(vvp_out, " %%ix/vec4/s %u;\n", n_reg);
|
||||
if (mode == 0 || mode == 1) {
|
||||
if (mode == 0 || mode == 1 || mode == 6 || mode == 7) {
|
||||
fprintf(vvp_out, " %%queue/new_empty/v;\n");
|
||||
}
|
||||
if (!reverse) {
|
||||
|
|
@ -529,6 +541,10 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
|
|||
fprintf(vvp_out, " %%push/ix/vec4 %u, 32, 1;\n", i_reg);
|
||||
fprintf(vvp_out, " %%queue/append_word/v 32;\n");
|
||||
fprintf(vvp_out, " %%jmp T_%u.%u;\n", thread_count, lab_nom);
|
||||
} else if (mode == 6 || mode == 7) {
|
||||
fprintf(vvp_out, " %%load/vec4 v%p_0;\n", item_sig);
|
||||
fprintf(vvp_out, " %%queue/append_word/v %u;\n", elem_wid);
|
||||
fprintf(vvp_out, " %%jmp T_%u.%u;\n", thread_count, lab_nom);
|
||||
} else if (mode == 2) {
|
||||
fprintf(vvp_out, " %%queue/new_empty/v;\n");
|
||||
fprintf(vvp_out, " %%load/vec4 v%p_0;\n", item_sig);
|
||||
|
|
@ -562,7 +578,10 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
|
|||
|
||||
fprintf(vvp_out, "T_%u.%u ; loop end (var)\n", thread_count,
|
||||
lab_loop_end);
|
||||
if (mode >= 2) {
|
||||
if (mode == 6 || mode == 7) {
|
||||
fprintf(vvp_out, " %%queue/%s/obj/v %u;\n",
|
||||
mode == 6 ? "min" : "max", elem_wid);
|
||||
} else if (mode >= 2) {
|
||||
fprintf(vvp_out, " %%queue/new_empty/v;\n");
|
||||
}
|
||||
fprintf(vvp_out, "T_%u.%u ; with end (var)\n", thread_count, lab_end);
|
||||
|
|
|
|||
|
|
@ -270,8 +270,10 @@ extern bool of_QUEUE_FIND_LAST_INDEX_V(vthread_t thr, vvp_code_t code);
|
|||
extern bool of_QUEUE_FIND_LAST_PROP_V(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_QUEUE_FIND_LAST_V(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_QUEUE_MIN_PROP_V(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_QUEUE_MIN_OBJ_V(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_QUEUE_MIN_V(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_QUEUE_MAX_PROP_V(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_QUEUE_MAX_OBJ_V(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_QUEUE_MAX_V(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_QUEUE_UNIQUE_INDEX_PROP_V(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_QUEUE_UNIQUE_INDEX_V(vthread_t thr, vvp_code_t code);
|
||||
|
|
|
|||
|
|
@ -291,8 +291,10 @@ static const struct opcode_table_s opcode_table[] = {
|
|||
{ "%queue/find_last/index/v", of_QUEUE_FIND_LAST_INDEX_V, 2, {OA_FUNC_PTR, OA_BIT1, OA_NONE} },
|
||||
{ "%queue/find_last/prop/v", of_QUEUE_FIND_LAST_PROP_V, 2, {OA_NUMBER, OA_BIT1, OA_NONE} },
|
||||
{ "%queue/find_last/v", of_QUEUE_FIND_LAST_V, 2, {OA_FUNC_PTR, OA_BIT1, OA_NONE} },
|
||||
{ "%queue/max/obj/v", of_QUEUE_MAX_OBJ_V, 1, {OA_BIT1, OA_NONE, OA_NONE} },
|
||||
{ "%queue/max/prop/v", of_QUEUE_MAX_PROP_V, 2, {OA_NUMBER, OA_BIT1, OA_NONE} },
|
||||
{ "%queue/max/v", of_QUEUE_MAX_V, 2, {OA_FUNC_PTR, OA_BIT1, OA_NONE} },
|
||||
{ "%queue/min/obj/v", of_QUEUE_MIN_OBJ_V, 1, {OA_BIT1, OA_NONE, OA_NONE} },
|
||||
{ "%queue/min/prop/v", of_QUEUE_MIN_PROP_V, 2, {OA_NUMBER, OA_BIT1, OA_NONE} },
|
||||
{ "%queue/min/v", of_QUEUE_MIN_V, 2, {OA_FUNC_PTR, OA_BIT1, OA_NONE} },
|
||||
{ "%queue/new_empty/v", of_QUEUE_NEW_EMPTY_V, 0, {OA_NONE, OA_NONE, OA_NONE} },
|
||||
|
|
|
|||
|
|
@ -6677,6 +6677,25 @@ bool of_QUEUE_MIN_V(vthread_t thr, vvp_code_t cp)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool of_QUEUE_MIN_OBJ_V(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
unsigned wid = cp->bit_idx[0];
|
||||
vvp_object_t src_obj;
|
||||
thr->pop_object(src_obj);
|
||||
|
||||
vvp_queue_vec4* qsrc = src_obj.peek<vvp_queue_vec4>();
|
||||
vvp_darray* dsrc = 0;
|
||||
if (!qsrc) {
|
||||
vvp_darray* dany = src_obj.peek<vvp_darray>();
|
||||
if (dany && dynamic_cast<vvp_queue*>(dany) == 0)
|
||||
dsrc = dany;
|
||||
}
|
||||
vvp_queue_vec4* dst = qsrc ? queue_run_min_max_src(qsrc, wid, false)
|
||||
: queue_run_min_max_src(dsrc, wid, false);
|
||||
thr->push_object(vvp_object_t(dst));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool of_QUEUE_MIN_PROP_V(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
size_t pid = cp->number;
|
||||
|
|
@ -6722,6 +6741,25 @@ bool of_QUEUE_MAX_V(vthread_t thr, vvp_code_t cp)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool of_QUEUE_MAX_OBJ_V(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
unsigned wid = cp->bit_idx[0];
|
||||
vvp_object_t src_obj;
|
||||
thr->pop_object(src_obj);
|
||||
|
||||
vvp_queue_vec4* qsrc = src_obj.peek<vvp_queue_vec4>();
|
||||
vvp_darray* dsrc = 0;
|
||||
if (!qsrc) {
|
||||
vvp_darray* dany = src_obj.peek<vvp_darray>();
|
||||
if (dany && dynamic_cast<vvp_queue*>(dany) == 0)
|
||||
dsrc = dany;
|
||||
}
|
||||
vvp_queue_vec4* dst = qsrc ? queue_run_min_max_src(qsrc, wid, true)
|
||||
: queue_run_min_max_src(dsrc, wid, true);
|
||||
thr->push_object(vvp_object_t(dst));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool of_QUEUE_MAX_PROP_V(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
size_t pid = cp->number;
|
||||
|
|
|
|||
Loading…
Reference in New Issue