diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index 297282cfabe9..32814164313e 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -429,6 +429,7 @@ def LowerFIRRTLToHW : Pass<"lower-firrtl-to-hw", "mlir::ModuleOp"> { "emit::EmitDialect", "hw::HWDialect", "ltl::LTLDialect", + "mlir::scf::SCFDialect", "seq::SeqDialect", "sim::SimDialect", "sv::SVDialect", diff --git a/include/circt/Dialect/Sim/SimOps.td b/include/circt/Dialect/Sim/SimOps.td index 265ee5732c8a..459ec9a33797 100644 --- a/include/circt/Dialect/Sim/SimOps.td +++ b/include/circt/Dialect/Sim/SimOps.td @@ -1187,6 +1187,7 @@ def TriggeredOp : SimOp<"triggered", [ let arguments = (ins ClockType:$clock, Optional:$condition); let regions = (region SizedRegion<1>:$body); + let hasCanonicalizeMethod = true; let assemblyFormat = [{ $clock (`if` $condition^)? $body attr-dict }]; diff --git a/lib/Conversion/FIRRTLToHW/CMakeLists.txt b/lib/Conversion/FIRRTLToHW/CMakeLists.txt index c35080f70cd5..1b6f2319decb 100644 --- a/lib/Conversion/FIRRTLToHW/CMakeLists.txt +++ b/lib/Conversion/FIRRTLToHW/CMakeLists.txt @@ -18,5 +18,6 @@ add_circt_conversion_library(CIRCTFIRRTLToHW CIRCTSV CIRCTSVLoweringUtils CIRCTVerif + MLIRSCFDialect MLIRTransforms ) diff --git a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp index e0245a2cfe54..8321356e0b70 100644 --- a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp +++ b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp @@ -34,6 +34,7 @@ #include "circt/Dialect/Verif/VerifOps.h" #include "circt/Support/BackedgeBuilder.h" #include "circt/Support/Namespace.h" +#include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/ImplicitLocOpBuilder.h" @@ -1808,6 +1809,8 @@ struct FIRRTLLowering : public FIRRTLVisitor { sv::EventControl(), Value(), body, std::function()); } + void addToSimTriggeredBlock(Value clock, Value condition, + const std::function &body = {}); LogicalResult emitGuards(Location loc, ArrayRef guards, std::function emit); @@ -2107,6 +2110,10 @@ struct FIRRTLLowering : public FIRRTLVisitor { sv::ResetType, sv::EventControl, Value>; llvm::SmallDenseMap> alwaysBlocks; + llvm::SmallDenseMap, sim::TriggeredOp> + simTriggeredBlocks; + llvm::SmallDenseMap> + simTriggeredLastConditionBlocks; llvm::SmallDenseMap, sv::IfDefOp> ifdefBlocks; llvm::SmallDenseMap initialBlocks; @@ -3271,6 +3278,48 @@ void FIRRTLLowering::addToAlwaysBlock( builder.getInsertionPoint()); } +void FIRRTLLowering::addToSimTriggeredBlock( + Value clock, Value condition, const std::function &body) { + auto key = std::make_pair(builder.getBlock(), clock); + auto triggered = simTriggeredBlocks.lookup(key); + if (!triggered) { + triggered = sim::TriggeredOp::create(builder, clock); + simTriggeredBlocks[key] = triggered; + } + + if (condition) { + auto &lastConditionBlock = simTriggeredLastConditionBlocks[triggered]; + auto ifOp = lastConditionBlock.first == condition + ? lastConditionBlock.second + : mlir::scf::IfOp(); + if (!ifOp) { + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToEnd(triggered.getBodyBlock()); + ifOp = mlir::scf::IfOp::create(builder, builder.getLoc(), TypeRange{}, + condition, true, false); + builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); + mlir::scf::YieldOp::create(builder, builder.getLoc()); + lastConditionBlock = {condition, ifOp}; + } + + OpBuilder::InsertionGuard guard(builder); + auto &thenBlock = ifOp.getThenRegion().front(); + builder.setInsertionPoint(thenBlock.getTerminator()); + body(); + } else { + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToEnd(triggered.getBodyBlock()); + body(); + simTriggeredLastConditionBlocks[triggered] = {}; + } + + // Move the earlier triggered block(s) down to where the last would have been + // inserted. This ensures that any values used by the triggered blocks are + // defined ahead of the uses. + triggered->moveBefore(builder.getInsertionBlock(), + builder.getInsertionPoint()); +} + LogicalResult FIRRTLLowering::emitGuards(Location loc, ArrayRef guards, std::function emit) { @@ -5456,7 +5505,7 @@ LogicalResult FIRRTLLowering::visitStmt(PrintFOp op) { return failure(); auto stderrOp = sim::StderrStreamOp::create(builder); - sim::TriggeredOp::create(builder, clock, cond, [&] { + addToSimTriggeredBlock(clock, cond, [&] { sim::PrintFormattedProcOp::create(builder, *formatString, stderrOp); }); return success(); @@ -5479,7 +5528,7 @@ LogicalResult FIRRTLLowering::visitStmt(FPrintFOp op) { if (failed(formatString)) return failure(); - sim::TriggeredOp::create(builder, clock, cond, [&] { + addToSimTriggeredBlock(clock, cond, [&] { auto fileOp = sim::GetFileOp::create(builder, *fileFormatString); sim::PrintFormattedProcOp::create(builder, *formatString, fileOp); }); @@ -5499,8 +5548,28 @@ LogicalResult FIRRTLLowering::visitStmt(FPrintFOp op) { // FFlush lowers into $fflush statement. LogicalResult FIRRTLLowering::visitStmt(FFlushOp op) { - if (circuitState.lowerToCore) - return op.emitOpError("lower-to-core does not support firrtl.fflush yet"); + if (circuitState.lowerToCore) { + auto clock = getLoweredValue(op.getClock()); + auto cond = getLoweredValue(op.getCond()); + if (!clock || !cond) + return failure(); + + if (!op.getOutputFileAttr()) { + auto stderrOp = sim::StderrStreamOp::create(builder); + addToSimTriggeredBlock(clock, cond, + [&] { sim::FlushOp::create(builder, stderrOp); }); + } else { + auto fileFormatString = lowerSimFormatString( + op.getOutputFileAttr(), op.getOutputFileSubstitutions()); + if (failed(fileFormatString)) + return failure(); + addToSimTriggeredBlock(clock, cond, [&] { + auto fileOp = sim::GetFileOp::create(builder, *fileFormatString); + sim::FlushOp::create(builder, fileOp); + }); + } + return success(); + } auto clock = getLoweredNonClockValue(op.getClock()); auto cond = getLoweredValue(op.getCond()); diff --git a/lib/Dialect/Sim/CMakeLists.txt b/lib/Dialect/Sim/CMakeLists.txt index 08ec677c82ee..84d239e7fcf3 100644 --- a/lib/Dialect/Sim/CMakeLists.txt +++ b/lib/Dialect/Sim/CMakeLists.txt @@ -34,6 +34,7 @@ add_circt_dialect_library(CIRCTSim MLIRLLVMDialect MLIRIR MLIRPass + MLIRSCFDialect MLIRTransforms ) diff --git a/lib/Dialect/Sim/SimOps.cpp b/lib/Dialect/Sim/SimOps.cpp index d1e17bbfccc3..2acf7d8675e6 100644 --- a/lib/Dialect/Sim/SimOps.cpp +++ b/lib/Dialect/Sim/SimOps.cpp @@ -18,6 +18,7 @@ #include "circt/Support/ProceduralRegionTrait.h" #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/LLVMIR/LLVMTypes.h" +#include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Interfaces/FunctionImplementation.h" #include "llvm/ADT/MapVector.h" @@ -805,6 +806,30 @@ void TriggeredOp::build(OpBuilder &builder, OperationState &odsState, bodyCtor(); } +LogicalResult TriggeredOp::canonicalize(TriggeredOp op, + PatternRewriter &rewriter) { + if (op.getCondition()) + return failure(); + + Block *body = op.getBodyBlock(); + if (!llvm::hasSingleElement(body->getOperations())) + return failure(); + + auto ifOp = dyn_cast(body->front()); + if (!ifOp || ifOp->getNumResults() != 0 || ifOp.elseBlock()) + return failure(); + + op.getConditionMutable().append(ifOp.getCondition()); + + Block *thenBlock = ifOp.thenBlock(); + auto insertionPoint = ifOp->getIterator(); + body->getOperations().splice(insertionPoint, thenBlock->getOperations(), + thenBlock->begin(), + Block::iterator(thenBlock->getTerminator())); + rewriter.eraseOp(ifOp); + return success(); +} + //===----------------------------------------------------------------------===// // TableGen generated logic. //===----------------------------------------------------------------------===// diff --git a/test/Conversion/FIRRTLToHW/lower-to-core-errors.mlir b/test/Conversion/FIRRTLToHW/lower-to-core-errors.mlir index f323ac6b0f54..060636bec8bb 100644 --- a/test/Conversion/FIRRTLToHW/lower-to-core-errors.mlir +++ b/test/Conversion/FIRRTLToHW/lower-to-core-errors.mlir @@ -15,20 +15,6 @@ firrtl.circuit "time_printf" { // ----- -firrtl.circuit "fflush_unsupported" { - firrtl.module @fflush_unsupported( - in %clock: !firrtl.clock, - in %enable: !firrtl.uint<1>) { - // expected-error @+2 {{'firrtl.fflush' op lower-to-core does not support firrtl.fflush yet}} - // expected-error @below {{'firrtl.fflush' op LowerToHW couldn't handle this operation}} - firrtl.fflush %clock, %enable, "out.txt"() - : !firrtl.clock, !firrtl.uint<1> - firrtl.skip - } -} - -// ----- - firrtl.circuit "force_unsupported" { firrtl.module @force_unsupported(in %in: !firrtl.uint<42>) { %foo = firrtl.verbatim.wire "foo" : () -> !firrtl.uint<42> diff --git a/test/Conversion/FIRRTLToHW/lower-to-core.mlir b/test/Conversion/FIRRTLToHW/lower-to-core.mlir index b71dc1186778..551eb3bbb594 100644 --- a/test/Conversion/FIRRTLToHW/lower-to-core.mlir +++ b/test/Conversion/FIRRTLToHW/lower-to-core.mlir @@ -21,22 +21,15 @@ firrtl.circuit "LowerToCore" { // CHECK: [[LIT1:%.+]] = sim.fmt.literal " @ " // CHECK: [[HIER:%.+]] = sim.fmt.hier_path // CHECK: [[NL:%.+]] = sim.fmt.literal "\0A" - // CHECK: [[MSG:%.+]] = sim.fmt.concat ([[LIT0]], [[FMTVAL]], [[LIT1]], [[HIER]], [[NL]]) - // CHECK: [[STDERR:%.+]] = sim.stderr_stream - // CHECK: sim.triggered %clock if %enable { - // CHECK-NEXT: sim.proc.print [[MSG]] to [[STDERR]] - // CHECK-NEXT: } + // CHECK: [[MSG0:%.+]] = sim.fmt.concat ([[LIT0]], [[FMTVAL]], [[LIT1]], [[HIER]], [[NL]]) + // CHECK: [[STDERR0:%.+]] = sim.stderr_stream // CHECK-NOT: sv.assert // CHECK-NOT: sv.fwrite firrtl.printf %clock, %enable, "value=%d @ {{}}\0A"(%x, %hier) : !firrtl.clock, !firrtl.uint<1>, !firrtl.sint<4>, !firrtl.fstring // CHECK: [[FMTFILE1:%.+]] = sim.fmt.literal "out.txt" - // CHECK: [[MSG:%.+]] = sim.fmt.concat - // CHECK: sim.triggered %clock if %enable { - // CHECK-NEXT: [[FILE1:%.+]] = sim.get_file [[FMTFILE1]] - // CHECK-NEXT: sim.proc.print [[MSG]] to [[FILE1]] - // CHECK-NEXT: } + // CHECK: [[MSG1:%.+]] = sim.fmt.concat firrtl.fprintf %clock, %enable, "out.txt"(), "value=%d @ {{}}\0A"(%x, %hier) : !firrtl.clock, !firrtl.uint<1>, !firrtl.sint<4>, !firrtl.fstring @@ -44,14 +37,33 @@ firrtl.circuit "LowerToCore" { // CHECK: [[FILEVAL:%.+]] = sim.fmt.dec %x signed : i4 // CHECK: [[LIT3:%.+]] = sim.fmt.literal ".txt" // CHECK: [[FMTFILE2:%.+]] = sim.fmt.concat ([[LIT2]], [[FILEVAL]], [[LIT3]]) - // CHECK: [[MSG:%.+]] = sim.fmt.concat - // CHECK: sim.triggered %clock if %enable { - // CHECK-NEXT: [[FILE2:%.+]] = sim.get_file [[FMTFILE2]] - // CHECK-NEXT: sim.proc.print [[MSG]] to [[FILE2]] - // CHECK-NEXT: } + // CHECK: [[MSG2:%.+]] = sim.fmt.concat firrtl.fprintf %clock, %enable, "out%d.txt"(%x), "value=%d @ {{}}\0A"(%x, %hier) : !firrtl.clock, !firrtl.uint<1>, !firrtl.sint<4>, !firrtl.sint<4>, !firrtl.fstring + // CHECK: [[LIT4:%.+]] = sim.fmt.literal "out" + // CHECK: [[FILEVAL:%.+]] = sim.fmt.dec %x signed : i4 + // CHECK: [[LIT5:%.+]] = sim.fmt.literal ".txt + // CHECK: [[FMTFILE3:%.+]] = sim.fmt.concat ([[LIT4]], [[FILEVAL]], [[LIT5]]) + firrtl.fflush %clock, %enable, "out%d.txt"(%x) + : !firrtl.clock, !firrtl.uint<1>, !firrtl.sint<4> + + // CHECK: [[STDERR1:%.+]] = sim.stderr_stream + // CHECK: sim.triggered %clock { + // CHECK-NEXT: scf.if %enable { + // CHECK-NEXT: sim.proc.print [[MSG0]] to [[STDERR0]] + // CHECK-NEXT: [[FILE1:%.+]] = sim.get_file [[FMTFILE1]] + // CHECK-NEXT: sim.proc.print [[MSG1]] to [[FILE1]] + // CHECK-NEXT: [[FILE2:%.+]] = sim.get_file [[FMTFILE2]] + // CHECK-NEXT: sim.proc.print [[MSG2]] to [[FILE2]] + // CHECK-NEXT: [[FILE3:%.+]] = sim.get_file [[FMTFILE3]] + // CHECK-NEXT: sim.flush [[FILE3]] + // CHECK-NEXT: sim.flush [[STDERR1]] + // CHECK-NEXT: } + // CHECK-NEXT: } + firrtl.fflush %clock, %enable + : !firrtl.clock, !firrtl.uint<1> + firrtl.skip } @@ -63,4 +75,35 @@ firrtl.circuit "LowerToCore" { %sink = firrtl.instance sink @AnalogSink(in a: !firrtl.analog<8>) firrtl.attach %a, %sink : !firrtl.analog<8>, !firrtl.analog<8> } + + // CHECK-LABEL: hw.module @InterleavedConditions( + firrtl.module @InterleavedConditions( + in %clock: !firrtl.clock, + in %enable_a: !firrtl.uint<1>, + in %enable_b: !firrtl.uint<1>) { + // CHECK: [[A0:%.+]] = sim.fmt.literal "a0\0A" + // CHECK: [[STDERR0:%.+]] = sim.stderr_stream + // CHECK: [[B0:%.+]] = sim.fmt.literal "b0\0A" + // CHECK: [[STDERR1:%.+]] = sim.stderr_stream + // CHECK: [[A1:%.+]] = sim.fmt.literal "a1\0A" + // CHECK: [[STDERR2:%.+]] = sim.stderr_stream + // CHECK: sim.triggered %clock { + // CHECK-NEXT: scf.if %enable_a { + // CHECK-NEXT: sim.proc.print [[A0]] to [[STDERR0]] + // CHECK-NEXT: } + // CHECK-NEXT: scf.if %enable_b { + // CHECK-NEXT: sim.proc.print [[B0]] to [[STDERR1]] + // CHECK-NEXT: } + // CHECK-NEXT: scf.if %enable_a { + // CHECK-NEXT: sim.proc.print [[A1]] to [[STDERR2]] + // CHECK-NEXT: } + // CHECK-NEXT: } + firrtl.printf %clock, %enable_a, "a0\0A"() + : !firrtl.clock, !firrtl.uint<1> + firrtl.printf %clock, %enable_b, "b0\0A"() + : !firrtl.clock, !firrtl.uint<1> + firrtl.printf %clock, %enable_a, "a1\0A"() + : !firrtl.clock, !firrtl.uint<1> + firrtl.skip + } } diff --git a/test/Dialect/Sim/canonicalization.mlir b/test/Dialect/Sim/canonicalization.mlir index 886246e7ea2d..06566315d223 100644 --- a/test/Dialect/Sim/canonicalization.mlir +++ b/test/Dialect/Sim/canonicalization.mlir @@ -73,3 +73,55 @@ func.func @string_get_special_chars() -> i8 { // CHECK: return [[TMP]] return %char : i8 } + +//===----------------------------------------------------------------------===// +// TriggeredOp +//===----------------------------------------------------------------------===// + +// CHECK-LABEL: hw.module @triggered_hoist_single_if( +hw.module @triggered_hoist_single_if(in %clock: !seq.clock, in %enable: i1) { + %msg = sim.fmt.literal "hello" + // CHECK: [[MSG:%.+]] = sim.fmt.literal "hello" + // CHECK: sim.triggered %clock if %enable { + // CHECK-NEXT: sim.proc.print [[MSG]] + // CHECK-NEXT: } + sim.triggered %clock { + scf.if %enable { + sim.proc.print %msg + } + } + hw.output +} + +// CHECK-LABEL: hw.module @triggered_keep_interleaved_ifs( +hw.module @triggered_keep_interleaved_ifs(in %clock: !seq.clock, in %a: i1, in %b: i1) { + %a0 = sim.fmt.literal "a0" + %b0 = sim.fmt.literal "b0" + %a1 = sim.fmt.literal "a1" + // CHECK: [[A0:%.+]] = sim.fmt.literal "a0" + // CHECK: [[B0:%.+]] = sim.fmt.literal "b0" + // CHECK: [[A1:%.+]] = sim.fmt.literal "a1" + // CHECK: sim.triggered %clock { + // CHECK-NEXT: scf.if %a { + // CHECK-NEXT: sim.proc.print [[A0]] + // CHECK-NEXT: } + // CHECK-NEXT: scf.if %b { + // CHECK-NEXT: sim.proc.print [[B0]] + // CHECK-NEXT: } + // CHECK-NEXT: scf.if %a { + // CHECK-NEXT: sim.proc.print [[A1]] + // CHECK-NEXT: } + // CHECK-NEXT: } + sim.triggered %clock { + scf.if %a { + sim.proc.print %a0 + } + scf.if %b { + sim.proc.print %b0 + } + scf.if %a { + sim.proc.print %a1 + } + } + hw.output +} diff --git a/test/firtool/lower-to-core.fir b/test/firtool/lower-to-core.fir index 3928f51dcc87..3864a965a7fe 100644 --- a/test/firtool/lower-to-core.fir +++ b/test/firtool/lower-to-core.fir @@ -12,6 +12,8 @@ circuit LowerToCore: printf(clock, enable, "value=%d\n", x) fprintf(clock, enable, "out.txt", "value=%d\n", x) fprintf(clock, enable, "out%d.txt", x, "value=%d\n", x) + fflush(clock, enable, "out%d.txt", x) + fflush(clock, enable) ; CHECK-LABEL: hw.module @LowerToCore ; CHECK-DAG: [[LITFILE1:%.+]] = sim.fmt.literal "out" @@ -22,19 +24,18 @@ circuit LowerToCore: ; CHECK-DAG: [[CLK:%.+]] = seq.from_clock %clock ; CHECK-DAG: verif.clocked_assert %pred if %enable, posedge [[CLK]] : i1 ; CHECK-DAG: [[FMT:%.+]] = sim.fmt.dec %x signed : i4 -; CHECK: [[MSG:%.+]] = sim.fmt.concat ([[LIT]], [[FMT]], [[NL]]) -; CHECK: [[STDERR:%.+]] = sim.stderr_stream +; CHECK-DAG: [[MSG:%.+]] = sim.fmt.concat ([[LIT]], [[FMT]], [[NL]]) +; CHECK-DAG: [[FMTFILE2:%.+]] = sim.fmt.concat ([[LITFILE1]], [[FMT]], [[LITFILE2]]) +; CHECK-DAG: [[STDERR:%.+]] = sim.stderr_stream ; CHECK: sim.triggered %clock if %enable { ; CHECK-NEXT: sim.proc.print [[MSG]] to [[STDERR]] -; CHECK-NEXT: } -; CHECK: sim.triggered %clock if %enable { ; CHECK-NEXT: [[FILE1:%.+]] = sim.get_file [[FMTFILE1]] ; CHECK-NEXT: sim.proc.print [[MSG]] to [[FILE1]] -; CHECK-NEXT: } -; CHECK: [[FMTFILE2:%.+]] = sim.fmt.concat ([[LITFILE1]], [[FMT]], [[LITFILE2]]) -; CHECK: sim.triggered %clock if %enable { ; CHECK-NEXT: [[FILE2:%.+]] = sim.get_file [[FMTFILE2]] ; CHECK-NEXT: sim.proc.print [[MSG]] to [[FILE2]] +; CHECK-NEXT: [[FILE3:%.+]] = sim.get_file [[FMTFILE2]] +; CHECK-NEXT: sim.flush [[FILE3]] +; CHECK-NEXT: sim.flush [[STDERR]] ; CHECK-NEXT: } ; CHECK-NOT: sv.assert ; CHECK-NOT: sv.fwrite