mirror of https://github.com/llvm/circt.git
[FIRRTL] Create memory metadata (#1923)
Add memory metadata emission to `CreatesifiveMetadata`. The metadata is used for memory macro substitution by memory generator tools. A subsequent checkin will delay the `CreatesifiveMetadata` pass in the FIRRTL pipeline.
This commit is contained in:
parent
55182f1797
commit
b3e68a9370
|
@ -471,6 +471,32 @@ Example:
|
|||
}
|
||||
```
|
||||
|
||||
### SeqMemInstanceMetadataAnnotation
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------- | ------ | ------------- |
|
||||
| class | string | `sifive.enterprise.firrtl.SeqMemInstanceMetadataAnnotation` |
|
||||
| target | string | Reference target |
|
||||
|
||||
This annotation attaches metadata to the firrtl.mem operation. The `data` is
|
||||
emitted onto the `seq_mems.json` and `tb_seq_mems.json` file. It is required
|
||||
for verification only and used by memory generator tools for simulation.
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"class":"sifive.enterprise.firrtl.SeqMemInstanceMetadataAnnotation",
|
||||
"data":{
|
||||
"baseAddress":2147483648,
|
||||
"eccScheme":"none",
|
||||
"eccBits":0,
|
||||
"dataBits":8,
|
||||
"eccIndices":[ ]
|
||||
},
|
||||
"target":"~CoreIPSubsystemVerifTestHarness|TLRAM>mem"
|
||||
}
|
||||
```
|
||||
|
||||
### ScalaClassAnnotation
|
||||
|
||||
| Property | Type | Description |
|
||||
|
|
|
@ -225,6 +225,9 @@ def MemOp : FIRRTLOp<"mem"> {
|
|||
/// Hooks for port annotations.
|
||||
ArrayAttr getPortAnnotation(unsigned portIdx);
|
||||
void setAllPortAnnotations(ArrayRef<Attribute> annotations);
|
||||
|
||||
/// Get the number of read ports, write ports and read write ports.
|
||||
void getNumPorts(size_t &numReadPorts, size_t &numWritePorts, size_t &numReadWritePorts);
|
||||
}];
|
||||
}
|
||||
|
||||
|
|
|
@ -1023,6 +1023,23 @@ void MemOp::setAllPortAnnotations(ArrayRef<Attribute> annotations) {
|
|||
ArrayAttr::get(getContext(), annotations));
|
||||
}
|
||||
|
||||
// Get the number of read, write and read-write ports.
|
||||
void MemOp::getNumPorts(size_t &numReadPorts, size_t &numWritePorts,
|
||||
size_t &numReadWritePorts) {
|
||||
numReadPorts = 0;
|
||||
numWritePorts = 0;
|
||||
numReadWritePorts = 0;
|
||||
for (size_t i = 0, e = getNumResults(); i != e; ++i) {
|
||||
auto portKind = getPortKind(i);
|
||||
if (portKind == MemOp::PortKind::Read)
|
||||
++numReadPorts;
|
||||
else if (portKind == MemOp::PortKind::Write) {
|
||||
++numWritePorts;
|
||||
} else
|
||||
++numReadWritePorts;
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify the correctness of a MemOp.
|
||||
static LogicalResult verifyMemOp(MemOp mem) {
|
||||
|
||||
|
|
|
@ -25,16 +25,213 @@
|
|||
using namespace circt;
|
||||
using namespace firrtl;
|
||||
|
||||
static const char seqMemAnnoClass[] =
|
||||
"sifive.enterprise.firrtl.SeqMemInstanceMetadataAnnotation";
|
||||
static const char dutAnnoClass[] = "sifive.enterprise.firrtl.MarkDUTAnnotation";
|
||||
/// Attribute that indicates where some json files should be dumped.
|
||||
static const char metadataDirectoryAnnoClass[] =
|
||||
"sifive.enterprise.firrtl.MetadataDirAnnotation";
|
||||
|
||||
namespace {
|
||||
class CreateSiFiveMetadataPass
|
||||
: public CreateSiFiveMetadataBase<CreateSiFiveMetadataPass> {
|
||||
LogicalResult emitRetimeModulesMetadata();
|
||||
LogicalResult emitSitestBlackboxMetadata();
|
||||
LogicalResult emitMemoryMetadata();
|
||||
void getDependentDialects(mlir::DialectRegistry ®istry) const override;
|
||||
void runOnOperation() override;
|
||||
|
||||
// The set of all modules underneath the design under test module.
|
||||
DenseSet<Operation *> dutModuleSet;
|
||||
// The design under test module.
|
||||
FModuleOp dutMod;
|
||||
};
|
||||
} // end anonymous namespace
|
||||
|
||||
/// This function collects all the firrtl.mem ops and creates a verbatim op with
|
||||
/// the relevant memory attributes.
|
||||
LogicalResult CreateSiFiveMetadataPass::emitMemoryMetadata() {
|
||||
|
||||
// Lambda to get the number of read, write and read-write ports corresponding
|
||||
// to a MemOp.
|
||||
|
||||
CircuitOp circuitOp = getOperation();
|
||||
// The instance graph analysis will be required to print the hierarchy names
|
||||
// of the memory.
|
||||
auto instancePathCache = InstancePathCache(getAnalysis<InstanceGraph>());
|
||||
|
||||
auto printAttr = [&](llvm::json::OStream &jsonStream, Attribute &verifValAttr,
|
||||
std::string &id) {
|
||||
if (auto intV = verifValAttr.dyn_cast<IntegerAttr>())
|
||||
jsonStream.attribute(id, (int64_t)intV.getValue().getZExtValue());
|
||||
else if (auto strV = verifValAttr.dyn_cast<StringAttr>())
|
||||
jsonStream.attribute(id, strV.getValue().str());
|
||||
else if (auto arrV = verifValAttr.dyn_cast<ArrayAttr>()) {
|
||||
std::string indices;
|
||||
jsonStream.attributeArray(id, [&] {
|
||||
for (auto arrI : llvm::enumerate(arrV)) {
|
||||
auto i = arrI.value();
|
||||
if (auto intV = i.dyn_cast<IntegerAttr>())
|
||||
jsonStream.value(std::to_string(intV.getValue().getZExtValue()));
|
||||
else if (auto strV = i.dyn_cast<StringAttr>())
|
||||
jsonStream.value(strV.getValue().str());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
// This lambda, writes to the given Json stream all the relevant memory
|
||||
// attributes. Also adds the memory attrbutes to the string for creating the
|
||||
// memmory conf file.
|
||||
auto createMemMetadata = [&](MemOp memOp, llvm::json::OStream &jsonStream,
|
||||
std::string &seqMemConfStr) {
|
||||
size_t numReadPorts = 0, numWritePorts = 0, numReadWritePorts = 0;
|
||||
// Get the number of read,write ports.
|
||||
memOp.getNumPorts(numReadPorts, numWritePorts, numReadWritePorts);
|
||||
// Get the memory data width.
|
||||
auto width = memOp.getDataType().getBitWidthOrSentinel();
|
||||
// Metadata needs to be printed for memories which are candidates for macro
|
||||
// replacement. The requirements for macro replacement::
|
||||
// 1. read latency and write latency of one.
|
||||
// 2. only one readwrite port or write port.
|
||||
// 3. zero or one read port.
|
||||
// 4. undefined read-under-write behavior.
|
||||
if (!((memOp.readLatency() == 1 && memOp.writeLatency() == 1) &&
|
||||
(numWritePorts + numReadWritePorts == 1) && (numReadPorts <= 1) &&
|
||||
width > 0))
|
||||
return;
|
||||
// Get the absolute path for the parent memory, to create the hierarchy
|
||||
// names.
|
||||
auto paths =
|
||||
instancePathCache.getAbsolutePaths(memOp->getParentOfType<FModuleOp>());
|
||||
|
||||
AnnotationSet anno = AnnotationSet(memOp);
|
||||
DictionaryAttr verifData = {};
|
||||
// Get the verification data attached with the memory op, if any.
|
||||
if (auto v = anno.getAnnotation(seqMemAnnoClass)) {
|
||||
verifData = v.get("data").cast<DictionaryAttr>();
|
||||
AnnotationSet::removeAnnotations(memOp, seqMemAnnoClass);
|
||||
}
|
||||
|
||||
// Compute the mask granularity.
|
||||
auto maskGran = width / memOp.getMaskBits();
|
||||
// Now create the config string for the memory.
|
||||
std::string portStr;
|
||||
if (numWritePorts)
|
||||
portStr += "mwrite";
|
||||
if (numReadPorts) {
|
||||
if (!portStr.empty())
|
||||
portStr += ",";
|
||||
portStr += "read";
|
||||
}
|
||||
if (numReadWritePorts)
|
||||
portStr = "mrw";
|
||||
seqMemConfStr += "name " + memOp.name().str() + " depth " +
|
||||
std::to_string(memOp.depth()) + " width " +
|
||||
std::to_string(width) + " ports " + portStr +
|
||||
" mask_gran " + std::to_string(maskGran) + "\n";
|
||||
// This adds a Json array element entry corresponding to this memory.
|
||||
jsonStream.object([&] {
|
||||
jsonStream.attribute("module_name", memOp.name());
|
||||
jsonStream.attribute("depth", (int64_t)memOp.depth());
|
||||
jsonStream.attribute("width", (int64_t)width);
|
||||
jsonStream.attribute("masked", "true");
|
||||
jsonStream.attribute("read", numReadPorts ? "true" : "false");
|
||||
jsonStream.attribute("write", numWritePorts ? "true" : "false");
|
||||
jsonStream.attribute("readwrite", numReadWritePorts ? "true" : "false");
|
||||
jsonStream.attribute("mask_granularity", (int64_t)maskGran);
|
||||
jsonStream.attributeArray("extra_ports", [&] {});
|
||||
// Record all the hierarchy names.
|
||||
SmallVector<std::string> hierNames;
|
||||
jsonStream.attributeArray("hierarchy", [&] {
|
||||
for (auto p : paths) {
|
||||
const InstanceOp &x = p.front();
|
||||
std::string hierName =
|
||||
x->getParentOfType<FModuleOp>().getName().str();
|
||||
for (InstanceOp inst : p) {
|
||||
hierName = hierName + "." + inst.name().str();
|
||||
}
|
||||
hierNames.push_back(hierName);
|
||||
jsonStream.value(hierName);
|
||||
}
|
||||
});
|
||||
// If verification annotation added to the memory op then print the data.
|
||||
if (verifData)
|
||||
jsonStream.attributeObject("verification_only_data", [&] {
|
||||
for (auto name : hierNames) {
|
||||
jsonStream.attributeObject(name, [&] {
|
||||
for (auto data : verifData) {
|
||||
// Id for the memory verification property.
|
||||
std::string id = data.first.strref().str();
|
||||
// Value for the property.
|
||||
auto verifValAttr = data.second;
|
||||
// Now print the value attribute based on its type.
|
||||
printAttr(jsonStream, verifValAttr, id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
std::string testBenchJsonBuffer;
|
||||
llvm::raw_string_ostream testBenchOs(testBenchJsonBuffer);
|
||||
llvm::json::OStream testBenchJson(testBenchOs);
|
||||
std::string dutJsonBuffer;
|
||||
llvm::raw_string_ostream dutOs(dutJsonBuffer);
|
||||
llvm::json::OStream dutJson(dutOs);
|
||||
SmallVector<MemOp> dutMems;
|
||||
SmallVector<MemOp> tbMems;
|
||||
|
||||
for (auto mod : circuitOp.getOps<FModuleOp>()) {
|
||||
bool isDut = dutModuleSet.contains(mod);
|
||||
for (auto memOp : mod.getBody()->getOps<MemOp>())
|
||||
if (isDut)
|
||||
dutMems.push_back(memOp);
|
||||
else
|
||||
tbMems.push_back(memOp);
|
||||
}
|
||||
std::string seqMemConfStr, tbConfStr;
|
||||
dutJson.array([&] {
|
||||
for (auto memOp : dutMems)
|
||||
createMemMetadata(memOp, dutJson, seqMemConfStr);
|
||||
});
|
||||
testBenchJson.array([&] {
|
||||
// The tbConfStr is populated here, but unused, it will not be printed to
|
||||
// file.
|
||||
for (auto memOp : tbMems)
|
||||
createMemMetadata(memOp, testBenchJson, tbConfStr);
|
||||
});
|
||||
|
||||
auto *context = &getContext();
|
||||
auto builder = OpBuilder::atBlockEnd(circuitOp.getBody());
|
||||
AnnotationSet annos(circuitOp);
|
||||
auto dirAnno = annos.getAnnotation(metadataDirectoryAnnoClass);
|
||||
StringRef metadataDir = "metadata";
|
||||
if (dirAnno)
|
||||
if (auto dir = dirAnno.getAs<StringAttr>("dirname"))
|
||||
metadataDir = dir.getValue();
|
||||
|
||||
// Use unknown loc to avoid printing the location in the metadata files.
|
||||
auto tbVerbatimOp = builder.create<sv::VerbatimOp>(builder.getUnknownLoc(),
|
||||
testBenchJsonBuffer);
|
||||
auto dutVerbatimOp =
|
||||
builder.create<sv::VerbatimOp>(builder.getUnknownLoc(), dutJsonBuffer);
|
||||
auto confVerbatimOp =
|
||||
builder.create<sv::VerbatimOp>(builder.getUnknownLoc(), seqMemConfStr);
|
||||
auto fileAttr = hw::OutputFileAttr::getFromDirectoryAndFilename(
|
||||
context, metadataDir, "seq_mems.json", /*excludeFromFilelist=*/true);
|
||||
dutVerbatimOp->setAttr("output_file", fileAttr);
|
||||
fileAttr = hw::OutputFileAttr::getFromDirectoryAndFilename(
|
||||
context, metadataDir, "tb_seq_mems.json", /*excludeFromFilelist=*/true);
|
||||
tbVerbatimOp->setAttr("output_file", fileAttr);
|
||||
StringRef confFile = "memory";
|
||||
if (dutMod)
|
||||
confFile = dutMod.getName();
|
||||
fileAttr = hw::OutputFileAttr::getFromDirectoryAndFilename(
|
||||
context, metadataDir, confFile + ".conf", /*excludeFromFilelist=*/true);
|
||||
confVerbatimOp->setAttr("output_file", fileAttr);
|
||||
|
||||
return success();
|
||||
}
|
||||
/// This will search for a target annotation and remove it from the operation.
|
||||
/// If the annotation has a filename, it will be returned in the output
|
||||
/// argument. If the annotation is missing the filename member, or if more than
|
||||
|
@ -142,7 +339,6 @@ LogicalResult CreateSiFiveMetadataPass::emitSitestBlackboxMetadata() {
|
|||
"sifive.enterprise.firrtl.SitestBlackBoxAnnotation";
|
||||
auto *testBlackboxAnnoClass =
|
||||
"sifive.enterprise.firrtl.SitestTestHarnessBlackBoxAnnotation";
|
||||
auto *dutAnnoClass = "sifive.enterprise.firrtl.MarkDUTAnnotation";
|
||||
|
||||
// Any extmodule with these annotations or one of these ScalaClass classes
|
||||
// should be excluded from the blackbox list.
|
||||
|
@ -172,21 +368,6 @@ LogicalResult CreateSiFiveMetadataPass::emitSitestBlackboxMetadata() {
|
|||
if (dutFilename.empty() && testFilename.empty())
|
||||
return success();
|
||||
|
||||
auto *body = circuitOp.getBody();
|
||||
|
||||
// Find the device under test and create a set of all modules underneath it.
|
||||
DenseSet<Operation *> dutModuleSet;
|
||||
auto it = llvm::find_if(*body, [&](Operation &op) -> bool {
|
||||
return AnnotationSet(&op).hasAnnotation(dutAnnoClass);
|
||||
});
|
||||
if (it != body->end()) {
|
||||
auto instanceGraph = getAnalysis<InstanceGraph>();
|
||||
auto *node = instanceGraph.lookup(&(*it));
|
||||
llvm::for_each(llvm::depth_first(node), [&](InstanceGraphNode *node) {
|
||||
dutModuleSet.insert(node->getModule());
|
||||
});
|
||||
}
|
||||
|
||||
// Find all extmodules in the circuit. Check if they are black-listed from
|
||||
// being included in the list. If they are not, separate them into two groups
|
||||
// depending on if theyre in the DUT or the test harness.
|
||||
|
@ -242,6 +423,7 @@ LogicalResult CreateSiFiveMetadataPass::emitSitestBlackboxMetadata() {
|
|||
j.value(name);
|
||||
});
|
||||
|
||||
auto *body = circuitOp.getBody();
|
||||
// Put the information in a verbatim operation.
|
||||
auto builder = OpBuilder::atBlockEnd(body);
|
||||
auto verbatimOp =
|
||||
|
@ -263,8 +445,23 @@ void CreateSiFiveMetadataPass::getDependentDialects(
|
|||
}
|
||||
|
||||
void CreateSiFiveMetadataPass::runOnOperation() {
|
||||
auto circuitOp = getOperation();
|
||||
auto *body = circuitOp.getBody();
|
||||
|
||||
// Find the device under test and create a set of all modules underneath it.
|
||||
auto it = llvm::find_if(*body, [&](Operation &op) -> bool {
|
||||
return AnnotationSet(&op).hasAnnotation(dutAnnoClass);
|
||||
});
|
||||
if (it != body->end()) {
|
||||
dutMod = dyn_cast<FModuleOp>(*it);
|
||||
auto instanceGraph = getAnalysis<InstanceGraph>();
|
||||
auto *node = instanceGraph.lookup(&(*it));
|
||||
llvm::for_each(llvm::depth_first(node), [&](InstanceGraphNode *node) {
|
||||
dutModuleSet.insert(node->getModule());
|
||||
});
|
||||
}
|
||||
if (failed(emitRetimeModulesMetadata()) ||
|
||||
failed(emitSitestBlackboxMetadata()))
|
||||
failed(emitSitestBlackboxMetadata()) || failed(emitMemoryMetadata()))
|
||||
return signalPassFailure();
|
||||
|
||||
// This pass does not modify the hierarchy.
|
||||
|
|
|
@ -102,3 +102,26 @@ firrtl.circuit "BasicBlackboxes" attributes { annotations = [{
|
|||
firrtl.extmodule @DUTBlackbox_2() attributes {defname = "DUTBlackbox1"}
|
||||
// CHECK: sv.verbatim "[\22DUTBlackbox1\22,\22DUTBlackbox2\22]" {output_file = #hw.output_file<"dut_blackboxes.json", excludeFromFileList>, symbols = []}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: firrtl.circuit "top"
|
||||
firrtl.circuit "top"
|
||||
{
|
||||
firrtl.module @top() {
|
||||
firrtl.instance @dut {name = "dut"}
|
||||
firrtl.instance @Mem1 {name = "mem1"}
|
||||
}
|
||||
firrtl.module @Mem1() {
|
||||
%head_MPORT_2 = firrtl.mem Undefined {depth = 20 : i64, name = "head", portNames = ["MPORT_2", "MPORT_6"], readLatency = 1 : i32, writeLatency = 1 : i32} : !firrtl.bundle<addr: uint<5>, en: uint<1>, clk: clock, data: uint<5>, mask: uint<1>>
|
||||
}
|
||||
firrtl.module @dut()attributes {annotations = [
|
||||
{class = "sifive.enterprise.firrtl.MarkDUTAnnotation"}]} {
|
||||
firrtl.instance @Mem {name = "mem1"}
|
||||
}
|
||||
firrtl.module @Mem() {
|
||||
%memory_rw, %memory_rw_r = firrtl.mem Undefined {annotations = [{class = "sifive.enterprise.firrtl.SeqMemInstanceMetadataAnnotation", data = {baseAddress = 2147483648 : i64, dataBits = 8 : i64, eccBits = 0 : i64, eccIndices = [], eccScheme = "none"}}], depth = 16 : i64, name = "memory", portNames = ["rw", "rw_r", "rw_w"], readLatency = 1 : i32, writeLatency = 1 : i32} : !firrtl.bundle<addr: uint<4>, en: uint<1>, clk: clock, rdata flip: uint<8>, wmode: uint<1>, wdata: uint<8>, wmask: uint<1>>, !firrtl.bundle<addr: uint<4>, en: uint<1>, clk: clock, data flip: uint<8>>
|
||||
%head_MPORT_2, %head_MPORT_6 = firrtl.mem Undefined {depth = 20 : i64, name = "dumm", portNames = ["MPORT_2", "MPORT_6"], readLatency = 0 : i32, writeLatency = 1 : i32} : !firrtl.bundle<addr: uint<5>, en: uint<1>, clk: clock, data: uint<5>, mask: uint<1>>, !firrtl.bundle<addr: uint<5>, en: uint<1>, clk: clock, data: uint<5>, mask: uint<1>>
|
||||
}
|
||||
// CHECK: sv.verbatim "[{\22module_name\22:\22head\22,\22depth\22:20,\22width\22:5,\22masked\22:\22true\22,\22read\22:\22false\22,\22write\22:\22true\22,\22readwrite\22:\22false\22,\22mask_granularity\22:5,\22extra_ports\22:[],\22hierarchy\22:[\22top.mem1\22]}]" {output_file = #hw.output_file<"metadata/tb_seq_mems.json", excludeFromFileList>, symbols = []}
|
||||
// CHECK: sv.verbatim "[{\22module_name\22:\22memory\22,\22depth\22:16,\22width\22:8,\22masked\22:\22true\22,\22read\22:\22true\22,\22write\22:\22false\22,\22readwrite\22:\22true\22,\22mask_granularity\22:8,\22extra_ports\22:[],\22hierarchy\22:[\22top.dut.mem1\22],\22verification_only_data\22:{\22top.dut.mem1\22:{\22baseAddress\22:2147483648,\22dataBits\22:8,\22eccBits\22:0,\22eccIndices\22:[],\22eccScheme\22:\22none\22}}}]" {output_file = #hw.output_file<"metadata/seq_mems.json", excludeFromFileList>, symbols = []}
|
||||
// CHECK: sv.verbatim "name memory depth 16 width 8 ports mrw mask_gran 8\0A" {output_file = #hw.output_file<"metadata/dut.conf", excludeFromFileList>, symbols = []}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
; RUN: firtool %s --format=fir --annotation-file %s.anno.json -emit-metadata --verilog | FileCheck %s
|
||||
|
||||
circuit test:
|
||||
module memoryTest1:
|
||||
input clock: Clock
|
||||
input rAddr: UInt<4>
|
||||
input rEn: UInt<1>
|
||||
output rData: UInt<8>
|
||||
input wMask: UInt<1>
|
||||
input wData: UInt<8>
|
||||
|
||||
mem memory:
|
||||
data-type => UInt<8>
|
||||
depth => 16
|
||||
reader => r
|
||||
writer => w
|
||||
read-latency => 1
|
||||
write-latency => 1
|
||||
read-under-write => undefined
|
||||
|
||||
; All of these are unified together
|
||||
memory.r.clk <= clock
|
||||
memory.r.en <= rEn
|
||||
memory.r.addr <= rAddr
|
||||
rData <= memory.r.data
|
||||
|
||||
memory.w.clk <= clock
|
||||
memory.w.en <= rEn
|
||||
memory.w.addr <= rAddr
|
||||
; These two are split
|
||||
memory.w.mask <= wMask
|
||||
memory.w.data <= wData
|
||||
|
||||
module memoryTest2:
|
||||
input clock: Clock
|
||||
input rAddr: UInt<4>
|
||||
input rEn: UInt<1>
|
||||
output rData: UInt<8>
|
||||
input wMask: UInt<1>
|
||||
input wData: UInt<8>
|
||||
|
||||
mem memory:
|
||||
data-type => UInt<8>
|
||||
depth => 16
|
||||
reader => r
|
||||
writer => w
|
||||
read-latency => 1
|
||||
write-latency => 1
|
||||
read-under-write => undefined
|
||||
|
||||
; All of these are unified together
|
||||
memory.r.clk <= clock
|
||||
memory.r.en <= rEn
|
||||
memory.r.addr <= rAddr
|
||||
rData <= memory.r.data
|
||||
|
||||
memory.w.clk <= clock
|
||||
memory.w.en <= rEn
|
||||
memory.w.addr <= rAddr
|
||||
; These two are split
|
||||
memory.w.mask <= wMask
|
||||
memory.w.data <= wData
|
||||
|
||||
|
||||
module test:
|
||||
input clock: Clock
|
||||
input rAddr: UInt<4>
|
||||
input rEn: UInt<1>
|
||||
output rData: UInt<8>
|
||||
input wMask: UInt<1>
|
||||
input wData: UInt<8>
|
||||
|
||||
|
||||
inst m of memoryTest1
|
||||
m.clock <= clock
|
||||
m.rAddr <= rAddr
|
||||
m.rEn <= rEn
|
||||
rData <= m.rData
|
||||
m.wMask <= wMask
|
||||
m.wData <= wData
|
||||
|
||||
inst m2 of memoryP
|
||||
m2.clock <= clock
|
||||
m2.rAddr <= rAddr
|
||||
m2.rEn <= rEn
|
||||
rData <= m2.rData
|
||||
m2.wMask <= wMask
|
||||
m2.wData <= wData
|
||||
|
||||
|
||||
module memoryP:
|
||||
input clock: Clock
|
||||
input rAddr: UInt<4>
|
||||
input rEn: UInt<1>
|
||||
output rData: UInt<8>
|
||||
input wMask: UInt<1>
|
||||
input wData: UInt<8>
|
||||
|
||||
|
||||
inst m of memoryTest2
|
||||
m.clock <= clock
|
||||
m.rAddr <= rAddr
|
||||
m.rEn <= rEn
|
||||
rData <= m.rData
|
||||
m.wMask <= wMask
|
||||
m.wData <= wData
|
||||
|
||||
; CHECK-LABEL: module test
|
||||
; CHECK: memoryP [[m2:.+]] (
|
||||
|
||||
; CHECK-LABEL: module memoryP
|
||||
; CHECK: memoryTest2 [[m:.+]] (
|
||||
|
||||
; CHECK: FILE "metadata/tb_seq_mems.json"
|
||||
; CHECK: [{"module_name":"memory","depth":16,"width":8,"masked":"true",
|
||||
; CHECK-SAME: "read":"true","write":"true","readwrite":"false",
|
||||
; CHECK_SAME: "mask_granularity":8,"extra_ports":[],
|
||||
; CHECK-SAME: "hierarchy":["test.[[m]]"]}]
|
||||
|
||||
; CHECK: FILE "metadata/seq_mems.json"
|
||||
; CHECK: [{"module_name":"memory","depth":16,"width":8,"masked":"true",
|
||||
; CHECK-SAME: "read":"true","write":"true","readwrite":"false",
|
||||
; CHECK_SAME: "mask_granularity":8,"extra_ports":[],
|
||||
; CHECK-SAME: "hierarchy":["test.[[m2]].[[m]]"],
|
||||
; CHECK-SAME: "verification_only_data":{
|
||||
; CHECK-SAME: "test.[[m2]].[[m]]":{"baseAddress":1073741824,
|
||||
; CHECK-SAME: "dataBits":8,"eccBits":0,"eccIndices":[],"eccScheme":"none"}}}]
|
||||
|
||||
; CHECK: FILE "metadata/memoryP.conf"
|
||||
; CHECK: name memory depth 16 width 8 ports mwrite,read mask_gran 8
|
|
@ -0,0 +1,17 @@
|
|||
[
|
||||
{
|
||||
"class":"sifive.enterprise.firrtl.MarkDUTAnnotation",
|
||||
"target": "test.memoryP"
|
||||
},
|
||||
{
|
||||
"class":"sifive.enterprise.firrtl.SeqMemInstanceMetadataAnnotation",
|
||||
"data":{
|
||||
"baseAddress":1073741824,
|
||||
"eccScheme":"none",
|
||||
"eccBits":0,
|
||||
"dataBits":8,
|
||||
"eccIndices":[]
|
||||
},
|
||||
"target":"~test|memoryTest2>memory"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,129 @@
|
|||
; RUN: firtool %s --format=fir --annotation-file %s.anno.json -emit-metadata --verilog | FileCheck %s
|
||||
; XFAIL: true
|
||||
|
||||
circuit test:
|
||||
module memoryTest1:
|
||||
input clock: Clock
|
||||
input rAddr: UInt<4>
|
||||
input rEn: UInt<1>
|
||||
output rData: UInt<8>
|
||||
input wMask: UInt<1>
|
||||
input wData: UInt<8>
|
||||
|
||||
mem memory:
|
||||
data-type => UInt<8>
|
||||
depth => 16
|
||||
reader => r
|
||||
writer => w
|
||||
read-latency => 1
|
||||
write-latency => 1
|
||||
read-under-write => undefined
|
||||
|
||||
; All of these are unified together
|
||||
memory.r.clk <= clock
|
||||
memory.r.en <= rEn
|
||||
memory.r.addr <= rAddr
|
||||
rData <= memory.r.data
|
||||
|
||||
memory.w.clk <= clock
|
||||
memory.w.en <= rEn
|
||||
memory.w.addr <= rAddr
|
||||
; These two are split
|
||||
memory.w.mask <= wMask
|
||||
memory.w.data <= wData
|
||||
|
||||
module memoryTest2:
|
||||
input clock: Clock
|
||||
input rAddr: UInt<4>
|
||||
input rEn: UInt<1>
|
||||
output rData: UInt<8>
|
||||
input wMask: UInt<1>
|
||||
input wData: UInt<8>
|
||||
|
||||
mem memory:
|
||||
data-type => UInt<8>
|
||||
depth => 16
|
||||
reader => r
|
||||
writer => w
|
||||
read-latency => 1
|
||||
write-latency => 1
|
||||
read-under-write => undefined
|
||||
|
||||
; All of these are unified together
|
||||
memory.r.clk <= clock
|
||||
memory.r.en <= rEn
|
||||
memory.r.addr <= rAddr
|
||||
rData <= memory.r.data
|
||||
|
||||
memory.w.clk <= clock
|
||||
memory.w.en <= rEn
|
||||
memory.w.addr <= rAddr
|
||||
; These two are split
|
||||
memory.w.mask <= wMask
|
||||
memory.w.data <= wData
|
||||
|
||||
|
||||
module test:
|
||||
input clock: Clock
|
||||
input rAddr: UInt<4>
|
||||
input rEn: UInt<1>
|
||||
output rData: UInt<8>
|
||||
input wMask: UInt<1>
|
||||
input wData: UInt<8>
|
||||
|
||||
|
||||
inst m of memoryTest1
|
||||
m.clock <= clock
|
||||
m.rAddr <= rAddr
|
||||
m.rEn <= rEn
|
||||
rData <= m.rData
|
||||
m.wMask <= wMask
|
||||
m.wData <= wData
|
||||
|
||||
inst signed of memoryP
|
||||
signed.clock <= clock
|
||||
signed.rAddr <= rAddr
|
||||
signed.rEn <= rEn
|
||||
rData <= signed.rData
|
||||
signed.wMask <= wMask
|
||||
signed.wData <= wData
|
||||
|
||||
|
||||
module memoryP:
|
||||
input clock: Clock
|
||||
input rAddr: UInt<4>
|
||||
input rEn: UInt<1>
|
||||
output rData: UInt<8>
|
||||
input wMask: UInt<1>
|
||||
input wData: UInt<8>
|
||||
|
||||
|
||||
inst m of memoryTest2
|
||||
m.clock <= clock
|
||||
m.rAddr <= rAddr
|
||||
m.rEn <= rEn
|
||||
rData <= m.rData
|
||||
m.wMask <= wMask
|
||||
m.wData <= wData
|
||||
|
||||
; CHECK-LABEL: module test
|
||||
; CHECK: memoryP [[signed:.+]] (
|
||||
|
||||
; CHECK-LABEL: module memoryP
|
||||
; CHECK: memoryTest2 [[m:.+]] (
|
||||
|
||||
; CHECK: FILE "metadata/tb_seq_mems.json"
|
||||
; CHECK: [{"module_name":"memory","depth":16,"width":8,"masked":"true",
|
||||
; CHECK-SAME: "read":"true","write":"true","readwrite":"false",
|
||||
; CHECK_SAME: "mask_granularity":8,"extra_ports":[],
|
||||
; CHECK-SAME: "hierarchy":["test.[[m]]"]}]
|
||||
|
||||
; CHECK: FILE "metadata/seq_mems.json"
|
||||
; CHECK: [{"module_name":"memory","depth":16,"width":8,"masked":"true",
|
||||
; CHECK-SAME: "read":"true","write":"true","readwrite":"false",
|
||||
; CHECK_SAME: "mask_granularity":8,"extra_ports":[],
|
||||
; CHECK-SAME: "hierarchy":["test.[[signed]].[[m]]"],
|
||||
; CHECK-SAME: "verification_only_data":{
|
||||
; CHECK-SAME: "test.[[signed]].[[m]]":{"baseAddress":1073741824,
|
||||
; CHECK-SAME: "dataBits":8,"eccBits":0,"eccIndices":[],"eccScheme":"none"}}}]
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
[
|
||||
{
|
||||
"class":"sifive.enterprise.firrtl.MarkDUTAnnotation",
|
||||
"target": "test.memoryP"
|
||||
},
|
||||
{
|
||||
"class":"sifive.enterprise.firrtl.SeqMemInstanceMetadataAnnotation",
|
||||
"data":{
|
||||
"baseAddress":1073741824,
|
||||
"eccScheme":"none",
|
||||
"eccBits":0,
|
||||
"dataBits":8,
|
||||
"eccIndices":[]
|
||||
},
|
||||
"target":"~test|memoryTest2>memory"
|
||||
}
|
||||
]
|
Loading…
Reference in New Issue