Add initial support for array assignment patterns

SystemVerilog allows to use assignment patterns to assign values to an
array. E.g. `int a[4] = '{1, 2, 3, 4}`.

Each value is evaluated in the context of the element type of the array.

Nested assignment patterns are supported. E.g. `int a[2][2] = '{'{1, 2},
'{1, 2}};`

Add initial support for array assignment patterns for both continuous as
well as procedural assignments.

For continuous assignments the assignment pattern is synthesized into an
array of nets. Each pin is connected to one of the assignment pattern
values and then the whole net array is connected to target array.

For procedural assignments it is unrolled in the vvp backend. E.g
effectively turning `a = '{1, 2};` into `a[0] = 1; a[1] = 2;`.

Not yet supported are indexed initializers or `default`.
E.g. `int a[10] = '{1:10, default: 20};`

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
This commit is contained in:
Lars-Peter Clausen 2023-06-17 11:07:33 -07:00
parent 4defb9f51e
commit 9549156226
8 changed files with 264 additions and 20 deletions

View File

@ -222,8 +222,13 @@ class PEAssignPattern : public PExpr {
NetExpr* elaborate_expr_struct_(Design *des, NetScope *scope,
const netstruct_t *struct_type,
bool need_const) const;
NetExpr* elaborate_expr_darray_(Design *des, NetScope *scope,
const netdarray_t *array_type,
NetExpr* elaborate_expr_array_(Design *des, NetScope *scope,
const netarray_t *array_type,
bool need_const, bool up) const;
NetExpr* elaborate_expr_uarray_(Design *des, NetScope *scope,
const netuarray_t *uarray_type,
const std::vector<netrange_t> &dims,
unsigned int cur_dim,
bool need_const) const;
private:

View File

@ -142,6 +142,11 @@ NetExpr* elaborate_rval_expr(Design*des, NetScope*scope, ivl_type_t lv_net_type,
break;
}
// If the target is an unpacked array we want full type checking,
// regardless of the base type of the array.
if (dynamic_cast<const netuarray_t *>(lv_net_type))
typed_elab = true;
// Special case, PEAssignPattern is context dependend on the type and
// always uses the typed elaboration
if (dynamic_cast<PEAssignPattern*>(expr))
@ -236,7 +241,13 @@ NetExpr*PEAssignPattern::elaborate_expr(Design*des, NetScope*scope,
bool need_const = NEED_CONST & flags;
if (auto darray_type = dynamic_cast<const netdarray_t*>(ntype))
return elaborate_expr_darray_(des, scope, darray_type, need_const);
return elaborate_expr_array_(des, scope, darray_type, need_const, true);
if (auto uarray_type = dynamic_cast<const netuarray_t*>(ntype)) {
return elaborate_expr_uarray_(des, scope, uarray_type,
uarray_type->static_dimensions(), 0,
need_const);
}
if (auto parray_type = dynamic_cast<const netparray_t*>(ntype)) {
return elaborate_expr_packed_(des, scope, parray_type->base_type(),
@ -265,9 +276,9 @@ NetExpr*PEAssignPattern::elaborate_expr(Design*des, NetScope*scope,
return 0;
}
NetExpr* PEAssignPattern::elaborate_expr_darray_(Design *des, NetScope *scope,
const netdarray_t *array_type,
bool need_const) const
NetExpr* PEAssignPattern::elaborate_expr_array_(Design *des, NetScope *scope,
const netarray_t *array_type,
bool need_const, bool up) const
{
// Special case: If this is an empty pattern (i.e. '{}) then convert
// this to a null handle. Internally, Icarus Verilog uses this to
@ -283,10 +294,14 @@ NetExpr* PEAssignPattern::elaborate_expr_darray_(Design *des, NetScope *scope,
// element_type expressions.
ivl_type_t elem_type = array_type->element_type();
vector<NetExpr*> elem_exprs (parms_.size());
size_t elem_idx = up ? 0 : parms_.size() - 1;
for (size_t idx = 0 ; idx < parms_.size() ; idx += 1) {
NetExpr*tmp = elaborate_rval_expr(des, scope, elem_type,
parms_[idx], need_const);
elem_exprs[idx] = tmp;
elem_exprs[elem_idx] = elaborate_rval_expr(des, scope, elem_type,
parms_[idx], need_const);
if (up)
elem_idx++;
else
elem_idx--;
}
NetEArrayPattern*res = new NetEArrayPattern(array_type, elem_exprs);
@ -294,6 +309,74 @@ NetExpr* PEAssignPattern::elaborate_expr_darray_(Design *des, NetScope *scope,
return res;
}
NetExpr* PEAssignPattern::elaborate_expr_uarray_(Design *des, NetScope *scope,
const netuarray_t *uarray_type,
const std::vector<netrange_t> &dims,
unsigned int cur_dim,
bool need_const) const
{
if (dims.size() <= cur_dim)
return nullptr;
if (dims[cur_dim].width() != parms_.size()) {
cerr << get_fileline() << ": error: Unpacked array assignment pattern expects "
<< dims[cur_dim].width() << " element(s) in this context.\n"
<< get_fileline() << ": : Found "
<< parms_.size() << " element(s)." << endl;
des->errors++;
}
bool up = dims[cur_dim].get_msb() < dims[cur_dim].get_lsb();
if (cur_dim == dims.size() - 1) {
return elaborate_expr_array_(des, scope, uarray_type, need_const, up);
}
cur_dim++;
vector<NetExpr*> elem_exprs(parms_.size());
size_t elem_idx = up ? 0 : parms_.size() - 1;
for (size_t idx = 0; idx < parms_.size(); idx++) {
NetExpr *expr = nullptr;
// Handle nested assignment patterns as a special case. We do not
// have a good way of passing the inner dimensions through the
// generic elaborate_expr() API and assigment patterns is the only
// place where we need it.
if (auto ap = dynamic_cast<PEAssignPattern*>(parms_[idx])) {
expr = ap->elaborate_expr_uarray_(des, scope, uarray_type,
dims, cur_dim, need_const);
} else if (dynamic_cast<PEConcat*>(parms_[idx])) {
cerr << get_fileline() << ": sorry: "
<< "Array concatenation is not yet supported."
<< endl;
des->errors++;
} else if (dynamic_cast<PEIdent*>(parms_[idx])) {
// The only other thing that's allow in this
// context is an array slice or identifier.
cerr << get_fileline() << ": sorry: "
<< "Procedural assignment of array or array slice"
<< " is not yet supported." << endl;
des->errors++;
} else if (parms_[idx]) {
cerr << get_fileline() << ": error: Expression "
<< *parms_[idx]
<< " is not compatible with this context."
<< " Expected array or array-like expression."
<< endl;
des->errors++;
}
elem_exprs[elem_idx] = expr;
if (up)
elem_idx++;
else
elem_idx--;
}
NetEArrayPattern *res = new NetEArrayPattern(uarray_type, elem_exprs);
res->set_line(*this);
return res;
}
NetExpr* PEAssignPattern::elaborate_expr_packed_(Design *des, NetScope *scope,
ivl_variable_type_t base_type,
unsigned int width,

View File

@ -242,8 +242,12 @@ NetAssign_*PEIdent::elaborate_lval_var_(Design *des, NetScope *scope,
// is less than the array dimensions (unpacked).
if (reg->unpacked_dimensions() > name_tail.index.size()) {
if (gn_system_verilog()) {
cerr << get_fileline() << ": sorry: Assignment to an entire"
" array or to an array slice is not yet supported."
if (name_tail.index.empty()) {
NetAssign_*lv = new NetAssign_(reg);
return lv;
}
cerr << get_fileline() << ": sorry: Assignment to an "
" array slice is not yet supported."
<< endl;
} else {
cerr << get_fileline() << ": error: Assignment to an entire"

View File

@ -251,25 +251,29 @@ void PGAssign::elaborate(Design*des, NetScope*scope) const
NetNet *elaborate_unpacked_array(Design *des, NetScope *scope, const LineInfo &loc,
const NetNet *lval, PExpr *expr)
{
NetNet *expr_net;
PEIdent* ident = dynamic_cast<PEIdent*> (expr);
if (!ident) {
des->errors++;
if (dynamic_cast<PEConcat*> (expr)) {
cout << loc.get_fileline() << ": sorry: Continuous assignment"
<< " of array concatenation is not yet supported."
<< endl;
des->errors++;
return nullptr;
} else if (dynamic_cast<PEAssignPattern*> (expr)) {
cout << loc.get_fileline() << ": sorry: Continuous assignment"
<< " of assignment pattern is not yet supported." << endl;
auto net_expr = elaborate_rval_expr(des, scope, lval->array_type(), expr);
expr_net = net_expr->synthesize(des, scope, net_expr);
} else {
cout << loc.get_fileline() << ": error: Can not assign"
<< " non-array expression `" << *expr << "` to array."
<< endl;
des->errors++;
return nullptr;
}
return nullptr;
} else {
expr_net = ident->elaborate_unpacked_net(des, scope);
}
NetNet *expr_net = ident->elaborate_unpacked_net(des, scope);
if (!expr_net)
return nullptr;
@ -2642,8 +2646,9 @@ NetProc* PAssign::elaborate(Design*des, NetScope*scope) const
cerr << get_fileline() << ": PAssign::elaborate: "
<< "lv->word() = <nil>" << endl;
}
ivl_assert(*this, lv->word());
ivl_type_t use_lv_type = utype->element_type();
ivl_type_t use_lv_type = lv_net_type;
if (lv->word())
use_lv_type = utype->element_type();
ivl_assert(*this, use_lv_type);
rv = elaborate_rval_(des, scope, use_lv_type);

View File

@ -25,6 +25,7 @@
# include "netlist.h"
# include "netvector.h"
# include "netparray.h"
# include "netmisc.h"
# include "ivl_assert.h"
@ -813,6 +814,58 @@ NetNet* NetEConcat::synthesize(Design*des, NetScope*scope, NetExpr*root)
return osig;
}
NetNet *NetEArrayPattern::synthesize(Design *des, NetScope *scope, NetExpr *root)
{
const netsarray_t *array_type = dynamic_cast<const netsarray_t *>(net_type());
ivl_assert(*this, array_type);
if (items_.empty())
return nullptr;
bool failed = false;
std::unique_ptr<NetNet*[]> nets(new NetNet*[items_.size()]);
for (unsigned int idx = 0; idx < items_.size(); idx++) {
if (!items_[idx]) {
failed = true;
continue;
}
nets[idx] = items_[idx]->synthesize(des, scope, root);
if (!nets[idx])
failed = true;
}
if (failed)
return nullptr;
// Infer which dimension we are in for nested assignment patterns based on
// the dimensions of the element.
size_t dim = nets[0]->unpacked_dims().size() + 1;
const auto &type_dims = array_type->static_dimensions();
if (dim > type_dims.size())
return nullptr;
std::list<netrange_t> dims(type_dims.end() - dim, type_dims.end());
if (dims.front().width() != items_.size())
return nullptr;
perm_string path = scope->local_symbol();
NetNet *osig = new NetNet(scope, path, NetNet::IMPLICIT, dims,
array_type->element_type());
osig->set_line(*this);
osig->local_flag(true);
unsigned int opin = 0;
for (unsigned int idx = 0; idx < items_.size(); idx++) {
for (unsigned int net_pin = 0; net_pin < nets[idx]->pin_count(); net_pin++)
connect(osig->pin(opin++), nets[idx]->pin(net_pin));
}
return osig;
}
NetNet* NetEConst::synthesize(Design*des, NetScope*scope, NetExpr*)
{
perm_string path = scope->local_symbol();

View File

@ -86,6 +86,7 @@ struct enum_type_t;
class netclass_t;
class netdarray_t;
class netparray_t;
class netuarray_t;
class netqueue_t;
class netenum_t;
class netstruct_t;
@ -2106,6 +2107,7 @@ class NetEArrayPattern : public NetExpr {
NetEArrayPattern* dup_expr() const;
NexusSet* nex_input(bool rem_out = true, bool always_sens = false,
bool nested_func = false) const;
NetNet* synthesize(Design *des, NetScope *scope, NetExpr *root);
private:
std::vector<NetExpr*> items_;

View File

@ -986,7 +986,23 @@ NetExpr* elab_and_eval(Design*des, NetScope*scope, PExpr*pe,
ivl_variable_type_t cast_type = ivl_type_base(lv_net_type);
ivl_variable_type_t expr_type = tmp->expr_type();
if ((cast_type != IVL_VT_NO_TYPE) && (cast_type != expr_type)) {
bool compatible;
// For arrays we need strict type checking here. Long term strict type
// checking should be used for all expressions, but at the moment not
// all expressions do have a ivl_type_t attached to it.
if (dynamic_cast<const netuarray_t*>(lv_net_type)) {
if (tmp->net_type())
compatible = lv_net_type->type_compatible(tmp->net_type());
else
compatible = false;
} else if (cast_type == IVL_VT_NO_TYPE) {
compatible = true;
} else {
compatible = cast_type == expr_type;
}
if (!compatible) {
// Catch some special cases.
switch (cast_type) {
case IVL_VT_DARRAY:

View File

@ -490,6 +490,58 @@ static void store_vec4_to_lval(ivl_statement_t net)
}
}
static unsigned int draw_array_pattern(ivl_signal_t var, ivl_expr_t rval,
unsigned int array_idx)
{
ivl_type_t var_type = ivl_signal_net_type(var);
for (unsigned int idx = 0; idx < ivl_expr_parms(rval); idx += 1) {
ivl_expr_t expr = ivl_expr_parm(rval, idx);
switch (ivl_expr_type(expr)) {
case IVL_EX_ARRAY_PATTERN:
/* Flatten nested array patterns */
array_idx = draw_array_pattern(var, expr, array_idx);
break;
default:
switch (ivl_type_base(var_type)) {
case IVL_VT_BOOL:
case IVL_VT_LOGIC:
draw_eval_vec4(expr);
fprintf(vvp_out, " %%ix/load 3, %u, 0;\n", array_idx);
fprintf(vvp_out, " %%flag_set/imm 4, 0;\n");
fprintf(vvp_out, " %%store/vec4a v%p, 3, 0;\n", var);
break;
case IVL_VT_REAL:
draw_eval_real(expr);
fprintf(vvp_out, " %%ix/load 3, %u, 0;\n", array_idx);
fprintf(vvp_out, " %%flag_set/imm 4, 0;\n");
fprintf(vvp_out, " %%store/reala v%p, 3;\n", var);
break;
case IVL_VT_STRING:
draw_eval_string(expr);
fprintf(vvp_out, " %%ix/load 3, %u, 0;\n", array_idx);
fprintf(vvp_out, " %%flag_set/imm 4, 0;\n");
fprintf(vvp_out, " %%store/stra v%p, 3;\n", var);
break;
case IVL_VT_CLASS:
draw_eval_object(expr);
fprintf(vvp_out, " %%ix/load 3, %u, 0;\n", array_idx);
fprintf(vvp_out, " %%flag_set/imm 4, 0;\n");
fprintf(vvp_out, " %%store/obja v%p, 3;\n", var);
break;
default:
assert(0);
break;
}
array_idx++;
break;
}
}
return array_idx;
}
static int show_stmt_assign_vector(ivl_statement_t net)
{
ivl_expr_t rval = ivl_stmt_rval(net);
@ -498,6 +550,13 @@ static int show_stmt_assign_vector(ivl_statement_t net)
struct vec_slice_info*slices = 0;
int idx_reg;
if (ivl_expr_type(rval) == IVL_EX_ARRAY_PATTERN) {
ivl_lval_t lval = ivl_stmt_lval(net, 0);
ivl_signal_t sig = ivl_lval_sig(lval);
draw_array_pattern(sig, rval, 0);
return 0;
}
/* If this is a compressed assignment, then get the contents
of the l-value. We need these values as part of the r-value
calculation. */
@ -791,6 +850,13 @@ static int show_stmt_assign_sig_real(ivl_statement_t net)
assert(ivl_stmt_lvals(net) == 1);
lval = ivl_stmt_lval(net, 0);
ivl_expr_t rval = ivl_stmt_rval(net);
if (ivl_expr_type(rval) == IVL_EX_ARRAY_PATTERN) {
ivl_signal_t sig = ivl_lval_sig(lval);
draw_array_pattern(sig, rval, 0);
return 0;
}
/* If this is a compressed assignment, then get the contents
of the l-value. We need this value as part of the r-value
calculation. */
@ -800,7 +866,7 @@ static int show_stmt_assign_sig_real(ivl_statement_t net)
get_real_from_lval(lval, slice);
}
draw_eval_real(ivl_stmt_rval(net));
draw_eval_real(rval);
switch (ivl_stmt_opcode(net)) {
case 0:
@ -850,6 +916,11 @@ static int show_stmt_assign_sig_string(ivl_statement_t net)
assert(ivl_stmt_lvals(net) == 1);
assert(ivl_stmt_opcode(net) == 0);
if (ivl_expr_type(rval) == IVL_EX_ARRAY_PATTERN) {
draw_array_pattern(var, rval, 0);
return 0;
}
/* Special case: If the l-value signal (string) is named after
its scope, and the scope is a function, then this is an
assign to a return value and should be handled
@ -1291,6 +1362,11 @@ static int show_stmt_assign_sig_cobject(ivl_statement_t net)
}
} else {
if (ivl_expr_type(rval) == IVL_EX_ARRAY_PATTERN) {
draw_array_pattern(sig, rval, 0);
return 0;
}
/* There is no property select, so evaluate the r-value
as an object and assign the entire object to the
variable. */