24 #include "mlir/IR/ImplicitLocOpBuilder.h"
25 #include "llvm/ADT/TypeSwitch.h"
26 #include "llvm/Support/Path.h"
28 using namespace circt;
34 #define GEN_PASS_DEF_HWMEMSIMIMPL
35 #include "circt/Dialect/Seq/SeqPasses.h.inc"
48 bool disableMemRandomization;
49 bool disableRegRandomization;
50 bool addVivadoRAMAddressConflictSynthesisBugWorkaround;
52 SmallVector<sv::RegOp> registers;
54 Value addPipelineStages(ImplicitLocOpBuilder &b,
55 hw::InnerSymbolNamespace &moduleNamespace,
56 size_t stages, Value clock, Value
data,
57 const Twine &name, Value gate = {});
58 sv::AlwaysOp lastPipelineAlwaysOp;
64 bool disableMemRandomization,
bool disableRegRandomization,
65 bool addVivadoRAMAddressConflictSynthesisBugWorkaround,
67 : readEnableMode(readEnableMode), addMuxPragmas(addMuxPragmas),
68 disableMemRandomization(disableMemRandomization),
69 disableRegRandomization(disableRegRandomization),
70 addVivadoRAMAddressConflictSynthesisBugWorkaround(
71 addVivadoRAMAddressConflictSynthesisBugWorkaround),
72 mlirModuleNamespace(mlirModuleNamespace) {}
74 void generateMemory(
HWModuleOp op, FirMemory mem);
77 struct HWMemSimImplPass :
public impl::HWMemSimImplBase<HWMemSimImplPass> {
78 using HWMemSimImplBase::HWMemSimImplBase;
80 void runOnOperation()
override;
89 Operation *valueOp = value.getDefiningOp();
91 valueOp ? valueOp->getBlock() : value.cast<BlockArgument>().getOwner();
92 while (op->getBlock() && op->getBlock() != valueBlock)
93 op = op->getParentOp();
94 return valueBlock == op->getBlock() &&
95 (!valueOp || valueOp->isBeforeInBlock(op));
108 static Value
getMemoryRead(ImplicitLocOpBuilder &b, Value memory, Value addr,
109 bool addMuxPragmas) {
111 b.create<
sv::ReadInOutOp>(b.create<sv::ArrayIndexInOutOp>(memory, addr));
113 if (!addMuxPragmas || memory.getType()
116 .cast<hw::UnpackedArrayType>()
117 .getNumElements() <= 1)
122 auto valWire = b.create<
sv::WireOp>(slot.getType());
126 "synopsys infer_mux_override",
132 Value HWMemSimImpl::addPipelineStages(ImplicitLocOpBuilder &b,
133 hw::InnerSymbolNamespace &moduleNamespace,
134 size_t stages, Value clock, Value data,
135 const Twine &name, Value gate) {
141 auto alwaysOp = lastPipelineAlwaysOp;
143 if (alwaysOp.getClocks() != ValueRange{clock} ||
149 alwaysOp = b.create<sv::AlwaysOp>(sv::EventControl::AtPosEdge, clock);
152 auto savedIP = b.saveInsertionPoint();
153 SmallVector<sv::RegOp> regs;
154 b.setInsertionPoint(alwaysOp);
155 for (
unsigned i = 0; i < stages; ++i) {
157 b.getStringAttr(moduleNamespace.newName(
"_" + name +
"_d" + Twine(i)));
161 registers.push_back(
reg);
165 b.setInsertionPointToEnd(alwaysOp.getBodyBlock());
166 for (
unsigned i = 0; i < stages; ++i) {
169 auto emitAssign = [&] { b.
create<sv::PAssignOp>(regs[i],
data); };
171 b.create<sv::IfOp>(gate, [&]() { emitAssign(); });
177 b.restoreInsertionPoint(savedIP);
180 lastPipelineAlwaysOp = alwaysOp;
184 void HWMemSimImpl::generateMemory(
HWModuleOp op, FirMemory mem) {
185 ImplicitLocOpBuilder b(op.getLoc(), op.getBody());
190 if (mem.maskGran == 0)
191 mem.maskGran = mem.dataWidth;
192 auto maskBits = mem.dataWidth / mem.maskGran;
193 bool isMasked = maskBits > 1;
195 auto dataType = b.getIntegerType(mem.dataWidth);
199 mem.numReadPorts + mem.numWritePorts + mem.numReadWritePorts;
205 if (addVivadoRAMAddressConflictSynthesisBugWorkaround) {
206 if (mem.readLatency == 0) {
216 }
else if (mem.readLatency == 1 && numPorts > 1) {
230 for (
size_t i = 0; i < mem.numReadPorts; ++i) {
231 Value
addr = op.getBody().getArgument(inArg++);
232 Value
en = op.getBody().getArgument(inArg++);
233 Value clock = op.getBody().getArgument(inArg++);
235 if (readEnableMode == ReadEnableMode::Ignore) {
236 for (
size_t j = 0, e = mem.readLatency; j != e; ++j) {
239 en = addPipelineStages(b, moduleNamespace, 1, clock, en,
240 "R" + Twine(i) +
"_en");
241 addr = addPipelineStages(b, moduleNamespace, 1, clock, addr,
242 "R" + Twine(i) +
"_addr", enLast);
245 en = addPipelineStages(b, moduleNamespace, mem.readLatency, clock, en,
246 "R" + Twine(i) +
"_en");
247 addr = addPipelineStages(b, moduleNamespace, mem.readLatency, clock, addr,
248 "R" + Twine(i) +
"_addr");
253 switch (readEnableMode) {
254 case ReadEnableMode::Undefined: {
255 Value x = b.create<sv::ConstantXOp>(
rdata.getType());
259 case ReadEnableMode::Zero: {
264 case ReadEnableMode::Ignore:
270 for (
size_t i = 0; i < mem.numReadWritePorts; ++i) {
271 auto numReadStages = mem.readLatency;
272 auto numWriteStages = mem.writeLatency - 1;
273 auto numCommonStages = std::min(numReadStages, numWriteStages);
274 Value
addr = op.getBody().getArgument(inArg++);
275 Value
en = op.getBody().getArgument(inArg++);
276 Value clock = op.getBody().getArgument(inArg++);
277 Value
wmode = op.getBody().getArgument(inArg++);
278 Value wdataIn = op.getBody().getArgument(inArg++);
283 wmaskBits = op.getBody().getArgument(inArg++);
285 wmaskBits = b.create<
ConstantOp>(b.getIntegerAttr(
en.getType(), 1));
288 addr = addPipelineStages(b, moduleNamespace, numCommonStages, clock, addr,
289 "RW" + Twine(i) +
"_addr");
290 en = addPipelineStages(b, moduleNamespace, numCommonStages, clock, en,
291 "RW" + Twine(i) +
"_en");
292 wmode = addPipelineStages(b, moduleNamespace, numCommonStages, clock, wmode,
293 "RW" + Twine(i) +
"_mode");
296 Value readAddr =
addr;
298 if (readEnableMode == ReadEnableMode::Ignore) {
299 for (
size_t j = 0, e = mem.readLatency; j != e; ++j) {
302 readEn = addPipelineStages(b, moduleNamespace, 1, clock, en,
303 "RW" + Twine(i) +
"_ren");
304 readAddr = addPipelineStages(b, moduleNamespace, 1, clock, addr,
305 "RW" + Twine(i) +
"_raddr", enLast);
309 addPipelineStages(b, moduleNamespace, numReadStages - numCommonStages,
310 clock, addr,
"RW" + Twine(i) +
"_raddr");
312 addPipelineStages(b, moduleNamespace, numReadStages - numCommonStages,
313 clock, en,
"RW" + Twine(i) +
"_ren");
316 addPipelineStages(b, moduleNamespace, numReadStages - numCommonStages,
317 clock, wmode,
"RW" + Twine(i) +
"_rmode");
321 addPipelineStages(b, moduleNamespace, numWriteStages - numCommonStages,
322 clock, addr,
"RW" + Twine(i) +
"_waddr");
324 addPipelineStages(b, moduleNamespace, numWriteStages - numCommonStages,
325 clock, en,
"RW" + Twine(i) +
"_wen");
327 addPipelineStages(b, moduleNamespace, numWriteStages - numCommonStages,
328 clock, wmode,
"RW" + Twine(i) +
"_wmode");
329 wdataIn = addPipelineStages(b, moduleNamespace, numWriteStages, clock,
330 wdataIn,
"RW" + Twine(i) +
"_wdata");
332 wmaskBits = addPipelineStages(b, moduleNamespace, numWriteStages, clock,
333 wmaskBits,
"RW" + Twine(i) +
"_wmask");
335 SmallVector<Value, 4> maskValues(maskBits);
336 SmallVector<Value, 4> dataValues(maskBits);
340 for (
size_t i = 0; i < maskBits; ++i) {
342 dataValues[i] = b.createOrFold<
comb::ExtractOp>(wdataIn, i * mem.maskGran,
353 b.createOrFold<comb::ICmpOp>(
354 comb::ICmpPredicate::eq, readWMode,
355 b.createOrFold<
ConstantOp>(readWMode.getType(), 0),
false),
360 switch (readEnableMode) {
361 case ReadEnableMode::Undefined: {
362 Value x = b.create<sv::ConstantXOp>(val.getType());
366 case ReadEnableMode::Zero: {
371 case ReadEnableMode::Ignore:
377 for (
auto wmask : llvm::enumerate(maskValues)) {
378 b.
create<sv::AlwaysOp>(sv::EventControl::AtPosEdge, clock, [&]() {
383 b.create<sv::IfOp>(wcond, [&]() {
384 Value slotReg = b.create<sv::ArrayIndexInOutOp>(
reg, writeAddr);
385 b.create<sv::PAssignOp>(
386 b.createOrFold<sv::IndexedPartSelectInOutOp>(
388 b.createOrFold<
ConstantOp>(b.getIntegerType(32),
389 wmask.index() * mem.maskGran),
391 dataValues[
wmask.index()]);
398 DenseMap<unsigned, Operation *> writeProcesses;
399 for (
size_t i = 0; i < mem.numWritePorts; ++i) {
400 auto numStages = mem.writeLatency - 1;
401 Value
addr = op.getBody().getArgument(inArg++);
402 Value
en = op.getBody().getArgument(inArg++);
403 Value clock = op.getBody().getArgument(inArg++);
404 Value wdataIn = op.getBody().getArgument(inArg++);
409 wmaskBits = op.getBody().getArgument(inArg++);
411 wmaskBits = b.create<
ConstantOp>(b.getIntegerAttr(
en.getType(), 1));
413 addr = addPipelineStages(b, moduleNamespace, numStages, clock, addr,
414 "W" + Twine(i) +
"addr");
415 en = addPipelineStages(b, moduleNamespace, numStages, clock, en,
416 "W" + Twine(i) +
"en");
417 wdataIn = addPipelineStages(b, moduleNamespace, numStages, clock, wdataIn,
418 "W" + Twine(i) +
"data");
420 wmaskBits = addPipelineStages(b, moduleNamespace, numStages, clock,
421 wmaskBits,
"W" + Twine(i) +
"mask");
423 SmallVector<Value, 4> maskValues(maskBits);
424 SmallVector<Value, 4> dataValues(maskBits);
428 for (
size_t i = 0; i < maskBits; ++i) {
430 dataValues[i] = b.createOrFold<
comb::ExtractOp>(wdataIn, i * mem.maskGran,
434 auto writeLogic = [&] {
437 for (
auto wmask : llvm::enumerate(maskValues)) {
440 b.create<sv::IfOp>(wcond, [&]() {
441 auto slot = b.create<sv::ArrayIndexInOutOp>(
reg,
addr);
442 b.create<sv::PAssignOp>(
443 b.createOrFold<sv::IndexedPartSelectInOutOp>(
445 b.createOrFold<
ConstantOp>(b.getIntegerType(32),
446 wmask.index() * mem.maskGran),
448 dataValues[
wmask.index()]);
454 auto alwaysBlock = [&] {
455 return b.create<sv::AlwaysOp>(sv::EventControl::AtPosEdge, clock,
456 [&]() { writeLogic(); });
459 switch (mem.writeUnderWrite) {
462 case seq::WUW::Undefined:
467 case seq::WUW::PortOrder:
468 if (
auto *existingAlwaysBlock =
469 writeProcesses.lookup(mem.writeClockIDs[i])) {
470 OpBuilder::InsertionGuard guard(b);
471 b.setInsertionPointToEnd(
472 cast<sv::AlwaysOp>(existingAlwaysBlock).getBodyBlock());
475 writeProcesses[i] = alwaysBlock();
480 auto *outputOp = op.getBodyBlock()->getTerminator();
481 outputOp->setOperands(
outputs);
485 if (!mem.initFilename.empty()) {
487 if (!
reg.getInnerSymAttr())
489 b.getStringAttr(moduleNamespace.newName(
reg.getName()))));
491 if (mem.initIsInline) {
492 b.create<
sv::IfDefOp>(
"ENABLE_INITIAL_MEM_", [&]() {
493 b.create<sv::InitialOp>([&]() {
494 b.create<sv::ReadMemOp>(
reg, mem.initFilename,
496 ? MemBaseTypeAttr::MemBaseBin
497 : MemBaseTypeAttr::MemBaseHex);
501 OpBuilder::InsertionGuard guard(b);
504 StringAttr boundModuleName =
505 b.getStringAttr(mlirModuleNamespace.newName(op.getName() +
"_init"));
509 if (
auto fileAttr = op->getAttrOfType<OutputFileAttr>(
"output_file")) {
510 if (!fileAttr.isDirectory()) {
511 SmallString<128> path(fileAttr.getFilename().getValue());
512 llvm::sys::path::remove_filename(path);
514 filename = b.getStringAttr(path);
516 filename = fileAttr.getFilename();
519 filename = b.getStringAttr(boundModuleName.getValue() +
".sv");
523 b.setInsertionPointAfter(op);
525 b.create<
HWModuleOp>(boundModuleName, ArrayRef<PortInfo>());
528 auto path = b.create<hw::HierPathOp>(
529 mlirModuleNamespace.newName(op.getName() +
"_path"),
533 b.setInsertionPointToStart(boundModule.getBodyBlock());
534 b.create<sv::InitialOp>([&]() {
535 auto xmr = b.create<sv::XMRRefOp>(
reg.getType(), path.getSymNameAttr());
536 b.create<sv::ReadMemOp>(xmr, mem.initFilename,
537 mem.initIsBinary ? MemBaseTypeAttr::MemBaseBin
538 : MemBaseTypeAttr::MemBaseHex);
542 b.setInsertionPointAfter(
reg);
543 auto boundInstance = b.create<hw::InstanceOp>(
544 boundModule, boundModule.getName(), ArrayRef<Value>());
545 boundInstance->setAttr(
548 moduleNamespace.newName(boundInstance.getInstanceName()))));
549 boundInstance->setAttr(
"doNotPrint", b.getBoolAttr(
true));
552 b.setInsertionPointAfter(op);
553 b.create<emit::FileOp>(filename, [&] {
556 op.getNameAttr(), boundInstance.getInnerSymAttr().getSymName()));
563 if (disableMemRandomization && disableRegRandomization)
566 constexpr
unsigned randomWidth = 32;
567 b.create<
sv::IfDefOp>(
"ENABLE_INITIAL_MEM_", [&]() {
569 SmallVector<sv::RegOp> randRegs;
570 if (!disableRegRandomization) {
571 b.create<
sv::IfDefOp>(
"RANDOMIZE_REG_INIT", [&]() {
572 signed totalWidth = 0;
574 totalWidth +=
reg.getElementType().getIntOrFloatBitWidth();
575 while (totalWidth > 0) {
576 auto name = b.getStringAttr(moduleNamespace.newName(
"_RANDOM"));
578 randRegs.push_back(b.create<
sv::RegOp>(b.getIntegerType(randomWidth),
580 totalWidth -= randomWidth;
585 b.getIntegerType(llvm::divideCeil(mem.dataWidth, randomWidth) *
587 b.getStringAttr(
"_RANDOM_MEM"));
588 b.create<sv::InitialOp>([&]() {
589 b.create<sv::VerbatimOp>(
"`INIT_RANDOM_PROLOG_");
592 if (!disableMemRandomization) {
593 b.create<sv::IfDefProceduralOp>(
"RANDOMIZE_MEM_INIT", [&]() {
594 auto outerLoopIndVarType =
595 b.getIntegerType(llvm::Log2_64_Ceil(mem.depth + 1));
596 auto innerUpperBoundWidth = randomMemReg.getType()
600 auto innerLoopIndVarType =
601 b.getIntegerType(llvm::Log2_64_Ceil(innerUpperBoundWidth + 1));
610 0, mem.depth, 1, outerLoopIndVarType,
"i",
611 [&](BlockArgument outerIndVar) {
613 0, innerUpperBoundWidth, randomWidth, innerLoopIndVarType,
614 "j", [&](BlockArgument innerIndVar) {
615 auto rhs = b.create<sv::MacroRefExprSEOp>(
616 b.getIntegerType(randomWidth),
"RANDOM");
617 auto lhs = b.create<sv::IndexedPartSelectInOutOp>(
618 randomMemReg, innerIndVar, randomWidth,
false);
619 b.create<sv::BPAssignOp>(lhs, rhs);
622 Value iterValue = outerIndVar;
624 if (!outerIndVar.getType().isInteger(
625 llvm::Log2_64_Ceil(mem.depth)))
627 iterValue, 0, llvm::Log2_64_Ceil(mem.depth));
628 auto lhs = b.
create<sv::ArrayIndexInOutOp>(
reg, iterValue);
631 b.create<sv::BPAssignOp>(lhs, rhs);
641 if (!disableRegRandomization) {
642 b.create<sv::IfDefProceduralOp>(
"RANDOMIZE_REG_INIT", [&]() {
643 unsigned bits = randomWidth;
645 b.create<sv::VerbatimOp>(
646 b.getStringAttr(
"{{0}} = {`RANDOM};"), ValueRange{},
648 reg.getInnerNameAttr())));
651 SmallVector<std::pair<Attribute, std::pair<size_t, size_t>>> values;
652 auto width =
reg.getElementType().getIntOrFloatBitWidth();
653 auto widthRemaining =
width;
654 while (widthRemaining > 0) {
655 if (bits == randomWidth) {
656 randReg = randRegs[randRegIdx++];
660 randReg.getInnerNameAttr());
661 if (widthRemaining <= randomWidth - bits) {
662 values.push_back({innerRef, {bits + widthRemaining - 1, bits}});
663 bits += widthRemaining;
667 values.push_back({innerRef, {randomWidth - 1, bits}});
668 widthRemaining -= (randomWidth - bits);
671 SmallString<32> rhs(
"{{0}} = ");
675 op.getNameAttr(),
reg.getInnerNameAttr())});
676 if (values.size() > 1)
678 for (
auto &v : values) {
681 auto [sym, range] = v;
682 symbols.push_back(sym);
683 rhs.append((
"{{" + Twine(idx++) +
"}}").str());
685 if (range.first == randomWidth - 1 && range.second == 0)
688 if (range.first == range.second) {
689 rhs.append((
"[" + Twine(range.first) +
"]").str());
694 (
"[" + Twine(range.first) +
":" + Twine(range.second) +
"]")
697 if (values.size() > 1)
700 b.create<sv::VerbatimOp>(rhs, ValueRange{},
701 b.getArrayAttr(symbols));
709 void HWMemSimImplPass::runOnOperation() {
710 auto topModule = getOperation();
718 mlirModuleNamespace.
add(symbolCache);
720 SmallVector<HWModuleGeneratedOp> toErase;
721 bool anythingChanged =
false;
724 llvm::make_early_inc_range(topModule.getOps<HWModuleGeneratedOp>())) {
725 auto oldModule = cast<HWModuleGeneratedOp>(op);
726 auto gen = oldModule.getGeneratorKind();
727 auto genOp = cast<HWGeneratorSchemaOp>(
728 SymbolTable::lookupSymbolIn(getOperation(), gen));
730 if (genOp.getDescriptor() ==
"FIRRTL_Memory") {
731 FirMemory mem(oldModule);
734 auto nameAttr =
builder.getStringAttr(oldModule.getName());
739 if (replSeqMem && ((mem.readLatency == 1 && mem.writeLatency == 1) &&
740 mem.dataWidth > 0)) {
742 oldModule.getPortList());
745 oldModule.getLoc(), nameAttr, oldModule.getPortList());
746 if (
auto outdir = oldModule->getAttr(
"output_file"))
747 newModule->setAttr(
"output_file", outdir);
748 newModule.setCommentAttr(
749 builder.getStringAttr(
"VCS coverage exclude_file"));
750 newModule.setPrivate();
752 HWMemSimImpl(readEnableMode, addMuxPragmas, disableMemRandomization,
753 disableRegRandomization,
754 addVivadoRAMAddressConflictSynthesisBugWorkaround,
756 .generateMemory(newModule, mem);
760 anythingChanged =
true;
764 if (!anythingChanged)
765 markAllAnalysesPreserved();
768 std::unique_ptr<Pass>
770 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
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
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.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)