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