ql_dsp_*: Clean up

Clean up the code up to Yosys standards. Drop detection of
QL_DSP2_MULTADD in io_regs since those cells can't be inferred with
the current flow anyway.
This commit is contained in:
Martin Povišer 2023-10-02 14:40:10 +02:00
parent 4bb4fd358e
commit 7d738b07da
2 changed files with 148 additions and 319 deletions

View File

@ -30,26 +30,24 @@ PRIVATE_NAMESPACE_BEGIN
// ============================================================================
struct QlDspIORegs : public Pass {
const std::vector<std::string> ports2del_mult = {"load_acc", "subtract", "acc_fir", "dly_b"};
const std::vector<std::string> ports2del_mult = {"load_acc", "subtract", "acc_fir", "dly_b",
"saturate_enable", "shift_right", "round"};
const std::vector<std::string> ports2del_mult_acc = {"acc_fir", "dly_b"};
const std::vector<std::string> ports2del_mult_add = {"dly_b"};
const std::vector<std::string> ports2del_extension = {"saturate_enable", "shift_right", "round"};
/// Temporary SigBit to SigBit helper map.
SigMap m_SigMap;
SigMap sigmap;
// ..........................................
QlDspIORegs() : Pass("ql_dsp_io_regs", "Changes types of QL_DSP2/QL_DSP3 depending on their configuration.") {}
QlDspIORegs() : Pass("ql_dsp_io_regs", "change types of QL_DSP2 depending on configuration") {}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" ql_dsp_io_regs [options] [selection]\n");
log("\n");
log("Looks for QL_DSP2/QL_DSP3 cells and changes their types depending\n");
log("on their configuration.\n");
log("This pass looks for QL_DSP2 cells and changes their cell type depending on their\n");
log("configuration.\n");
}
void execute(std::vector<std::string> a_Args, RTLIL::Design *a_Design) override
@ -67,178 +65,92 @@ struct QlDspIORegs : public Pass {
}
}
// Returns a pair of mask and value describing constant bit connections of
// a SigSpec
std::pair<uint32_t, uint32_t> get_constant_mask_value(const RTLIL::SigSpec *sigspec)
{
uint32_t mask = 0L;
uint32_t value = 0L;
auto sigbits = sigspec->bits();
for (ssize_t i = (sigbits.size() - 1); i >= 0; --i) {
auto other = m_SigMap(sigbits[i]);
mask <<= 1;
value <<= 1;
// A known constant
if (!other.is_wire() && other.data != RTLIL::Sx) {
mask |= 0x1;
value |= (other.data == RTLIL::S1);
}
}
return std::make_pair(mask, value);
}
void ql_dsp_io_regs_pass(RTLIL::Module *module)
{
// Setup the SigMap
m_SigMap.clear();
m_SigMap.set(module);
sigmap.set(module);
for (auto cell : module->cells_) {
std::string cell_type = cell.second->type.str();
if (cell_type == RTLIL::escape_id("QL_DSP2") || cell_type == RTLIL::escape_id("QL_DSP3")) {
auto dsp = cell.second;
for (auto cell : module->cells()) {
if (cell->type != ID(QL_DSP2))
continue;
// If the cell does not have the "is_inferred" attribute set
// then don't touch it.
if (!dsp->has_attribute(RTLIL::escape_id("is_inferred")) || dsp->get_bool_attribute(RTLIL::escape_id("is_inferred")) == false) {
continue;
}
// If the cell does not have the "is_inferred" attribute set
// then don't touch it.
if (!cell->get_bool_attribute(ID(is_inferred)))
continue;
bool del_clk = true;
bool use_dsp_cfg_params = (cell_type == RTLIL::escape_id("QL_DSP3"));
// Get DSP configuration
for (auto cfg_port : {ID(register_inputs), ID(output_select)})
if (!cell->hasPort(cfg_port) || sigmap(cell->getPort(cfg_port)).is_fully_const())
log_error("Missing or non-constant '%s' port on DSP cell %s\n",
log_id(cfg_port), log_id(cell));
int reg_in_i = sigmap(cell->getPort(ID(register_inputs))).as_int();
int out_sel_i = sigmap(cell->getPort(ID(output_select))).as_int();
int reg_in_i;
int out_sel_i;
// Get the feedback port
if (!cell->hasPort(ID(feedback)))
log_error("Missing 'feedback' port on %s", log_id(cell));
SigSpec feedback = sigmap(cell->getPort(ID(feedback)));
// Get DSP configuration
if (use_dsp_cfg_params) {
// Read MODE_BITS at correct indexes
auto mode_bits = &dsp->getParam(RTLIL::escape_id("MODE_BITS"));
RTLIL::Const register_inputs;
register_inputs = mode_bits->bits.at(MODE_BITS_REGISTER_INPUTS_ID);
reg_in_i = register_inputs.as_int();
// Check the top two bits on 'feedback' to be constant zero.
// That's what we are expecting from inference.
if (feedback.extract(1, 2) != SigSpec(0, 2))
log_error("Unexpected feedback configuration on %s\n", log_id(cell));
RTLIL::Const output_select;
output_select = mode_bits->extract(MODE_BITS_OUTPUT_SELECT_START_ID, MODE_BITS_OUTPUT_SELECT_WIDTH);
out_sel_i = output_select.as_int();
} else {
// Read dedicated configuration ports
const RTLIL::SigSpec *register_inputs;
register_inputs = &dsp->getPort(RTLIL::escape_id("register_inputs"));
if (!register_inputs)
log_error("register_inputs port not found!");
auto reg_in_c = register_inputs->as_const();
reg_in_i = reg_in_c.as_int();
// Build new type name
std::string new_type = "QL_DSP2_MULT";
const RTLIL::SigSpec *output_select;
output_select = &dsp->getPort(RTLIL::escape_id("output_select"));
if (!output_select)
log_error("output_select port not found!");
auto out_sel_c = output_select->as_const();
out_sel_i = out_sel_c.as_int();
}
// Decide if we should be deleting the clock port
bool del_clk = true;
// Get the feedback port
const RTLIL::SigSpec *feedback;
feedback = &dsp->getPort(RTLIL::escape_id("feedback"));
if (!feedback)
log_error("feedback port not found!");
// Check if feedback is or can be set to 0 which implies MACC
auto feedback_con = get_constant_mask_value(feedback);
bool have_macc = (feedback_con.second == 0x0);
// log("mask=0x%08X value=0x%08X\n", consts.first, consts.second);
// log_error("=== END HERE ===\n");
// Build new type name
std::string new_type = cell_type;
new_type += "_MULT";
if (have_macc) {
switch (out_sel_i) {
case 1:
case 2:
case 3:
case 5:
case 7:
del_clk = false;
new_type += "ACC";
break;
default:
break;
}
} else {
switch (out_sel_i) {
case 1:
case 2:
case 3:
case 5:
case 7:
new_type += "ADD";
break;
default:
break;
}
}
if (reg_in_i) {
del_clk = false;
new_type += "_REGIN";
}
if (out_sel_i > 3) {
del_clk = false;
new_type += "_REGOUT";
}
// Set new type name
dsp->type = RTLIL::IdString(new_type);
std::vector<std::string> ports2del;
if (del_clk)
ports2del.push_back("clk");
switch (out_sel_i) {
case 0:
case 4:
case 6:
ports2del.insert(ports2del.end(), ports2del_mult.begin(), ports2del_mult.end());
// Mark for deleton additional configuration ports
if (!use_dsp_cfg_params) {
ports2del.insert(ports2del.end(), ports2del_extension.begin(), ports2del_extension.end());
}
break;
switch (out_sel_i) {
case 1:
case 2:
case 3:
case 5:
case 7:
if (have_macc) {
ports2del.insert(ports2del.end(), ports2del_mult_acc.begin(), ports2del_mult_acc.end());
} else {
ports2del.insert(ports2del.end(), ports2del_mult_add.begin(), ports2del_mult_add.end());
}
del_clk = false;
new_type += "ACC";
break;
}
default:
break;
}
for (auto portname : ports2del) {
const RTLIL::SigSpec *port = &dsp->getPort(RTLIL::escape_id(portname));
if (!port)
log_error("%s port not found!", portname.c_str());
dsp->connections_.erase(RTLIL::escape_id(portname));
}
if (reg_in_i) {
del_clk = false;
new_type += "_REGIN";
}
if (out_sel_i > 3) {
del_clk = false;
new_type += "_REGOUT";
}
// Set new type name
cell->type = RTLIL::IdString(new_type);
std::vector<std::string> ports2del;
if (del_clk)
cell->unsetPort(ID(clk));
switch (out_sel_i) {
case 0:
case 4:
case 6:
for (auto port : ports2del_mult)
cell->unsetPort(port);
break;
case 1:
case 2:
case 3:
case 5:
case 7:
for (auto port : ports2del_mult_acc)
cell->unsetPort(port);
break;
}
}
// Clear the sigmap
m_SigMap.clear();
}
} QlDspIORegs;
PRIVATE_NAMESPACE_END

View File

@ -24,39 +24,30 @@
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
#define MODE_BITS_BASE_SIZE 80
#define MODE_BITS_EXTENSION_SIZE 13
// ============================================================================
struct QlDspSimdPass : public Pass {
QlDspSimdPass() : Pass("ql_dsp_simd", "Infers QuickLogic k6n10f DSP pairs that can operate in SIMD mode") {}
QlDspSimdPass() : Pass("ql_dsp_simd", "merge QuickLogic K6N10f DSP pairs to operate in SIMD mode") {}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
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");
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");
}
// ..........................................
/// Describes DSP config unique to a whole DSP cell
struct DspConfig {
// Port connections
dict<RTLIL::IdString, RTLIL::SigSpec> connections;
// Whether DSPs pass configuration bits through ports of parameters
bool use_cfg_params;
// TODO: Possibly include parameters here. For now we have just
// connections.
DspConfig() = default;
DspConfig(const DspConfig &ref) = default;
@ -64,50 +55,50 @@ struct QlDspSimdPass : public Pass {
unsigned int hash() const { return connections.hash(); }
bool operator==(const DspConfig &ref) const { return connections == ref.connections && use_cfg_params == ref.use_cfg_params; }
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"),
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))
};
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("subtract_i", "subtract")};
// For QL_DSP2 expand with configuration ports
const std::vector<std::pair<std::string, std::string>> m_DspCfgPorts_expand = {
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("register_inputs_i", "register_inputs")};
// For QL_DSP3 use parameters instead
const std::vector<std::string> m_DspParams2Mode = {"OUTPUT_SELECT", "SATURATE_ENABLE", "SHIFT_RIGHT", "ROUND", "REGISTER_INPUTS"};
const int m_ModeBitsSize = 80;
// 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"),
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))
};
// DSP parameters
const std::vector<std::string> m_DspParams = {"COEFF_3", "COEFF_2", "COEFF_1", "COEFF_0"};
// Source DSP cell type (SISD)
const std::string m_SisdDspType = "dsp_t1_10x9x32";
// Suffix for DSP cell with configuration parameters
const std::string m_SisdDspType_cfg_params_suffix = "_cfg_params";
const IdString m_SisdDspType = ID(dsp_t1_10x9x32);
// Target DSP cell types for the SIMD mode
const std::string m_SimdDspType_cfg_ports = "QL_DSP2";
const std::string m_SimdDspType_cfg_params = "QL_DSP3";
const IdString m_SimdDspType = ID(QL_DSP2);
/// Temporary SigBit to SigBit helper map.
SigMap m_SigMap;
SigMap sigmap;
// ..........................................
@ -120,38 +111,32 @@ struct QlDspSimdPass : public Pass {
// Process modules
for (auto module : a_Design->selected_modules()) {
// Setup the SigMap
m_SigMap.clear();
m_SigMap.set(module);
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 we are looking for (type starts with m_SisdDspType)
if (strncmp(cell->type.c_str(), RTLIL::escape_id(m_SisdDspType).c_str(), RTLIL::escape_id(m_SisdDspType).size()) != 0) {
if (cell->type != m_SisdDspType)
continue;
}
// Skip if it has the (* keep *) attribute set
if (cell->has_keep_attr()) {
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;
std::vector<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;
bool use_cfg_params = config.use_cfg_params;
// Ensure an even number
size_t count = group.size();
if (count & 1)
@ -159,66 +144,45 @@ struct QlDspSimdPass : public Pass {
// 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%ld", i / 2);
std::string SimdDspType;
if (use_cfg_params)
SimdDspType = m_SimdDspType_cfg_params;
else
SimdDspType = m_SimdDspType_cfg_ports;
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(),
SimdDspType.c_str());
Cell *dsp_a = group[i];
Cell *dsp_b = group[i + 1];
// Create the new cell
RTLIL::Cell *simd = module->addCell(RTLIL::escape_id(name), RTLIL::escape_id(SimdDspType));
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));
// 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!", SimdDspType.c_str());
}
std::vector<std::pair<std::string, std::string>> DspCfgPorts = m_DspCfgPorts;
if (!use_cfg_params)
DspCfgPorts.insert(DspCfgPorts.end(), m_DspCfgPorts_expand.begin(), m_DspCfgPorts_expand.end());
if (!simd->known())
log_error(" The target cell type '%s' is not known!", log_id(simd));
// Connect common ports
for (const auto &it : DspCfgPorts) {
auto sport = RTLIL::escape_id(it.first);
auto dport = RTLIL::escape_id(it.second);
simd->setPort(dport, config.connections.at(sport));
}
for (const auto &it : m_DspCfgPorts)
simd->setPort(it.first, config.connections.at(it.second));
// Connect data ports
for (const auto &it : m_DspDataPorts) {
auto sport = RTLIL::escape_id(it.first);
auto dport = RTLIL::escape_id(it.second);
size_t width;
bool isOutput;
std::tie(width, isOutput) = getPortInfo(simd, dport);
std::tie(width, isOutput) = getPortInfo(simd, it.second);
auto getConnection = [&](const RTLIL::Cell *cell) {
RTLIL::SigSpec sigspec;
if (cell->hasPort(sport)) {
const auto &sig = cell->getPort(sport);
if (cell->hasPort(it.first)) {
const auto &sig = cell->getPort(it.first);
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()));
}
int padding = width / 2 - sigspec.bits().size();
if (padding) {
if (!isOutput)
sigspec.append(RTLIL::SigSpec(RTLIL::Sx, padding));
else
sigspec.append(module->addWire(NEW_ID, padding));
}
return sigspec;
};
@ -226,49 +190,31 @@ struct QlDspSimdPass : public Pass {
RTLIL::SigSpec sigspec;
sigspec.append(getConnection(dsp_a));
sigspec.append(getConnection(dsp_b));
simd->setPort(dport, sigspec);
simd->setPort(it.second, sigspec);
}
// Concatenate FIR coefficient parameters into the single
// MODE_BITS parameter
std::vector<RTLIL::State> mode_bits;
Const mode_bits;
for (const auto &it : m_DspParams) {
auto val_a = dsp_a->getParam(RTLIL::escape_id(it));
auto val_b = dsp_b->getParam(RTLIL::escape_id(it));
auto val_a = dsp_a->getParam(it);
auto val_b = dsp_b->getParam(it);
mode_bits.insert(mode_bits.end(), val_a.begin(), val_a.end());
mode_bits.insert(mode_bits.end(), val_b.begin(), val_b.end());
mode_bits.bits.insert(mode_bits.end(), val_a.begin(), val_a.end());
mode_bits.bits.insert(mode_bits.end(), val_b.begin(), val_b.end());
}
long unsigned int mode_bits_size = MODE_BITS_BASE_SIZE;
if (use_cfg_params) {
// Add additional config parameters if necessary
mode_bits.push_back(RTLIL::S1); // MODE_BITS[80] == F_MODE : Enable fractured mode
for (const auto &it : m_DspParams2Mode) {
log_assert(dsp_a->getParam(RTLIL::escape_id(it)) == dsp_b->getParam(RTLIL::escape_id(it)));
auto param = dsp_a->getParam(RTLIL::escape_id(it));
if (param.size() > 1) {
mode_bits.insert(mode_bits.end(), param.bits.begin(), param.bits.end());
} else {
mode_bits.push_back(param.bits[0]);
}
}
mode_bits_size += MODE_BITS_EXTENSION_SIZE;
} else {
// Enable the fractured mode by connecting the control
// port.
simd->setPort(RTLIL::escape_id("f_mode"), RTLIL::S1);
}
simd->setParam(RTLIL::escape_id("MODE_BITS"), RTLIL::Const(mode_bits));
log_assert(mode_bits.size() == mode_bits_size);
// 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);
// Handle the "is_inferred" attribute. If one of the fragments
// is not inferred mark the whole DSP as not inferred
bool is_inferred_a =
dsp_a->has_attribute(RTLIL::escape_id("is_inferred")) ? dsp_a->get_bool_attribute(RTLIL::escape_id("is_inferred")) : false;
bool is_inferred_b =
dsp_b->has_attribute(RTLIL::escape_id("is_inferred")) ? dsp_b->get_bool_attribute(RTLIL::escape_id("is_inferred")) : false;
simd->set_bool_attribute(RTLIL::escape_id("is_inferred"), is_inferred_a && is_inferred_b);
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);
// Mark DSP parts for removal
cellsToRemove.push_back(dsp_a);
@ -277,13 +223,9 @@ struct QlDspSimdPass : public Pass {
}
// Remove old cells
for (const auto &cell : cellsToRemove) {
module->remove(const_cast<RTLIL::Cell *>(cell));
}
for (auto cell : cellsToRemove)
module->remove(cell);
}
// Clear
m_SigMap.clear();
}
// ..........................................
@ -317,43 +259,18 @@ struct QlDspSimdPass : public Pass {
{
DspConfig config;
string cell_type = a_Cell->type.str();
string suffix = m_SisdDspType_cfg_params_suffix;
bool use_cfg_params = cell_type.size() >= suffix.size() && 0 == cell_type.compare(cell_type.size() - suffix.size(), suffix.size(), suffix);
std::vector<std::pair<std::string, std::string>> DspCfgPorts = m_DspCfgPorts;
if (!use_cfg_params)
DspCfgPorts.insert(DspCfgPorts.end(), m_DspCfgPorts_expand.begin(), m_DspCfgPorts_expand.end());
config.use_cfg_params = use_cfg_params;
for (const auto &it : DspCfgPorts) {
auto port = RTLIL::escape_id(it.first);
for (const auto &it : m_DspCfgPorts) {
auto port = it.first;
// Port unconnected
if (!a_Cell->hasPort(port)) {
config.connections[port] = RTLIL::SigSpec(RTLIL::Sx);
if (!a_Cell->hasPort(port))
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;
config.connections[port] = sigmap(a_Cell->getPort(port));
}
return config;
}
} QlDspSimdPass;
PRIVATE_NAMESPACE_END