Merge pull request #12 from antmicro/ql-ios
A plugin for heterogeneous IOBs for QuickLogic FPGAs
diff --git a/.gitignore b/.gitignore
index 1f63fcd..4c85aa2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
*.d
-*.so
*.o
+*.so
*.swp
*.log
diff --git a/Makefile b/Makefile
index 6e74158..72611b4 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-PLUGIN_LIST := fasm xdc params selection sdc get_count
+PLUGIN_LIST := fasm xdc params selection sdc get_count ql-iob
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/ql-iob-plugin/Makefile b/ql-iob-plugin/Makefile
new file mode 100644
index 0000000..636ebfe
--- /dev/null
+++ b/ql-iob-plugin/Makefile
@@ -0,0 +1,24 @@
+CXX = $(shell yosys-config --cxx)
+CXXFLAGS = $(shell yosys-config --cxxflags)
+LDFLAGS = $(shell yosys-config --ldflags)
+LDLIBS = $(shell yosys-config --ldlibs)
+PLUGINS_DIR = $(shell yosys-config --datdir)/plugins
+
+NAME = ql-iob
+OBJS = $(NAME).o pcf_parser.cc pinmap_parser.cc
+
+$(NAME).so: $(OBJS)
+ $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS)
+
+.PHONY: install test
+
+install: $(NAME).so
+ mkdir -p $(PLUGINS_DIR)
+ cp $(NAME).so $(PLUGINS_DIR)/$(NAME).so
+
+test: $(NAME).so
+ $(MAKE) -C tests all
+
+clean:
+ rm -f *.d *.o *.so
+ $(MAKE) -C tests clean
diff --git a/ql-iob-plugin/README.md b/ql-iob-plugin/README.md
new file mode 100644
index 0000000..8e3e85a
--- /dev/null
+++ b/ql-iob-plugin/README.md
@@ -0,0 +1,11 @@
+# QuickLogic IO buffer plugin
+
+This plugin allows to annotate IO buffer cells with information from IO placement constraints. This is required to determine at the netlist level what types of IO buffers have to be used where.
+
+The plugin reads IO constraints from a PCF file and a board pinmap from a pinmap CSV file. The pinmap file should contain the followin columns: `name`, `x`, `y` and `type` (optional). Basing on this information each IO cell has the following parameters set:
+
+- IO_PAD - Name of the IO pad,
+- IO_LOC - Location of the IO pad (eg. "X10Y20"),
+- IO_TYPE - Type of the IO buffer (to be used inside techmap).
+
+See the plugin's help for more details.
diff --git a/ql-iob-plugin/pcf_parser.cc b/ql-iob-plugin/pcf_parser.cc
new file mode 100644
index 0000000..c7a9309
--- /dev/null
+++ b/ql-iob-plugin/pcf_parser.cc
@@ -0,0 +1,72 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2020 The Symbiflow Authors
+ *
+ * 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 "pcf_parser.hh"
+
+#include <regex>
+
+// ============================================================================
+
+bool PcfParser::parse (const std::string& a_FileName) {
+
+ // Open the file
+ std::ifstream file(a_FileName.c_str());
+
+ // Parse it
+ return parse(file);
+}
+
+const std::vector<PcfParser::Constraint> PcfParser::getConstraints () const {
+ return m_Constraints;
+}
+
+
+// ============================================================================
+
+bool PcfParser::parse (std::ifstream& a_Stream) {
+
+ if (!a_Stream.good()) {
+ return false;
+ }
+
+ // Clear constraints
+ m_Constraints.clear();
+
+ // Parse PCF lines
+ std::regex re("^\\s*set_io\\s+([^#\\s]+)\\s+([^#\\s]+)(?:\\s+#(.*))?");
+
+ while (a_Stream.good()) {
+ std::string line;
+ std::getline(a_Stream, line);
+
+ // Match against regex
+ std::cmatch cm;
+ if (std::regex_match(line.c_str(), cm, re)) {
+ m_Constraints.push_back(
+ Constraint(
+ cm[1].str(),
+ cm[2].str(),
+ cm[3].str()
+ )
+ );
+ }
+ }
+
+ return true;
+}
diff --git a/ql-iob-plugin/pcf_parser.hh b/ql-iob-plugin/pcf_parser.hh
new file mode 100644
index 0000000..8a4a920
--- /dev/null
+++ b/ql-iob-plugin/pcf_parser.hh
@@ -0,0 +1,65 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2020 The Symbiflow Authors
+ *
+ * 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.
+ *
+ */
+#ifndef PCF_PARSER_HH
+#define PCF_PARSER_HH
+
+#include <fstream>
+#include <string>
+#include <vector>
+
+// ============================================================================
+
+class PcfParser {
+public:
+
+ /// A constraint
+ struct Constraint {
+
+ const std::string netName;
+ const std::string padName;
+ const std::string comment;
+
+ Constraint () = default;
+
+ Constraint (
+ const std::string& a_NetName,
+ const std::string& a_PadName,
+ const std::string& a_Comment = std::string()
+ ) : netName(a_NetName), padName(a_PadName), comment(a_Comment) {}
+ };
+
+ /// Constructor
+ PcfParser () = default;
+
+ /// Parses a PCF file and stores constraint within the class instance.
+ /// Returns false in case of error
+ bool parse (const std::string& a_FileName);
+ bool parse (std::ifstream& a_Stream);
+
+ /// Returns the constraint list
+ const std::vector<Constraint> getConstraints () const;
+
+private:
+
+ /// A list of constraints
+ std::vector<Constraint> m_Constraints;
+};
+
+#endif // PCF_PARSER_HH
diff --git a/ql-iob-plugin/pinmap_parser.cc b/ql-iob-plugin/pinmap_parser.cc
new file mode 100644
index 0000000..77e3662
--- /dev/null
+++ b/ql-iob-plugin/pinmap_parser.cc
@@ -0,0 +1,121 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2020 The Symbiflow Authors
+ *
+ * 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 "pinmap_parser.hh"
+
+#include <sstream>
+
+// ============================================================================
+
+bool PinmapParser::parse (const std::string& a_FileName) {
+
+ // Open the file
+ std::ifstream file(a_FileName.c_str());
+
+ // Parse it
+ return parse(file);
+}
+
+const std::vector<PinmapParser::Entry> PinmapParser::getEntries() const {
+ return m_Entries;
+}
+
+// ============================================================================
+
+std::vector<std::string> PinmapParser::getFields (const std::string& a_String) {
+
+ std::vector<std::string> fields;
+ std::stringstream ss(a_String);
+
+ while (ss.good()) {
+ std::string field;
+ std::getline(ss, field, ',');
+
+ fields.push_back(field);
+ }
+
+ return fields;
+}
+
+bool PinmapParser::parseHeader (std::ifstream& a_Stream) {
+
+ // Get the header line
+ std::string header;
+ std::getline(a_Stream, header);
+
+ // Parse fields
+ m_Fields = getFields(header);
+ if (m_Fields.empty()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool PinmapParser::parseData (std::ifstream& a_Stream) {
+
+ // Parse lines as they come
+ while (a_Stream.good()) {
+ std::string line;
+ std::getline(a_Stream, line);
+
+ if (line.empty()) {
+ continue;
+ }
+
+ // Parse datafields
+ auto data = getFields(line);
+
+ // Assign data fields to columns
+ Entry entry;
+ for (size_t i=0; i<data.size(); ++i) {
+
+ if (i >= m_Fields.size()) {
+ return false;
+ }
+
+ entry[m_Fields[i]] = data[i];
+ }
+
+ m_Entries.push_back(entry);
+ }
+
+ return true;
+}
+
+bool PinmapParser::parse (std::ifstream& a_Stream) {
+
+ if (!a_Stream.good()) {
+ return false;
+ }
+
+ // Clear pinmap entries
+ m_Entries.clear();
+
+ // Parse header
+ if (!parseHeader(a_Stream)) {
+ return false;
+ }
+ // Parse data fields
+ if (!parseData(a_Stream)) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/ql-iob-plugin/pinmap_parser.hh b/ql-iob-plugin/pinmap_parser.hh
new file mode 100644
index 0000000..5139244
--- /dev/null
+++ b/ql-iob-plugin/pinmap_parser.hh
@@ -0,0 +1,63 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2020 The Symbiflow Authors
+ *
+ * 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.
+ *
+ */
+#ifndef PINMAP_PARSER_HH
+#define PINMAP_PARSER_HH
+
+#include <fstream>
+#include <string>
+#include <vector>
+#include <map>
+
+// ============================================================================
+
+class PinmapParser {
+public:
+
+ /// A pinmap entry type
+ typedef std::map<std::string, std::string> Entry;
+
+ /// Constructor
+ PinmapParser () = default;
+
+ /// Parses a pinmap CSV file
+ bool parse (const std::string& a_FileName);
+ bool parse (std::ifstream& a_Stream);
+
+ /// Returns a vector of entries
+ const std::vector<Entry> getEntries() const;
+
+private:
+
+ /// Splits the input string into a vector of fields. Fields are comma
+ /// separated.
+ static std::vector<std::string> getFields (const std::string& a_String);
+
+ /// Parses the header
+ bool parseHeader (std::ifstream& a_Stream);
+ /// Parses the data
+ bool parseData (std::ifstream& a_Stream);
+
+ /// Header fields
+ std::vector<std::string> m_Fields;
+ /// A list of entries
+ std::vector<Entry> m_Entries;
+};
+
+#endif // PINMAP_PARSER_HH
diff --git a/ql-iob-plugin/ql-iob.cc b/ql-iob-plugin/ql-iob.cc
new file mode 100644
index 0000000..6574535
--- /dev/null
+++ b/ql-iob-plugin/ql-iob.cc
@@ -0,0 +1,308 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2020 The Symbiflow Authors
+ *
+ * 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 "pcf_parser.hh"
+#include "pinmap_parser.hh"
+
+#include "kernel/register.h"
+#include "kernel/rtlil.h"
+
+#include <regex>
+#include <sstream>
+
+#ifndef YS_OVERRIDE
+#define YS_OVERRIDE override
+#endif
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+struct QuicklogicIob : public Pass {
+
+ struct IoCellType {
+ std::string type; // Cell type
+ std::string port; // Name of the port that goes to a pad
+ std::vector<std::string> preferredTypes; // A list of preferred IO cell types
+
+ IoCellType (const std::string& _type, const std::string& _port, const std::vector<std::string> _preferredTypes = std::vector<std::string>()) :
+ type(_type),
+ port(_port),
+ preferredTypes(_preferredTypes)
+ {}
+ };
+
+ QuicklogicIob () :
+ Pass("quicklogic_iob", "Map IO buffers to cells that correspond to their assigned locations") {
+ }
+
+ void help() YS_OVERRIDE {
+ log("\n");
+ log(" quicklogic_iob <PCF file> <pinmap file> [<io cell specs>]");
+ log("\n");
+ log("This command assigns certain parameters of the specified IO cell types\n");
+ log("basing on the placement constraints and the pin map of the target device\n");
+ log("\n");
+ log("Each affected IO cell is assigned the followin parameters:\n");
+ log(" - IO_PAD = \"<IO pad name>\"\n");
+ log(" - IO_LOC = \"<IO cell location>\"\n");
+ log(" - IO_CELL = \"<IO cell type>\"\n");
+ log("\n");
+ log("Parameters:\n");
+ log("\n");
+ log(" - <PCF file>\n");
+ log(" Path to a PCF file with IO constraints for the design\n");
+ log("\n");
+ log(" - <pinmap file>\n");
+ log(" Path to a pinmap CSV file with package pin map\n");
+ log("\n");
+ log(" - <io cell specs> (optional)\n");
+ log(" A space-separated list of <io cell type>:<port> or of\n");
+ log(" <io cell type>:<port>:<preferred type 1>,<preferred type 2>...\n");
+ log(" Each entry defines a type of IO cell to be affected an its port\n");
+ log(" name that should connect to the top-level port of the design.\n");
+ log("\n");
+ log(" The third argument is a comma-separated list of preferred IO cell\n");
+ log(" types in order of preference.\n");
+ log("\n");
+ }
+
+ void execute(std::vector<std::string> a_Args, RTLIL::Design* a_Design) YS_OVERRIDE {
+ if (a_Args.size() < 3) {
+ log_cmd_error(" Usage: quicklogic_iob <PCF file> <pinmap file> [<io cell specs>]");
+ }
+
+ // A map of IO cell types and their port names that should go to a pad
+ std::unordered_map<std::string, IoCellType> ioCellTypes;
+
+
+ // Parse io cell specification
+ if (a_Args.size() > 3) {
+
+ // FIXME: Are these characters set the only ones that can be in
+ // cell / port name ?
+ std::regex re1("^([\\w$]+):([\\w$]+)$");
+ std::regex re2("^([\\w$]+):([\\w$]+):([\\w,$]+)$");
+
+ for (size_t i=3; i<a_Args.size(); ++i) {
+ std::cmatch cm;
+
+ // No preffered IO cell types
+ if (std::regex_match(a_Args[i].c_str(), cm, re1)) {
+ ioCellTypes.emplace(cm[1].str(), IoCellType(cm[1], cm[2]));
+ }
+
+ // With preferred IO cell types
+ else if (std::regex_match(a_Args[i].c_str(), cm, re2)) {
+ std::vector<std::string> preferredTypes;
+ std::stringstream ss(cm[3]);
+
+ while (ss.good()) {
+ std::string field;
+ std::getline(ss, field, ',');
+
+ preferredTypes.push_back(field);
+ }
+
+ ioCellTypes.emplace(cm[1].str(), IoCellType(cm[1], cm[2], preferredTypes));
+ }
+
+ // Invalid
+ else {
+ log_cmd_error("Invalid IO cell+port spec: '%s'\n", a_Args[i].c_str());
+ }
+ }
+ }
+
+ // Use the default IO cells for QuickLogic FPGAs
+ else {
+ ioCellTypes.emplace("inpad", IoCellType("inpad", "P", {"BIDIR", "SDIOMUX"}));
+ ioCellTypes.emplace("outpad", IoCellType("outpad", "P", {"BIDIR", "SDIOMUX"}));
+ ioCellTypes.emplace("bipad", IoCellType("bipad", "P", {"BIDIR", "SDIOMUX"}));
+ ioCellTypes.emplace("ckpad", IoCellType("ckpad", "P", {"CLOCK", "BIDIR", "SDIOMUX"}));
+ }
+
+ // Get the top module of the design
+ RTLIL::Module* topModule = a_Design->top_module();
+ if (topModule == nullptr) {
+ log_cmd_error("No top module detected!\n");
+ }
+
+ // Read and parse the PCF file
+ log("Loading PCF from '%s'...\n", a_Args[1].c_str());
+ auto pcfParser = PcfParser();
+ if (!pcfParser.parse(a_Args[1])) {
+ log_cmd_error("Failed to parse the PCF file!\n");
+ }
+
+ // Build a map of net names to constraints
+ std::unordered_map<std::string, const PcfParser::Constraint> constraintMap;
+ for (auto& constraint : pcfParser.getConstraints()) {
+ if (constraintMap.count(constraint.netName) != 0) {
+ log_cmd_error("The net '%s' is constrained twice!", constraint.netName.c_str());
+ }
+ constraintMap.emplace(constraint.netName, constraint);
+ }
+
+ // Read and parse pinmap CSV file
+ log("Loading pinmap CSV from '%s'...\n", a_Args[2].c_str());
+ auto pinmapParser = PinmapParser();
+ if (!pinmapParser.parse(a_Args[2])) {
+ log_cmd_error("Failed to parse the pinmap CSV file!\n");
+ }
+
+ // Build a map of pad names to entries
+ std::unordered_map<std::string, std::vector<PinmapParser::Entry>> pinmapMap;
+ for (auto& entry : pinmapParser.getEntries()) {
+ if (entry.count("name") != 0) {
+ auto& name = entry.at("name");
+
+ if (pinmapMap.count(name) == 0) {
+ pinmapMap[name] = std::vector<PinmapParser::Entry>();
+ }
+
+ pinmapMap[name].push_back(entry);
+ }
+ }
+
+ // Check all IO cells
+ log("Processing cells...");
+ log("\n");
+ log(" type | net | pad | loc | type | instance\n");
+ log(" ------------+------------+------------+----------+----------+-----------\n");
+ for (auto cell : topModule->cells()) {
+ auto ysCellType = RTLIL::unescape_id(cell->type);
+
+ // Not an IO cell
+ if (ioCellTypes.count(ysCellType) == 0) {
+ continue;
+ }
+
+ log(" %-10s ", ysCellType.c_str());
+
+ std::string netName;
+ std::string padName;
+ std::string locName;
+ std::string cellType;
+
+ // Get connections to the specified port
+ const auto& ioCellType = ioCellTypes.at(ysCellType);
+ const std::string port = RTLIL::escape_id(ioCellType.port);
+ if (cell->connections().count(port)) {
+
+ // Get the sigspec of the connection
+ auto sigspec = cell->connections().at(port);
+
+ // Get the connected wire
+ for (auto sigbit : sigspec.bits()) {
+ if (sigbit.wire != nullptr) {
+ auto wire = sigbit.wire;
+
+ // Has to be top level wire
+ if (wire->port_input || wire->port_output) {
+
+ // Check if the wire is constrained. Get pad name.
+ std::string baseName = RTLIL::unescape_id(wire->name);
+ std::string netNames[] = {
+ baseName,
+ stringf("%s[%d]", baseName.c_str(), sigbit.offset),
+ stringf("%s(%d)", baseName.c_str(), sigbit.offset),
+ };
+
+ padName = "";
+ netName = "";
+
+ for (auto& name : netNames) {
+ if (constraintMap.count(name)) {
+ auto constraint = constraintMap.at(name);
+ padName = constraint.padName;
+ netName = name;
+ break;
+ }
+ }
+
+ // Check if there is an entry in the pinmap for this pad name
+ if (pinmapMap.count(padName)) {
+
+ // Choose a correct entry for the cell
+ auto entry = choosePinmapEntry(
+ pinmapMap.at(padName),
+ ioCellType
+ );
+
+ // Location string
+ if (entry.count("x") && entry.count("y")) {
+ locName = stringf("X%sY%s",
+ entry.at("x").c_str(),
+ entry.at("y").c_str()
+ );
+ }
+
+ // Cell type
+ if (entry.count("type")) {
+ cellType = entry.at("type");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ log("| %-10s | %-10s | %-8s | %-8s | %s\n",
+ netName.c_str(),
+ padName.c_str(),
+ locName.c_str(),
+ cellType.c_str(),
+ cell->name.c_str()
+ );
+
+ // Annotate the cell by setting its parameters
+ cell->setParam(RTLIL::escape_id("IO_PAD"), padName);
+ cell->setParam(RTLIL::escape_id("IO_LOC"), locName);
+ cell->setParam(RTLIL::escape_id("IO_TYPE"), cellType);
+ }
+ }
+
+ PinmapParser::Entry choosePinmapEntry(
+ const std::vector<PinmapParser::Entry>& a_Entries,
+ const IoCellType& a_IoCellType)
+ {
+ // No preferred types, pick the first one
+ if (a_IoCellType.preferredTypes.empty()) {
+ return a_Entries[0];
+ }
+
+ // Loop over preferred types
+ for (auto& type : a_IoCellType.preferredTypes) {
+
+ // Find an entry for that type. If found then return it.
+ for (auto& entry : a_Entries) {
+ if (type == entry.at("type")) {
+ return entry;
+ }
+ }
+ }
+
+ // No preferred type was found, pick the first one.
+ return a_Entries[0];
+ }
+
+} QuicklogicIob;
+
+PRIVATE_NAMESPACE_END
diff --git a/ql-iob-plugin/tests/.gitignore b/ql-iob-plugin/tests/.gitignore
new file mode 100644
index 0000000..744eec9
--- /dev/null
+++ b/ql-iob-plugin/tests/.gitignore
@@ -0,0 +1,2 @@
+*.eblif
+ok
diff --git a/ql-iob-plugin/tests/Makefile b/ql-iob-plugin/tests/Makefile
new file mode 100644
index 0000000..ed853e1
--- /dev/null
+++ b/ql-iob-plugin/tests/Makefile
@@ -0,0 +1,13 @@
+TESTS = sdiomux ckpad
+
+all: clean $(addsuffix /ok,$(TESTS))
+
+clean:
+ @find . -name "ok" | xargs rm -rf
+
+sdiomux/ok:
+ cd sdiomux && $(MAKE) test
+ckpad/ok:
+ cd ckpad && $(MAKE) test
+
+.PHONY: all clean
diff --git a/ql-iob-plugin/tests/ckpad/Makefile b/ql-iob-plugin/tests/ckpad/Makefile
new file mode 100644
index 0000000..ac7b664
--- /dev/null
+++ b/ql-iob-plugin/tests/ckpad/Makefile
@@ -0,0 +1,4 @@
+test:
+ yosys -s script.ys
+ @echo $@ PASS
+ @touch ok
diff --git a/ql-iob-plugin/tests/ckpad/design.pcf b/ql-iob-plugin/tests/ckpad/design.pcf
new file mode 100644
index 0000000..00563c5
--- /dev/null
+++ b/ql-iob-plugin/tests/ckpad/design.pcf
@@ -0,0 +1,14 @@
+set_io clk0 B3 # to a BIDIR
+set_io clk1 A3 # to a BIDIR/CLOCK
+set_io clk2 B4 # to a BIDIR
+set_io clk3 C4 # to a BIDIR/CLOCK
+
+set_io d(0) C1
+set_io d(1) A1
+set_io d(2) A2
+set_io d(3) B2
+
+set_io q(0) B5
+set_io q(1) D6
+set_io q(2) A5
+set_io q(3) C6
diff --git a/ql-iob-plugin/tests/ckpad/design.v b/ql-iob-plugin/tests/ckpad/design.v
new file mode 100644
index 0000000..8ef1418
--- /dev/null
+++ b/ql-iob-plugin/tests/ckpad/design.v
@@ -0,0 +1,22 @@
+module top (
+ input wire clk0,
+ input wire clk1,
+ (* clkbuf_inhibit *)
+ input wire clk2,
+ (* clkbuf_inhibit *)
+ input wire clk3,
+
+ input wire [3:0] d,
+ output reg [3:0] q
+);
+
+ always @(posedge clk0)
+ q[0] <= d[0];
+ always @(posedge clk1)
+ q[1] <= d[1];
+ always @(posedge clk2)
+ q[2] <= d[2];
+ always @(posedge clk3)
+ q[3] <= d[3];
+
+endmodule
diff --git a/ql-iob-plugin/tests/ckpad/script.ys b/ql-iob-plugin/tests/ckpad/script.ys
new file mode 100644
index 0000000..b5b8713
--- /dev/null
+++ b/ql-iob-plugin/tests/ckpad/script.ys
@@ -0,0 +1,25 @@
+plugin -i ql-iob
+read_verilog design.v
+
+# Generic synthesis
+synth -lut 4 -flatten -auto-top
+
+# Techmap
+read_verilog -lib ../common/pp3_cells_sim.v
+techmap -map ../common/pp3_cells_map.v
+
+# Insert QuickLogic specific IOBs and clock buffers
+clkbufmap -buf $_BUF_ Y:A -inpad ckpad Q:P
+iopadmap -bits -outpad outpad A:P -inpad inpad Q:P -tinoutpad bipad EN:Q:A:P A:top
+opt_clean
+
+stat
+
+quicklogic_iob design.pcf ../pinmap.csv
+
+select r:IO_TYPE=BIDIR -assert-count 11
+select r:IO_TYPE=CLOCK -assert-count 1
+select r:IO_TYPE=SDIOMUX -assert-count 0
+select r:IO_TYPE= -assert-count 0
+
+write_blif -attr -param -cname design.eblif
diff --git a/ql-iob-plugin/tests/common/pp3_cells_map.v b/ql-iob-plugin/tests/common/pp3_cells_map.v
new file mode 100644
index 0000000..c68042e
--- /dev/null
+++ b/ql-iob-plugin/tests/common/pp3_cells_map.v
@@ -0,0 +1,7 @@
+module \$_DFF_P_ (D, Q, C);
+ input D;
+ input C;
+ output Q;
+ dff _TECHMAP_REPLACE_ (.Q(Q), .D(D), .CLK(C));
+endmodule
+
diff --git a/ql-iob-plugin/tests/common/pp3_cells_sim.v b/ql-iob-plugin/tests/common/pp3_cells_sim.v
new file mode 100644
index 0000000..f77ea11
--- /dev/null
+++ b/ql-iob-plugin/tests/common/pp3_cells_sim.v
@@ -0,0 +1,48 @@
+module inpad(
+ output Q,
+ (* iopad_external_pin *)
+ input P
+);
+ assign Q = P;
+endmodule
+
+module outpad(
+ (* iopad_external_pin *)
+ output P,
+ input A
+);
+ assign P = A;
+endmodule
+
+module ckpad(
+ output Q,
+ (* iopad_external_pin *)
+ input P
+);
+ assign Q = P;
+endmodule
+
+module bipad(
+ input A,
+ input EN,
+ output Q,
+ (* iopad_external_pin *)
+ inout P
+);
+ assign Q = P;
+ assign P = EN ? A : 1'bz;
+endmodule
+
+
+module dff(
+ output reg Q,
+ input D,
+ (* clkbuf_sink *)
+ input CLK
+);
+ parameter [0:0] INIT = 1'b0;
+ initial Q = INIT;
+ always @(posedge CLK)
+ Q <= D;
+endmodule
+
diff --git a/ql-iob-plugin/tests/pinmap.csv b/ql-iob-plugin/tests/pinmap.csv
new file mode 100644
index 0000000..c7edb17
--- /dev/null
+++ b/ql-iob-plugin/tests/pinmap.csv
@@ -0,0 +1,52 @@
+name,x,y,z,type
+B1,4,3,0,BIDIR
+C1,6,3,0,BIDIR
+A1,8,3,0,BIDIR
+A2,10,3,0,BIDIR
+B2,12,3,0,BIDIR
+C3,14,3,0,BIDIR
+B3,16,3,0,BIDIR
+A3,18,2,0,CLOCK
+A3,18,3,0,BIDIR
+C4,20,2,0,CLOCK
+C4,20,3,0,BIDIR
+B4,22,3,0,BIDIR
+A4,24,3,0,BIDIR
+C5,26,3,0,BIDIR
+B5,28,3,0,BIDIR
+D6,30,3,0,BIDIR
+A5,32,3,0,BIDIR
+C6,34,3,0,BIDIR
+E7,34,32,0,BIDIR
+D7,32,32,0,BIDIR
+E8,30,32,0,BIDIR
+H8,28,32,0,BIDIR
+G8,26,32,0,BIDIR
+H7,24,32,0,BIDIR
+G7,22,33,0,CLOCK
+G7,22,32,0,BIDIR
+H6,20,32,0,BIDIR
+H6,20,33,0,CLOCK
+G6,18,32,0,BIDIR
+G6,18,33,0,CLOCK
+F7,16,32,0,BIDIR
+F6,14,32,0,BIDIR
+H5,12,32,0,BIDIR
+G5,10,32,0,BIDIR
+F5,8,32,0,BIDIR
+F4,6,32,0,BIDIR
+G4,4,32,0,BIDIR
+H4,3,31,0,SDIOMUX
+E3,2,31,0,SDIOMUX
+F3,1,31,0,SDIOMUX
+F2,3,30,0,SDIOMUX
+H3,2,30,0,SDIOMUX
+G2,1,30,0,SDIOMUX
+E2,3,29,0,SDIOMUX
+H2,2,29,0,SDIOMUX
+D2,1,29,0,SDIOMUX
+F1,3,28,0,SDIOMUX
+H1,2,28,0,SDIOMUX
+D1,1,28,0,SDIOMUX
+E1,3,27,0,SDIOMUX
+G1,2,27,0,SDIOMUX
diff --git a/ql-iob-plugin/tests/sdiomux/Makefile b/ql-iob-plugin/tests/sdiomux/Makefile
new file mode 100644
index 0000000..ac7b664
--- /dev/null
+++ b/ql-iob-plugin/tests/sdiomux/Makefile
@@ -0,0 +1,4 @@
+test:
+ yosys -s script.ys
+ @echo $@ PASS
+ @touch ok
diff --git a/ql-iob-plugin/tests/sdiomux/design.pcf b/ql-iob-plugin/tests/sdiomux/design.pcf
new file mode 100644
index 0000000..9aa0551
--- /dev/null
+++ b/ql-iob-plugin/tests/sdiomux/design.pcf
@@ -0,0 +1,5 @@
+set_io clk B1
+set_io led(0) C1
+set_io led(1) A1
+set_io led(2) H3
+set_io led(3) E3
diff --git a/ql-iob-plugin/tests/sdiomux/design.v b/ql-iob-plugin/tests/sdiomux/design.v
new file mode 100644
index 0000000..1aa05fb
--- /dev/null
+++ b/ql-iob-plugin/tests/sdiomux/design.v
@@ -0,0 +1,17 @@
+module top
+(
+ input wire clk,
+ output wire [3:0] led,
+ inout wire io
+);
+
+ reg [3:0] r;
+ initial r <= 0;
+
+ always @(posedge clk)
+ r <= r + io;
+
+ assign led = {r[0], r[1], r[2], r[3]};
+ assign io = r[0] ? 1 : 1'bz;
+
+endmodule
diff --git a/ql-iob-plugin/tests/sdiomux/script.ys b/ql-iob-plugin/tests/sdiomux/script.ys
new file mode 100644
index 0000000..0af0a0e
--- /dev/null
+++ b/ql-iob-plugin/tests/sdiomux/script.ys
@@ -0,0 +1,24 @@
+plugin -i ql-iob
+read_verilog design.v
+
+# Generic synthesis
+synth -lut 4 -flatten -auto-top
+
+# Techmap
+read_verilog -lib ../common/pp3_cells_sim.v
+techmap -map ../common/pp3_cells_map.v
+
+# Insert QuickLogic specific IOBs and clock buffers
+clkbufmap -buf $_BUF_ Y:A -inpad ckpad Q:P
+iopadmap -bits -outpad outpad A:P -inpad inpad Q:P -tinoutpad bipad EN:Q:A:P A:top
+opt_clean
+
+stat
+
+quicklogic_iob design.pcf ../pinmap.csv
+
+select r:IO_TYPE=BIDIR -assert-count 3
+select r:IO_TYPE=SDIOMUX -assert-count 2
+select r:IO_TYPE= -assert-count 1
+
+write_blif -attr -param -cname design.eblif