2023-11-27 03:35:29 -06:00
|
|
|
/*
|
|
|
|
* Copyright 2020-2022 F4PGA Authors
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
|
|
|
#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 {
|
|
|
|
|
2023-10-02 07:40:10 -05:00
|
|
|
QlDspSimdPass() : Pass("ql_dsp_simd", "merge QuickLogic K6N10f DSP pairs to operate in SIMD mode") {}
|
2023-11-27 03:35:29 -06:00
|
|
|
|
|
|
|
void help() override
|
|
|
|
{
|
2023-10-02 07:40:10 -05:00
|
|
|
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
2023-11-27 03:35:29 -06:00
|
|
|
log("\n");
|
|
|
|
log(" ql_dsp_simd [selection]\n");
|
|
|
|
log("\n");
|
2023-10-02 07:40:10 -05:00
|
|
|
log("This pass identifies K6N10f DSP cells with identical configuration and pack pairs\n");
|
|
|
|
log("of them together into other DSP cells that can perform SIMD operation.\n");
|
2023-11-27 03:35:29 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// ..........................................
|
|
|
|
|
|
|
|
/// Describes DSP config unique to a whole DSP cell
|
|
|
|
struct DspConfig {
|
|
|
|
// Port connections
|
|
|
|
dict<RTLIL::IdString, RTLIL::SigSpec> connections;
|
|
|
|
|
|
|
|
DspConfig() = default;
|
|
|
|
|
|
|
|
DspConfig(const DspConfig &ref) = default;
|
|
|
|
DspConfig(DspConfig &&ref) = default;
|
|
|
|
|
|
|
|
unsigned int hash() const { return connections.hash(); }
|
|
|
|
|
2023-10-02 07:40:10 -05:00
|
|
|
bool operator==(const DspConfig &ref) const { return connections == ref.connections; }
|
2023-11-27 03:35:29 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
// ..........................................
|
|
|
|
|
2024-11-28 17:31:34 -06:00
|
|
|
const int m_ModeBitsSize = 80;
|
2023-11-27 03:35:29 -06:00
|
|
|
|
|
|
|
// DSP parameters
|
|
|
|
const std::vector<std::string> m_DspParams = {"COEFF_3", "COEFF_2", "COEFF_1", "COEFF_0"};
|
|
|
|
|
|
|
|
/// Temporary SigBit to SigBit helper map.
|
2023-10-02 07:40:10 -05:00
|
|
|
SigMap sigmap;
|
2023-11-27 03:35:29 -06:00
|
|
|
|
|
|
|
// ..........................................
|
|
|
|
|
|
|
|
void execute(std::vector<std::string> a_Args, RTLIL::Design *a_Design) override
|
|
|
|
{
|
|
|
|
log_header(a_Design, "Executing QL_DSP_SIMD pass.\n");
|
|
|
|
|
2024-01-02 04:26:48 -06:00
|
|
|
// DSP control and config ports to consider and how to map them to ports
|
|
|
|
// of the target DSP cell
|
|
|
|
static const std::vector<std::pair<IdString, IdString>> m_DspCfgPorts = {
|
|
|
|
std::make_pair(ID(clock_i), ID(clk)),
|
|
|
|
std::make_pair(ID(reset_i), ID(reset)),
|
|
|
|
std::make_pair(ID(feedback_i), ID(feedback)),
|
|
|
|
std::make_pair(ID(load_acc_i), ID(load_acc)),
|
|
|
|
std::make_pair(ID(unsigned_a_i), ID(unsigned_a)),
|
|
|
|
std::make_pair(ID(unsigned_b_i), ID(unsigned_b)),
|
|
|
|
std::make_pair(ID(subtract_i), ID(subtract)),
|
|
|
|
std::make_pair(ID(output_select_i), ID(output_select)),
|
|
|
|
std::make_pair(ID(saturate_enable_i), ID(saturate_enable)),
|
|
|
|
std::make_pair(ID(shift_right_i), ID(shift_right)),
|
|
|
|
std::make_pair(ID(round_i), ID(round)),
|
|
|
|
std::make_pair(ID(register_inputs_i), ID(register_inputs))
|
|
|
|
};
|
|
|
|
|
|
|
|
// DSP data ports and how to map them to ports of the target DSP cell
|
|
|
|
static const std::vector<std::pair<IdString, IdString>> m_DspDataPorts = {
|
|
|
|
std::make_pair(ID(a_i), ID(a)),
|
|
|
|
std::make_pair(ID(b_i), ID(b)),
|
|
|
|
std::make_pair(ID(acc_fir_i), ID(acc_fir)),
|
|
|
|
std::make_pair(ID(z_o), ID(z)),
|
|
|
|
std::make_pair(ID(dly_b_o), ID(dly_b))
|
|
|
|
};
|
|
|
|
|
|
|
|
// Source DSP cell type (SISD)
|
|
|
|
static const IdString m_SisdDspType = ID(dsp_t1_10x9x32);
|
|
|
|
|
|
|
|
// Target DSP cell types for the SIMD mode
|
|
|
|
static const IdString m_SimdDspType = ID(QL_DSP2);
|
|
|
|
|
2023-11-27 03:35:29 -06:00
|
|
|
// Parse args
|
|
|
|
extra_args(a_Args, 1, a_Design);
|
|
|
|
|
|
|
|
// Process modules
|
|
|
|
for (auto module : a_Design->selected_modules()) {
|
|
|
|
// Setup the SigMap
|
2023-10-02 07:40:10 -05:00
|
|
|
sigmap.set(module);
|
2023-11-27 03:35:29 -06:00
|
|
|
|
|
|
|
// Assemble DSP cell groups
|
|
|
|
dict<DspConfig, std::vector<RTLIL::Cell *>> groups;
|
|
|
|
for (auto cell : module->selected_cells()) {
|
|
|
|
// Check if this is a DSP cell we are looking for (type starts with m_SisdDspType)
|
2023-10-02 07:40:10 -05:00
|
|
|
if (cell->type != m_SisdDspType)
|
2023-11-27 03:35:29 -06:00
|
|
|
continue;
|
|
|
|
|
|
|
|
// Skip if it has the (* keep *) attribute set
|
2023-10-02 07:40:10 -05:00
|
|
|
if (cell->has_keep_attr())
|
2023-11-27 03:35:29 -06:00
|
|
|
continue;
|
|
|
|
|
|
|
|
// Add to a group
|
2024-01-02 04:26:48 -06:00
|
|
|
const auto key = getDspConfig(cell, m_DspCfgPorts);
|
2023-11-27 03:35:29 -06:00
|
|
|
groups[key].push_back(cell);
|
|
|
|
}
|
|
|
|
|
2023-10-02 07:40:10 -05:00
|
|
|
std::vector<Cell *> cellsToRemove;
|
2023-11-27 03:35:29 -06:00
|
|
|
|
|
|
|
// 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) {
|
2023-10-02 07:40:10 -05:00
|
|
|
Cell *dsp_a = group[i];
|
|
|
|
Cell *dsp_b = group[i + 1];
|
2023-11-27 03:35:29 -06:00
|
|
|
|
|
|
|
// Create the new cell
|
2023-10-02 07:40:10 -05:00
|
|
|
Cell *simd = module->addCell(NEW_ID, m_SimdDspType);
|
|
|
|
|
|
|
|
log(" SIMD: %s (%s) + %s (%s) => %s (%s)\n", log_id(dsp_a), log_id(dsp_a->type),
|
|
|
|
log_id(dsp_b), log_id(dsp_b->type), log_id(simd), log_id(simd->type));
|
2023-11-27 03:35:29 -06:00
|
|
|
|
|
|
|
// Check if the target cell is known (important to know
|
|
|
|
// its port widths)
|
2023-10-02 07:40:10 -05:00
|
|
|
if (!simd->known())
|
|
|
|
log_error(" The target cell type '%s' is not known!", log_id(simd));
|
2023-11-27 03:35:29 -06:00
|
|
|
|
|
|
|
// Connect common ports
|
2023-10-02 07:40:10 -05:00
|
|
|
for (const auto &it : m_DspCfgPorts)
|
|
|
|
simd->setPort(it.first, config.connections.at(it.second));
|
2023-11-27 03:35:29 -06:00
|
|
|
|
|
|
|
// Connect data ports
|
|
|
|
for (const auto &it : m_DspDataPorts) {
|
|
|
|
size_t width;
|
|
|
|
bool isOutput;
|
|
|
|
|
2023-10-02 07:40:10 -05:00
|
|
|
std::tie(width, isOutput) = getPortInfo(simd, it.second);
|
2023-11-27 03:35:29 -06:00
|
|
|
|
|
|
|
auto getConnection = [&](const RTLIL::Cell *cell) {
|
|
|
|
RTLIL::SigSpec sigspec;
|
2023-10-02 07:40:10 -05:00
|
|
|
if (cell->hasPort(it.first)) {
|
|
|
|
const auto &sig = cell->getPort(it.first);
|
2023-11-27 03:35:29 -06:00
|
|
|
sigspec.append(sig);
|
|
|
|
}
|
2023-10-02 07:40:10 -05:00
|
|
|
|
2024-10-09 12:39:45 -05:00
|
|
|
int padding = width / 2 - sigspec.size();
|
2023-10-02 07:40:10 -05:00
|
|
|
|
|
|
|
if (padding) {
|
|
|
|
if (!isOutput)
|
|
|
|
sigspec.append(RTLIL::SigSpec(RTLIL::Sx, padding));
|
|
|
|
else
|
|
|
|
sigspec.append(module->addWire(NEW_ID, padding));
|
2023-11-27 03:35:29 -06:00
|
|
|
}
|
|
|
|
return sigspec;
|
|
|
|
};
|
|
|
|
|
|
|
|
RTLIL::SigSpec sigspec;
|
|
|
|
sigspec.append(getConnection(dsp_a));
|
|
|
|
sigspec.append(getConnection(dsp_b));
|
2023-10-02 07:40:10 -05:00
|
|
|
simd->setPort(it.second, sigspec);
|
2023-11-27 03:35:29 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Concatenate FIR coefficient parameters into the single
|
|
|
|
// MODE_BITS parameter
|
2023-10-02 07:40:10 -05:00
|
|
|
Const mode_bits;
|
2023-11-27 03:35:29 -06:00
|
|
|
for (const auto &it : m_DspParams) {
|
2023-10-02 07:40:10 -05:00
|
|
|
auto val_a = dsp_a->getParam(it);
|
|
|
|
auto val_b = dsp_b->getParam(it);
|
2023-11-27 03:35:29 -06:00
|
|
|
|
2024-10-09 12:39:45 -05:00
|
|
|
mode_bits.bits().insert(mode_bits.bits().end(),
|
|
|
|
val_a.begin(), val_a.end());
|
|
|
|
mode_bits.bits().insert(mode_bits.bits().end(),
|
|
|
|
val_b.begin(), val_b.end());
|
2023-11-27 03:35:29 -06:00
|
|
|
}
|
2023-10-02 07:40:10 -05:00
|
|
|
|
|
|
|
// Enable the fractured mode by connecting the control
|
|
|
|
// port.
|
|
|
|
simd->setPort(ID(f_mode), State::S1);
|
|
|
|
simd->setParam(ID(MODE_BITS), mode_bits);
|
|
|
|
log_assert(mode_bits.size() == m_ModeBitsSize);
|
2023-11-27 03:35:29 -06:00
|
|
|
|
|
|
|
// Handle the "is_inferred" attribute. If one of the fragments
|
|
|
|
// is not inferred mark the whole DSP as not inferred
|
2023-10-02 07:40:10 -05:00
|
|
|
bool is_inferred_a = dsp_a->get_bool_attribute(ID(is_inferred));
|
|
|
|
bool is_inferred_b = dsp_b->get_bool_attribute(ID(is_inferred));
|
|
|
|
simd->set_bool_attribute(ID(is_inferred), is_inferred_a && is_inferred_b);
|
2023-11-27 03:35:29 -06:00
|
|
|
|
|
|
|
// Mark DSP parts for removal
|
|
|
|
cellsToRemove.push_back(dsp_a);
|
|
|
|
cellsToRemove.push_back(dsp_b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove old cells
|
2023-10-02 07:40:10 -05:00
|
|
|
for (auto cell : cellsToRemove)
|
|
|
|
module->remove(cell);
|
2023-11-27 03:35:29 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ..........................................
|
|
|
|
|
|
|
|
/// 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 DSP cell populates and returns a DspConfig struct for it.
|
2024-01-02 04:26:48 -06:00
|
|
|
DspConfig getDspConfig(RTLIL::Cell *a_Cell, const std::vector<std::pair<IdString, IdString>> &dspCfgPorts)
|
2023-11-27 03:35:29 -06:00
|
|
|
{
|
|
|
|
DspConfig config;
|
|
|
|
|
2024-01-02 04:26:48 -06:00
|
|
|
for (const auto &it : dspCfgPorts) {
|
2023-10-02 07:40:10 -05:00
|
|
|
auto port = it.first;
|
2023-11-27 03:35:29 -06:00
|
|
|
|
|
|
|
// Port unconnected
|
2023-10-02 07:40:10 -05:00
|
|
|
if (!a_Cell->hasPort(port))
|
2023-11-27 03:35:29 -06:00
|
|
|
continue;
|
|
|
|
|
2023-10-02 07:40:10 -05:00
|
|
|
config.connections[port] = sigmap(a_Cell->getPort(port));
|
2023-11-27 03:35:29 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return config;
|
|
|
|
}
|
|
|
|
} QlDspSimdPass;
|
|
|
|
|
|
|
|
PRIVATE_NAMESPACE_END
|