Merge pull request #226 from antmicro/k6n10f_mult_inference
qlf_k6n10f DSP multiplier inference
diff --git a/Makefile b/Makefile
index 801c6a2..00e0263 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/Makefile_plugin.common b/Makefile_plugin.common
index 0870f21..49d6ab5 100644
--- a/Makefile_plugin.common
+++ b/Makefile_plugin.common
@@ -55,7 +55,7 @@
DATA_DIR ?= $(shell $(YOSYS_CONFIG) --datdir)
EXTRA_FLAGS ?=
-OBJS := $(SOURCES:cc=o)
+OBJS := $(patsubst %.cc,%.o,$(SOURCES))
all: $(NAME).so
diff --git a/Makefile_test.common b/Makefile_test.common
index e430839..16e016e 100644
--- a/Makefile_test.common
+++ b/Makefile_test.common
@@ -35,11 +35,11 @@
@set +e; \
$$($$(subst /,-,$(1)_verify)); \
if [ $$$$? -eq 0 ]; then \
- printf "Test %-18s \e[32mPASSED\e[0m @ %s\n" $(1) $(CURDIR); \
+ printf "Test %-20s \e[32mPASSED\e[0m @ %s\n" $(1) $(CURDIR); \
touch $$<; \
true; \
else \
- printf "Test %-18s \e[31;1mFAILED\e[0m @ %s\n" $(1) $(CURDIR); \
+ printf "Test %-20s \e[31;1mFAILED\e[0m @ %s\n" $(1) $(CURDIR); \
false; \
fi
@@ -54,15 +54,15 @@
rm -f run-$$(notdir $(1)).tcl; \
if [ ! -z "$$($(1)_negative)" ] && [ $$($(1)_negative) -eq 1 ]; then \
if [ $$$$RETVAL -ne 0 ]; then \
- printf "Negative test %-18s \e[32mPASSED\e[0m @ %s\n" $(1) $(CURDIR); \
+ printf "Negative test %-20s \e[32mPASSED\e[0m @ %s\n" $(1) $(CURDIR); \
true; \
else \
- printf "Negative test %-18s \e[31;1mFAILED\e[0m @ %s\n" $(1) $(CURDIR); \
+ printf "Negative test %-20s \e[31;1mFAILED\e[0m @ %s\n" $(1) $(CURDIR); \
false; \
fi \
else \
if [ $$$$RETVAL -ne 0 ]; then \
- echo "Unexpected runtime error"; \
+ printf "Test %-20s \e[31;1mFAILED\e[0m @ %s\n" $(1) $(CURDIR); \
false; \
fi \
fi
diff --git a/dsp-ff-plugin/Makefile b/dsp-ff-plugin/Makefile
new file mode 100644
index 0000000..3e59b3d
--- /dev/null
+++ b/dsp-ff-plugin/Makefile
@@ -0,0 +1,16 @@
+# 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
+
+install:
+ install -D nexus-dsp_rules.txt $(DATA_DIR)/nexus/dsp_rules.txt
+
diff --git a/dsp-ff-plugin/dsp_ff.cc b/dsp-ff-plugin/dsp_ff.cc
new file mode 100644
index 0000000..26e5acd
--- /dev/null
+++ b/dsp-ff-plugin/dsp_ff.cc
@@ -0,0 +1,1373 @@
+#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); }
+
+ std::string as_string() const
+ {
+ if (cell != nullptr) {
+ return stringf("%s.%s[%d]", RTLIL::unescape_id(cell->name).c_str(), RTLIL::unescape_id(port).c_str(), bit);
+ } else {
+ return stringf("%s[%d]", RTLIL::unescape_id(port).c_str(), bit);
+ }
+ }
+ };
+
+ // ..........................................
+
+ /// Connection map
+ struct ConnMap {
+
+ /// Maps source SigBit to all sinks it drives CellPin.
+ dict<RTLIL::SigBit, std::vector<CellPin>> sinks;
+ /// Maps source SigBit to its driver CellPin
+ dict<RTLIL::SigBit, CellPin> drivers;
+
+ /// Builds the map
+ void build(RTLIL::Module *module, const SigMap &sigmap)
+ {
+ clear();
+
+ // Scan cell ports
+ for (auto *cell : module->cells()) {
+ for (const auto &it : cell->connections_) {
+ const auto &port = it.first;
+ const auto &sigbits = it.second.bits();
+ for (size_t i = 0; i < sigbits.size(); ++i) {
+ auto sigbit = sigmap(sigbits[i]);
+
+ // This is an input port (sink))
+ if (cell->input(port)) {
+ auto &vec = sinks[sigbit];
+ vec.push_back(CellPin(cell, port, i));
+ }
+ // This is a source
+ if (cell->output(port)) {
+ drivers.insert(std::make_pair(sigbit, CellPin(cell, port, i)));
+ }
+ }
+ }
+ }
+
+ // Scan top-level ports
+ for (auto &it : module->wires_) {
+ auto *wire = it.second;
+
+ if (!wire->port_input && !wire->port_output) {
+ continue;
+ }
+
+ RTLIL::SigSpec sigspec(wire, wire->start_offset, wire->width);
+ const auto &sigbits = sigspec.bits();
+ for (size_t i = 0; i < sigbits.size(); ++i) {
+ auto sigbit = sigbits[i];
+ if (!sigbit.wire) {
+ continue;
+ }
+
+ // Output port (sink)
+ if (sigbit.wire->port_output) {
+ auto &vec = sinks[sigmap(sigbit)];
+ vec.push_back(CellPin(nullptr, sigbit.wire->name, i));
+ }
+ // Input port (source)
+ if (sigbit.wire->port_input) {
+ drivers.insert(std::make_pair(sigbit, CellPin(nullptr, sigbit.wire->name, i)));
+ }
+ }
+ }
+ }
+
+ /// Clears the map
+ void clear()
+ {
+ sinks.clear();
+ drivers.clear();
+ };
+ };
+
+ // ..........................................
+
+ /// Describes a flip-flop type that can be integrated with a DSP cell
+ struct FlopType {
+ RTLIL::IdString name;
+
+ /// A dict of port names indexed by their functions (like "clk", "rst")
+ dict<RTLIL::IdString, RTLIL::IdString> ports;
+
+ struct {
+ /// A list of parameters that must match for all flip-flops
+ std::vector<RTLIL::IdString> matching;
+ /// A dict of parameter values that must match for a flip-flop
+ dict<RTLIL::IdString, RTLIL::Const> required;
+ /// A dict of parameters to be set in the DSP cell after integration
+ dict<RTLIL::IdString, RTLIL::Const> set;
+ /// A dict of parameters to be mapped to the DSP cell after integration
+ dict<RTLIL::IdString, RTLIL::IdString> map;
+ } params;
+ };
+
+ /// Describes a DSP cell port that has built-in register (flip-flops)
+ struct PortType {
+ RTLIL::IdString name;
+
+ /// Range of port pins that have FFs (low to high, inclusive)
+ std::pair<int, int> bits;
+ /// A dict of associated cell ports indexed by their function (like "clk, "rst")
+ /// along with the default value to connect when unused.
+ dict<RTLIL::IdString, std::pair<RTLIL::IdString, RTLIL::Const>> assoc;
+ };
+
+ /// Describes a DSP register
+ struct RegisterType {
+
+ /// Control parameters
+ struct {
+ /// A dict of parameters to be set in the cell after integration
+ dict<RTLIL::IdString, RTLIL::Const> set;
+ /// A dict of parameters to be mapped to the cell after integration
+ dict<RTLIL::IdString, RTLIL::IdString> map;
+ } params;
+
+ /// A list of ports to be connected to specific constants after flip-flop
+ /// integration.
+ dict<RTLIL::IdString, RTLIL::Const> connect;
+
+ unsigned int hash() const
+ {
+ unsigned int h = 0;
+ h = mkhash_add(h, params.set.hash());
+ h = mkhash_add(h, params.map.hash());
+ h = mkhash_add(h, connect.hash());
+ return h;
+ }
+
+ bool operator==(const RegisterType &ref) const
+ {
+ return (params.set == ref.params.set) && (params.map == ref.params.map) && (connect == ref.connect);
+ }
+ };
+
+ /// Describes a DSP cell type
+ struct DspType {
+ RTLIL::IdString name;
+ dict<RegisterType, std::vector<PortType>> registers;
+ };
+
+ /// Describes a changes made to a DSP cell
+ struct DspChanges {
+ pool<RTLIL::IdString> params; // Modified params
+ pool<RTLIL::IdString> conns; // Altered connections (ports)
+ };
+
+ // ..........................................
+
+ /// Describes unique flip-flop configuration that is exclusive.
+ struct FlopData {
+ RTLIL::IdString type;
+ dict<RTLIL::IdString, RTLIL::SigBit> conns;
+ struct {
+ dict<RTLIL::IdString, RTLIL::Const> flop;
+ dict<RTLIL::IdString, RTLIL::Const> dsp;
+ } params;
+
+ FlopData(const RTLIL::IdString &_type) : type(_type){};
+
+ FlopData(const FlopData &ref) = default;
+ FlopData(FlopData &&ref) = default;
+
+ unsigned int hash() const
+ {
+ unsigned int h = 0;
+ h = mkhash_add(h, type.hash());
+ h = mkhash_add(h, conns.hash());
+ h = mkhash_add(h, params.flop.hash());
+ h = mkhash_add(h, params.dsp.hash());
+ return h;
+ }
+
+ bool operator==(const FlopData &ref) const
+ {
+ return (type == ref.type) && (conns == ref.conns) && (params.flop == ref.params.flop) && (params.dsp == ref.params.dsp);
+ }
+ };
+
+ // ..........................................
+
+ /// Loads FF and DSP integration rules from a file
+ void load_rules(const std::string &a_FileName)
+ {
+
+ // Parses a string and returns a vector of fields delimited by the
+ // given character.
+ auto getFields = [](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;
+ };
+
+ // 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;
+ };
+
+ // Parses port name as "<name>[<hi>:<lo>]" or just "<name>"
+ auto parsePortName = [&](const std::string &str) {
+ const std::regex expr("^(.*)\\[([0-9]+):([0-9]+)\\]");
+ std::smatch match;
+
+ std::tuple<std::string, int, int> data;
+ auto res = std::regex_match(str, match, expr);
+ if (res) {
+ data = std::make_tuple(std::string(match[1]), std::stoi(match[2]), std::stoi(match[3]));
+
+ if ((std::get<2>(data) > std::get<1>(data)) || std::get<2>(data) < 0 || std::get<1>(data) < 0) {
+ log_error(" invalid port spec: '%s'\n", str.c_str());
+ }
+ } else {
+ data = std::make_tuple(str, -1, -1);
+ }
+
+ return data;
+ };
+
+ 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");
+ }
+
+ // Parse each port as if it was associated with its own DSP register.
+ // Group them each time a port definition is complete.
+ PortType portType;
+ RegisterType registerType;
+
+ std::vector<DspType> dspTypes;
+ std::vector<FlopType> flopTypes;
+
+ std::vector<RTLIL::IdString> dspAliases;
+ std::vector<std::string> portNames;
+
+ 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 = getFields(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]);
+
+ dspAliases.clear();
+ for (size_t i = 2; i < fields.size(); ++i) {
+ dspAliases.push_back(RTLIL::escape_id(fields[i]));
+ }
+ } 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();
+
+ const auto dspType = dspTypes.back();
+
+ for (const auto &alias : dspAliases) {
+ dspTypes.push_back(dspType);
+ dspTypes.back().name = alias;
+ }
+ }
+
+ // 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 spec = parsePortName(fields[1]);
+
+ portType = PortType();
+ portType.name = RTLIL::escape_id(std::get<0>(spec));
+ portType.bits = std::make_pair(std::get<2>(spec), std::get<1>(spec));
+ portType.assoc.insert(std::make_pair(RTLIL::escape_id("clk"), std::make_pair(RTLIL::IdString(), RTLIL::Sx)));
+ portType.assoc.insert(std::make_pair(RTLIL::escape_id("rst"), std::make_pair(RTLIL::IdString(), RTLIL::Sx)));
+ portType.assoc.insert(std::make_pair(RTLIL::escape_id("ena"), std::make_pair(RTLIL::IdString(), RTLIL::Sx)));
+
+ registerType = RegisterType();
+
+ portNames.clear();
+ for (size_t i = 2; i < fields.size(); ++i) {
+ portNames.push_back(fields[i]);
+ }
+
+ } 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();
+
+ // Store the DSP port
+ auto &dspType = dspTypes.back();
+ dspType.registers[registerType].push_back(portType);
+
+ // Store any extra DSP ports belonging to the same register
+ for (const auto &name : portNames) {
+ auto spec = parsePortName(name);
+
+ PortType portTypeCopy = portType;
+ portTypeCopy.name = RTLIL::escape_id(std::get<0>(spec));
+ portTypeCopy.bits = std::make_pair(std::get<2>(spec), std::get<1>(spec));
+
+ dspType.registers[registerType].push_back(portTypeCopy);
+ }
+ }
+
+ // 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]);
+ flopTypes.back().ports.insert(std::make_pair(RTLIL::escape_id("clk"), RTLIL::IdString()));
+ flopTypes.back().ports.insert(std::make_pair(RTLIL::escape_id("rst"), RTLIL::IdString()));
+ flopTypes.back().ports.insert(std::make_pair(RTLIL::escape_id("ena"), RTLIL::IdString()));
+ flopTypes.back().ports.insert(std::make_pair(RTLIL::escape_id("d"), RTLIL::IdString()));
+ flopTypes.back().ports.insert(std::make_pair(RTLIL::escape_id("q"), RTLIL::IdString()));
+ } 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 (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") {
+ if (fields.size() != 3) {
+ log_error(" syntax error: '%s'\n", line.c_str());
+ }
+ portType.assoc[RTLIL::escape_id("clk")] = std::make_pair(RTLIL::escape_id(fields[1]), RTLIL::Const::from_string(fields[2]));
+ } else if (tok.back() == "ff") {
+ if (fields.size() != 2) {
+ log_error(" syntax error: '%s'\n", line.c_str());
+ }
+ flopTypes.back().ports[RTLIL::escape_id("clk")] = RTLIL::escape_id(fields[1]);
+ }
+ } else if (fields[0] == "rst") {
+ 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") {
+ if (fields.size() != 3) {
+ log_error(" syntax error: '%s'\n", line.c_str());
+ }
+ portType.assoc[RTLIL::escape_id("rst")] = std::make_pair(RTLIL::escape_id(fields[1]), RTLIL::Const::from_string(fields[2]));
+ } else if (tok.back() == "ff") {
+ if (fields.size() != 2) {
+ log_error(" syntax error: '%s'\n", line.c_str());
+ }
+ flopTypes.back().ports[RTLIL::escape_id("rst")] = RTLIL::escape_id(fields[1]);
+ }
+ } else if (fields[0] == "ena") {
+ 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") {
+ if (fields.size() != 3) {
+ log_error(" syntax error: '%s'\n", line.c_str());
+ }
+ portType.assoc[RTLIL::escape_id("ena")] = std::make_pair(RTLIL::escape_id(fields[1]), RTLIL::Const::from_string(fields[2]));
+ } else if (tok.back() == "ff") {
+ if (fields.size() != 2) {
+ log_error(" syntax error: '%s'\n", line.c_str());
+ }
+ flopTypes.back().ports[RTLIL::escape_id("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[RTLIL::escape_id("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[RTLIL::escape_id("q")] = RTLIL::escape_id(fields[1]);
+ }
+
+ // Parameters that must be set to certain values
+ else if (fields[0] == "require") {
+ 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());
+ }
+
+ const auto vec = parseNameValue(fields);
+ for (const auto &it : vec) {
+ flopTypes.back().params.required.insert(std::make_pair(RTLIL::escape_id(it.first), RTLIL::Const(it.second)));
+ }
+ }
+ // Parameters that has to match for a flip-flop
+ else if (fields[0] == "match") {
+ 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());
+ }
+
+ for (size_t i = 1; i < fields.size(); ++i) {
+ flopTypes.back().params.matching.push_back(RTLIL::escape_id(fields[i]));
+ }
+ }
+ // 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") {
+ registerType.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") {
+ registerType.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);
+ for (const auto &it : vec) {
+ registerType.connect.insert(std::make_pair(RTLIL::escape_id(it.first), RTLIL::Const(it.second)));
+ }
+ }
+
+ else {
+ log_error(" unexpected keyword '%s'\n", fields[0].c_str());
+ }
+ }
+
+ // Convert lists to maps
+ for (const auto &it : dspTypes) {
+ if (m_DspTypes.count(it.name)) {
+ log_error(" duplicated rule for DSP '%s'\n", it.name.c_str());
+ }
+ m_DspTypes.insert(std::make_pair(it.name, it));
+ }
+ for (const auto &it : flopTypes) {
+ if (m_FlopTypes.count(it.name)) {
+ log_error(" duplicated rule for flip-flop '%s'\n", it.name.c_str());
+ }
+ m_FlopTypes.insert(std::make_pair(it.name, it));
+ }
+ }
+
+ void dump_rules()
+ {
+
+ // Dump DSP types
+ log("DSP types:\n");
+ for (const auto &it1 : m_DspTypes) {
+ const auto &dsp = it1.second;
+ log(" %s\n", dsp.name.c_str());
+
+ for (const auto &it2 : dsp.registers) {
+ const auto ® = it2.first;
+ const auto &ports = it2.second;
+ log(" ports:\n");
+ for (const auto &port : ports) {
+
+ std::string range;
+ if (port.bits.first != -1 && port.bits.second != -1) {
+ range = stringf("[%d:%d]", port.bits.second, port.bits.first);
+ }
+
+ log(" %s.%s%s\n", dsp.name.c_str(), port.name.c_str(), range.c_str());
+
+ for (const auto &it : port.assoc) {
+ log(" %.3s: %s\n", it.first.c_str(), !it.second.first.empty() ? it.second.first.c_str() : "<none>");
+ }
+
+ if (!reg.params.set.empty()) {
+ log(" set params:\n");
+ for (const auto &it : reg.params.set) {
+ log(" %s=%s\n", it.first.c_str(), it.second.decode_string().c_str());
+ }
+ }
+ if (!reg.params.map.empty()) {
+ log(" map params:\n");
+ for (const auto &it : reg.params.map) {
+ log(" %s=%s\n", it.first.c_str(), it.second.c_str());
+ }
+ }
+ if (!reg.connect.empty()) {
+ log(" connect ports:\n");
+ for (const auto &it : reg.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());
+
+ for (const auto &it : ff.ports) {
+ log(" %.3s: %s\n", it.first.c_str(), !it.second.empty() ? it.second.c_str() : "<none>");
+ }
+
+ if (!ff.params.required.empty()) {
+ log(" required params:\n");
+ for (const auto &it : ff.params.required) {
+ log(" %s=%s\n", it.first.c_str(), it.second.decode_string().c_str());
+ }
+ }
+ if (!ff.params.matching.empty()) {
+ log(" params that must match:\n");
+ for (const auto &it : ff.params.matching) {
+ log(" %s\n", it.c_str());
+ }
+ }
+ 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;
+ /// Module connection map
+ ConnMap m_ConnMap;
+
+ /// Cells to be removed (per module!)
+ pool<RTLIL::Cell *> m_CellsToRemove;
+ /// DSP cells that got changed
+ dict<RTLIL::Cell *, DspChanges> m_DspChanges;
+
+ /// 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");
+ log("The pass loads a set of rules from the file given with the '-rules' parameter.\n");
+ log("The rules define what ports of a DSP module have internal registers and what\n");
+ log("has to be done to enable them. They also define compatible flip-flop cell\n");
+ log("types.\n");
+ log("\n");
+ log("The format of the rules file is the following:\n");
+ log("\n");
+ log(" # This is a comment\n");
+ log("\n");
+ log(" dsp <dsp_type> [<dsp_type> ...]\n");
+ log(" port <dsp_port> [<dsp_port> ...]\n");
+ log(" clk <associated clk> <default>\n");
+ log(" [rst <associated reset>] <default>\n");
+ log(" [ena <associated enable>] <default>\n");
+ log("\n");
+ log(" [set <param>=<value> [<param>=<value> ...]]\n");
+ log(" [map <dsp_param>=<ff_param> [<dsp_param>=<ff_param> ...]]\n");
+ log(" [con <port>=<const> [<port>=<const> ...]]\n");
+ log(" endport\n");
+ log(" enddsp\n");
+ log("\n");
+ log(" ff <ff_type>\n");
+ log(" clk <clock input>\n");
+ log(" [rst <reset input>]\n");
+ log(" [ena <enable input>]\n");
+ log(" d <data input>\n");
+ log(" q <data output>\n");
+ log("\n");
+ log(" require <param>=<value> [<param>=<value> ...]\n");
+ log(" match <param> [<param> ...]\n");
+ log("\n");
+ log(" set <param>=<value> [<param>=<value> ...]\n");
+ log(" map <dsp_param>=<ff_param> [<dsp_param>=<ff_param> ...]\n");
+ log(" endff\n");
+ log("\n");
+ log("Each 'dsp' section defines a DSP cell type (can apply to multiple types).\n");
+ log("Within it each 'port' section defines a data port with internal register.\n");
+ log("There can be multiple port names given if they belong to the same control register.\n");
+ log("The port can be specified as a whole (eg. 'DATA') or as a subset of the whole\n");
+ log("(eg. 'DATA[7:0]').\n");
+ log("\n");
+ log("Statemenst 'clk', 'rst' and 'ena' define names of clock, reset and enable\n");
+ log("ports associated with the data port along with default constant values to\n");
+ log("connect them to when a given port has no counterpart in the flip-flop being\n");
+ log("integrated.\n");
+ log("\n");
+ log("The 'set' statement tells how to set control parameter(s) of the DSP that\n");
+ log("enable the input register on the port. The 'map' statement defines how to\n");
+ log("map parameter(s) of the flip-flip being integrated to the DSP. Finally the\n");
+ log("'con' statement informs how to connected control port(s) of the DSP to enable\n");
+ log("the register.\n");
+ log("\n");
+ log("Each 'ff' section defines a flip-flop type that can be integrated into a DSP\n");
+ log("cell. Inside this section 'clk', 'rst', 'ena', 'd' and 'q' define names of\n");
+ log("clock, reset, enable, data in and data out ports of the flip-flop respectively.\n");
+ log("\n");
+ log("The 'require' statement defines parameter(s) that must have specific value\n");
+ log("for a flip-flop to be considered for integration. The 'match' statement\n");
+ log("lists names of flip-flop parameters that must match on all flip-flops connected\n");
+ log("to a single DSP data port.\n");
+ log("\n");
+ log("The 'set' and 'map' statements serve the same function as in the DSP port\n");
+ log("section but here they may differ depending on the flip-flop type being\n");
+ log("integrated.\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!");
+ }
+
+ // Reset state
+ m_CellsToRemove.clear();
+ m_DspChanges.clear();
+ m_DspTypes.clear();
+ m_FlopTypes.clear();
+
+ // Load rules
+ rewrite_filename(rulesFile);
+ 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);
+
+ // Build the connection map
+ m_ConnMap.clear();
+ m_ConnMap.build(module, m_SigMap);
+
+ // Look for DSP cells
+ for (auto cell : module->cells()) {
+
+ // Not a DSP
+ if (!m_DspTypes.count(cell->type)) {
+ continue;
+ }
+
+ // Process all registers
+ auto &dspType = m_DspTypes.at(cell->type);
+ for (auto &rule : dspType.registers) {
+ processRegister(cell, rule.first, rule.second);
+ }
+ }
+
+ // Remove cells
+ for (const auto &cell : m_CellsToRemove) {
+ module->remove(cell);
+ }
+ m_CellsToRemove.clear();
+ }
+
+ // Clear maps
+ m_SigMap.clear();
+ m_ConnMap.clear();
+ }
+
+ // ..........................................
+
+ bool checkFlop(RTLIL::Cell *a_Cell)
+ {
+ const auto &flopType = m_FlopTypes.at(a_Cell->type);
+ bool isOk = true;
+
+ log_debug("checking connected flip-flop '%s' of type '%s'... ", a_Cell->name.c_str(), a_Cell->type.c_str());
+
+ // Must not have the "keep" attribute
+ if (a_Cell->has_keep_attr()) {
+ log_debug("\n the 'keep' attribute is set");
+ isOk = false;
+ }
+
+ // Check if required parameters are set as they should be
+ for (const auto &it : flopType.params.required) {
+ const auto curr = a_Cell->getParam(it.first);
+ if (curr != it.second) {
+ log_debug("\n param '%s' mismatch ('%s' instead of '%s')", it.first.c_str(), curr.decode_string().c_str(),
+ it.second.decode_string().c_str());
+ isOk = false;
+ }
+ }
+
+ if (isOk) {
+ log_debug("Ok\n");
+ } else {
+ log_debug("\n");
+ }
+ return isOk;
+ }
+
+ bool checkFlopDataAgainstDspRegister(const FlopData &a_FlopData, RTLIL::Cell *a_Cell, const RegisterType &a_Register,
+ const std::vector<PortType> &a_Ports)
+ {
+ const auto &flopType = m_FlopTypes.at(a_FlopData.type);
+ const auto &changes = m_DspChanges[a_Cell];
+ bool isOk = true;
+
+ log_debug(" checking connected flip-flop settings against the DSP register... ");
+
+ // Check control signal connections
+ for (const auto &port : a_Ports) {
+ for (const auto &it : port.assoc) {
+ const auto &key = it.first;
+ const auto &port = it.second.first;
+
+ SigBit conn(RTLIL::Sx);
+ if (!port.empty() && a_Cell->hasPort(port)) {
+ auto sigspec = a_Cell->getPort(port);
+ auto sigbits = sigspec.bits();
+ log_assert(sigbits.size() <= 1);
+ if (!sigbits.empty()) {
+ conn = m_SigMap(sigbits[0]);
+ }
+ }
+
+ if (conn.is_wire() || (!conn.is_wire() && conn.data != RTLIL::Sx)) {
+ if (conn != a_FlopData.conns.at(key)) {
+ log_debug("\n connection to port '%s' mismatch", port.c_str());
+ isOk = false;
+ }
+ }
+ }
+ }
+
+ auto checkParam = [&](const RTLIL::IdString &name, const RTLIL::Const &curr, const RTLIL::Const &next) {
+ if (curr != next && changes.params.count(name)) {
+ log_debug("\n the param '%s' mismatch ('%s' instead of '%s')", name.c_str(), curr.decode_string().c_str(),
+ next.decode_string().c_str());
+ isOk = false;
+ return false;
+ }
+ return true;
+ };
+
+ // Check parameters to be mapped (by the port rule)
+ for (const auto &it : a_Register.params.map) {
+ if (a_Cell->hasParam(it.first) && a_FlopData.params.dsp.count(it.second)) {
+ const auto curr = a_Cell->getParam(it.first);
+ const auto flop = a_FlopData.params.dsp.at(it.second);
+ checkParam(it.first, curr, flop);
+ }
+ }
+
+ // Check parameters to be set (by the port rule)
+ for (const auto &it : a_Register.params.set) {
+ if (a_Cell->hasParam(it.first)) {
+ const auto curr = a_Cell->getParam(it.first);
+ checkParam(it.first, curr, it.second);
+ }
+ }
+
+ // Check parameters to be mapped (by the flip-flop rule)
+ for (const auto &it : flopType.params.map) {
+ if (a_Cell->hasParam(it.first) && a_FlopData.params.dsp.count(it.second)) {
+ const auto curr = a_Cell->getParam(it.first);
+ const auto flop = a_FlopData.params.dsp.at(it.second);
+ checkParam(it.first, curr, flop);
+ }
+ }
+
+ // Check parameters to be set (by the flip-flop rule)
+ for (const auto &it : flopType.params.set) {
+ if (a_Cell->hasParam(it.first)) {
+ const auto curr = a_Cell->getParam(it.first);
+ checkParam(it.first, curr, it.second);
+ }
+ }
+
+ if (isOk) {
+ log_debug("Ok\n");
+ } else {
+ log_debug("\n");
+ }
+ return isOk;
+ }
+
+ /// Returns a string with either wire name or constant value for a SigBit
+ static std::string sigBitName(const RTLIL::SigBit &a_SigBit)
+ {
+ if (a_SigBit.is_wire()) {
+ RTLIL::Wire *w = a_SigBit.wire;
+ return RTLIL::unescape_id(w->name);
+ } else {
+ switch (a_SigBit.data) {
+ case RTLIL::State::S0:
+ return "1'b0";
+ case RTLIL::State::S1:
+ return "1'b1";
+ case RTLIL::State::Sx:
+ return "1'bx";
+ case RTLIL::State::Sz:
+ return "1'bz";
+ case RTLIL::State::Sa:
+ return "-";
+ case RTLIL::State::Sm:
+ return "m";
+ }
+ return "?";
+ }
+ }
+
+ // ..........................................
+
+ void processRegister(RTLIL::Cell *a_Cell, const RegisterType &a_Register, const std::vector<PortType> &a_Ports)
+ {
+
+ // The cell register control parameter(s) must not be set
+ for (const auto &it : a_Register.params.set) {
+ const auto curr = a_Cell->getParam(it.first);
+ if (curr == it.second) {
+ log_debug(" the param '%s' is already set to '%s'\n", it.first.c_str(), it.second.decode_string().c_str());
+ return;
+ }
+ }
+
+ pool<FlopData> groups;
+ dict<RTLIL::IdString, std::vector<RTLIL::Cell *>> flops;
+
+ // Process ports
+ bool flopsOk = true;
+ for (const auto &port : a_Ports) {
+ log_debug(" attempting flip-flop integration for %s.%s of %s\n", a_Cell->type.c_str(), port.name.c_str(), a_Cell->name.c_str());
+
+ if (!a_Cell->hasPort(port.name)) {
+ log_debug(" port unconnected.\n");
+ continue;
+ }
+ log_assert(a_Cell->output(port.name) || a_Cell->input(port.name));
+
+ // Get port connections
+ auto sigspec = a_Cell->getPort(port.name);
+ auto sigbits = sigspec.bits();
+
+ flops[port.name] = std::vector<RTLIL::Cell *>(sigbits.size(), nullptr);
+ for (size_t i = 0; i < sigbits.size(); ++i) {
+ auto sigbit = m_SigMap(sigbits[i]);
+
+ log_debug(" %2zu. ", i);
+
+ // Port connected to a const.
+ if (!sigbit.wire) {
+ log_debug("constant\n");
+ continue;
+ }
+
+ // Skip bits out of the specified range
+ if ((port.bits.first >= 0 && (int)i < port.bits.first) || (port.bits.second >= 0 && (int)i > port.bits.second)) {
+ log_debug("(excluded)\n");
+ continue;
+ }
+
+ pool<CellPin> others;
+
+ // Get sinks(s), discard the port completely if more than one sink
+ // is found.
+ if (a_Cell->output(port.name)) {
+ if (m_ConnMap.sinks.count(sigbit)) {
+ for (const auto &sink : m_ConnMap.sinks.at(sigbit)) {
+ if (sink.cell != nullptr && m_CellsToRemove.count(sink.cell)) {
+ continue;
+ }
+ others.insert(sink);
+ }
+ }
+
+ }
+ // Get driver. Discard if the driver drives something else too
+ else if (a_Cell->input(port.name)) {
+ if (m_ConnMap.drivers.count(sigbit)) {
+ auto driver = m_ConnMap.drivers.at(sigbit);
+
+ if (m_ConnMap.sinks.count(sigbit)) {
+ auto sinks = m_ConnMap.sinks.at(sigbit);
+ if (sinks.size() > 1) {
+ log_debug("multiple sinks (%zu)\n", others.size());
+ flopsOk = false;
+ continue;
+ }
+ }
+
+ others.insert(driver);
+ }
+ }
+
+ // No others - unconnected
+ if (others.empty()) {
+ log_debug("unconnected\n");
+ continue;
+ }
+
+ if (others.size() > 1) {
+ log_debug("multiple sinks (%zu)\n", others.size());
+ flopsOk = false;
+ continue;
+ }
+
+ // Get the sink, check if this is a flip-flop
+ auto &other = *others.begin();
+ auto *flop = other.cell;
+
+ if (flop == nullptr) {
+ if (!other.port.empty()) {
+ log_debug("connection reaches module edge\n");
+ flopsOk = false;
+ }
+ log_debug("unconnected\n");
+ continue;
+ }
+
+ if (!m_FlopTypes.count(flop->type)) {
+ log_debug("non-flip-flop connected\n");
+ flopsOk = false;
+ continue;
+ }
+
+ // Check if the connection goes to the data input/output port
+ const auto &flopType = m_FlopTypes.at(flop->type);
+ RTLIL::IdString flopPort;
+ if (a_Cell->output(port.name)) {
+ flopPort = flopType.ports.at(RTLIL::escape_id("d"));
+ } else if (a_Cell->input(port.name)) {
+ flopPort = flopType.ports.at(RTLIL::escape_id("q"));
+ }
+
+ if (flopPort != other.port) {
+ log_debug("connection to non-data port of a flip-flip");
+ flopsOk = false;
+ continue;
+ }
+
+ // Check the flip-flop configuration
+ if (!checkFlop(flop)) {
+ flopsOk = false;
+ continue;
+ }
+
+ // Get parameters to be mapped to the DSP according to the port
+ // rule.
+ dict<RTLIL::IdString, RTLIL::Const> mappedParams;
+ for (const auto &it : a_Register.params.map) {
+ if (flop->hasParam(it.second)) {
+ const auto &value = flop->getParam(it.second);
+ mappedParams.insert(std::make_pair(it.first, value));
+ }
+ }
+
+ // Store the flop and its data
+ groups.insert(getFlopData(flop, mappedParams));
+ flops[port.name][i] = flop;
+ }
+ }
+
+ // Cannot integrate for various reasons
+ if (!flopsOk) {
+ log_debug(" cannot use the DSP register\n");
+ return;
+ }
+
+ // No matching flip-flop groups
+ if (groups.empty()) {
+ log_debug(" no matching flip-flops found\n");
+ return;
+ }
+
+ // Do not allow more than a single group
+ if (groups.size() != 1) {
+ log_debug(" %zu flip-flop groups, only a single one allowed\n", groups.size());
+ return;
+ }
+
+ // Validate the flip flop data agains the DSP cell
+ const auto &flopData = *groups.begin();
+ if (!checkFlopDataAgainstDspRegister(flopData, a_Cell, a_Register, a_Ports)) {
+ log_debug(" flip-flops vs. DSP check failed\n");
+ return;
+ }
+
+ // Log connections
+ for (const auto &port : a_Ports) {
+
+ if (!flops.count(port.name)) {
+ continue;
+ }
+
+ log(" %s %s.%s\n", a_Cell->type.c_str(), a_Cell->name.c_str(), port.name.c_str());
+
+ const auto &conns = flops.at(port.name);
+ for (size_t i = 0; i < conns.size(); ++i) {
+ if (conns[i] != nullptr) {
+ log_debug(" %2zu. %s %s\n", i, conns[i]->type.c_str(), conns[i]->name.c_str());
+ } else if ((port.bits.first >= 0 && (int)i < port.bits.first) || (port.bits.second >= 0 && (int)i > port.bits.second)) {
+ log_debug(" %2zu. (excluded)\n", i);
+ } else {
+ log_debug(" %2zu. None\n", i);
+ }
+ }
+ }
+
+ // Reconnect data signals, mark the flip-flop for removal
+ const auto &flopType = m_FlopTypes.at(flopData.type);
+ for (const auto &port : a_Ports) {
+
+ if (!flops.count(port.name)) {
+ continue;
+ }
+
+ const auto &conns = flops.at(port.name);
+ auto sigspec = a_Cell->getPort(port.name);
+ auto sigbits = sigspec.bits();
+
+ for (size_t i = 0; i < conns.size(); ++i) {
+
+ auto *flop = conns[i];
+ if (flop == nullptr) {
+ continue;
+ }
+
+ RTLIL::IdString flopPort;
+ if (a_Cell->output(port.name)) {
+ flopPort = flopType.ports.at(RTLIL::escape_id("q"));
+ } else if (a_Cell->input(port.name)) {
+ flopPort = flopType.ports.at(RTLIL::escape_id("d"));
+ }
+
+ if (!flop->hasPort(flopPort)) {
+ log_error("cell '%s' does not have port '%s'!\n", flop->type.c_str(), flopPort.c_str());
+ }
+
+ sigbits[i] = SigBit(RTLIL::Sx);
+ auto sigspec = flop->getPort(flopPort);
+ log_assert(sigspec.bits().size() <= 1);
+ if (sigspec.bits().size() == 1) {
+ sigbits[i] = sigspec.bits()[0];
+ }
+
+ m_CellsToRemove.insert(flop);
+ }
+
+ a_Cell->setPort(port.name, RTLIL::SigSpec(sigbits));
+ }
+
+ // Reconnect (map) control signals. Connect the default value if
+ // a particular signal is not present in the flip-flop.
+ for (const auto &port : a_Ports) {
+ for (const auto &it : port.assoc) {
+ const auto &key = it.first;
+ const auto &port = it.second.first;
+
+ auto conn = RTLIL::SigBit(RTLIL::SigChunk(it.second.second));
+ if (flopData.conns.count(key)) {
+ conn = flopData.conns.at(key);
+ }
+
+ log_debug(" connecting %s.%s to %s\n", a_Cell->type.c_str(), port.c_str(), sigBitName(conn).c_str());
+ a_Cell->setPort(port, conn);
+ m_DspChanges[a_Cell].conns.insert(port);
+ }
+ }
+
+ // Connect control signals according to the register rule
+ for (const auto &it : a_Register.connect) {
+ log_debug(" connecting %s.%s to %s\n", a_Cell->type.c_str(), it.first.c_str(), it.second.as_string().c_str());
+ a_Cell->setPort(it.first, it.second);
+ m_DspChanges[a_Cell].conns.insert(it.first);
+ }
+
+ // Map parameters (register rule)
+ for (const auto &it : a_Register.params.map) {
+ if (flopData.params.dsp.count(it.second)) {
+ const auto ¶m = flopData.params.dsp.at(it.second);
+ log_debug(" setting param '%s' to '%s'\n", it.first.c_str(), param.decode_string().c_str());
+ a_Cell->setParam(it.first, param);
+ m_DspChanges[a_Cell].params.insert(it.first);
+ }
+ }
+
+ // Map parameters (flip-flop rule)
+ for (const auto &it : flopType.params.map) {
+ if (flopData.params.dsp.count(it.second)) {
+ const auto ¶m = flopData.params.dsp.at(it.second);
+ log_debug(" setting param '%s' to '%s'\n", it.first.c_str(), param.decode_string().c_str());
+ a_Cell->setParam(it.first, param);
+ m_DspChanges[a_Cell].params.insert(it.first);
+ }
+ }
+
+ // Set parameters (port rule)
+ for (const auto &it : a_Register.params.set) {
+ log_debug(" setting param '%s' to '%s'\n", it.first.c_str(), it.second.decode_string().c_str());
+ a_Cell->setParam(it.first, it.second);
+ m_DspChanges[a_Cell].params.insert(it.first);
+ }
+
+ // Set parameters (flip-flop rule)
+ for (const auto &it : flopType.params.set) {
+ log_debug(" setting param '%s' to '%s'\n", it.first.c_str(), it.second.decode_string().c_str());
+ a_Cell->setParam(it.first, it.second);
+ m_DspChanges[a_Cell].params.insert(it.first);
+ }
+ }
+
+ // ..........................................
+
+ /// Collects flip-flop connectivity data and parameters which defines the
+ /// group it belongs to.
+ FlopData getFlopData(RTLIL::Cell *a_Cell, const dict<RTLIL::IdString, RTLIL::Const> &a_ExtraParams)
+ {
+ FlopData data(a_Cell->type);
+
+ log_assert(m_FlopTypes.count(a_Cell->type) != 0);
+ const auto &flopType = m_FlopTypes.at(a_Cell->type);
+
+ // Gather connections to control ports
+ for (const auto &it : flopType.ports) {
+
+ // Skip "D" and "Q" as they connection will always differ.
+ if (it.first == RTLIL::escape_id("d") || it.first == RTLIL::escape_id("q")) {
+ continue;
+ }
+
+ if (!it.second.empty() && a_Cell->hasPort(it.second)) {
+ auto sigspec = a_Cell->getPort(it.second);
+ auto sigbits = sigspec.bits();
+ log_assert(sigbits.size() <= 1);
+ if (!sigbits.empty()) {
+ data.conns[it.first] = m_SigMap(sigbits[0]);
+ }
+ }
+ }
+
+ // Gather flip-flop parameters that need to match
+ for (const auto &it : flopType.params.matching) {
+ log_assert(a_Cell->hasParam(it));
+ data.params.flop.insert(std::make_pair(it, a_Cell->getParam(it)));
+ }
+
+ // Gather flip-flop parameters to be mapped to the DSP as well
+ for (const auto &it : flopType.params.map) {
+ log_assert(a_Cell->hasParam(it.second));
+ data.params.flop.insert(std::make_pair(it.second, a_Cell->getParam(it.second)));
+ }
+
+ // Gather DSP parameters and their values to be set to too
+ for (const auto &it : flopType.params.set) {
+ data.params.dsp.insert(it);
+ }
+
+ // Append extra DSP parameters
+ for (const auto &it : a_ExtraParams) {
+ data.params.dsp.insert(it);
+ }
+
+ return data;
+ }
+
+} DspFF;
+
+PRIVATE_NAMESPACE_END
diff --git a/dsp-ff-plugin/nexus-dsp_rules.txt b/dsp-ff-plugin/nexus-dsp_rules.txt
new file mode 100644
index 0000000..0ed8947
--- /dev/null
+++ b/dsp-ff-plugin/nexus-dsp_rules.txt
@@ -0,0 +1,175 @@
+# Copyright (C) 2020-2022 The SymbiFlow Authors.
+#
+# Use of this source code is governed by a ISC-style
+# license that can be found in the LICENSE file or at
+# https://opensource.org/licenses/ISC
+#
+# SPDX-License-Identifier:ISC
+
+dsp MULT9X9 MULT18X18 MULT18X36 MULT36X36
+ port A SIGNEDA
+ clk CLK 0
+ rst RSTA 0
+ ena CEA 1
+
+ set REGINPUTA=REGISTER
+ map GSR=GSR
+ endport
+ port B SIGNEDB
+ clk CLK 0
+ rst RSTB 0
+ ena CEB 1
+
+ set REGINPUTB=REGISTER
+ map GSR=GSR
+ endport
+ port Z
+ clk CLK 0
+ rst RSTOUT 0
+ ena CEOUT 1
+
+ set REGOUTPUT=REGISTER
+ map GSR=GSR
+ endport
+enddsp
+
+dsp MULTPREADD9X9 MULTPREADD18X18 MULTADDSUB18X18 MULTADDSUB36X36
+ port A SIGNEDA
+ clk CLK 0
+ rst RSTA 0
+ ena CEA 1
+
+ set REGINPUTA=REGISTER
+ map GSR=GSR
+ endport
+ port B SIGNEDB
+ clk CLK 0
+ rst RSTB 0
+ ena CEB 1
+
+ set REGINPUTB=REGISTER
+ map GSR=GSR
+ endport
+ port C SIGNEDC
+ clk CLK 0
+ rst RSTC 0
+ ena CEC 1
+
+ set REGINPUTC=REGISTER
+ map GSR=GSR
+ endport
+ port Z
+ clk CLK 0
+ rst RSTOUT 0
+ ena CEOUT 1
+
+ set REGOUTPUT=REGISTER
+ map GSR=GSR
+ endport
+enddsp
+
+dsp MULTADDSUB9X9WIDE
+ port A0 SIGNED
+ clk CLK 0
+ rst RSTA0A1 0
+ ena CEA0A1 1
+
+ set REGINPUTAB0=REGISTER
+ map GSR=GSR
+ endport
+ port A1 SIGNED
+ clk CLK 0
+ rst RSTA0A1 0
+ ena CEA0A1 1
+
+ set REGINPUTAB1=REGISTER
+ map GSR=GSR
+ endport
+ port A2 SIGNED
+ clk CLK 0
+ rst RSTA2A3 0
+ ena CEA2A3 1
+
+ set REGINPUTAB2=REGISTER
+ map GSR=GSR
+ endport
+ port A3 SIGNED
+ clk CLK 0
+ rst RSTA2A3 0
+ ena CEA2A3 1
+
+ set REGINPUTAB3=REGISTER
+ map GSR=GSR
+ endport
+ port B0 SIGNED
+ clk CLK 0
+ rst RSTB0B1 0
+ ena CEB0B1 1
+
+ set REGINPUTAB0=REGISTER
+ map GSR=GSR
+ endport
+ port B1 SIGNED
+ clk CLK 0
+ rst RSTB0B1 0
+ ena CEB0B1 1
+
+ set REGINPUTAB1=REGISTER
+ map GSR=GSR
+ endport
+ port B2 SIGNED
+ clk CLK 0
+ rst RSTB2B3 0
+ ena CEB2B3 1
+
+ set REGINPUTAB2=REGISTER
+ map GSR=GSR
+ endport
+ port B3 SIGNED
+ clk CLK 0
+ rst RSTB2B3 0
+ ena CEB2B3 1
+
+ set REGINPUTAB3=REGISTER
+ map GSR=GSR
+ endport
+ port C SIGNED
+ clk CLK 0
+ rst RSTC 0
+ ena CEC 1
+
+ set REGINPUTC=REGISTER
+ map GSR=GSR
+ endport
+ port Z
+ clk CLK 0
+ rst RSTOUT 0
+ ena CEOUT 1
+
+ set REGOUTPUT=REGISTER
+ map GSR=GSR
+ endport
+enddsp
+
+ff FD1P3DX
+ clk CK
+ rst CD
+ ena SP
+ d D
+ q Q
+
+ match GSR
+ set RESETMODE=ASYNC
+endff
+
+ff FD1P3IX
+ clk CK
+ rst CD
+ ena SP
+ d D
+ q Q
+
+ match GSR
+ set RESETMODE=SYNC
+endff
+
diff --git a/dsp-ff-plugin/tests/Makefile b/dsp-ff-plugin/tests/Makefile
new file mode 100644
index 0000000..305092f
--- /dev/null
+++ b/dsp-ff-plugin/tests/Makefile
@@ -0,0 +1,24 @@
+# Copyright (C) 2020-2022 The SymbiFlow Authors.
+#
+# Use of this source code is governed by a ISC-style
+# license that can be found in the LICENSE file or at
+# https://opensource.org/licenses/ISC
+#
+# SPDX-License-Identifier:ISC
+
+TESTS = \
+ nexus_mult \
+ nexus_mult_wide \
+ nexus_fftypes \
+ nexus_conn_conflict \
+ nexus_conn_share \
+ nexus_param_conflict
+
+include $(shell pwd)/../../Makefile_test.common
+
+nexus_mult_verify = true
+nexus_mult_wide_verify = true
+nexus_fftypes_verify = true
+nexus_conn_conflict_verify = true
+nexus_conn_share_verify = true
+nexus_param_conflict_verify = true
diff --git a/dsp-ff-plugin/tests/nexus_conn_conflict/nexus_conn_conflict.tcl b/dsp-ff-plugin/tests/nexus_conn_conflict/nexus_conn_conflict.tcl
new file mode 100644
index 0000000..87486f9
--- /dev/null
+++ b/dsp-ff-plugin/tests/nexus_conn_conflict/nexus_conn_conflict.tcl
@@ -0,0 +1,58 @@
+yosys -import
+if { [info procs dsp_ff] == {} } { plugin -i dsp-ff }
+yosys -import ;# ingest plugin commands
+
+read_verilog $::env(DESIGN_TOP).v
+design -save read
+
+set TOP "conflict_dsp_clk"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+equiv_opt -assert -async2sync -map +/nexus/cells_sim.v debug dsp_ff -rules ../../nexus-dsp_rules.txt
+design -load postopt
+yosys cd ${TOP}
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 9 t:FD1P3IX
+
+set TOP "conflict_ff_clk"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+debug dsp_ff -rules ../../nexus-dsp_rules.txt
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 18 t:FD1P3IX
+
+set TOP "conflict_ff_rst"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+debug dsp_ff -rules ../../nexus-dsp_rules.txt
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 18 t:FD1P3DX
+
+set TOP "conflict_ff_ena"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+debug dsp_ff -rules ../../nexus-dsp_rules.txt
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 18 t:FD1P3IX
+
+set TOP "conflict_dsp_port"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+debug dsp_ff -rules ../../nexus-dsp_rules.txt
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 9 t:FD1P3IX
diff --git a/dsp-ff-plugin/tests/nexus_conn_conflict/nexus_conn_conflict.v b/dsp-ff-plugin/tests/nexus_conn_conflict/nexus_conn_conflict.v
new file mode 100644
index 0000000..35856d9
--- /dev/null
+++ b/dsp-ff-plugin/tests/nexus_conn_conflict/nexus_conn_conflict.v
@@ -0,0 +1,151 @@
+// 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
+
+module conflict_dsp_clk (
+ input wire CLK_A,
+ input wire CLK_B,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output wire [17:0] Z
+);
+
+ reg [8:0] ra;
+ always @(posedge CLK_A)
+ ra <= A;
+
+ reg [8:0] rb;
+ always @(posedge CLK_B)
+ rb <= B;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .B (rb),
+ .Z (Z)
+ );
+
+endmodule
+
+module conflict_ff_clk (
+ input wire CLK1,
+ input wire CLK2,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output wire [17:0] Z
+);
+
+ reg [17:0] z;
+
+ always @(posedge CLK1)
+ Z[17:9] <= z[17:9];
+ always @(posedge CLK2)
+ Z[ 8:0] <= z[ 8:0];
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (A),
+ .B (B),
+ .Z (z)
+ );
+
+endmodule
+
+module conflict_ff_rst (
+ input wire CLK,
+ input wire RST1,
+ input wire RST2,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output wire [17:0] Z
+);
+
+ reg [17:0] z;
+
+ always @(posedge CLK or posedge RST1)
+ if (RST1)
+ Z[17:9] <= 0;
+ else
+ Z[17:9] <= z[17:9];
+ always @(posedge CLK or posedge RST2)
+ if (RST2)
+ Z[ 8:0] <= 0;
+ else
+ Z[ 8:0] <= z[ 8:0];
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (A),
+ .B (B),
+ .Z (z)
+ );
+
+endmodule
+
+module conflict_ff_ena (
+ input wire CLK,
+ input wire ENA1,
+ input wire ENA2,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output wire [17:0] Z
+);
+
+ reg [17:0] z;
+
+ always @(posedge CLK)
+ if (ENA1)
+ Z[17:9] <= z[17:9];
+ always @(posedge CLK)
+ if (ENA2)
+ Z[ 8:0] <= z[ 8:0];
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (A),
+ .B (B),
+ .Z (z)
+ );
+
+endmodule
+
+module conflict_dsp_port (
+ input wire CLK_A,
+ input wire [ 8:0] A,
+ input wire SA,
+ input wire [ 8:0] B,
+ output wire [17:0] Z
+);
+
+ reg [8:0] ra;
+ always @(posedge CLK_A)
+ ra <= A;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .SIGNEDA (SA),
+ .B (B),
+ .Z (Z)
+ );
+
+endmodule
diff --git a/dsp-ff-plugin/tests/nexus_conn_share/nexus_conn_share.tcl b/dsp-ff-plugin/tests/nexus_conn_share/nexus_conn_share.tcl
new file mode 100644
index 0000000..2f13e67
--- /dev/null
+++ b/dsp-ff-plugin/tests/nexus_conn_share/nexus_conn_share.tcl
@@ -0,0 +1,48 @@
+yosys -import
+if { [info procs dsp_ff] == {} } { plugin -i dsp-ff }
+yosys -import ;# ingest plugin commands
+
+read_verilog $::env(DESIGN_TOP).v
+design -save read
+
+set TOP "conflict_out_fanout"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+debug dsp_ff -rules ../../nexus-dsp_rules.txt
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 18 t:FD1P3IX
+
+set TOP "conflict_out_fanout_to_top"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+debug dsp_ff -rules ../../nexus-dsp_rules.txt
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 18 t:FD1P3IX
+
+set TOP "conflict_inp_fanout"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+debug dsp_ff -rules ../../nexus-dsp_rules.txt
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 9 t:FD1P3IX
+
+set TOP "conflict_inp_fanout_to_top"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+debug dsp_ff -rules ../../nexus-dsp_rules.txt
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 9 t:FD1P3IX
+
+
diff --git a/dsp-ff-plugin/tests/nexus_conn_share/nexus_conn_share.v b/dsp-ff-plugin/tests/nexus_conn_share/nexus_conn_share.v
new file mode 100644
index 0000000..88be6b0
--- /dev/null
+++ b/dsp-ff-plugin/tests/nexus_conn_share/nexus_conn_share.v
@@ -0,0 +1,112 @@
+// 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
+
+module conflict_out_fanout (
+ input wire CLK,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output reg [17:0] Z,
+ output wire [ 8:0] X,
+);
+
+ wire [17:0] z;
+ always @(posedge CLK)
+ Z <= z;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (A),
+ .B (B),
+ .Z (z)
+ );
+
+ assign X = ~z[8:0];
+
+endmodule
+
+module conflict_out_fanout_to_top (
+ input wire CLK,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output reg [17:0] Z,
+ output wire [ 8:0] X,
+);
+
+ wire [17:0] z;
+ always @(posedge CLK)
+ Z <= z;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (A),
+ .B (B),
+ .Z (z)
+ );
+
+ assign X = z[8:0];
+
+endmodule
+
+module conflict_inp_fanout (
+ input wire CLK,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output reg [17:0] Z,
+ output wire [ 3:0] X,
+);
+
+ wire [8:0] ra;
+ always @(posedge CLK)
+ ra <= A;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .B (B),
+ .Z (Z)
+ );
+
+ assign X = ~ra[3:0];
+
+endmodule
+
+module conflict_inp_fanout_to_top (
+ input wire CLK,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output reg [17:0] Z,
+ output wire [ 3:0] X,
+);
+
+ wire [8:0] ra;
+ always @(posedge CLK)
+ ra <= A;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .B (B),
+ .Z (Z)
+ );
+
+ assign X = ra[3:0];
+
+endmodule
+
diff --git a/dsp-ff-plugin/tests/nexus_fftypes/nexus_fftypes.tcl b/dsp-ff-plugin/tests/nexus_fftypes/nexus_fftypes.tcl
new file mode 100644
index 0000000..fa006c8
--- /dev/null
+++ b/dsp-ff-plugin/tests/nexus_fftypes/nexus_fftypes.tcl
@@ -0,0 +1,71 @@
+yosys -import
+if { [info procs dsp_ff] == {} } { plugin -i dsp-ff }
+yosys -import ;# ingest plugin commands
+
+read_verilog $::env(DESIGN_TOP).v
+design -save read
+
+set TOP "mult_ena"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+equiv_opt -assert -async2sync -map +/nexus/cells_sim.v debug dsp_ff -rules ../../nexus-dsp_rules.txt
+design -load postopt
+yosys cd ${TOP}
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 0 t:FD1P3IX
+select -assert-count 0 t:FD1P3DX
+
+set TOP "mult_arst"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+equiv_opt -assert -async2sync -map +/nexus/cells_sim.v debug dsp_ff -rules ../../nexus-dsp_rules.txt
+design -load postopt
+yosys cd ${TOP}
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 0 t:FD1P3IX
+select -assert-count 0 t:FD1P3DX
+
+set TOP "mult_arst_ena"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+equiv_opt -assert -async2sync -map +/nexus/cells_sim.v debug dsp_ff -rules ../../nexus-dsp_rules.txt
+design -load postopt
+yosys cd ${TOP}
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 0 t:FD1P3IX
+select -assert-count 0 t:FD1P3DX
+
+set TOP "mult_srst"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+equiv_opt -assert -async2sync -map +/nexus/cells_sim.v debug dsp_ff -rules ../../nexus-dsp_rules.txt
+design -load postopt
+yosys cd ${TOP}
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 0 t:FD1P3IX
+select -assert-count 0 t:FD1P3DX
+
+set TOP "mult_srst_ena"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+equiv_opt -assert -async2sync -map +/nexus/cells_sim.v debug dsp_ff -rules ../../nexus-dsp_rules.txt
+design -load postopt
+yosys cd ${TOP}
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 0 t:FD1P3IX
+select -assert-count 0 t:FD1P3DX
diff --git a/dsp-ff-plugin/tests/nexus_fftypes/nexus_fftypes.v b/dsp-ff-plugin/tests/nexus_fftypes/nexus_fftypes.v
new file mode 100644
index 0000000..2c5b8ec
--- /dev/null
+++ b/dsp-ff-plugin/tests/nexus_fftypes/nexus_fftypes.v
@@ -0,0 +1,134 @@
+// 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
+
+module mult_ena (
+ input wire CLK,
+ input wire ENA,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output wire [17:0] Z
+);
+
+ reg [8:0] ra;
+ always @(posedge CLK)
+ if (ENA) ra <= A;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .B (B),
+ .Z (Z)
+ );
+
+endmodule
+
+module mult_arst (
+ input wire CLK,
+ input wire RST,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output wire [17:0] Z
+);
+
+ reg [8:0] ra;
+ always @(posedge CLK or posedge RST)
+ if (RST) ra <= 0;
+ else ra <= A;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .B (B),
+ .Z (Z)
+ );
+
+endmodule
+
+module mult_arst_ena (
+ input wire CLK,
+ input wire RST,
+ input wire ENA,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output wire [17:0] Z
+);
+
+ reg [8:0] ra;
+ always @(posedge CLK or posedge RST)
+ if (RST) ra <= 0;
+ else if (ENA) ra <= A;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .B (B),
+ .Z (Z)
+ );
+
+endmodule
+
+module mult_srst (
+ input wire CLK,
+ input wire RST,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output wire [17:0] Z
+);
+
+ reg [8:0] ra;
+ always @(posedge CLK)
+ if (RST) ra <= 0;
+ else ra <= A;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .B (B),
+ .Z (Z)
+ );
+
+endmodule
+
+module mult_srst_ena (
+ input wire CLK,
+ input wire RST,
+ input wire ENA,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output wire [17:0] Z
+);
+
+ reg [8:0] ra;
+ always @(posedge CLK)
+ if (RST) ra <= 0;
+ else if (ENA) ra <= A;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .B (B),
+ .Z (Z)
+ );
+
+endmodule
+
diff --git a/dsp-ff-plugin/tests/nexus_mult/README.md b/dsp-ff-plugin/tests/nexus_mult/README.md
new file mode 100644
index 0000000..f72e27c
--- /dev/null
+++ b/dsp-ff-plugin/tests/nexus_mult/README.md
@@ -0,0 +1 @@
+Simple DSP register inference
diff --git a/dsp-ff-plugin/tests/nexus_mult/nexus_mult.tcl b/dsp-ff-plugin/tests/nexus_mult/nexus_mult.tcl
new file mode 100644
index 0000000..1eef04a
--- /dev/null
+++ b/dsp-ff-plugin/tests/nexus_mult/nexus_mult.tcl
@@ -0,0 +1,58 @@
+yosys -import
+if { [info procs dsp_ff] == {} } { plugin -i dsp-ff }
+yosys -import ;# ingest plugin commands
+
+read_verilog $::env(DESIGN_TOP).v
+design -save read
+
+set TOP "mult_ireg"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+equiv_opt -assert -async2sync -map +/nexus/cells_sim.v debug dsp_ff -rules ../../nexus-dsp_rules.txt
+design -load postopt
+yosys cd ${TOP}
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 0 t:FD1P3IX
+
+set TOP "mult_oreg"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+equiv_opt -assert -async2sync -map +/nexus/cells_sim.v debug dsp_ff -rules ../../nexus-dsp_rules.txt
+design -load postopt
+yosys cd ${TOP}
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 0 t:FD1P3IX
+
+set TOP "mult_all"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+equiv_opt -assert -async2sync -map +/nexus/cells_sim.v debug dsp_ff -rules ../../nexus-dsp_rules.txt
+design -load postopt
+yosys cd ${TOP}
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 0 t:FD1P3IX
+
+# The test cannot be run because the equivalence check fails at some internal
+# wires of the DSP simulation model which ends up dangling. So that's not a
+# real issue but makes the test fail.
+
+#set TOP "mult_ctrl"
+#design -load read
+#hierarchy -top ${TOP}
+#synth_nexus -flatten
+#techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+#equiv_opt -assert -async2sync -map +/nexus/cells_sim.v debug dsp_ff -rules ../../nexus-dsp_rules.txt
+#design -load postopt
+#yosys cd ${TOP}
+#stat
+#select -assert-count 1 t:MULT9X9
+#select -assert-count 0 t:FD1P3IX
diff --git a/dsp-ff-plugin/tests/nexus_mult/nexus_mult.v b/dsp-ff-plugin/tests/nexus_mult/nexus_mult.v
new file mode 100644
index 0000000..9836f7f
--- /dev/null
+++ b/dsp-ff-plugin/tests/nexus_mult/nexus_mult.v
@@ -0,0 +1,114 @@
+// 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
+
+module mult_ireg (
+ input wire CLK,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output wire [17:0] Z
+);
+
+ reg [8:0] ra;
+ always @(posedge CLK)
+ ra <= A;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .B (B),
+ .Z (Z)
+ );
+
+endmodule
+
+module mult_oreg (
+ input wire CLK,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output reg [17:0] Z
+);
+
+ reg [17:0] z;
+ always @(posedge CLK)
+ Z <= z;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (A),
+ .B (B),
+ .Z (z)
+ );
+
+endmodule
+
+module mult_all (
+ input wire CLK,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output reg [17:0] Z
+);
+
+ reg [8:0] ra;
+ always @(posedge CLK)
+ ra <= A;
+
+ reg [8:0] rb;
+ always @(posedge CLK)
+ rb <= B;
+
+ reg [17:0] z;
+ always @(posedge CLK)
+ Z <= z;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .B (rb),
+ .Z (z)
+ );
+
+endmodule
+
+module mult_ctrl (
+ input wire CLK,
+ input wire [ 8:0] A,
+ input wire SA,
+ input wire [ 8:0] B,
+ output wire [17:0] Z
+);
+
+ reg [8:0] ra;
+ reg rsa;
+
+ always @(posedge CLK) begin
+ ra <= A;
+ rsa <= SA;
+ end
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .SIGNEDA (rsa),
+ .B (B),
+ .Z (Z)
+ );
+
+endmodule
+
diff --git a/dsp-ff-plugin/tests/nexus_mult_wide/nexus_mult_wide.tcl b/dsp-ff-plugin/tests/nexus_mult_wide/nexus_mult_wide.tcl
new file mode 100644
index 0000000..70f5a61
--- /dev/null
+++ b/dsp-ff-plugin/tests/nexus_mult_wide/nexus_mult_wide.tcl
@@ -0,0 +1,18 @@
+yosys -import
+if { [info procs dsp_ff] == {} } { plugin -i dsp-ff }
+yosys -import ;# ingest plugin commands
+
+read_verilog $::env(DESIGN_TOP).v
+design -save read
+
+set TOP "mult_wide"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+equiv_opt -assert -async2sync -map +/nexus/cells_sim.v debug dsp_ff -rules ../../nexus-dsp_rules.txt
+design -load postopt
+yosys cd ${TOP}
+stat
+select -assert-count 1 t:MULTADDSUB9X9WIDE
+select -assert-count 9 t:FD1P3IX
diff --git a/dsp-ff-plugin/tests/nexus_mult_wide/nexus_mult_wide.v b/dsp-ff-plugin/tests/nexus_mult_wide/nexus_mult_wide.v
new file mode 100644
index 0000000..cb539e5
--- /dev/null
+++ b/dsp-ff-plugin/tests/nexus_mult_wide/nexus_mult_wide.v
@@ -0,0 +1,63 @@
+// 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
+
+module mult_wide (
+ input wire CLK,
+ input wire [ 8:0] A0,
+ input wire [ 8:0] A1,
+ input wire [ 8:0] A2,
+ input wire [ 8:0] A3,
+ input wire [ 8:0] B0,
+ input wire [ 8:0] B1,
+ input wire [ 8:0] B2,
+ input wire [ 8:0] B3,
+ input wire [53:0] C,
+ output wire [53:0] Z
+);
+
+ reg [8:0] ra0;
+ always @(posedge CLK)
+ ra0 <= A0;
+
+ reg [8:0] rb0;
+ always @(posedge CLK)
+ rb0 <= B0;
+
+ reg [8:0] rb2;
+ always @(posedge CLK)
+ rb2 <= B2;
+
+ MULTADDSUB9X9WIDE # (
+ .REGINPUTAB0("BYPASS"),
+ .REGINPUTAB1("BYPASS"),
+ .REGINPUTAB2("BYPASS"),
+ .REGINPUTAB3("BYPASS"),
+ .REGINPUTC("BYPASS"),
+ .REGADDSUB("BYPASS"),
+ .REGLOADC("BYPASS"),
+ .REGLOADC2("BYPASS"),
+ .REGPIPELINE("BYPASS"),
+ .REGOUTPUT("REGISTER")
+ ) mult (
+ .A0 (ra0),
+ .A1 (A1),
+ .A2 (A2),
+ .A3 (A3),
+ .B0 (rb0),
+ .B1 (B1),
+ .B2 (rb2),
+ .B3 (B3),
+ .C (C),
+ .Z (Z),
+
+ .LOADC (1'b0),
+ .ADDSUB (4'hF),
+ .SIGNED (1'b1),
+ );
+
+endmodule
diff --git a/dsp-ff-plugin/tests/nexus_param_conflict/nexus_param_conflict.tcl b/dsp-ff-plugin/tests/nexus_param_conflict/nexus_param_conflict.tcl
new file mode 100644
index 0000000..ff46e0b
--- /dev/null
+++ b/dsp-ff-plugin/tests/nexus_param_conflict/nexus_param_conflict.tcl
@@ -0,0 +1,40 @@
+yosys -import
+if { [info procs dsp_ff] == {} } { plugin -i dsp-ff }
+yosys -import ;# ingest plugin commands
+
+read_verilog $::env(DESIGN_TOP).v
+design -save read
+
+set TOP "conflict_dsp_ctrl_param"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+debug dsp_ff -rules ../../nexus-dsp_rules.txt
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 18 t:FD1P3IX
+
+set TOP "conflict_dsp_common_param"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+equiv_opt -assert -async2sync -map +/nexus/cells_sim.v debug dsp_ff -rules ../../nexus-dsp_rules.txt
+design -load postopt
+yosys cd ${TOP}
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 9 t:FD1P3IX t:FD1P3DX %u
+
+set TOP "conflict_ff_param"
+design -load read
+hierarchy -top ${TOP}
+synth_nexus -flatten
+techmap -map +/nexus/cells_sim.v t:VLO t:VHI %u ;# Unmap VHI and VLO
+debug dsp_ff -rules ../../nexus-dsp_rules.txt
+stat
+select -assert-count 1 t:MULT9X9
+select -assert-count 4 t:FD1P3IX
+select -assert-count 5 t:FD1P3DX
+
diff --git a/dsp-ff-plugin/tests/nexus_param_conflict/nexus_param_conflict.v b/dsp-ff-plugin/tests/nexus_param_conflict/nexus_param_conflict.v
new file mode 100644
index 0000000..d9ec0e8
--- /dev/null
+++ b/dsp-ff-plugin/tests/nexus_param_conflict/nexus_param_conflict.v
@@ -0,0 +1,90 @@
+// 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
+
+module conflict_dsp_ctrl_param (
+ input wire CLK,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output reg [17:0] Z,
+);
+
+ wire [17:0] z;
+ always @(posedge CLK)
+ Z <= z;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("REGISTER")
+ ) mult (
+ .A (A),
+ .B (B),
+ .Z (z)
+ );
+
+endmodule
+
+module conflict_dsp_common_param (
+ input wire CLK,
+ input wire RST,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output wire [17:0] Z,
+);
+
+ wire [8:0] ra;
+ always @(posedge CLK or posedge RST)
+ if (RST) ra <= 0;
+ else ra <= A;
+
+ wire [8:0] rb;
+ always @(posedge CLK)
+ if (RST) rb <= 0;
+ else rb <= B;
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .B (rb),
+ .Z (Z)
+ );
+
+endmodule
+
+module conflict_ff_param (
+ input wire CLK,
+ input wire RST,
+ input wire [ 8:0] A,
+ input wire [ 8:0] B,
+ output wire [17:0] Z,
+);
+
+ wire [8:0] ra;
+ always @(posedge CLK or posedge RST)
+ if (RST) ra[8:4] <= 0;
+ else ra[8:4] <= A[8:4];
+
+ always @(posedge CLK)
+ if (RST) ra[3:0] <= 0;
+ else ra[3:0] <= A[3:0];
+
+ MULT9X9 # (
+ .REGINPUTA("BYPASS"),
+ .REGINPUTB("BYPASS"),
+ .REGOUTPUT("BYPASS")
+ ) mult (
+ .A (ra),
+ .B (B),
+ .Z (Z)
+ );
+
+endmodule
+
diff --git a/ql-qlf-plugin/synth_quicklogic.cc b/ql-qlf-plugin/synth_quicklogic.cc
index d115709..01ce6ef 100644
--- a/ql-qlf-plugin/synth_quicklogic.cc
+++ b/ql-qlf-plugin/synth_quicklogic.cc
@@ -221,6 +221,8 @@
std::string noDFFArgs;
if (family == "qlf_k4n8") {
noDFFArgs = " -nodffe -nosdff";
+ } else if (family == "qlf_k6n10f") {
+ noDFFArgs = " -nosdff";
}
if (check_label("coarse")) {
diff --git a/uhdm-plugin/Makefile b/uhdm-plugin/Makefile
index 0ad9ee0..223a126 100644
--- a/uhdm-plugin/Makefile
+++ b/uhdm-plugin/Makefile
@@ -9,6 +9,7 @@
NAME = uhdm
SOURCES = UhdmAst.cc \
uhdmastfrontend.cc \
+ uhdmcommonfrontend.cc \
uhdmsurelogastfrontend.cc \
uhdmastreport.cc
@@ -16,7 +17,7 @@
CPPFLAGS += -std=c++17 -Wall -W -Wextra -Werror \
-I${UHDM_INSTALL_DIR}/include \
- -I${UHDM_INSTALL_DIR}/include/surelog
+ -I${UHDM_INSTALL_DIR}/include/Surelog
CXXFLAGS += -Wno-unused-parameter
LDFLAGS += -L${UHDM_INSTALL_DIR}/lib/uhdm -L${UHDM_INSTALL_DIR}/lib/surelog -L${UHDM_INSTALL_DIR}/lib -L${UHDM_INSTALL_DIR}/lib64/uhdm -L${UHDM_INSTALL_DIR}/lib64/surelog -L${UHDM_INSTALL_DIR}/lib64
diff --git a/uhdm-plugin/Makefile.inc b/uhdm-plugin/Makefile.inc
deleted file mode 100644
index ddd8e8f..0000000
--- a/uhdm-plugin/Makefile.inc
+++ /dev/null
@@ -1,15 +0,0 @@
-
-OBJS += frontends/uhdm/UhdmAst.o
-OBJS += frontends/uhdm/uhdmastreport.o
-OBJS += frontends/uhdm/uhdmastfrontend.o
-OBJS += frontends/uhdm/vpivisitor.o
-
-UHDM_INSTALL_DIR ?= $(PREFIX)
-
-#*** UHDM ***
-CPPFLAGS += -std=c++14 -I${UHDM_INSTALL_DIR}/include/uhdm \
- -I${UHDM_INSTALL_DIR}/include/uhdm/include \
- -I${UHDM_INSTALL_DIR}/include/uhdm/headers
-CXXFLAGS += -Wno-inconsistent-missing-override
-LDFLAGS += -L${UHDM_INSTALL_DIR}/lib/uhdm -L${UHDM_INSTALL_DIR}/lib
-LDLIBS += -luhdm -lcapnp -lkj -ldl -lutil -lm -lrt -lpthread
diff --git a/uhdm-plugin/UhdmAst.cc b/uhdm-plugin/UhdmAst.cc
index b6b541b..c9dc82d 100644
--- a/uhdm-plugin/UhdmAst.cc
+++ b/uhdm-plugin/UhdmAst.cc
@@ -1,6 +1,7 @@
#include <algorithm>
#include <cstring>
#include <functional>
+#include <regex>
#include <string>
#include <vector>
@@ -105,6 +106,24 @@
return range;
}
+static void copy_packed_unpacked_attribute(AST::AstNode *from, AST::AstNode *to)
+{
+ if (!to->attributes.count(UhdmAst::packed_ranges()))
+ to->attributes[UhdmAst::packed_ranges()] = AST::AstNode::mkconst_int(1, false, 1);
+ if (!to->attributes.count(UhdmAst::unpacked_ranges()))
+ to->attributes[UhdmAst::unpacked_ranges()] = AST::AstNode::mkconst_int(1, false, 1);
+ if (from->attributes.count(UhdmAst::packed_ranges())) {
+ for (auto r : from->attributes[UhdmAst::packed_ranges()]->children) {
+ to->attributes[UhdmAst::packed_ranges()]->children.push_back(r->clone());
+ }
+ }
+ if (from->attributes.count(UhdmAst::unpacked_ranges())) {
+ for (auto r : from->attributes[UhdmAst::unpacked_ranges()]->children) {
+ to->attributes[UhdmAst::unpacked_ranges()]->children.push_back(r->clone());
+ }
+ }
+}
+
#include "UhdmAstUpstream.cc"
static int get_max_offset_struct(AST::AstNode *node)
@@ -554,10 +573,10 @@
} else {
expanded->children[1] = new AST::AstNode(
AST::AST_ADD, expanded->children[1],
- new AST::AstNode(AST::AST_MUL, AST::AstNode::mkconst_int(struct_size_int, true, 32), AST::AstNode::mkconst_int(range, true, 32)));
+ new AST::AstNode(AST::AST_MUL, AST::AstNode::mkconst_int(struct_size_int, true, 32), node->children[0]->children[0]->clone()));
expanded->children[0] = new AST::AstNode(
AST::AST_ADD, expanded->children[0],
- new AST::AstNode(AST::AST_MUL, AST::AstNode::mkconst_int(struct_size_int, true, 32), AST::AstNode::mkconst_int(range, true, 32)));
+ new AST::AstNode(AST::AST_MUL, AST::AstNode::mkconst_int(struct_size_int, true, 32), node->children[0]->children[0]->clone()));
}
}
return expanded;
@@ -765,7 +784,8 @@
wnode->is_signed = template_node->is_signed;
int offset = get_max_offset_struct(template_node);
auto range = make_range(offset, 0);
- wnode->children.push_back(range);
+ copy_packed_unpacked_attribute(template_node, wnode);
+ wnode->attributes[UhdmAst::packed_ranges()]->children.insert(wnode->attributes[UhdmAst::packed_ranges()]->children.begin(), range);
// make sure this node is the one in scope for this name
AST_INTERNAL::current_scope[name] = wnode;
// add all the struct members to scope under the wire's name
@@ -773,6 +793,40 @@
return wnode;
}
+static void simplify_format_string(AST::AstNode *current_node)
+{
+ std::string sformat = current_node->children[0]->str;
+ std::string preformatted_string = "";
+ int next_arg = 1;
+ for (size_t i = 0; i < sformat.length(); i++) {
+ if (sformat[i] == '%') {
+ AST::AstNode *node_arg = current_node->children[next_arg];
+ char cformat = sformat[++i];
+ if (cformat == 'b' or cformat == 'B') {
+ node_arg->simplify(true, false, false, 1, -1, false, false);
+ if (node_arg->type != AST::AST_CONSTANT)
+ log_file_error(current_node->filename, current_node->location.first_line,
+ "Failed to evaluate system task `%s' with non-constant argument.\n", current_node->str.c_str());
+
+ RTLIL::Const val = node_arg->bitsAsConst();
+ for (int j = val.size() - 1; j >= 0; j--) {
+ // We add ACII value of 0 to convert number to character
+ preformatted_string += ('0' + val[j]);
+ }
+ delete current_node->children[next_arg];
+ current_node->children.erase(current_node->children.begin() + next_arg);
+ } else {
+ next_arg++;
+ preformatted_string += std::string("%") + cformat;
+ }
+ } else {
+ preformatted_string += sformat[i];
+ }
+ }
+ delete current_node->children[0];
+ current_node->children[0] = AST::AstNode::mkconst_str(preformatted_string);
+}
+
static void simplify(AST::AstNode *current_node, AST::AstNode *parent_node)
{
AST::AstNode *expanded = nullptr;
@@ -862,6 +916,7 @@
if (!current_node->str.empty() && current_node->str[0] == '\\') {
// instance so add a wire for the packed structure
auto wnode = make_packed_struct_local(current_node, current_node->str);
+ convert_packed_unpacked_range(wnode);
log_assert(AST_INTERNAL::current_ast_mod);
AST_INTERNAL::current_ast_mod->children.push_back(wnode);
AST_INTERNAL::current_scope[wnode->str]->attributes[ID::wiretype] = AST::AstNode::mkconst_str(current_node->str);
@@ -876,6 +931,10 @@
while (current_node->simplify(true, false, false, 1, -1, false, false)) {
};
break;
+ case AST::AST_TCALL:
+ if (current_node->str == "$display" || current_node->str == "$write")
+ simplify_format_string(current_node);
+ break;
default:
break;
}
@@ -1023,8 +1082,8 @@
if (size == 64) {
size = 32;
}
- auto c = AST::AstNode::mkconst_int(val.value.integer, true, size ? size : 32);
- if (size == 0)
+ auto c = AST::AstNode::mkconst_int(val.value.integer, true, size > 0 ? size : 32);
+ if (size == 0 || size == -1)
c->is_unsized = true;
return c;
}
@@ -1340,8 +1399,6 @@
visitEachDescendant(module_node, [&](AST::AstNode *current_scope_node) {
if (current_scope_node->type == AST::AST_TYPEDEF || current_scope_node->type == AST::AST_PARAMETER ||
current_scope_node->type == AST::AST_LOCALPARAM) {
- if (current_scope_node->type == AST::AST_TYPEDEF)
- simplify(current_scope_node, nullptr);
AST_INTERNAL::current_scope[current_scope_node->str] = current_scope_node;
}
});
@@ -1761,6 +1818,7 @@
if (node->str.empty()) {
// anonymous typespec, move the children to variable
current_node->type = node->type;
+ copy_packed_unpacked_attribute(node, current_node);
current_node->children = std::move(node->children);
} else {
auto wiretype_node = new AST::AstNode(AST::AST_WIRETYPE);
@@ -2070,7 +2128,7 @@
if (net_type == vpiLogicNet) {
current_node->is_logic = true;
current_node->is_signed = vpi_get(vpiSigned, net_h);
- visit_range(net_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); });
+ visit_one_to_many({vpiRange}, net_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); });
shared.report.mark_handled(net_h);
} else if (net_type == vpiStructNet) {
visit_one_to_one({vpiTypespec}, net_h, [&](AST::AstNode *node) {
@@ -2173,7 +2231,7 @@
visit_one_to_one({vpiExpr}, obj_h, [&](AST::AstNode *node) { current_node = node; });
if (current_node == nullptr) {
current_node = make_ast_node(AST::AST_MODPORTMEMBER);
- visit_one_to_many({vpiRange}, obj_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); });
+ visit_one_to_many({vpiRange}, obj_h, [&](AST::AstNode *node) { unpacked_ranges.push_back(node); });
}
visit_one_to_one({vpiTypedef}, obj_h, [&](AST::AstNode *node) {
if (node) {
@@ -3134,14 +3192,13 @@
return;
}
+ std::string task_calls[] = {"\\$display", "\\$monitor", "\\$write", "\\$time", "\\$readmemh", "\\$readmemb", "\\$finish", "\\$stop"};
+
if (current_node->str == "\\$signed") {
current_node->type = AST::AST_TO_SIGNED;
} else if (current_node->str == "\\$unsigned") {
current_node->type = AST::AST_TO_UNSIGNED;
- } else if (current_node->str == "\\$display" || current_node->str == "\\$time" || current_node->str == "\\$monitor") {
- current_node->type = AST::AST_TCALL;
- current_node->str = current_node->str.substr(1);
- } else if (current_node->str == "\\$readmemh") {
+ } else if (std::find(std::begin(task_calls), std::end(task_calls), current_node->str) != std::end(task_calls)) {
current_node->type = AST::AST_TCALL;
}
@@ -3150,6 +3207,19 @@
current_node->children.push_back(node);
}
});
+
+ if (current_node->str == "\\$display" || current_node->str == "\\$write") {
+ // According to standard, %h and %x mean the same, but %h is currently unsupported by mainline yosys
+ std::string replaced_string = std::regex_replace(current_node->children[0]->str, std::regex("%[h|H]"), "%x");
+ delete current_node->children[0];
+ current_node->children[0] = AST::AstNode::mkconst_str(replaced_string);
+ }
+
+ std::string remove_backslash[] = {"\\$display", "\\$strobe", "\\$write", "\\$monitor", "\\$time", "\\$finish",
+ "\\$stop", "\\$dumpfile", "\\$dumpvars", "\\$dumpon", "\\$dumpoff", "\\$dumpall"};
+
+ if (std::find(std::begin(remove_backslash), std::end(remove_backslash), current_node->str) != std::end(remove_backslash))
+ current_node->str = current_node->str.substr(1);
}
void UhdmAst::process_func_call()
diff --git a/uhdm-plugin/uhdmastfrontend.cc b/uhdm-plugin/uhdmastfrontend.cc
index c857076..9a22339 100644
--- a/uhdm-plugin/uhdmastfrontend.cc
+++ b/uhdm-plugin/uhdmastfrontend.cc
@@ -19,9 +19,7 @@
*
*/
-#include "UhdmAst.h"
-#include "frontends/ast/ast.h"
-#include "kernel/yosys.h"
+#include "uhdmcommonfrontend.h"
namespace UHDM
{
@@ -31,15 +29,9 @@
YOSYS_NAMESPACE_BEGIN
-/* Stub for AST::process */
-static void set_line_num(int) {}
-
-/* Stub for AST::process */
-static int get_line_num(void) { return 1; }
-
-struct UhdmAstFrontend : public Frontend {
- UhdmAstFrontend() : Frontend("uhdm", "read UHDM file") {}
- void help()
+struct UhdmAstFrontend : public UhdmCommonFrontend {
+ UhdmAstFrontend() : UhdmCommonFrontend("uhdm", "read UHDM file") {}
+ void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
@@ -47,70 +39,27 @@
log("\n");
log("Load design from a UHDM file into the current design\n");
log("\n");
- log(" -noassert\n");
- log(" ignore assert() statements");
- log("\n");
- log(" -debug\n");
- log(" print debug info to stdout");
- log("\n");
- log(" -report [directory]\n");
- log(" write a coverage report for the UHDM file\n");
- log("\n");
- log(" -defer\n");
- log(" only read the abstract syntax tree and defer actual compilation\n");
- log(" to a later 'hierarchy' command. Useful in cases where the default\n");
- log(" parameters of modules yield invalid or not synthesizable code.\n");
- log("\n");
+ this->print_read_options();
}
- void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design)
+ AST::AstNode *parse(std::string filename) override
{
- log_header(design, "Executing UHDM frontend.\n");
-
- UhdmAstShared shared;
- UhdmAst uhdm_ast(shared);
- bool defer = false;
-
- std::string report_directory;
- for (size_t i = 1; i < args.size(); i++) {
- if (args[i] == "-debug") {
- shared.debug_flag = true;
- } else if (args[i] == "-report" && ++i < args.size()) {
- report_directory = args[i];
- shared.stop_on_error = false;
- } else if (args[i] == "-noassert") {
- shared.no_assert = true;
- } else if (args[i] == "-defer") {
- defer = true;
- }
- }
- extra_args(f, filename, args, args.size() - 1);
-
- AST::current_filename = filename;
- AST::set_line_num = &set_line_num;
- AST::get_line_num = &get_line_num;
- struct AST::AstNode *current_ast;
-
UHDM::Serializer serializer;
std::vector<vpiHandle> restoredDesigns = serializer.Restore(filename);
for (auto design : restoredDesigns) {
std::stringstream strstr;
- UHDM::visit_object(design, 1, "", &shared.report.unhandled, shared.debug_flag ? std::cout : strstr);
+ UHDM::visit_object(design, 1, "", &this->shared.report.unhandled, this->shared.debug_flag ? std::cout : strstr);
}
- current_ast = uhdm_ast.visit_designs(restoredDesigns);
- if (!report_directory.empty()) {
- shared.report.write(report_directory);
+ UhdmAst uhdm_ast(this->shared);
+ AST::AstNode *current_ast = uhdm_ast.visit_designs(restoredDesigns);
+ if (!this->report_directory.empty()) {
+ this->shared.report.write(this->report_directory);
}
for (auto design : restoredDesigns)
vpi_release_handle(design);
- bool dump_ast1 = shared.debug_flag;
- bool dump_ast2 = shared.debug_flag;
- bool dont_redefine = false;
- bool default_nettype_wire = true;
- AST::process(design, current_ast, dump_ast1, dump_ast2, false, false, false, false, false, false, false, false, false, false, false, false,
- false, false, dont_redefine, false, defer, default_nettype_wire);
- delete current_ast;
+ return current_ast;
}
+ void call_log_header(RTLIL::Design *design) override { log_header(design, "Executing UHDM frontend.\n"); }
} UhdmAstFrontend;
YOSYS_NAMESPACE_END
diff --git a/uhdm-plugin/uhdmcommonfrontend.cc b/uhdm-plugin/uhdmcommonfrontend.cc
new file mode 100644
index 0000000..a1536d4
--- /dev/null
+++ b/uhdm-plugin/uhdmcommonfrontend.cc
@@ -0,0 +1,130 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 Antmicro
+
+ * Based on frontends/json/jsonparse.cc
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include "uhdmcommonfrontend.h"
+
+YOSYS_NAMESPACE_BEGIN
+
+/* Stub for AST::process */
+static void set_line_num(int) {}
+
+/* Stub for AST::process */
+static int get_line_num(void) { return 1; }
+
+void UhdmCommonFrontend::print_read_options()
+{
+ log(" -noassert\n");
+ log(" ignore assert() statements");
+ log("\n");
+ log(" -debug\n");
+ log(" alias for -dump_ast1 -dump_ast2 -dump_vlog1 -dump_vlog2 -yydebug\n");
+ log("\n");
+ log(" -dump_ast1\n");
+ log(" dump abstract syntax tree (before simplification)\n");
+ log("\n");
+ log(" -dump_ast2\n");
+ log(" dump abstract syntax tree (after simplification)\n");
+ log("\n");
+ log(" -no_dump_ptr\n");
+ log(" do not include hex memory addresses in dump (easier to diff dumps)\n");
+ log("\n");
+ log(" -dump_vlog1\n");
+ log(" dump ast as Verilog code (before simplification)\n");
+ log("\n");
+ log(" -dump_vlog2\n");
+ log(" dump ast as Verilog code (after simplification)\n");
+ log("\n");
+ log(" -dump_rtlil\n");
+ log(" dump generated RTLIL netlist\n");
+ log("\n");
+ log(" -yydebug\n");
+ log(" enable parser debug output\n");
+ log("\n");
+ log(" -report [directory]\n");
+ log(" write a coverage report for the UHDM file\n");
+ log("\n");
+ log(" -defer\n");
+ log(" only read the abstract syntax tree and defer actual compilation\n");
+ log(" to a later 'hierarchy' command. Useful in cases where the default\n");
+ log(" parameters of modules yield invalid or not synthesizable code.\n");
+ log("\n");
+}
+
+void UhdmCommonFrontend::execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design)
+{
+ this->call_log_header(design);
+ this->args = args;
+
+ bool defer = false;
+ bool dump_ast1 = false;
+ bool dump_ast2 = false;
+ bool dump_vlog1 = false;
+ bool dump_vlog2 = false;
+ bool no_dump_ptr = false;
+ bool dump_rtlil = false;
+
+ for (size_t i = 1; i < args.size(); i++) {
+ if (args[i] == "-debug") {
+ dump_ast1 = true;
+ dump_ast2 = true;
+ dump_vlog1 = true;
+ dump_vlog2 = true;
+ this->shared.debug_flag = true;
+ } else if (args[i] == "-report" && ++i < args.size()) {
+ this->report_directory = args[i];
+ this->shared.stop_on_error = false;
+ } else if (args[i] == "-noassert") {
+ this->shared.no_assert = true;
+ } else if (args[i] == "-defer") {
+ defer = true;
+ } else if (args[i] == "-dump_ast1") {
+ dump_ast1 = true;
+ } else if (args[i] == "-dump_ast2") {
+ dump_ast2 = true;
+ } else if (args[i] == "-dump_vlog1") {
+ dump_vlog1 = true;
+ } else if (args[i] == "-dump_vlog2") {
+ dump_vlog2 = true;
+ } else if (args[i] == "-no_dump_ptr") {
+ no_dump_ptr = true;
+ } else if (args[i] == "-dump_rtlil") {
+ dump_rtlil = true;
+ } else if (args[i] == "-yydebug") {
+ this->shared.debug_flag = true;
+ }
+ }
+ extra_args(f, filename, args, args.size() - 1);
+
+ AST::current_filename = filename;
+ AST::set_line_num = &set_line_num;
+ AST::get_line_num = &get_line_num;
+
+ bool dont_redefine = false;
+ bool default_nettype_wire = true;
+
+ AST::AstNode *current_ast = parse(filename);
+
+ AST::process(design, current_ast, dump_ast1, dump_ast2, no_dump_ptr, dump_vlog1, dump_vlog2, dump_rtlil, false, false, false, false, false, false,
+ false, false, false, false, dont_redefine, false, defer, default_nettype_wire);
+ delete current_ast;
+}
+
+YOSYS_NAMESPACE_END
diff --git a/uhdm-plugin/uhdmcommonfrontend.h b/uhdm-plugin/uhdmcommonfrontend.h
new file mode 100644
index 0000000..f2d12a8
--- /dev/null
+++ b/uhdm-plugin/uhdmcommonfrontend.h
@@ -0,0 +1,42 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 Antmicro
+
+ * Based on frontends/json/jsonparse.cc
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include "UhdmAst.h"
+#include "frontends/ast/ast.h"
+#include "kernel/yosys.h"
+#include <string>
+#include <vector>
+
+YOSYS_NAMESPACE_BEGIN
+
+struct UhdmCommonFrontend : public Frontend {
+ UhdmAstShared shared;
+ std::string report_directory;
+ std::vector<std::string> args;
+ UhdmCommonFrontend(std::string name, std::string short_help) : Frontend(name, short_help) {}
+ virtual void print_read_options();
+ virtual void help() = 0;
+ virtual AST::AstNode *parse(std::string filename) = 0;
+ virtual void call_log_header(RTLIL::Design *design) = 0;
+ void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design);
+};
+
+YOSYS_NAMESPACE_END
diff --git a/uhdm-plugin/uhdmsurelogastfrontend.cc b/uhdm-plugin/uhdmsurelogastfrontend.cc
index 7bfd53d..ce79772 100644
--- a/uhdm-plugin/uhdmsurelogastfrontend.cc
+++ b/uhdm-plugin/uhdmsurelogastfrontend.cc
@@ -22,6 +22,7 @@
#include "UhdmAst.h"
#include "frontends/ast/ast.h"
#include "kernel/yosys.h"
+#include "uhdmcommonfrontend.h"
#if defined(_MSC_VER)
#include <direct.h>
@@ -31,8 +32,8 @@
#include <unistd.h>
#endif
-#include "surelog/ErrorReporting/Report.h"
-#include "surelog/surelog.h"
+#include "ErrorReporting/Report.h"
+#include "surelog.h"
namespace UHDM
{
@@ -76,9 +77,16 @@
return the_design;
}
-struct UhdmSurelogAstFrontend : public Frontend {
- UhdmSurelogAstFrontend() : Frontend("verilog_with_uhdm", "generate/read UHDM file") {}
- void help()
+struct UhdmSurelogAstFrontend : public UhdmCommonFrontend {
+ UhdmSurelogAstFrontend() : UhdmCommonFrontend("verilog_with_uhdm", "generate/read UHDM file") {}
+ void print_read_options() override
+ {
+ log(" -process\n");
+ log(" loads design from given UHDM file\n");
+ log("\n");
+ UhdmCommonFrontend::print_read_options();
+ }
+ void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
@@ -86,56 +94,14 @@
log("\n");
log("Generate or load design from a UHDM file into the current design\n");
log("\n");
- log(" -process\n");
- log(" loads design from given UHDM file\n");
- log("\n");
- log(" -noassert\n");
- log(" ignore assert() statements");
- log("\n");
- log(" -debug\n");
- log(" print debug info to stdout");
- log("\n");
- log(" -report [directory]\n");
- log(" write a coverage report for the UHDM file\n");
- log("\n");
- log(" -defer\n");
- log(" only read the abstract syntax tree and defer actual compilation\n");
- log(" to a later 'hierarchy' command. Useful in cases where the default\n");
- log(" parameters of modules yield invalid or not synthesizable code.\n");
- log("\n");
+ this->print_read_options();
}
- void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design)
+ AST::AstNode *parse(std::string filename) override
{
- log_header(design, "Executing Verilog with UHDM frontend.\n");
-
- UhdmAstShared shared;
- UhdmAst uhdm_ast(shared);
- bool defer = false;
-
- std::string report_directory;
- auto it = args.begin();
- while (it != args.end()) {
- if (*it == "-debug") {
- shared.debug_flag = true;
- it = args.erase(it);
- } else if (*it == "-report" && (it = args.erase(it)) < args.end()) {
- report_directory = *it;
- shared.stop_on_error = false;
- it = args.erase(it);
- } else if (*it == "-noassert") {
- shared.no_assert = true;
- it = args.erase(it);
- } else if (*it == "-defer") {
- defer = true;
- it = args.erase(it);
- } else {
- ++it;
- }
- }
std::vector<const char *> cstrings;
- cstrings.reserve(args.size());
- for (size_t i = 0; i < args.size(); ++i)
- cstrings.push_back(const_cast<char *>(args[i].c_str()));
+ cstrings.reserve(this->args.size());
+ for (size_t i = 0; i < this->args.size(); ++i)
+ cstrings.push_back(const_cast<char *>(this->args[i].c_str()));
SURELOG::SymbolTable *symbolTable = new SURELOG::SymbolTable();
SURELOG::ErrorContainer *errors = new SURELOG::ErrorContainer(symbolTable);
@@ -146,22 +112,21 @@
}
SURELOG::scompiler *compiler = nullptr;
const std::vector<vpiHandle> uhdm_design = executeCompilation(symbolTable, errors, clp, compiler);
- struct AST::AstNode *current_ast = uhdm_ast.visit_designs(uhdm_design);
- if (report_directory != "") {
- shared.report.write(report_directory);
- }
- bool dump_ast1 = shared.debug_flag;
- bool dump_ast2 = shared.debug_flag;
- bool dont_redefine = false;
- bool default_nettype_wire = true;
- AST::process(design, current_ast, dump_ast1, dump_ast2, false, false, false, false, false, false, false, false, false, false, false, false,
- false, false, dont_redefine, false, defer, default_nettype_wire);
- delete current_ast;
+
SURELOG::shutdown_compiler(compiler);
delete clp;
delete symbolTable;
delete errors;
+
+ UhdmAst uhdm_ast(this->shared);
+ AST::AstNode *current_ast = uhdm_ast.visit_designs(uhdm_design);
+ if (report_directory != "") {
+ shared.report.write(report_directory);
+ }
+
+ return current_ast;
}
+ void call_log_header(RTLIL::Design *design) override { log_header(design, "Executing Verilog with UHDM frontend.\n"); }
} UhdmSurelogAstFrontend;
YOSYS_NAMESPACE_END