CIRCT 23.0.0git
Loading...
Searching...
No Matches
LowerState.cpp
Go to the documentation of this file.
1//===- LowerState.cpp -----------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
18#include "mlir/Analysis/TopologicalSortUtils.h"
19#include "mlir/Dialect/Arith/IR/Arith.h"
20#include "mlir/Dialect/Func/IR/FuncOps.h"
21#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
22#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
23#include "mlir/Dialect/SCF/IR/SCF.h"
24#include "mlir/IR/IRMapping.h"
25#include "mlir/IR/ImplicitLocOpBuilder.h"
26#include "mlir/IR/SymbolTable.h"
27#include "mlir/Interfaces/SideEffectInterfaces.h"
28#include "mlir/Pass/Pass.h"
29#include "llvm/ADT/TypeSwitch.h"
30#include "llvm/Support/Debug.h"
31
32#define DEBUG_TYPE "arc-lower-state"
33
34namespace circt {
35namespace arc {
36#define GEN_PASS_DEF_LOWERSTATEPASS
37#include "circt/Dialect/Arc/ArcPasses.h.inc"
38} // namespace arc
39} // namespace circt
40
41using namespace circt;
42using namespace arc;
43using namespace hw;
44using namespace mlir;
45using llvm::SmallDenseSet;
46
47namespace {
48enum class Phase { Initial, Old, New, Final };
49
50template <class OS>
51OS &operator<<(OS &os, Phase phase) {
52 switch (phase) {
53 case Phase::Initial:
54 return os << "initial";
55 case Phase::Old:
56 return os << "old";
57 case Phase::New:
58 return os << "new";
59 case Phase::Final:
60 return os << "final";
61 }
62}
63
64struct ModuleLowering;
65
66/// All state associated with lowering a single operation. Instances of this
67/// struct are kept on a worklist to perform a depth-first traversal of the
68/// module being lowered.
69///
70/// The actual lowering occurs in `lower()`. This function is called exactly
71/// twice. A first time with `initial` being true, where other values and
72/// operations that have to be lowered first may be marked with `addPending`. No
73/// actual lowering or error reporting should occur when `initial` is true. The
74/// worklist then ensures that all `pending` ops are lowered before `lower()` is
75/// called a second time with `initial` being false. At this point the actual
76/// lowering and error reporting should occur.
77///
78/// The `initial` variable is used to allow for a single block of code to mark
79/// values and ops as dependencies and actually do the lowering based on them.
80struct OpLowering {
81 Operation *op;
82 Phase phase;
83 ModuleLowering &module;
84
85 bool initial = true;
86 SmallVector<std::pair<Operation *, Phase>, 2> pending;
87
88 OpLowering(Operation *op, Phase phase, ModuleLowering &module)
89 : op(op), phase(phase), module(module) {}
90
91 // Operation Lowering.
92 LogicalResult lower();
93 LogicalResult lowerDefault();
94 LogicalResult lower(StateOp op);
95 LogicalResult lower(sim::DPICallOp op);
96 LogicalResult
97 lowerStateful(Value clock, Value enable, Value reset, ValueRange inputs,
98 ResultRange results,
99 llvm::function_ref<ValueRange(ValueRange)> createMapping);
100 LogicalResult lower(MemoryOp op);
101 LogicalResult lower(TapOp op);
102 LogicalResult lower(InstanceOp op);
103 LogicalResult lower(CoroutineInstanceOp op);
104 LogicalResult lower(hw::TriggeredOp op);
105 LogicalResult lower(hw::OutputOp op);
106 LogicalResult lower(seq::InitialOp op);
107 LogicalResult lower(llhd::FinalOp op);
108 LogicalResult lower(llhd::CurrentTimeOp op);
109 LogicalResult lower(sim::ClockedTerminateOp op);
110
111 scf::IfOp createIfClockOp(Value clock);
112
113 // Value Lowering. These functions are called from the `lower()` functions
114 // above. They handle values used by the `op`. This can generate reads from
115 // state and memory storage on-the-fly, or mark other ops as dependencies to
116 // be lowered first.
117 Value lowerValue(Value value, Phase phase);
118 Value lowerValue(InstanceOp op, OpResult result, Phase phase);
119 Value lowerValue(CoroutineInstanceOp op, OpResult result, Phase phase);
120 Value lowerValue(StateOp op, OpResult result, Phase phase);
121 Value lowerValue(sim::DPICallOp op, OpResult result, Phase phase);
122 Value lowerValue(MemoryReadPortOp op, OpResult result, Phase phase);
123 Value lowerValue(seq::InitialOp op, OpResult result, Phase phase);
124 Value lowerValue(seq::FromImmutableOp op, OpResult result, Phase phase);
125
126 void addPending(Value value, Phase phase);
127 void addPending(Operation *op, Phase phase);
128};
129
130/// All state associated with lowering a single module.
131struct ModuleLowering {
132 /// The module being lowered.
133 HWModuleOp moduleOp;
134 /// The builder for the main body of the model.
135 OpBuilder builder;
136 /// The builder for state allocation ops.
137 OpBuilder allocBuilder;
138 /// The builder for the initial phase.
139 OpBuilder initialBuilder;
140 /// The builder for the final phase.
141 OpBuilder finalBuilder;
142
143 /// The storage value that can be used for `arc.alloc_state` and friends.
144 Value storageArg;
145
146 /// The symbol table of the enclosing top-level module. Used to resolve
147 /// coroutine callees without walking the entire IR.
148 SymbolTable &symbolTable;
149
150 /// A worklist of pending op lowerings.
151 SmallVector<OpLowering> opsWorklist;
152 /// The set of ops currently in the worklist. Used to detect cycles.
153 SmallDenseSet<std::pair<Operation *, Phase>> opsSeen;
154 /// The ops that have already been lowered.
155 DenseSet<std::pair<Operation *, Phase>> loweredOps;
156 /// The values that have already been lowered.
157 DenseMap<std::pair<Value, Phase>, Value> loweredValues;
158
159 /// The allocated input ports.
160 SmallVector<Value> allocatedInputs;
161 /// The allocated states as a mapping from op results to `arc.alloc_state`
162 /// results.
163 DenseMap<Value, Value> allocatedStates;
164 /// The allocated storage for instance inputs and top module outputs.
165 DenseMap<OpOperand *, Value> allocatedOutputs;
166 /// The allocated storage for values computed during the initial phase.
167 DenseMap<Value, Value> allocatedInitials;
168 /// The allocated storage for taps.
169 DenseMap<Operation *, Value> allocatedTaps;
170
171 /// A mapping from unlowered clocks to a value indicating a posedge. This is
172 /// used to not create an excessive number of posedge detectors.
173 DenseMap<Value, Value> loweredPosedges;
174 /// The previous enable and the value it was lowered to. This is used to reuse
175 /// previous if ops for the same enable value.
176 std::pair<Value, Value> prevEnable;
177 /// The previous reset and the value it was lowered to. This is used to reuse
178 /// previous if ops for the same reset value.
179 std::pair<Value, Value> prevReset;
180
181 ModuleLowering(HWModuleOp moduleOp, SymbolTable &symbolTable)
182 : moduleOp(moduleOp), builder(moduleOp), allocBuilder(moduleOp),
183 initialBuilder(moduleOp), finalBuilder(moduleOp),
184 symbolTable(symbolTable) {}
185 LogicalResult run();
186 LogicalResult lowerOp(Operation *op);
187 Value getAllocatedState(OpResult result);
188 Value detectPosedge(Value clock);
189 OpBuilder &getBuilder(Phase phase);
190 Value requireLoweredValue(Value value, Phase phase, Location useLoc);
191};
192} // namespace
193
194//===----------------------------------------------------------------------===//
195// Module Lowering
196//===----------------------------------------------------------------------===//
197
198LogicalResult ModuleLowering::run() {
199 LLVM_DEBUG(llvm::dbgs() << "Lowering module `" << moduleOp.getModuleName()
200 << "`\n");
201
202 // Create the replacement `ModelOp`.
203 auto modelOp =
204 ModelOp::create(builder, moduleOp.getLoc(), moduleOp.getModuleNameAttr(),
205 TypeAttr::get(moduleOp.getModuleType()), IntegerAttr{},
206 FlatSymbolRefAttr{}, FlatSymbolRefAttr{}, ArrayAttr{});
207 auto &modelBlock = modelOp.getBody().emplaceBlock();
208 storageArg = modelBlock.addArgument(StorageType::get(builder.getContext()),
209 modelOp.getLoc());
210 builder.setInsertionPointToStart(&modelBlock);
211
212 // Reset the next wakeup slot to `UINT64_MAX` ("no wakeup pending") at the
213 // start of every eval. Process suspension code lowers the value to the
214 // earliest scheduled wakeup over the course of the evaluation.
215 auto noWakeup = hw::ConstantOp::create(builder, moduleOp.getLoc(),
216 builder.getI64Type(), -1);
217 SetNextWakeupOp::create(builder, moduleOp.getLoc(), storageArg, noWakeup);
218
219 // Create the `arc.initial` op to contain the ops for the initialization
220 // phase.
221 auto initialOp = InitialOp::create(builder, moduleOp.getLoc());
222 initialBuilder.setInsertionPointToStart(&initialOp.getBody().emplaceBlock());
223
224 // Create the `arc.final` op to contain the ops for the finalization phase.
225 auto finalOp = FinalOp::create(builder, moduleOp.getLoc());
226 finalBuilder.setInsertionPointToStart(&finalOp.getBody().emplaceBlock());
227
228 // Position the alloc builder such that allocation ops get inserted above the
229 // initial op.
230 allocBuilder.setInsertionPoint(initialOp);
231
232 // Allocate storage for the inputs.
233 for (auto arg : moduleOp.getBodyBlock()->getArguments()) {
234 auto name = moduleOp.getArgName(arg.getArgNumber());
235 auto state =
236 RootInputOp::create(allocBuilder, arg.getLoc(),
237 StateType::get(arg.getType()), name, storageArg);
238 allocatedInputs.push_back(state);
239 }
240
241 // Lower the ops.
242 for (auto &op : moduleOp.getOps()) {
243 if (mlir::isMemoryEffectFree(&op) &&
244 !isa<hw::OutputOp, sim::ClockedTerminateOp>(op))
245 continue;
246 if (isa<MemoryReadPortOp, MemoryWritePortOp>(op))
247 continue; // handled as part of `MemoryOp`
248 if (failed(lowerOp(&op)))
249 return failure();
250 }
251
252 // Clean up any dead ops. The lowering inserts a few defensive
253 // `arc.state_read` ops that may remain unused. This cleans them up.
254 for (auto &op : llvm::make_early_inc_range(llvm::reverse(modelBlock)))
255 if (mlir::isOpTriviallyDead(&op))
256 op.erase();
257
258 return success();
259}
260
261/// Lower an op and its entire fan-in cone.
262LogicalResult ModuleLowering::lowerOp(Operation *op) {
263 LLVM_DEBUG(llvm::dbgs() << "- Handling " << *op << "\n");
264
265 // Pick in which phases the given operation has to perform some work.
266 SmallVector<Phase, 2> phases = {Phase::New};
267 if (isa<seq::InitialOp>(op))
268 phases = {Phase::Initial};
269 if (isa<llhd::FinalOp>(op))
270 phases = {Phase::Final};
271 if (isa<StateOp>(op))
272 phases = {Phase::Initial, Phase::New};
273
274 for (auto phase : phases) {
275 if (loweredOps.contains({op, phase}))
276 return success();
277 opsWorklist.push_back(OpLowering(op, phase, *this));
278 opsSeen.insert({op, phase});
279 }
280
281 auto dumpWorklist = [&] {
282 for (auto &opLowering : llvm::reverse(opsWorklist))
283 opLowering.op->emitRemark()
284 << "computing " << opLowering.phase << " phase here";
285 };
286
287 while (!opsWorklist.empty()) {
288 auto &opLowering = opsWorklist.back();
289
290 // Collect an initial list of operands that need to be lowered.
291 if (opLowering.initial) {
292 if (failed(opLowering.lower())) {
293 dumpWorklist();
294 return failure();
295 }
296 std::reverse(opLowering.pending.begin(), opLowering.pending.end());
297 opLowering.initial = false;
298 }
299
300 // Push operands onto the worklist.
301 if (!opLowering.pending.empty()) {
302 auto [defOp, phase] = opLowering.pending.pop_back_val();
303 if (loweredOps.contains({defOp, phase}))
304 continue;
305 if (!opsSeen.insert({defOp, phase}).second) {
306 defOp->emitOpError("is on a combinational loop");
307 dumpWorklist();
308 return failure();
309 }
310 opsWorklist.push_back(OpLowering(defOp, phase, *this));
311 continue;
312 }
313
314 // At this point all operands are available and the op itself can be
315 // lowered.
316 LLVM_DEBUG(llvm::dbgs() << " - Lowering " << opLowering.phase << " "
317 << *opLowering.op << "\n");
318 if (failed(opLowering.lower())) {
319 dumpWorklist();
320 return failure();
321 }
322 loweredOps.insert({opLowering.op, opLowering.phase});
323 opsSeen.erase({opLowering.op, opLowering.phase});
324 opsWorklist.pop_back();
325 }
326
327 return success();
328}
329
330/// Return the `arc.alloc_state` associated with the given state op result.
331/// Creates the allocation op if it does not yet exist.
332Value ModuleLowering::getAllocatedState(OpResult result) {
333 if (auto alloc = allocatedStates.lookup(result))
334 return alloc;
335
336 // Handle memories.
337 if (auto memOp = dyn_cast<MemoryOp>(result.getOwner())) {
338 auto alloc =
339 AllocMemoryOp::create(allocBuilder, memOp.getLoc(), memOp.getType(),
340 storageArg, memOp->getAttrs());
341 allocatedStates.insert({result, alloc});
342 return alloc;
343 }
344
345 // Create the allocation op.
346 auto alloc =
347 AllocStateOp::create(allocBuilder, result.getLoc(),
348 StateType::get(result.getType()), storageArg);
349 allocatedStates.insert({result, alloc});
350
351 // HACK: If the result comes from an instance op, add the instance and port
352 // name as an attribute to the allocation. This will make it show up in the C
353 // headers later. Get rid of this once we have proper debug dialect support.
354 if (auto instOp = dyn_cast<InstanceOp>(result.getOwner()))
355 alloc->setAttr(
356 "name", builder.getStringAttr(
357 instOp.getInstanceName() + "/" +
358 instOp.getOutputName(result.getResultNumber()).getValue()));
359
360 // HACK: If the result comes from an op that has a "names" attribute, use that
361 // as a name for the allocation. This should no longer be necessary once we
362 // properly support the Debug dialect.
363 if (isa<StateOp, sim::DPICallOp>(result.getOwner()))
364 if (auto names = result.getOwner()->getAttrOfType<ArrayAttr>("names"))
365 if (result.getResultNumber() < names.size())
366 alloc->setAttr("name", names[result.getResultNumber()]);
367
368 return alloc;
369}
370
371/// Allocate the necessary storage, reads, writes, and comparisons to detect a
372/// rising edge on a clock value.
373Value ModuleLowering::detectPosedge(Value clock) {
374 auto loc = clock.getLoc();
375 if (isa<seq::ClockType>(clock.getType()))
376 clock = seq::FromClockOp::create(builder, loc, clock);
377
378 // Allocate storage to store the previous clock value.
379 auto oldStorage = AllocStateOp::create(
380 allocBuilder, loc, StateType::get(builder.getI1Type()), storageArg);
381
382 // Read the old clock value from storage and write the new clock value to
383 // storage.
384 auto oldClock = StateReadOp::create(builder, loc, oldStorage);
385 StateWriteOp::create(builder, loc, oldStorage, clock);
386
387 // Detect a rising edge.
388 auto edge = comb::XorOp::create(builder, loc, oldClock, clock);
389 return comb::AndOp::create(builder, loc, edge, clock);
390}
391
392/// Get the builder appropriate for the given phase.
393OpBuilder &ModuleLowering::getBuilder(Phase phase) {
394 switch (phase) {
395 case Phase::Initial:
396 return initialBuilder;
397 case Phase::Old:
398 case Phase::New:
399 return builder;
400 case Phase::Final:
401 return finalBuilder;
402 }
403}
404
405/// Get the lowered value, or emit a diagnostic and return null.
406Value ModuleLowering::requireLoweredValue(Value value, Phase phase,
407 Location useLoc) {
408 if (auto lowered = loweredValues.lookup({value, phase}))
409 return lowered;
410 auto d = emitError(value.getLoc()) << "value has not been lowered";
411 d.attachNote(useLoc) << "value used here";
412 return {};
413}
414
415//===----------------------------------------------------------------------===//
416// Operation Lowering
417//===----------------------------------------------------------------------===//
418
419/// Create a new `scf.if` operation with the given builder, or reuse a previous
420/// `scf.if` if the builder's insertion point is located right after it.
421static scf::IfOp createOrReuseIf(OpBuilder &builder, Value condition,
422 bool withElse) {
423 if (auto ip = builder.getInsertionPoint(); ip != builder.getBlock()->begin())
424 if (auto ifOp = dyn_cast<scf::IfOp>(*std::prev(ip)))
425 if (ifOp.getCondition() == condition)
426 return ifOp;
427 return scf::IfOp::create(builder, condition.getLoc(), condition, withElse);
428}
429
430/// This function is called from the lowering worklist in order to perform a
431/// depth-first traversal of the surrounding module. These functions call
432/// `lowerValue` to mark their operands as dependencies in the depth-first
433/// traversal, and to map them to the lowered value in one go.
434LogicalResult OpLowering::lower() {
435 return TypeSwitch<Operation *, LogicalResult>(op)
436 // Operations with special lowering.
437 .Case<StateOp, sim::DPICallOp, MemoryOp, TapOp, InstanceOp,
438 CoroutineInstanceOp, hw::TriggeredOp, hw::OutputOp, seq::InitialOp,
439 llhd::FinalOp, llhd::CurrentTimeOp, sim::ClockedTerminateOp>(
440 [&](auto op) { return lower(op); })
441
442 // Operations that should be skipped entirely and never land on the
443 // worklist to be lowered.
444 .Case<MemoryWritePortOp, MemoryReadPortOp>([&](auto op) {
445 assert(false && "ports must be lowered by memory op");
446 return failure();
447 })
448
449 // All other ops are simply cloned into the lowered model.
450 .Default([&](auto) { return lowerDefault(); });
451}
452
453/// Called for all operations for which there is no special lowering. Simply
454/// clones the operation.
455LogicalResult OpLowering::lowerDefault() {
456 // Make sure that all operand values are lowered first.
457 IRMapping mapping;
458 auto anyFailed = false;
459 op->walk([&](Operation *nestedOp) {
460 for (auto operand : nestedOp->getOperands()) {
461 if (op->isAncestor(operand.getParentBlock()->getParentOp()))
462 continue;
463 auto lowered = lowerValue(operand, phase);
464 if (!lowered)
465 anyFailed = true;
466 mapping.map(operand, lowered);
467 }
468 });
469 if (initial)
470 return success();
471 if (anyFailed)
472 return failure();
473
474 // Clone the operation.
475 auto *clonedOp = module.getBuilder(phase).clone(*op, mapping);
476
477 // Keep track of the results.
478 for (auto [oldResult, newResult] :
479 llvm::zip(op->getResults(), clonedOp->getResults()))
480 module.loweredValues[{oldResult, phase}] = newResult;
481
482 return success();
483}
484
485/// Lower a state to a corresponding storage allocation and `write` of the
486/// state's new value to it. This function uses the `Old` phase to get the
487/// values at the state input before the current update, and then uses them to
488/// compute the `New` value.
489LogicalResult OpLowering::lower(StateOp op) {
490 // Handle initialization.
491 if (phase == Phase::Initial) {
492 // Ensure the initial values of the register have been lowered before.
493 if (initial) {
494 for (auto initial : op.getInitials())
495 lowerValue(initial, Phase::Initial);
496 return success();
497 }
498
499 // Write the initial values to the allocated storage in the initial block.
500 if (op.getInitials().empty())
501 return success();
502 for (auto [initial, result] :
503 llvm::zip(op.getInitials(), op.getResults())) {
504 auto value = lowerValue(initial, Phase::Initial);
505 if (!value)
506 return failure();
507 auto state = module.getAllocatedState(result);
508 if (!state)
509 return failure();
510 StateWriteOp::create(module.initialBuilder, value.getLoc(), state, value);
511 }
512 return success();
513 }
514
515 assert(phase == Phase::New);
516
517 if (!initial) {
518 if (!op.getClock())
519 return op.emitOpError() << "must have a clock";
520 if (op.getLatency() > 1)
521 return op.emitOpError("latencies > 1 not supported yet");
522 }
523
524 return lowerStateful(op.getClock(), op.getEnable(), op.getReset(),
525 op.getInputs(), op.getResults(), [&](ValueRange inputs) {
526 return CallOp::create(module.builder, op.getLoc(),
527 op.getResultTypes(), op.getArc(),
528 inputs)
529 .getResults();
530 });
531}
532
533/// Lower a DPI call to a corresponding storage allocation and write of the
534/// state's new value to it. This function uses the `Old` phase to get the
535/// values at the state input before the current update, and then uses them to
536/// compute the `New` value.
537LogicalResult OpLowering::lower(sim::DPICallOp op) {
538 // Handle unclocked DPI calls.
539 if (!op.getClock()) {
540 // Make sure that all operands have been lowered.
541 SmallVector<Value> inputs;
542 for (auto operand : op.getInputs())
543 inputs.push_back(lowerValue(operand, phase));
544 if (initial)
545 return success();
546 if (llvm::is_contained(inputs, Value{}))
547 return failure();
548 if (op.getEnable())
549 return op.emitOpError() << "without clock cannot have an enable";
550
551 // Lower the op to a regular function call.
552 auto callOp =
553 func::CallOp::create(module.getBuilder(phase), op.getLoc(),
554 op.getCalleeAttr(), op.getResultTypes(), inputs);
555 for (auto [oldResult, newResult] :
556 llvm::zip(op.getResults(), callOp.getResults()))
557 module.loweredValues[{oldResult, phase}] = newResult;
558 return success();
559 }
560
561 assert(phase == Phase::New);
562
563 return lowerStateful(op.getClock(), op.getEnable(), /*reset=*/{},
564 op.getInputs(), op.getResults(), [&](ValueRange inputs) {
565 return func::CallOp::create(
566 module.builder, op.getLoc(),
567 op.getCalleeAttr(), op.getResultTypes(),
568 inputs)
569 .getResults();
570 });
571}
572
573/// Lower a state to a corresponding storage allocation and `write` of the
574/// state's new value to it. This function uses the `Old` phase to get the
575/// values at the state input before the current update, and then uses them to
576/// compute the `New` value.
577LogicalResult OpLowering::lowerStateful(
578 Value clock, Value enable, Value reset, ValueRange inputs,
579 ResultRange results,
580 llvm::function_ref<ValueRange(ValueRange)> createMapping) {
581 // Ensure all operands are lowered before we lower the op itself. State ops
582 // are special in that they require the "old" value of their inputs and
583 // enable, in order to compute the updated "new" value. The clock needs to be
584 // the "new" value though, such that other states can act as a clock source.
585 if (initial) {
586 lowerValue(clock, Phase::New);
587 if (enable)
588 lowerValue(enable, Phase::Old);
589 if (reset)
590 lowerValue(reset, Phase::Old);
591 for (auto input : inputs)
592 lowerValue(input, Phase::Old);
593 return success();
594 }
595
596 // Check if we're inserting right after an `if` op for the same clock edge, in
597 // which case we can reuse that op. Otherwise, create the new `if` op.
598 auto ifClockOp = createIfClockOp(clock);
599 if (!ifClockOp)
600 return failure();
601 OpBuilder::InsertionGuard guard(module.builder);
602 module.builder.setInsertionPoint(ifClockOp.thenYield());
603
604 // Make sure we have the state storage available such that we can read and
605 // write from and to them.
606 SmallVector<Value> states;
607 for (auto result : results) {
608 auto state = module.getAllocatedState(result);
609 if (!state)
610 return failure();
611 states.push_back(state);
612 }
613
614 // Handle the reset.
615 if (reset) {
616 // Check if we can reuse a previous reset value.
617 auto &[unloweredReset, loweredReset] = module.prevReset;
618 if (unloweredReset != reset ||
619 loweredReset.getParentBlock() != module.builder.getBlock()) {
620 unloweredReset = reset;
621 loweredReset = lowerValue(reset, Phase::Old);
622 if (!loweredReset)
623 return failure();
624 }
625
626 // Check if we're inserting right after an if op for the same reset, in
627 // which case we can reuse that op. Otherwise create the new if op.
628 auto ifResetOp = createOrReuseIf(module.builder, loweredReset, true);
629 module.builder.setInsertionPoint(ifResetOp.thenYield());
630
631 // Generate the zero value writes.
632 for (auto state : states) {
633 auto type = cast<StateType>(state.getType()).getType();
634 Value value = ConstantOp::create(
635 module.builder, loweredReset.getLoc(),
636 module.builder.getIntegerType(hw::getBitWidth(type)), 0);
637 if (value.getType() != type)
638 value = BitcastOp::create(module.builder, loweredReset.getLoc(), type,
639 value);
640 StateWriteOp::create(module.builder, loweredReset.getLoc(), state, value);
641 }
642 module.builder.setInsertionPoint(ifResetOp.elseYield());
643 }
644
645 // Handle the enable.
646 if (enable) {
647 // Check if we can reuse a previous enable value.
648 auto &[unloweredEnable, loweredEnable] = module.prevEnable;
649 if (unloweredEnable != enable ||
650 loweredEnable.getParentBlock() != module.builder.getBlock()) {
651 unloweredEnable = enable;
652 loweredEnable = lowerValue(enable, Phase::Old);
653 if (!loweredEnable)
654 return failure();
655 }
656
657 // Check if we're inserting right after an if op for the same enable, in
658 // which case we can reuse that op. Otherwise create the new if op.
659 auto ifEnableOp = createOrReuseIf(module.builder, loweredEnable, false);
660 module.builder.setInsertionPoint(ifEnableOp.thenYield());
661 }
662
663 // Get the transfer function inputs. This potentially inserts read ops.
664 SmallVector<Value> loweredInputs;
665 for (auto input : inputs) {
666 auto lowered = lowerValue(input, Phase::Old);
667 if (!lowered)
668 return failure();
669 loweredInputs.push_back(lowered);
670 }
671
672 // Compute the transfer function and write its results to the state's storage.
673 auto loweredResults = createMapping(loweredInputs);
674 for (auto [state, value] : llvm::zip(states, loweredResults))
675 StateWriteOp::create(module.builder, value.getLoc(), state, value);
676
677 // Since we just wrote the new state value to storage, insert read ops just
678 // before the if op that keep the old value around for any later ops that
679 // still need it.
680 module.builder.setInsertionPoint(ifClockOp);
681 for (auto [state, result] : llvm::zip(states, results)) {
682 auto oldValue = StateReadOp::create(module.builder, result.getLoc(), state);
683 module.loweredValues[{result, Phase::Old}] = oldValue;
684 }
685
686 return success();
687}
688
689/// Lower a memory and its read and write ports to corresponding
690/// `arc.memory_write` operations. Reads are also executed at this point and
691/// stored in `loweredValues` for later operations to pick up.
692LogicalResult OpLowering::lower(MemoryOp op) {
693 assert(phase == Phase::New);
694
695 // Collect all the reads and writes.
696 SmallVector<MemoryReadPortOp> reads;
697 SmallVector<MemoryWritePortOp> writes;
698
699 for (auto *user : op->getUsers()) {
700 if (auto read = dyn_cast<MemoryReadPortOp>(user)) {
701 reads.push_back(read);
702 } else if (auto write = dyn_cast<MemoryWritePortOp>(user)) {
703 writes.push_back(write);
704 } else {
705 auto d = op.emitOpError()
706 << "users must all be memory read or write port ops";
707 d.attachNote(user->getLoc())
708 << "but found " << user->getName() << " user here";
709 return d;
710 }
711 }
712
713 // Ensure all operands are lowered before we lower the memory itself.
714 if (initial) {
715 for (auto read : reads)
716 lowerValue(read, Phase::Old);
717 for (auto write : writes) {
718 if (write.getClock())
719 lowerValue(write.getClock(), Phase::New);
720 for (auto input : write.getInputs())
721 lowerValue(input, Phase::Old);
722 }
723 return success();
724 }
725
726 // Get the allocated storage for the memory.
727 auto state = module.getAllocatedState(op->getResult(0));
728
729 // Since we are going to write new values into storage, insert read ops that
730 // keep the old values around for any later ops that still need them.
731 for (auto read : reads) {
732 auto oldValue = lowerValue(read, Phase::Old);
733 if (!oldValue)
734 return failure();
735 module.loweredValues[{read, Phase::Old}] = oldValue;
736 }
737
738 // Lower the writes.
739 for (auto write : writes) {
740 if (!write.getClock())
741 return write.emitOpError() << "must have a clock";
742 if (write.getLatency() > 1)
743 return write.emitOpError("latencies > 1 not supported yet");
744
745 // Create the if op for the clock edge.
746 auto ifClockOp = createIfClockOp(write.getClock());
747 if (!ifClockOp)
748 return failure();
749 OpBuilder::InsertionGuard guard(module.builder);
750 module.builder.setInsertionPoint(ifClockOp.thenYield());
751
752 // Call the arc that computes the address, data, and enable.
753 SmallVector<Value> inputs;
754 for (auto input : write.getInputs()) {
755 auto lowered = lowerValue(input, Phase::Old);
756 if (!lowered)
757 return failure();
758 inputs.push_back(lowered);
759 }
760 auto callOp =
761 CallOp::create(module.builder, write.getLoc(),
762 write.getArcResultTypes(), write.getArc(), inputs);
763
764 // If the write has an enable, wrap the remaining logic in an if op.
765 if (write.getEnable()) {
766 auto ifEnableOp = createOrReuseIf(
767 module.builder, callOp.getResult(write.getEnableIdx()), false);
768 module.builder.setInsertionPoint(ifEnableOp.thenYield());
769 }
770
771 // If the write is masked, read the current
772 // value in the memory and merge it with the updated value.
773 auto address = callOp.getResult(write.getAddressIdx());
774 auto data = callOp.getResult(write.getDataIdx());
775 if (write.getMask()) {
776 auto mask = callOp.getResult(write.getMaskIdx(write.getEnable()));
777 auto maskInv = module.builder.createOrFold<comb::XorOp>(
778 write.getLoc(), mask,
779 ConstantOp::create(module.builder, write.getLoc(), mask.getType(),
780 -1),
781 true);
782 auto oldData =
783 MemoryReadOp::create(module.builder, write.getLoc(), state, address);
784 auto oldMasked = comb::AndOp::create(module.builder, write.getLoc(),
785 maskInv, oldData, true);
786 auto newMasked =
787 comb::AndOp::create(module.builder, write.getLoc(), mask, data, true);
788 data = comb::OrOp::create(module.builder, write.getLoc(), oldMasked,
789 newMasked, true);
790 }
791
792 // Actually write to the memory.
793 MemoryWriteOp::create(module.builder, write.getLoc(), state, address, data);
794 }
795
796 return success();
797}
798
799/// Lower a tap by allocating state storage for it and writing the current value
800/// observed by the tap to it.
801LogicalResult OpLowering::lower(TapOp op) {
802 assert(phase == Phase::New);
803
804 auto value = lowerValue(op.getValue(), phase);
805 if (initial)
806 return success();
807 if (!value)
808 return failure();
809
810 auto &state = module.allocatedTaps[op];
811 if (!state) {
812 auto alloc = AllocStateOp::create(module.allocBuilder, op.getLoc(),
813 StateType::get(value.getType()),
814 module.storageArg, true);
815 alloc->setAttr("names", op.getNamesAttr());
816 state = alloc;
817 }
818 StateWriteOp::create(module.builder, op.getLoc(), state, value);
819 return success();
820}
821
822/// Lower an instance by allocating state storage for each of its inputs and
823/// writing the current value into that storage. This makes instance inputs
824/// behave like outputs of the top-level module.
825LogicalResult OpLowering::lower(InstanceOp op) {
826 assert(phase == Phase::New);
827
828 // Get the current values flowing into the instance's inputs.
829 SmallVector<Value> values;
830 for (auto operand : op.getOperands())
831 values.push_back(lowerValue(operand, Phase::New));
832 if (initial)
833 return success();
834 if (llvm::is_contained(values, Value{}))
835 return failure();
836
837 // Then allocate storage for each instance input and assign the corresponding
838 // value.
839 for (auto [value, name] : llvm::zip(values, op.getArgNames())) {
840 auto state = AllocStateOp::create(module.allocBuilder, value.getLoc(),
841 StateType::get(value.getType()),
842 module.storageArg);
843 state->setAttr("name", module.builder.getStringAttr(
844 op.getInstanceName() + "/" +
845 cast<StringAttr>(name).getValue()));
846 StateWriteOp::create(module.builder, value.getLoc(), state, value);
847 }
848
849 // HACK: Also ensure that storage has been allocated for all outputs.
850 // Otherwise only the actually used instance outputs would be allocated, which
851 // would make the optimization user-visible. Remove this once we use the debug
852 // dialect.
853 for (auto result : op.getResults())
854 module.getAllocatedState(result);
855
856 return success();
857}
858
859/// Lower a coroutine instance.
860///
861/// An `arc.coroutine.instance` runs a top-level coroutine continuously inside a
862/// model. The coroutine's program counter, local state, and next wakeup time
863/// are kept in persistent state slots, and the values it yields are latched
864/// into result slots so they remain readable on evaluations where the coroutine
865/// does not run. On every evaluation the instance re-enters the coroutine if
866/// its scheduled wakeup time has been reached, stores the resulting program
867/// counter, state, and yielded values, and folds the next wakeup time into the
868/// model's global wakeup schedule.
869///
870/// A coroutine that has halted or returned must never be re-entered. Instead of
871/// inspecting the program counter on entry, the lowering forces the stored
872/// wakeup time to `UINT64_MAX` ("never") as soon as the coroutine reports a
873/// halt or return, so the time guard alone keeps it suspended.
874LogicalResult OpLowering::lower(CoroutineInstanceOp op) {
875 assert(phase == Phase::New);
876
877 // A coroutine samples its arguments in the New phase, so that a re-entry sees
878 // the up-to-date values produced in the same evaluation and the change
879 // detector below compares against fresh values.
880 SmallVector<Value> inputs;
881 for (auto input : op.getArgs())
882 inputs.push_back(lowerValue(input, Phase::New));
883 if (initial)
884 return success();
885 if (llvm::is_contained(inputs, Value{}))
886 return failure();
887
888 // Resolve the callee to obtain its state, program counter, and result types.
889 // The callee's last result is the next wakeup time; it is consumed for
890 // scheduling and not exposed as a result of the instance.
891 auto callee = op.getCalleeAttr();
892 auto defineOp =
893 module.symbolTable.lookup<CoroutineDefineOp>(callee.getAttr());
894 assert(defineOp && "verified by CoroutineInstanceOp::verifySymbolUses");
895 auto loc = op.getLoc();
896 auto *context = op.getContext();
897 auto stateType = CoroutineStateType::get(context, callee);
898 auto pcType = CoroutinePCType::get(context, callee);
899 auto i64Type = module.builder.getI64Type();
900
901 // Allocate the persistent program counter, state, and wakeup slots. Their
902 // zero-initialized contents represent the coroutine's start program counter,
903 // an unread initial state, and a wakeup time of zero ("run immediately").
904 auto pcSlot = AllocStateOp::create(module.allocBuilder, loc,
905 StateType::get(pcType), module.storageArg);
906 auto stateSlot = AllocStateOp::create(
907 module.allocBuilder, loc, StateType::get(stateType), module.storageArg);
908 auto wakeupSlot = AllocStateOp::create(
909 module.allocBuilder, loc, StateType::get(i64Type), module.storageArg);
910
911 // Allocate a slot for each yielded value so that it persists across
912 // evaluations where the coroutine does not run.
913 SmallVector<Value> resultSlots;
914 for (auto result : op.getResults()) {
915 auto slot = module.getAllocatedState(result);
916 if (!slot)
917 return failure();
918 resultSlots.push_back(slot);
919 }
920
921 // Detect changes on the observed arguments: each argument's value from the
922 // previous evaluation is held in a state slot, and a change is an inequality
923 // against the freshly sampled value. The observe bitmask reported by the
924 // coroutine on its last run selects which arguments matter; unobserved
925 // changes are ignored. The previous-value slots are updated unconditionally
926 // so they always track the latest value.
927 Value maskSlot;
928 Value anyChange = hw::ConstantOp::create(module.builder, loc,
929 module.builder.getI1Type(), 0);
930 if (!inputs.empty()) {
931 auto maskType = module.builder.getIntegerType(inputs.size());
932 maskSlot = AllocStateOp::create(
933 module.allocBuilder, loc, StateType::get(maskType), module.storageArg);
934 auto mask = StateReadOp::create(module.builder, loc, maskSlot);
935 for (auto [index, input] : llvm::enumerate(inputs)) {
936 if (!op.getSensitivityMask()[index])
937 continue;
938 auto prevSlot = AllocStateOp::create(module.allocBuilder, loc,
939 StateType::get(input.getType()),
940 module.storageArg);
941 auto prev = StateReadOp::create(module.builder, loc, prevSlot);
942 StateWriteOp::create(module.builder, loc, prevSlot, input);
943 auto changed = comb::ICmpOp::create(module.builder, loc,
944 comb::ICmpPredicate::ne, input, prev);
945 auto maskBit =
946 comb::ExtractOp::create(module.builder, loc, mask,
947 static_cast<unsigned>(index), /*bitWidth=*/1);
948 auto masked = comb::AndOp::create(module.builder, loc, changed, maskBit);
949 anyChange = comb::OrOp::create(module.builder, loc, anyChange, masked);
950 }
951 }
952
953 // Re-enter the coroutine if its scheduled wakeup time has been reached or if
954 // an observed argument changed.
955 auto now = CurrentTimeOp::create(module.builder, loc, module.storageArg);
956 auto wakeup = StateReadOp::create(module.builder, loc, wakeupSlot);
957 auto timeReady = comb::ICmpOp::create(module.builder, loc,
958 comb::ICmpPredicate::uge, now, wakeup);
959 auto ready = comb::OrOp::create(module.builder, loc, timeReady, anyChange);
960 auto ifOp =
961 scf::IfOp::create(module.builder, loc, ready, /*withElseRegion=*/false);
962 {
963 OpBuilder::InsertionGuard guard(module.builder);
964 module.builder.setInsertionPoint(ifOp.thenYield());
965
966 auto oldState = StateReadOp::create(module.builder, loc, stateSlot);
967 auto oldPc = StateReadOp::create(module.builder, loc, pcSlot);
968
969 // The call returns the resume state and program counter followed by the
970 // coroutine's own results, the last of which is the next wakeup time.
971 SmallVector<Type> callResultTypes;
972 callResultTypes.push_back(stateType);
973 callResultTypes.push_back(pcType);
974 llvm::append_range(callResultTypes, defineOp.getResultTypes());
975 auto call = CoroutineCallOp::create(module.builder, loc, callResultTypes,
976 callee, oldState, oldPc, inputs);
977 auto newState = call.getResult(0);
978 auto newPc = call.getResult(1);
979 auto wakeupNew = call.getResults().back();
980 auto maskNew = call.getResult(2 + op.getNumResults());
981
982 // Force the wakeup time to "never" once the coroutine halts or returns, so
983 // the time guard above prevents it from ever being re-entered.
984 auto isHalt = CoroutinePCIsHaltOp::create(module.builder, loc, newPc);
985 auto isReturn = CoroutinePCIsReturnOp::create(module.builder, loc, newPc);
986 auto isDone = comb::OrOp::create(module.builder, loc, isHalt, isReturn);
987 auto never = hw::ConstantOp::create(module.builder, loc, i64Type, -1);
988 auto wakeupEff =
989 comb::MuxOp::create(module.builder, loc, isDone, never, wakeupNew);
990
991 StateWriteOp::create(module.builder, loc, stateSlot, newState);
992 StateWriteOp::create(module.builder, loc, pcSlot, newPc);
993 StateWriteOp::create(module.builder, loc, wakeupSlot, wakeupEff);
994 if (maskSlot)
995 StateWriteOp::create(module.builder, loc, maskSlot, maskNew);
996 for (auto [index, slot] : llvm::enumerate(resultSlots))
997 StateWriteOp::create(module.builder, loc, slot,
998 call.getResult(2 + index));
999 }
1000
1001 // Fold the coroutine's pending wakeup time into the model's wakeup schedule.
1002 // This runs unconditionally: even when the coroutine did not execute this
1003 // evaluation, its stored wakeup must keep the model scheduled.
1004 auto curWakeup = StateReadOp::create(module.builder, loc, wakeupSlot);
1005 auto nextWakeup =
1006 GetNextWakeupOp::create(module.builder, loc, module.storageArg);
1007 auto minWakeup =
1008 arith::MinUIOp::create(module.builder, loc, curWakeup, nextWakeup);
1009 SetNextWakeupOp::create(module.builder, loc, module.storageArg, minWakeup);
1010
1011 return success();
1012}
1013
1014/// Lower `hw.triggered` by inlining its body under a posedge check.
1015LogicalResult OpLowering::lower(hw::TriggeredOp op) {
1016 assert(phase == Phase::New);
1017
1018 if (op.getEvent() != hw::EventControl::AtPosEdge) {
1019 if (!initial)
1020 return op.emitOpError("only posedge triggers are supported");
1021 return success();
1022 }
1023
1024 lowerValue(op.getTrigger(), Phase::New);
1025 SmallVector<Value> inputs;
1026 for (auto input : op.getInputs())
1027 inputs.push_back(lowerValue(input, Phase::Old));
1028 if (initial)
1029 return success();
1030 if (llvm::is_contained(inputs, Value{}))
1031 return failure();
1032
1033 auto ifClockOp = createIfClockOp(op.getTrigger());
1034 if (!ifClockOp)
1035 return failure();
1036
1037 OpBuilder::InsertionGuard guard(module.builder);
1038 module.builder.setInsertionPoint(ifClockOp.thenYield());
1039
1040 // Expose the trigger inputs as values for the body block arguments.
1041 for (auto [arg, input] : llvm::zip(op.getBodyBlock()->getArguments(), inputs))
1042 module.loweredValues[{arg, Phase::New}] = input;
1043 for (auto &bodyOp : llvm::make_early_inc_range(*op.getBodyBlock())) {
1044 OpLowering bodyLowering(&bodyOp, Phase::New, module);
1045 bodyLowering.initial = false;
1046 if (failed(bodyLowering.lower()))
1047 return failure();
1048 }
1049
1050 return success();
1051}
1052
1053/// Lower the main module's outputs by allocating storage for each and then
1054/// writing the current value into that storage.
1055LogicalResult OpLowering::lower(hw::OutputOp op) {
1056 assert(phase == Phase::New);
1057
1058 // First get the current value of all outputs.
1059 SmallVector<Value> values;
1060 for (auto operand : op.getOperands())
1061 values.push_back(lowerValue(operand, Phase::New));
1062 if (initial)
1063 return success();
1064 if (llvm::is_contained(values, Value{}))
1065 return failure();
1066
1067 // Then allocate storage for each output and assign the corresponding value.
1068 for (auto [value, name] :
1069 llvm::zip(values, module.moduleOp.getOutputNames())) {
1070 auto state = RootOutputOp::create(
1071 module.allocBuilder, value.getLoc(), StateType::get(value.getType()),
1072 cast<StringAttr>(name), module.storageArg);
1073 StateWriteOp::create(module.builder, value.getLoc(), state, value);
1074 }
1075 return success();
1076}
1077
1078/// Lower `seq.initial` ops by inlining them into the `arc.initial` op.
1079LogicalResult OpLowering::lower(seq::InitialOp op) {
1080 assert(phase == Phase::Initial);
1081
1082 // First get the initial value of all operands.
1083 SmallVector<Value> operands;
1084 for (auto operand : op.getOperands())
1085 operands.push_back(lowerValue(operand, Phase::Initial));
1086 if (initial)
1087 return success();
1088 if (llvm::is_contained(operands, Value{}))
1089 return failure();
1090
1091 // Expose the `seq.initial` operands as values for the block arguments.
1092 for (auto [arg, operand] : llvm::zip(op.getBody().getArguments(), operands))
1093 module.loweredValues[{arg, Phase::Initial}] = operand;
1094
1095 // Lower each op in the body. We maintain a mapping from original values
1096 // defined in the body to their cloned counterparts.
1097 IRMapping bodyMapping;
1098 auto *initialBlock = module.initialBuilder.getBlock();
1099
1100 // Pre-lower all llhd.current_time ops inside the body. This reuses the
1101 // existing lower(llhd::CurrentTimeOp) logic which handles Phase::Initial
1102 // by replacing with constant 0 time.
1103 auto result = op.walk([&](llhd::CurrentTimeOp timeOp) {
1104 if (failed(lower(timeOp)))
1105 return WalkResult::interrupt();
1106 auto loweredTime = module.loweredValues.lookup({timeOp.getResult(), phase});
1107 timeOp.replaceAllUsesWith(loweredTime);
1108 timeOp.erase();
1109 return WalkResult::advance();
1110 });
1111 if (result.wasInterrupted())
1112 return failure();
1113
1114 for (auto &bodyOp : op.getOps()) {
1115 if (isa<seq::YieldOp>(bodyOp))
1116 continue;
1117
1118 // Clone the operation.
1119 auto *clonedOp = module.initialBuilder.clone(bodyOp, bodyMapping);
1120 auto result = clonedOp->walk([&](Operation *nestedClonedOp) {
1121 for (auto &operand : nestedClonedOp->getOpOperands()) {
1122 // Skip operands defined within the cloned tree.
1123 if (clonedOp->isAncestor(operand.get().getParentBlock()->getParentOp()))
1124 continue;
1125 // Skip operands defined within the initial block (e.g., results of
1126 // previously lowered ops like our zeroTime).
1127 if (auto *defOp = operand.get().getDefiningOp())
1128 if (defOp->getBlock() == initialBlock)
1129 continue;
1130 auto value = module.requireLoweredValue(operand.get(), Phase::Initial,
1131 nestedClonedOp->getLoc());
1132 if (!value)
1133 return WalkResult::interrupt();
1134 operand.set(value);
1135 }
1136 return WalkResult::advance();
1137 });
1138 if (result.wasInterrupted())
1139 return failure();
1140
1141 // Keep track of the results in both mappings.
1142 for (auto [result, lowered] :
1143 llvm::zip(bodyOp.getResults(), clonedOp->getResults())) {
1144 bodyMapping.map(result, lowered);
1145 module.loweredValues[{result, Phase::Initial}] = lowered;
1146 }
1147 }
1148
1149 // Expose the operands of `seq.yield` as results from the initial op.
1150 auto *terminator = op.getBodyBlock()->getTerminator();
1151 for (auto [result, operand] :
1152 llvm::zip(op.getResults(), terminator->getOperands())) {
1153 auto value = module.requireLoweredValue(operand, Phase::Initial,
1154 terminator->getLoc());
1155 if (!value)
1156 return failure();
1157 module.loweredValues[{result, Phase::Initial}] = value;
1158 }
1159
1160 return success();
1161}
1162
1163/// Lower `llhd.final` ops into `scf.execute_region` ops in the `arc.final` op.
1164LogicalResult OpLowering::lower(llhd::FinalOp op) {
1165 assert(phase == Phase::Final);
1166
1167 // Determine the uses of values defined outside the op.
1168 SmallVector<Value> externalOperands;
1169 op.walk([&](Operation *nestedOp) {
1170 for (auto value : nestedOp->getOperands())
1171 if (!op->isAncestor(value.getParentBlock()->getParentOp()))
1172 externalOperands.push_back(value);
1173 });
1174
1175 // Make sure that all uses of external values are lowered first.
1176 IRMapping mapping;
1177 for (auto operand : externalOperands) {
1178 auto lowered = lowerValue(operand, Phase::Final);
1179 if (!initial && !lowered)
1180 return failure();
1181 mapping.map(operand, lowered);
1182 }
1183 if (initial)
1184 return success();
1185
1186 // Pre-lower all llhd.current_time ops inside the body. This reuses the
1187 // existing lower(llhd::CurrentTimeOp) logic which handles Phase::Final
1188 // by replacing with arc.current_time.
1189 auto result = op.walk([&](llhd::CurrentTimeOp timeOp) {
1190 if (failed(lower(timeOp)))
1191 return WalkResult::interrupt();
1192 auto loweredTime = module.loweredValues.lookup({timeOp.getResult(), phase});
1193 timeOp.replaceAllUsesWith(loweredTime);
1194 timeOp.erase();
1195 return WalkResult::advance();
1196 });
1197 if (result.wasInterrupted())
1198 return failure();
1199
1200 // Handle the simple case where the final op contains only one block, which we
1201 // can inline directly.
1202 if (op.getBody().hasOneBlock()) {
1203 for (auto &bodyOp : op.getBody().front().without_terminator())
1204 module.finalBuilder.clone(bodyOp, mapping);
1205 return success();
1206 }
1207
1208 // Create a new `scf.execute_region` op and clone the entire `llhd.final` body
1209 // region into it. Replace `llhd.halt` ops with `scf.yield`.
1210 auto executeOp = scf::ExecuteRegionOp::create(module.finalBuilder,
1211 op.getLoc(), TypeRange{});
1212 module.finalBuilder.cloneRegionBefore(op.getBody(), executeOp.getRegion(),
1213 executeOp.getRegion().begin(), mapping);
1214 executeOp.walk([&](llhd::HaltOp haltOp) {
1215 auto builder = OpBuilder(haltOp);
1216 scf::YieldOp::create(builder, haltOp.getLoc());
1217 haltOp.erase();
1218 });
1219
1220 return success();
1221}
1222
1223/// Lower `llhd.current_time` based on the current phase:
1224/// - Phase::Initial: Replace with constant 0 time.
1225/// - Phase::Old, Phase::New, Phase::Final: Replace with `arc.current_time`
1226/// followed by `llhd.int_to_time`.
1227LogicalResult OpLowering::lower(llhd::CurrentTimeOp op) {
1228 if (initial)
1229 return success();
1230
1231 auto loc = op.getLoc();
1232 Value time;
1233
1234 switch (phase) {
1235 case Phase::Initial: {
1236 // During initialization, time is always 0.
1237 auto zeroInt = hw::ConstantOp::create(
1238 module.initialBuilder, loc, module.initialBuilder.getI64Type(), 0);
1239 time = llhd::IntToTimeOp::create(module.initialBuilder, loc, zeroInt);
1240 break;
1241 }
1242 case Phase::Old:
1243 case Phase::New:
1244 case Phase::Final: {
1245 // Get the current time from storage.
1246 auto &builder = module.getBuilder(phase);
1247 auto timeInt = CurrentTimeOp::create(builder, loc, module.storageArg);
1248 time = llhd::IntToTimeOp::create(builder, loc, timeInt);
1249 break;
1250 }
1251 }
1252
1253 module.loweredValues[{op.getResult(), phase}] = time;
1254 return success();
1255}
1256
1257LogicalResult OpLowering::lower(sim::ClockedTerminateOp op) {
1258 if (phase != Phase::New)
1259 return success();
1260
1261 if (initial)
1262 return success();
1263
1264 auto ifClockOp = createIfClockOp(op.getClock());
1265 if (!ifClockOp)
1266 return failure();
1267
1268 OpBuilder::InsertionGuard guard(module.builder);
1269 module.builder.setInsertionPoint(ifClockOp.thenYield());
1270
1271 auto loc = op.getLoc();
1272 Value cond = lowerValue(op.getCondition(), phase);
1273 if (!cond)
1274 return op.emitOpError("Failed to lower condition");
1275
1276 auto ifOp = createOrReuseIf(module.builder, cond, false);
1277 if (!ifOp)
1278 return op.emitOpError("Failed to create condition block");
1279
1280 module.builder.setInsertionPoint(ifOp.thenYield());
1281
1282 arc::TerminateOp::create(module.builder, loc, module.storageArg,
1283 op.getSuccessAttr());
1284
1285 return success();
1286}
1287
1288/// Create the operations necessary to detect a posedge on the given clock,
1289/// potentially reusing a previous posedge detection, and create an `scf.if`
1290/// operation for that posedge. This also tries to reuse an `scf.if` operation
1291/// immediately before the builder's insertion point if possible.
1292scf::IfOp OpLowering::createIfClockOp(Value clock) {
1293 auto &posedge = module.loweredPosedges[clock];
1294 if (!posedge) {
1295 auto loweredClock = lowerValue(clock, Phase::New);
1296 if (!loweredClock)
1297 return {};
1298 posedge = module.detectPosedge(loweredClock);
1299 }
1300 return createOrReuseIf(module.builder, posedge, false);
1301}
1302
1303//===----------------------------------------------------------------------===//
1304// Value Lowering
1305//===----------------------------------------------------------------------===//
1306
1307/// Lower a value being used by the current operation. This will mark the
1308/// defining operation as to be lowered first (through `addPending`) in most
1309/// cases. Some operations and values have special handling though. For example,
1310/// states and memory reads are immediately materialized as a new read op.
1311Value OpLowering::lowerValue(Value value, Phase phase) {
1312 // Check if the value has already been lowered.
1313 if (auto lowered = module.loweredValues.lookup({value, phase}))
1314 return lowered;
1315
1316 // Handle module inputs. They read the same in all phases.
1317 if (auto arg = dyn_cast<BlockArgument>(value)) {
1318 if (arg.getOwner() != module.moduleOp.getBodyBlock()) {
1319 if (!initial)
1320 emitError(arg.getLoc()) << "block argument has not been lowered";
1321 return {};
1322 }
1323 if (initial)
1324 return {};
1325 auto state = module.allocatedInputs[arg.getArgNumber()];
1326 return StateReadOp::create(module.getBuilder(phase), arg.getLoc(), state);
1327 }
1328
1329 // At this point the value is the result of an op. (Block arguments are
1330 // handled above.)
1331 auto result = cast<OpResult>(value);
1332 auto *op = result.getOwner();
1333
1334 // Special handling for some ops.
1335 if (auto instOp = dyn_cast<InstanceOp>(op))
1336 return lowerValue(instOp, result, phase);
1337 if (auto instOp = dyn_cast<CoroutineInstanceOp>(op))
1338 return lowerValue(instOp, result, phase);
1339 if (auto stateOp = dyn_cast<StateOp>(op))
1340 return lowerValue(stateOp, result, phase);
1341 if (auto dpiOp = dyn_cast<sim::DPICallOp>(op); dpiOp && dpiOp.getClock())
1342 return lowerValue(dpiOp, result, phase);
1343 if (auto readOp = dyn_cast<MemoryReadPortOp>(op))
1344 return lowerValue(readOp, result, phase);
1345 if (auto initialOp = dyn_cast<seq::InitialOp>(op))
1346 return lowerValue(initialOp, result, phase);
1347 if (auto castOp = dyn_cast<seq::FromImmutableOp>(op))
1348 return lowerValue(castOp, result, phase);
1349
1350 // Otherwise we mark the defining operation as to be lowered first. This will
1351 // cause the lookup in `loweredValues` above to return a value the next time
1352 // (i.e. when initial is false).
1353 if (initial) {
1354 addPending(op, phase);
1355 return {};
1356 }
1357 emitError(result.getLoc()) << "value has not been lowered";
1358 return {};
1359}
1360
1361/// Handle instance outputs. They behave essentially like a top-level module
1362/// input, and read the same in all phases.
1363Value OpLowering::lowerValue(InstanceOp op, OpResult result, Phase phase) {
1364 if (initial)
1365 return {};
1366 auto state = module.getAllocatedState(result);
1367 return StateReadOp::create(module.getBuilder(phase), result.getLoc(), state);
1368}
1369
1370/// Handle the yielded values of a coroutine instance. The values are latched
1371/// into a result slot by the instance lowering; reading the new value requires
1372/// the instance to be lowered first so the slot has been written, while reading
1373/// the old value observes the slot's contents before this evaluation's update.
1374Value OpLowering::lowerValue(CoroutineInstanceOp op, OpResult result,
1375 Phase phase) {
1376 if (initial) {
1377 // The instance only ever runs in the new phase, where it writes the result
1378 // slots. Make sure that has happened before we read them.
1379 if (phase == Phase::New)
1380 addPending(op, Phase::New);
1381 return {};
1382 }
1383
1384 // If we want to read the old value, no writes must have been lowered yet.
1385 if (phase == Phase::Old)
1386 assert(!module.loweredOps.contains({op, Phase::New}) &&
1387 "need old value but new value already written");
1388
1389 auto state = module.getAllocatedState(result);
1390 return StateReadOp::create(module.getBuilder(phase), result.getLoc(), state);
1391}
1392
1393/// Handle uses of a state. This creates an `arc.state_read` op to read from the
1394/// state's storage. If the new value after all updates is requested, marks the
1395/// state as to be lowered first (which will perform the writes). If the old
1396/// value is requested, asserts that no new values have been written.
1397Value OpLowering::lowerValue(StateOp op, OpResult result, Phase phase) {
1398 if (initial) {
1399 // Ensure that the new or initial value has been written by the lowering of
1400 // the state op before we attempt to read it.
1401 if (phase == Phase::New || phase == Phase::Initial)
1402 addPending(op, phase);
1403 return {};
1404 }
1405
1406 // If we want to read the old value, no writes must have been lowered yet.
1407 if (phase == Phase::Old)
1408 assert(!module.loweredOps.contains({op, Phase::New}) &&
1409 "need old value but new value already written");
1410
1411 auto state = module.getAllocatedState(result);
1412 return StateReadOp::create(module.getBuilder(phase), result.getLoc(), state);
1413}
1414
1415/// Handle uses of a DPI call. This creates an `arc.state_read` op to read from
1416/// the state's storage. If the new value after all updates is requested, marks
1417/// the state as to be lowered first (which will perform the writes). If the old
1418/// value is requested, asserts that no new values have been written.
1419Value OpLowering::lowerValue(sim::DPICallOp op, OpResult result, Phase phase) {
1420 if (initial) {
1421 // Ensure that the new or initial value has been written by the lowering of
1422 // the state op before we attempt to read it.
1423 if (phase == Phase::New || phase == Phase::Initial)
1424 addPending(op, phase);
1425 return {};
1426 }
1427
1428 // If we want to read the old value, no writes must have been lowered yet.
1429 if (phase == Phase::Old)
1430 assert(!module.loweredOps.contains({op, Phase::New}) &&
1431 "need old value but new value already written");
1432
1433 auto state = module.getAllocatedState(result);
1434 return StateReadOp::create(module.getBuilder(phase), result.getLoc(), state);
1435}
1436
1437/// Handle uses of a memory read operation. This creates an `arc.memory_read` op
1438/// to read from the memory's storage. Similar to the `StateOp` handling
1439/// otherwise.
1440Value OpLowering::lowerValue(MemoryReadPortOp op, OpResult result,
1441 Phase phase) {
1442 auto memOp = op.getMemory().getDefiningOp<MemoryOp>();
1443 if (!memOp) {
1444 if (!initial)
1445 op->emitOpError() << "memory must be defined locally";
1446 return {};
1447 }
1448
1449 auto address = lowerValue(op.getAddress(), phase);
1450 if (initial) {
1451 // Ensure that all new values are written before we attempt to read them.
1452 if (phase == Phase::New)
1453 addPending(memOp.getOperation(), Phase::New);
1454 return {};
1455 }
1456 if (!address)
1457 return {};
1458
1459 if (phase == Phase::Old) {
1460 // If we want to read the old value, no writes must have been lowered yet.
1461 assert(!module.loweredOps.contains({memOp, Phase::New}) &&
1462 "need old memory value but new value already written");
1463 } else {
1464 assert(phase == Phase::New);
1465 }
1466
1467 auto state = module.getAllocatedState(memOp->getResult(0));
1468 return MemoryReadOp::create(module.getBuilder(phase), result.getLoc(), state,
1469 address);
1470}
1471
1472/// Handle uses of `seq.initial` values computed during the initial phase. This
1473/// ensures that the interesting value is stored into storage during the initial
1474/// phase, and then reads it back using an `arc.state_read` op.
1475Value OpLowering::lowerValue(seq::InitialOp op, OpResult result, Phase phase) {
1476 // Ensure the op has been lowered first.
1477 if (initial) {
1478 addPending(op, Phase::Initial);
1479 return {};
1480 }
1481 auto value = module.loweredValues.lookup({result, Phase::Initial});
1482 if (!value) {
1483 emitError(result.getLoc()) << "value has not been lowered";
1484 return {};
1485 }
1486
1487 // If we are using the value of `seq.initial` in the initial phase directly,
1488 // there is no need to write it so any temporary storage.
1489 if (phase == Phase::Initial)
1490 return value;
1491
1492 // If necessary, allocate storage for the computed value and store it in the
1493 // initial phase.
1494 auto &state = module.allocatedInitials[result];
1495 if (!state) {
1496 state = AllocStateOp::create(module.allocBuilder, value.getLoc(),
1497 StateType::get(value.getType()),
1498 module.storageArg);
1499 OpBuilder::InsertionGuard guard(module.initialBuilder);
1500 module.initialBuilder.setInsertionPointAfterValue(value);
1501 StateWriteOp::create(module.initialBuilder, value.getLoc(), state, value);
1502 }
1503
1504 // Read back the value computed during the initial phase.
1505 return StateReadOp::create(module.getBuilder(phase), state.getLoc(), state);
1506}
1507
1508/// The `seq.from_immutable` cast is just a passthrough.
1509Value OpLowering::lowerValue(seq::FromImmutableOp op, OpResult result,
1510 Phase phase) {
1511 return lowerValue(op.getInput(), phase);
1512}
1513
1514/// Mark a value as to be lowered before the current op.
1515void OpLowering::addPending(Value value, Phase phase) {
1516 auto *defOp = value.getDefiningOp();
1517 assert(defOp && "block args should never be marked as a dependency");
1518 addPending(defOp, phase);
1519}
1520
1521/// Mark an operation as to be lowered before the current op. This adds that
1522/// operation to the `pending` list if the operation has not yet been lowered.
1523void OpLowering::addPending(Operation *op, Phase phase) {
1524 auto pair = std::make_pair(op, phase);
1525 if (!module.loweredOps.contains(pair))
1526 if (!llvm::is_contained(pending, pair))
1527 pending.push_back(pair);
1528}
1529
1530//===----------------------------------------------------------------------===//
1531// Pass Infrastructure
1532//===----------------------------------------------------------------------===//
1533
1534namespace {
1535struct LowerStatePass : public arc::impl::LowerStatePassBase<LowerStatePass> {
1536 using LowerStatePassBase::LowerStatePassBase;
1537 void runOnOperation() override;
1538};
1539} // namespace
1540
1541void LowerStatePass::runOnOperation() {
1542 auto op = getOperation();
1543 auto &symbolTable = getAnalysis<SymbolTable>();
1544 for (auto moduleOp : llvm::make_early_inc_range(op.getOps<HWModuleOp>())) {
1545 if (failed(ModuleLowering(moduleOp, symbolTable).run()))
1546 return signalPassFailure();
1547 moduleOp.erase();
1548 }
1549
1550 for (auto extModuleOp :
1551 llvm::make_early_inc_range(op.getOps<HWModuleExternOp>())) {
1552 // Make sure that we're not leaving behind a dangling reference to this
1553 // module
1554 auto uses = symbolTable.getSymbolUses(extModuleOp, op);
1555 if (!uses->empty()) {
1556 extModuleOp->emitError("Failed to remove external module because it is "
1557 "still referenced/instantiated");
1558 return signalPassFailure();
1559 }
1560 extModuleOp.erase();
1561 }
1562}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static bool isAncestor(Block *block, Block *other)
Definition LayerSink.cpp:57
static scf::IfOp createOrReuseIf(OpBuilder &builder, Value condition, bool withElse)
Create a new scf.if operation with the given builder, or reuse a previous scf.if if the builder's ins...
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:222
static Block * getBodyBlock(FModuleLike mod)
create(low_bit, result_type, input=None)
Definition comb.py:187
create(data_type, value)
Definition hw.py:441
create(data_type, value)
Definition hw.py:433
Definition arc.py:1
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int run(Type[Generator] generator=CppGenerator, List[str] cmdline_args=sys.argv)
Definition hw.py:1
write(addr, data)
Definition xrt_cosim.py:30
read(addr)
Definition xrt_cosim.py:23