|  | // Copyright (C) 2020-2022  The SymbiFlow Authors. | 
|  | // | 
|  | // Use of this source code is governed by a ISC-style | 
|  | // license that can be found in the LICENSE file or at | 
|  | // https://opensource.org/licenses/ISC | 
|  | // | 
|  | // SPDX-License-Identifier:ISC | 
|  |  | 
|  | #include "kernel/log.h" | 
|  | #include "kernel/register.h" | 
|  | #include "kernel/rtlil.h" | 
|  | #include "kernel/sigtools.h" | 
|  |  | 
|  | USING_YOSYS_NAMESPACE | 
|  | PRIVATE_NAMESPACE_BEGIN | 
|  |  | 
|  | // ============================================================================ | 
|  |  | 
|  | struct QlBramSplitPass : public Pass { | 
|  |  | 
|  | QlBramSplitPass() : Pass("ql_bram_split", "Infers QuickLogic k6n10f BRAM pairs that can operate independently") {} | 
|  |  | 
|  | void help() override | 
|  | { | 
|  | log("\n"); | 
|  | log("    ql_bram_split [selection]\n"); | 
|  | log("\n"); | 
|  | log("    This pass identifies k6n10f 18K BRAM cells\n"); | 
|  | log("    and packs pairs of them together into final TDP36K cell that can\n"); | 
|  | log("    be split into 2x18K BRAMs.\n"); | 
|  | } | 
|  |  | 
|  | // .......................................... | 
|  |  | 
|  | /// Describes BRAM config unique to a whole BRAM cell | 
|  | struct BramConfig { | 
|  |  | 
|  | // Port connections | 
|  | dict<RTLIL::IdString, RTLIL::SigSpec> connections; | 
|  |  | 
|  | // TODO: Possibly include parameters here. For now we have just | 
|  | // connections. | 
|  |  | 
|  | BramConfig() = default; | 
|  |  | 
|  | BramConfig(const BramConfig &ref) = default; | 
|  | BramConfig(BramConfig &&ref) = default; | 
|  |  | 
|  | unsigned int hash() const { return connections.hash(); } | 
|  |  | 
|  | bool operator==(const BramConfig &ref) const { return connections == ref.connections; } | 
|  | }; | 
|  |  | 
|  | // .......................................... | 
|  |  | 
|  | // BRAM control and config ports to consider and how to map them to ports | 
|  | // of the target BRAM cell | 
|  | const std::vector<std::pair<std::string, std::string>> m_BramSharedPorts = {}; | 
|  | // BRAM parameters | 
|  | const std::vector<std::string> m_BramParams = {"CFG_ABITS", "CFG_DBITS"}; | 
|  |  | 
|  | // TDP BRAM 1x18 data ports for subcell #1 and how to map them to ports of the target TDP BRAM 2x18 cell | 
|  | const std::vector<std::pair<std::string, std::string>> m_BramTDPDataPorts_0 = { | 
|  | std::make_pair("A1ADDR", "A1ADDR"), std::make_pair("A1DATA", "A1DATA"), std::make_pair("A1EN", "A1EN"),     std::make_pair("B1ADDR", "B1ADDR"), | 
|  | std::make_pair("B1DATA", "B1DATA"), std::make_pair("B1EN", "B1EN"),     std::make_pair("C1ADDR", "C1ADDR"), std::make_pair("C1DATA", "C1DATA"), | 
|  | std::make_pair("C1EN", "C1EN"),     std::make_pair("CLK1", "CLK1"),     std::make_pair("CLK2", "CLK2"),     std::make_pair("D1ADDR", "D1ADDR"), | 
|  | std::make_pair("D1DATA", "D1DATA"), std::make_pair("D1EN", "D1EN")}; | 
|  | // TDP BRAM 1x18 data ports for subcell #2 and how to map them to ports of the target TDP BRAM 2x18 cell | 
|  | const std::vector<std::pair<std::string, std::string>> m_BramTDPDataPorts_1 = { | 
|  | std::make_pair("A1ADDR", "E1ADDR"), std::make_pair("A1DATA", "E1DATA"), std::make_pair("A1EN", "E1EN"),     std::make_pair("B1ADDR", "F1ADDR"), | 
|  | std::make_pair("B1DATA", "F1DATA"), std::make_pair("B1EN", "F1EN"),     std::make_pair("C1ADDR", "G1ADDR"), std::make_pair("C1DATA", "G1DATA"), | 
|  | std::make_pair("C1EN", "G1EN"),     std::make_pair("CLK1", "CLK3"),     std::make_pair("CLK2", "CLK4"),     std::make_pair("D1ADDR", "H1ADDR"), | 
|  | std::make_pair("D1DATA", "H1DATA"), std::make_pair("D1EN", "H1EN")}; | 
|  | // Source BRAM TDP cell type (1x18K) | 
|  | const std::string m_Bram1x18TDPType = "$__QLF_FACTOR_BRAM18_TDP"; | 
|  | // Target BRAM TDP cell type for the split mode | 
|  | const std::string m_Bram2x18TDPType = "BRAM2x18_TDP"; | 
|  |  | 
|  | // SDP BRAM 1x18 data ports for subcell #1 and how to map them to ports of the target SDP BRAM 2x18 cell | 
|  | const std::vector<std::pair<std::string, std::string>> m_BramSDPDataPorts_0 = { | 
|  | std::make_pair("A1ADDR", "A1ADDR"), std::make_pair("A1DATA", "A1DATA"), std::make_pair("A1EN", "A1EN"), std::make_pair("B1ADDR", "B1ADDR"), | 
|  | std::make_pair("B1DATA", "B1DATA"), std::make_pair("B1EN", "B1EN"),     std::make_pair("CLK1", "CLK1")}; | 
|  | // SDP BRAM 1x18 data ports for subcell #2 and how to map them to ports of the target SDP BRAM 2x18 cell | 
|  | const std::vector<std::pair<std::string, std::string>> m_BramSDPDataPorts_1 = { | 
|  | std::make_pair("A1ADDR", "C1ADDR"), std::make_pair("A1DATA", "C1DATA"), std::make_pair("A1EN", "C1EN"), std::make_pair("B1ADDR", "D1ADDR"), | 
|  | std::make_pair("B1DATA", "D1DATA"), std::make_pair("B1EN", "D1EN"),     std::make_pair("CLK1", "CLK2")}; | 
|  | // Source BRAM SDP cell type (1x18K) | 
|  | const std::string m_Bram1x18SDPType = "$__QLF_FACTOR_BRAM18_SDP"; | 
|  | // Target BRAM SDP cell type for the split mode | 
|  | const std::string m_Bram2x18SDPType = "BRAM2x18_SDP"; | 
|  |  | 
|  | /// Temporary SigBit to SigBit helper map. | 
|  | SigMap m_SigMap; | 
|  |  | 
|  | // .......................................... | 
|  |  | 
|  | void map_ports(const std::vector<std::pair<std::string, std::string>> mapping, const RTLIL::Cell *bram_1x18, RTLIL::Cell *bram_2x18) | 
|  | { | 
|  | for (const auto &it : mapping) { | 
|  | auto src = RTLIL::escape_id(it.first); | 
|  | auto dst = RTLIL::escape_id(it.second); | 
|  |  | 
|  | size_t width; | 
|  | bool isOutput; | 
|  |  | 
|  | std::tie(width, isOutput) = getPortInfo(bram_2x18, dst); | 
|  |  | 
|  | auto getConnection = [&](const RTLIL::Cell *cell) { | 
|  | RTLIL::SigSpec sigspec; | 
|  | if (cell->hasPort(src)) { | 
|  | const auto &sig = cell->getPort(src); | 
|  | sigspec.append(sig); | 
|  | } | 
|  | if (sigspec.bits().size() < width / 2) { | 
|  | if (isOutput) { | 
|  | for (size_t i = 0; i < width / 2 - sigspec.bits().size(); ++i) { | 
|  | sigspec.append(RTLIL::SigSpec()); | 
|  | } | 
|  | } else { | 
|  | sigspec.append(RTLIL::SigSpec(RTLIL::Sx, width / 2 - sigspec.bits().size())); | 
|  | } | 
|  | } | 
|  | return sigspec; | 
|  | }; | 
|  |  | 
|  | RTLIL::SigSpec sigspec; | 
|  | sigspec.append(getConnection(bram_1x18)); | 
|  | bram_2x18->setPort(dst, sigspec); | 
|  | } | 
|  | } | 
|  |  | 
|  | void map_pairs(std::vector<RTLIL::Cell *> group, BramConfig config, std::vector<const RTLIL::Cell *> *cellsToRemove, RTLIL::Module *module) | 
|  | { | 
|  | // Ensure an even number | 
|  | size_t count = group.size(); | 
|  | if (count & 1) | 
|  | count--; | 
|  |  | 
|  | // Map SIMD pairs | 
|  | for (size_t i = 0; i < count; i += 2) { | 
|  | const RTLIL::Cell *bram_0 = group[i]; | 
|  | const RTLIL::Cell *bram_1 = group[i + 1]; | 
|  |  | 
|  | if (bram_0->type != bram_1->type) | 
|  | log_error("Unsupported BRAM configuration: one half of TDP36K is TDP, second SDP"); | 
|  |  | 
|  | std::vector<std::pair<std::string, std::string>> m_BramDataPorts_0; | 
|  | std::vector<std::pair<std::string, std::string>> m_BramDataPorts_1; | 
|  | std::string m_Bram1x18Type; | 
|  | std::string m_Bram2x18Type; | 
|  | // Distinguish between TDP and SDP | 
|  | if (bram_0->type == RTLIL::escape_id(m_Bram1x18TDPType)) { | 
|  | m_BramDataPorts_0 = m_BramTDPDataPorts_0; | 
|  | m_BramDataPorts_1 = m_BramTDPDataPorts_1; | 
|  | m_Bram1x18Type = m_Bram1x18TDPType; | 
|  | m_Bram2x18Type = m_Bram2x18TDPType; | 
|  | } else { | 
|  | m_BramDataPorts_0 = m_BramSDPDataPorts_0; | 
|  | m_BramDataPorts_1 = m_BramSDPDataPorts_1; | 
|  | m_Bram1x18Type = m_Bram1x18SDPType; | 
|  | m_Bram2x18Type = m_Bram2x18SDPType; | 
|  | } | 
|  |  | 
|  | std::string name = stringf("bram_%s_%s", RTLIL::unescape_id(bram_0->name).c_str(), RTLIL::unescape_id(bram_1->name).c_str()); | 
|  |  | 
|  | log(" BRAM: %s (%s) + %s (%s) => %s (%s)\n", RTLIL::unescape_id(bram_0->name).c_str(), RTLIL::unescape_id(bram_0->type).c_str(), | 
|  | RTLIL::unescape_id(bram_1->name).c_str(), RTLIL::unescape_id(bram_1->type).c_str(), RTLIL::unescape_id(name).c_str(), | 
|  | m_Bram2x18Type.c_str()); | 
|  |  | 
|  | // Create the new cell | 
|  | RTLIL::Cell *bram_2x18 = module->addCell(RTLIL::escape_id(name), RTLIL::escape_id(m_Bram2x18Type)); | 
|  |  | 
|  | // Check if the target cell is known (important to know | 
|  | // its port widths) | 
|  | if (!bram_2x18->known()) { | 
|  | log_error(" The target cell type '%s' is not known!", m_Bram2x18Type.c_str()); | 
|  | } | 
|  |  | 
|  | // Connect shared ports | 
|  | for (const auto &it : m_BramSharedPorts) { | 
|  | auto src = RTLIL::escape_id(it.first); | 
|  | auto dst = RTLIL::escape_id(it.second); | 
|  |  | 
|  | bram_2x18->setPort(dst, config.connections.at(src)); | 
|  | } | 
|  |  | 
|  | // Connect data ports | 
|  | // Connect first bram | 
|  | map_ports(m_BramDataPorts_0, bram_0, bram_2x18); | 
|  | // Connect second bram | 
|  | map_ports(m_BramDataPorts_1, bram_1, bram_2x18); | 
|  |  | 
|  | // Set bram parameters | 
|  | for (const auto &it : m_BramParams) { | 
|  | auto val = bram_0->getParam(RTLIL::escape_id(it)); | 
|  | bram_2x18->setParam(RTLIL::escape_id(it), val); | 
|  | } | 
|  |  | 
|  | // Setting manual parameters | 
|  | if (bram_0->type == RTLIL::escape_id(m_Bram1x18TDPType)) { | 
|  | bram_2x18->setParam(RTLIL::escape_id("CFG_ENABLE_B"), bram_0->getParam(RTLIL::escape_id("CFG_ENABLE_B"))); | 
|  | bram_2x18->setParam(RTLIL::escape_id("CFG_ENABLE_D"), bram_0->getParam(RTLIL::escape_id("CFG_ENABLE_D"))); | 
|  | bram_2x18->setParam(RTLIL::escape_id("CFG_ENABLE_F"), bram_1->getParam(RTLIL::escape_id("CFG_ENABLE_B"))); | 
|  | bram_2x18->setParam(RTLIL::escape_id("CFG_ENABLE_H"), bram_1->getParam(RTLIL::escape_id("CFG_ENABLE_D"))); | 
|  | } else { | 
|  | bram_2x18->setParam(RTLIL::escape_id("CFG_ENABLE_B"), bram_0->getParam(RTLIL::escape_id("CFG_ENABLE_B"))); | 
|  | bram_2x18->setParam(RTLIL::escape_id("CFG_ENABLE_D"), bram_1->getParam(RTLIL::escape_id("CFG_ENABLE_B"))); | 
|  | } | 
|  | if (bram_0->hasParam(RTLIL::escape_id("INIT"))) | 
|  | bram_2x18->setParam(RTLIL::escape_id("INIT0"), bram_0->getParam(RTLIL::escape_id("INIT"))); | 
|  | if (bram_1->hasParam(RTLIL::escape_id("INIT"))) | 
|  | bram_2x18->setParam(RTLIL::escape_id("INIT1"), bram_1->getParam(RTLIL::escape_id("INIT"))); | 
|  |  | 
|  | // Since in this pass we are mapping the inferred cell directly then mark it as inferred | 
|  | bram_2x18->set_bool_attribute(RTLIL::escape_id("is_inferred"), true); | 
|  |  | 
|  | // Mark BRAM parts for removal | 
|  | cellsToRemove->push_back(bram_0); | 
|  | cellsToRemove->push_back(bram_1); | 
|  | } | 
|  | } | 
|  |  | 
|  | void execute(std::vector<std::string> a_Args, RTLIL::Design *a_Design) override | 
|  | { | 
|  | log_header(a_Design, "Executing QL_BRAM_Split pass.\n"); | 
|  |  | 
|  | // Parse args | 
|  | extra_args(a_Args, 1, a_Design); | 
|  |  | 
|  | // Process modules | 
|  | for (auto module : a_Design->selected_modules()) { | 
|  |  | 
|  | // Setup the SigMap | 
|  | m_SigMap.clear(); | 
|  | m_SigMap.set(module); | 
|  |  | 
|  | // Assemble BRAM cell groups | 
|  | dict<BramConfig, std::vector<RTLIL::Cell *>> sdp_groups, tdp_groups; | 
|  | for (auto cell : module->selected_cells()) { | 
|  |  | 
|  | // Skip if it has the (* keep *) attribute set | 
|  | if (cell->has_keep_attr()) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // Check if this is a BRAM cell and add to a group | 
|  | const auto key = getBramConfig(cell); | 
|  | if (cell->type == RTLIL::escape_id(m_Bram1x18TDPType)) { | 
|  | tdp_groups[key].push_back(cell); | 
|  | } else if (cell->type == RTLIL::escape_id(m_Bram1x18SDPType)) { | 
|  | sdp_groups[key].push_back(cell); | 
|  | } else { | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | std::vector<const RTLIL::Cell *> cellsToRemove; | 
|  |  | 
|  | // Map cell pairs to the target BRAM 2x18 cell | 
|  | for (const auto &it : sdp_groups) { | 
|  | const auto &group = it.second; | 
|  | const auto &config = it.first; | 
|  | map_pairs(group, config, &cellsToRemove, module); | 
|  | } | 
|  | for (const auto &it : tdp_groups) { | 
|  | const auto &group = it.second; | 
|  | const auto &config = it.first; | 
|  | map_pairs(group, config, &cellsToRemove, module); | 
|  | } | 
|  |  | 
|  | // Remove old cells | 
|  | for (const auto &cell : cellsToRemove) { | 
|  | module->remove(const_cast<RTLIL::Cell *>(cell)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Clear | 
|  | m_SigMap.clear(); | 
|  | } | 
|  |  | 
|  | // .......................................... | 
|  |  | 
|  | /// Looks up port width and direction in the cell definition and returns it. | 
|  | /// Returns (0, false) if it cannot be determined. | 
|  | std::pair<size_t, bool> getPortInfo(RTLIL::Cell *a_Cell, RTLIL::IdString a_Port) | 
|  | { | 
|  | if (!a_Cell->known()) { | 
|  | return std::make_pair(0, false); | 
|  | } | 
|  |  | 
|  | // Get the module defining the cell (the previous condition ensures | 
|  | // that the pointers are valid) | 
|  | RTLIL::Module *mod = a_Cell->module->design->module(a_Cell->type); | 
|  | if (mod == nullptr) { | 
|  | return std::make_pair(0, false); | 
|  | } | 
|  |  | 
|  | // Get the wire representing the port | 
|  | RTLIL::Wire *wire = mod->wire(a_Port); | 
|  | if (wire == nullptr) { | 
|  | return std::make_pair(0, false); | 
|  | } | 
|  |  | 
|  | return std::make_pair(wire->width, wire->port_output); | 
|  | } | 
|  |  | 
|  | /// Given a BRAM cell populates and returns a BramConfig struct for it. | 
|  | BramConfig getBramConfig(RTLIL::Cell *a_Cell) | 
|  | { | 
|  | BramConfig config; | 
|  |  | 
|  | for (const auto &it : m_BramSharedPorts) { | 
|  | auto port = RTLIL::escape_id(it.first); | 
|  |  | 
|  | // Port unconnected | 
|  | if (!a_Cell->hasPort(port)) { | 
|  | config.connections[port] = RTLIL::SigSpec(RTLIL::Sx); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // Get the port connection and map it to unique SigBits | 
|  | const auto &orgSigSpec = a_Cell->getPort(port); | 
|  | const auto &orgSigBits = orgSigSpec.bits(); | 
|  |  | 
|  | RTLIL::SigSpec newSigSpec; | 
|  | for (size_t i = 0; i < orgSigBits.size(); ++i) { | 
|  | auto newSigBit = m_SigMap(orgSigBits[i]); | 
|  | newSigSpec.append(newSigBit); | 
|  | } | 
|  |  | 
|  | // Store | 
|  | config.connections[port] = newSigSpec; | 
|  | } | 
|  |  | 
|  | return config; | 
|  | } | 
|  |  | 
|  | } QlBramSplitPass; | 
|  |  | 
|  | PRIVATE_NAMESPACE_END |