23 #include "mlir/IR/ImplicitLocOpBuilder.h"
24 #include "llvm/ADT/TypeSwitch.h"
25 #include "llvm/Support/Path.h"
27 using namespace circt;
33 #define GEN_PASS_DEF_HWMEMSIMIMPL
34 #include "circt/Dialect/Seq/SeqPasses.h.inc"
45 bool ignoreReadEnable;
47 bool disableMemRandomization;
48 bool disableRegRandomization;
49 bool addVivadoRAMAddressConflictSynthesisBugWorkaround;
51 SmallVector<sv::RegOp> registers;
53 Value addPipelineStages(ImplicitLocOpBuilder &b,
54 hw::InnerSymbolNamespace &moduleNamespace,
55 size_t stages, Value clock, Value
data,
56 const Twine &name, Value gate = {});
57 sv::AlwaysOp lastPipelineAlwaysOp;
62 HWMemSimImpl(
bool ignoreReadEnable,
bool addMuxPragmas,
63 bool disableMemRandomization,
bool disableRegRandomization,
64 bool addVivadoRAMAddressConflictSynthesisBugWorkaround,
66 : ignoreReadEnable(ignoreReadEnable), addMuxPragmas(addMuxPragmas),
67 disableMemRandomization(disableMemRandomization),
68 disableRegRandomization(disableRegRandomization),
69 addVivadoRAMAddressConflictSynthesisBugWorkaround(
70 addVivadoRAMAddressConflictSynthesisBugWorkaround),
71 mlirModuleNamespace(mlirModuleNamespace) {}
73 void generateMemory(
HWModuleOp op, FirMemory mem);
76 struct HWMemSimImplPass :
public impl::HWMemSimImplBase<HWMemSimImplPass> {
77 using HWMemSimImplBase::HWMemSimImplBase;
79 void runOnOperation()
override;
88 Operation *valueOp =
value.getDefiningOp();
90 valueOp ? valueOp->getBlock() :
value.cast<BlockArgument>().getOwner();
91 while (op->getBlock() && op->getBlock() != valueBlock)
92 op = op->getParentOp();
93 return valueBlock == op->getBlock() &&
94 (!valueOp || valueOp->isBeforeInBlock(op));
107 static Value
getMemoryRead(ImplicitLocOpBuilder &b, Value memory, Value addr,
108 bool addMuxPragmas) {
110 b.create<
sv::ReadInOutOp>(b.create<sv::ArrayIndexInOutOp>(memory, addr));
112 if (!addMuxPragmas || memory.getType()
115 .cast<hw::UnpackedArrayType>()
116 .getNumElements() <= 1)
121 auto valWire = b.create<
sv::WireOp>(slot.getType());
125 "synopsys infer_mux_override",
131 Value HWMemSimImpl::addPipelineStages(ImplicitLocOpBuilder &b,
132 hw::InnerSymbolNamespace &moduleNamespace,
133 size_t stages, Value clock, Value data,
134 const Twine &name, Value gate) {
140 auto alwaysOp = lastPipelineAlwaysOp;
142 if (alwaysOp.getClocks() != ValueRange{clock} ||
148 alwaysOp = b.create<sv::AlwaysOp>(sv::EventControl::AtPosEdge, clock);
151 auto savedIP = b.saveInsertionPoint();
152 SmallVector<sv::RegOp> regs;
153 b.setInsertionPoint(alwaysOp);
154 for (
unsigned i = 0; i < stages; ++i) {
156 b.getStringAttr(moduleNamespace.newName(
"_" + name +
"_d" + Twine(i)));
160 registers.push_back(
reg);
164 b.setInsertionPointToEnd(alwaysOp.getBodyBlock());
165 for (
unsigned i = 0; i < stages; ++i) {
168 auto emitAssign = [&] { b.
create<sv::PAssignOp>(regs[i],
data); };
170 b.create<sv::IfOp>(gate, [&]() { emitAssign(); });
176 b.restoreInsertionPoint(savedIP);
179 lastPipelineAlwaysOp = alwaysOp;
183 void HWMemSimImpl::generateMemory(
HWModuleOp op, FirMemory mem) {
184 ImplicitLocOpBuilder b(op.getLoc(), op.getBody());
186 InnerSymbolNamespace moduleNamespace(op);
189 if (mem.maskGran == 0)
190 mem.maskGran = mem.dataWidth;
191 auto maskBits = mem.dataWidth / mem.maskGran;
192 bool isMasked = maskBits > 1;
194 auto dataType = b.getIntegerType(mem.dataWidth);
198 mem.numReadPorts + mem.numWritePorts + mem.numReadWritePorts;
204 if (addVivadoRAMAddressConflictSynthesisBugWorkaround) {
205 if (mem.readLatency == 0) {
215 }
else if (mem.readLatency == 1 && numPorts > 1) {
229 for (
size_t i = 0; i < mem.numReadPorts; ++i) {
230 Value
addr = op.getBody().getArgument(inArg++);
231 Value
en = op.getBody().getArgument(inArg++);
232 Value clock = op.getBody().getArgument(inArg++);
234 if (ignoreReadEnable) {
235 for (
size_t j = 0, e = mem.readLatency; j != e; ++j) {
238 en = addPipelineStages(b, moduleNamespace, 1, clock, en,
239 "R" + Twine(i) +
"_en");
240 addr = addPipelineStages(b, moduleNamespace, 1, clock, addr,
241 "R" + Twine(i) +
"_addr", enLast);
244 en = addPipelineStages(b, moduleNamespace, mem.readLatency, clock, en,
245 "R" + Twine(i) +
"_en");
246 addr = addPipelineStages(b, moduleNamespace, mem.readLatency, clock, addr,
247 "R" + Twine(i) +
"_addr");
252 if (!ignoreReadEnable) {
253 Value x = b.create<sv::ConstantXOp>(
rdata.getType());
259 for (
size_t i = 0; i < mem.numReadWritePorts; ++i) {
260 auto numReadStages = mem.readLatency;
261 auto numWriteStages = mem.writeLatency - 1;
262 auto numCommonStages = std::min(numReadStages, numWriteStages);
263 Value
addr = op.getBody().getArgument(inArg++);
264 Value
en = op.getBody().getArgument(inArg++);
265 Value clock = op.getBody().getArgument(inArg++);
266 Value
wmode = op.getBody().getArgument(inArg++);
267 Value wdataIn = op.getBody().getArgument(inArg++);
272 wmaskBits = op.getBody().getArgument(inArg++);
274 wmaskBits = b.create<
ConstantOp>(b.getIntegerAttr(
en.getType(), 1));
277 addr = addPipelineStages(b, moduleNamespace, numCommonStages, clock, addr,
278 "RW" + Twine(i) +
"_addr");
279 en = addPipelineStages(b, moduleNamespace, numCommonStages, clock, en,
280 "RW" + Twine(i) +
"_en");
281 wmode = addPipelineStages(b, moduleNamespace, numCommonStages, clock, wmode,
282 "RW" + Twine(i) +
"_mode");
285 Value readAddr =
addr;
287 if (ignoreReadEnable) {
288 for (
size_t j = 0, e = mem.readLatency; j != e; ++j) {
291 readEn = addPipelineStages(b, moduleNamespace, 1, clock, en,
292 "RW" + Twine(i) +
"_ren");
293 readAddr = addPipelineStages(b, moduleNamespace, 1, clock, addr,
294 "RW" + Twine(i) +
"_raddr", enLast);
298 addPipelineStages(b, moduleNamespace, numReadStages - numCommonStages,
299 clock, addr,
"RW" + Twine(i) +
"_raddr");
301 addPipelineStages(b, moduleNamespace, numReadStages - numCommonStages,
302 clock, en,
"RW" + Twine(i) +
"_ren");
305 addPipelineStages(b, moduleNamespace, numReadStages - numCommonStages,
306 clock, wmode,
"RW" + Twine(i) +
"_rmode");
310 addPipelineStages(b, moduleNamespace, numWriteStages - numCommonStages,
311 clock, addr,
"RW" + Twine(i) +
"_waddr");
313 addPipelineStages(b, moduleNamespace, numWriteStages - numCommonStages,
314 clock, en,
"RW" + Twine(i) +
"_wen");
316 addPipelineStages(b, moduleNamespace, numWriteStages - numCommonStages,
317 clock, wmode,
"RW" + Twine(i) +
"_wmode");
318 wdataIn = addPipelineStages(b, moduleNamespace, numWriteStages, clock,
319 wdataIn,
"RW" + Twine(i) +
"_wdata");
321 wmaskBits = addPipelineStages(b, moduleNamespace, numWriteStages, clock,
322 wmaskBits,
"RW" + Twine(i) +
"_wmask");
324 SmallVector<Value, 4> maskValues(maskBits);
325 SmallVector<Value, 4> dataValues(maskBits);
329 for (
size_t i = 0; i < maskBits; ++i) {
331 dataValues[i] = b.createOrFold<
comb::ExtractOp>(wdataIn, i * mem.maskGran,
342 b.createOrFold<comb::ICmpOp>(
343 comb::ICmpPredicate::eq, readWMode,
344 b.createOrFold<
ConstantOp>(readWMode.getType(), 0),
false),
348 if (!ignoreReadEnable) {
349 Value x = b.create<sv::ConstantXOp>(val.getType());
355 for (
auto wmask : llvm::enumerate(maskValues)) {
356 b.
create<sv::AlwaysOp>(sv::EventControl::AtPosEdge, clock, [&]() {
361 b.create<sv::IfOp>(wcond, [&]() {
362 Value slotReg = b.create<sv::ArrayIndexInOutOp>(
reg, writeAddr);
363 b.create<sv::PAssignOp>(
364 b.createOrFold<sv::IndexedPartSelectInOutOp>(
366 b.createOrFold<
ConstantOp>(b.getIntegerType(32),
367 wmask.index() * mem.maskGran),
369 dataValues[
wmask.index()]);
376 DenseMap<unsigned, Operation *> writeProcesses;
377 for (
size_t i = 0; i < mem.numWritePorts; ++i) {
378 auto numStages = mem.writeLatency - 1;
379 Value
addr = op.getBody().getArgument(inArg++);
380 Value
en = op.getBody().getArgument(inArg++);
381 Value clock = op.getBody().getArgument(inArg++);
382 Value wdataIn = op.getBody().getArgument(inArg++);
387 wmaskBits = op.getBody().getArgument(inArg++);
389 wmaskBits = b.create<
ConstantOp>(b.getIntegerAttr(
en.getType(), 1));
391 addr = addPipelineStages(b, moduleNamespace, numStages, clock, addr,
392 "W" + Twine(i) +
"addr");
393 en = addPipelineStages(b, moduleNamespace, numStages, clock, en,
394 "W" + Twine(i) +
"en");
395 wdataIn = addPipelineStages(b, moduleNamespace, numStages, clock, wdataIn,
396 "W" + Twine(i) +
"data");
398 wmaskBits = addPipelineStages(b, moduleNamespace, numStages, clock,
399 wmaskBits,
"W" + Twine(i) +
"mask");
401 SmallVector<Value, 4> maskValues(maskBits);
402 SmallVector<Value, 4> dataValues(maskBits);
406 for (
size_t i = 0; i < maskBits; ++i) {
408 dataValues[i] = b.createOrFold<
comb::ExtractOp>(wdataIn, i * mem.maskGran,
412 auto writeLogic = [&] {
415 for (
auto wmask : llvm::enumerate(maskValues)) {
418 b.create<sv::IfOp>(wcond, [&]() {
419 auto slot = b.create<sv::ArrayIndexInOutOp>(
reg,
addr);
420 b.create<sv::PAssignOp>(
421 b.createOrFold<sv::IndexedPartSelectInOutOp>(
423 b.createOrFold<
ConstantOp>(b.getIntegerType(32),
424 wmask.index() * mem.maskGran),
426 dataValues[
wmask.index()]);
432 auto alwaysBlock = [&] {
433 return b.create<sv::AlwaysOp>(sv::EventControl::AtPosEdge, clock,
434 [&]() { writeLogic(); });
437 switch (mem.writeUnderWrite) {
440 case seq::WUW::Undefined:
445 case seq::WUW::PortOrder:
446 if (
auto *existingAlwaysBlock =
447 writeProcesses.lookup(mem.writeClockIDs[i])) {
448 OpBuilder::InsertionGuard guard(b);
449 b.setInsertionPointToEnd(
450 cast<sv::AlwaysOp>(existingAlwaysBlock).getBodyBlock());
453 writeProcesses[i] = alwaysBlock();
458 auto *outputOp = op.getBodyBlock()->getTerminator();
459 outputOp->setOperands(
outputs);
463 if (!mem.initFilename.empty()) {
465 if (!
reg.getInnerSymAttr())
467 b.getStringAttr(moduleNamespace.newName(
reg.getName()))));
469 if (mem.initIsInline) {
470 b.create<
sv::IfDefOp>(
"ENABLE_INITIAL_MEM_", [&]() {
471 b.create<sv::InitialOp>([&]() {
472 b.create<sv::ReadMemOp>(
reg, mem.initFilename,
474 ? MemBaseTypeAttr::MemBaseBin
475 : MemBaseTypeAttr::MemBaseHex);
479 OpBuilder::InsertionGuard guard(b);
482 b.setInsertionPointAfter(op);
484 b.getStringAttr(mlirModuleNamespace.newName(op.getName() +
"_init")),
485 ArrayRef<PortInfo>());
487 auto filename = op->getAttrOfType<OutputFileAttr>(
"output_file");
489 if (!filename.isDirectory()) {
490 SmallString<128> dir(filename.getFilename().getValue());
491 llvm::sys::path::remove_filename(dir);
492 filename = hw::OutputFileAttr::getFromDirectoryAndFilename(
493 b.getContext(), dir, boundModule.getName() +
".sv");
496 filename = hw::OutputFileAttr::getFromFilename(
497 b.getContext(), boundModule.getName() +
".sv");
501 auto path = b.create<hw::HierPathOp>(
502 mlirModuleNamespace.newName(op.getName() +
"_path"),
506 boundModule->setAttr(
"output_file", filename);
507 b.setInsertionPointToStart(op.getBodyBlock());
508 b.setInsertionPointToStart(boundModule.getBodyBlock());
509 b.create<sv::InitialOp>([&]() {
510 auto xmr = b.create<sv::XMRRefOp>(
reg.getType(), path.getSymNameAttr());
511 b.create<sv::ReadMemOp>(xmr, mem.initFilename,
512 mem.initIsBinary ? MemBaseTypeAttr::MemBaseBin
513 : MemBaseTypeAttr::MemBaseHex);
517 b.setInsertionPointAfter(
reg);
518 auto boundInstance = b.create<hw::InstanceOp>(
519 boundModule, boundModule.getName(), ArrayRef<Value>());
520 boundInstance->setAttr(
523 moduleNamespace.newName(boundInstance.getInstanceName()))));
524 boundInstance->setAttr(
"doNotPrint", b.getBoolAttr(
true));
527 b.setInsertionPointAfter(boundModule);
529 op.getNameAttr(), boundInstance.getInnerSymAttr().getSymName()));
530 bind->setAttr(
"output_file", filename);
536 if (disableMemRandomization && disableRegRandomization)
539 constexpr
unsigned randomWidth = 32;
540 b.create<
sv::IfDefOp>(
"ENABLE_INITIAL_MEM_", [&]() {
542 SmallVector<sv::RegOp> randRegs;
543 if (!disableRegRandomization) {
544 b.create<
sv::IfDefOp>(
"RANDOMIZE_REG_INIT", [&]() {
545 signed totalWidth = 0;
547 totalWidth +=
reg.getElementType().getIntOrFloatBitWidth();
548 while (totalWidth > 0) {
549 auto name = b.getStringAttr(moduleNamespace.newName(
"_RANDOM"));
551 randRegs.push_back(b.create<
sv::RegOp>(b.getIntegerType(randomWidth),
553 totalWidth -= randomWidth;
558 b.getIntegerType(llvm::divideCeil(mem.dataWidth, randomWidth) *
560 b.getStringAttr(
"_RANDOM_MEM"));
561 b.create<sv::InitialOp>([&]() {
562 b.create<sv::VerbatimOp>(
"`INIT_RANDOM_PROLOG_");
565 if (!disableMemRandomization) {
566 b.create<sv::IfDefProceduralOp>(
"RANDOMIZE_MEM_INIT", [&]() {
567 auto outerLoopIndVarType =
568 b.getIntegerType(llvm::Log2_64_Ceil(mem.depth + 1));
569 auto innerUpperBoundWidth = randomMemReg.getType()
573 auto innerLoopIndVarType =
574 b.getIntegerType(llvm::Log2_64_Ceil(innerUpperBoundWidth + 1));
583 0, mem.depth, 1, outerLoopIndVarType,
"i",
584 [&](BlockArgument outerIndVar) {
586 0, innerUpperBoundWidth, randomWidth, innerLoopIndVarType,
587 "j", [&](BlockArgument innerIndVar) {
588 auto rhs = b.create<sv::MacroRefExprSEOp>(
589 b.getIntegerType(randomWidth),
"RANDOM");
590 auto lhs = b.create<sv::IndexedPartSelectInOutOp>(
591 randomMemReg, innerIndVar, randomWidth,
false);
592 b.create<sv::BPAssignOp>(lhs, rhs);
595 Value iterValue = outerIndVar;
597 if (!outerIndVar.getType().isInteger(
598 llvm::Log2_64_Ceil(mem.depth)))
600 iterValue, 0, llvm::Log2_64_Ceil(mem.depth));
601 auto lhs = b.
create<sv::ArrayIndexInOutOp>(
reg, iterValue);
604 b.create<sv::BPAssignOp>(lhs, rhs);
614 if (!disableRegRandomization) {
615 b.create<sv::IfDefProceduralOp>(
"RANDOMIZE_REG_INIT", [&]() {
616 unsigned bits = randomWidth;
618 b.create<sv::VerbatimOp>(
619 b.getStringAttr(
"{{0}} = {`RANDOM};"), ValueRange{},
621 reg.getInnerNameAttr())));
624 SmallVector<std::pair<Attribute, std::pair<size_t, size_t>>> values;
625 auto width =
reg.getElementType().getIntOrFloatBitWidth();
626 auto widthRemaining =
width;
627 while (widthRemaining > 0) {
628 if (bits == randomWidth) {
629 randReg = randRegs[randRegIdx++];
633 randReg.getInnerNameAttr());
634 if (widthRemaining <= randomWidth - bits) {
635 values.push_back({innerRef, {bits + widthRemaining - 1, bits}});
636 bits += widthRemaining;
640 values.push_back({innerRef, {randomWidth - 1, bits}});
641 widthRemaining -= (randomWidth - bits);
644 SmallString<32> rhs(
"{{0}} = ");
648 op.getNameAttr(),
reg.getInnerNameAttr())});
649 if (values.size() > 1)
651 for (
auto &v : values) {
654 auto [sym, range] = v;
655 symbols.push_back(sym);
656 rhs.append((
"{{" + Twine(idx++) +
"}}").str());
658 if (range.first == randomWidth - 1 && range.second == 0)
661 if (range.first == range.second) {
662 rhs.append((
"[" + Twine(range.first) +
"]").str());
667 (
"[" + Twine(range.first) +
":" + Twine(range.second) +
"]")
670 if (values.size() > 1)
673 b.create<sv::VerbatimOp>(rhs, ValueRange{},
674 b.getArrayAttr(symbols));
682 void HWMemSimImplPass::runOnOperation() {
683 auto topModule = getOperation();
691 mlirModuleNamespace.
add(symbolCache);
693 SmallVector<HWModuleGeneratedOp> toErase;
694 bool anythingChanged =
false;
697 llvm::make_early_inc_range(topModule.getOps<HWModuleGeneratedOp>())) {
698 auto oldModule = cast<HWModuleGeneratedOp>(op);
699 auto gen = oldModule.getGeneratorKind();
700 auto genOp = cast<HWGeneratorSchemaOp>(
701 SymbolTable::lookupSymbolIn(getOperation(), gen));
703 if (genOp.getDescriptor() ==
"FIRRTL_Memory") {
704 FirMemory mem(oldModule);
707 auto nameAttr =
builder.getStringAttr(oldModule.getName());
712 if (replSeqMem && ((mem.readLatency == 1 && mem.writeLatency == 1) &&
713 mem.dataWidth > 0)) {
715 oldModule.getPortList());
718 oldModule.getLoc(), nameAttr, oldModule.getPortList());
719 if (
auto outdir = oldModule->getAttr(
"output_file"))
720 newModule->setAttr(
"output_file", outdir);
721 newModule.setCommentAttr(
722 builder.getStringAttr(
"VCS coverage exclude_file"));
724 HWMemSimImpl(ignoreReadEnable, addMuxPragmas, disableMemRandomization,
725 disableRegRandomization,
726 addVivadoRAMAddressConflictSynthesisBugWorkaround,
728 .generateMemory(newModule, mem);
732 anythingChanged =
true;
736 if (!anythingChanged)
737 markAllAnalysesPreserved();
740 std::unique_ptr<Pass>
742 return std::make_unique<HWMemSimImplPass>(options);
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)
llvm::SmallVector< StringAttr > outputs
A namespace that is used to store existing names and generate new names in some scope within the IR.
void add(SymbolCache &symCache)
SymbolCache initializer; initialize from every key that is convertible to a StringAttr in the SymbolC...
void addDefinitions(mlir::Operation *top)
Populate the symbol cache with all symbol-defining operations within the 'top' operation.
Default symbol cache implementation; stores associations between names (StringAttr's) to mlir::Operat...
def create(data_type, name=None, sym_name=None)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
uint64_t getWidth(Type t)
std::unique_ptr< mlir::Pass > createHWMemSimImplPass(const HWMemSimImplOptions &options={})
circt::hw::InOutType InOutType
void setSVAttributes(mlir::Operation *op, mlir::ArrayAttr attrs)
Set the SV attributes of an operation.
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)