memory: Introduce $meminit_v2 cell, with EN input.

This commit is contained in:
Marcelina Kościelnicka 2021-05-21 02:26:52 +02:00
parent 37d76deef1
commit 19720b970d
10 changed files with 86 additions and 13 deletions

View File

@ -70,6 +70,7 @@ Yosys 0.9 .. Yosys 0.9-dev
- Added "dfflegalize" pass - Added "dfflegalize" pass
- Added "_TECHMAP_CELLNAME_" parameter for "techmap" pass - Added "_TECHMAP_CELLNAME_" parameter for "techmap" pass
- Merged "dffsr2dff", "opt_rmdff", "dff2dffe", "dff2dffs", "peepopt.dffmux" passes into a new "opt_dff" 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 Yosys 0.8 .. Yosys 0.9
---------------------- ----------------------

View File

@ -157,6 +157,7 @@ struct CellTypes
setup_type(ID($memrd), {ID::CLK, ID::EN, ID::ADDR}, {ID::DATA}); 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($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), {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($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}); setup_type(ID($fsm), {ID::CLK, ID::ARST, ID::CTRL_IN}, {ID::CTRL_OUT});

View File

@ -263,8 +263,11 @@ void Mem::emit() {
} }
idx = 0; idx = 0;
for (auto &init : inits) { for (auto &init : inits) {
bool v2 = !init.en.is_fully_ones();
if (!init.cell) 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->attributes = init.attributes;
init.cell->parameters[ID::MEMID] = memid.str(); init.cell->parameters[ID::MEMID] = memid.str();
init.cell->parameters[ID::ABITS] = GetSize(init.addr); init.cell->parameters[ID::ABITS] = GetSize(init.addr);
@ -273,6 +276,10 @@ void Mem::emit() {
init.cell->parameters[ID::PRIORITY] = idx++; init.cell->parameters[ID::PRIORITY] = idx++;
init.cell->setPort(ID::ADDR, init.addr); init.cell->setPort(ID::ADDR, init.addr);
init.cell->setPort(ID::DATA, init.data); 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) { for (auto &init : inits) {
if (init.removed) if (init.removed)
continue; 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 = init.addr.as_int();
int addr_e = addr + GetSize(init.data) / width; int addr_e = addr + GetSize(init.data) / width;
auto it_e = chunks.upper_bound(addr_e); auto it_e = chunks.upper_bound(addr_e);
@ -335,6 +350,13 @@ void Mem::coalesce_inits() {
int caddr_e = chunks[caddr]; int caddr_e = chunks[caddr];
auto &chunk_inits = it.second; auto &chunk_inits = it.second;
if (GetSize(chunk_inits) == 1) { 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; continue;
} }
Const cdata(State::Sx, (caddr_e - caddr) * width); Const cdata(State::Sx, (caddr_e - caddr) * width);
@ -344,12 +366,14 @@ void Mem::coalesce_inits() {
log_assert(offset >= 0); log_assert(offset >= 0);
log_assert(offset + GetSize(init.data) <= GetSize(cdata)); log_assert(offset + GetSize(init.data) <= GetSize(cdata));
for (int i = 0; i < GetSize(init.data); i++) for (int i = 0; i < GetSize(init.data); i++)
if (init.en[i % width] == State::S1)
cdata.bits[i+offset] = init.data.bits[i]; cdata.bits[i+offset] = init.data.bits[i];
init.removed = true; init.removed = true;
} }
MemInit new_init; MemInit new_init;
new_init.addr = caddr; new_init.addr = caddr;
new_init.data = cdata; new_init.data = cdata;
new_init.en = Const(State::S1, width);
inits.push_back(new_init); inits.push_back(new_init);
} }
} }
@ -361,7 +385,7 @@ Const Mem::get_init_data() const {
continue; continue;
int offset = (init.addr.as_int() - start_offset) * width; int offset = (init.addr.as_int() - start_offset) * width;
for (int i = 0; i < GetSize(init.data); i++) 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]; init_data.bits[i+offset] = init.data.bits[i];
} }
return init_data; return init_data;
@ -432,7 +456,7 @@ namespace {
wr_ports[cell->parameters.at(ID::MEMID).decode_string()].insert(cell); wr_ports[cell->parameters.at(ID::MEMID).decode_string()].insert(cell);
else if (cell->type == ID($memrd)) else if (cell->type == ID($memrd))
rd_ports[cell->parameters.at(ID::MEMID).decode_string()].insert(cell); 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); 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)); log_error("Non-constant data %s in memory initialization %s.\n", log_signal(data), log_id(cell));
init.addr = addr.as_const(); init.addr = addr.as_const();
init.data = data.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)); 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; }); 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; MemInit minit;
minit.addr = res.start_offset + pos; minit.addr = res.start_offset + pos;
minit.data = init.extract(pos * res.width, (epos - pos) * res.width, State::Sx); 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); res.inits.push_back(minit);
pos = epos; pos = epos;
} }

View File

@ -69,6 +69,7 @@ struct MemInit : RTLIL::AttrObject {
Cell *cell; Cell *cell;
Const addr; Const addr;
Const data; Const data;
Const en;
MemInit() : removed(false), cell(nullptr) {} MemInit() : removed(false), cell(nullptr) {}
}; };
@ -101,7 +102,8 @@ struct Mem : RTLIL::AttrObject {
// address ranges, they are combined into one, with the higher-priority // address ranges, they are combined into one, with the higher-priority
// one's data overwriting the other. Running this results in // one's data overwriting the other. Running this results in
// an inits list equivalent to the original, in which all entries // 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(); void coalesce_inits();
// Checks consistency of this memory and all its ports/inits, using // Checks consistency of this memory and all its ports/inits, using

View File

@ -1414,6 +1414,16 @@ namespace {
return; 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)) { if (cell->type == ID($mem)) {
param(ID::MEMID); param(ID::MEMID);
param(ID::SIZE); 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 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 bool RTLIL::Cell::is_mem_cell() const

View File

@ -338,14 +338,14 @@ In addition to {\tt \$dlatch} ports and parameters, they also have multi-bit
\subsection{Memories} \subsection{Memories}
\label{sec:memcells} \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. cells, or by {\tt \$mem} cells alone.
In the first alternative the RTLIL::Memory objects hold the general metadata for the memory (bit width, 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) 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 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 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. 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 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. The cell with the higher integer value in this parameter wins a write conflict.
\end{itemize} \end{itemize}
The {\tt \$meminit} cells have an address input \B{ADDR} and a data input \B{DATA}, with the width 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. Both of the inputs 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. must resolve to a constant for synthesis to succeed.
\begin{itemize} \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. This input is \B{WR\_PORTS}*\B{WIDTH} bits wide, containing all data signals for the write ports.
\end{itemize} \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. 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\_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. 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.

View File

@ -117,7 +117,7 @@ void rmunused_module_cells(Module *module, bool verbose)
} }
for (Cell *cell : module->cells()) { 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(); IdString mem_id = cell->getParam(ID::MEMID).decode_string();
mem2cells[mem_id].insert(cell); mem2cells[mem_id].insert(cell);
} }

View File

@ -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(); IdString memid = c->getParam(ID::MEMID).decode_string();
RTLIL::Memory *mem = module->memories.at(memid); RTLIL::Memory *mem = module->memories.at(memid);
if (mem->start_offset >= 0) { if (mem->start_offset >= 0) {

View File

@ -559,6 +559,7 @@ struct SimInstance
MemInit minit; MemInit minit;
minit.addr = mem.mem->start_offset; minit.addr = mem.mem->start_offset;
minit.data = mem.data; minit.data = mem.data;
minit.en = Const(State::S1, mem.mem->width);
mem.mem->inits.push_back(minit); mem.mem->inits.push_back(minit);
mem.mem->emit(); mem.mem->emit();
} }

View File

@ -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); module \$mem (RD_CLK, RD_EN, RD_ADDR, RD_DATA, WR_CLK, WR_EN, WR_ADDR, WR_DATA);
parameter MEMID = ""; parameter MEMID = "";