|  | #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 |