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