mirror of https://github.com/YosysHQ/yosys.git
memory: Introduce $meminit_v2 cell, with EN input.
This commit is contained in:
parent
37d76deef1
commit
19720b970d
|
@ -70,6 +70,7 @@ Yosys 0.9 .. Yosys 0.9-dev
|
|||
- Added "dfflegalize" pass
|
||||
- Added "_TECHMAP_CELLNAME_" parameter for "techmap" pass
|
||||
- Merged "dffsr2dff", "opt_rmdff", "dff2dffe", "dff2dffs", "peepopt.dffmux" passes into a new "opt_dff" pass
|
||||
- Added $meminit_v2 cells (with support for write mask)
|
||||
|
||||
Yosys 0.8 .. Yosys 0.9
|
||||
----------------------
|
||||
|
|
|
@ -157,6 +157,7 @@ struct CellTypes
|
|||
setup_type(ID($memrd), {ID::CLK, ID::EN, ID::ADDR}, {ID::DATA});
|
||||
setup_type(ID($memwr), {ID::CLK, ID::EN, ID::ADDR, ID::DATA}, pool<RTLIL::IdString>());
|
||||
setup_type(ID($meminit), {ID::ADDR, ID::DATA}, pool<RTLIL::IdString>());
|
||||
setup_type(ID($meminit_v2), {ID::ADDR, ID::DATA, ID::EN}, pool<RTLIL::IdString>());
|
||||
setup_type(ID($mem), {ID::RD_CLK, ID::RD_EN, ID::RD_ADDR, ID::WR_CLK, ID::WR_EN, ID::WR_ADDR, ID::WR_DATA}, {ID::RD_DATA});
|
||||
|
||||
setup_type(ID($fsm), {ID::CLK, ID::ARST, ID::CTRL_IN}, {ID::CTRL_OUT});
|
||||
|
|
|
@ -263,8 +263,11 @@ void Mem::emit() {
|
|||
}
|
||||
idx = 0;
|
||||
for (auto &init : inits) {
|
||||
bool v2 = !init.en.is_fully_ones();
|
||||
if (!init.cell)
|
||||
init.cell = module->addCell(NEW_ID, ID($meminit));
|
||||
init.cell = module->addCell(NEW_ID, v2 ? ID($meminit_v2) : ID($meminit));
|
||||
else
|
||||
init.cell->type = v2 ? ID($meminit_v2) : ID($meminit);
|
||||
init.cell->attributes = init.attributes;
|
||||
init.cell->parameters[ID::MEMID] = memid.str();
|
||||
init.cell->parameters[ID::ABITS] = GetSize(init.addr);
|
||||
|
@ -273,6 +276,10 @@ void Mem::emit() {
|
|||
init.cell->parameters[ID::PRIORITY] = idx++;
|
||||
init.cell->setPort(ID::ADDR, init.addr);
|
||||
init.cell->setPort(ID::DATA, init.data);
|
||||
if (v2)
|
||||
init.cell->setPort(ID::EN, init.en);
|
||||
else
|
||||
init.cell->unsetPort(ID::EN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -289,6 +296,14 @@ void Mem::coalesce_inits() {
|
|||
for (auto &init : inits) {
|
||||
if (init.removed)
|
||||
continue;
|
||||
bool valid = false;
|
||||
for (auto bit : init.en)
|
||||
if (bit == State::S1)
|
||||
valid = true;
|
||||
if (!valid) {
|
||||
init.removed = true;
|
||||
continue;
|
||||
}
|
||||
int addr = init.addr.as_int();
|
||||
int addr_e = addr + GetSize(init.data) / width;
|
||||
auto it_e = chunks.upper_bound(addr_e);
|
||||
|
@ -335,6 +350,13 @@ void Mem::coalesce_inits() {
|
|||
int caddr_e = chunks[caddr];
|
||||
auto &chunk_inits = it.second;
|
||||
if (GetSize(chunk_inits) == 1) {
|
||||
auto &init = inits[chunk_inits[0]];
|
||||
if (!init.en.is_fully_ones()) {
|
||||
for (int i = 0; i < GetSize(init.data); i++)
|
||||
if (init.en[i % width] != State::S1)
|
||||
init.data[i] = State::Sx;
|
||||
init.en = Const(State::S1, width);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Const cdata(State::Sx, (caddr_e - caddr) * width);
|
||||
|
@ -344,12 +366,14 @@ void Mem::coalesce_inits() {
|
|||
log_assert(offset >= 0);
|
||||
log_assert(offset + GetSize(init.data) <= GetSize(cdata));
|
||||
for (int i = 0; i < GetSize(init.data); i++)
|
||||
cdata.bits[i+offset] = init.data.bits[i];
|
||||
if (init.en[i % width] == State::S1)
|
||||
cdata.bits[i+offset] = init.data.bits[i];
|
||||
init.removed = true;
|
||||
}
|
||||
MemInit new_init;
|
||||
new_init.addr = caddr;
|
||||
new_init.data = cdata;
|
||||
new_init.en = Const(State::S1, width);
|
||||
inits.push_back(new_init);
|
||||
}
|
||||
}
|
||||
|
@ -361,7 +385,7 @@ Const Mem::get_init_data() const {
|
|||
continue;
|
||||
int offset = (init.addr.as_int() - start_offset) * width;
|
||||
for (int i = 0; i < GetSize(init.data); i++)
|
||||
if (0 <= i+offset && i+offset < GetSize(init_data))
|
||||
if (0 <= i+offset && i+offset < GetSize(init_data) && init.en[i % width] == State::S1)
|
||||
init_data.bits[i+offset] = init.data.bits[i];
|
||||
}
|
||||
return init_data;
|
||||
|
@ -432,7 +456,7 @@ namespace {
|
|||
wr_ports[cell->parameters.at(ID::MEMID).decode_string()].insert(cell);
|
||||
else if (cell->type == ID($memrd))
|
||||
rd_ports[cell->parameters.at(ID::MEMID).decode_string()].insert(cell);
|
||||
else if (cell->type == ID($meminit))
|
||||
else if (cell->type.in(ID($meminit), ID($meminit_v2)))
|
||||
inits[cell->parameters.at(ID::MEMID).decode_string()].insert(cell);
|
||||
}
|
||||
}
|
||||
|
@ -507,6 +531,14 @@ namespace {
|
|||
log_error("Non-constant data %s in memory initialization %s.\n", log_signal(data), log_id(cell));
|
||||
init.addr = addr.as_const();
|
||||
init.data = data.as_const();
|
||||
if (cell->type == ID($meminit_v2)) {
|
||||
auto en = cell->getPort(ID::EN);
|
||||
if (!en.is_fully_const())
|
||||
log_error("Non-constant enable %s in memory initialization %s.\n", log_signal(en), log_id(cell));
|
||||
init.en = en.as_const();
|
||||
} else {
|
||||
init.en = RTLIL::Const(State::S1, mem->width);
|
||||
}
|
||||
inits.push_back(std::make_pair(cell->parameters.at(ID::PRIORITY).as_int(), init));
|
||||
}
|
||||
std::sort(inits.begin(), inits.end(), [](const std::pair<int, MemInit> &a, const std::pair<int, MemInit> &b) { return a.first < b.first; });
|
||||
|
@ -558,6 +590,7 @@ namespace {
|
|||
MemInit minit;
|
||||
minit.addr = res.start_offset + pos;
|
||||
minit.data = init.extract(pos * res.width, (epos - pos) * res.width, State::Sx);
|
||||
minit.en = RTLIL::Const(State::S1, res.width);
|
||||
res.inits.push_back(minit);
|
||||
pos = epos;
|
||||
}
|
||||
|
|
|
@ -69,6 +69,7 @@ struct MemInit : RTLIL::AttrObject {
|
|||
Cell *cell;
|
||||
Const addr;
|
||||
Const data;
|
||||
Const en;
|
||||
MemInit() : removed(false), cell(nullptr) {}
|
||||
};
|
||||
|
||||
|
@ -101,7 +102,8 @@ struct Mem : RTLIL::AttrObject {
|
|||
// address ranges, they are combined into one, with the higher-priority
|
||||
// one's data overwriting the other. Running this results in
|
||||
// an inits list equivalent to the original, in which all entries
|
||||
// cover disjoint (and non-touching) address ranges.
|
||||
// cover disjoint (and non-touching) address ranges, and all enable
|
||||
// masks are all-1.
|
||||
void coalesce_inits();
|
||||
|
||||
// Checks consistency of this memory and all its ports/inits, using
|
||||
|
|
|
@ -1414,6 +1414,16 @@ namespace {
|
|||
return;
|
||||
}
|
||||
|
||||
if (cell->type == ID($meminit_v2)) {
|
||||
param(ID::MEMID);
|
||||
param(ID::PRIORITY);
|
||||
port(ID::ADDR, param(ID::ABITS));
|
||||
port(ID::DATA, param(ID::WIDTH) * param(ID::WORDS));
|
||||
port(ID::EN, param(ID::WIDTH));
|
||||
check_expected();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cell->type == ID($mem)) {
|
||||
param(ID::MEMID);
|
||||
param(ID::SIZE);
|
||||
|
@ -3177,7 +3187,7 @@ void RTLIL::Cell::fixup_parameters(bool set_a_signed, bool set_b_signed)
|
|||
|
||||
bool RTLIL::Cell::has_memid() const
|
||||
{
|
||||
return type.in(ID($memwr), ID($memrd), ID($meminit));
|
||||
return type.in(ID($memwr), ID($memrd), ID($meminit), ID($meminit_v2));
|
||||
}
|
||||
|
||||
bool RTLIL::Cell::is_mem_cell() const
|
||||
|
|
|
@ -338,14 +338,14 @@ In addition to {\tt \$dlatch} ports and parameters, they also have multi-bit
|
|||
\subsection{Memories}
|
||||
\label{sec:memcells}
|
||||
|
||||
Memories are either represented using RTLIL::Memory objects, {\tt \$memrd}, {\tt \$memwr}, and {\tt \$meminit}
|
||||
Memories are either represented using RTLIL::Memory objects, {\tt \$memrd}, {\tt \$memwr}, and {\tt \$meminit\_v2}
|
||||
cells, or by {\tt \$mem} cells alone.
|
||||
|
||||
In the first alternative the RTLIL::Memory objects hold the general metadata for the memory (bit width,
|
||||
size in number of words, etc.) and for each port a {\tt \$memrd} (read port) or {\tt \$memwr} (write port)
|
||||
cell is created. Having individual cells for read and write ports has the advantage that they can be
|
||||
consolidated using resource sharing passes. In some cases this drastically reduces the number of required
|
||||
ports on the memory cell. In this alternative, memory initialization data is represented by {\tt \$meminit} cells,
|
||||
ports on the memory cell. In this alternative, memory initialization data is represented by {\tt \$meminit\_v2} cells,
|
||||
which allow delaying constant folding for initialization addresses and data until after the frontend finishes.
|
||||
|
||||
The {\tt \$memrd} cells have a clock input \B{CLK}, an enable input \B{EN}, an
|
||||
|
@ -401,8 +401,9 @@ edge if this parameter is {\tt 1'b0}.
|
|||
The cell with the higher integer value in this parameter wins a write conflict.
|
||||
\end{itemize}
|
||||
|
||||
The {\tt \$meminit} cells have an address input \B{ADDR} and a data input \B{DATA}, with the width
|
||||
of the \B{DATA} port equal to \B{WIDTH} parameter times \B{WORDS} parameter. Both of the inputs
|
||||
The {\tt \$meminit\_v2} cells have an address input \B{ADDR}, a data input \B{DATA}, with the width
|
||||
of the \B{DATA} port equal to \B{WIDTH} parameter times \B{WORDS} parameter, and a bit enable mask input
|
||||
\B{EN} with width equal to \B{WIDTH} parameter. All three of the inputs
|
||||
must resolve to a constant for synthesis to succeed.
|
||||
|
||||
\begin{itemize}
|
||||
|
@ -497,7 +498,7 @@ This input is \B{WR\_PORTS}*\B{ABITS} bits wide, containing all address signals
|
|||
This input is \B{WR\_PORTS}*\B{WIDTH} bits wide, containing all data signals for the write ports.
|
||||
\end{itemize}
|
||||
|
||||
The {\tt memory\_collect} pass can be used to convert discrete {\tt \$memrd}, {\tt \$memwr}, and {\tt \$meminit} cells
|
||||
The {\tt memory\_collect} pass can be used to convert discrete {\tt \$memrd}, {\tt \$memwr}, and {\tt \$meminit\_v2} cells
|
||||
belonging to the same memory to a single {\tt \$mem} cell, whereas the {\tt memory\_unpack} pass performs the inverse operation.
|
||||
The {\tt memory\_dff} pass can combine asynchronous memory ports that are fed by or feeding registers into synchronous memory ports.
|
||||
The {\tt memory\_bram} pass can be used to recognize {\tt \$mem} cells that can be implemented with a block RAM resource on an FPGA.
|
||||
|
|
|
@ -117,7 +117,7 @@ void rmunused_module_cells(Module *module, bool verbose)
|
|||
}
|
||||
|
||||
for (Cell *cell : module->cells()) {
|
||||
if (cell->type.in(ID($memwr), ID($meminit))) {
|
||||
if (cell->type.in(ID($memwr), ID($meminit), ID($meminit_v2))) {
|
||||
IdString mem_id = cell->getParam(ID::MEMID).decode_string();
|
||||
mem2cells[mem_id].insert(cell);
|
||||
}
|
||||
|
|
|
@ -558,7 +558,7 @@ struct WreducePass : public Pass {
|
|||
}
|
||||
}
|
||||
|
||||
if (!opt_memx && c->type.in(ID($memrd), ID($memwr), ID($meminit))) {
|
||||
if (!opt_memx && c->type.in(ID($memrd), ID($memwr), ID($meminit), ID($meminit_v2))) {
|
||||
IdString memid = c->getParam(ID::MEMID).decode_string();
|
||||
RTLIL::Memory *mem = module->memories.at(memid);
|
||||
if (mem->start_offset >= 0) {
|
||||
|
|
|
@ -559,6 +559,7 @@ struct SimInstance
|
|||
MemInit minit;
|
||||
minit.addr = mem.mem->start_offset;
|
||||
minit.data = mem.data;
|
||||
minit.en = Const(State::S1, mem.mem->width);
|
||||
mem.mem->inits.push_back(minit);
|
||||
mem.mem->emit();
|
||||
}
|
||||
|
|
|
@ -2233,6 +2233,30 @@ endmodule
|
|||
|
||||
// --------------------------------------------------------
|
||||
|
||||
module \$meminit_v2 (ADDR, DATA, EN);
|
||||
|
||||
parameter MEMID = "";
|
||||
parameter ABITS = 8;
|
||||
parameter WIDTH = 8;
|
||||
parameter WORDS = 1;
|
||||
|
||||
parameter PRIORITY = 0;
|
||||
|
||||
input [ABITS-1:0] ADDR;
|
||||
input [WORDS*WIDTH-1:0] DATA;
|
||||
input [WIDTH-1:0] EN;
|
||||
|
||||
initial begin
|
||||
if (MEMID != "") begin
|
||||
$display("ERROR: Found non-simulatable instance of $meminit_v2!");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
// --------------------------------------------------------
|
||||
|
||||
module \$mem (RD_CLK, RD_EN, RD_ADDR, RD_DATA, WR_CLK, WR_EN, WR_ADDR, WR_DATA);
|
||||
|
||||
parameter MEMID = "";
|
||||
|
|
Loading…
Reference in New Issue