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