3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2026-06-20 15:50:27 +00:00

[core] add rf techlibs

This commit is contained in:
tangxifan 2026-05-14 17:33:24 -07:00
parent 54f8505045
commit d7cf53d86a
49 changed files with 34443 additions and 0 deletions

View file

@ -0,0 +1,553 @@
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <string>
#include "backends/rtlil/rtlil_backend.h"
#include "kernel/celltypes.h"
#include "kernel/log.h"
#include "kernel/register.h"
#include "kernel/rtlil.h"
#include "kernel/sigtools.h"
#include "kernel/yosys.h"
#include "pugixml.hpp"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
struct InsertClockBuffer : public Pass {
InsertClockBuffer()
: Pass("insert_clock_buffer",
"This command is to insert clock buffer into the design") {}
/*utility function used by insert_ckbuff; copied from blif.cc*/
const std::string str(RTLIL::IdString id) {
std::string str = RTLIL::unescape_id(id);
for (size_t i = 0; i < str.size(); i++)
if (str[i] == '#' || str[i] == '=' || str[i] == '<' || str[i] == '>')
str[i] = '?';
return str;
}
/*utility function used by insert_ckbuff; copied from blif.cc*/
const std::string str(RTLIL::SigBit sig) {
if (sig.wire == NULL) {
return "null";
}
std::string str = RTLIL::unescape_id(sig.wire->name);
for (size_t i = 0; i < str.size(); i++)
if (str[i] == '#' || str[i] == '=' || str[i] == '<' || str[i] == '>')
str[i] = '?';
if (sig.wire->width != 1)
str +=
stringf("[%d]", sig.wire->upto ? sig.wire->start_offset +
sig.wire->width - sig.offset - 1
: sig.wire->start_offset + sig.offset);
return str;
}
// eval_lut: Evaluate the output of a single LUT based on given input values
// Parameters:
// lut - Pointer to an RTLIL $lut cell
// inputs - map<SigBit, bool> specifying the values of input signals
// (can provide only a subset of inputs)
// sigmap - SigMap, used to get the actual driving signal for each input
// Returns:
// Boolean output of the LUT (true/false)
bool eval_lut(const RTLIL::Cell *lut, std::map<SigBit, bool> inputs,
const SigMap &sigmap) {
// Get the input vector of the LUT and map each signal to its actual driver
SigSpec lut_inputs = sigmap(lut->getPort(ID::A));
// Number of LUT inputs
int width = lut->getParam(ID::WIDTH).as_int();
// LUT truth table storing output for each input combination
Const lut_table = lut->getParam(ID::LUT);
// Index into the LUT truth table
int lut_index = 0;
// Iterate through each input bit
for (int i = 0; i < width; i++) {
// Get the i-th input signal and map it to its final driver
SigBit bit = sigmap(lut_inputs[i]);
// Boolean value of the current input
bool value;
// If the input value is provided by the user, use it
if (inputs.count(bit))
value = inputs[bit];
// Otherwise, use the signal's default or constant value
else
value = SigSpec(bit).as_bool();
// Accumulate this bit into the LUT index
// '<< i' places the value at the correct bit position
lut_index |= (value << i);
}
// Lookup the LUT output for the computed index and return as bool
return lut_table.extract(lut_index).as_bool();
}
/* This function rewires subckt such as flip-flops (FFs) and pcounter that use
internally generated signals as their clock inputs. We can perform this
rewiring before connecting the new clock buffer (ckbuf), because the new wires
are added to the top modulenot to the ckbuf itself. Therefore, once an
internally generated clock is detected, a new wire is created in the top
module and directly connected to the affected subckt. This rewiring process is
independent of the addition of the ckbuf. */
void rewire_subckt(RTLIL::Module *module, RTLIL::Cell *cell,
RTLIL::IdString id_name, std::string C_input) {
std::string C_output = C_input + "_ckbuf";
if (!module->wire("\\" + C_output)) {
auto output_wire = module->addWire("\\" + C_output, 1);
}
/* connect new ckbuf to the subckt */
cell->unsetPort(id_name); // unsetPort("C")
cell->setPort(id_name, module->wire("\\" + C_output));
cell->fixup_parameters();
}
/* This is a general-purpose function that works for all subcircuits (subckt).
It detects clock and reset signals and determines whether they are global.
If any global signal is detected, the function will rewire the subcircuit
accordingly.
*/
void process_cell(RTLIL::Module *module, RTLIL::Cell *cell,
std::map<int, RTLIL::Wire *> inputs,
std::vector<RTLIL::IdString> clk_indicator_group,
std::vector<RTLIL::IdString> reset_indicator_group,
std::set<std::string> &ckbuf_info,
std::map<std::string, std::string> &ckbuf_type) {
RTLIL::IdString clk_indicator;
for (auto clk : clk_indicator_group) {
if (cell->hasPort(clk)) {
clk_indicator = clk;
break;
}
}
// dff could have reset signal with keywords R or RN. We shall determine
// which is the port name for current cell
RTLIL::IdString reset_indicator;
for (auto rst : reset_indicator_group) {
if (cell->hasPort(rst)) {
reset_indicator = rst;
break;
}
}
if (reset_indicator.empty() && clk_indicator.empty()) {
return;
}
bool global_clock = false;
bool global_reset = false;
for (auto &it : inputs) {
RTLIL::Wire *wire = it.second;
for (int i = 0; i < wire->width; i++) {
if (cell->hasPort(clk_indicator)) {
if (cell->getPort(clk_indicator) == RTLIL::SigSpec(wire, i)) {
global_clock = true;
continue; /*if the signal is global clock, then there is no need
to check whether it is global reset or not*/
}
}
if (cell->hasPort(reset_indicator)) {
if (cell->getPort(reset_indicator) == RTLIL::SigSpec(wire, i)) {
global_reset = true;
}
}
}
}
/*grab the information of the internally generated clocks*/
if (!global_clock && cell->hasPort(clk_indicator)) {
std::string C_input = str(cell->getPort(clk_indicator)).c_str();
ckbuf_info.insert(C_input);
ckbuf_type[C_input] = "clock";
rewire_subckt(module, cell, clk_indicator, C_input);
}
/*grab the information of the internally generated resets*/
if (!global_reset && cell->hasPort(reset_indicator)) {
std::string C_input = str(cell->getPort(reset_indicator)).c_str();
ckbuf_info.insert(C_input);
ckbuf_type[C_input] = "reset";
rewire_subckt(module, cell, reset_indicator, C_input);
}
}
/* This function examines sequential logic and returns a set of strings
representing internally generated signals. When such a signal is found, it
invokes rewire_subckt.*/
std::set<std::string>
find_internal_clk_r_signal(RTLIL::Module *module, RTLIL::Design *design,
std::map<std::string, std::string> &ckbuf_type) {
std::set<std::string> ckbuf_info;
/*get input ports of the top module*/
std::map<int, RTLIL::Wire *> inputs, outputs;
for (auto wire : module->wires()) {
if (wire->port_input)
inputs[wire->port_id] = wire;
}
for (auto cell : module->cells()) {
/*Bypass yosys internal cells $lut which doesn't have clock signal */
if ((cell->type) == ID($lut)) {
continue;
} else {
/*check whether the C port is internally generated clock signal or not*/
std::vector<RTLIL::IdString> clk_indicator_group = {ID(C), ID(clk_i)};
std::vector<RTLIL::IdString> reset_indicator_group = {ID(RN), ID(R),
ID(rst_i)};
process_cell(module, cell, inputs, clk_indicator_group,
reset_indicator_group, ckbuf_info, ckbuf_type);
}
}
return ckbuf_info;
}
/* insert .subckt ckbuf for each internally generated clock or reset signal */
void insert_ckbuf(RTLIL::Module *module,
const std::set<std::string> &ckbuf_info) {
for (const std::string &ckbuf : ckbuf_info) {
RTLIL::Cell *ckbuf_cell =
module->addCell(stringf("$ckbuf$%s", ckbuf.c_str()), "\\ckbuf");
ckbuf_cell->setPort("\\in", module->wire("\\" + ckbuf));
ckbuf_cell->setPort("\\out", module->wire("\\" + ckbuf + "_ckbuf"));
ckbuf_cell->set_src_attribute("");
}
}
/*This function is for generating cell map file*/
void generate_cell_map(const char *fname,
const std::set<std::string> &ckbuf_info,
const std::map<std::string, std::string> &ckbuf_type) {
pugi::xml_document out_xml;
pugi::xml_node root_node = out_xml.append_child("ckbuf_cell_map");
for (const std::string &ckbuf : ckbuf_info) {
std::string in = ckbuf;
std::string out = ckbuf + "_ckbuf";
std::string type;
auto it = ckbuf_type.find(ckbuf);
if (it != ckbuf_type.end()) {
type = it->second;
} else {
log_error("No port type defined for ckbuf %s \n", out.c_str());
}
pugi::xml_node ckbuf_node = root_node.append_child("ckbuf");
ckbuf_node.append_attribute("input_net") = in.c_str();
ckbuf_node.append_attribute("cell") = out.c_str();
ckbuf_node.append_attribute("type") = type.c_str();
}
out_xml.save_file(fname);
log("cell map is stored in file %s \n", fname);
}
void rewire_lut_primitive(RTLIL::Cell *cell,
const std::vector<RTLIL::SigBit> &sig,
const RTLIL::Const &new_lut, int new_width) {
cell->unsetPort(ID::A);
cell->setPort(ID::A, sig);
// Update LUT parameters
cell->parameters[ID::LUT] = new_lut; // Set new truth table
cell->parameters[ID::WIDTH] = new_width;
cell->fixup_parameters();
}
void process_cell_rewire_lut(
RTLIL::Module *module, RTLIL::Cell *cell,
const std::map<std::string, std::set<RTLIL::SigBit>>
&internal_signal_io_map,
const std::map<std::string, std::string> &internal_signal_lut,
const Yosys::SigMap &sigmap) {
std::set<RTLIL::SigBit> sig;
/* sig and ordered_sig contain the same signals.
* sig is used to store the rewired signals and avoid duplicates,
* while ordered_sig preserves the signal order so it stays aligned
* with the truth table information.
*/
std::vector<RTLIL::SigBit> ordered_sig;
std::map<RTLIL::SigBit, std::set<RTLIL::SigBit>> sig_replace_port_map;
std::map<RTLIL::SigBit, RTLIL::IdString> sig_replace_cell_map;
std::set<RTLIL::SigBit> temp_sig;
std::set<RTLIL::SigBit> lut_inputs = cell->getPort(ID::A).to_sigbit_set();
std::vector<RTLIL::SigBit> lut_inputs_vector =
cell->getPort(ID::A).to_sigbit_vector();
std::set<RTLIL::SigBit> common_port_all;
std::vector<bool> replaced_boolean_value;
std::vector<bool> remained_boolean_value;
bool rewire_required = false;
std::vector<RTLIL::State> old_lut =
cell->parameters.at(ID::LUT).bits(); // Original LUT truth table
for (const auto &[internal_signal_output, internal_signal_input] :
internal_signal_io_map) {
std::set<RTLIL::SigBit> common_port;
std::string mapped_ckbuf_name;
if (std::includes(lut_inputs.begin(), lut_inputs.end(),
internal_signal_input.begin(),
internal_signal_input.end())) {
std::set_intersection(lut_inputs.begin(), lut_inputs.end(),
internal_signal_input.begin(),
internal_signal_input.end(),
std::inserter(common_port, common_port.end()));
std::string C_output = internal_signal_output + "_ckbuf";
mapped_ckbuf_name = internal_signal_output;
if (!module->wire("\\" + C_output)) {
log("wire %s is not defined", C_output.c_str());
}
auto replaced_sig = module->wire("\\" + C_output);
if (sig.find(replaced_sig) != sig.end()) {
/* element has already been recorded*/
log("signal %s has been replaced!\n", C_output.c_str());
continue;
}
sig.insert(replaced_sig);
if (std::find(ordered_sig.begin(), ordered_sig.end(), replaced_sig) ==
ordered_sig.end()) {
ordered_sig.push_back(replaced_sig);
}
sig_replace_port_map[replaced_sig] = common_port;
auto src_cell = internal_signal_lut.at(mapped_ckbuf_name);
sig_replace_cell_map[replaced_sig] = src_cell;
rewire_required = true;
common_port_all.insert(common_port.begin(), common_port.end());
}
}
if (rewire_required) {
// For example, the following lut will be rewired
// .names a b internal_clock1
// .names a b c out
// as
// .names internal_clock1_ckbuf c out
// where internal_clock1 is the original internal signals and
// internal_clock1_ckbuf is the outputs of ckbuf. (.subckt ckbuf
// internal_clock1 internal_clock1_ckbuf)
// we need to update the truth table of .names internal_clock1_ckbuf
// c out concurrently
/* The first step is to get the original truth table, i.e. the truth
* table of .names a b c d out */
int bit_width = lut_inputs_vector.size();
std::vector<std::vector<bool>> original_truth_table;
std::map<RTLIL::SigBit, int> sig_index_map;
for (size_t index = 0; index < lut_inputs_vector.size(); index++) {
sig_index_map[lut_inputs_vector[index]] = index;
}
for (int index = 0; index < old_lut.size(); index++) {
if (old_lut[index] == RTLIL::State::S1) {
std::vector<bool> bits(bit_width, false);
for (int b = 0; b < bit_width; b++) {
bits[b] = (index >> b) & 1;
}
original_truth_table.push_back(bits);
}
}
/* get remained sigs */
std::set_difference(lut_inputs.begin(), lut_inputs.end(),
common_port_all.begin(), common_port_all.end(),
std::inserter(temp_sig, temp_sig.end()));
std::vector<std::vector<bool>> modified_bit;
for (const auto &line : original_truth_table) {
std::vector<bool> remained_bit;
/* get sigs that can be replaced by ckbuf and evaluate its truth
* table values and updates the final truth table*/
for (auto replaced_sig : sig) {
const RTLIL::Cell *cell_temp =
module->cell(sig_replace_cell_map[replaced_sig]);
auto ports = sig_replace_port_map[replaced_sig];
std::map<RTLIL::SigBit, bool> common_port_map;
for (auto port : ports) {
common_port_map[port] = line[sig_index_map[port]];
}
bool changed_bit = eval_lut(cell_temp, common_port_map, sigmap);
remained_bit.push_back(changed_bit);
}
/* get remained sigs and use previous truth table values*/
for (auto port : temp_sig) {
remained_bit.push_back(line[sig_index_map[port]]);
}
modified_bit.push_back(remained_bit);
}
/* get the final truth table of the rewired lut such as .names
* internal_clock1_ckbuf c out */
std::vector<int> modified_bit_int;
for (const auto &line : modified_bit) {
int value = 0;
for (size_t i = 0; i < line.size(); i++) {
if (line[i]) {
value |= (1 << i);
}
}
modified_bit_int.push_back(value);
}
sig.insert(temp_sig.begin(), temp_sig.end());
ordered_sig.insert(ordered_sig.end(), temp_sig.begin(), temp_sig.end());
int new_width = sig.size();
int num_entries = 1 << new_width;
RTLIL::Const new_lut(num_entries);
/*initialize lut value to 0*/
for (int i = 0; i < num_entries; i++) {
new_lut.bits()[i] = RTLIL::State::S0;
}
/* assign final truth table's info to current cell */
for (int i : modified_bit_int) {
new_lut.bits()[i] = RTLIL::State::S1;
}
rewire_lut_primitive(cell, ordered_sig, new_lut, new_width);
module->fixup_ports();
std::set<RTLIL::SigBit> lut_outputs =
cell->getPort(ID::Y).to_sigbit_set();
}
}
/* This function rewires luts which have internally generated signals as its
* input*/
void rewire_luts(RTLIL::Module *module,
const std::set<std::string> &ckbuf_info) {
/* find the lut that has internally generated clock as an output and get its
* io map */
Yosys::SigMap sigmap(module);
std::map<std::string, std::set<RTLIL::SigBit>> internal_signal_io_map;
std::map<std::string, std::string> internal_signal_lut;
std::set<RTLIL::SigBit> flattened_io_map;
/*the first for loop constructs the map between internall signal name and
its corresponding fan-ins For example, consider the following netlist:
.names a b internal_clock1
.names c d internal_clock2
The internal_signal_io_map stores key-value pairs from all luts like this:
[internal_clock1, {a, b}], [internal_clock2, {c, d}].
*/
for (const auto cell : module->cells()) {
if ((cell->type) == ID($lut)) {
auto &inputs = cell->getPort(ID::A);
std::string output = str(cell->getPort(ID::Y));
auto width = cell->parameters.at(ID::WIDTH).as_int();
log_assert(inputs.size() == width);
if (ckbuf_info.find(output) != ckbuf_info.end()) {
/* We assume that all LUT cells are explicitly named before the
current pass. Anonymous cells indicate an unexpected state after
techmapping and are treated as a fatal error. */
if (cell->name.empty()) {
log_error("cell->name is empty for output=%s\n", output.c_str());
}
auto io_set = inputs.to_sigbit_set();
internal_signal_io_map[output] = io_set;
internal_signal_lut[output] = cell->name.c_str();
flattened_io_map.insert(internal_signal_io_map[output].begin(),
internal_signal_io_map[output].end());
continue;
}
}
}
/* This for loop does two things:
1. detect logic that can be replaced by internal signals
For example, consider the following netlist:
.names a b internal_clock1
.names c d internal_clock2
The internal_signal_io_map stores key-value pairs from all luts like
this: [internal_clock1, {a, b}], [internal_clock2, {c, d}]. When we
encounter a netlist like: .names a b c d out we can replace it with .names
internal_clock1 internal_clock2 out
2. rewire luts
For example, the following lut will be rewired
.names internal_clock1 internal_clock2 out
as
.names internal_clock1_ckbuf internal_clock2_ckbuf out_ckbuf
where internal_clock1/2 are the original internal signals and
internal_clock1_ckbuf/2_ckbuf are the outputs of ckbuf. (.subckt ckbuf
internal_clock1 internal_clock1_ckbuf)
*/
for (auto cell : module->cells()) {
if ((cell->type) == ID($lut)) {
std::string output = str(cell->getPort(ID::Y));
if (ckbuf_info.find(output) != ckbuf_info.end()) {
continue; /*by pass the luts that generate internal clk/reset */
} else {
process_cell_rewire_lut(module, cell, internal_signal_io_map,
internal_signal_lut, sigmap);
/*replace internal clk/reset with clk/reset_buf signal */
}
}
}
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override {
log("Arguments to the command insert_clock_buffer:\n");
std::string top_module_name;
std::string cell_map_file;
for (size_t i = 0; i < args.size(); i++) {
log(" %s\n", args[i].c_str());
if (args[i] == "-top" && i + 1 < args.size()) {
top_module_name = args[i + 1];
} else if (args[i] == "-cell_map_file" && i + 1 < args.size()) {
cell_map_file = args[i + 1];
}
}
if (cell_map_file.empty()) {
cell_map_file = "cell_map.xml";
}
log("cell map location is %s \n", cell_map_file.c_str());
/*if top_module_name is empty, get it from design*/
if (top_module_name.empty())
for (auto module : design->modules())
if (module->get_bool_attribute(ID::top))
top_module_name = module->name.str();
if (top_module_name.empty()) {
log_error("No top module detected. Insert clock buffer failed.");
} else {
log("Top module in current design: %s \n", top_module_name.c_str());
}
/*Insert clock buffer into the top module*/
design->sort();
for (auto module : design->modules()) {
/*only insert buffer to top module*/
if (module->name == RTLIL::escape_id(top_module_name)) {
std::map<std::string, std::string> ckbuf_type;
std::set<std::string> ckbuf_info =
find_internal_clk_r_signal(module, design, ckbuf_type);
/*insert ckbuf and rewire dff */
insert_ckbuf(module, ckbuf_info);
module->fixup_ports();
/*rewire luts */
rewire_luts(module, ckbuf_info);
module->fixup_ports();
/* print out cells */
if (!ckbuf_info.empty()) {
generate_cell_map(cell_map_file.c_str(), ckbuf_info, ckbuf_type);
} else {
log("Ckbuf info is empty. No cell map file will be generated! \n");
}
break;
}
}
design->check();
}
} Insert_clock_buffer;
PRIVATE_NAMESPACE_END

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,192 @@
#include "kernel/sigtools.h"
#include "kernel/yosys.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
#include "pmgen/rf_dsp_mad.h"
static void create_rf_mad_dsp(rf_dsp_mad_pm &pm) {
auto &st = pm.st_rf_dsp_mad;
// Reject if multiplier drives anything else than $add
if (st.mul_nusers > 2) {
return;
}
// Get port widths
size_t a_width = GetSize(st.mul->getPort(ID(A)));
size_t b_width = GetSize(st.mul->getPort(ID(B)));
size_t c_width = GetSize(st.add->getPort(ID(A)));
if (st.add_ba == ID(B)) {
c_width = GetSize(st.add->getPort(ID(B)));
}
size_t z_width = GetSize(st.add->getPort(ID(Y)));
size_t min_width = std::min(a_width, b_width);
size_t max_width = std::max(a_width, b_width);
// Signed / unsigned
bool a_signed = st.mul->getParam(ID(A_SIGNED)).as_bool();
bool b_signed = st.mul->getParam(ID(B_SIGNED)).as_bool();
bool c_signed = st.add->getParam(ID(A_SIGNED)).as_bool();
if (st.add_ba == ID(B)) {
c_signed = st.add->getParam(ID(B_SIGNED)).as_bool();
}
// Determine DSP type or discard if too narrow / wide
RTLIL::IdString type;
size_t tgt_a_width;
size_t tgt_b_width;
size_t tgt_c_width;
size_t tgt_z_width;
string cell_base_name = "mad";
string cell_size_name = "";
string cell_cfg_name = "";
string cell_full_name = "";
if (min_width <= 2 && max_width <= 2 && z_width <= 4) {
// Too narrow
return;
} else if (min_width <= 12 && max_width <= 10 && z_width <= 22) {
cell_size_name = "12x10x22";
tgt_a_width = 12;
tgt_b_width = 10;
tgt_c_width = 22;
tgt_z_width = 22;
} else if (min_width <= 24 && max_width <= 20 && z_width <= 44) {
cell_size_name = "24x20x44";
tgt_a_width = 24;
tgt_b_width = 20;
tgt_c_width = 44;
tgt_z_width = 44;
} else {
// Too wide
return;
}
cell_full_name = cell_base_name + cell_size_name + cell_cfg_name;
type = RTLIL::escape_id(cell_full_name);
log("Inferring MAD %zux%zu+%zu->%zu as %s from:\n", a_width, b_width, c_width,
z_width, RTLIL::unescape_id(type).c_str());
for (auto cell : {st.mul, st.add}) {
if (cell != nullptr) {
log(" %s (%s)\n", RTLIL::unescape_id(cell->name).c_str(),
RTLIL::unescape_id(cell->type).c_str());
}
}
// Build the DSP cell name
std::string name;
name += RTLIL::unescape_id(st.mul->name) + "_";
name += RTLIL::unescape_id(st.add->name) + "_";
// Add the DSP cell
RTLIL::Cell *cell = pm.module->addCell(RTLIL::escape_id(name), type);
// Set attributes
cell->set_bool_attribute(RTLIL::escape_id("is_inferred"), true);
// Get input/output data signals
RTLIL::SigSpec sig_a;
RTLIL::SigSpec sig_b;
RTLIL::SigSpec sig_c;
RTLIL::SigSpec sig_z;
if (a_width >= b_width) {
sig_a = st.mul->getPort(ID(A));
sig_b = st.mul->getPort(ID(B));
} else {
sig_a = st.mul->getPort(ID(B));
sig_b = st.mul->getPort(ID(A));
}
sig_c = st.add->getPort(ID(A));
if (st.add_ba == ID(B)) {
sig_c = st.add->getPort(ID(B));
}
sig_z = st.add->getPort(ID(Y));
// Connect input data ports, sign extend / pad with zeros
sig_a.extend_u0(tgt_a_width, a_signed);
sig_b.extend_u0(tgt_b_width, b_signed);
sig_c.extend_u0(tgt_c_width, c_signed);
cell->setPort(RTLIL::escape_id("A0"), sig_a);
cell->setPort(RTLIL::escape_id("B0"), sig_b);
// Connect input data port, pad if needed
if ((size_t)GetSize(sig_c) < tgt_c_width) {
auto *wire = pm.module->addWire(NEW_ID, tgt_c_width - GetSize(sig_c));
sig_c.append(wire);
}
cell->setPort(RTLIL::escape_id("C0"), sig_c);
// Connect output data port, pad if needed
if ((size_t)GetSize(sig_z) < tgt_z_width) {
auto *wire = pm.module->addWire(NEW_ID, tgt_z_width - GetSize(sig_z));
sig_z.append(wire);
}
cell->setPort(RTLIL::escape_id("Y"), sig_z);
bool subtract = (st.add->type == RTLIL::escape_id("$sub"));
if (subtract) {
cell->setPort(RTLIL::escape_id("subtract_i"),
RTLIL::SigSpec(subtract ? RTLIL::S1 : RTLIL::S0));
}
// Mark the cells for removal
pm.autoremove(st.mul);
pm.autoremove(st.add);
}
struct RfDspMacc : public Pass {
// Local variables
bool show_help;
RfDspMacc()
: Pass("rf_dsp_mad", "Extract multiply-add and multiply-subtract "
"operators and map to dedicated DSPs") {}
void help() override {
log("\n");
log(" rf_dsp_mad [options] [selection]\n");
log("\n");
log(" Extract multiply-add and multiply-subtract operators and map to "
"dedicated DSPs\n");
log("\n");
log(" -help: show help desk\n");
log("\n");
}
void clear_flags() override { show_help = false; }
void execute(std::vector<std::string> a_Args,
RTLIL::Design *a_Design) override {
log_header(a_Design, "Executing RF_DSP_MAD pass.\n");
size_t argidx;
for (argidx = 1; argidx < a_Args.size(); argidx++) {
if (a_Args[argidx] == "-help") {
show_help = true;
continue;
}
break;
}
extra_args(a_Args, argidx, a_Design);
if (show_help) {
help();
return;
}
for (auto module : a_Design->selected_modules()) {
rf_dsp_mad_pm(module, module->selected_cells())
.run_rf_dsp_mad(create_rf_mad_dsp);
}
}
} RfDspMad;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,26 @@
pattern rf_dsp_mad
state <IdString> add_ba
state <int> mul_nusers
state <int> add_nusers
match mul
select mul->type.in($mul)
select nusers(port(mul, \Y)) <= 3
set mul_nusers nusers(port(mul, \Y))
endmatch
match add
select add->type.in($add, $sub)
choice <IdString> AB {\A, \B}
define <IdString> BA (AB == \A ? \B : \A)
index <SigSpec> port(add, AB) === port(mul, \Y)
select nusers(port(add, \Y)) <= 3
set add_nusers nusers(port(add, \Y))
set add_ba BA
endmatch
code
accept;
endcode

View file

@ -0,0 +1,441 @@
#include "kernel/sigtools.h"
#include "kernel/yosys.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
#include "pmgen/rf_new_dsp.h"
void swapinput(RTLIL::SigSpec &sigA, RTLIL::SigSpec &sigB) {
if (GetSize(sigA) < GetSize(sigB)) {
RTLIL::SigSpec sigC = sigB;
sigB = sigA;
sigA = sigC;
}
}
void rf_new_dsp(rf_new_dsp_pm &pm) {
auto &st = pm.st_rf_new_dsp;
log("mul1: %s\n", log_id(st.mul1, "--"));
log("mul2: %s\n", log_id(st.mul2, "--"));
log("mul3: %s\n", log_id(st.mul3, "--"));
log("mul4: %s\n", log_id(st.mul4, "--"));
log("postAdd1: %s\n", log_id(st.postAdd1, "--"));
log("postAdd2: %s\n", log_id(st.postAdd2, "--"));
log("postAdd3: %s\n", log_id(st.postAdd3, "--"));
log("postAdd4: %s\n", log_id(st.postAdd4, "--"));
RTLIL::SigSpec sigA, sigB, sigD, sigY;
// mode
string mode;
if (st.level == 4) {
if (st.dinput)
mode +=
"1001"; // 4-level mac with d input: d + a1*b1 + a2*b2 + a3*b3 + a4*b4
else
mode +=
"0000"; // 4-level mac without d input: a1*b1 + a2*b2 + a3*b3 + a4*b4
}
if (st.level == 3)
mode += "1001"; // 3-level mac with d input: d + a1*b1 + a2*b2 + a3*b3
if (st.level == 2) {
if (st.dinput)
mode += "0001"; // 2-level mac with d input: d + a1*b1 + a2*b2
else
mode += "0010"; // 2-level mac without d input: a1*b1 + a2*b2
}
if (st.level == 1) {
if (st.dinput)
mode += "0101"; // 1-level mac with d input: d + a1*b1
else
return;
}
// input size
int n_size = 0;
int m_size = 0;
int d_size = 0;
string cell_base_name = "mad";
string cell_size_name = "";
string cell_cfg_name = "";
string cell_full_name = "";
if (st.mul1) {
swapinput(st.sigA1, st.sigB1);
n_size = n_size > GetSize(st.sigA1) ? n_size : GetSize(st.sigA1);
m_size = m_size > GetSize(st.sigB1) ? m_size : GetSize(st.sigB1);
}
if (st.mul2) {
swapinput(st.sigA2, st.sigB2);
n_size = n_size > GetSize(st.sigA2) ? n_size : GetSize(st.sigA2);
m_size = m_size > GetSize(st.sigB2) ? m_size : GetSize(st.sigB2);
}
if (st.mul3) {
swapinput(st.sigA3, st.sigB3);
n_size = n_size > GetSize(st.sigA3) ? n_size : GetSize(st.sigA3);
m_size = m_size > GetSize(st.sigB3) ? m_size : GetSize(st.sigB3);
}
if (st.mul4) {
swapinput(st.sigA4, st.sigB4);
n_size = n_size > GetSize(st.sigA4) ? n_size : GetSize(st.sigA4);
m_size = m_size > GetSize(st.sigB4) ? m_size : GetSize(st.sigB4);
}
if (st.dinput)
d_size = GetSize(st.sigD);
if (mode == "0100") {
n_size = (n_size + 1) / 2;
m_size = (m_size + 1) / 2;
}
if (n_size <= 2 && m_size <= 2 && d_size <= 4) {
// Too narrow
return;
} else if (n_size <= 12 && m_size <= 10 && d_size <= 30) {
cell_size_name = "12x10x22";
n_size = 12;
m_size = 10;
d_size = 30;
} else if (n_size <= 24 && m_size <= 20 && d_size <= 52) {
cell_size_name = "24x20x44";
n_size = 24;
m_size = 20;
d_size = 52;
} else {
// Too wide
return;
}
// cell
cell_full_name = cell_base_name + cell_size_name + cell_cfg_name;
string cellname;
cellname += "newdsp_" + RTLIL::unescape_id(st.mul1->name);
RTLIL::Cell *cell = pm.module->addCell(RTLIL::escape_id(cellname),
RTLIL::escape_id(cell_full_name));
// D input
bool d_signed = false;
if (st.dinput) {
d_signed = st.postAdd1->getParam(ID(A_SIGNED)).as_bool();
if (mode == "0001")
sigD.extend_u0(d_size);
sigD.append(st.sigD);
}
sigD.extend_u0(2 * d_size, d_signed);
// output
if (st.multiout2 || st.multiout3) {
auto *wire = pm.module->addWire(NEW_ID, d_size - GetSize(st.sigY2));
sigY.append(st.sigY2);
sigY.append(wire);
sigY.append(st.sigY);
} else if (mode == "0001" || (st.level == 4 && st.dinput)) {
auto *wire = pm.module->addWire(NEW_ID, d_size);
sigY.append(wire);
sigY.append(st.sigY);
} else
sigY.append(st.sigY);
auto *wire = pm.module->addWire(NEW_ID, 2 * d_size - GetSize(sigY));
sigY.append(wire);
// input
bool a_signed, b_signed;
if (mode == "0001") {
sigA.extend_u0(2 * n_size);
sigB.extend_u0(2 * m_size);
}
if (st.mul1 && mode != "0100") {
a_signed = st.mul1->getParam(ID(A_SIGNED)).as_bool();
b_signed = st.mul1->getParam(ID(B_SIGNED)).as_bool();
st.sigA1.extend_u0(n_size, a_signed);
st.sigB1.extend_u0(m_size, b_signed);
sigA.append(st.sigA1);
sigB.append(st.sigB1);
}
if (mode == "0100") {
a_signed = st.mul1->getParam(ID(A_SIGNED)).as_bool();
b_signed = st.mul1->getParam(ID(B_SIGNED)).as_bool();
st.sigA1.extend_u0(2 * n_size, a_signed);
st.sigB1.extend_u0(2 * m_size, b_signed);
sigA.append(st.sigA1);
sigB.append(st.sigB1);
}
if (!st.dinput && st.mul4) {
a_signed = st.mul4->getParam(ID(A_SIGNED)).as_bool();
b_signed = st.mul4->getParam(ID(B_SIGNED)).as_bool();
st.sigA4.extend_u0(n_size, a_signed);
st.sigB4.extend_u0(m_size, b_signed);
sigA.append(st.sigA4);
sigB.append(st.sigB4);
}
if (st.mul2) {
a_signed = st.mul2->getParam(ID(A_SIGNED)).as_bool();
b_signed = st.mul2->getParam(ID(B_SIGNED)).as_bool();
st.sigA2.extend_u0(n_size, a_signed);
st.sigB2.extend_u0(m_size, b_signed);
sigA.append(st.sigA2);
sigB.append(st.sigB2);
}
if (st.mul3) {
a_signed = st.mul3->getParam(ID(A_SIGNED)).as_bool();
b_signed = st.mul3->getParam(ID(B_SIGNED)).as_bool();
st.sigA3.extend_u0(n_size, a_signed);
st.sigB3.extend_u0(m_size, b_signed);
sigA.append(st.sigA3);
sigB.append(st.sigB3);
}
if (st.dinput && st.mul4) {
a_signed = st.mul4->getParam(ID(A_SIGNED)).as_bool();
b_signed = st.mul4->getParam(ID(B_SIGNED)).as_bool();
st.sigA4.extend_u0(n_size, a_signed);
st.sigB4.extend_u0(m_size, b_signed);
sigA.append(st.sigA4);
sigB.append(st.sigB4);
}
sigA.extend_u0(4 * n_size);
sigB.extend_u0(4 * m_size);
// reg
mode = "00000" + mode + "0000";
if (st.level == 1) {
if (st.ffA1 && st.ffB1 && !(st.dinput && !st.ffD)) {
mode[1] = mode[2] = '1';
pm.autoremove(st.ffA1);
pm.autoremove(st.ffB1);
if (st.dinput)
pm.autoremove(st.ffD);
}
if (st.ffM1 && st.ffD && st.postAdd1) {
mode[11] = '1';
pm.autoremove(st.ffM1);
pm.autoremove(st.ffD);
}
if (st.ffY1 || (!st.postAdd1 && st.ffM1)) {
mode[3] = mode[4] = '1';
pm.autoremove(st.ffY1);
pm.autoremove(st.ffM1);
}
}
if (st.level == 2) {
if (st.ffA1 && st.ffB1 && st.ffA2 && st.ffB2 && st.dinput &&
st.ffD) // in reg
{
mode[1] = mode[2] = '1';
pm.autoremove(st.ffA1);
pm.autoremove(st.ffB1);
pm.autoremove(st.ffA2);
pm.autoremove(st.ffB2);
pm.autoremove(st.ffD);
}
if (st.ffA1 && st.ffB1 && st.ffA4 && st.ffB4 && !st.dinput) // in reg
{
mode[1] = mode[2] = '1';
pm.autoremove(st.ffA1);
pm.autoremove(st.ffB1);
pm.autoremove(st.ffA4);
pm.autoremove(st.ffB4);
}
if (st.ffM1 && ((st.dinput && st.ffM2) || st.ffD)) // mul in reg
{
mode[11] = '1';
pm.autoremove(st.ffM1);
pm.autoremove(st.ffD);
if (st.dinput)
pm.autoremove(st.ffM2);
}
if (st.ffY1 && st.ffY2 && st.dinput) // mul out reg
{
mode[12] = '1';
pm.autoremove(st.ffY1);
pm.autoremove(st.ffY2);
} else if (st.ffY1 || st.ffY2) // out reg
{
mode[3] = mode[4] = '1';
pm.autoremove(st.ffY1);
pm.autoremove(st.ffY2);
}
}
if (st.ffA1 && st.ffB1 && st.ffA2 && st.ffB2 && st.dinput &&
st.ffD) // in reg low
{
mode[2] = '1';
pm.autoremove(st.ffA1);
pm.autoremove(st.ffB1);
pm.autoremove(st.ffA2);
pm.autoremove(st.ffB2);
pm.autoremove(st.ffD);
}
if (st.ffA1 && st.ffB1 && st.ffA4 && st.ffB4 && !st.dinput) // in reg low
{
mode[2] = '1';
pm.autoremove(st.ffA1);
pm.autoremove(st.ffB1);
pm.autoremove(st.ffA4);
pm.autoremove(st.ffB4);
}
if (st.level == 3) {
if (st.ffA3 && st.ffB3 && st.dinput) // in reg high
{
mode[1] = '1';
pm.autoremove(st.ffA3);
pm.autoremove(st.ffB3);
}
if (st.ffA2 && st.ffB2 && !st.dinput) // in reg high
{
mode[1] = '1';
pm.autoremove(st.ffA2);
pm.autoremove(st.ffB2);
}
if (st.ffM1 && st.ffM2 && ((st.dinput && st.ffM3) || st.ffD)) // mul in reg
{
mode[11] = '1';
pm.autoremove(st.ffM1);
pm.autoremove(st.ffD);
pm.autoremove(st.ffM2);
if (st.dinput)
pm.autoremove(st.ffM3);
}
if (st.ffY1 && st.ffY2 && !(st.dinput && !st.ffY3)) // mul out reg
{
mode[12] = '1';
pm.autoremove(st.ffY1);
pm.autoremove(st.ffY2);
if (st.dinput)
pm.autoremove(st.ffY3);
} else if (st.ffY2 || st.ffY3) // out reg
{
mode[3] = mode[4] = '1';
pm.autoremove(st.ffY2);
pm.autoremove(st.ffY3);
}
}
if (st.level == 4) {
if (st.ffA3 && st.ffB3 && st.ffA4 && st.ffB4 && st.dinput) // in reg high
{
mode[1] = '1';
pm.autoremove(st.ffA3);
pm.autoremove(st.ffB3);
pm.autoremove(st.ffA4);
pm.autoremove(st.ffB4);
}
if (st.ffA2 && st.ffB2 && st.ffA3 && st.ffB3 && !st.dinput) // in reg high
{
mode[1] = '1';
pm.autoremove(st.ffA2);
pm.autoremove(st.ffB2);
pm.autoremove(st.ffA3);
pm.autoremove(st.ffB3);
}
if (st.ffM1 && st.ffM2 && st.ffM3 &&
((st.dinput && st.ffM4) || st.ffD)) // mul in reg
{
mode[11] = '1';
pm.autoremove(st.ffM1);
pm.autoremove(st.ffD);
pm.autoremove(st.ffM2);
pm.autoremove(st.ffM3);
if (st.dinput)
pm.autoremove(st.ffM4);
}
if (st.ffY1 && st.ffY2 && st.ffY3 &&
!(st.dinput && !st.ffY4)) // mul out reg
{
mode[12] = '1';
pm.autoremove(st.ffY1);
pm.autoremove(st.ffY2);
pm.autoremove(st.ffY3);
if (st.dinput)
pm.autoremove(st.ffY4);
} else if (st.ffY3 || st.ffY4) // out reg high
{
if (st.multiout2 || st.multiout3)
mode[3] = '1';
else
mode[3] = mode[4] = '1';
pm.autoremove(st.ffY3);
pm.autoremove(st.ffY4);
}
if (st.ffMY) // out reg low
{
mode[4] = '1';
pm.autoremove(st.ffMY);
}
}
cell->setPort(RTLIL::escape_id("a_i"), sigA);
cell->setPort(RTLIL::escape_id("b_i"), sigB);
cell->setPort(RTLIL::escape_id("d_i"), sigD);
cell->setPort(RTLIL::escape_id("out_o"), sigY);
cell->setPort(RTLIL::escape_id("mode_i"), Const::from_string(mode));
if (st.clock != SigBit())
cell->setPort(RTLIL::escape_id("clk_i"), st.clock);
cell->setPort(RTLIL::escape_id("rst_acc"), RTLIL::SigSpec(0, 1));
cell->setPort(RTLIL::escape_id("accsel"), RTLIL::SigSpec(0, 1));
cell->setPort(RTLIL::escape_id("cas_g"), RTLIL::SigSpec(0, 1));
pm.autoremove(st.mul1);
pm.autoremove(st.mul2);
pm.autoremove(st.mul3);
pm.autoremove(st.mul4);
pm.autoremove(st.postAdd1);
pm.autoremove(st.postAdd2);
pm.autoremove(st.postAdd3);
pm.autoremove(st.postAdd4);
}
struct RfNewDSP : public Pass {
bool show_help;
RfNewDSP()
: Pass("rf_new_dsp",
"Extract multiply-add operators and map to new_dsps") {}
void help() override {
log("\n");
log(" rf_new_dsp [options] [selection]\n");
log("\n");
log(" Extract multiply-add operators and map to new_dsps\n");
log("\n");
log(" -help: show help desk\n");
log("\n");
log(" -n_size: specify input n size\n");
log("\n");
log(" -m_size: specify input m size\n");
log("\n");
}
void clear_flags() override { show_help = false; }
void execute(std::vector<std::string> a_Args,
RTLIL::Design *a_Design) override {
log_header(a_Design, "Executing RF_NEW_DSP pass.\n");
size_t argidx;
for (argidx = 1; argidx < a_Args.size(); argidx++) {
if (a_Args[argidx] == "-help") {
show_help = true;
continue;
}
break;
}
extra_args(a_Args, argidx, a_Design);
if (show_help) {
help();
return;
}
for (auto module : a_Design->selected_modules()) {
rf_new_dsp_pm pm(module, module->selected_cells());
pm.run_rf_new_dsp(rf_new_dsp);
}
}
} RfNewDsp;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,478 @@
pattern rf_new_dsp
state <SigBit> clock
state <SigSpec> sigA1 sigA2 sigA3 sigA4
state <SigSpec> sigB1 sigB2 sigB3 sigB4
state <SigSpec> sigD sigY sigY2 sigM
state <Cell*> ffA1 ffA2 ffA3 ffA4
state <Cell*> ffB1 ffB2 ffB3 ffB4
state <Cell*> ffD
state <Cell*> ffM1 ffM2 ffM3 ffM4
state <Cell*> ffY1 ffY2 ffY3 ffY4 ffMY
state <Cell*> postAdd1 postAdd2 postAdd3 postAdd4
state <Cell*> mul2 mul3 mul4
state <bool> multiout2 multiout3 dinput y_signed y2_signed
state <int> level
// Variables used for subpatterns
state <SigSpec> argQ argD argA argM
udata <SigSpec> dffD dffQ addD addY
udata <SigBit> dffclock
udata <Cell*> dff postadder multiplier
state <IdString> postAddAB
// (1) match multiplier 1
match mul1
select mul1->type.in($mul)
endmatch
code sigA1 sigB1 sigY multiout2 multiout3 dinput y_signed y2_signed level
sigA1 = port(mul1, \A);
sigB1 = port(mul1, \B);
sigY = port(mul1, \Y);
multiout2 = false;
multiout3 = false;
y_signed = param(mul1, \A_SIGNED).as_bool();
dinput = false;
level = 1;
endcode
// (2) Match A input register 1
code argQ ffA1 sigA1 clock
argQ = sigA1;
subpattern(in_dffe);
if (dff) {
ffA1 = dff;
clock = dffclock;
sigA1 = dffD;
}
endcode
// (3) Match B input register 1
code argQ ffB1 sigB1 clock
argQ = sigB1;
subpattern(in_dffe);
if (dff) {
ffB1 = dff;
clock = dffclock;
sigB1 = dffD;
}
endcode
// (4) Match mul output register 1
code argD ffM1 sigY clock
argD = sigY;
subpattern(out_dffe);
if (dff) {
ffM1 = dff;
clock = dffclock;
sigY = dffQ;
}
endcode
// (5) Match post adder 1
code argA postAdd1 sigY sigD y_signed
argA = sigY;
subpattern(post_add);
if(postadder) {
postAdd1 = postadder;
sigY = addY;
sigD = addD;
y_signed = param(postAdd1, \A_SIGNED).as_bool();
}
endcode
// (6) Match D input register
code argQ ffD sigD clock
argQ = sigD;
subpattern(in_dffe);
if (dff) {
ffD = dff;
clock = dffclock;
sigD = dffD;
}
endcode
// (6-1) Match multiplier 4
code argM mul4 sigA4 sigB4 sigD level dinput
argM = sigD;
subpattern(more_mult);
if (multiplier) {
mul4 = multiplier;
sigA4 = port(mul4, \A);
sigB4 = port(mul4, \B);
level += 1;
}
else if(postAdd1)
{
dinput = true;
}
endcode
// (7) Match mac output register 1
code argD ffY1 sigY clock
argD = sigY;
subpattern(out_dffe);
if (dff) {
ffY1 = dff;
clock = dffclock;
sigY = dffQ;
}
endcode
// (8) Match post adder 2
code argA postAdd2 sigM sigY sigD y_signed
argA = sigY;
subpattern(post_add);
if(postadder) {
postAdd2 = postadder;
sigY = addY;
sigM = addD;
y_signed = param(postAdd2, \A_SIGNED).as_bool();
}
endcode
// (9) Match mul output register 2
code argQ ffM2 sigM clock
argQ = sigM;
subpattern(in_dffe);
if (dff) {
ffM2 = dff;
clock = dffclock;
sigM = dffD;
}
endcode
// (10) match multiplier 2
code argM mul2 sigA2 sigB2 sigM level
argM = sigM;
subpattern(more_mult);
if (multiplier) {
mul2 = multiplier;
sigA2 = port(mul2, \A);
sigB2 = port(mul2, \B);
level += 1;
}
sigM.remove(0, GetSize(sigM));
endcode
// (11) Match A input register 2
code argQ ffA2 sigA2 clock
argQ = sigA2;
subpattern(in_dffe);
if (dff) {
ffA2 = dff;
clock = dffclock;
sigA2 = dffD;
}
endcode
// (12) Match B input register 2
code argQ ffB2 sigB2 clock
argQ = sigB2;
subpattern(in_dffe);
if (dff) {
ffB2 = dff;
clock = dffclock;
sigB2 = dffD;
}
endcode
// (13) Match mac output register 2
code argD ffY2 sigY clock
argD = sigY;
subpattern(out_dffe);
if (dff) {
ffY2 = dff;
clock = dffclock;
sigY = dffQ;
}
endcode
// (14) Match post adder 3
code argA postAdd3 sigM sigY sigD y_signed
argA = sigY;
subpattern(post_add);
if(postadder) {
postAdd3 = postadder;
sigY = addY;
sigM = addD;
y_signed = param(postAdd3, \A_SIGNED).as_bool();
}
endcode
// (15) Match mul output register 3
code argQ ffM3 sigM clock
argQ = sigM;
subpattern(in_dffe);
if (dff) {
ffM3 = dff;
clock = dffclock;
sigM = dffD;
}
endcode
// (16) match multiplier 3
code argM mul3 sigA3 sigB3 level sigM
argM = sigM;
subpattern(more_mult);
if (multiplier) {
mul3 = multiplier;
sigA3 = port(mul3, \A);
sigB3 = port(mul3, \B);
level += 1;
}
sigM.remove(0, GetSize(sigM));
endcode
// (17) Match A input register 3
code argQ ffA3 sigA3 clock
argQ = sigA3;
subpattern(in_dffe);
if (dff) {
ffA3 = dff;
clock = dffclock;
sigA3 = dffD;
}
endcode
// (18) Match B input register 3
code argQ ffB3 sigB3 clock
argQ = sigB3;
subpattern(in_dffe);
if (dff) {
ffB3 = dff;
clock = dffclock;
sigB3 = dffD;
}
endcode
// (19) Match mac output register 3
code argD ffY3 sigY clock
argD = sigY;
subpattern(out_dffe);
if (dff) {
ffY3 = dff;
clock = dffclock;
sigY = dffQ;
}
endcode
// (20) Match post adder 4
code argA postAdd4 sigM sigY sigD y_signed
argA = sigY;
subpattern(post_add);
if(postadder) {
postAdd4 = postadder;
sigY = addY;
sigM = addD;
y_signed = param(postAdd4, \A_SIGNED).as_bool();
}
endcode
// (21) Match mul output register 4
code argQ ffM4 sigM clock
argQ = sigM;
subpattern(in_dffe);
if (dff) {
ffM4 = dff;
clock = dffclock;
sigM = dffD;
}
endcode
// (22) match multiplier 4
code argM mul4 sigA4 sigB4 level sigM
argM = sigM;
if(!mul4)
{
subpattern(more_mult);
if (multiplier) {
mul4 = multiplier;
sigA4 = port(mul4, \A);
sigB4 = port(mul4, \B);
level += 1;
}
}
sigM.remove(0, GetSize(sigM));
endcode
// (23) Match A input register 4
code argQ ffA4 sigA4 clock
argQ = sigA4;
subpattern(in_dffe);
if (dff) {
ffA4 = dff;
clock = dffclock;
sigA4 = dffD;
}
endcode
// (24) Match B input register 4
code argQ ffB4 sigB4 clock
argQ = sigB4;
subpattern(in_dffe);
if (dff) {
ffB4 = dff;
clock = dffclock;
sigB4 = dffD;
}
endcode
// (25) Match mac output register 4
code argD ffY4 sigY clock
argD = sigY;
subpattern(out_dffe);
if (dff) {
ffY4 = dff;
clock = dffclock;
sigY = dffQ;
}
endcode
code
accept;
endcode
// #######################
// Subpattern for matching against input registers
subpattern in_dffe
arg argQ clock
code
dff = nullptr;
if (argQ.empty())
reject;
for (const auto &c : argQ.chunks())
{
// Abandon matches when 'Q' is a constant
if (!c.wire)
reject;
// Abandon matches when 'Q' has the keep attribute set
if (c.wire->get_bool_attribute(\keep))
reject;
}
endcode
match ff
select ff->type.in($dff, $dffe, $sdff, $sdffe, $adff, $adffe)
slice offset GetSize(port(ff, \D))
index <SigBit> port(ff, \Q)[offset] === argQ[0]
// Check that the rest of argQ is present
filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
filter clock == SigBit() || port(ff, \CLK) == clock
endmatch
code argQ
SigSpec Q = port(ff, \Q);
dff = ff;
dffclock = port(ff, \CLK);
dffD = argQ;
SigSpec D = port(ff, \D);
argQ = Q;
dffD.replace(argQ, D); //to.replace(pattern, with). 'to' become 'with' according 'pattern'
endcode
// #######################
// Subpattern for matching output registers
subpattern out_dffe
arg argD argQ clock
code
dff = nullptr;
for (auto c : argD.chunks())
// Abandon matches when 'D' has the keep attribute set
if (c.wire->get_bool_attribute(\keep))
reject;
endcode
match ff
select ff->type.in($dff, $dffe, $sdff, $sdffe)
slice offset GetSize(port(ff, \D))
index <SigBit> port(ff, \D)[offset] === argD[0]
// Check that the rest of argD is present
filter GetSize(port(ff, \D)) >= offset + GetSize(argD)
filter port(ff, \D).extract(offset, GetSize(argD)) == argD
filter clock == SigBit() || port(ff, \CLK) == clock
endmatch
code argQ
SigSpec D = port(ff, \D);
SigSpec Q = port(ff, \Q);
argQ = argD;
argQ.replace(D, Q);
dff = ff;
dffQ = argQ;
dffclock = port(ff, \CLK);
endcode
// #######################
// Subpattern for matching post adder
subpattern post_add
arg argA
code
postadder = nullptr;
endcode
match adder
select adder->type.in($add)
choice <IdString> AB {\A, \B}
select nusers(port(adder, AB)) == 2
index <SigBit> port(adder, AB)[0] === argA[0]
filter GetSize(port(adder, AB)) >= GetSize(argA)
filter port(adder, AB).extract(0, GetSize(argA)) == argA
set postAddAB AB
endmatch
code argA
SigSpec A = port(adder, postAddAB);
SigSpec D = port(adder, postAddAB == \A ? \B : \A);
SigSpec Y = port(adder, \Y);
postadder = adder;
addY = argA;
addY.replace(A, Y);
addD = D;
endcode
// #######################
// Subpattern for matching multiplier
subpattern more_mult
arg argM
code
multiplier = nullptr;
endcode
match mult
select mult->type.in($mul)
filter GetSize(port(mult, \Y)) <= GetSize(argM)
filter port(mult, \Y) == argM.extract(0, GetSize(port(mult, \Y)))
endmatch
code
multiplier = mult;
endcode

View file

@ -0,0 +1,526 @@
/*
* Copyright 2020-2024 RapidFlex
*/
#include "kernel/celltypes.h"
#include "kernel/log.h"
#include "kernel/register.h"
#include "kernel/rtlil.h"
using namespace std;
/* Constants for device name */
constexpr const char *ALKDC_DNAME = "alkaidC";
constexpr const char *ALKDL_DNAME = "alkaidL";
constexpr const char *ALKDT_DNAME = "alkaidT";
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
#define XSTR(val) #val
#define STR(val) XSTR(val)
#ifndef PASS_NAME
#define PASS_NAME synth_rf_alkaid
#endif
struct SynthRapidFlexPass : public ScriptPass {
SynthRapidFlexPass()
: ScriptPass(STR(PASS_NAME), "Synthesis for RapidFlex Alkaid FPGAs") {}
void help() override {
log("\n");
log(" %s [options]\n", STR(PASS_NAME));
log("This command runs synthesis for RapidFlex Alkaid FPGAs\n");
log("\n");
log(" -top <module>\n");
log(" use the specified module as top module\n");
log("\n");
log(" -family <family>\n");
log(" run synthesis for the specified RapidFlex architecture\n");
log(" generate the synthesis netlist for the specified family.\n");
log(" supported values:\n");
log(" - alkaidL\n");
log(" - alkaidT\n");
log(" - alkaidC\n");
log("\n");
log(" -edif <file>\n");
log(" write the design to the specified edif file. Writing of an "
"output file\n");
log(" is omitted if this parameter is not specified.\n");
log("\n");
log(" -blif <file>\n");
log(" write the design to the specified BLIF file. Writing of an "
"output file\n");
log(" is omitted if this parameter is not specified.\n");
log("\n");
log(" -verilog <file>\n");
log(" write the design to the specified verilog file. Writing of an "
"output\n");
log(" file is omitted if this parameter is not specified.\n");
log("\n");
log(" -no_dsp\n");
log(" By default use DSP blocks in output netlist.\n");
log(" do not use DSP blocks to implement multipliers and associated "
"logic\n");
log("\n");
log(" -no_adder\n");
log(" By default use adder cells in output netlist.\n");
log(" Specifying this switch turns it off.\n");
log("\n");
log(" -no_bram\n");
log(" By default use Block RAM in output netlist.\n");
log(" Specifying this switch turns it off.\n");
log("\n");
log(" -insert_clock_buffer\n");
log(" By default no insertion of clock buffer for output "
"netlist.\n");
log(" -cell_map_file <file>\n");
log(" write the ckbuf into to the specified XML file. Writing of an "
"output file\n");
log(" Specifying this switch turns it on.\n");
log(" -K <int>\n");
log(" Specify the input size of LUT when running optimization. If "
"not specified, a default value will be applied. Please do not modify "
"this parameter except architecture exploration\n");
log(" -parse_only\n");
log(" Only apply verilog parsing. This is for rewriting purpose.\n");
log(" Specifying this switch turns it on.\n");
log("\n");
log("The following commands are executed by this synthesis command:\n");
log("\n");
log(" -save_block_diagram <file>\n");
log(" generate a block diagram and save to the specified file\n");
log(" supported formats: dot, png, svg, eps, pdf\n");
log("\n");
help_script();
}
std::string top_opt, edif_file, blif_file, family, currmodule, verilog_file,
cell_map_file, lib_path, block_diagram_file;
bool nodsp;
bool no_opt;
bool abc9;
bool inferAdder;
bool inferBram;
bool show_help;
bool insert_clock_buffer;
size_t DEFAULT_K = 5;
size_t MIN_K = 4;
size_t MAX_K = 6;
size_t max_lut_size = DEFAULT_K;
bool parse_only = false;
void clear_flags() override {
top_opt = "-auto-top";
edif_file = "";
blif_file = "";
cell_map_file = "";
verilog_file = "";
currmodule = "";
family = ALKDL_DNAME;
inferAdder = true;
inferBram = true;
nodsp = false;
no_opt = false;
abc9 = false;
lib_path = "+/rapidflex/";
show_help = false;
insert_clock_buffer = false;
max_lut_size = DEFAULT_K;
parse_only = false;
block_diagram_file = "";
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override {
string run_from, run_to;
clear_flags();
lib_path = design->scratchpad_get_string("rf.lib_path", lib_path);
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
if (args[argidx] == "-run" && argidx + 1 < args.size()) {
size_t pos = args[argidx + 1].find(':');
if (pos == std::string::npos) {
run_from = args[++argidx];
run_to = args[argidx];
} else {
run_from = args[++argidx].substr(0, pos);
run_to = args[argidx].substr(pos + 1);
}
continue;
}
if (args[argidx] == "-top" && argidx + 1 < args.size()) {
top_opt = "-top " + args[++argidx];
continue;
}
if (args[argidx] == "-edif" && argidx + 1 < args.size()) {
edif_file = args[++argidx];
continue;
}
if (args[argidx] == "-family" && argidx + 1 < args.size()) {
family = args[++argidx];
continue;
}
if (args[argidx] == "-blif" && argidx + 1 < args.size()) {
blif_file = args[++argidx];
continue;
}
if (args[argidx] == "-cell_map_file" && argidx + 1 < args.size()) {
cell_map_file = args[++argidx];
continue;
}
if (args[argidx] == "-verilog" && argidx + 1 < args.size()) {
verilog_file = args[++argidx];
continue;
}
if (args[argidx] == "-K" && argidx + 1 < args.size()) {
max_lut_size = std::stoi(args[++argidx]);
continue;
}
if (args[argidx] == "-no_dsp") {
nodsp = true;
continue;
}
if (args[argidx] == "-no_adder") {
inferAdder = false;
continue;
}
if (args[argidx] == "-no_bram") {
inferBram = false;
continue;
}
if (args[argidx] == "-no_opt") {
no_opt = false;
continue;
}
if (args[argidx] == "-parse_only") {
parse_only = true;
continue;
}
if (args[argidx] == "-help") {
show_help = true;
continue;
}
if (args[argidx] == "-insert_clock_buffer") {
insert_clock_buffer = true;
continue;
}
if (args[argidx] == "-save_block_diagram") {
if (argidx + 1 < args.size() && args[argidx + 1][0] != '-') {
block_diagram_file = args[++argidx];
} else {
block_diagram_file = "";
}
continue;
}
break;
}
extra_args(args, argidx, design);
if (show_help) {
help();
return;
}
if (!design->full_selection()) {
log_cmd_error("This command only operates on fully selected designs!\n");
}
/* Pre-check on family name and confirm on selection*/
if (family != ALKDL_DNAME && family != ALKDT_DNAME &&
family != ALKDC_DNAME) {
log_cmd_error("Invalid family specified: '%s'\n", family.c_str());
}
log("Selected device family: %s\n", family.c_str());
/* Force to enable/disable options upon device limits */
if (family == ALKDL_DNAME || family == ALKDC_DNAME) {
if (!nodsp) {
log_warning("Force to disable dsp inference as the selected device "
"does not contain dedicated resources\n");
nodsp = true;
}
if (inferBram) {
log_warning("Force to disable RAM inference as the selected device "
"does not contain dedicated resources\n");
inferBram = false;
}
}
/* By default, no opt should be enabled. Throw a warning if not */
if (no_opt) {
log_warning("Force to disable any optimization, which may cast an "
"negative impact on QoR\n");
}
if (abc9 && design->scratchpad_get_int("abc9.D", 0) == 0) {
log_warning("Delay target has not been set via SDC or scratchpad; "
"Assuming 1GHz clock.\n");
design->scratchpad_set_int("abc9.W",
1000); // set interconnet delay as 1ns
}
/* Sanity checks on max lut size */
if (max_lut_size < MIN_K || max_lut_size > MAX_K) {
log_cmd_error(
"The provided K=%ld is out of the acceptable range [%ld, %ld]!\n",
max_lut_size, MIN_K, MAX_K);
return;
}
log_header(design, "Executing SYNTH_RAPIDFLEX pass.\n");
log_push();
run_script(design, run_from, run_to);
log_pop();
}
void script() override {
if (help_mode) {
family = "<family>";
}
std::string noDFFArgs;
if (check_label("begin")) {
std::string family_path = " " + lib_path + family;
std::string read_vlog_args;
// Read simulation library
read_vlog_args = family_path + "/cell_sim.v";
// Use -nomem2reg here to prevent Yosys from complaining about
// some block ram cell models. After all the only part of the cells
// library required here is cell port definitions plus specify blocks.
if (parse_only) {
run("read_verilog " + lib_path + "common/cells_sim.v" + read_vlog_args);
} else {
run("read_verilog -lib -specify -nomem2reg " + lib_path +
"common/cells_sim.v" + read_vlog_args);
}
run("logger -werror \"multiple conflicting drivers\"");
run("check");
run(stringf("hierarchy -check %s",
help_mode ? "-top <top>" : top_opt.c_str()));
run("stat");
}
if (check_label("prepare")) {
run("proc");
run("flatten");
if (parse_only) {
log("Running parse-only flow. Exit after flattening the design\n");
return;
}
if (help_mode) {
run("tribuf -logic");
}
if (!no_opt) {
run("opt_expr");
run("opt_clean");
}
run("deminout");
if (!no_opt) {
run("opt -nodffe");
}
run("check");
if (!no_opt) {
run("opt -nodffe");
run("fsm");
run("opt -nodffe");
run("wreduce -keepdc");
run("peepopt");
run("pmuxtree");
run("opt_clean");
run("share");
}
}
if (check_label("map_dsp"), "(skip if -no_dsp)") {
struct DspParams {
size_t a_maxwidth;
size_t b_maxwidth;
size_t a_minwidth;
size_t b_minwidth;
std::string type;
};
const std::vector<DspParams> dsp_rules = {
{24, 20, 13, 11, "mult_24x20_map"},
{12, 10, 2, 2, "mult_12x10_map"},
};
if (help_mode || family == ALKDT_DNAME) {
if (help_mode || !nodsp) {
run("memory_dff", " (for alkaidT)");
if (!no_opt) {
run("wreduce t:$mul", " (for alkaidT)");
}
run("rf_new_dsp");
for (const auto &rule : dsp_rules) {
run(stringf("techmap -map +/mul2dsp.v "
"-map %s/dsp_map.v "
"-D DSP_A_MAXWIDTH=%zu -D DSP_B_MAXWIDTH=%zu "
"-D DSP_A_MINWIDTH=%zu -D DSP_B_MINWIDTH=%zu "
"-D DSP_NAME=%s",
std::string(lib_path + family).c_str(), rule.a_maxwidth,
rule.b_maxwidth, rule.a_minwidth, rule.b_minwidth,
rule.type.c_str()));
/* Without the following command, some multiplier may be skipped */
run("chtype -set $mul t:$__soft_mul", " (for alkaidT)");
}
run("select a:mul2dsp", " (for alkaidT)");
run("setattr -unset mul2dsp", " (for alkaidT)");
if (!no_opt) {
run("opt_expr -fine", " (for alkaidT)");
run("wreduce", " (for alkaidT)");
}
run("select -clear", " (for alkaidT)");
// Comment out for further development
// run("rf_dsp", " (for
// alkaidT)");
run("chtype -set $mul t:$__soft_mul", " (for alkaidT)");
}
}
}
if (check_label("coarse")) {
run("techmap -map +/cmp2lut.v -D LUT_WIDTH=5");
if (!no_opt) {
run("opt_expr");
run("opt_clean");
}
run("alumacc");
run("pmuxtree");
if (!no_opt) {
run("opt -nodffe");
}
run("memory -nomap");
if (!no_opt) {
run("opt_clean");
}
}
if (check_label("map_bram", "(skip if -no_bram)") &&
((help_mode || family == ALKDT_DNAME) && inferBram)) {
if (help_mode || family == ALKDT_DNAME) {
run("memory_bram -rules " + lib_path + family + "/bram.txt");
}
/* TODO: Add bram initilization support */
run("techmap -map " + lib_path + family + "/bram_map.v");
}
if (check_label("map_ffram")) {
if (!no_opt) {
run("opt -fast -mux_undef -undriven -fine -nodffe");
}
run("memory_map");
if (!no_opt) {
run("opt -undriven -fine -nodffe");
}
}
if (check_label("map_gates")) {
if (help_mode ||
(inferAdder && (family == ALKDL_DNAME || family == ALKDT_DNAME ||
family == ALKDC_DNAME))) {
run("techmap -map +/techmap.v -map " + lib_path + family +
"/arith_map.v",
"(unless -no_adder)");
} else {
run("techmap");
}
if (!no_opt) {
run("opt -fast -nodffe");
run("opt_expr");
run("opt_merge");
run("opt_clean");
run("opt -nodffe");
}
}
if (check_label("map_ffs")) {
run("memory");
/* TODO: Support shift-register mapping */
/* Run 2 times dff mapping incase anything missing */
run("dfflegalize -cell $_DFF_?_ 01 -cell $_DFF_???_ 01 -cell $_SDFF_???_ "
"01");
run("techmap -map " + lib_path + family + "/dff_map.v");
run("dfflegalize -cell $_DFF_?_ 01 -cell $_DFF_???_ 01 -cell $_SDFF_???_ "
"01");
run("techmap -map " + lib_path + family + "/dff_map.v");
run("opt_expr -mux_undef");
run("simplemap");
run("opt_expr");
if (!no_opt) {
run("opt_merge");
run("opt_dff -nodffe");
run("opt_clean");
run("opt -nodffe");
}
}
if (check_label("map_luts")) {
run("abc -lut " + std::to_string(max_lut_size));
/* Map dff and adder again since ABC may generate new gates */
run("techmap -map " + lib_path + family + "/dff_map.v");
run("techmap -map " + lib_path + family + "/arith_map.v");
}
if (check_label("check")) {
run("autoname");
run("hierarchy -check");
run("stat");
run("check -noinit");
}
if (check_label("finalize")) {
if (!no_opt) {
run("opt_clean -purge");
}
run("check");
}
if (check_label("insert_clock_buffer", "(if -insert_clock_buffer)")) {
if (insert_clock_buffer) {
run(stringf("insert_clock_buffer -top %s -cell_map_file %s",
top_opt.c_str(), cell_map_file.c_str()));
}
}
if (check_label("blif", "(if -blif)")) {
if (help_mode || !blif_file.empty()) {
run(stringf("write_blif -param %s ",
help_mode ? "<file-name>" : blif_file.c_str()));
}
}
if (check_label("verilog", "(if -verilog)")) {
if (help_mode || !verilog_file.empty()) {
run("write_verilog -noattr -nohex " +
(help_mode ? "<file-name>" : verilog_file));
}
}
if (check_label("save_block_diagram", "(if -save_block_diagram)")) {
if (!block_diagram_file.empty()) {
size_t dot_pos = block_diagram_file.find_last_of('.');
std::string ext, prefix;
if (dot_pos != std::string::npos &&
dot_pos + 1 < block_diagram_file.length()) {
ext = block_diagram_file.substr(dot_pos + 1);
prefix = block_diagram_file.substr(0, dot_pos);
} else {
ext = "dot";
prefix = block_diagram_file;
}
if (ext != "dot" && ext != "png" && ext != "svg" && ext != "eps" &&
ext != "pdf") {
log_cmd_error("Unsupported block diagram file format: %s. Supported "
"formats: dot, png, svg, eps, pdf\n",
ext.c_str());
return;
}
run(stringf("show -format %s -prefix %s", ext.c_str(), prefix.c_str()));
} else {
run("show -format dot");
}
}
}
} SynthRapidFlexPass;
PRIVATE_NAMESPACE_END