diff --git a/lib/Dialect/Arc/Transforms/LowerState.cpp b/lib/Dialect/Arc/Transforms/LowerState.cpp index 0108b6bb4535..fc48ce279bcf 100644 --- a/lib/Dialect/Arc/Transforms/LowerState.cpp +++ b/lib/Dialect/Arc/Transforms/LowerState.cpp @@ -99,6 +99,7 @@ struct OpLowering { LogicalResult lower(MemoryOp op); LogicalResult lower(TapOp op); LogicalResult lower(InstanceOp op); + LogicalResult lower(hw::TriggeredOp op); LogicalResult lower(hw::OutputOp op); LogicalResult lower(seq::InitialOp op); LogicalResult lower(llhd::FinalOp op); @@ -425,9 +426,10 @@ static scf::IfOp createOrReuseIf(OpBuilder &builder, Value condition, LogicalResult OpLowering::lower() { return TypeSwitch(op) // Operations with special lowering. - .Case([&](auto op) { return lower(op); }) + .Case( + [&](auto op) { return lower(op); }) // Operations that should be skipped entirely and never land on the // worklist to be lowered. @@ -846,6 +848,45 @@ LogicalResult OpLowering::lower(InstanceOp op) { return success(); } +/// Lower `hw.triggered` by inlining its body under a posedge check. +LogicalResult OpLowering::lower(hw::TriggeredOp op) { + assert(phase == Phase::New); + + if (op.getEvent() != hw::EventControl::AtPosEdge) { + if (!initial) + return op.emitOpError("only posedge triggers are supported"); + return success(); + } + + lowerValue(op.getTrigger(), Phase::New); + SmallVector inputs; + for (auto input : op.getInputs()) + inputs.push_back(lowerValue(input, Phase::Old)); + if (initial) + return success(); + if (llvm::is_contained(inputs, Value{})) + return failure(); + + auto ifClockOp = createIfClockOp(op.getTrigger()); + if (!ifClockOp) + return failure(); + + OpBuilder::InsertionGuard guard(module.builder); + module.builder.setInsertionPoint(ifClockOp.thenYield()); + + // Expose the trigger inputs as values for the body block arguments. + for (auto [arg, input] : llvm::zip(op.getBodyBlock()->getArguments(), inputs)) + module.loweredValues[{arg, Phase::New}] = input; + for (auto &bodyOp : llvm::make_early_inc_range(*op.getBodyBlock())) { + OpLowering bodyLowering(&bodyOp, Phase::New, module); + bodyLowering.initial = false; + if (failed(bodyLowering.lower())) + return failure(); + } + + return success(); +} + /// Lower the main module's outputs by allocating storage for each and then /// writing the current value into that storage. LogicalResult OpLowering::lower(hw::OutputOp op) { @@ -1105,18 +1146,23 @@ scf::IfOp OpLowering::createIfClockOp(Value clock) { /// cases. Some operations and values have special handling though. For example, /// states and memory reads are immediately materialized as a new read op. Value OpLowering::lowerValue(Value value, Phase phase) { + // Check if the value has already been lowered. + if (auto lowered = module.loweredValues.lookup({value, phase})) + return lowered; + // Handle module inputs. They read the same in all phases. if (auto arg = dyn_cast(value)) { + if (arg.getOwner() != module.moduleOp.getBodyBlock()) { + if (!initial) + emitError(arg.getLoc()) << "block argument has not been lowered"; + return {}; + } if (initial) return {}; auto state = module.allocatedInputs[arg.getArgNumber()]; return StateReadOp::create(module.getBuilder(phase), arg.getLoc(), state); } - // Check if the value has already been lowered. - if (auto lowered = module.loweredValues.lookup({value, phase})) - return lowered; - // At this point the value is the result of an op. (Block arguments are // handled above.) auto result = cast(value); diff --git a/lib/Tools/arcilator/pipelines.cpp b/lib/Tools/arcilator/pipelines.cpp index 8bcfafb53470..ddce669dbe36 100644 --- a/lib/Tools/arcilator/pipelines.cpp +++ b/lib/Tools/arcilator/pipelines.cpp @@ -59,6 +59,11 @@ void circt::populateArcPreprocessingPipeline( void circt::populateArcConversionPipeline(OpPassManager &pm, const ArcConversionOptions &options) { + { + sim::SquashSimTriggeredOptions opts; + opts.convertToHW = true; + pm.addNestedPass(sim::createSquashSimTriggered(opts)); + } { ConvertToArcsPassOptions opts; opts.tapRegisters = options.observeRegisters; diff --git a/test/Dialect/Arc/lower-state.mlir b/test/Dialect/Arc/lower-state.mlir index be22b2628a11..9e3c4f59076b 100644 --- a/test/Dialect/Arc/lower-state.mlir +++ b/test/Dialect/Arc/lower-state.mlir @@ -613,6 +613,47 @@ hw.module @OpsWithRegions(in %clock: !seq.clock, in %a: i42, in %b: i1, out c: i hw.output %0 : i42 } +// CHECK-LABEL: arc.model @Triggered +hw.module @Triggered(in %clock: i1, in %a: i42) { + // CHECK: [[IN_CLOCK:%.+]] = arc.root_input "clock" + // CHECK: [[IN_A:%.+]] = arc.root_input "a" + // CHECK: [[A:%.+]] = arc.state_read [[IN_A]] + // CHECK: [[CLOCK:%.+]] = arc.state_read [[IN_CLOCK]] + // CHECK: [[OLD_CLOCK:%.+]] = arc.state_read + // CHECK: arc.state_write + // CHECK: [[EDGE:%.+]] = comb.xor [[OLD_CLOCK]], [[CLOCK]] + // CHECK: [[POSEDGE:%.+]] = comb.and [[EDGE]], [[CLOCK]] + // CHECK: scf.if [[POSEDGE]] { + // CHECK: func.call @ConsumeI42([[A]]) + // CHECK: } + hw.triggered posedge %clock(%a) : i42 { + ^bb0(%arg: i42): + func.call @ConsumeI42(%arg) : (i42) -> () + } +} + +// CHECK-LABEL: arc.model @TriggeredCurrentTime +hw.module @TriggeredCurrentTime(in %clock: i1) { + // CHECK: [[IN_CLOCK:%.+]] = arc.root_input "clock" + // CHECK: [[CLOCK:%.+]] = arc.state_read [[IN_CLOCK]] + // CHECK: [[OLD_CLOCK:%.+]] = arc.state_read + // CHECK: arc.state_write + // CHECK: [[EDGE:%.+]] = comb.xor [[OLD_CLOCK]], [[CLOCK]] + // CHECK: [[POSEDGE:%.+]] = comb.and [[EDGE]], [[CLOCK]] + // CHECK: scf.if [[POSEDGE]] { + // CHECK: [[TIME_INT:%.+]] = arc.current_time %arg0 + // CHECK: [[TIME:%.+]] = llhd.int_to_time [[TIME_INT]] + // CHECK: [[TIME_AS_INT:%.+]] = llhd.time_to_int [[TIME]] + // CHECK: func.call @ConsumeI64([[TIME_AS_INT]]) + // CHECK: } + // CHECK-NOT: llhd.current_time + hw.triggered posedge %clock { + %0 = llhd.current_time + %1 = llhd.time_to_int %0 + func.call @ConsumeI64(%1) : (i64) -> () + } +} + // Regression check on worklist producing false positive comb loop errors. // CHECK-LABEL: @CombLoopRegression hw.module @CombLoopRegression(in %clk: !seq.clock) {