blob: df7ed0899fdb9f2a2c46aa7d4f941c3337f455e7 [file] [log] [blame]
// 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 QlDspSimdPass : public Pass {
QlDspSimdPass () : Pass("ql_dsp_simd", "Infers QuickLogic k6n10f DSP pairs that can operate in SIMD mode") {}
void help() override {
log("\n");
log(" ql_dsp_simd [selection]\n");
log("\n");
log(" This pass identifies k6n10f DSP cells with identical configuration\n");
log(" and packs pairs of them together into other DSP cells that can\n");
log(" perform SIMD operation.\n");
}
// ..........................................
/// Describes DSP config unique to a whole DSP cell
struct DspConfig {
// Port connections
dict<RTLIL::IdString, RTLIL::SigSpec> connections;
// TODO: Possibly include parameters here. For now we have just
// connections.
DspConfig () = default;
DspConfig (const DspConfig& ref) = default;
DspConfig (DspConfig&& ref) = default;
unsigned int hash () const {
return connections.hash();
}
bool operator == (const DspConfig& ref) const {
return connections == ref.connections;
}
};
// ..........................................
// DSP control and config ports to consider and how to map them to ports
// of the target DSP cell
const std::vector<std::pair<std::string, std::string>> m_DspCfgPorts = {
std::make_pair("clock_i", "clk"),
std::make_pair("reset_i", "reset"),
std::make_pair("feedback_i", "feedback"),
std::make_pair("load_acc_i", "load_acc"),
std::make_pair("unsigned_a_i", "unsigned_a"),
std::make_pair("unsigned_b_i", "unsigned_b"),
std::make_pair("output_select_i", "output_select"),
std::make_pair("saturate_enable_i", "saturate_enable"),
std::make_pair("shift_right_i", "shift_right"),
std::make_pair("round_i", "round"),
std::make_pair("subtract_i", "subtract"),
std::make_pair("register_inputs_i", "register_inputs")
};
// DSP data ports and how to map them to ports of the target DSP cell
const std::vector<std::pair<std::string, std::string>> m_DspDataPorts = {
std::make_pair("a_i", "a"),
std::make_pair("b_i", "b"),
std::make_pair("acc_fir_i", "acc_fir"),
std::make_pair("z_o", "z"),
std::make_pair("dly_b_o", "dly_b"),
};
// Source DSP cell type (SISD)
const RTLIL::IdString m_SisdDspType = RTLIL::escape_id("dsp_t1_10x9x32");
// Target DSP cell type for the SIMD mode
const RTLIL::IdString m_SimdDspType = RTLIL::escape_id("QL_DSP2");
/// Temporary SigBit to SigBit helper map.
SigMap m_SigMap;
// ..........................................
void execute (std::vector<std::string> a_Args, RTLIL::Design* a_Design) override {
log_header(a_Design, "Executing QL_DSP_SIMD 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 DSP cell groups
dict<DspConfig, std::vector<RTLIL::Cell*>> groups;
for (auto cell : module->selected_cells()) {
// Check if this is a DSP cell
if (cell->type != m_SisdDspType) {
continue;
}
// Skip if it has the (* keep *) attribute set
if (cell->has_keep_attr()) {
continue;
}
// Add to a group
const auto key = getDspConfig(cell);
groups[key].push_back(cell);
}
std::vector<const RTLIL::Cell*> cellsToRemove;
// Map cell pairs to the target DSP SIMD cell
for (const auto& it : groups) {
const auto& group = it.second;
const auto& config = it.first;
// 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* dsp_a = group[i];
const RTLIL::Cell* dsp_b = group[i+1];
std::string name = stringf("simd_%s_%s",
RTLIL::unescape_id(dsp_a->name).c_str(),
RTLIL::unescape_id(dsp_b->name).c_str()
);
log(" SIMD: %s (%s) + %s (%s) => %s (%s)\n",
RTLIL::unescape_id(dsp_a->name).c_str(),
RTLIL::unescape_id(dsp_a->type).c_str(),
RTLIL::unescape_id(dsp_b->name).c_str(),
RTLIL::unescape_id(dsp_b->type).c_str(),
RTLIL::unescape_id(name).c_str(),
RTLIL::unescape_id(m_SimdDspType).c_str()
);
// Create the new cell
RTLIL::Cell* simd = module->addCell(
RTLIL::escape_id(name),
m_SimdDspType
);
// Check if the target cell is known (important to know
// its port widths)
if (!simd->known()) {
log_error(" The target cell type '%s' is not known!",
RTLIL::unescape_id(m_SimdDspType).c_str()
);
}
// Connect common ports
for (const auto& it : m_DspCfgPorts) {
auto sport = RTLIL::escape_id(it.first);
auto dport = RTLIL::escape_id(it.second);
simd->setPort(dport, config.connections.at(sport));
}
// Connect data ports
for (const auto& it : m_DspDataPorts) {
auto sport = RTLIL::escape_id(it.first);
auto dport = RTLIL::escape_id(it.second);
RTLIL::SigSpec sigspec;
size_t width = getPortWidth(simd, dport);
// A part
if (dsp_a->hasPort(sport)) {
const auto& sig = dsp_a->getPort(sport);
sigspec.append(sig);
if (sig.bits().size() < width / 2) {
sigspec.append(RTLIL::SigSpec(RTLIL::Sx,
sig.bits().size() - width / 2)
);
}
} else {
sigspec.append(RTLIL::SigSpec(RTLIL::Sx, width / 2));
}
// B part
if (dsp_b->hasPort(sport)) {
const auto& sig = dsp_b->getPort(sport);
sigspec.append(sig);
if (sig.bits().size() < width / 2) {
sigspec.append(RTLIL::SigSpec(RTLIL::Sx,
sig.bits().size() - width / 2)
);
}
} else {
sigspec.append(RTLIL::SigSpec(RTLIL::Sx, width / 2));
}
simd->setPort(dport, sigspec);
}
// Enable the fractured mode by connecting the control
// port.
simd->setPort(RTLIL::escape_id("f_mode"), RTLIL::S1);
// Mark DSP parts for removal
cellsToRemove.push_back(dsp_a);
cellsToRemove.push_back(dsp_b);
}
}
// Remove old cells
for (const auto& cell : cellsToRemove) {
module->remove(const_cast<RTLIL::Cell*>(cell));
}
}
// Clear
m_SigMap.clear();
}
// ..........................................
/// Looks up port width in the cell definition and returns it. Returns 0
/// if it cannot be determined.
size_t getPortWidth (RTLIL::Cell* a_Cell, RTLIL::IdString a_Port) {
if (!a_Cell->known()) {
return 0;
}
// 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 0;
}
// Get the wire representing the port
RTLIL::Wire* wire = mod->wire(a_Port);
if (wire == nullptr) {
return 0;
}
return wire->width;
}
/// Given a DSP cell populates and returns a DspConfig struct for it.
DspConfig getDspConfig (RTLIL::Cell* a_Cell) {
DspConfig config;
for (const auto& it : m_DspCfgPorts) {
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;
}
} QlDspSimdPass;
PRIVATE_NAMESPACE_END