Initial pass scaffold plus rule parser Signed-off-by: Maciej Kurc <mkurc@antmicro.com>
diff --git a/Makefile b/Makefile index 801c6a2..9d36450 100644 --- a/Makefile +++ b/Makefile
@@ -6,7 +6,7 @@ # # SPDX-License-Identifier:ISC -PLUGIN_LIST := fasm xdc params sdc ql-iob design_introspection integrateinv ql-qlf uhdm +PLUGIN_LIST := fasm xdc params sdc ql-iob design_introspection integrateinv ql-qlf uhdm dsp_ff PLUGINS := $(foreach plugin,$(PLUGIN_LIST),$(plugin).so) PLUGINS_INSTALL := $(foreach plugin,$(PLUGIN_LIST),install_$(plugin)) PLUGINS_CLEAN := $(foreach plugin,$(PLUGIN_LIST),clean_$(plugin))
diff --git a/dsp_ff-plugin/Makefile b/dsp_ff-plugin/Makefile new file mode 100644 index 0000000..8f90d46 --- /dev/null +++ b/dsp_ff-plugin/Makefile
@@ -0,0 +1,12 @@ +# Copyright (C) 2020-2021 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 + +NAME = dsp-ff +SOURCES = dsp_ff.cc + +include ../Makefile_plugin.common
diff --git a/dsp_ff-plugin/dsp_ff.cc b/dsp_ff-plugin/dsp_ff.cc new file mode 100644 index 0000000..738e428 --- /dev/null +++ b/dsp_ff-plugin/dsp_ff.cc
@@ -0,0 +1,601 @@ +#include "kernel/register.h" +#include "kernel/rtlil.h" +#include "kernel/sigtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +// ============================================================================ + +struct DspFF : public Pass { + + /// A structure identifying specific pin in a cell instance + struct CellPin { + RTLIL::Cell* cell; /// Cell pointer (nullptr for top-level ports) + RTLIL::IdString port; /// Port name + int bit; /// Bit index + + CellPin (RTLIL::Cell* _cell, + const RTLIL::IdString& _port, + int _bit = 0) : + cell(_cell), + port(_port), + bit (_bit) + {} + + CellPin (const CellPin& ref) = default; + CellPin (CellPin&& ref) = default; + + unsigned int hash () const { + unsigned int h = 0; + if (cell != nullptr) { + h = mkhash_add(h, cell->hash()); + } + h = mkhash_add(h, port.hash()); + h = mkhash_add(h, bit); + return h; + } + + bool operator == (const CellPin& ref) const { + return (cell == ref.cell) && + (port == ref.port) && + (bit == ref.bit); + } + }; + + // .......................................... + + struct FlopType { + RTLIL::IdString name; + + struct { + RTLIL::IdString clk; + RTLIL::IdString rst; + RTLIL::IdString ena; + RTLIL::IdString d; + RTLIL::IdString q; + } ports; + + struct { + std::vector<RTLIL::IdString> matching; + dict<RTLIL::IdString, RTLIL::Const> required; + dict<RTLIL::IdString, RTLIL::Const> set; + dict<RTLIL::IdString, RTLIL::IdString> map; + } params; + }; + + struct DspPortType { + RTLIL::IdString name; + + struct { + RTLIL::IdString clk; + RTLIL::IdString rst; + RTLIL::IdString ena; + } assoc; + + struct { + dict<RTLIL::IdString, RTLIL::Const> set; + dict<RTLIL::IdString, RTLIL::IdString> map; + } params; + + dict<RTLIL::IdString, RTLIL::Const> connect; + }; + + struct DspType { + RTLIL::IdString name; + std::vector<DspPortType> ports; + }; + + // .......................................... + + void load_rules(const std::string& a_FileName) { + + // Parses a vector of strings like "<name>=<value>" starting from the + // second one on the list + auto parseNameValue = [&](const std::vector<std::string>& strs) { + const std::regex expr ("(\\S+)=(\\S+)"); + std::smatch match; + + std::vector<std::pair<std::string, std::string>> vec; + + for (size_t i=1; i<strs.size(); ++i) { + if (std::regex_match(strs[i], match, expr)) { + vec.push_back(std::make_pair(match[1], match[2])); + } + else { + log_error(" syntax error: '%s'\n", strs[i].c_str()); + } + } + + return vec; + }; + + std::ifstream file (a_FileName); + std::string line; + + log("Loading rules from '%s'...\n", a_FileName.c_str()); + if (!file) { + log_error(" Error opening file!\n"); + } + + std::vector<DspType> dspTypes; + std::vector<FlopType> flopTypes; + + std::vector<std::string> tok; + + // Parse the file + while (1) { + + // Get line + std::getline(file, line); + if (!file) { + break; + } + + // Strip comment if any, skip empty lines + size_t pos = line.find("#"); + if (pos != std::string::npos) { + line = line.substr(0, pos); + } + if (line.find_first_not_of(" \r\n\t") == std::string::npos) { + continue; + } + + // Split the line + const auto fields = get_fields(line); + log_assert(fields.size() >= 1); + + // DSP section + if (fields[0] == "dsp") { + if (fields.size() != 2) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (!tok.empty()) { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + tok.push_back(fields[0]); + + dspTypes.resize(dspTypes.size() + 1);\ + dspTypes.back().name = RTLIL::escape_id(fields[1]); + } + else if (fields[0] == "enddsp") { + if (fields.size() != 1) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (tok.size() != 1 || tok.back() != "dsp") { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + tok.pop_back(); + } + + // DSP port section + else if (fields[0] == "port") { + if (fields.size() != 2) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (tok.size() != 1 || tok.back() != "dsp") { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + tok.push_back(fields[0]); + + auto& ports = dspTypes.back().ports; + ports.resize(ports.size() + 1); + ports.back().name = RTLIL::escape_id(fields[1]); + } + else if (fields[0] == "endport") { + if (fields.size() != 1) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (tok.size() != 2 || tok.back() != "port") { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + tok.pop_back(); + } + + // Flip-flop type section + else if (fields[0] == "ff") { + if (fields.size() != 2) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (!tok.empty()) { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + tok.push_back(fields[0]); + + flopTypes.resize(flopTypes.size() + 1); + flopTypes.back().name = RTLIL::escape_id(fields[1]); + } + else if (fields[0] == "endff") { + if (fields.size() != 1) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (tok.size() != 1 || tok.back() != "ff") { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + tok.pop_back(); + } + + // Signals + else if (fields[0] == "clk") { + if (fields.size() != 2) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (tok.size() == 0 || (tok.back() != "port" && tok.back() != "ff")) { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + + // Associated clock + if (tok.back() == "port") { + auto& ports = dspTypes.back().ports; + ports.back().assoc.clk = RTLIL::escape_id(fields[1]); + } + else if (tok.back() == "ff") { + flopTypes.back().ports.clk = RTLIL::escape_id(fields[1]); + } + } + else if (fields[0] == "rst") { + if (fields.size() != 2) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (tok.size() == 0 || (tok.back() != "port" && tok.back() != "ff")) { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + + // Associated reset + if (tok.back() == "port") { + auto& ports = dspTypes.back().ports; + ports.back().assoc.rst = RTLIL::escape_id(fields[1]); + } + else if (tok.back() == "ff") { + flopTypes.back().ports.rst = RTLIL::escape_id(fields[1]); + } + } + else if (fields[0] == "ena") { + if (fields.size() != 2) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (tok.size() == 0 || (tok.back() != "port" && tok.back() != "ff")) { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + + // Associated enable + if (tok.back() == "port") { + auto& ports = dspTypes.back().ports; + ports.back().assoc.ena = RTLIL::escape_id(fields[1]); + } + else if (tok.back() == "ff") { + flopTypes.back().ports.ena = RTLIL::escape_id(fields[1]); + } + } + + else if (fields[0] == "d") { + if (fields.size() != 2) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (tok.size() == 0 || tok.back() != "ff") { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + + flopTypes.back().ports.d = RTLIL::escape_id(fields[1]); + } + else if (fields[0] == "q") { + if (fields.size() != 2) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (tok.size() == 0 || tok.back() != "ff") { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + + flopTypes.back().ports.q = RTLIL::escape_id(fields[1]); + } + + // Parameters to set + else if (fields[0] == "set") { + if (fields.size() < 2) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (tok.size() == 0 || (tok.back() != "port" && tok.back() != "ff")) { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + + const auto vec = parseNameValue(fields); + dict<RTLIL::IdString, RTLIL::Const> set; + for (const auto& it : vec) { + set.insert(std::make_pair( + RTLIL::escape_id(it.first), + RTLIL::Const(it.second) + )); + } + + if (tok.back() == "port") { + auto& ports = dspTypes.back().ports; + ports.back().params.set.swap(set); + } + else if (tok.back() == "ff") { + flopTypes.back().params.set.swap(set); + } + } + // Parameters to copy / map + else if (fields[0] == "map") { + if (fields.size() < 2) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (tok.size() == 0 || (tok.back() != "port" && tok.back() != "ff")) { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + + const auto vec = parseNameValue(fields); + dict<RTLIL::IdString, RTLIL::IdString> map; + for (const auto& it : vec) { + map.insert(std::make_pair( + RTLIL::escape_id(it.first), + RTLIL::escape_id(it.second) + )); + } + + if (tok.back() == "port") { + auto& ports = dspTypes.back().ports; + ports.back().params.map.swap(map); + } + else if (tok.back() == "ff") { + flopTypes.back().params.map.swap(map); + } + } + // Connections to make + else if (fields[0] == "con") { + if (fields.size() < 2) { + log_error(" syntax error: '%s'\n", line.c_str()); + } + if (tok.size() == 0 || tok.back() != "port") { + log_error(" unexpected keyword '%s'\n", fields[0].c_str()); + } + + const auto vec = parseNameValue(fields); + auto& ports = dspTypes.back().ports; + for (const auto& it : vec) { + ports.back().connect.insert(std::make_pair( + RTLIL::escape_id(it.first), + RTLIL::Const(it.second) + )); + } + } + + else { + log(" unexpected keyword '%s'\n", fields[0].c_str()); + } + } + + // Convert lists to maps + for (const auto& it : dspTypes) { + m_DspTypes.insert(std::make_pair(it.name, it)); + } + for (const auto& it : flopTypes) { + m_FlopTypes.insert(std::make_pair(it.name, it)); + } + } + + // TODO: make lambda + std::vector<std::string> get_fields(const std::string& a_String, + const char a_Delim = ' ', + bool a_KeepEmpty = false) + { + std::vector<std::string> fields; + std::stringstream ss(a_String); + + while (ss.good()) { + std::string field; + std::getline(ss, field, a_Delim); + if (!field.empty() || a_KeepEmpty) { + fields.push_back(field); + } + } + + return fields; + } + + void dump_rules() { + + // Dump DSP types + log("DSP types:\n"); + for (const auto& it : m_DspTypes) { + const auto& dsp = it.second; + log(" %s\n", dsp.name.c_str()); + + log(" ports:\n"); + for (const auto& port : dsp.ports) { + log(" %s.%s\n", dsp.name.c_str(), port.name.c_str()); + log(" clk: %s\n", !port.assoc.clk.empty() ? port.assoc.clk.c_str() : "<none>"); + log(" rst: %s\n", !port.assoc.rst.empty() ? port.assoc.rst.c_str() : "<none>"); + log(" ena: %s\n", !port.assoc.ena.empty() ? port.assoc.ena.c_str() : "<none>"); + + if (!port.params.set.empty()) { + log(" set params:\n"); + for (const auto& it : port.params.set) { + log(" %s=%s\n", it.first.c_str(), it.second.decode_string().c_str()); + } + } + if (!port.params.map.empty()) { + log(" map params:\n"); + for (const auto& it : port.params.map) { + log(" %s=%s\n", it.first.c_str(), it.second.c_str()); + } + } + if (!port.connect.empty()) { + log(" connect ports:\n"); + for (const auto& it : port.connect) { + log(" %s.%s=%s\n", dsp.name.c_str(), it.first.c_str(), it.second.as_string().c_str()); + } + } + } + } + + // Dump flop types + log("Flip-flop types:\n"); + for (const auto& it : m_FlopTypes) { + const auto& ff = it.second; + log(" %s\n", ff.name.c_str()); + log(" clk: %s\n", !ff.ports.clk.empty() ? ff.ports.clk.c_str() : "<none>"); + log(" rst: %s\n", !ff.ports.rst.empty() ? ff.ports.rst.c_str() : "<none>"); + log(" ena: %s\n", !ff.ports.ena.empty() ? ff.ports.ena.c_str() : "<none>"); + log(" d : %s\n", !ff.ports.d.empty() ? ff.ports.d.c_str() : "<none>"); + log(" q : %s\n", !ff.ports.q.empty() ? ff.ports.q.c_str() : "<none>"); + + if (!ff.params.set.empty()) { + log(" set params:\n"); + for (const auto& it : ff.params.set) { + log(" %s=%s\n", it.first.c_str(), it.second.decode_string().c_str()); + } + } + if (!ff.params.map.empty()) { + log(" map params:\n"); + for (const auto& it : ff.params.map) { + log(" %s=%s\n", it.first.c_str(), it.second.c_str()); + } + } + } + } + + // .......................................... + + /// Temporary SigBit to SigBit helper map. + SigMap m_SigMap; + /// Net map + dict<RTLIL::SigBit, RTLIL::SigBit> m_NetMap; + + /// DSP types + dict<RTLIL::IdString, DspType> m_DspTypes; + /// Flip-flop types + dict<RTLIL::IdString, FlopType> m_FlopTypes; + + // .......................................... + + DspFF() : + Pass("dsp_ff", "Integrates flip-flop into DSP blocks") + {} + + void help () override { + log("\n"); + log(" dsp_ff -rules <rules.txt> [selection]\n"); + log("\n"); + log("Integrates flip-flops with DSP blocks and enables their internal registers.\n"); + log("\n"); + } + + void execute(std::vector<std::string> a_Args, RTLIL::Design *a_Design) override + { + log_header(a_Design, "Executing DSP_FF pass.\n"); + + std::string rulesFile; + + // Parse args + size_t argidx; + for (argidx = 1; argidx < a_Args.size(); argidx++) { + if (a_Args[argidx] == "-rules" && (argidx + 1) < a_Args.size()) { + rulesFile = a_Args[++argidx]; + continue; + } + + break; + } + extra_args(a_Args, argidx, a_Design); + + // Check args + if (rulesFile.empty()) { + log_cmd_error("No rules file specified!"); + } + + // Load rules + load_rules(rulesFile); + if (log_force_debug) { + dump_rules(); + } + + // Process modules + for (auto module : a_Design->selected_modules()) { + + // Setup the SigMap + m_SigMap.clear(); + m_SigMap.set(module); + + } + + // Clear maps + m_SigMap.clear(); + } + +/* + pool<CellPin> getSinks (const CellPin& a_Driver) { + + auto module = a_Driver.cell->module; + pool<CellPin> sinks; + + // The driver has to be an output pin + if (!a_Driver.cell->output(a_Driver.port)) { + return sinks; + } + + // Get the driver sigbit + auto driverSigspec = a_Driver.cell->getPort(a_Driver.port); + auto driverSigbit = m_SigMap(driverSigspec.bits().at(a_Driver.bit)); + + // Look for connected sinks + for (auto cell : module->cells()) { + for (auto conn : cell->connections()) { + auto port = conn.first; + auto sigspec = conn.second; + + // Consider only sinks (inputs) + if (!cell->input(port)) { + continue; + } + + // Check all sigbits + auto sigbits = sigspec.bits(); + for (size_t bit = 0; bit < sigbits.size(); ++bit) { + + auto sigbit = sigbits[bit]; + if (!sigbit.wire) { + continue; + } + + // Got a sink pin of another cell + sigbit = m_SigMap(sigbit); + if (sigbit == driverSigbit) { + sinks.insert(CellPin(cell, port, bit)); + } + } + } + } + + // Look for connected top-level output ports + for (auto conn : module->connections()) { + auto dst = conn.first; + auto src = conn.second; + + auto sigbits = dst.bits(); + for (size_t bit = 0; bit < sigbits.size(); ++bit) { + + auto sigbit = sigbits[bit]; + if (!sigbit.wire) { + continue; + } + + if (!sigbit.wire->port_output) { + continue; + } + + sigbit = m_SigMap(sigbit); + if (sigbit == driverSigbit) { + sinks.insert(CellPin(nullptr, sigbit.wire->name, bit)); + } + } + } + + return sinks; + } +*/ + +} DspFF; + +PRIVATE_NAMESPACE_END +