Skip to content

Commit

Permalink
Added nordshift attribute
Browse files Browse the repository at this point in the history
nordshift on a register 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.
  • Loading branch information
daglem committed Nov 9, 2023
1 parent 5691cd0 commit 068931e
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 66 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 packed array prohibits the generation of
shift type circuits for reads from that array.

- 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.

Expand Down
94 changes: 28 additions & 66 deletions frontends/ast/genrtlil.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
}

Expand Down
164 changes: 164 additions & 0 deletions frontends/ast/simplify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2324,6 +2324,170 @@ 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 (stage > 1 && 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 source_width = member_node ?
member_node->range_left - member_node->range_right + 1 :
id2ast->range_left - id2ast->range_right + 1;
int source_offset = id2ast->range_right;
int result_width = 1;

AstNode *shift_expr = NULL;
AstNode *range = children[0];

if (range->children.size() == 1) {
shift_expr = range->children[0]->clone();
} else {
shift_expr = range->children[1]->clone();
if (!try_determine_range_width(range, result_width))
input_error("Unsupported expression on dynamic range select on signal `%s'!\n", str.c_str());
}

int shift_expr_width_hint = -1;
bool shift_expr_sign_hint = true;
shift_expr->detectSignWidth(shift_expr_width_hint, shift_expr_sign_hint);

bool use_case_method = false;

if (id2ast->attributes.count(ID::nordshift)) {
AstNode *node = id2ast->attributes.at(ID::nordshift);
while (node->simplify(true, false, stage, -1, false, false)) { }
if (node->type != AST_CONSTANT)
input_error("Non-constant value for `nordshift' attribute on `%s'!\n", id2ast->str.c_str());
if (node->asAttrConst().as_bool())
use_case_method = true;
}

if (use_case_method) {
// AST_CASE with an AST_COND for each possible bit slice.

int stride = 1;
int div_stride = 1;

// 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;

// Optimization: Extract stride from indexing of two-dimensional packed arrays and
// variable slices on the form dst = src[i*stride +: width].
if (!source_offset && lsb_expr->type == AST_MUL &&
(lsb_expr->children[0]->type == AST_CONSTANT || lsb_expr->children[1]->type == AST_CONSTANT))
{
int const_i = lsb_expr->children[1]->type == AST_CONSTANT;
stride = (int)lsb_expr->children[const_i]->integer;

// Remove multiplication of index by stride.
// FIXME: Counterintuitively, this can yield higher resource usage. Disable for now.
#if 0
div_stride = stride;
AstNode *tmp = lsb_expr->children[1 - const_i]->clone();
delete shift_expr;
shift_expr = tmp;
#endif
}
else if (member_node)
{
// Clamp chunk to range of member within struct/union.
log_assert(!source_offset && !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 = source_width;
for (int dim = 0; dim < dims; dim++) {
stride /= get_struct_range_width(member_node, dim);
}
}

// Limit case conditions to possible index range.
int max_bits = min(shift_expr_width_hint, 31 + shift_expr_sign_hint);
int max_offset = (1u << (max_bits - shift_expr_sign_hint)) - 1;
int min_offset = shift_expr_sign_hint ? -(1u << (max_bits - 1)) : 0;

// New identifier to replace the current rvalue; used as lvalue in AST_COND assignments.
AstNode *wire = new AstNode(AST_WIRE, new AstNode(AST_RANGE, node_int(result_width - 1), node_int(0)));
wire->str = stringf("$bitsel$%s$%s:%d$%d", str.c_str(), RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
if (current_block)
wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
wire->is_logic = true;
while (wire->simplify(true, false, 1, -1, false, false)) { }
current_ast_mod->children.push_back(wire);

AstNode *lvalue = new AstNode(AST_IDENTIFIER);
lvalue->str = wire->str;
lvalue->was_checked = true;

AstNode *assign = new AstNode(AST_CASE, shift_expr);
for (int i = (1 - result_width)/stride*stride; i < source_width; i += stride) {
int start_bit = source_offset + i;
int end_bit = std::min(start_bit + result_width, source_offset + source_width) - 1;
if (start_bit/div_stride < min_offset || start_bit/div_stride > max_offset)
continue;
AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit/div_stride, shift_expr_sign_hint, shift_expr_width_hint));
AstNode *rvalue = clone();
rvalue->delete_children();
if (member_node)
rvalue->attributes[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);
}

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);
wire->is_reg = true;
} 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.
AstNode *rvalue = clone();
rvalue->delete_children();
if (member_node)
rvalue->attributes[ID::wiretype] = member_node->clone();

if (!shift_expr_sign_hint && (source_offset || id2ast->range_swapped)) {
// convert to signed while preserving the sign and value
shift_expr = new AstNode(AST_TO_SIGNED,
new AstNode(AST_CAST_SIZE,
node_int(shift_expr_width_hint + 1),
shift_expr));
}
if (source_offset) {
// offset the shift amount by the lower bound of the dimension
shift_expr = new AstNode(AST_SUB, shift_expr, node_int(source_offset));
}
if (id2ast->range_swapped) {
// reflect the shift amount if the dimension is swapped
shift_expr = new AstNode(AST_SUB, node_int(source_width - result_width), 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");

Expand Down
1 change: 1 addition & 0 deletions kernel/constids.inc
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ X(nolatches)
X(nomem2init)
X(nomem2reg)
X(nomeminit)
X(nordshift)
X(nosync)
X(nowrshmsk)
X(no_ram)
Expand Down

0 comments on commit 068931e

Please sign in to comment.