/* * Copyright (c) 2001-2019 Stephen Williams (steve@icarus.com) * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ # include "config.h" # include # include # include "netlist.h" # include "netparray.h" # include "netvector.h" # include "netmisc.h" # include "PExpr.h" # include "pform_types.h" # include "compiler.h" # include "ivl_assert.h" NetNet* sub_net_from(Design*des, NetScope*scope, long val, NetNet*sig) { netvector_t*zero_vec = new netvector_t(sig->data_type(), sig->vector_width()-1, 0); NetNet*zero_net = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, zero_vec); zero_net->set_line(*sig); zero_net->local_flag(true); if (sig->data_type() == IVL_VT_REAL) { verireal zero (val); NetLiteral*zero_obj = new NetLiteral(scope, scope->local_symbol(), zero); zero_obj->set_line(*sig); des->add_node(zero_obj); connect(zero_net->pin(0), zero_obj->pin(0)); } else { verinum zero ((int64_t)val); zero = cast_to_width(zero, sig->vector_width()); zero.has_sign(sig->get_signed()); NetConst*zero_obj = new NetConst(scope, scope->local_symbol(), zero); zero_obj->set_line(*sig); des->add_node(zero_obj); connect(zero_net->pin(0), zero_obj->pin(0)); } NetAddSub*adder = new NetAddSub(scope, scope->local_symbol(), sig->vector_width()); adder->set_line(*sig); des->add_node(adder); adder->attribute(perm_string::literal("LPM_Direction"), verinum("SUB")); connect(zero_net->pin(0), adder->pin_DataA()); connect(adder->pin_DataB(), sig->pin(0)); netvector_t*tmp_vec = new netvector_t(sig->data_type(), sig->vector_width()-1, 0); NetNet*tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, tmp_vec); tmp->set_line(*sig); tmp->local_flag(true); connect(adder->pin_Result(), tmp->pin(0)); return tmp; } NetNet* cast_to_int2(Design*des, NetScope*scope, NetNet*src, unsigned wid) { if (src->data_type() == IVL_VT_BOOL) return src; netvector_t*tmp_vec = new netvector_t(IVL_VT_BOOL, wid-1, 0, src->get_signed()); NetNet*tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, tmp_vec); tmp->set_line(*src); tmp->local_flag(true); NetCastInt2*cast = new NetCastInt2(scope, scope->local_symbol(), wid); cast->set_line(*src); des->add_node(cast); connect(cast->pin(0), tmp->pin(0)); connect(cast->pin(1), src->pin(0)); return tmp; } NetNet* cast_to_int4(Design*des, NetScope*scope, NetNet*src, unsigned wid) { if (src->data_type() != IVL_VT_REAL) return src; netvector_t*tmp_vec = new netvector_t(IVL_VT_LOGIC, wid-1, 0); NetNet*tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, tmp_vec); tmp->set_line(*src); tmp->local_flag(true); NetCastInt4*cast = new NetCastInt4(scope, scope->local_symbol(), wid); cast->set_line(*src); des->add_node(cast); connect(cast->pin(0), tmp->pin(0)); connect(cast->pin(1), src->pin(0)); return tmp; } NetNet* cast_to_real(Design*des, NetScope*scope, NetNet*src) { if (src->data_type() == IVL_VT_REAL) return src; netvector_t*tmp_vec = new netvector_t(IVL_VT_REAL); NetNet*tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, tmp_vec); tmp->set_line(*src); tmp->local_flag(true); NetCastReal*cast = new NetCastReal(scope, scope->local_symbol(), src->get_signed()); cast->set_line(*src); des->add_node(cast); connect(cast->pin(0), tmp->pin(0)); connect(cast->pin(1), src->pin(0)); return tmp; } NetExpr* cast_to_int2(NetExpr*expr, unsigned width) { // Special case: The expression is already BOOL if (expr->expr_type() == IVL_VT_BOOL) return expr; if (debug_elaborate) cerr << expr->get_fileline() << ": debug: " << "Cast expression to int2, width=" << width << "." << endl; NetECast*cast = new NetECast('2', expr, width, expr->has_sign()); cast->set_line(*expr); return cast; } NetExpr* cast_to_int4(NetExpr*expr, unsigned width) { // Special case: The expression is already LOGIC or BOOL if (expr->expr_type() == IVL_VT_LOGIC || expr->expr_type() == IVL_VT_BOOL) return expr; if (debug_elaborate) cerr << expr->get_fileline() << ": debug: " << "Cast expression to int4, width=" << width << "." << endl; NetECast*cast = new NetECast('v', expr, width, expr->has_sign()); cast->set_line(*expr); return cast; } NetExpr* cast_to_real(NetExpr*expr) { if (expr->expr_type() == IVL_VT_REAL) return expr; if (debug_elaborate) cerr << expr->get_fileline() << ": debug: " << "Cast expression to real." << endl; NetECast*cast = new NetECast('r', expr, 1, true); cast->set_line(*expr); return cast; } /* * Add a signed constant to an existing expression. Generate a new * NetEBAdd node that has the input expression and an expression made * from the constant value. */ static NetExpr* make_add_expr(NetExpr*expr, long val) { if (val == 0) return expr; // If the value to be added is <0, then instead generate a // SUBTRACT node and turn the value positive. char add_op = '+'; if (val < 0) { add_op = '-'; val = -val; } verinum val_v (val, expr->expr_width()); val_v.has_sign(expr->has_sign()); NetEConst*val_c = new NetEConst(val_v); val_c->set_line(*expr); NetEBAdd*res = new NetEBAdd(add_op, expr, val_c, expr->expr_width(), expr->has_sign()); res->set_line(*expr); return res; } static NetExpr* make_add_expr(const LineInfo*loc, NetExpr*expr1, NetExpr*expr2) { bool use_signed = expr1->has_sign() && expr2->has_sign(); unsigned use_wid = expr1->expr_width(); if (expr2->expr_width() > use_wid) use_wid = expr2->expr_width(); expr1 = pad_to_width(expr1, use_wid, *loc); expr2 = pad_to_width(expr2, use_wid, *loc); NetEBAdd*tmp = new NetEBAdd('+', expr1, expr2, use_wid, use_signed); return tmp; } /* * Subtract an existing expression from a signed constant. */ static NetExpr* make_sub_expr(long val, NetExpr*expr) { verinum val_v (val, expr->expr_width()); val_v.has_sign(expr->has_sign()); NetEConst*val_c = new NetEConst(val_v); val_c->set_line(*expr); NetEBAdd*res = new NetEBAdd('-', val_c, expr, expr->expr_width(), expr->has_sign()); res->set_line(*expr); return res; } /* * Subtract a signed constant from an existing expression. */ static NetExpr* make_sub_expr(NetExpr*expr, long val) { verinum val_v (val, expr->expr_width()); val_v.has_sign(expr->has_sign()); NetEConst*val_c = new NetEConst(val_v); val_c->set_line(*expr); NetEBAdd*res = new NetEBAdd('-', expr, val_c, expr->expr_width(), expr->has_sign()); res->set_line(*expr); return res; } /* * Multiply an existing expression by a signed positive number. * This does a lossless multiply, so the arguments will need to be * sized to match the output size. */ static NetExpr* make_mult_expr(NetExpr*expr, unsigned long val) { const unsigned val_wid = ceil(log2((double)val)) ; unsigned use_wid = expr->expr_width() + val_wid; verinum val_v (val, use_wid); val_v.has_sign(expr->has_sign()); NetEConst*val_c = new NetEConst(val_v); val_c->set_line(*expr); // We know by definitions that the expr argument needs to be // padded to be the right argument width for this lossless multiply. expr = pad_to_width(expr, use_wid, *expr); NetEBMult*res = new NetEBMult('*', expr, val_c, use_wid, expr->has_sign()); res->set_line(*expr); return res; } /* * This routine is used to calculate the number of bits needed to * contain the given number. */ static unsigned num_bits(long arg) { unsigned res = 0; /* For a negative value we have room for one extra value, but * we have a signed result so we need an extra bit for this. */ if (arg < 0) { arg = -arg - 1; res += 1; } /* Calculate the number of bits needed here. */ while (arg) { res += 1; arg >>= 1; } return res; } /* * This routine generates the normalization expression needed for a variable * bit select or a variable base expression for an indexed part * select. This function doesn't actually look at the variable * dimensions, it just does the final calculation using msb/lsb of the * last slice, and the off of the slice in the variable. */ NetExpr *normalize_variable_base(NetExpr *base, long msb, long lsb, unsigned long wid, bool is_up, long soff) { long offset = lsb; if (msb < lsb) { /* Correct the offset if needed. */ if (is_up) offset -= wid - 1; /* Calculate the space needed for the offset. */ unsigned min_wid = num_bits(offset); if (num_bits(soff) > min_wid) min_wid = num_bits(soff); /* We need enough space for the larger of the offset or the * base expression. */ if (min_wid < base->expr_width()) min_wid = base->expr_width(); /* Now that we have the minimum needed width increase it by * one to make room for the normalization calculation. */ min_wid += 2; /* Pad the base expression to the correct width. */ base = pad_to_width(base, min_wid, *base); /* If the base expression is unsigned and either the lsb * is negative or it does not fill the width of the base * expression then we could generate negative normalized * values so cast the expression to signed to get the * math correct. */ if ((lsb < 0 || num_bits(lsb+1) <= base->expr_width()) && ! base->has_sign()) { /* We need this extra select to hide the signed * property from the padding above. It will be * removed automatically during code generation. */ NetESelect *tmp = new NetESelect(base, 0 , min_wid); tmp->set_line(*base); tmp->cast_signed(true); base = tmp; } /* Normalize the expression. */ base = make_sub_expr(offset+soff, base); } else { /* Correct the offset if needed. */ if (!is_up) offset += wid - 1; /* If the offset is zero then just return the base (index) * expression. */ if ((soff-offset) == 0) return base; /* Calculate the space needed for the offset. */ unsigned min_wid = num_bits(-offset); if (num_bits(soff) > min_wid) min_wid = num_bits(soff); /* We need enough space for the larger of the offset or the * base expression. */ if (min_wid < base->expr_width()) min_wid = base->expr_width(); /* Now that we have the minimum needed width increase it by * one to make room for the normalization calculation. */ min_wid += 2; /* Pad the base expression to the correct width. */ base = pad_to_width(base, min_wid, *base); /* If the offset is greater than zero then we need to do * signed math to get the location value correct. */ if (offset > 0 && ! base->has_sign()) { /* We need this extra select to hide the signed * property from the padding above. It will be * removed automatically during code generation. */ NetESelect *tmp = new NetESelect(base, 0 , min_wid); tmp->set_line(*base); tmp->cast_signed(true); base = tmp; } /* Normalize the expression. */ base = make_add_expr(base, soff-offset); } return base; } /* * This method is how indices should work except that the base should * be a vector of expressions that matches the size of the dims list, * so that we can generate an expression based on the entire packed * vector. For now, we assert that there is only one set of dimensions. */ NetExpr *normalize_variable_base(NetExpr *base, const list&dims, unsigned long wid, bool is_up) { ivl_assert(*base, dims.size() == 1); const netrange_t&rng = dims.back(); return normalize_variable_base(base, rng.get_msb(), rng.get_lsb(), wid, is_up); } NetExpr *normalize_variable_bit_base(const list&indices, NetExpr*base, const NetNet*reg) { const vector&packed_dims = reg->packed_dims(); ivl_assert(*base, indices.size()+1 == packed_dims.size()); // Get the canonical offset of the slice within which we are // addressing. We need that address as a slice offset to // calculate the proper complete address const netrange_t&rng = packed_dims.back(); long slice_off = reg->sb_to_idx(indices, rng.get_lsb()); return normalize_variable_base(base, rng.get_msb(), rng.get_lsb(), 1, true, slice_off); } NetExpr *normalize_variable_part_base(const list&indices, NetExpr*base, const NetNet*reg, unsigned long wid, bool is_up) { const vector&packed_dims = reg->packed_dims(); ivl_assert(*base, indices.size()+1 == packed_dims.size()); // Get the canonical offset of the slice within which we are // addressing. We need that address as a slice offset to // calculate the proper complete address const netrange_t&rng = packed_dims.back(); long slice_off = reg->sb_to_idx(indices, rng.get_lsb()); return normalize_variable_base(base, rng.get_msb(), rng.get_lsb(), wid, is_up, slice_off); } NetExpr *normalize_variable_slice_base(const list&indices, NetExpr*base, const NetNet*reg, unsigned long&lwid) { const vector&packed_dims = reg->packed_dims(); ivl_assert(*base, indices.size() < packed_dims.size()); vector::const_iterator pcur = packed_dims.end(); for (size_t idx = indices.size() ; idx < packed_dims.size(); idx += 1) { -- pcur; } long sb = min(pcur->get_lsb(), pcur->get_msb()); long loff; reg->sb_to_slice(indices, sb, loff, lwid); unsigned min_wid = base->expr_width(); if ((sb < 0) && !base->has_sign()) min_wid += 1; if (min_wid < num_bits(pcur->get_lsb())) min_wid = pcur->get_lsb(); if (min_wid < num_bits(pcur->get_msb())) min_wid = pcur->get_msb(); base = pad_to_width(base, min_wid, *base); if ((sb < 0) && !base->has_sign()) { NetESelect *tmp = new NetESelect(base, 0 , min_wid); tmp->set_line(*base); tmp->cast_signed(true); base = tmp; } if (pcur->get_msb() >= pcur->get_lsb()) { if (pcur->get_lsb() != 0) base = make_sub_expr(base, pcur->get_lsb()); base = make_mult_expr(base, lwid); min_wid = base->expr_width(); if (min_wid < num_bits(loff)) min_wid = num_bits(loff); if (loff != 0) min_wid += 1; base = pad_to_width(base, min_wid, *base); base = make_add_expr(base, loff); } else { if (pcur->get_msb() != 0) base = make_sub_expr(base, pcur->get_msb()); base = make_mult_expr(base, lwid); min_wid = base->expr_width(); if (min_wid < num_bits(loff)) min_wid = num_bits(loff); if (loff != 0) min_wid += 1; base = pad_to_width(base, min_wid, *base); base = make_sub_expr(loff, base); } return base; } ostream& operator << (ostream&o, __IndicesManip val) { for (list::const_iterator cur = val.val.begin() ; cur != val.val.end() ; ++cur) { o << "[" << *cur << "]"; } return o; } ostream& operator << (ostream&o, __IndicesManip val) { for (list::const_iterator cur = val.val.begin() ; cur != val.val.end() ; ++cur) { o << "[" << *(*cur) << "]"; } return o; } /* * The src is the input index expression list from the expression, and * the count is the number that are to be elaborated into the indices * list. At the same time, create a indices_const list that contains * the evaluated values for the expression, if they can be evaluated. */ void indices_to_expressions(Design*des, NetScope*scope, // loc is for error messages. const LineInfo*loc, // src is the index list, and count is // the number of items in the list to use. const list&src, unsigned count, // True if the expression MUST be constant. bool need_const, // These are the outputs. indices_flags&flags, list&indices, list&indices_const) { ivl_assert(*loc, count <= src.size()); flags.invalid = false; flags.variable = false; flags.undefined = false; for (list::const_iterator cur = src.begin() ; count > 0 ; ++cur, --count) { ivl_assert(*loc, cur->sel != index_component_t::SEL_NONE); if (cur->sel != index_component_t::SEL_BIT) { cerr << loc->get_fileline() << ": error: " << "Array cannot be indexed by a range." << endl; des->errors += 1; } ivl_assert(*loc, cur->msb); NetExpr*word_index = elab_and_eval_lossless(des, scope, cur->msb, -2, need_const); if (word_index == 0) flags.invalid = true; // Track if we detect any non-constant expressions // here. This may allow for a special case. NetEConst*word_const = dynamic_cast (word_index); if (word_const == 0) flags.variable = true; else if (!word_const->value().is_defined()) flags.undefined = true; else if (!flags.variable && !flags.undefined) indices_const.push_back(word_const->value().as_long()); indices.push_back(word_index); } } static void make_strides(const vector&dims, vector&stride) { stride[dims.size()-1] = 1; for (size_t idx = stride.size()-1 ; idx > 0 ; --idx) { long tmp = dims[idx].width(); if (idx < stride.size()) tmp *= stride[idx]; stride[idx-1] = tmp; } } /* * Take in a vector of constant indices and convert them to a single * number that is the canonical address (zero based, 1-d) of the * word. If any of the indices are out of bounds, return nil instead * of an expression. */ static NetExpr* normalize_variable_unpacked(const vector&dims, list&indices) { // Make strides for each index. The stride is the distance (in // words) to the next element in the canonical array. vector stride (dims.size()); make_strides(dims, stride); int64_t canonical_addr = 0; int idx = 0; for (list::const_iterator cur = indices.begin() ; cur != indices.end() ; ++cur, ++idx) { long tmp = *cur; if (dims[idx].get_lsb() <= dims[idx].get_msb()) tmp -= dims[idx].get_lsb(); else tmp -= dims[idx].get_msb(); // Notice of this index is out of range. if (tmp < 0 || tmp >= (long)dims[idx].width()) { return 0; } canonical_addr += tmp * stride[idx]; } NetEConst*canonical_expr = new NetEConst(verinum(canonical_addr)); return canonical_expr; } NetExpr* normalize_variable_unpacked(const NetNet*net, list&indices) { const vector&dims = net->unpacked_dims(); return normalize_variable_unpacked(dims, indices); } NetExpr* normalize_variable_unpacked(const netsarray_t*stype, list&indices) { const vector&dims = stype->static_dimensions(); return normalize_variable_unpacked(dims, indices); } NetExpr* normalize_variable_unpacked(const LineInfo&loc, const vector&dims, list&indices) { // Make strides for each index. The stride is the distance (in // words) to the next element in the canonical array. vector stride (dims.size()); make_strides(dims, stride); NetExpr*canonical_expr = 0; int idx = 0; for (list::const_iterator cur = indices.begin() ; cur != indices.end() ; ++cur, ++idx) { NetExpr*tmp = *cur; // If the expression elaboration generated errors, then // give up. Presumably, the error during expression // elaboration already generated the error message. if (tmp == 0) return 0; int64_t use_base; if (! dims[idx].defined()) use_base = 0; else if (dims[idx].get_lsb() <= dims[idx].get_msb()) use_base = dims[idx].get_lsb(); else use_base = dims[idx].get_msb(); int64_t use_stride = stride[idx]; // Account for that we are doing arithmetic and should // have a proper width to make sure there are no // losses. So calculate a min_wid width. unsigned tmp_wid; unsigned min_wid = tmp->expr_width(); if (use_base != 0 && ((tmp_wid = num_bits(use_base)) >= min_wid)) min_wid = tmp_wid + 1; if ((tmp_wid = num_bits(dims[idx].width()+1)) >= min_wid) min_wid = tmp_wid + 1; if (use_stride != 1) min_wid += num_bits(use_stride); tmp = pad_to_width(tmp, min_wid, loc); // Now generate the math to calculate the canonical address. NetExpr*tmp_scaled = 0; if (NetEConst*tmp_const = dynamic_cast (tmp)) { // Special case: the index is constant, so this // iteration can be replaced with a constant // expression. int64_t val = tmp_const->value().as_long(); val -= use_base; val *= use_stride; // Very special case: the index is zero, so we can // skip this iteration if (val == 0) continue; tmp_scaled = new NetEConst(verinum(val)); } else { tmp_scaled = tmp; if (use_base != 0) tmp_scaled = make_add_expr(tmp_scaled, -use_base); if (use_stride != 1) tmp_scaled = make_mult_expr(tmp_scaled, use_stride); } if (canonical_expr == 0) { canonical_expr = tmp_scaled; } else { bool expr_has_sign = canonical_expr->has_sign() && tmp_scaled->has_sign(); canonical_expr = new NetEBAdd('+', canonical_expr, tmp_scaled, canonical_expr->expr_width()+1, expr_has_sign); } } // If we don't have an expression at this point, all the indices were // constant zero. But this variant of normalize_variable_unpacked() // is only used when at least one index is not a constant. ivl_assert(loc, canonical_expr); return canonical_expr; } NetExpr* normalize_variable_unpacked(const NetNet*net, list&indices) { const vector&dims = net->unpacked_dims(); return normalize_variable_unpacked(*net, dims, indices); } NetExpr* normalize_variable_unpacked(const LineInfo&loc, const netsarray_t*stype, list&indices) { const vector&dims = stype->static_dimensions(); return normalize_variable_unpacked(loc, dims, indices); } NetExpr* make_canonical_index(Design*des, NetScope*scope, const LineInfo*loc, const std::list&src, const netsarray_t*stype, bool need_const) { NetExpr*canon_index = 0; list indices_const; list indices_expr; indices_flags flags; indices_to_expressions(des, scope, loc, src, src.size(), need_const, flags, indices_expr, indices_const); if (flags.undefined) { cerr << loc->get_fileline() << ": warning: " << "ignoring undefined value array access." << endl; } else if (flags.variable) { canon_index = normalize_variable_unpacked(*loc, stype, indices_expr); } else { canon_index = normalize_variable_unpacked(stype, indices_const); } return canon_index; } NetEConst* make_const_x(unsigned long wid) { verinum xxx (verinum::Vx, wid); NetEConst*resx = new NetEConst(xxx); return resx; } NetEConst* make_const_0(unsigned long wid) { verinum xxx (verinum::V0, wid); NetEConst*resx = new NetEConst(xxx); return resx; } NetEConst* make_const_val(unsigned long value) { verinum tmp (value, integer_width); NetEConst*res = new NetEConst(tmp); return res; } NetEConst* make_const_val_s(long value) { verinum tmp (value, integer_width); tmp.has_sign(true); NetEConst*res = new NetEConst(tmp); return res; } NetNet* make_const_x(Design*des, NetScope*scope, unsigned long wid) { verinum xxx (verinum::Vx, wid); NetConst*res = new NetConst(scope, scope->local_symbol(), xxx); des->add_node(res); netvector_t*sig_vec = new netvector_t(IVL_VT_LOGIC, wid-1, 0); NetNet*sig = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, sig_vec); sig->local_flag(true); connect(sig->pin(0), res->pin(0)); return sig; } NetNet* make_const_z(Design*des, NetScope*scope, unsigned long wid) { verinum xxx (verinum::Vz, wid); NetConst*res = new NetConst(scope, scope->local_symbol(), xxx); des->add_node(res); netvector_t*sig_vec = new netvector_t(IVL_VT_LOGIC, wid-1, 0); NetNet*sig = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, sig_vec); sig->local_flag(true); connect(sig->pin(0), res->pin(0)); return sig; } NetExpr* condition_reduce(NetExpr*expr) { if (expr->expr_type() == IVL_VT_REAL) { if (NetECReal *tmp = dynamic_cast(expr)) { verinum::V res; if (tmp->value().as_double() == 0.0) res = verinum::V0; else res = verinum::V1; verinum vres (res, 1, true); NetExpr *rtn = new NetEConst(vres); rtn->set_line(*expr); delete expr; return rtn; } NetExpr *rtn = new NetEBComp('n', expr, new NetECReal(verireal(0.0))); rtn->set_line(*expr); return rtn; } if (expr->expr_width() == 1) return expr; verinum zero (verinum::V0, expr->expr_width()); zero.has_sign(expr->has_sign()); NetEConst*ezero = new NetEConst(zero); ezero->set_line(*expr); NetEBComp*cmp = new NetEBComp('n', expr, ezero); cmp->set_line(*expr); cmp->cast_signed(false); return cmp; } static NetExpr* do_elab_and_eval(Design*des, NetScope*scope, PExpr*pe, int context_width, bool need_const, bool annotatable, bool force_expand, ivl_variable_type_t cast_type, bool force_unsigned) { PExpr::width_mode_t mode = PExpr::SIZED; if ((context_width == -2) && !gn_strict_expr_width_flag) mode = PExpr::EXPAND; if (force_expand) mode = PExpr::EXPAND; pe->test_width(des, scope, mode); // Get the final expression width. If the expression is unsized, // this may be different from the value returned by test_width(). unsigned expr_width = pe->expr_width(); // If context_width is positive, this is the RHS of an assignment, // so the LHS width must also be included in the width calculation. unsigned pos_context_width = context_width > 0 ? context_width : 0; if ((pe->expr_type() != IVL_VT_REAL) && (expr_width < pos_context_width)) expr_width = pos_context_width; // If this is the RHS of a compressed assignment, the LHS also // affects the expression type (signed/unsigned). if (force_unsigned) pe->cast_signed(false); if (debug_elaborate) { cerr << pe->get_fileline() << ": elab_and_eval: test_width of " << *pe << endl; cerr << pe->get_fileline() << ": : " << "returns type=" << pe->expr_type() << ", context_width=" << context_width << ", signed=" << pe->has_sign() << ", force_expand=" << force_expand << ", expr_width=" << expr_width << ", mode=" << PExpr::width_mode_name(mode) << endl; cerr << pe->get_fileline() << ": : " << "cast_type=" << cast_type << endl; } // If we can get the same result using a smaller expression // width, do so. unsigned min_width = pe->min_width(); if ((min_width != UINT_MAX) && (pe->expr_type() != IVL_VT_REAL) && (pos_context_width > 0) && (expr_width > pos_context_width)) { expr_width = max(min_width, pos_context_width); if (debug_elaborate) { cerr << pe->get_fileline() << ": : " << "pruned to width=" << expr_width << endl; } } if ((mode >= PExpr::LOSSLESS) && (expr_width > width_cap) && (expr_width > pos_context_width)) { cerr << pe->get_fileline() << ": warning: excessive unsized " << "expression width detected." << endl; cerr << pe->get_fileline() << ": : The expression width " << "is capped at " << width_cap << " bits." << endl; expr_width = width_cap; } unsigned flags = PExpr::NO_FLAGS; if (need_const) flags |= PExpr::NEED_CONST; if (annotatable) flags |= PExpr::ANNOTATABLE; if (debug_elaborate) { cerr << pe->get_fileline() << ": elab_and_eval: " << "Calculated width is " << expr_width << "." << endl; } NetExpr*tmp = pe->elaborate_expr(des, scope, expr_width, flags); if (tmp == 0) return 0; if ((cast_type != IVL_VT_NO_TYPE) && (cast_type != tmp->expr_type())) { switch (tmp->expr_type()) { case IVL_VT_BOOL: case IVL_VT_LOGIC: case IVL_VT_REAL: break; default: cerr << tmp->get_fileline() << ": error: " "The expression '" << *pe << "' cannot be implicitly " "cast to the target type." << endl; des->errors += 1; delete tmp; return 0; } switch (cast_type) { case IVL_VT_REAL: tmp = cast_to_real(tmp); break; case IVL_VT_BOOL: tmp = cast_to_int2(tmp, pos_context_width); break; case IVL_VT_LOGIC: tmp = cast_to_int4(tmp, pos_context_width); break; default: break; } } // If the context_width sent is is actually the minimum width, // then raise the context_width to be big enough for the // lossless expression. if (force_expand && context_width > 0) { context_width = max(context_width, (int)expr_width); } eval_expr(tmp, context_width); if (NetEConst*ce = dynamic_cast(tmp)) { if ((mode >= PExpr::LOSSLESS) && (context_width < 0)) ce->trim(); } return tmp; } NetExpr* elab_and_eval(Design*des, NetScope*scope, PExpr*pe, int context_width, bool need_const, bool annotatable, ivl_variable_type_t cast_type, bool force_unsigned) { return do_elab_and_eval(des, scope, pe, context_width, need_const, annotatable, false, cast_type, force_unsigned); } /* * This variant of elab_and_eval does the expression losslessly, no * matter what the generation of Verilog. This is in support of * certain special contexts, notably index expressions. */ NetExpr* elab_and_eval_lossless(Design*des, NetScope*scope, PExpr*pe, int context_width, bool need_const, bool annotatable, ivl_variable_type_t cast_type) { return do_elab_and_eval(des, scope, pe, context_width, need_const, annotatable, true, cast_type, false); } NetExpr* elab_and_eval(Design*des, NetScope*scope, PExpr*pe, ivl_type_t lv_net_type, bool need_const) { if (debug_elaborate) { cerr << pe->get_fileline() << ": elab_and_eval: " << "pe=" << *pe << ", lv_net_type=" << *lv_net_type << endl; } // Elaborate the expression using the more general // elaborate_expr method. unsigned flags = PExpr::NO_FLAGS; if (need_const) flags |= PExpr::NEED_CONST; NetExpr*tmp = pe->elaborate_expr(des, scope, lv_net_type, flags); if (tmp == 0) return 0; ivl_variable_type_t cast_type = ivl_type_base(lv_net_type); if ((cast_type != IVL_VT_NO_TYPE) && (cast_type != tmp->expr_type())) { // Catch some special cases. switch (cast_type) { case IVL_VT_DARRAY: case IVL_VT_QUEUE: if (dynamic_cast(pe)) return tmp; // fall through case IVL_VT_STRING: if (dynamic_cast(pe)) return tmp; break; case IVL_VT_CLASS: if (dynamic_cast(pe)) return tmp; break; default: break; } cerr << tmp->get_fileline() << ": error: " "The expression '" << *pe << "' cannot be implicitly " "cast to the target type." << endl; des->errors += 1; delete tmp; return 0; } return tmp; } NetExpr* elab_sys_task_arg(Design*des, NetScope*scope, perm_string name, unsigned arg_idx, PExpr*pe, bool need_const) { PExpr::width_mode_t mode = PExpr::SIZED; pe->test_width(des, scope, mode); if (debug_elaborate) { cerr << pe->get_fileline() << ": debug: test_width of " << name << " argument " << (arg_idx+1) << " " << *pe << endl; cerr << pe->get_fileline() << ": " << "returns type=" << pe->expr_type() << ", width=" << pe->expr_width() << ", signed=" << pe->has_sign() << ", mode=" << PExpr::width_mode_name(mode) << endl; } unsigned flags = PExpr::SYS_TASK_ARG; if (need_const) flags |= PExpr::NEED_CONST; NetExpr*tmp = pe->elaborate_expr(des, scope, pe->expr_width(), flags); if (tmp == 0) return 0; eval_expr(tmp, -1); if (NetEConst*ce = dynamic_cast(tmp)) { // For lossless/unsized constant expressions, we can now // determine the exact width required to hold the result. // But leave literal numbers exactly as the user supplied // them. if ((mode >= PExpr::LOSSLESS) && !dynamic_cast(pe) && tmp->expr_width()>32) ce->trim(); } return tmp; } bool evaluate_range(Design*des, NetScope*scope, const LineInfo*li, const pform_range_t&range, long&index_l, long&index_r) { bool dimension_ok = true; // Unsized and queue dimensions should be handled before calling // this function. If we find them here, we are in a context where // they are not allowed. if (range.first == 0) { cerr << li->get_fileline() << ": error: " "An unsized dimension is not allowed here." << endl; dimension_ok = false; des->errors += 1; } else if (dynamic_cast(range.first)) { cerr << li->get_fileline() << ": error: " "A queue dimension is not allowed here." << endl; dimension_ok = false; des->errors += 1; } else { NetExpr*texpr = elab_and_eval(des, scope, range.first, -1, true); if (! eval_as_long(index_l, texpr)) { cerr << range.first->get_fileline() << ": error: " "Dimensions must be constant." << endl; cerr << range.first->get_fileline() << " : " << (range.second ? "This MSB" : "This size") << " expression violates the rule: " << *range.first << endl; dimension_ok = false; des->errors += 1; } delete texpr; if (range.second == 0) { // This is a SystemVerilog [size] dimension. The IEEE // standard does not allow this in a packed dimension, // but we do. At least one commercial simulator does too. if (!dimension_ok) { // bail out } else if (index_l > 0) { index_l = index_l - 1; index_r = 0; } else { cerr << range.first->get_fileline() << ": error: " "Dimension size must be greater than zero." << endl; cerr << range.first->get_fileline() << " : " "This size expression violates the rule: " << *range.first << endl; dimension_ok = false; des->errors += 1; } } else { texpr = elab_and_eval(des, scope, range.second, -1, true); if (! eval_as_long(index_r, texpr)) { cerr << range.second->get_fileline() << ": error: " "Dimensions must be constant." << endl; cerr << range.second->get_fileline() << " : " "This LSB expression violates the rule: " << *range.second << endl; dimension_ok = false; des->errors += 1; } delete texpr; } } /* Error recovery */ if (!dimension_ok) { index_l = 0; index_r = 0; } return dimension_ok; } bool evaluate_ranges(Design*des, NetScope*scope, const LineInfo*li, vector&llist, const list&rlist) { bool dimensions_ok = true; for (list::const_iterator cur = rlist.begin() ; cur != rlist.end() ; ++cur) { long index_l, index_r; dimensions_ok &= evaluate_range(des, scope, li, *cur, index_l, index_r); llist.push_back(netrange_t(index_l, index_r)); } return dimensions_ok; } void eval_expr(NetExpr*&expr, int context_width) { assert(expr); if (dynamic_cast(expr)) return; NetExpr*tmp = expr->eval_tree(); if (tmp != 0) { tmp->set_line(*expr); delete expr; expr = tmp; } if (context_width <= 0) return; NetEConst *ce = dynamic_cast(expr); if (ce == 0) return; // The expression is a constant, so resize it if needed. if (ce->expr_width() < (unsigned)context_width) { expr = pad_to_width(expr, context_width, *expr); } else if (ce->expr_width() > (unsigned)context_width) { verinum value(ce->value(), context_width); ce = new NetEConst(value); ce->set_line(*expr); delete expr; expr = ce; } } bool eval_as_long(long&value, const NetExpr*expr) { if (const NetEConst*tmp = dynamic_cast(expr) ) { value = tmp->value().as_long(); return true; } if (const NetECReal*rtmp = dynamic_cast(expr)) { value = rtmp->value().as_long(); return true; } return false; } bool eval_as_double(double&value, NetExpr*expr) { if (NetEConst*tmp = dynamic_cast(expr) ) { value = tmp->value().as_double(); return true; } if (NetECReal*rtmp = dynamic_cast(expr)) { value = rtmp->value().as_double(); return true; } return false; } /* * At the parser level, a name component is a name with a collection * of expressions. For example foo[N] is the name "foo" and the index * expression "N". This function takes as input the name component and * returns the path component name. It will evaluate the index * expression if it is present. */ hname_t eval_path_component(Design*des, NetScope*scope, const name_component_t&comp, bool&error_flag) { // No index expression, so the path component is an undecorated // name, for example "foo". if (comp.index.empty()) return hname_t(comp.name); vector index_values; for (list::const_iterator cur = comp.index.begin() ; cur != comp.index.end() ; ++cur) { const index_component_t&index = *cur; if (index.sel != index_component_t::SEL_BIT) { cerr << index.msb->get_fileline() << ": error: " << "Part select is not valid for this kind of object." << endl; des->errors += 1; return hname_t(comp.name, 0); } // The parser will assure that path components will have only // bit select index expressions. For example, "foo[n]" is OK, // but "foo[n:m]" is not. assert(index.sel == index_component_t::SEL_BIT); // Evaluate the bit select to get a number. NetExpr*tmp = elab_and_eval(des, scope, index.msb, -1); ivl_assert(*index.msb, tmp); if (NetEConst*ctmp = dynamic_cast(tmp)) { index_values.push_back(ctmp->value().as_long()); delete ctmp; continue; } #if 1 // Darn, the expression doesn't evaluate to a constant. That's // an error to be reported. And make up a fake index value to // return to the caller. cerr << index.msb->get_fileline() << ": error: " << "Scope index expression is not constant: " << *index.msb << endl; des->errors += 1; #endif error_flag = true; delete tmp; } return hname_t(comp.name, index_values); } std::list eval_scope_path(Design*des, NetScope*scope, const pform_name_t&path) { bool path_error_flag = false; list res; typedef pform_name_t::const_iterator pform_path_it; for (pform_path_it cur = path.begin() ; cur != path.end(); ++ cur ) { const name_component_t&comp = *cur; res.push_back( eval_path_component(des,scope,comp,path_error_flag) ); } #if 0 if (path_error_flag) { cerr << "XXXXX: Errors evaluating path " << path << endl; } #endif return res; } /* * Human readable version of op. Used in elaboration error messages. */ const char *human_readable_op(const char op, bool unary) { const char *type; switch (op) { case '~': type = "~"; break; // Negation case '+': type = "+"; break; case '-': type = "-"; break; case '*': type = "*"; break; case '/': type = "/"; break; case '%': type = "%"; break; case '<': type = "<"; break; case '>': type = ">"; break; case 'L': type = "<="; break; case 'G': type = ">="; break; case '^': type = "^"; break; // XOR case 'X': type = "~^"; break; // XNOR case '&': type = "&"; break; // Bitwise AND case 'A': type = "~&"; break; // NAND (~&) case '|': type = "|"; break; // Bitwise OR case 'O': type = "~|"; break; // NOR case '!': type = "!"; break; // Logical NOT case 'a': type = "&&"; break; // Logical AND case 'o': type = "||"; break; // Logical OR case 'e': type = "=="; break; case 'n': type = "!="; break; case 'E': type = "==="; break; // Case equality case 'N': if (unary) type = "~|"; // NOR else type = "!=="; // Case inequality break; case 'w': type = "==?"; break; // Wild equality case 'W': type = "!=?"; break; // Wild inequality case 'l': type = "<<(<)"; break; // Left shifts case 'r': type = ">>"; break; // Logical right shift case 'R': type = ">>>"; break; // Arithmetic right shift case 'p': type = "**"; break; // Power case 'i': case 'I': type = "++"; break; /* increment */ case 'd': case 'D': type = "--"; break; /* decrement */ default: type = "???"; assert(0); } return type; } const_bool const_logical(const NetExpr*expr) { switch (expr->expr_type()) { case IVL_VT_REAL: { const NetECReal*val = dynamic_cast (expr); if (val == 0) return C_NON; if (val->value().as_double() == 0.0) return C_0; else return C_1; } case IVL_VT_BOOL: case IVL_VT_LOGIC: { const NetEConst*val = dynamic_cast (expr); if (val == 0) return C_NON; verinum cval = val->value(); const_bool res = C_0; for (unsigned idx = 0; idx < cval.len(); idx += 1) { switch (cval.get(idx)) { case verinum::V1: return C_1; break; case verinum::V0: break; default: if (res == C_0) res = C_X; break; } } return res; } default: break; } return C_NON; } uint64_t get_scaled_time_from_real(Design*des, NetScope*scope, NetECReal*val) { verireal fn = val->value(); int shift = scope->time_unit() - scope->time_precision(); assert(shift >= 0); int64_t delay = fn.as_long64(shift); shift = scope->time_precision() - des->get_precision(); assert(shift >= 0); for (int lp = 0; lp < shift; lp += 1) delay *= 10; return delay; } /* * This function looks at the NetNet signal to see if there are any * NetPartSelect::PV nodes driving this signal. If so, See if they can * be collapsed into a single concatenation. */ void collapse_partselect_pv_to_concat(Design*des, NetNet*sig) { NetScope*scope = sig->scope(); vector ps_map (sig->vector_width()); Nexus*nex = sig->pin(0).nexus(); for (Link*cur = nex->first_nlink(); cur ; cur = cur->next_nlink()) { NetPins*obj; unsigned obj_pin; cur->cur_link(obj, obj_pin); // Look for NetPartSelect devices, where this signal is // connected to pin 1 of a NetPartSelect::PV. NetPartSelect*ps_obj = dynamic_cast (obj); if (ps_obj == 0) continue; if (ps_obj->dir() != NetPartSelect::PV) continue; if (obj_pin != 1) continue; // Don't support overrun selects here. if (ps_obj->base()+ps_obj->width() > ps_map.size()) continue; ivl_assert(*ps_obj, ps_obj->base() < ps_map.size()); ps_map[ps_obj->base()] = ps_obj; } // Check the collected NetPartSelect::PV objects to see if // they cover the vector. unsigned idx = 0; unsigned device_count = 0; while (idx < ps_map.size()) { NetPartSelect*ps_obj = ps_map[idx]; if (ps_obj == 0) return; idx += ps_obj->width(); device_count += 1; } ivl_assert(*sig, idx == ps_map.size()); /* The vlog95 and possibly other code generators do not want * to have a group of part selects turned into a transparent * concatenation. */ if (disable_concatz_generation) { // HERE: If the part selects have matching strengths then we can use // a normal concat with a buf-Z after if the strengths are not // both strong. We would ideally delete any buf-Z driving the // concat, but that is not required for the vlog95 generator. return; } // Ah HAH! The NetPartSelect::PV objects exactly cover the // target signal. We can replace all of them with a single // concatenation. if (debug_elaborate) { cerr << sig->get_fileline() << ": debug: " << "Collapse " << device_count << " NetPartSelect::PV devices into a concatenation." << endl; } NetConcat*cat = new NetConcat(scope, scope->local_symbol(), ps_map.size(), device_count, true); des->add_node(cat); cat->set_line(*sig); connect(cat->pin(0), sig->pin(0)); idx = 0; unsigned concat_position = 1; while (idx < ps_map.size()) { assert(ps_map[idx]); NetPartSelect*ps_obj = ps_map[idx]; connect(cat->pin(concat_position), ps_obj->pin(0)); concat_position += 1; idx += ps_obj->width(); delete ps_obj; } } /* * Evaluate the prefix indices. All but the final index in a * chain of indices must be a single value and must evaluate * to constants at compile time. For example: * [x] - OK * [1][2][x] - OK * [1][x:y] - OK * [2:0][x] - BAD * [y][x] - BAD * Leave the last index for special handling. */ bool evaluate_index_prefix(Design*des, NetScope*scope, list&prefix_indices, const list&indices) { list::const_iterator icur = indices.begin(); for (size_t idx = 0 ; (idx+1) < indices.size() ; idx += 1, ++icur) { assert(icur != indices.end()); assert(icur->sel == index_component_t::SEL_BIT); NetExpr*texpr = elab_and_eval(des, scope, icur->msb, -1, true); long tmp; if (texpr == 0 || !eval_as_long(tmp, texpr)) { cerr << icur->msb->get_fileline() << ": error: " "Array index expressions must be constant here." << endl; des->errors += 1; return false; } prefix_indices.push_back(tmp); delete texpr; } return true; } /* * Evaluate the indices. The chain of indices are applied to the * packed indices of a NetNet to generate a canonical expression to * replace the exprs. */ NetExpr*collapse_array_exprs(Design*des, NetScope*scope, const LineInfo*loc, NetNet*net, const list&indices) { // First elaborate all the expressions as far as possible. list exprs; list exprs_const; indices_flags flags; indices_to_expressions(des, scope, loc, indices, net->packed_dimensions(), false, flags, exprs, exprs_const); ivl_assert(*loc, exprs.size() == net->packed_dimensions()); // Special Case: there is only 1 packed dimension, so the // single expression should already be naturally canonical. if (net->slice_width(1) == 1) { return *exprs.begin(); } const std::vector&pdims = net->packed_dims(); std::vector::const_iterator pcur = pdims.begin(); list::iterator ecur = exprs.begin(); NetExpr* base = 0; for (size_t idx = 0 ; idx < net->packed_dimensions() ; idx += 1, ++pcur, ++ecur) { unsigned cur_slice_width = net->slice_width(idx+1); long lsb = pcur->get_lsb(); long msb = pcur->get_msb(); // This normalizes the expression of this index based on // the msb/lsb values. NetExpr*tmp = normalize_variable_base(*ecur, msb, lsb, cur_slice_width, msb > lsb); // If this slice has width, then scale it. if (net->slice_width(idx+1) != 1) { unsigned min_wid = tmp->expr_width(); if (num_bits(cur_slice_width) >= min_wid) { min_wid = num_bits(cur_slice_width)+1; tmp = pad_to_width(tmp, min_wid, *loc); } tmp = make_mult_expr(tmp, cur_slice_width); } // Now add it to the position we've accumulated so far. if (base) { base = make_add_expr(loc, base, tmp); } else { base = tmp; } } return base; } /* * Given a list of indices, treat them as packed indices and convert * them to an expression that normalizes the list to a single index * expression over a canonical equivalent 1-dimensional array. */ NetExpr*collapse_array_indices(Design*des, NetScope*scope, NetNet*net, const list&indices) { listprefix_indices; bool rc = evaluate_index_prefix(des, scope, prefix_indices, indices); assert(rc); const index_component_t&back_index = indices.back(); assert(back_index.sel == index_component_t::SEL_BIT); assert(back_index.msb && !back_index.lsb); NetExpr*base = elab_and_eval(des, scope, back_index.msb, -1, true); NetExpr*res = normalize_variable_bit_base(prefix_indices, base, net); eval_expr(res, -1); return res; } void assign_unpacked_with_bufz(Design*des, NetScope*scope, const LineInfo*loc, NetNet*lval, NetNet*rval) { ivl_assert(*loc, lval->pin_count()==rval->pin_count()); for (unsigned idx = 0 ; idx < lval->pin_count() ; idx += 1) { NetBUFZ*driver = new NetBUFZ(scope, scope->local_symbol(), lval->vector_width(), false); driver->set_line(*loc); des->add_node(driver); connect(lval->pin(idx), driver->pin(0)); connect(driver->pin(1), rval->pin(idx)); } } /* * synthesis sometimes needs to unpack assignment to a part * select. That looks like this: * * foo[N] <= ; * * The NetAssignBase::synth_async() method will turn that into a * netlist like this: * * NetAssignBase(PV) --> base()== * (0) (1) * | | * v v * foo * * This search will return a pointer to the NetAssignBase(PV) object, * but only if it matches this pattern. */ NetPartSelect* detect_partselect_lval(Link&pin) { NetPartSelect*found_ps = 0; Nexus*nex = pin.nexus(); for (Link*cur = nex->first_nlink() ; cur ; cur = cur->next_nlink()) { NetPins*obj; unsigned obj_pin; cur->cur_link(obj, obj_pin); // Skip NexusSet objects. if (obj == 0) continue; // NetNet pins have no effect on this search. if (dynamic_cast (obj)) continue; if (NetPartSelect*ps = dynamic_cast (obj)) { // If this is the input side of a NetPartSelect, skip. if (ps->pin(obj_pin).get_dir()==Link::INPUT) continue; // Oops, driven by the wrong size of a // NetPartSelect, so this is not going to work out. if (ps->dir()==NetPartSelect::VP) return 0; // So now we know this is a NetPartSelect::PV. It // is a candidate for our part-select assign. If // we already have a candidate, then give up. if (found_ps) return 0; // This is our candidate. Carry on. found_ps = ps; continue; } // If this is a driver to the Nexus that is not a // NetPartSelect device. This cannot happen to // part selected lval nets, so quit now. if (obj->pin(obj_pin).get_dir() == Link::OUTPUT) return 0; } return found_ps; } const netclass_t* find_class_containing_scope(const LineInfo&loc, const NetScope*scope) { while (scope && scope->type() != NetScope::CLASS) scope = scope->parent(); if (scope == 0) return 0; const netclass_t*found_in = scope->class_def(); ivl_assert(loc, found_in); return found_in; } /* * Find the scope that contains this scope, that is the method for a * class scope. Look for the scope whose PARENT is the scope for a * class. This is going to be a method. */ NetScope* find_method_containing_scope(const LineInfo&, NetScope*scope) { NetScope*up = scope->parent(); while (up && up->type() != NetScope::CLASS) { scope = up; up = up->parent(); } if (up == 0) return 0; // Should I check if this scope is a TASK or FUNC? return scope; } /* * Print a warning if we find a mixture of default and explicit timescale * based delays in the design, since this is likely an error. */ void check_for_inconsistent_delays(NetScope*scope) { static bool used_implicit_timescale = false; static bool used_explicit_timescale = false; static bool display_ts_dly_warning = true; if (scope->time_from_timescale()) used_explicit_timescale = true; else used_implicit_timescale = true; if (display_ts_dly_warning && used_explicit_timescale && used_implicit_timescale) { if (gn_system_verilog()) { cerr << "warning: Found both default and explicit " "timescale based delays. Use" << endl; cerr << " : -Wtimescale to find the design " "element(s) with no explicit" << endl; cerr << " : timescale." << endl; } else { cerr << "warning: Found both default and " "`timescale based delays. Use" << endl; cerr << " : -Wtimescale to find the " "module(s) with no `timescale." << endl; } display_ts_dly_warning = false; } }