From 9549156226097215ba6079e8228400470a3ab612 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 17 Jun 2023 11:07:33 -0700 Subject: [PATCH] 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 --- PExpr.h | 9 +++- elab_expr.cc | 97 +++++++++++++++++++++++++++++++++++++++---- elab_lval.cc | 8 +++- elaborate.cc | 19 +++++---- expr_synth.cc | 53 +++++++++++++++++++++++ netlist.h | 2 + netmisc.cc | 18 +++++++- tgt-vvp/stmt_assign.c | 78 +++++++++++++++++++++++++++++++++- 8 files changed, 264 insertions(+), 20 deletions(-) diff --git a/PExpr.h b/PExpr.h index c55f0d521..b91e9bdcf 100644 --- a/PExpr.h +++ b/PExpr.h @@ -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 &dims, + unsigned int cur_dim, bool need_const) const; private: diff --git a/elab_expr.cc b/elab_expr.cc index 1758427df..fa3d3f87d 100644 --- a/elab_expr.cc +++ b/elab_expr.cc @@ -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(lv_net_type)) + typed_elab = true; + // Special case, PEAssignPattern is context dependend on the type and // always uses the typed elaboration if (dynamic_cast(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(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(ntype)) { + return elaborate_expr_uarray_(des, scope, uarray_type, + uarray_type->static_dimensions(), 0, + need_const); + } if (auto parray_type = dynamic_cast(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 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 &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 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(parms_[idx])) { + expr = ap->elaborate_expr_uarray_(des, scope, uarray_type, + dims, cur_dim, need_const); + } else if (dynamic_cast(parms_[idx])) { + cerr << get_fileline() << ": sorry: " + << "Array concatenation is not yet supported." + << endl; + des->errors++; + } else if (dynamic_cast(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, diff --git a/elab_lval.cc b/elab_lval.cc index 96fdd011e..74c28267e 100644 --- a/elab_lval.cc +++ b/elab_lval.cc @@ -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" diff --git a/elaborate.cc b/elaborate.cc index 1d21002da..0ee0656c4 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -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 (expr); if (!ident) { - des->errors++; if (dynamic_cast (expr)) { cout << loc.get_fileline() << ": sorry: Continuous assignment" << " of array concatenation is not yet supported." << endl; + des->errors++; + return nullptr; } else if (dynamic_cast (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() = " << 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); diff --git a/expr_synth.cc b/expr_synth.cc index cc626277e..f8105e679 100644 --- a/expr_synth.cc +++ b/expr_synth.cc @@ -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(net_type()); + ivl_assert(*this, array_type); + + if (items_.empty()) + return nullptr; + + bool failed = false; + + std::unique_ptr 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 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(); diff --git a/netlist.h b/netlist.h index 61163d66e..5953d2048 100644 --- a/netlist.h +++ b/netlist.h @@ -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 items_; diff --git a/netmisc.cc b/netmisc.cc index fbcc0ccc4..5954db89f 100644 --- a/netmisc.cc +++ b/netmisc.cc @@ -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(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: diff --git a/tgt-vvp/stmt_assign.c b/tgt-vvp/stmt_assign.c index 6e818acff..83bcd7df3 100644 --- a/tgt-vvp/stmt_assign.c +++ b/tgt-vvp/stmt_assign.c @@ -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. */