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