From fbebaa4acaeda5d7fea6a73661df6526d997d96e Mon Sep 17 00:00:00 2001 From: Dag Lem Date: Mon, 13 Mar 2023 21:29:11 +0100 Subject: [PATCH] Added nordshift attribute nordshift on a net or variable yields generation of muxes instead of shift circuits for dynamic rvalue indexing, akin to nowrshmsk for lvalue indexing. To facilitate this, the AST transformations for rvalue indexing are moved from genrtlil.cc to simplify.cc, bringing them in line with transformations for lvalue indexing. --- README.md | 3 + frontends/ast/genrtlil.cc | 94 ++++++-------------- frontends/ast/simplify.cc | 180 ++++++++++++++++++++++++++++++++++++++ kernel/constids.inc | 1 + 4 files changed, 212 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 69a227b7fb5..e465b9df537 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,9 @@ Verilog Attributes and non-standard features - The ``nowrshmsk`` attribute on a register prohibits the generation of shift-and-mask type circuits for writing to bit slices of that register. +- The ``nordshift`` attribute on a net or variable prohibits the generation of + shift type circuits for reading bit slices from that data object. + - The ``onehot`` attribute on wires mark them as one-hot state register. This is used for example for memory port sharing and set by the fsm_map pass. diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index 0bae0f67374..ed69d4c0941 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -1449,8 +1449,6 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) } // simply return the corresponding RTLIL::SigSpec for an AST_IDENTIFIER node - // for identifiers with dynamic bit ranges (e.g. "foo[bar]" or "foo[bar+3:bar]") a - // shifter cell is created and the output signal of this cell is returned case AST_IDENTIFIER: { RTLIL::Wire *wire = NULL; @@ -1529,8 +1527,8 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) use_const_chunk: if (children.size() != 0) { - if (children[0]->type != AST_RANGE) - input_error("Single range expected.\n"); + if (children[0]->type != AST_RANGE || !children[0]->range_valid) + input_error("Single static range expected.\n"); int source_width = id2ast->range_left - id2ast->range_right + 1; int source_offset = id2ast->range_right; int chunk_left = source_width - 1; @@ -1543,70 +1541,34 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) chunk_right = chunk.offset; } - if (!children[0]->range_valid) { - AstNode *left_at_zero_ast = children[0]->children[0]->clone_at_zero(); - AstNode *right_at_zero_ast = children[0]->children.size() >= 2 ? children[0]->children[1]->clone_at_zero() : left_at_zero_ast->clone(); - while (left_at_zero_ast->simplify(true, 1, -1, false)) { } - while (right_at_zero_ast->simplify(true, 1, -1, false)) { } - if (left_at_zero_ast->type != AST_CONSTANT || right_at_zero_ast->type != AST_CONSTANT) - input_error("Unsupported expression on dynamic range select on signal `%s'!\n", str.c_str()); - int width = abs(int(left_at_zero_ast->integer - right_at_zero_ast->integer)) + 1; - AstNode *fake_ast = new AstNode(AST_NONE, clone(), children[0]->children.size() >= 2 ? - children[0]->children[1]->clone() : children[0]->children[0]->clone()); - fake_ast->children[0]->delete_children(); - if (member_node) - fake_ast->children[0]->set_attribute(ID::wiretype, member_node->clone()); - - int fake_ast_width = 0; - bool fake_ast_sign = true; - fake_ast->children[1]->detectSignWidth(fake_ast_width, fake_ast_sign); - RTLIL::SigSpec shift_val = fake_ast->children[1]->genRTLIL(fake_ast_width, fake_ast_sign); - - if (source_offset != 0) { - shift_val = current_module->Sub(NEW_ID, shift_val, source_offset, fake_ast_sign); - fake_ast->children[1]->is_signed = true; - } - if (id2ast->range_swapped) { - shift_val = current_module->Sub(NEW_ID, RTLIL::SigSpec(source_width - width), shift_val, fake_ast_sign); - fake_ast->children[1]->is_signed = true; - } - if (GetSize(shift_val) >= 32) - fake_ast->children[1]->is_signed = true; - RTLIL::SigSpec sig = binop2rtlil(fake_ast, ID($shiftx), width, fake_ast->children[0]->genRTLIL(), shift_val); - delete left_at_zero_ast; - delete right_at_zero_ast; - delete fake_ast; - return sig; + chunk.width = children[0]->range_left - children[0]->range_right + 1; + chunk.offset += children[0]->range_right - source_offset; + if (id2ast->range_swapped) + chunk.offset = source_width - (chunk.offset + chunk.width); + if (chunk.offset > chunk_left || chunk.offset + chunk.width < chunk_right) { + if (chunk.width == 1) + log_file_warning(filename, location.first_line, "Range select out of bounds on signal `%s': Setting result bit to undef.\n", + str.c_str()); + else + log_file_warning(filename, location.first_line, "Range select [%d:%d] out of bounds on signal `%s': Setting all %d result bits to undef.\n", + children[0]->range_left, children[0]->range_right, str.c_str(), chunk.width); + chunk = RTLIL::SigChunk(RTLIL::State::Sx, chunk.width); } else { - chunk.width = children[0]->range_left - children[0]->range_right + 1; - chunk.offset += children[0]->range_right - source_offset; - if (id2ast->range_swapped) - chunk.offset = source_width - (chunk.offset + chunk.width); - if (chunk.offset > chunk_left || chunk.offset + chunk.width < chunk_right) { - if (chunk.width == 1) - log_file_warning(filename, location.first_line, "Range select out of bounds on signal `%s': Setting result bit to undef.\n", - str.c_str()); - else - log_file_warning(filename, location.first_line, "Range select [%d:%d] out of bounds on signal `%s': Setting all %d result bits to undef.\n", - children[0]->range_left, children[0]->range_right, str.c_str(), chunk.width); - chunk = RTLIL::SigChunk(RTLIL::State::Sx, chunk.width); - } else { - if (chunk.offset + chunk.width - 1 > chunk_left) { - add_undef_bits_msb = (chunk.offset + chunk.width - 1) - chunk_left; - chunk.width -= add_undef_bits_msb; - } - if (chunk.offset < chunk_right) { - add_undef_bits_lsb = chunk_right - chunk.offset; - chunk.width -= add_undef_bits_lsb; - chunk.offset += add_undef_bits_lsb; - } - if (add_undef_bits_lsb) - log_file_warning(filename, location.first_line, "Range [%d:%d] select out of bounds on signal `%s': Setting %d LSB bits to undef.\n", - children[0]->range_left, children[0]->range_right, str.c_str(), add_undef_bits_lsb); - if (add_undef_bits_msb) - log_file_warning(filename, location.first_line, "Range [%d:%d] select out of bounds on signal `%s': Setting %d MSB bits to undef.\n", - children[0]->range_left, children[0]->range_right, str.c_str(), add_undef_bits_msb); + if (chunk.offset + chunk.width - 1 > chunk_left) { + add_undef_bits_msb = (chunk.offset + chunk.width - 1) - chunk_left; + chunk.width -= add_undef_bits_msb; + } + if (chunk.offset < chunk_right) { + add_undef_bits_lsb = chunk_right - chunk.offset; + chunk.width -= add_undef_bits_lsb; + chunk.offset += add_undef_bits_lsb; } + if (add_undef_bits_lsb) + log_file_warning(filename, location.first_line, "Range [%d:%d] select out of bounds on signal `%s': Setting %d LSB bits to undef.\n", + children[0]->range_left, children[0]->range_right, str.c_str(), add_undef_bits_lsb); + if (add_undef_bits_msb) + log_file_warning(filename, location.first_line, "Range [%d:%d] select out of bounds on signal `%s': Setting %d MSB bits to undef.\n", + children[0]->range_left, children[0]->range_right, str.c_str(), add_undef_bits_msb); } } diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index f313e3360b8..9741bd98062 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -1387,6 +1387,8 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin case AST_ASSIGN: while (!children[0]->basic_prep && children[0]->simplify(false, stage, -1, false) == true) did_something = true; + if (type == AST_ASSIGN && children[0]->type == AST_IDENTIFIER && children[0]->children.size() > 0 && !children[0]->children[0]->range_valid) + log_error("Non-constant range in continuous assignment to %s at %s.\n", children[0]->str.c_str(), loc_string().c_str()); while (!children[1]->basic_prep && children[1]->simplify(false, stage, -1, false) == true) did_something = true; children[0]->detectSignWidth(backup_width_hint, backup_sign_hint); @@ -2337,6 +2339,184 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin goto apply_newNode; } + // Rewrite dynamic indexing of rvalue packed dimensions. + if (type == AST_IDENTIFIER && id2ast != NULL && id2ast->type != AST_MEMORY && !in_lvalue && + GetSize(children) == 1 && children[0]->type == AST_RANGE && !children[0]->range_valid) + { + AST::AstNode *member_node = get_struct_member(this); + int wire_width = member_node ? + member_node->range_left - member_node->range_right + 1 : + id2ast->range_left - id2ast->range_right + 1; + int wire_offset = id2ast->range_right; + + AstNode *range = children[0]; + int result_width; + if (!try_determine_range_width(range, result_width)) + input_error("Unsupported expression on dynamic range select on signal `%s'!\n", str.c_str()); + AstNode *shift_expr = range->children.size() >= 2 ? range->children[1]->clone() : range->children[0]->clone(); + + int shift_expr_width_hint; + bool shift_expr_sign_hint; + shift_expr->detectSignWidth(shift_expr_width_hint, shift_expr_sign_hint); + + bool use_case_method = id2ast->get_bool_attribute(ID::nordshift); + + if (use_case_method) { + // AST_CASE with an AST_COND for each possible bit slice. + + int stride = 1; + long long bitno_div = stride; + int shift_expr_width_max = shift_expr_width_hint; + + if (member_node) { + // Clamp chunk to range of member within struct/union. + log_assert(wire_offset == 0 && !id2ast->range_swapped); + + // When the (* nordshift *) attribute is set, a CASE block is generated below + // to select the indexed bit slice. When a multirange array is indexed, the + // start of each possible slice is separated by the bit stride of the last + // index dimension, and we can optimize the CASE block accordingly. + // The dimension of the original array expression is saved in the 'integer' field. + int dims = integer; + stride = wire_width; + for (int dim = 0; dim < dims; dim++) { + stride /= get_struct_range_width(member_node, dim); + } + bitno_div = stride; + } else { + // Extract (index)*(width) from non_opt_range pattern (@selfsz@((index)*(width)))+(0). + AstNode *lsb_expr = + shift_expr->type == AST_ADD && shift_expr->children[0]->type == AST_SELFSZ && + shift_expr->children[1]->type == AST_CONSTANT && shift_expr->children[1]->integer == 0 ? + shift_expr->children[0]->children[0] : + shift_expr; + + // Extract stride from indexing of two-dimensional packed arrays and + // variable slices on the form src[i*stride +: width]. + if (lsb_expr->type == AST_MUL && + (lsb_expr->children[0]->type == AST_CONSTANT || + lsb_expr->children[1]->type == AST_CONSTANT)) + { + int stride_ix = lsb_expr->children[1]->type == AST_CONSTANT; + stride = (int)lsb_expr->children[stride_ix]->integer; + bitno_div = stride != 0 ? stride : 1; + + // Check whether i*stride can overflow. + int i_width; + bool i_sign; + lsb_expr->children[1 - stride_ix]->detectSignWidth(i_width, i_sign); + int stride_width; + bool stride_sign; + lsb_expr->children[stride_ix]->detectSignWidth(stride_width, stride_sign); + shift_expr_width_max = std::max(i_width, stride_width); + // Stride width calculated from actual stride value. + stride_width = std::ceil(std::log2(std::abs(stride))); + + if (i_width + stride_width > shift_expr_width_max) { + // For (truncated) i*stride to be within the range of dst, the following must hold: + // i*stride ≡ bitno (mod shift_mod), i.e. + // i*stride = k*shift_mod + bitno + // + // The Diophantine equation on the form ax + by = c: + // stride*i - shift_mod*k = bitno + // has solutions iff c is a multiple of d = gcd(a, b), i.e. + // bitno mod gcd(stride, shift_mod) = 0 + // + // long long is at least 64 bits in C++11 + long long shift_mod = 1ll << (shift_expr_width_max - shift_expr_sign_hint); + // std::gcd requires C++17 + // bitno_div = std::gcd(stride, shift_mod); + bitno_div = gcd((long long)stride, shift_mod); + } + } + } + + // long long is at least 64 bits in C++11 + long long max_offset = (1ll << (shift_expr_width_max - shift_expr_sign_hint)) - 1; + long long min_offset = shift_expr_sign_hint ? -(1ll << (shift_expr_width_max - 1)) : 0; + + if (!shift_expr_sign_hint && max_offset >= wire_offset + wire_width) { + // When an unsigned address expression can go beyond the address range, + // we must handle potential overflows causing the lower bits of the wire + // to be selected. + min_offset = -((max_offset + 1) >> 1); + } + + // Temporary register to replace the current rvalue; used as lvalue in AST_COND assignments. + AstNode *lvalue = mktemp_logic("$bitsel$"+str+"$", current_ast_mod, current_block, result_width - 1, 0, false); + + AstNode *assign = new AstNode(AST_CASE, shift_expr); + for (int i = 1 - result_width; i < wire_width; i++) { + // Out of range indexes are handled in genrtlil.cc + int start_bit = wire_offset + i; + int end_bit = start_bit + result_width - 1; + // Check whether the current index can be generated by shift_expr. + if (start_bit < min_offset || start_bit > max_offset) + continue; + if (start_bit%bitno_div != 0 || (stride == 0 && start_bit != 0)) + continue; + AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit, shift_expr_sign_hint, shift_expr_width_max)); + AstNode *rvalue = clone(); + rvalue->delete_children(); + if (member_node) + rvalue->set_attribute(ID::wiretype, member_node->clone()); + rvalue->children.push_back(new AstNode(AST_RANGE, + node_int(end_bit), node_int(start_bit))); + cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(AST_ASSIGN_EQ, lvalue->clone(), rvalue))); + assign->children.push_back(cond); + } + + // Default to x bits for out of range addresses. + std::vector x_const(result_width, RTLIL::State::Sx); + assign->children.push_back(new AstNode(AST_COND, + new AstNode(AST_DEFAULT), + new AstNode(AST_BLOCK, + new AstNode(AST_ASSIGN_EQ, + lvalue->clone(), AstNode::mkconst_bits(x_const, false))))); + + if (current_block) { + size_t assign_idx = 0; + while (assign_idx < current_block->children.size() && current_block->children[assign_idx] != current_block_child) + assign_idx++; + log_assert(assign_idx < current_block->children.size()); + current_block->children.insert(current_block->children.begin()+assign_idx, assign); + } else { + AstNode *proc = new AstNode(AST_ALWAYS, new AstNode(AST_BLOCK, assign)); + current_ast_mod->children.push_back(proc); + } + + newNode = lvalue; + } else { + // Shift to access the indexed bit slice. + // This is adapted from old code in genrtlil.cc + AstNode *rvalue = clone(); + rvalue->delete_children(); + if (member_node) + rvalue->set_attribute(ID::wiretype, member_node->clone()); + + if (wire_offset != 0) { + // Offset the shift amount by the lower bound of the dimension. + shift_expr = new AstNode(AST_SUB, shift_expr, node_int(wire_offset)); + } + if (id2ast->range_swapped) { + // Reflect the shift amount if the dimension is swapped. + shift_expr = new AstNode(AST_SUB, node_int(wire_width - result_width), shift_expr); + } + + if (!shift_expr_sign_hint && (wire_offset != 0 || id2ast->range_swapped || shift_expr_width_hint >= 32)) { + // Handle potential unsigned overflows, causing the lower bits of the wire to be selected. + shift_expr = new AstNode(AST_TO_SIGNED, shift_expr); + } + + // Shift rvalue to the right so that the bit slice starts at bit 0. + newNode = new AstNode(AST_CAST_SIZE, + node_int(result_width), + new AstNode(AST_SHIFTX, rvalue, shift_expr)); + } + + goto apply_newNode; + } + if (type == AST_WHILE) input_error("While loops are only allowed in constant functions!\n"); diff --git a/kernel/constids.inc b/kernel/constids.inc index 480e2afc6fa..ec01e63121c 100644 --- a/kernel/constids.inc +++ b/kernel/constids.inc @@ -138,6 +138,7 @@ X(nolatches) X(nomem2init) X(nomem2reg) X(nomeminit) +X(nordshift) X(nosync) X(nowrshmsk) X(no_ram)