CIRCT 24.0.0git
Loading...
Searching...
No Matches
HWMemSimImpl.cpp
Go to the documentation of this file.
1//===- HWMemSimImpl.cpp - HW Memory Implementation Pass -------------------===//
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//
9// This transformation pass converts generated FIRRTL memory modules to
10// simulation models.
11//
12//===----------------------------------------------------------------------===//
13
25#include "mlir/IR/ImplicitLocOpBuilder.h"
26#include "mlir/Pass/Pass.h"
27#include "llvm/ADT/TypeSwitch.h"
28#include "llvm/Support/Path.h"
29
30using namespace circt;
31using namespace hw;
32using namespace seq;
33
34namespace circt {
35namespace seq {
36#define GEN_PASS_DEF_HWMEMSIMIMPL
37#include "circt/Dialect/Seq/SeqPasses.h.inc"
38} // namespace seq
39} // namespace circt
40
41//===----------------------------------------------------------------------===//
42// HWMemSimImplPass Pass
43//===----------------------------------------------------------------------===//
44
45namespace {
46
47class HWMemSimImpl {
48 ReadEnableMode readEnableMode;
49 bool addMuxPragmas;
50 bool disableMemRandomization;
51 bool disableRegRandomization;
52 bool addVivadoRAMAddressConflictSynthesisBugWorkaround;
53
54 SmallVector<sv::VarOp> registers;
55
56 Value addPipelineStages(ImplicitLocOpBuilder &b,
57 hw::InnerSymbolNamespace &moduleNamespace,
58 size_t stages, Value clock, Value data,
59 const Twine &name, Value gate = {});
60 sv::AlwaysOp lastPipelineAlwaysOp;
61
62public:
63 Namespace &mlirModuleNamespace;
64
65 HWMemSimImpl(ReadEnableMode readEnableMode, bool addMuxPragmas,
66 bool disableMemRandomization, bool disableRegRandomization,
67 bool addVivadoRAMAddressConflictSynthesisBugWorkaround,
68 Namespace &mlirModuleNamespace)
69 : readEnableMode(readEnableMode), addMuxPragmas(addMuxPragmas),
70 disableMemRandomization(disableMemRandomization),
71 disableRegRandomization(disableRegRandomization),
72 addVivadoRAMAddressConflictSynthesisBugWorkaround(
73 addVivadoRAMAddressConflictSynthesisBugWorkaround),
74 mlirModuleNamespace(mlirModuleNamespace) {}
75
76 void generateMemory(HWModuleOp op, FirMemory mem);
77};
78
79struct HWMemSimImplPass : public impl::HWMemSimImplBase<HWMemSimImplPass> {
80 using HWMemSimImplBase::HWMemSimImplBase;
81
82 void runOnOperation() override;
83};
84
85} // end anonymous namespace
86
87/// A helper that returns true if a value definition (or block argument) is
88/// visible to another operation, either because it's a block argument or
89/// because the defining op is before that other op.
90static bool valueDefinedBeforeOp(Value value, Operation *op) {
91 Operation *valueOp = value.getDefiningOp();
92 Block *valueBlock =
93 valueOp ? valueOp->getBlock() : cast<BlockArgument>(value).getOwner();
94 while (op->getBlock() && op->getBlock() != valueBlock)
95 op = op->getParentOp();
96 return valueBlock == op->getBlock() &&
97 (!valueOp || valueOp->isBeforeInBlock(op));
98}
99
100//
101// Construct memory read annotated with mux pragmas in the following
102// form:
103// ```
104// wire GEN;
105// /* synopsys infer_mux_override */
106// assign GEN = memory[addr] /* cadence map_to_mux */;
107// ```
108// If `addMuxPragmas` is enabled, just return the read value without
109// annotations.
110static Value getMemoryRead(ImplicitLocOpBuilder &b, Value memory, Value addr,
111 bool addMuxPragmas) {
112 auto slot = sv::ReadInOutOp::create(
113 b, sv::ArrayIndexInOutOp::create(b, memory, addr));
114 // If we don't want to add mux pragmas, just return the read value.
115 if (!addMuxPragmas ||
116 cast<hw::UnpackedArrayType>(
117 cast<sv::VarType>(memory.getType()).getElementType())
118 .getNumElements() <= 1)
119 return slot;
121 slot, sv::SVAttributeAttr::get(b.getContext(), "cadence map_to_mux",
122 /*emitAsComment=*/true));
123 auto valWire = sv::WireOp::create(b, sv::NetType::get(slot.getType()));
124 auto assignOp = sv::AssignOp::create(b, valWire, slot);
125 sv::setSVAttributes(assignOp,
126 sv::SVAttributeAttr::get(b.getContext(),
127 "synopsys infer_mux_override",
128 /*emitAsComment=*/true));
129
130 return sv::ReadInOutOp::create(b, valWire);
131}
132
133Value HWMemSimImpl::addPipelineStages(ImplicitLocOpBuilder &b,
134 hw::InnerSymbolNamespace &moduleNamespace,
135 size_t stages, Value clock, Value data,
136 const Twine &name, Value gate) {
137 if (!stages)
138 return data;
139
140 // Try to reuse the previous always block. This is only possible if the clocks
141 // agree and the data and gate all dominate the always block.
142 auto alwaysOp = lastPipelineAlwaysOp;
143 if (alwaysOp) {
144 if (alwaysOp.getClocks() != ValueRange{clock} ||
145 !valueDefinedBeforeOp(data, alwaysOp) ||
146 (gate && !valueDefinedBeforeOp(gate, alwaysOp)))
147 alwaysOp = {};
148 }
149 if (!alwaysOp)
150 alwaysOp = sv::AlwaysOp::create(b, sv::EventControl::AtPosEdge, clock);
151
152 // Add the necessary registers.
153 auto savedIP = b.saveInsertionPoint();
154 SmallVector<sv::VarOp> regs;
155 b.setInsertionPoint(alwaysOp);
156 for (unsigned i = 0; i < stages; ++i) {
157 auto regName =
158 b.getStringAttr(moduleNamespace.newName("_" + name + "_d" + Twine(i)));
159 auto reg = sv::VarOp::create(b, data.getType(), regName,
160 hw::InnerSymAttr::get(regName));
161 regs.push_back(reg);
162 registers.push_back(reg);
163 }
164
165 // Populate the assignments in the always block.
166 b.setInsertionPointToEnd(alwaysOp.getBodyBlock());
167 for (unsigned i = 0; i < stages; ++i) {
168 if (i > 0)
169 data = sv::ReadInOutOp::create(b, data);
170 auto emitAssign = [&] { sv::PAssignOp::create(b, regs[i], data); };
171 if (gate)
172 sv::IfOp::create(b, gate, [&]() { emitAssign(); });
173 else
174 emitAssign();
175 data = regs[i];
176 gate = {};
177 }
178 b.restoreInsertionPoint(savedIP);
179 data = sv::ReadInOutOp::create(b, data);
180
181 lastPipelineAlwaysOp = alwaysOp;
182 return data;
183}
184
185void HWMemSimImpl::generateMemory(HWModuleOp op, FirMemory mem) {
186 ImplicitLocOpBuilder b(op.getLoc(), op.getBody());
187
188 InnerSymbolNamespace moduleNamespace(op);
189
190 // Compute total number of mask bits.
191 if (mem.maskGran == 0)
192 mem.maskGran = mem.dataWidth;
193 auto maskBits = mem.dataWidth / mem.maskGran;
194 bool isMasked = maskBits > 1;
195 // Each mask bit controls mask-granularity number of data bits.
196 auto dataType = b.getIntegerType(mem.dataWidth);
197
198 // Count the total number of ports.
199 unsigned numPorts =
200 mem.numReadPorts + mem.numWritePorts + mem.numReadWritePorts;
201
202 // Create registers for the memory.
203 sv::VarOp reg =
204 sv::VarOp::create(b, UnpackedArrayType::get(dataType, mem.depth),
205 b.getStringAttr("Memory"));
206
207 if (addVivadoRAMAddressConflictSynthesisBugWorkaround) {
208 if (mem.readLatency == 0) {
209 // If the read latency is zero, we regard the memory as write-first.
210 // We add a SV attribute to specify a ram style to use LUTs for Vivado
211 // to avoid a bug that miscompiles the write-first memory. See "RAM
212 // address conflict and Vivado synthesis bug" issue in the vivado forum
213 // for the more detail.
215 reg, sv::SVAttributeAttr::get(b.getContext(), "ram_style",
216 R"("distributed")",
217 /*emitAsComment=*/false));
218 } else if (mem.readLatency == 1 && numPorts > 1) {
219 // If the read address is registered and the RAM has multiple ports,
220 // force write-first behaviour by setting rw_addr_collision. This avoids
221 // unpredictable behaviour. Downstreams flows should watch for `VPL
222 // 8-6430`.
224 reg, sv::SVAttributeAttr::get(b.getContext(), "rw_addr_collision",
225 R"("yes")", /*emitAsComment=*/false));
226 }
227 }
228
229 SmallVector<Value, 4> outputs;
230
231 size_t inArg = 0;
232 for (size_t i = 0; i < mem.numReadPorts; ++i) {
233 Value addr = op.getBody().getArgument(inArg++);
234 Value en = op.getBody().getArgument(inArg++);
235 Value clock = op.getBody().getArgument(inArg++);
236 // Add pipeline stages
237 if (readEnableMode == ReadEnableMode::Ignore) {
238 for (size_t j = 0, e = mem.readLatency; j != e; ++j) {
239 auto enLast = en;
240 if (j < e - 1)
241 en = addPipelineStages(b, moduleNamespace, 1, clock, en,
242 "R" + Twine(i) + "_en");
243 addr = addPipelineStages(b, moduleNamespace, 1, clock, addr,
244 "R" + Twine(i) + "_addr", enLast);
245 }
246 } else {
247 en = addPipelineStages(b, moduleNamespace, mem.readLatency, clock, en,
248 "R" + Twine(i) + "_en");
249 addr = addPipelineStages(b, moduleNamespace, mem.readLatency, clock, addr,
250 "R" + Twine(i) + "_addr");
251 }
252
253 // Read Logic
254 Value rdata = getMemoryRead(b, reg, addr, addMuxPragmas);
255 switch (readEnableMode) {
256 case ReadEnableMode::Undefined: {
257 Value x = sv::ConstantXOp::create(b, rdata.getType());
258 rdata = comb::MuxOp::create(b, en, rdata, x, false);
259 break;
260 }
261 case ReadEnableMode::Zero: {
262 Value x = hw::ConstantOp::create(b, rdata.getType(), 0);
263 rdata = comb::MuxOp::create(b, en, rdata, x, false);
264 break;
265 }
266 case ReadEnableMode::Ignore:
267 break;
268 }
269 outputs.push_back(rdata);
270 }
271
272 for (size_t i = 0; i < mem.numReadWritePorts; ++i) {
273 auto numReadStages = mem.readLatency;
274 auto numWriteStages = mem.writeLatency - 1;
275 auto numCommonStages = std::min(numReadStages, numWriteStages);
276 Value addr = op.getBody().getArgument(inArg++);
277 Value en = op.getBody().getArgument(inArg++);
278 Value clock = op.getBody().getArgument(inArg++);
279 Value wmode = op.getBody().getArgument(inArg++);
280 Value wdataIn = op.getBody().getArgument(inArg++);
281 Value wmaskBits;
282 // There are no input mask ports, if maskBits =1. Create a dummy true value
283 // for mask.
284 if (isMasked)
285 wmaskBits = op.getBody().getArgument(inArg++);
286 else
287 wmaskBits = ConstantOp::create(b, b.getIntegerAttr(en.getType(), 1));
288
289 // Add common pipeline stages.
290 addr = addPipelineStages(b, moduleNamespace, numCommonStages, clock, addr,
291 "RW" + Twine(i) + "_addr");
292 en = addPipelineStages(b, moduleNamespace, numCommonStages, clock, en,
293 "RW" + Twine(i) + "_en");
294 wmode = addPipelineStages(b, moduleNamespace, numCommonStages, clock, wmode,
295 "RW" + Twine(i) + "_mode");
296
297 // Add read-only pipeline stages.
298 Value readAddr = addr;
299 Value readEn = en;
300 if (readEnableMode == ReadEnableMode::Ignore) {
301 for (size_t j = 0, e = mem.readLatency; j != e; ++j) {
302 auto enLast = en;
303 if (j < e - 1)
304 readEn = addPipelineStages(b, moduleNamespace, 1, clock, en,
305 "RW" + Twine(i) + "_ren");
306 readAddr = addPipelineStages(b, moduleNamespace, 1, clock, addr,
307 "RW" + Twine(i) + "_raddr", enLast);
308 }
309 } else {
310 readAddr =
311 addPipelineStages(b, moduleNamespace, numReadStages - numCommonStages,
312 clock, addr, "RW" + Twine(i) + "_raddr");
313 readEn =
314 addPipelineStages(b, moduleNamespace, numReadStages - numCommonStages,
315 clock, en, "RW" + Twine(i) + "_ren");
316 }
317 auto readWMode =
318 addPipelineStages(b, moduleNamespace, numReadStages - numCommonStages,
319 clock, wmode, "RW" + Twine(i) + "_rmode");
320
321 // Add write-only pipeline stages.
322 auto writeAddr =
323 addPipelineStages(b, moduleNamespace, numWriteStages - numCommonStages,
324 clock, addr, "RW" + Twine(i) + "_waddr");
325 auto writeEn =
326 addPipelineStages(b, moduleNamespace, numWriteStages - numCommonStages,
327 clock, en, "RW" + Twine(i) + "_wen");
328 auto writeWMode =
329 addPipelineStages(b, moduleNamespace, numWriteStages - numCommonStages,
330 clock, wmode, "RW" + Twine(i) + "_wmode");
331 wdataIn = addPipelineStages(b, moduleNamespace, numWriteStages, clock,
332 wdataIn, "RW" + Twine(i) + "_wdata");
333 if (isMasked)
334 wmaskBits = addPipelineStages(b, moduleNamespace, numWriteStages, clock,
335 wmaskBits, "RW" + Twine(i) + "_wmask");
336
337 SmallVector<Value, 4> maskValues(maskBits);
338 SmallVector<Value, 4> dataValues(maskBits);
339 // For multi-bit mask, extract corresponding write data bits of
340 // mask-granularity size each. Each of the extracted data bits will be
341 // written to a register, gaurded by the corresponding mask bit.
342 for (size_t i = 0; i < maskBits; ++i) {
343 maskValues[i] = b.createOrFold<comb::ExtractOp>(wmaskBits, i, 1);
344 dataValues[i] = b.createOrFold<comb::ExtractOp>(wdataIn, i * mem.maskGran,
345 mem.maskGran);
346 }
347
348 // wire to store read result
349 auto rWire = sv::WireOp::create(b, sv::NetType::get(wdataIn.getType()));
350 Value rdata = sv::ReadInOutOp::create(b, rWire);
351
352 // Read logic.
353 Value rcond = b.createOrFold<comb::AndOp>(
354 readEn,
355 b.createOrFold<comb::ICmpOp>(
356 comb::ICmpPredicate::eq, readWMode,
357 b.createOrFold<ConstantOp>(readWMode.getType(), 0), false),
358 false);
359
360 auto val = getMemoryRead(b, reg, readAddr, addMuxPragmas);
361
362 switch (readEnableMode) {
363 case ReadEnableMode::Undefined: {
364 Value x = sv::ConstantXOp::create(b, val.getType());
365 val = comb::MuxOp::create(b, rcond, val, x, false);
366 break;
367 }
368 case ReadEnableMode::Zero: {
369 Value x = hw::ConstantOp::create(b, val.getType(), 0);
370 val = comb::MuxOp::create(b, rcond, val, x, false);
371 break;
372 }
373 case ReadEnableMode::Ignore:
374 break;
375 }
376 sv::AssignOp::create(b, rWire, val);
377
378 // Write logic gaurded by the corresponding mask bit.
379 for (auto wmask : llvm::enumerate(maskValues)) {
380 sv::AlwaysOp::create(b, sv::EventControl::AtPosEdge, clock, [&]() {
381 auto wcond = b.createOrFold<comb::AndOp>(
382 writeEn,
383 b.createOrFold<comb::AndOp>(wmask.value(), writeWMode, false),
384 false);
385 sv::IfOp::create(b, wcond, [&]() {
386 Value slotReg = sv::ArrayIndexInOutOp::create(b, reg, writeAddr);
387 sv::PAssignOp::create(
388 b,
389 b.createOrFold<sv::IndexedPartSelectInOutOp>(
390 slotReg,
391 b.createOrFold<ConstantOp>(b.getIntegerType(32),
392 wmask.index() * mem.maskGran),
393 mem.maskGran),
394 dataValues[wmask.index()]);
395 });
396 });
397 }
398 outputs.push_back(rdata);
399 }
400
401 DenseMap<unsigned, Operation *> writeProcesses;
402 for (size_t i = 0; i < mem.numWritePorts; ++i) {
403 auto numStages = mem.writeLatency - 1;
404 Value addr = op.getBody().getArgument(inArg++);
405 Value en = op.getBody().getArgument(inArg++);
406 Value clock = op.getBody().getArgument(inArg++);
407 Value wdataIn = op.getBody().getArgument(inArg++);
408 Value wmaskBits;
409 // There are no input mask ports, if maskBits =1. Create a dummy true value
410 // for mask.
411 if (isMasked)
412 wmaskBits = op.getBody().getArgument(inArg++);
413 else
414 wmaskBits = ConstantOp::create(b, b.getIntegerAttr(en.getType(), 1));
415 // Add pipeline stages
416 addr = addPipelineStages(b, moduleNamespace, numStages, clock, addr,
417 "W" + Twine(i) + "addr");
418 en = addPipelineStages(b, moduleNamespace, numStages, clock, en,
419 "W" + Twine(i) + "en");
420 wdataIn = addPipelineStages(b, moduleNamespace, numStages, clock, wdataIn,
421 "W" + Twine(i) + "data");
422 if (isMasked)
423 wmaskBits = addPipelineStages(b, moduleNamespace, numStages, clock,
424 wmaskBits, "W" + Twine(i) + "mask");
425
426 SmallVector<Value, 4> maskValues(maskBits);
427 SmallVector<Value, 4> dataValues(maskBits);
428 // For multi-bit mask, extract corresponding write data bits of
429 // mask-granularity size each. Each of the extracted data bits will be
430 // written to a register, gaurded by the corresponding mask bit.
431 for (size_t i = 0; i < maskBits; ++i) {
432 maskValues[i] = b.createOrFold<comb::ExtractOp>(wmaskBits, i, 1);
433 dataValues[i] = b.createOrFold<comb::ExtractOp>(wdataIn, i * mem.maskGran,
434 mem.maskGran);
435 }
436 // Build write port logic.
437 auto writeLogic = [&] {
438 // For each register, create the connections to write the corresponding
439 // data into it.
440 for (auto wmask : llvm::enumerate(maskValues)) {
441 // Guard by corresponding mask bit.
442 auto wcond = b.createOrFold<comb::AndOp>(en, wmask.value(), false);
443 sv::IfOp::create(b, wcond, [&]() {
444 auto slot = sv::ArrayIndexInOutOp::create(b, reg, addr);
445 sv::PAssignOp::create(
446 b,
447 b.createOrFold<sv::IndexedPartSelectInOutOp>(
448 slot,
449 b.createOrFold<ConstantOp>(b.getIntegerType(32),
450 wmask.index() * mem.maskGran),
451 mem.maskGran),
452 dataValues[wmask.index()]);
453 });
454 }
455 };
456
457 // Build a new always block with write port logic.
458 auto alwaysBlock = [&] {
459 return sv::AlwaysOp::create(b, sv::EventControl::AtPosEdge, clock,
460 [&]() { writeLogic(); });
461 };
462
463 switch (mem.writeUnderWrite) {
464 // Undefined write order: lower each write port into a separate always
465 // block.
466 case seq::WUW::Undefined:
467 alwaysBlock();
468 break;
469 // Port-ordered write order: lower each write port into an always block
470 // based on its clock ID.
471 case seq::WUW::PortOrder:
472 if (auto *existingAlwaysBlock =
473 writeProcesses.lookup(mem.writeClockIDs[i])) {
474 OpBuilder::InsertionGuard guard(b);
475 b.setInsertionPointToEnd(
476 cast<sv::AlwaysOp>(existingAlwaysBlock).getBodyBlock());
477 writeLogic();
478 } else {
479 writeProcesses[i] = alwaysBlock();
480 }
481 }
482 }
483
484 auto *outputOp = op.getBodyBlock()->getTerminator();
485 outputOp->setOperands(outputs);
486
487 // Add logic to initialize the memory based on a file emission request. This
488 // disables randomization.
489 if (!mem.initFilename.empty()) {
490 // Set an inner symbol on the register if one does not exist.
491 if (!reg.getInnerSymAttr())
492 reg.setInnerSymAttr(hw::InnerSymAttr::get(
493 b.getStringAttr(moduleNamespace.newName(reg.getName()))));
494
495 if (mem.initIsInline) {
496 sv::IfDefOp::create(b, "ENABLE_INITIAL_MEM_", [&]() {
497 sv::InitialOp::create(b, [&]() {
498 sv::ReadMemOp::create(b, reg, mem.initFilename,
499 mem.initIsBinary ? MemBaseTypeAttr::MemBaseBin
500 : MemBaseTypeAttr::MemBaseHex);
501 });
502 });
503 } else {
504 OpBuilder::InsertionGuard guard(b);
505
506 // Assign a name to the bound module.
507 StringAttr boundModuleName =
508 b.getStringAttr(mlirModuleNamespace.newName(op.getName() + "_init"));
509
510 // Generate a name for the file containing the bound module and the bind.
511 StringAttr filename;
512 if (auto fileAttr = op->getAttrOfType<OutputFileAttr>("output_file")) {
513 if (!fileAttr.isDirectory()) {
514 SmallString<128> path(fileAttr.getFilename().getValue());
515 llvm::sys::path::remove_filename(path);
516 llvm::sys::path::append(path, boundModuleName.getValue() + ".sv");
517 filename = b.getStringAttr(path);
518 } else {
519 filename = fileAttr.getFilename();
520 }
521 } else {
522 filename = b.getStringAttr(boundModuleName.getValue() + ".sv");
523 }
524
525 // Create a new module with the readmem op.
526 b.setInsertionPointAfter(op);
527 auto boundModule =
528 HWModuleOp::create(b, boundModuleName, ArrayRef<PortInfo>());
529
530 // Build the hierpathop
531 auto path = hw::HierPathOp::create(
532 b, mlirModuleNamespace.newName(op.getName() + "_path"),
533 /*sym_visibility=*/{},
534 b.getArrayAttr(
535 ::InnerRefAttr::get(op.getNameAttr(), reg.getInnerNameAttr())));
536
537 b.setInsertionPointToStart(boundModule.getBodyBlock());
538 sv::InitialOp::create(b, [&]() {
539 auto xmr =
540 sv::XMRRefOp::create(b, reg.getType(), path.getSymNameAttr());
541 sv::ReadMemOp::create(b, xmr, mem.initFilename,
542 mem.initIsBinary ? MemBaseTypeAttr::MemBaseBin
543 : MemBaseTypeAttr::MemBaseHex);
544 });
545
546 // Instantiate this new module inside the memory module.
547 b.setInsertionPointAfter(reg);
548 auto boundInstance = hw::InstanceOp::create(
549 b, boundModule, boundModule.getName(), ArrayRef<Value>());
550 boundInstance->setAttr(
551 "inner_sym",
552 hw::InnerSymAttr::get(b.getStringAttr(
553 moduleNamespace.newName(boundInstance.getInstanceName()))));
554 boundInstance.setDoNotPrintAttr(b.getUnitAttr());
555
556 // Build the file container and reference the module from it.
557 b.setInsertionPointAfter(op);
558 emit::FileOp::create(b, filename, [&] {
559 emit::RefOp::create(b, FlatSymbolRefAttr::get(boundModuleName));
560 sv::BindOp::create(b,
561 hw::InnerRefAttr::get(
562 op.getNameAttr(),
563 boundInstance.getInnerSymAttr().getSymName()));
564 });
565 }
566 }
567
568 // Add logic to initialize the memory and any internal registers to random
569 // values.
570 if (disableMemRandomization && disableRegRandomization)
571 return;
572
573 constexpr unsigned randomWidth = 32;
574 sv::IfDefOp::create(b, "ENABLE_INITIAL_MEM_", [&]() {
575 sv::VarOp randReg;
576 SmallVector<sv::VarOp> randRegs;
577 if (!disableRegRandomization) {
578 sv::IfDefOp::create(b, "RANDOMIZE_REG_INIT", [&]() {
579 signed totalWidth = 0;
580 for (sv::VarOp &reg : registers)
581 totalWidth += reg.getElementType().getIntOrFloatBitWidth();
582 while (totalWidth > 0) {
583 auto name = b.getStringAttr(moduleNamespace.newName("_RANDOM"));
584 auto innerSym = hw::InnerSymAttr::get(name);
585 randRegs.push_back(sv::VarOp::create(b, b.getIntegerType(randomWidth),
586 name, innerSym));
587 totalWidth -= randomWidth;
588 }
589 });
590 }
591 auto randomMemReg = sv::VarOp::create(
592 b,
593 b.getIntegerType(llvm::divideCeil(mem.dataWidth, randomWidth) *
594 randomWidth),
595 b.getStringAttr("_RANDOM_MEM"));
596 sv::InitialOp::create(b, [&]() {
597 sv::VerbatimOp::create(b, "`INIT_RANDOM_PROLOG_");
598
599 // Memory randomization logic. The entire memory is randomized.
600 if (!disableMemRandomization) {
601 sv::IfDefProceduralOp::create(b, "RANDOMIZE_MEM_INIT", [&]() {
602 auto outerLoopIndVarType =
603 b.getIntegerType(llvm::Log2_64_Ceil(mem.depth + 1));
604 auto innerUpperBoundWidth =
605 cast<IntegerType>(randomMemReg.getType().getElementType())
606 .getWidth();
607 auto innerLoopIndVarType =
608 b.getIntegerType(llvm::Log2_64_Ceil(innerUpperBoundWidth + 1));
609 // Construct the following nested for loops:
610 // ```
611 // for (int i = 0; i < mem.depth; i++) begin
612 // for (int j = 0; j < randomMeg.size; j += 32)
613 // randomMem[j[mem.width-1: +: 32] = `RANDOM
614 // Memory[i] = randomMem[mem.dataWidth - 1: 0];
615 // ```
616 sv::ForOp::create(
617 b, 0, mem.depth, 1, outerLoopIndVarType, "i",
618 [&](BlockArgument outerIndVar) {
619 sv::ForOp::create(
620 b, 0, innerUpperBoundWidth, randomWidth,
621 innerLoopIndVarType, "j", [&](BlockArgument innerIndVar) {
622 auto rhs = sv::MacroRefExprSEOp::create(
623 b, b.getIntegerType(randomWidth), "RANDOM");
624 Value truncInnerIndVar;
625 if (mem.dataWidth <= 1)
626 truncInnerIndVar =
627 hw::ConstantOp::create(b, b.getI1Type(), 0);
628 else
629 truncInnerIndVar = b.createOrFold<comb::ExtractOp>(
630 innerIndVar, 0, llvm::Log2_64_Ceil(mem.dataWidth));
631 auto lhs = sv::IndexedPartSelectInOutOp::create(
632 b, randomMemReg, truncInnerIndVar, randomWidth,
633 false);
634 sv::BPAssignOp::create(b, lhs, rhs);
635 });
636
637 Value iterValue = outerIndVar;
638 // Truncate the induction variable if necessary.
639 if (!outerIndVar.getType().isInteger(
640 llvm::Log2_64_Ceil(mem.depth)))
641 iterValue = b.createOrFold<comb::ExtractOp>(
642 iterValue, 0, llvm::Log2_64_Ceil(mem.depth));
643 auto lhs = sv::ArrayIndexInOutOp::create(b, reg, iterValue);
644 auto rhs = b.createOrFold<comb::ExtractOp>(
645 sv::ReadInOutOp::create(b, randomMemReg), 0, mem.dataWidth);
646 sv::BPAssignOp::create(b, lhs, rhs);
647 });
648 });
649 }
650
651 // Register randomization logic. Randomize every register to a random
652 // making efficient use of available randomization registers.
653 //
654 // TODO: This shares a lot of common logic with LowerToHW. Combine
655 // these two in a common randomization utility.
656 if (!disableRegRandomization) {
657 sv::IfDefProceduralOp::create(b, "RANDOMIZE_REG_INIT", [&]() {
658 unsigned bits = randomWidth;
659 for (sv::VarOp &reg : randRegs)
660 sv::VerbatimOp::create(
661 b, b.getStringAttr("{{0}} = {`RANDOM};"), ValueRange{},
662 b.getArrayAttr(hw::InnerRefAttr::get(op.getNameAttr(),
663 reg.getInnerNameAttr())));
664 auto randRegIdx = 0;
665 for (sv::VarOp &reg : registers) {
666 SmallVector<std::pair<Attribute, std::pair<size_t, size_t>>> values;
667 auto width = reg.getElementType().getIntOrFloatBitWidth();
668 auto widthRemaining = width;
669 while (widthRemaining > 0) {
670 if (bits == randomWidth) {
671 randReg = randRegs[randRegIdx++];
672 bits = 0;
673 }
674 auto innerRef = hw::InnerRefAttr::get(op.getNameAttr(),
675 randReg.getInnerNameAttr());
676 if (widthRemaining <= randomWidth - bits) {
677 values.push_back({innerRef, {bits + widthRemaining - 1, bits}});
678 bits += widthRemaining;
679 widthRemaining = 0;
680 continue;
681 }
682 values.push_back({innerRef, {randomWidth - 1, bits}});
683 widthRemaining -= (randomWidth - bits);
684 bits = randomWidth;
685 }
686 SmallString<32> rhs("{{0}} = ");
687 unsigned idx = 1;
688 assert(reg.getInnerSymAttr());
689 SmallVector<Attribute, 4> symbols({hw::InnerRefAttr::get(
690 op.getNameAttr(), reg.getInnerNameAttr())});
691 if (values.size() > 1)
692 rhs.append("{");
693 for (auto &v : values) {
694 if (idx > 1)
695 rhs.append(", ");
696 auto [sym, range] = v;
697 symbols.push_back(sym);
698 rhs.append(("{{" + Twine(idx++) + "}}").str());
699 // Do not emit a part select as the whole value is used.
700 if (range.first == randomWidth - 1 && range.second == 0)
701 continue;
702 // Emit a single bit part select, e.g., "[3]"
703 if (range.first == range.second) {
704 rhs.append(("[" + Twine(range.first) + "]").str());
705 continue;
706 }
707 // Emit a part select, e.g., "[4:2]"
708 rhs.append(
709 ("[" + Twine(range.first) + ":" + Twine(range.second) + "]")
710 .str());
711 }
712 if (values.size() > 1)
713 rhs.append("}");
714 rhs.append(";");
715 sv::VerbatimOp::create(b, rhs, ValueRange{},
716 b.getArrayAttr(symbols));
717 }
718 });
719 }
720 });
721 });
722}
723
724void HWMemSimImplPass::runOnOperation() {
725 auto topModule = getOperation();
726
727 // Populate a namespace from the symbols visible to the top-level MLIR module.
728 // Memories with initializations create modules and these need to be legal
729 // symbols.
730 SymbolCache symbolCache;
731 symbolCache.addDefinitions(topModule);
732 Namespace mlirModuleNamespace;
733 mlirModuleNamespace.add(symbolCache);
734
735 SmallVector<HWModuleGeneratedOp> toErase;
736 bool anythingChanged = false;
737
738 for (auto op :
739 llvm::make_early_inc_range(topModule.getOps<HWModuleGeneratedOp>())) {
740 auto oldModule = cast<HWModuleGeneratedOp>(op);
741 auto gen = oldModule.getGeneratorKind();
742 auto genOp = cast<HWGeneratorSchemaOp>(
743 SymbolTable::lookupSymbolIn(getOperation(), gen));
744
745 if (genOp.getDescriptor() == "FIRRTL_Memory") {
746 FirMemory mem(oldModule);
747
748 OpBuilder builder(oldModule);
749 auto nameAttr = builder.getStringAttr(oldModule.getName());
750
751 // The requirements for macro replacement:
752 // 1. read latency and write latency of at least one.
753 // 2. undefined read-under-write behavior.
754 if (replSeqMem && ((mem.readLatency >= 1 && mem.writeLatency >= 1) &&
755 mem.dataWidth > 0)) {
756 HWModuleExternOp::create(builder, oldModule.getLoc(), nameAttr,
757 oldModule.getPortList());
758 } else {
759 auto newModule = HWModuleOp::create(builder, oldModule.getLoc(),
760 nameAttr, oldModule.getPortList());
761 if (auto outdir = oldModule->getAttr("output_file"))
762 newModule->setAttr("output_file", outdir);
763 newModule.setCommentAttr(
764 builder.getStringAttr("VCS coverage exclude_file"));
765 newModule.setPrivate();
766
767 HWMemSimImpl(readEnableMode, addMuxPragmas, disableMemRandomization,
768 disableRegRandomization,
769 addVivadoRAMAddressConflictSynthesisBugWorkaround,
770 mlirModuleNamespace)
771 .generateMemory(newModule, mem);
772 if (auto fragments = oldModule->getAttr(emit::getFragmentsAttrName()))
773 newModule->setAttr(emit::getFragmentsAttrName(), fragments);
774 }
775
776 oldModule.erase();
777 anythingChanged = true;
778 }
779 }
780
781 if (!anythingChanged)
782 markAllAnalysesPreserved();
783}
assert(baseType &&"element must be base type")
static bool valueDefinedBeforeOp(Value value, Operation *op)
A helper that returns true if a value definition (or block argument) is visible to another operation,...
static Value getMemoryRead(ImplicitLocOpBuilder &b, Value memory, Value addr, bool addMuxPragmas)
static Block * getBodyBlock(FModuleLike mod)
void add(mlir::ModuleOp module)
Definition Namespace.h:48
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:86
void addDefinitions(mlir::Operation *top)
Populate the symbol cache with all symbol-defining operations within the 'top' operation.
Definition SymCache.cpp:23
create(data_type, value)
Definition hw.py:433
create(dest, src)
Definition sv.py:135
create(value)
Definition sv.py:151
create(data_type, name=None, sym_name=None)
Definition sv.py:125
create(data_type, name=None, sym_name=None)
Definition sv.py:63
void setSVAttributes(mlir::Operation *op, mlir::ArrayAttr attrs)
Set the SV attributes of an operation.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition hw.py:1
Definition seq.py:1
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition seq.py:21