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