CIRCT 24.0.0git
Loading...
Searching...
No Matches
LowerSeqFIFO.cpp
Go to the documentation of this file.
1//===- LowerSeqFIFO.cpp - seq.fifo 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
15#include "mlir/Pass/Pass.h"
16#include "mlir/Transforms/DialectConversion.h"
17#include "llvm/ADT/TypeSwitch.h"
18
19namespace circt {
20namespace seq {
21#define GEN_PASS_DEF_LOWERSEQFIFO
22#include "circt/Dialect/Seq/SeqPasses.h.inc"
23} // namespace seq
24} // namespace circt
25
26using namespace circt;
27using namespace seq;
28
29namespace {
30
31struct FIFOLowering : public OpConversionPattern<seq::FIFOOp> {
32public:
33 using OpConversionPattern::OpConversionPattern;
34
35 LogicalResult
36 matchAndRewrite(seq::FIFOOp mem, OpAdaptor adaptor,
37 ConversionPatternRewriter &rewriter) const final {
38 Location loc = mem.getLoc();
39 Type eltType = adaptor.getInput().getType();
40 Value clk = adaptor.getClk();
41 Value rst = adaptor.getRst();
42 BackedgeBuilder bb(rewriter, loc);
43 size_t depth = mem.getDepth();
44 Type countType = rewriter.getIntegerType(llvm::Log2_64_Ceil(depth + 1));
45 Type ptrType = rewriter.getIntegerType(llvm::Log2_64_Ceil(depth));
46 Backedge rdAddrNext = bb.get(ptrType);
47 Backedge wrAddrNext = bb.get(ptrType);
48 Backedge nextCount = bb.get(countType);
49
50 // ====== Some constants ======
51 Value countTcFull = hw::ConstantOp::create(rewriter, loc, countType, depth);
52 Value countTc1 = hw::ConstantOp::create(rewriter, loc, countType, 1);
53 Value countTc0 = hw::ConstantOp::create(rewriter, loc, countType, 0);
54 Value ptrTc0 = hw::ConstantOp::create(rewriter, loc, ptrType, 0);
55 Value ptrTc1 = hw::ConstantOp::create(rewriter, loc, ptrType, 1);
56 Value ptrTcFull = hw::ConstantOp::create(rewriter, loc, ptrType, depth);
57
58 // ====== Hardware units ======
59 Value count = seq::CompRegOp::create(
60 rewriter, loc, nextCount, clk, rst,
61 hw::ConstantOp::create(rewriter, loc, countType, 0), "fifo_count");
62 seq::HLMemOp hlmem = seq::HLMemOp::create(
63 rewriter, loc, clk, rst, "fifo_mem",
64 llvm::SmallVector<int64_t>{static_cast<int64_t>(depth)}, eltType);
65 Value rdAddr = seq::CompRegOp::create(
66 rewriter, loc, rdAddrNext, clk, rst,
67 hw::ConstantOp::create(rewriter, loc, ptrType, 0), "fifo_rd_addr");
68 Value wrAddr = seq::CompRegOp::create(
69 rewriter, loc, wrAddrNext, clk, rst,
70 hw::ConstantOp::create(rewriter, loc, ptrType, 0), "fifo_wr_addr");
71
72 // The memory read port only samples the read address 'rdLatency - 1'
73 // cycles after a pop. Until then the popped slot must not be overwritten,
74 // so track the number of slots which are either holding data or awaiting
75 // their delayed read, and use that to compute fullness. A slot is released
76 // on the same edge on which it is read.
77 uint64_t rdLatency = mem.getRdLatency();
78 uint64_t numInflightStages = rdLatency > 1 ? rdLatency - 1 : 0;
79 Value used = count;
80 Backedge nextUsed;
81 if (numInflightStages > 0) {
82 nextUsed = bb.get(countType);
84 rewriter, loc, nextUsed, clk, rst,
85 hw::ConstantOp::create(rewriter, loc, countType, 0), "fifo_used");
86 }
87
88 Value readData = seq::ReadPortOp::create(
89 rewriter, loc, hlmem, llvm::SmallVector<Value>{rdAddr},
90 adaptor.getRdEn(), mem.getRdLatency());
91 seq::WritePortOp::create(rewriter, loc, hlmem,
92 llvm::SmallVector<Value>{wrAddr},
93 adaptor.getInput(), adaptor.getWrEn(),
94 /*latency*/ 1);
95
96 // ====== some more constants =====
97 comb::ICmpOp fifoFull = comb::ICmpOp::create(
98 rewriter, loc, comb::ICmpPredicate::eq, used, countTcFull);
99 fifoFull->setAttr("sv.namehint", rewriter.getStringAttr("fifo_full"));
100 comb::ICmpOp fifoEmpty = comb::ICmpOp::create(
101 rewriter, loc, comb::ICmpPredicate::eq, count, countTc0);
102 fifoEmpty->setAttr("sv.namehint", rewriter.getStringAttr("fifo_empty"));
103
104 // ====== Next-state count ======
105 auto notRdEn = comb::createOrFoldNot(rewriter, loc, adaptor.getRdEn());
106 auto notWrEn = comb::createOrFoldNot(rewriter, loc, adaptor.getWrEn());
107 Value rdEnNandWrEn = comb::AndOp::create(rewriter, loc, notRdEn, notWrEn);
108 Value rdEnAndNotWrEn =
109 comb::AndOp::create(rewriter, loc, adaptor.getRdEn(), notWrEn);
110 Value wrEnAndNotRdEn =
111 comb::AndOp::create(rewriter, loc, adaptor.getWrEn(), notRdEn);
112
113 auto addCountTc1 = comb::AddOp::create(rewriter, loc, count, countTc1);
114 Value wrEnNext = comb::MuxOp::create(rewriter, loc, fifoFull,
115 // keep value
116 count,
117 // increment
118 addCountTc1);
119 auto countEqTc0 = comb::ICmpOp::create(
120 rewriter, loc, comb::ICmpPredicate::eq, count, countTc0);
121 auto subCountTc1 = comb::SubOp::create(rewriter, loc, count, countTc1);
122
123 Value rdEnNext = comb::MuxOp::create(rewriter, loc, countEqTc0,
124 // keep value
125 count,
126 // decrement
127 subCountTc1);
128
129 auto nextInnerMux =
130 comb::MuxOp::create(rewriter, loc, rdEnAndNotWrEn, rdEnNext, count);
131 auto nextMux = comb::MuxOp::create(rewriter, loc, wrEnAndNotRdEn, wrEnNext,
132 nextInnerMux);
133 nextCount.setValue(comb::MuxOp::create(rewriter, loc, rdEnNandWrEn,
134 /*keep value*/ count, nextMux));
135 static_cast<Value>(nextCount).getDefiningOp()->setAttr(
136 "sv.namehint", rewriter.getStringAttr("fifo_count_next"));
137
138 // ====== Read/write pointers ======
139 Value wrAndNotFull =
140 comb::AndOp::create(rewriter, loc, adaptor.getWrEn(),
141 comb::createOrFoldNot(rewriter, loc, fifoFull));
142 auto addWrAddrPtrTc1 = comb::AddOp::create(rewriter, loc, wrAddr, ptrTc1);
143
144 auto wrAddrNextNoRollover = comb::MuxOp::create(rewriter, loc, wrAndNotFull,
145 addWrAddrPtrTc1, wrAddr);
146 auto isMaxAddrWr =
147 comb::ICmpOp::create(rewriter, loc, comb::ICmpPredicate::eq,
148 wrAddrNextNoRollover, ptrTcFull);
149 wrAddrNext.setValue(comb::MuxOp::create(rewriter, loc, isMaxAddrWr, ptrTc0,
150 wrAddrNextNoRollover));
151 static_cast<Value>(wrAddrNext)
152 .getDefiningOp()
153 ->setAttr("sv.namehint", rewriter.getStringAttr("fifo_wr_addr_next"));
154
155 auto notFifoEmpty = comb::createOrFoldNot(rewriter, loc, fifoEmpty);
156 Value rdAndNotEmpty =
157 comb::AndOp::create(rewriter, loc, adaptor.getRdEn(), notFifoEmpty);
158 auto addRdAddrPtrTc1 = comb::AddOp::create(rewriter, loc, rdAddr, ptrTc1);
159 auto rdAddrNextNoRollover = comb::MuxOp::create(
160 rewriter, loc, rdAndNotEmpty, addRdAddrPtrTc1, rdAddr);
161 auto isMaxAddrRd =
162 comb::ICmpOp::create(rewriter, loc, comb::ICmpPredicate::eq,
163 rdAddrNextNoRollover, ptrTcFull);
164 rdAddrNext.setValue(comb::MuxOp::create(rewriter, loc, isMaxAddrRd, ptrTc0,
165 rdAddrNextNoRollover));
166 static_cast<Value>(rdAddrNext)
167 .getDefiningOp()
168 ->setAttr("sv.namehint", rewriter.getStringAttr("fifo_rd_addr_next"));
169
170 // ====== In-flight reads ======
171 if (numInflightStages > 0) {
172 // Delay each pop until the edge on which its slot is read.
173 Value retire = rdAndNotEmpty;
174 Value false_ =
175 hw::ConstantOp::create(rewriter, loc, rewriter.getI1Type(), 0);
176 for (uint64_t i = 0; i < numInflightStages; ++i)
177 retire =
178 seq::CompRegOp::create(rewriter, loc, retire, clk, rst, false_,
179 "fifo_rd_inflight_dly" + std::to_string(i));
180
181 auto notRetire = comb::createOrFoldNot(rewriter, loc, retire);
182 auto notWrAndNotFull = comb::createOrFoldNot(rewriter, loc, wrAndNotFull);
183 Value incUsed =
184 comb::AndOp::create(rewriter, loc, wrAndNotFull, notRetire);
185 Value decUsed =
186 comb::AndOp::create(rewriter, loc, retire, notWrAndNotFull);
187 auto usedPlus1 = comb::AddOp::create(rewriter, loc, used, countTc1);
188 auto usedMinus1 = comb::SubOp::create(rewriter, loc, used, countTc1);
189 auto usedDecMux =
190 comb::MuxOp::create(rewriter, loc, decUsed, usedMinus1, used);
191 auto usedNextMux =
192 comb::MuxOp::create(rewriter, loc, incUsed, usedPlus1, usedDecMux);
193 usedNextMux->setAttr("sv.namehint",
194 rewriter.getStringAttr("fifo_used_next"));
195 nextUsed.setValue(usedNextMux);
196 }
197
198 // ====== Result values ======
199 llvm::SmallVector<Value> results;
200
201 // Data
202 results.push_back(readData);
203 // Full
204 results.push_back(fifoFull);
205 // Empty
206 results.push_back(fifoEmpty);
207
208 if (auto almostFull = mem.getAlmostFullThreshold()) {
209 results.push_back(
210 comb::ICmpOp::create(rewriter, loc, comb::ICmpPredicate::uge, used,
211 hw::ConstantOp::create(rewriter, loc, countType,
212 almostFull.value())));
213 static_cast<Value>(results.back())
214 .getDefiningOp()
215 ->setAttr("sv.namehint", rewriter.getStringAttr("fifo_almost_full"));
216 }
217
218 if (auto almostEmpty = mem.getAlmostEmptyThreshold()) {
219 results.push_back(
220 comb::ICmpOp::create(rewriter, loc, comb::ICmpPredicate::ule, count,
221 hw::ConstantOp::create(rewriter, loc, countType,
222 almostEmpty.value())));
223 static_cast<Value>(results.back())
224 .getDefiningOp()
225 ->setAttr("sv.namehint", rewriter.getStringAttr("fifo_almost_empty"));
226 }
227
228 // ====== Protocol checks =====
229 Value clkI1 = seq::FromClockOp::create(rewriter, loc, clk);
230 Value notEmptyAndRden = comb::createOrFoldNot(
231 rewriter, loc,
232 comb::AndOp::create(rewriter, loc, adaptor.getRdEn(), fifoEmpty));
233 verif::ClockedAssertOp::create(
234 rewriter, loc, notEmptyAndRden, verif::ClockEdge::Pos, clkI1,
235 /*enable=*/Value(),
236 rewriter.getStringAttr("FIFO empty when read enabled"));
237 Value notFullAndWren = comb::createOrFoldNot(
238 rewriter, loc,
239 comb::AndOp::create(rewriter, loc, adaptor.getWrEn(), fifoFull));
240 verif::ClockedAssertOp::create(
241 rewriter, loc, notFullAndWren, verif::ClockEdge::Pos, clkI1,
242 /*enable=*/Value(),
243 rewriter.getStringAttr("FIFO full when write enabled"));
244
245 rewriter.replaceOp(mem, results);
246 return success();
247 }
248};
249
250struct LowerSeqFIFOPass
251 : public circt::seq::impl::LowerSeqFIFOBase<LowerSeqFIFOPass> {
252 void runOnOperation() override;
253};
254
255} // namespace
256
257void LowerSeqFIFOPass::runOnOperation() {
258 MLIRContext &ctxt = getContext();
259 ConversionTarget target(ctxt);
260
261 // Lowering patterns must lower away all HLMem-related operations.
262 target.addIllegalOp<seq::FIFOOp>();
263 target.addLegalDialect<seq::SeqDialect, hw::HWDialect, comb::CombDialect,
264 verif::VerifDialect>();
265 RewritePatternSet patterns(&ctxt);
266 patterns.add<FIFOLowering>(&ctxt);
267
268 if (failed(
269 applyPartialConversion(getOperation(), target, std::move(patterns))))
270 signalPassFailure();
271}
return success()
Backedge get(mlir::Type resultType, mlir::LocationAttr optionalLoc={})
Create a typed backedge.
void setValue(mlir::Value)
create(data_type, value)
Definition hw.py:433
create(cls, result_type, reset=None, reset_value=None, name=None, sym_name=None, **kwargs)
Definition seq.py:157
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition seq.py:1