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