mirror of https://github.com/llvm/circt.git
[VerilogQuality] Remove muxes feeding registers their old value (#965)
When a mux feeds an assign to a register and one branch of the mux is the register's old value, we can turn that into a conditional assign. This results in much cleaner verilog output.
This commit is contained in:
parent
72c81189c7
commit
2e11065461
|
@ -366,6 +366,7 @@ def PAssignOp : SVOp<"passign", [InOutTypeConstraint<"src", "dest">,
|
|||
These occur in initial, always, task, and function blocks. The statement
|
||||
can be scheduled without blocking procedural flow. See SV Spec 10.4.2.
|
||||
}];
|
||||
let hasCanonicalizeMethod = true;
|
||||
let arguments = (ins InOutType:$dest, InOutElementType:$src);
|
||||
let results = (outs);
|
||||
let assemblyFormat = "$dest `,` $src attr-dict `:` type($src)";
|
||||
|
|
|
@ -874,6 +874,64 @@ static LogicalResult verifyAliasOp(AliasOp op) {
|
|||
return success();
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// PAssignOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// reg s <= cond ? val : s simplification.
|
||||
// Don't assign a register's value to itself, conditionally assign the new value
|
||||
// instead.
|
||||
LogicalResult PAssignOp::canonicalize(PAssignOp op, PatternRewriter &rewriter) {
|
||||
auto mux = op.src().getDefiningOp<comb::MuxOp>();
|
||||
if (!mux)
|
||||
return failure();
|
||||
|
||||
auto reg = dyn_cast<sv::RegOp>(op.dest().getDefiningOp());
|
||||
if (!reg)
|
||||
return failure();
|
||||
|
||||
bool trueBranch; // did we find the register on the true branch?
|
||||
auto tvread = mux.trueValue().getDefiningOp<sv::ReadInOutOp>();
|
||||
auto fvread = mux.falseValue().getDefiningOp<sv::ReadInOutOp>();
|
||||
if (tvread && reg == tvread.input().getDefiningOp<sv::RegOp>())
|
||||
trueBranch = true;
|
||||
else if (fvread && reg == fvread.input().getDefiningOp<sv::RegOp>())
|
||||
trueBranch = false;
|
||||
else
|
||||
return failure();
|
||||
|
||||
// Check that this is the only write of the register
|
||||
for (auto &use : reg->getUses()) {
|
||||
if (isa<ReadInOutOp>(use.getOwner()))
|
||||
continue;
|
||||
if (use.getOwner() == op)
|
||||
continue;
|
||||
return failure();
|
||||
}
|
||||
|
||||
// Replace a non-blocking procedural assign in a procedural region with a
|
||||
// conditional procedural assign. We've ensured that this is the only write
|
||||
// of the register.
|
||||
if (trueBranch) {
|
||||
auto one = rewriter.create<rtl::ConstantOp>(mux.getLoc(),
|
||||
mux.cond().getType(), -1);
|
||||
Value ops[] = {mux.cond(), one};
|
||||
auto cond = rewriter.createOrFold<comb::XorOp>(mux.getLoc(),
|
||||
mux.cond().getType(), ops);
|
||||
rewriter.create<sv::IfOp>(mux.getLoc(), cond, [&]() {
|
||||
rewriter.create<PAssignOp>(op.getLoc(), reg, mux.falseValue());
|
||||
});
|
||||
} else {
|
||||
rewriter.create<sv::IfOp>(mux.getLoc(), mux.cond(), [&]() {
|
||||
rewriter.create<PAssignOp>(op.getLoc(), reg, mux.trueValue());
|
||||
});
|
||||
}
|
||||
|
||||
// Remove the wire.
|
||||
rewriter.eraseOp(op);
|
||||
return success();
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// TableGen generated logic.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -85,3 +85,43 @@ func @invert_if(%arg0: i1) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CHECK-LABEL: func @mux_to_cond_assign_f
|
||||
// CHECK-NEXT: %r = sv.reg : !rtl.inout<i2>
|
||||
// CHECK-NEXT: sv.alwaysff(posedge %arg0) {
|
||||
// CHECK-NEXT: sv.if %arg1 {
|
||||
// CHECK-NEXT: sv.passign %r, %arg2 : i2
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: return
|
||||
// CHECK-NEXT: }
|
||||
func @mux_to_cond_assign_f(%clock: i1, %c: i1, %data: i2) {
|
||||
%r = sv.reg : !rtl.inout<i2>
|
||||
%1 = sv.read_inout %r : !rtl.inout<i2>
|
||||
%0 = comb.mux %c, %data, %1 : i2
|
||||
sv.alwaysff(posedge %clock) {
|
||||
sv.passign %r, %0 : i2
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CHECK-LABEL: func @mux_to_cond_assign_t
|
||||
// CHECK-NEXT: %true = rtl.constant true
|
||||
// CHECK-NEXT: %r = sv.reg : !rtl.inout<i2>
|
||||
// CHECK-NEXT: sv.alwaysff(posedge %arg0) {
|
||||
// CHECK-NEXT: %0 = comb.xor %arg1, %true : i1
|
||||
// CHECK-NEXT: sv.if %0 {
|
||||
// CHECK-NEXT: sv.passign %r, %arg2 : i2
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: return
|
||||
// CHECK-NEXT: }
|
||||
func @mux_to_cond_assign_t(%clock: i1, %c: i1, %data: i2) {
|
||||
%r = sv.reg : !rtl.inout<i2>
|
||||
%1 = sv.read_inout %r : !rtl.inout<i2>
|
||||
%0 = comb.mux %c, %1, %data : i2
|
||||
sv.alwaysff(posedge %clock) {
|
||||
sv.passign %r, %0 : i2
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -36,8 +36,8 @@ yosys -q -p "
|
|||
opt -purge
|
||||
equiv_make top1 top2 equiv
|
||||
hierarchy -top equiv
|
||||
equiv_simple
|
||||
equiv_induct
|
||||
equiv_simple -undef
|
||||
equiv_induct -undef
|
||||
equiv_status -assert
|
||||
"
|
||||
if [ $? -eq 0 ]
|
||||
|
|
Loading…
Reference in New Issue