CIRCT 24.0.0git
Loading...
Searching...
No Matches
UnrollMemoryLatencies.cpp
Go to the documentation of this file.
1//===- UnrollMemoryLatencies.cpp - seq.shiftreg lowering ------------------===//
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
13#include "mlir/Pass/Pass.h"
14#include "mlir/Transforms/DialectConversion.h"
15#include "llvm/ADT/TypeSwitch.h"
16
17namespace circt {
18namespace seq {
19#define GEN_PASS_DEF_UNROLLMEMORYLATENCIES
20#include "circt/Dialect/Seq/SeqPasses.h.inc"
21} // namespace seq
22} // namespace circt
23
24using namespace circt;
25using namespace seq;
26
27namespace {
28struct UnrollMemoryLatenciesPass
29 : public circt::seq::impl::UnrollMemoryLatenciesBase<
30 UnrollMemoryLatenciesPass> {
31 void runOnOperation() override;
32
33private:
34 /// Delays all of the addresses in the given value range using the given delay
35 /// and stores the delayed addresses in the given vector.
36 void delayAddresses(OpBuilder &builder, Location loc, Value clk, Value ce,
37 MutableOperandRange addrs, IntegerAttr delayAttr) const {
38
39 builder.setInsertionPoint(addrs.getOwner());
40
41 // My understanding is that when multiple addresses are given, the same
42 // data is written to each location. Therefore we must delay all of the
43 // addresses individually.
44 for (auto &addr : addrs)
45 // delay the addr
46 addr.set(seq::ShiftRegOp::create(builder, loc, delayAttr, addr.get(), clk,
47 ce, {}, {}, {}, {}, {}));
48 }
49
50 /// Unrolling for the HLMem write ports. This reduces the latency attribute
51 /// of the port to 1 and implements that latency as a delay of n-1 on its
52 /// data and address inputs.
53 void hlMemOpWritePortUnrolling(seq::WritePortOp &op,
54 OpBuilder &builder) const {
55
56 // Compute the new latency attributes
57 IntegerAttr latencyAttr = op.getLatencyAttr();
58 IntegerAttr delayAttr =
59 IntegerAttr::get(latencyAttr.getType(), latencyAttr.getInt() - 1);
60 IntegerAttr oneAttr = IntegerAttr::get(latencyAttr.getType(), 1);
61
62 // Retrieve all useful arguments
63 OpOperand &data = op.getInDataMutable();
64 OpOperand &wrEn = op.getWrEnMutable();
65 MutableOperandRange addrs = op.getAddressesMutable();
66 Value clk = op.getMemory().getDefiningOp()->getOperand(0);
67
68 builder.setInsertionPointAfter(op);
69
70 // A global clock enable for all ports that is always true
71 Value ce = builder.createOrFold<hw::ConstantOp>(op.getLoc(),
72 builder.getI1Type(), 1);
73
74 // Delay all of the addresses
75 delayAddresses(builder, op.getLoc(), clk, ce, addrs, delayAttr);
76
77 // delay the data
78 data.set(seq::ShiftRegOp::create(builder, op.getLoc(), delayAttr,
79 data.get(), clk, ce, {}, {}, {}, {}, {}));
80
81 // delay the write enable
82 wrEn.set(seq::ShiftRegOp::create(builder, op.getLoc(), delayAttr,
83 wrEn.get(), clk, ce, {}, {}, {}, {}, {}));
84
85 // Update the latency
86 op.setLatencyAttr(oneAttr);
87 }
88
89 /// Unrolling for the HLMem Read ports. This reduces the latency attribute
90 /// of the port to 0 and implements that latency as a delay of n, modeled
91 /// as a shift-register, on its address input.
92 void hlMemOpReadPortUnrolling(seq::ReadPortOp &op, OpBuilder &builder) const {
93 // Compute the new latency attributes
94 IntegerAttr latencyAttr = op.getLatencyAttr();
95 IntegerAttr zeroAttr = IntegerAttr::get(latencyAttr.getType(), 0);
96
97 // Retrieve all useful arguments
98 MutableOperandRange addrs = op.getAddressesMutable();
99 MutableOperandRange rdEn = op.getRdEnMutable();
100 Value clk = op.getMemory().getDefiningOp()->getOperand(0);
101
102 builder.setInsertionPointAfter(op);
103
104 // A global clock enable for all ports that is always true
105 Value ce = builder.createOrFold<hw::ConstantOp>(op.getLoc(),
106 builder.getI1Type(), 1);
107
108 // Same as write port, delay all of the addresses
109 delayAddresses(builder, op.getLoc(), clk, ce, addrs, latencyAttr);
110
111 // delay the read enable
112 for (auto &rden : rdEn)
113 rden.set(seq::ShiftRegOp::create(builder, op.getLoc(), latencyAttr,
114 rden.get(), clk, ce, {}, {}, {}, {},
115 {}));
116
117 op.setLatencyAttr(zeroAttr);
118 }
119
120 /// Unrolling for FirMem latencies. Unlike HLMem, FirMem stores
121 /// latencies it the memory declaration op rather than the ports themselves.
122 /// This makes the unrolling a little more annoying, as we need to do fetch
123 /// all of the ports from the memory itself, i.e. walk its users to find the
124 /// all of the relevant ports to delay before updating the memory declaration
125 /// to have its new delays. The actual port updates are identical to those
126 /// done in the HLMem ports.
127 void unrollFirMemWritePort(seq::FirMemWriteOp &op, IntegerAttr delayAttr,
128 Value ce, OpBuilder &builder) const {
129
130 // Retrieve all of the relevant arguments
131 OpOperand &addr = op.getAddressMutable();
132 OpOperand &data = op.getDataMutable();
133 MutableOperandRange ens = op.getEnableMutable();
134 Value clk = op.getClk();
135
136 // Delay the address, data, and enable inputs
137 data.set(seq::ShiftRegOp::create(builder, op.getLoc(), delayAttr,
138 data.get(), clk, ce, {}, {}, {}, {}, {}));
139
140 addr.set(seq::ShiftRegOp::create(builder, op.getLoc(), delayAttr,
141 addr.get(), clk, ce, {}, {}, {}, {}, {}));
142
143 for (auto &en : ens)
144 en.set(seq::ShiftRegOp::create(builder, op.getLoc(), delayAttr, en.get(),
145 clk, ce, {}, {}, {}, {}, {}));
146 }
147
148 /// Performs the latency unrolling of an individual read port
149 void unrollFirMemReadPort(seq::FirMemReadOp &op, IntegerAttr delayRdAttr,
150 Value ce, OpBuilder &builder) const {
151 // Retrieve all of the relevant arguments
152 OpOperand &addr = op.getAddressMutable();
153 MutableOperandRange ens = op.getEnableMutable();
154 Value clk = op.getClk();
155
156 addr.set(seq::ShiftRegOp::create(builder, op.getLoc(), delayRdAttr,
157 addr.get(), clk, ce, {}, {}, {}, {}, {}));
158
159 for (auto &en : ens)
160 en.set(seq::ShiftRegOp::create(builder, op.getLoc(), delayRdAttr,
161 en.get(), clk, ce, {}, {}, {}, {}, {}));
162 }
163
164 /// Performs the latency unrolling of a read-write port.
165 /// These are annoying as they require implementing both the read and write
166 /// unrolling and then muxing the two delays using the mode as a selector.
167 void unrollFirMemReadWritePort(seq::FirMemReadWriteOp &op,
168 IntegerAttr delayWrAttr,
169 IntegerAttr delayRdAttr, Value ce,
170 OpBuilder &builder) const {
171 // Retrieve all of the relevant arguments
172 OpOperand &addr = op.getAddressMutable();
173 OpOperand &data = op.getWriteDataMutable();
174 OpOperand &mode = op.getModeMutable();
175 MutableOperandRange ens = op.getEnableMutable();
176 Value clk = op.getClk();
177
178 // Create two versions of the delayed address, one using the write
179 // latency, one using the read latency
180 auto delayedReadAddr =
181 seq::ShiftRegOp::create(builder, op.getLoc(), delayRdAttr, addr.get(),
182 clk, ce, {}, {}, {}, {}, {});
183
184 auto delayedWriteAddr =
185 seq::ShiftRegOp::create(builder, op.getLoc(), delayWrAttr, addr.get(),
186 clk, ce, {}, {}, {}, {}, {});
187
188 // Delay the write data, this will only be used in case of write so no
189 // need for a second mux.
190 data.set(seq::ShiftRegOp::create(builder, op.getLoc(), delayWrAttr,
191 data.get(), clk, ce, {}, {}, {}, {}, {}));
192
193 // Delay mode to match read and write inputs
194 // Select the delay sucht that mode = mux(
195 // mode.dealy(min(rd_lat, wr_lat)),
196 // mode.delay(wr_lat), mode.delay(rd_lat)
197 // )
198 // This makes sure that we always select the correct address using the
199 // relevant mode
200
201 auto delayedModeWr =
202 seq::ShiftRegOp::create(builder, op.getLoc(), delayWrAttr, mode.get(),
203 clk, ce, {}, {}, {}, {}, {});
204
205 auto delayedModeRd =
206 seq::ShiftRegOp::create(builder, op.getLoc(), delayRdAttr, mode.get(),
207 clk, ce, {}, {}, {}, {}, {});
208
209 // We reuse the smallest delayed mode to avoid creating a duplicate
210 // shiftreg
211 auto selectMode = comb::MuxOp::create(
212 builder, op.getLoc(),
213 delayWrAttr.getInt() < delayRdAttr.getInt() ? delayedModeWr
214 : delayedModeRd,
215 delayedModeWr, delayedModeRd);
216 mode.set(selectMode);
217
218 // Select which delayed version we want based on the mode
219 addr.set(comb::MuxOp::create(builder, op.getLoc(), selectMode,
220 delayedWriteAddr, delayedReadAddr));
221
222 // Finally also delay the enable signal
223 for (auto &en : ens)
224 en.set(seq::ShiftRegOp::create(builder, op.getLoc(), delayWrAttr,
225 en.get(), clk, ce, {}, {}, {}, {}, {}));
226 }
227
228 void firMemOpUnrolling(seq::FirMemOp &mem, OpBuilder &builder) const {
229
230 // Retrieve read and write latencies
231 IntegerAttr wrLatency = mem.getWriteLatencyAttr();
232 IntegerAttr rdLatency = mem.getReadLatencyAttr();
233 IntegerAttr delayRdAttr = builder.getI64IntegerAttr(rdLatency.getInt());
234 IntegerAttr delayWrAttr = builder.getI64IntegerAttr(wrLatency.getInt() - 1);
235 IntegerAttr zeroAttr = builder.getI32IntegerAttr(0);
236 IntegerAttr oneAttr = builder.getI32IntegerAttr(1);
237
238 builder.setInsertionPointAfter(mem);
239
240 // A global clock enable for all ports that is always true
241 Value ce = builder.createOrFold<hw::ConstantOp>(mem.getLoc(),
242 builder.getI1Type(), 1);
243
244 // Find all of the relevant ports and update them
245 for (auto *user : mem->getUsers()) {
246 // Figure out what type of port it is an unroll it
247 TypeSwitch<Operation *, void>(user)
248 .Case<seq::FirMemWriteOp>([&](auto port) {
249 unrollFirMemWritePort(port, delayWrAttr, ce, builder);
250 })
251 .Case<seq::FirMemReadOp>([&](auto port) {
252 unrollFirMemReadPort(port, delayRdAttr, ce, builder);
253 })
254 .Case<seq::FirMemReadWriteOp>([&](auto port) {
255 unrollFirMemReadWritePort(port, delayWrAttr, delayRdAttr, ce,
256 builder);
257 })
258 .Default([](auto) { /*ignore non-port users*/ });
259 }
260
261 // Update the memory's latency attributes
262 mem.setReadLatencyAttr(zeroAttr);
263 mem.setWriteLatencyAttr(oneAttr);
264 }
265};
266
267} // namespace
268
269void UnrollMemoryLatenciesPass::runOnOperation() {
270 auto top = getOperation();
271 OpBuilder builder(top);
272
273 // Match over all hlmemport ops and firmem ops
274 top->walk([&](Operation *op) {
275 TypeSwitch<Operation *>(op)
276 .Case<seq::WritePortOp>(
277 [&](auto &wrop) { hlMemOpWritePortUnrolling(wrop, builder); })
278 .Case<seq::ReadPortOp>(
279 [&](auto &rdop) { hlMemOpReadPortUnrolling(rdop, builder); })
280 .Case<seq::FirMemOp>(
281 [&](auto &firmem) { firMemOpUnrolling(firmem, builder); })
282 .Default([](auto) {});
283 });
284}
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition seq.py:1