25#include "mlir/IR/ImplicitLocOpBuilder.h"
26#include "mlir/Pass/Pass.h"
27#include "llvm/ADT/TypeSwitch.h"
28#include "llvm/Support/Path.h"
36#define GEN_PASS_DEF_HWMEMSIMIMPL
37#include "circt/Dialect/Seq/SeqPasses.h.inc"
50 bool disableMemRandomization;
51 bool disableRegRandomization;
52 bool addVivadoRAMAddressConflictSynthesisBugWorkaround;
54 SmallVector<sv::VarOp> registers;
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;
63 Namespace &mlirModuleNamespace;
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) {}
76 void generateMemory(HWModuleOp op, FirMemory mem);
79struct HWMemSimImplPass :
public impl::HWMemSimImplBase<HWMemSimImplPass> {
80 using HWMemSimImplBase::HWMemSimImplBase;
82 void runOnOperation()
override;
91 Operation *valueOp = value.getDefiningOp();
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));
110static Value
getMemoryRead(ImplicitLocOpBuilder &b, Value memory, Value addr,
111 bool addMuxPragmas) {
113 b, sv::ArrayIndexInOutOp::create(b, memory, addr));
115 if (!addMuxPragmas ||
116 cast<hw::UnpackedArrayType>(
117 cast<sv::VarType>(memory.getType()).getElementType())
118 .getNumElements() <= 1)
121 slot, sv::SVAttributeAttr::get(b.getContext(),
"cadence map_to_mux",
126 sv::SVAttributeAttr::get(b.getContext(),
127 "synopsys infer_mux_override",
133Value HWMemSimImpl::addPipelineStages(ImplicitLocOpBuilder &b,
134 hw::InnerSymbolNamespace &moduleNamespace,
135 size_t stages, Value clock, Value data,
136 const Twine &name, Value gate) {
142 auto alwaysOp = lastPipelineAlwaysOp;
144 if (alwaysOp.getClocks() != ValueRange{clock} ||
150 alwaysOp = sv::AlwaysOp::create(b, sv::EventControl::AtPosEdge, clock);
153 auto savedIP = b.saveInsertionPoint();
154 SmallVector<sv::VarOp> regs;
155 b.setInsertionPoint(alwaysOp);
156 for (
unsigned i = 0; i < stages; ++i) {
158 b.getStringAttr(moduleNamespace.newName(
"_" + name +
"_d" + Twine(i)));
160 hw::InnerSymAttr::get(regName));
162 registers.push_back(
reg);
166 b.setInsertionPointToEnd(alwaysOp.getBodyBlock());
167 for (
unsigned i = 0; i < stages; ++i) {
170 auto emitAssign = [&] { sv::PAssignOp::create(b, regs[i], data); };
172 sv::IfOp::create(b, gate, [&]() { emitAssign(); });
178 b.restoreInsertionPoint(savedIP);
181 lastPipelineAlwaysOp = alwaysOp;
185void HWMemSimImpl::generateMemory(HWModuleOp op, FirMemory mem) {
186 ImplicitLocOpBuilder b(op.getLoc(), op.getBody());
188 InnerSymbolNamespace moduleNamespace(op);
191 if (mem.maskGran == 0)
192 mem.maskGran = mem.dataWidth;
193 auto maskBits = mem.dataWidth / mem.maskGran;
194 bool isMasked = maskBits > 1;
196 auto dataType = b.getIntegerType(mem.dataWidth);
200 mem.numReadPorts + mem.numWritePorts + mem.numReadWritePorts;
205 b.getStringAttr(
"Memory"));
207 if (addVivadoRAMAddressConflictSynthesisBugWorkaround) {
208 if (mem.readLatency == 0) {
215 reg, sv::SVAttributeAttr::get(b.getContext(),
"ram_style",
218 }
else if (mem.readLatency == 1 && numPorts > 1) {
224 reg, sv::SVAttributeAttr::get(b.getContext(),
"rw_addr_collision",
229 SmallVector<Value, 4> outputs;
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++);
237 if (readEnableMode == ReadEnableMode::Ignore) {
238 for (
size_t j = 0, e = mem.readLatency; j != e; ++j) {
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);
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");
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);
261 case ReadEnableMode::Zero: {
263 rdata = comb::MuxOp::create(b, en, rdata, x,
false);
266 case ReadEnableMode::Ignore:
269 outputs.push_back(rdata);
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++);
285 wmaskBits = op.getBody().getArgument(inArg++);
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");
298 Value readAddr =
addr;
300 if (readEnableMode == ReadEnableMode::Ignore) {
301 for (
size_t j = 0, e = mem.readLatency; j != e; ++j) {
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);
311 addPipelineStages(b, moduleNamespace, numReadStages - numCommonStages,
312 clock, addr,
"RW" + Twine(i) +
"_raddr");
314 addPipelineStages(b, moduleNamespace, numReadStages - numCommonStages,
315 clock, en,
"RW" + Twine(i) +
"_ren");
318 addPipelineStages(b, moduleNamespace, numReadStages - numCommonStages,
319 clock, wmode,
"RW" + Twine(i) +
"_rmode");
323 addPipelineStages(b, moduleNamespace, numWriteStages - numCommonStages,
324 clock, addr,
"RW" + Twine(i) +
"_waddr");
326 addPipelineStages(b, moduleNamespace, numWriteStages - numCommonStages,
327 clock, en,
"RW" + Twine(i) +
"_wen");
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");
334 wmaskBits = addPipelineStages(b, moduleNamespace, numWriteStages, clock,
335 wmaskBits,
"RW" + Twine(i) +
"_wmask");
337 SmallVector<Value, 4> maskValues(maskBits);
338 SmallVector<Value, 4> dataValues(maskBits);
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,
353 Value rcond = b.createOrFold<comb::AndOp>(
355 b.createOrFold<comb::ICmpOp>(
356 comb::ICmpPredicate::eq, readWMode,
357 b.createOrFold<ConstantOp>(readWMode.getType(), 0),
false),
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);
368 case ReadEnableMode::Zero: {
370 val = comb::MuxOp::create(b, rcond, val, x,
false);
373 case ReadEnableMode::Ignore:
379 for (
auto wmask : llvm::enumerate(maskValues)) {
380 sv::AlwaysOp::create(b, sv::EventControl::AtPosEdge, clock, [&]() {
381 auto wcond = b.createOrFold<comb::AndOp>(
383 b.createOrFold<comb::AndOp>(
wmask.value(), writeWMode,
false),
385 sv::IfOp::create(b, wcond, [&]() {
386 Value slotReg = sv::ArrayIndexInOutOp::create(b,
reg, writeAddr);
387 sv::PAssignOp::create(
389 b.createOrFold<sv::IndexedPartSelectInOutOp>(
391 b.createOrFold<ConstantOp>(b.getIntegerType(32),
392 wmask.index() * mem.maskGran),
394 dataValues[
wmask.index()]);
398 outputs.push_back(rdata);
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++);
412 wmaskBits = op.getBody().getArgument(inArg++);
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");
423 wmaskBits = addPipelineStages(b, moduleNamespace, numStages, clock,
424 wmaskBits,
"W" + Twine(i) +
"mask");
426 SmallVector<Value, 4> maskValues(maskBits);
427 SmallVector<Value, 4> dataValues(maskBits);
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,
437 auto writeLogic = [&] {
440 for (
auto wmask : llvm::enumerate(maskValues)) {
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(
447 b.createOrFold<sv::IndexedPartSelectInOutOp>(
449 b.createOrFold<ConstantOp>(b.getIntegerType(32),
450 wmask.index() * mem.maskGran),
452 dataValues[
wmask.index()]);
458 auto alwaysBlock = [&] {
459 return sv::AlwaysOp::create(b, sv::EventControl::AtPosEdge, clock,
460 [&]() { writeLogic(); });
463 switch (mem.writeUnderWrite) {
466 case seq::WUW::Undefined:
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());
479 writeProcesses[i] = alwaysBlock();
484 auto *outputOp = op.getBodyBlock()->getTerminator();
485 outputOp->setOperands(outputs);
489 if (!mem.initFilename.empty()) {
491 if (!
reg.getInnerSymAttr())
492 reg.setInnerSymAttr(hw::InnerSymAttr::get(
493 b.getStringAttr(moduleNamespace.newName(
reg.getName()))));
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);
504 OpBuilder::InsertionGuard guard(b);
507 StringAttr boundModuleName =
508 b.getStringAttr(mlirModuleNamespace.
newName(op.getName() +
"_init"));
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);
519 filename = fileAttr.getFilename();
522 filename = b.getStringAttr(boundModuleName.getValue() +
".sv");
526 b.setInsertionPointAfter(op);
528 HWModuleOp::create(b, boundModuleName, ArrayRef<PortInfo>());
531 auto path = hw::HierPathOp::create(
532 b, mlirModuleNamespace.
newName(op.getName() +
"_path"),
535 ::InnerRefAttr::get(op.getNameAttr(),
reg.getInnerNameAttr())));
537 b.setInsertionPointToStart(boundModule.getBodyBlock());
538 sv::InitialOp::create(b, [&]() {
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);
547 b.setInsertionPointAfter(
reg);
548 auto boundInstance = hw::InstanceOp::create(
549 b, boundModule, boundModule.getName(), ArrayRef<Value>());
550 boundInstance->setAttr(
552 hw::InnerSymAttr::get(b.getStringAttr(
553 moduleNamespace.newName(boundInstance.getInstanceName()))));
554 boundInstance.setDoNotPrintAttr(b.getUnitAttr());
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(
563 boundInstance.getInnerSymAttr().getSymName()));
570 if (disableMemRandomization && disableRegRandomization)
573 constexpr unsigned randomWidth = 32;
574 sv::IfDefOp::create(b,
"ENABLE_INITIAL_MEM_", [&]() {
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);
587 totalWidth -= randomWidth;
593 b.getIntegerType(llvm::divideCeil(mem.dataWidth, randomWidth) *
595 b.getStringAttr(
"_RANDOM_MEM"));
596 sv::InitialOp::create(b, [&]() {
597 sv::VerbatimOp::create(b,
"`INIT_RANDOM_PROLOG_");
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())
607 auto innerLoopIndVarType =
608 b.getIntegerType(llvm::Log2_64_Ceil(innerUpperBoundWidth + 1));
617 b, 0, mem.depth, 1, outerLoopIndVarType,
"i",
618 [&](BlockArgument outerIndVar) {
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)
627 hw::ConstantOp::create(b, b.getI1Type(), 0);
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,
634 sv::BPAssignOp::create(b, lhs, rhs);
637 Value iterValue = outerIndVar;
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>(
646 sv::BPAssignOp::create(b, lhs, rhs);
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())));
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++];
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;
682 values.push_back({innerRef, {randomWidth - 1, bits}});
683 widthRemaining -= (randomWidth - bits);
686 SmallString<32> rhs(
"{{0}} = ");
689 SmallVector<Attribute, 4> symbols({hw::InnerRefAttr::get(
690 op.getNameAttr(),
reg.getInnerNameAttr())});
691 if (values.size() > 1)
693 for (
auto &v : values) {
696 auto [sym, range] = v;
697 symbols.push_back(sym);
698 rhs.append((
"{{" + Twine(idx++) +
"}}").str());
700 if (range.first == randomWidth - 1 && range.second == 0)
703 if (range.first == range.second) {
704 rhs.append((
"[" + Twine(range.first) +
"]").str());
709 (
"[" + Twine(range.first) +
":" + Twine(range.second) +
"]")
712 if (values.size() > 1)
715 sv::VerbatimOp::create(b, rhs, ValueRange{},
716 b.getArrayAttr(symbols));
724void HWMemSimImplPass::runOnOperation() {
725 auto topModule = getOperation();
730 SymbolCache symbolCache;
732 Namespace mlirModuleNamespace;
733 mlirModuleNamespace.
add(symbolCache);
735 SmallVector<HWModuleGeneratedOp> toErase;
736 bool anythingChanged =
false;
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));
745 if (genOp.getDescriptor() ==
"FIRRTL_Memory") {
746 FirMemory mem(oldModule);
748 OpBuilder builder(oldModule);
749 auto nameAttr = builder.getStringAttr(oldModule.getName());
754 if (replSeqMem && ((mem.readLatency >= 1 && mem.writeLatency >= 1) &&
755 mem.dataWidth > 0)) {
756 HWModuleExternOp::create(builder, oldModule.getLoc(), nameAttr,
757 oldModule.getPortList());
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();
767 HWMemSimImpl(readEnableMode, addMuxPragmas, disableMemRandomization,
768 disableRegRandomization,
769 addVivadoRAMAddressConflictSynthesisBugWorkaround,
771 .generateMemory(newModule, mem);
772 if (
auto fragments = oldModule->getAttr(emit::getFragmentsAttrName()))
773 newModule->setAttr(emit::getFragmentsAttrName(), fragments);
777 anythingChanged =
true;
781 if (!anythingChanged)
782 markAllAnalysesPreserved();
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)
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
void addDefinitions(mlir::Operation *top)
Populate the symbol cache with all symbol-defining operations within the 'top' operation.
create(data_type, name=None, sym_name=None)
create(data_type, name=None, sym_name=None)
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.
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)