Skip to content

Commit

Permalink
Merge pull request #4069 from daglem/simplify-array-slice-assignment
Browse files Browse the repository at this point in the history
Simplify and correct array slice assignment
  • Loading branch information
povik authored Jan 11, 2024
2 parents f26495e + e0566ea commit eeadbb5
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 70 deletions.
111 changes: 41 additions & 70 deletions frontends/ast/simplify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2966,96 +2966,67 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
}
} else {
// mask and shift operations

AstNode *wire_mask = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(wire_width-1, true), mkconst_int(0, true)));
wire_mask->str = stringf("$bitselwrite$mask$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
wire_mask->set_attribute(ID::nosync, AstNode::mkconst_int(1, false));
wire_mask->is_logic = true;
while (wire_mask->simplify(true, 1, -1, false)) { }
current_ast_mod->children.push_back(wire_mask);

AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(wire_width-1, true), mkconst_int(0, true)));
wire_data->str = stringf("$bitselwrite$data$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
wire_data->set_attribute(ID::nosync, AstNode::mkconst_int(1, false));
wire_data->is_logic = true;
while (wire_data->simplify(true, 1, -1, false)) { }
current_ast_mod->children.push_back(wire_data);

int shamt_width_hint = -1;
bool shamt_sign_hint = true;
shift_expr->detectSignWidth(shamt_width_hint, shamt_sign_hint);

AstNode *wire_sel = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(shamt_width_hint-1, true), mkconst_int(0, true)));
wire_sel->str = stringf("$bitselwrite$sel$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
wire_sel->set_attribute(ID::nosync, AstNode::mkconst_int(1, false));
wire_sel->is_logic = true;
wire_sel->is_signed = shamt_sign_hint;
while (wire_sel->simplify(true, 1, -1, false)) { }
current_ast_mod->children.push_back(wire_sel);

did_something = true;
newNode = new AstNode(AST_BLOCK);
// dst = (dst & ~(width'1 << lsb)) | unsigned'(width'(src)) << lsb)

AstNode *lvalue = children[0]->clone();
lvalue->delete_children();
if (member_node)
lvalue->set_attribute(ID::wiretype, member_node->clone());

AstNode *ref_mask = new AstNode(AST_IDENTIFIER);
ref_mask->str = wire_mask->str;
ref_mask->id2ast = wire_mask;
ref_mask->was_checked = true;

AstNode *ref_data = new AstNode(AST_IDENTIFIER);
ref_data->str = wire_data->str;
ref_data->id2ast = wire_data;
ref_data->was_checked = true;

AstNode *ref_sel = new AstNode(AST_IDENTIFIER);
ref_sel->str = wire_sel->str;
ref_sel->id2ast = wire_sel;
ref_sel->was_checked = true;

AstNode *old_data = lvalue->clone();
if (type == AST_ASSIGN_LE)
old_data->lookahead = true;

AstNode *s = new AstNode(AST_ASSIGN_EQ, ref_sel->clone(), shift_expr);
newNode->children.push_back(s);
int shift_width_hint;
bool shift_sign_hint;
shift_expr->detectSignWidth(shift_width_hint, shift_sign_hint);

// All operations are carried out in a new block.
newNode = new AstNode(AST_BLOCK);

// Temporary register holding the result of the bit- or part-select position expression.
AstNode *pos = mktemp_logic("$bitselwrite$pos$", current_ast_mod, true, shift_width_hint - 1, 0, shift_sign_hint);
newNode->children.push_back(new AstNode(AST_ASSIGN_EQ, pos, shift_expr));

AstNode *shamt = ref_sel;
// Calculate lsb from position.
AstNode *shift_val = pos->clone();

// convert to signed while preserving the sign and value
shamt = new AstNode(AST_CAST_SIZE, mkconst_int(shamt_width_hint + 1, true), shamt);
shamt = new AstNode(AST_TO_SIGNED, shamt);
// If the expression is signed, we must add an extra bit for possible negation of the most negative number.
// If the expression is unsigned, we must add an extra bit for sign.
shift_val = new AstNode(AST_CAST_SIZE, mkconst_int(shift_width_hint + 1, true), shift_val);
if (!shift_sign_hint)
shift_val = new AstNode(AST_TO_SIGNED, shift_val);

// offset the shift amount by the lower bound of the dimension
int start_bit = wire_offset;
shamt = new AstNode(AST_SUB, shamt, mkconst_int(start_bit, true));
if (wire_offset != 0)
shift_val = new AstNode(AST_SUB, shift_val, mkconst_int(wire_offset, true));

// reflect the shift amount if the dimension is swapped
if (children[0]->id2ast->range_swapped)
shamt = new AstNode(AST_SUB, mkconst_int(wire_width - result_width, true), shamt);
shift_val = new AstNode(AST_SUB, mkconst_int(wire_width - result_width, true), shift_val);

// AST_SHIFT uses negative amounts for shifting left
shamt = new AstNode(AST_NEG, shamt);
shift_val = new AstNode(AST_NEG, shift_val);

AstNode *t;

t = mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false);
t = new AstNode(AST_SHIFT, t, shamt->clone());
t = new AstNode(AST_ASSIGN_EQ, ref_mask->clone(), t);
newNode->children.push_back(t);

t = new AstNode(AST_BIT_AND, mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false), children[1]->clone());
t = new AstNode(AST_SHIFT, t, shamt);
t = new AstNode(AST_ASSIGN_EQ, ref_data->clone(), t);
newNode->children.push_back(t);

t = new AstNode(AST_BIT_AND, old_data, new AstNode(AST_BIT_NOT, ref_mask));
t = new AstNode(AST_BIT_OR, t, ref_data);
t = new AstNode(type, lvalue, t);
newNode->children.push_back(t);
// dst = (dst & ~(width'1 << lsb)) | unsigned'(width'(src)) << lsb)
did_something = true;
AstNode *bitmask = mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false);
newNode->children.push_back(
new AstNode(type,
lvalue,
new AstNode(AST_BIT_OR,
new AstNode(AST_BIT_AND,
old_data,
new AstNode(AST_BIT_NOT,
new AstNode(AST_SHIFT,
bitmask,
shift_val->clone()))),
new AstNode(AST_SHIFT,
new AstNode(AST_TO_UNSIGNED,
new AstNode(AST_CAST_SIZE,
mkconst_int(result_width, true),
children[1]->clone())),
shift_val))));

newNode->fixup_hierarchy_flags(true);
}
Expand Down
20 changes: 20 additions & 0 deletions tests/simple/sign_part_assign.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module test (
offset_i,
data_o,
data_ref_o
);
input wire [ 2:0] offset_i;
output reg [15:0] data_o;
output reg [15:0] data_ref_o;

always @(*) begin
// defaults
data_o = '0;
data_ref_o = '0;

// partial assigns
data_ref_o[offset_i+:4] = 4'b1111; // unsigned
data_o[offset_i+:4] = 1'sb1; // sign extension to 4'b1111
end

endmodule

0 comments on commit eeadbb5

Please sign in to comment.