blob: f8659ee452d3dcd5d3f665ea215806577fe18e59 [file] [log] [blame]
#include "kernel/sigtools.h"
#include "kernel/yosys.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
#include "pmgen/ql-bram-asymmetric-wider-read.h"
#include "pmgen/ql-bram-asymmetric-wider-write.h"
void test_ql_bram_asymmetric_wider_read(ql_bram_asymmetric_wider_read_pm &pm)
{
auto mem = pm.st_ql_bram_asymmetric_wider_read.mem;
auto mem_wr_addr = pm.st_ql_bram_asymmetric_wider_read.mem_wr_addr;
auto mem_rd_data = pm.st_ql_bram_asymmetric_wider_read.mem_rd_data;
auto mem_rd_addr = pm.st_ql_bram_asymmetric_wider_read.mem_rd_addr;
auto mux = pm.st_ql_bram_asymmetric_wider_read.mux;
auto mux_s = pm.st_ql_bram_asymmetric_wider_read.mux_s;
auto wr_en_shift = pm.st_ql_bram_asymmetric_wider_read.wr_en_shift;
auto wr_en_shift_b = pm.st_ql_bram_asymmetric_wider_read.wr_en_shift_b;
auto wr_data_shift = pm.st_ql_bram_asymmetric_wider_read.wr_data_shift;
auto wr_data_shift_a = pm.st_ql_bram_asymmetric_wider_read.wr_data_shift_a;
auto wr_data_shift_b = pm.st_ql_bram_asymmetric_wider_read.wr_data_shift_b;
auto wr_en_and = pm.st_ql_bram_asymmetric_wider_read.wr_en_and;
auto wr_en_and_a = pm.st_ql_bram_asymmetric_wider_read.wr_en_and_a;
auto wr_en_and_b = pm.st_ql_bram_asymmetric_wider_read.wr_en_and_b;
auto wr_en_and_y = pm.st_ql_bram_asymmetric_wider_read.wr_en_and_y;
// Add the BRAM cell
std::string name = mem->name.str() + "$asymmetric";
RTLIL::Cell *cell = pm.module->addCell(RTLIL::escape_id(name), mem);
// Set new type for cell so that it won't be processed by memory_bram pass
cell->type = IdString(RTLIL::escape_id("_$_mem_v2_asymmetric"));
// Prepare wires from memory cell side to compare against module wires
if (!mux_s.is_wire())
log_error("WR_EN input wire not found\n");
RTLIL::Wire *wr_en_cw = mux_s.as_wire();
// The WR address wire can be narrower
RTLIL::Wire *wr_addr_cw = nullptr;
if (mem_wr_addr.is_wire())
wr_addr_cw = mem_wr_addr.as_wire();
else if (!mem_wr_addr.chunks().empty()) {
auto chunk = mem_wr_addr.chunks()[0];
if (chunk.is_wire())
wr_addr_cw = chunk.wire;
}
if (!wr_addr_cw)
log_error("WR_ADDR input wire not found\n");
if (!wr_data_shift_a.is_wire())
log_error("WR_DATA input wire not found\n");
RTLIL::Wire *wr_data_cw = wr_data_shift_a.as_wire();
if (!mem_rd_addr.is_wire())
log_error("RD_ADDR input wire not found\n");
RTLIL::Wire *rd_addr_cw = mem_rd_addr.as_wire();
if (!mem_rd_data.is_wire())
log_error("RD_DATA input wire not found\n");
RTLIL::Wire *rd_data_cw = mem_rd_data.as_wire();
// Check if wr_en_and cell has one of its inputs connected to write address
RTLIL::Wire *wr_en_and_a_w = nullptr;
RTLIL::Wire *wr_en_and_b_w = nullptr;
bool has_wire = false;
if (wr_en_and_a.is_wire()) {
has_wire = true;
wr_en_and_a_w = wr_en_and_a.as_wire();
}
if (wr_en_and_b.is_wire()) {
has_wire = true;
wr_en_and_b_w = wr_en_and_b.as_wire();
}
if (!has_wire)
log_error("RD_ADDR $and cell input wire not found\n");
if ((wr_en_and_a_w != wr_addr_cw) & (wr_en_and_b_w != wr_addr_cw))
log_error("This is not the $and cell we are looking for\n");
// Compare and assign wires
RTLIL::Wire *wr_en_w = nullptr;
RTLIL::Wire *wr_addr_w = nullptr;
RTLIL::Wire *wr_data_w = nullptr;
RTLIL::Wire *rd_addr_w = nullptr;
RTLIL::Wire *rd_data_w = nullptr;
for (auto wire : pm.module->wires_) {
if (wire.second == wr_en_cw)
wr_en_w = wire.second;
if (wire.second == wr_addr_cw)
wr_addr_w = wire.second;
if (wire.second == wr_data_cw)
wr_data_w = wire.second;
if (wire.second == rd_data_cw)
rd_data_w = wire.second;
if (wire.second == rd_addr_cw)
rd_addr_w = wire.second;
}
if (!wr_en_w | !wr_addr_w | !wr_data_w | !rd_data_w | !rd_addr_w)
log_error("Match between RAM input wires and memory cell ports not found\n");
// Get address and data lines widths
int rd_addr_width = rd_addr_w->width;
int wr_addr_width = wr_addr_w->width;
int wr_data_width = wr_data_w->width;
int rd_data_width = rd_data_w->width;
log_debug("Set RD_ADDR_WIDTH = %d, ", rd_addr_width);
log_debug("WR_ADDR_WIDTH = %d, ", wr_addr_width);
log_debug("RD_DATA_WIDTH = %d, ", rd_data_width);
log_debug("WR_DATA_WIDTH = %d\n", wr_data_width);
// Set address and data lines width parameters used later in techmap
cell->setParam(RTLIL::escape_id("RD_ADDR_WIDTH"), RTLIL::Const(rd_addr_width));
cell->setParam(RTLIL::escape_id("RD_DATA_WIDTH"), RTLIL::Const(rd_data_width));
cell->setParam(RTLIL::escape_id("WR_ADDR_WIDTH"), RTLIL::Const(wr_addr_width));
cell->setParam(RTLIL::escape_id("WR_DATA_WIDTH"), RTLIL::Const(wr_data_width));
int offset;
switch (wr_data_width) {
case 1:
offset = 0;
break;
case 2:
offset = 1;
break;
case 4:
offset = 2;
break;
case 8:
case 9:
offset = 3;
break;
case 16:
case 18:
offset = 4;
break;
case 32:
case 36:
offset = 5;
break;
default:
offset = 0;
break;
}
if (wr_en_and_y != wr_en_shift_b.extract(offset, wr_addr_width))
log_error("This is not the wr_en $shl cell we are looking for\n");
if (wr_en_and_y != wr_data_shift_b.extract(offset, wr_addr_width))
log_error("This is not the wr_data $shl cell we are looking for\n");
// Bypass shift on write address line
cell->setPort(RTLIL::escape_id("WR_ADDR"), RTLIL::SigSpec(wr_addr_w));
// Bypass shift on write address line
cell->setPort(RTLIL::escape_id("WR_DATA"), RTLIL::SigSpec(wr_data_w));
// Bypass shift on write address line
cell->setPort(RTLIL::escape_id("WR_EN"), RTLIL::SigSpec(wr_en_w));
// Cleanup the module from unused cells
pm.module->remove(mem);
pm.module->remove(mux);
pm.module->remove(wr_en_shift);
pm.module->remove(wr_en_and);
pm.module->remove(wr_data_shift);
}
void test_ql_bram_asymmetric_wider_write(ql_bram_asymmetric_wider_write_pm &pm)
{
auto mem = pm.st_ql_bram_asymmetric_wider_write.mem;
auto mem_wr_addr = pm.st_ql_bram_asymmetric_wider_write.mem_wr_addr;
auto mem_wr_data = pm.st_ql_bram_asymmetric_wider_write.mem_wr_data;
auto mem_rd_data = pm.st_ql_bram_asymmetric_wider_write.mem_rd_data;
auto mem_rd_addr = pm.st_ql_bram_asymmetric_wider_write.mem_rd_addr;
auto rd_data_shift = pm.st_ql_bram_asymmetric_wider_write.rd_data_shift;
auto rd_data_shift_y = pm.st_ql_bram_asymmetric_wider_write.rd_data_shift_y;
auto rd_data_ff = pm.st_ql_bram_asymmetric_wider_write.rd_data_ff;
auto rd_data_ff_q = pm.st_ql_bram_asymmetric_wider_write.rd_data_ff_q;
auto rd_data_ff_en = pm.st_ql_bram_asymmetric_wider_write.rd_data_ff_en;
auto rd_data_ff_clk = pm.st_ql_bram_asymmetric_wider_write.rd_data_ff_clk;
auto wr_addr_ff = pm.st_ql_bram_asymmetric_wider_write.wr_addr_ff;
auto wr_addr_ff_d = pm.st_ql_bram_asymmetric_wider_write.wr_addr_ff_d;
auto wr_en_mux = pm.st_ql_bram_asymmetric_wider_write.wr_en_mux;
auto wr_en_mux_s = pm.st_ql_bram_asymmetric_wider_write.wr_en_mux_s;
auto rd_addr_and = pm.st_ql_bram_asymmetric_wider_write.rd_addr_and;
auto rd_addr_and_a = pm.st_ql_bram_asymmetric_wider_write.rd_addr_and_a;
auto rd_addr_and_b = pm.st_ql_bram_asymmetric_wider_write.rd_addr_and_b;
// Add the BRAM cell
std::string name = mem->name.str() + "$asymmetric";
RTLIL::Cell *cell = pm.module->addCell(RTLIL::escape_id(name), mem);
// Set new type for cell so that it won't be processed by memory_bram pass
cell->type = IdString(RTLIL::escape_id("_$_mem_v2_asymmetric"));
// Prepare wires from memory cell side to compare against module wires
RTLIL::Wire *rd_data_wc = nullptr;
RTLIL::Wire *rd_en_wc = nullptr;
RTLIL::Wire *clk_wc = nullptr;
RTLIL::Wire *rd_addr_and_a_wc = nullptr;
RTLIL::Wire *rd_addr_and_b_wc = nullptr;
if (rd_data_ff) {
if (!rd_data_ff_q.is_wire())
log_error("RD_DATA input wire not found\n");
rd_data_wc = rd_data_ff_q.as_wire();
if (!rd_data_ff_en.is_wire())
log_error("RD_EN input wire not found\n");
rd_en_wc = rd_data_ff_en.as_wire();
if (!rd_data_ff_clk.is_wire())
log_error("RD_CLK input wire not found\n");
clk_wc = rd_data_ff_clk.as_wire();
} else {
log_error("output FF not found\n");
}
if (rd_addr_and) {
bool has_wire = false;
if (rd_addr_and_a.is_wire()) {
has_wire = true;
rd_addr_and_a_wc = rd_addr_and_a.as_wire();
}
if (rd_addr_and_b.is_wire()) {
has_wire = true;
rd_addr_and_b_wc = rd_addr_and_b.as_wire();
}
if (!has_wire)
log_error("RD_ADDR $and cell input wire not found\n");
} else {
log_debug("RD_ADDR $and cell not found\n");
}
RTLIL::Wire *wr_addr_wc;
if (wr_addr_ff) {
if (!wr_addr_ff_d.is_wire())
log_error("WR_ADDR input wire not found\n");
wr_addr_wc = wr_addr_ff_d.as_wire();
} else {
if (!mem_wr_addr.is_wire())
log_error("WR_ADDR input wire not found\n");
wr_addr_wc = mem_wr_addr.as_wire();
}
// The RD address wire can be narrower
RTLIL::Wire *rd_addr_wc = nullptr;
if (mem_rd_addr.is_wire())
rd_addr_wc = mem_rd_addr.as_wire();
else if (!mem_rd_addr.chunks().empty()) {
auto chunk = mem_rd_addr.chunks()[0];
if (chunk.is_wire())
rd_addr_wc = chunk.wire;
}
if (!rd_addr_wc)
log_error("RD_ADDR input wire not found\n");
if (!mem_wr_data.is_wire())
log_error("WR_DATA input wire not found\n");
auto wr_data_wc = mem_wr_data.as_wire();
// Check if wr_en_and cell has one of its inputs connected to write address
// Compare and assign wires
RTLIL::Wire *rd_addr_w = nullptr;
RTLIL::Wire *rd_data_w = nullptr;
RTLIL::Wire *rd_en_w = nullptr;
RTLIL::Wire *rd_clk_w = nullptr;
RTLIL::Wire *wr_addr_w = nullptr;
RTLIL::Wire *wr_data_w = nullptr;
for (auto wire : pm.module->wires_) {
if (wire.second == rd_addr_wc)
rd_addr_w = wire.second;
if (wire.second == rd_data_wc)
rd_data_w = wire.second;
if (wire.second == rd_en_wc)
rd_en_w = wire.second;
if (wire.second == clk_wc)
rd_clk_w = wire.second;
if (wire.second == wr_addr_wc)
wr_addr_w = wire.second;
if (wire.second == wr_data_wc)
wr_data_w = wire.second;
}
if (!rd_addr_w | !rd_data_w | !rd_en_w | !rd_clk_w | !wr_addr_w | !wr_data_w)
log_error("Match between RAM input wires and memory cell ports not found\n");
// Set shift output SigSpec as RD_DATA
cell->setPort(RTLIL::escape_id("RD_DATA"), rd_data_shift_y);
// Get address and data lines widths
int rd_addr_width = rd_addr_w->width;
int wr_addr_width = wr_addr_w->width;
int wr_data_width = wr_data_w->width;
int rd_data_width = rd_data_w->width;
log_debug("Set RD_ADDR_WIDTH = %d, ", rd_addr_width);
log_debug("WR_ADDR_WIDTH = %d, ", wr_addr_width);
log_debug("RD_DATA_WIDTH = %d, ", rd_data_width);
log_debug("WR_DATA_WIDTH = %d\n", wr_data_width);
// Set address and data lines width parameters used later in techmap
cell->setParam(RTLIL::escape_id("RD_ADDR_WIDTH"), RTLIL::Const(rd_addr_width));
cell->setParam(RTLIL::escape_id("RD_DATA_WIDTH"), RTLIL::Const(rd_data_width));
cell->setParam(RTLIL::escape_id("WR_ADDR_WIDTH"), RTLIL::Const(wr_addr_width));
cell->setParam(RTLIL::escape_id("WR_DATA_WIDTH"), RTLIL::Const(wr_data_width));
// Bypass read address shift and connect line straight to memory cell
auto rd_addr_s = RTLIL::SigSpec(rd_addr_w);
cell->setPort(RTLIL::escape_id("RD_ADDR"), rd_addr_s);
if (wr_addr_ff) {
// Bypass FF on write address line if exists
// wr_addr_ff_d will not be assigned if wr_addr_ff was not detected earlier
cell->setPort(RTLIL::escape_id("WR_ADDR"), wr_addr_ff_d);
} else {
// When there are no regs on address lines, the clock isn't connected to memory
// Reconnect the clock
auto rd_clk_s = RTLIL::SigSpec(rd_clk_w);
cell->setPort(RTLIL::escape_id("RD_CLK"), rd_clk_s);
}
// Bypass FF on Data Output and connect the output straight to RD_DATA port
cell->setPort(RTLIL::escape_id("RD_DATA"), rd_data_ff_q);
// Bypass MUX on WRITE ENABLE and connect the output straight to WR_EN port
cell->setPort(RTLIL::escape_id("WR_EN"), wr_en_mux_s);
// Connect Read Enable signal to memory cell
if (!rd_en_w)
log_error("Wire \\rce not found\n");
auto rd_en_s = RTLIL::SigSpec(rd_en_w);
cell->setPort(RTLIL::escape_id("RD_EN"), rd_en_s);
// Cleanup the module from unused cells
pm.module->remove(mem);
pm.module->remove(rd_data_shift);
pm.module->remove(rd_data_ff);
pm.module->remove(wr_en_mux);
if (wr_addr_ff)
pm.module->remove(wr_addr_ff);
// Check if detected $and is connected to RD_ADDR
if ((rd_addr_and_a_wc != rd_addr_w) & (rd_addr_and_b_wc != rd_addr_w))
log_error("This is not the $and cell we are looking for\n");
else
pm.module->remove(rd_addr_and);
}
struct QLBramAsymmetric : public Pass {
QLBramAsymmetric()
: Pass("ql_bram_asymmetric",
"Detects memory cells with asymmetric read and write port widths implemented with shifts and infers custom asymmetric memory cell")
{
}
void help() override
{
log("\n");
log(" ql_bram_asymmetric\n");
log("\n");
log(" Detects memory cells with asymmetric read and write port widths implemented with shifts and infers custom asymmetric memory "
"cell");
log("\n");
}
void execute(std::vector<std::string> a_Args, RTLIL::Design *a_Design) override
{
log_header(a_Design, "Executing QL_BRAM_ASYMMETRIC pass.\n");
size_t argidx;
for (argidx = 1; argidx < a_Args.size(); argidx++) {
break;
}
extra_args(a_Args, argidx, a_Design);
int found_cells;
for (auto module : a_Design->selected_modules()) {
found_cells = ql_bram_asymmetric_wider_write_pm(module, module->selected_cells())
.run_ql_bram_asymmetric_wider_write(test_ql_bram_asymmetric_wider_write);
log_debug("found %d cells matching for wider write port\n", found_cells);
found_cells = ql_bram_asymmetric_wider_read_pm(module, module->selected_cells())
.run_ql_bram_asymmetric_wider_read(test_ql_bram_asymmetric_wider_read);
log_debug("found %d cells matching for wider read port\n", found_cells);
}
}
} QLBramAsymmetric;
PRIVATE_NAMESPACE_END