aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml3
-rw-r--r--.cirrus/Dockerfile.ubuntu16.042
-rw-r--r--README.md3
-rw-r--r--common/command.cc3
-rw-r--r--common/design_utils.cc17
-rw-r--r--common/design_utils.h3
-rw-r--r--common/nextpnr.cc8
-rw-r--r--common/nextpnr.h6
-rw-r--r--common/pybindings.cc44
-rw-r--r--common/pywrappers.h111
-rw-r--r--common/timing.cc5
-rw-r--r--docs/generic.md119
-rw-r--r--ecp5/arch.cc8
-rw-r--r--ecp5/arch.h2
-rw-r--r--ecp5/bitstream.cc19
-rw-r--r--ecp5/family.cmake56
-rw-r--r--ecp5/pack.cc14
-rw-r--r--generic/arch.cc161
-rw-r--r--generic/arch.h53
-rw-r--r--generic/arch_pybindings.cc197
-rw-r--r--generic/archdefs.h10
-rw-r--r--generic/cells.cc139
-rw-r--r--generic/cells.h55
-rw-r--r--generic/examples/.gitignore3
-rw-r--r--generic/examples/README.md14
-rw-r--r--generic/examples/__init__.py0
-rw-r--r--generic/examples/bitstream.py17
-rw-r--r--generic/examples/blinky.v9
-rw-r--r--generic/examples/simple.py72
-rwxr-xr-xgeneric/examples/simple.sh4
-rw-r--r--generic/examples/simple_config.py15
-rw-r--r--generic/examples/simple_timing.py15
-rw-r--r--generic/examples/write_fasm.py52
-rw-r--r--generic/main.cc2
-rw-r--r--generic/pack.cc293
-rw-r--r--generic/synth/cells_map.v10
-rw-r--r--generic/synth/prims.v59
-rw-r--r--generic/synth/synth_generic.tcl24
-rw-r--r--gui/application.cc24
-rw-r--r--gui/application.h2
-rw-r--r--gui/fpgaviewwidget.cc14
-rw-r--r--gui/generic/mainwindow.cc13
-rw-r--r--gui/lineshader.h13
-rw-r--r--gui/pythontab.cc3
-rw-r--r--ice40/arch.cc8
-rw-r--r--ice40/archdefs.h4
-rw-r--r--ice40/bitstream.cc8
-rw-r--r--ice40/cells.cc16
-rw-r--r--ice40/cells.h7
-rw-r--r--ice40/chipdb.py2
-rw-r--r--ice40/constids.inc5
-rw-r--r--ice40/pack.cc42
-rw-r--r--json/jsonparse.cc57
m---------tests0
54 files changed, 1726 insertions, 119 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index c97b521c..5cac994e 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -2,7 +2,7 @@ task:
name: build-test-ubuntu1604
container:
cpu: 4
- memory: 16
+ memory: 20
dockerfile: .cirrus/Dockerfile.ubuntu16.04
build_script: mkdir build && cd build && cmake .. -DARCH=all -DTRELLIS_ROOT=/usr/local/src/prjtrellis -DBUILD_TESTS=on && make -j $(nproc)
@@ -11,5 +11,6 @@ task:
test_ice40_script: cd build && ./nextpnr-ice40-test
smoketest_ice40_script: export NEXTPNR=$(pwd)/build/nextpnr-ice40 && cd ice40/smoketest/attosoc && ./smoketest.sh
test_ecp5_script: cd build && ./nextpnr-ecp5-test
+ smoketest_generic_script: export NEXTPNR=$(pwd)/build/nextpnr-generic && cd generic/examples && ./simple.sh
regressiontest_ice40_script: make -j $(nproc) -C tests/ice40/regressions NPNR=$(pwd)/build/nextpnr-ice40
regressiontest_ecp5_script: make -j $(nproc) -C tests/ecp5/regressions NPNR=$(pwd)/build/nextpnr-ecp5
diff --git a/.cirrus/Dockerfile.ubuntu16.04 b/.cirrus/Dockerfile.ubuntu16.04
index 0c8201b8..4b149b7b 100644
--- a/.cirrus/Dockerfile.ubuntu16.04
+++ b/.cirrus/Dockerfile.ubuntu16.04
@@ -36,7 +36,7 @@ RUN set -e -x ;\
cd /usr/local/src ;\
git clone --recursive https://github.com/YosysHQ/yosys.git ;\
cd yosys ;\
- git reset --hard 47a5dfdaa4bd7d400c6e3d58476de80904df460d ;\
+ git reset --hard ea8ac0aaad3a1f89ead8eb44b2fef5927f29a099 ;\
make -j $(nproc) ;\
make install ;\
rm -rf /usr/local/src/yosys
diff --git a/README.md b/README.md
index b2144ce5..dc57c6bd 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,9 @@ We hope to see Xilinx 7 Series thanks to
supported in the future. We would love your help in developing this
awesome new project!
+A brief (academic) paper describing the Yosys+nextpnr flow can be found
+on [arXiv](https://arxiv.org/abs/1903.10407).
+
Here is a screenshot of nextpnr for iCE40. Build instructions and
[getting started notes](#getting-started) can be found below.
diff --git a/common/command.cc b/common/command.cc
index 49081e72..3eafdb17 100644
--- a/common/command.cc
+++ b/common/command.cc
@@ -107,6 +107,7 @@ po::options_description CommandHandler::getGeneralOptions()
general.add_options()("force,f", "keep running after errors");
#ifndef NO_GUI
general.add_options()("gui", "start gui");
+ general.add_options()("gui-no-aa", "disable anti aliasing (use together with --gui option)");
#endif
#ifndef NO_PYTHON
general.add_options()("run", po::value<std::vector<std::string>>(),
@@ -235,7 +236,7 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
#ifndef NO_GUI
if (vm.count("gui")) {
- Application a(argc, argv);
+ Application a(argc, argv, (vm.count("gui-no-aa") > 0));
MainWindow w(std::move(ctx), chipArgs);
try {
if (vm.count("json")) {
diff --git a/common/design_utils.cc b/common/design_utils.cc
index da170030..bdf5ca5c 100644
--- a/common/design_utils.cc
+++ b/common/design_utils.cc
@@ -129,4 +129,21 @@ void connect_ports(Context *ctx, CellInfo *cell1, IdString port1_name, CellInfo
connect_port(ctx, port1.net, cell2, port2_name);
}
+void rename_port(Context *ctx, CellInfo *cell, IdString old_name, IdString new_name)
+{
+ if (!cell->ports.count(old_name))
+ return;
+ PortInfo pi = cell->ports.at(old_name);
+ if (pi.net != nullptr) {
+ if (pi.net->driver.cell == cell && pi.net->driver.port == old_name)
+ pi.net->driver.port = new_name;
+ for (auto &usr : pi.net->users)
+ if (usr.cell == cell && usr.port == old_name)
+ usr.port = new_name;
+ }
+ cell->ports.erase(old_name);
+ pi.name = new_name;
+ cell->ports[new_name] = pi;
+}
+
NEXTPNR_NAMESPACE_END
diff --git a/common/design_utils.h b/common/design_utils.h
index 8a42d21f..3eb9024f 100644
--- a/common/design_utils.h
+++ b/common/design_utils.h
@@ -91,6 +91,9 @@ void disconnect_port(const Context *ctx, CellInfo *cell, IdString port_name);
// Connect two ports together
void connect_ports(Context *ctx, CellInfo *cell1, IdString port1_name, CellInfo *cell2, IdString port2_name);
+// Rename a port if it exists on a cell
+void rename_port(Context *ctx, CellInfo *cell, IdString old_name, IdString new_name);
+
void print_utilisation(const Context *ctx);
NEXTPNR_NAMESPACE_END
diff --git a/common/nextpnr.cc b/common/nextpnr.cc
index 54333b15..daaadf28 100644
--- a/common/nextpnr.cc
+++ b/common/nextpnr.cc
@@ -444,5 +444,13 @@ void BaseCtx::constrainCellToRegion(IdString cell, IdString region_name)
{
cells[cell]->region = region[region_name].get();
}
+DecalXY BaseCtx::constructDecalXY(DecalId decal, float x, float y)
+{
+ DecalXY dxy;
+ dxy.decal = decal;
+ dxy.x = x;
+ dxy.y = y;
+ return dxy;
+}
NEXTPNR_NAMESPACE_END
diff --git a/common/nextpnr.h b/common/nextpnr.h
index 5967ecee..fc49300e 100644
--- a/common/nextpnr.h
+++ b/common/nextpnr.h
@@ -181,6 +181,9 @@ struct GraphicElement
float x1 = 0, y1 = 0, x2 = 0, y2 = 0, z = 0;
std::string text;
+ GraphicElement(){};
+ GraphicElement(type_t type, style_t style, float x1, float y1, float x2, float y2, float z)
+ : type(type), style(style), x1(x1), y1(y1), x2(x2), y2(y2), z(z){};
};
struct Loc
@@ -640,6 +643,9 @@ struct BaseCtx
void createRectangularRegion(IdString name, int x0, int y0, int x1, int y1);
void addBelToRegion(IdString name, BelId bel);
void constrainCellToRegion(IdString cell, IdString region_name);
+
+ // Workaround for lack of wrappable constructors
+ DecalXY constructDecalXY(DecalId decal, float x, float y);
};
NEXTPNR_NAMESPACE_END
diff --git a/common/pybindings.cc b/common/pybindings.cc
index eee78b5e..60f87e27 100644
--- a/common/pybindings.cc
+++ b/common/pybindings.cc
@@ -23,8 +23,10 @@
#include "pybindings.h"
#include "arch_pybindings.h"
#include "jsonparse.h"
+#include "log.h"
#include "nextpnr.h"
+#include <boost/filesystem.hpp>
#include <fstream>
#include <memory>
#include <signal.h>
@@ -87,7 +89,26 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
using namespace PythonConversion;
+ enum_<GraphicElement::type_t>("GraphicElementType")
+ .value("TYPE_NONE", GraphicElement::TYPE_NONE)
+ .value("TYPE_LINE", GraphicElement::TYPE_LINE)
+ .value("TYPE_ARROW", GraphicElement::TYPE_ARROW)
+ .value("TYPE_BOX", GraphicElement::TYPE_BOX)
+ .value("TYPE_CIRCLE", GraphicElement::TYPE_CIRCLE)
+ .value("TYPE_LABEL", GraphicElement::TYPE_LABEL)
+ .export_values();
+
+ enum_<GraphicElement::style_t>("GraphicElementStyle")
+ .value("STYLE_GRID", GraphicElement::STYLE_GRID)
+ .value("STYLE_FRAME", GraphicElement::STYLE_FRAME)
+ .value("STYLE_HIDDEN", GraphicElement::STYLE_HIDDEN)
+ .value("STYLE_INACTIVE", GraphicElement::STYLE_INACTIVE)
+ .value("STYLE_ACTIVE", GraphicElement::STYLE_ACTIVE)
+ .export_values();
+
class_<GraphicElement>("GraphicElement")
+ .def(init<GraphicElement::type_t, GraphicElement::style_t, float, float, float, float, float>(
+ (args("type"), "style", "x1", "y1", "x2", "y2", "z")))
.def_readwrite("type", &GraphicElement::type)
.def_readwrite("x1", &GraphicElement::x1)
.def_readwrite("y1", &GraphicElement::y1)
@@ -108,6 +129,12 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
class_<BaseCtx, BaseCtx *, boost::noncopyable>("BaseCtx", no_init);
+ auto loc_cls = class_<Loc>("Loc")
+ .def(init<int, int, int>())
+ .def_readwrite("x", &Loc::x)
+ .def_readwrite("y", &Loc::y)
+ .def_readwrite("z", &Loc::z);
+
auto ci_cls = class_<ContextualWrapper<CellInfo &>>("CellInfo", no_init);
readwrite_wrapper<CellInfo &, decltype(&CellInfo::name), &CellInfo::name, conv_to_str<IdString>,
conv_from_str<IdString>>::def_wrap(ci_cls, "name");
@@ -208,8 +235,14 @@ void init_python(const char *executable, bool first)
PyImport_AppendInittab(TOSTRING(MODULE_NAME), PYINIT_MODULE_NAME);
Py_SetProgramName(program);
Py_Initialize();
- if (first)
- PyImport_ImportModule(TOSTRING(MODULE_NAME));
+
+ // Add cwd to Python's search path so `import` can be used in user scripts
+ boost::filesystem::path cwd = boost::filesystem::absolute("./").normalize();
+ PyObject *sys_path = PySys_GetObject("path");
+ PyList_Insert(sys_path, 0, PyUnicode_FromString(cwd.string().c_str()));
+
+ PyImport_ImportModule(TOSTRING(MODULE_NAME));
+ PyRun_SimpleString("from " TOSTRING(MODULE_NAME) " import *");
} catch (boost::python::error_already_set const &) {
// Parse and output the exception
std::string perror_str = parse_python_exception();
@@ -235,12 +268,15 @@ void execute_python_file(const char *python_file)
fprintf(stderr, "Fatal error: file not found %s\n", python_file);
exit(1);
}
- PyRun_SimpleFile(fp, python_file);
+ int result = PyRun_SimpleFile(fp, python_file);
fclose(fp);
+ if (result == -1) {
+ log_error("Error occurred while executing Python script %s\n", python_file);
+ }
} catch (boost::python::error_already_set const &) {
// Parse and output the exception
std::string perror_str = parse_python_exception();
- std::cout << "Error in Python: " << perror_str << std::endl;
+ log_error("Error in Python: %s\n", perror_str.c_str());
}
}
diff --git a/common/pywrappers.h b/common/pywrappers.h
index 427c3623..1d970985 100644
--- a/common/pywrappers.h
+++ b/common/pywrappers.h
@@ -155,11 +155,15 @@ template <typename Class, typename FuncT, FuncT fn, typename rv_conv> struct fn_
using class_type = typename WrapIfNotContext<Class>::maybe_wrapped_t;
using conv_result_type = typename rv_conv::ret_type;
- static conv_result_type wrapped_fn(class_type &cls)
+ static object wrapped_fn(class_type &cls)
{
Context *ctx = get_ctx<Class>(cls);
Class &base = get_base<Class>(cls);
- return rv_conv()(ctx, (base.*fn)());
+ try {
+ return object(rv_conv()(ctx, (base.*fn)()));
+ } catch (bad_wrap &) {
+ return object();
+ }
}
template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
@@ -172,11 +176,15 @@ template <typename Class, typename FuncT, FuncT fn, typename rv_conv, typename a
using conv_result_type = typename rv_conv::ret_type;
using conv_arg1_type = typename arg1_conv::arg_type;
- static conv_result_type wrapped_fn(class_type &cls, conv_arg1_type arg1)
+ static object wrapped_fn(class_type &cls, conv_arg1_type arg1)
{
Context *ctx = get_ctx<Class>(cls);
Class &base = get_base<Class>(cls);
- return rv_conv()(ctx, (base.*fn)(arg1_conv()(ctx, arg1)));
+ try {
+ return object(rv_conv()(ctx, (base.*fn)(arg1_conv()(ctx, arg1))));
+ } catch (bad_wrap &) {
+ return object();
+ }
}
template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
@@ -191,11 +199,15 @@ struct fn_wrapper_2a
using conv_arg1_type = typename arg1_conv::arg_type;
using conv_arg2_type = typename arg2_conv::arg_type;
- static conv_result_type wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2)
+ static object wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2)
{
Context *ctx = get_ctx<Class>(cls);
Class &base = get_base<Class>(cls);
- return rv_conv()(ctx, (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2)));
+ try {
+ return object(rv_conv()(ctx, (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2))));
+ } catch (bad_wrap &) {
+ return object();
+ }
}
template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
@@ -212,11 +224,16 @@ struct fn_wrapper_3a
using conv_arg2_type = typename arg2_conv::arg_type;
using conv_arg3_type = typename arg3_conv::arg_type;
- static conv_result_type wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3)
+ static object wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3)
{
Context *ctx = get_ctx<Class>(cls);
Class &base = get_base<Class>(cls);
- return rv_conv()(ctx, (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3)));
+ try {
+ return object(
+ rv_conv()(ctx, (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3))));
+ } catch (bad_wrap &) {
+ return object();
+ }
}
template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
@@ -250,6 +267,11 @@ template <typename Class, typename FuncT, FuncT fn, typename arg1_conv> struct f
}
template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
+
+ template <typename WrapCls, typename Ta> static void def_wrap(WrapCls cls_, const char *name, Ta a = arg("arg1"))
+ {
+ cls_.def(name, wrapped_fn, a);
+ }
};
// Two parameters, one return
@@ -267,6 +289,11 @@ template <typename Class, typename FuncT, FuncT fn, typename arg1_conv, typename
}
template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
+
+ template <typename WrapCls, typename Ta> static void def_wrap(WrapCls cls_, const char *name, const Ta &a)
+ {
+ cls_.def(name, wrapped_fn, a);
+ }
};
// Three parameters, no return
@@ -286,6 +313,39 @@ struct fn_wrapper_3a_v
}
template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
+
+ template <typename WrapCls, typename Ta> static void def_wrap(WrapCls cls_, const char *name, const Ta &a)
+ {
+ cls_.def(name, wrapped_fn, a);
+ }
+};
+
+// Four parameters, no return
+template <typename Class, typename FuncT, FuncT fn, typename arg1_conv, typename arg2_conv, typename arg3_conv,
+ typename arg4_conv>
+struct fn_wrapper_4a_v
+{
+ using class_type = typename WrapIfNotContext<Class>::maybe_wrapped_t;
+ using conv_arg1_type = typename arg1_conv::arg_type;
+ using conv_arg2_type = typename arg2_conv::arg_type;
+ using conv_arg3_type = typename arg3_conv::arg_type;
+ using conv_arg4_type = typename arg4_conv::arg_type;
+
+ static void wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3,
+ conv_arg4_type arg4)
+ {
+ Context *ctx = get_ctx<Class>(cls);
+ Class &base = get_base<Class>(cls);
+ return (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3),
+ arg4_conv()(ctx, arg4));
+ }
+
+ template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
+
+ template <typename WrapCls, typename Ta> static void def_wrap(WrapCls cls_, const char *name, const Ta &a)
+ {
+ cls_.def(name, wrapped_fn, a);
+ }
};
// Five parameters, no return
@@ -310,6 +370,41 @@ struct fn_wrapper_5a_v
}
template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
+
+ template <typename WrapCls, typename Ta> static void def_wrap(WrapCls cls_, const char *name, const Ta &a)
+ {
+ cls_.def(name, wrapped_fn, a);
+ }
+};
+
+// Six parameters, no return
+template <typename Class, typename FuncT, FuncT fn, typename arg1_conv, typename arg2_conv, typename arg3_conv,
+ typename arg4_conv, typename arg5_conv, typename arg6_conv>
+struct fn_wrapper_6a_v
+{
+ using class_type = typename WrapIfNotContext<Class>::maybe_wrapped_t;
+ using conv_arg1_type = typename arg1_conv::arg_type;
+ using conv_arg2_type = typename arg2_conv::arg_type;
+ using conv_arg3_type = typename arg3_conv::arg_type;
+ using conv_arg4_type = typename arg4_conv::arg_type;
+ using conv_arg5_type = typename arg5_conv::arg_type;
+ using conv_arg6_type = typename arg6_conv::arg_type;
+
+ static void wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3,
+ conv_arg4_type arg4, conv_arg5_type arg5, conv_arg6_type arg6)
+ {
+ Context *ctx = get_ctx<Class>(cls);
+ Class &base = get_base<Class>(cls);
+ return (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3),
+ arg4_conv()(ctx, arg4), arg5_conv()(ctx, arg5), arg6_conv()(ctx, arg6));
+ }
+
+ template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
+
+ template <typename WrapCls, typename Ta> static void def_wrap(WrapCls cls_, const char *name, const Ta &a)
+ {
+ cls_.def(name, wrapped_fn, a);
+ }
};
// Wrapped getter
diff --git a/common/timing.cc b/common/timing.cc
index 2ce9eea3..e67ac231 100644
--- a/common/timing.cc
+++ b/common/timing.cc
@@ -546,7 +546,8 @@ struct Timing
for (size_t i = 0; i < sink_net->users.size(); i++) {
auto &user = sink_net->users.at(i);
if (user.cell == drv.cell && user.port == port.first) {
- sink_nd.min_required.at(i) = net_min_required - comb_delay.maxDelay();
+ sink_nd.min_required.at(i) = std::min(sink_nd.min_required.at(i),
+ net_min_required - comb_delay.maxDelay());
break;
}
}
@@ -752,7 +753,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
}
if (clock_reports.empty()) {
- log_warning("No clocks found in design");
+ log_warning("No clocks found in design\n");
}
std::sort(xclock_paths.begin(), xclock_paths.end(), [ctx](const ClockPair &a, const ClockPair &b) {
diff --git a/docs/generic.md b/docs/generic.md
new file mode 100644
index 00000000..d6ddbfb6
--- /dev/null
+++ b/docs/generic.md
@@ -0,0 +1,119 @@
+# nextpnr Generic Architecture
+
+Instead of implementing the [C++ API](archapi.md), you can programmatically
+build up a description of an FPGA using the generic architecture and the
+Python API.
+
+A basic packer is provided that supports LUTs, flipflops and IO buffer insertion.
+Packing could also be implemented using the Python API.
+
+At present there is no support for cell timing in the generic architecture. This
+will be worked on in the future.
+
+## Python API
+
+All identifiers (`IdString`) are automatically converted to
+and from a Python string, so no manual conversion is required.
+
+Argument names are included in the Python bindings,
+so named arguments may be used.
+
+### void addWire(IdString name, IdString type, int x, int y);
+
+Adds a wire with a name, type (for user purposes only, ignored by all nextpnr code other than the UI) to the FPGA description. x and y give a nominal location of the wire for delay estimation purposes. Delay estimates are important for router performance (as the router uses an A* type algorithm), even if timing is not of importance.
+
+### addPip(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay, Loc loc);
+
+Adds a pip (programmable connection between two named wires). Pip delays that correspond to delay estimates are important for router performance (as the router uses an A* type algorithm), even if timing is otherwise not of importance.
+
+Loc is constructed using `Loc(x, y, z)`. 'z' for pips is only important if region constraints (e.g. for partial reconfiguration regions) are used.
+
+### void addAlias(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay);
+
+Adds a wire alias (fixed connection between two named wires). Alias delays that correspond to delay estimates are important for router performance (as the router uses an A* type algorithm), even if timing is otherwise not of importance.
+
+### void addBel(IdString name, IdString type, Loc loc, bool gb);
+
+Adds a bel to the FPGA description. Bel type should match the type of cells in the netlist that are placed at this bel (see below for information on special bel types supported by the packer). Loc is constructed using `Loc(x, y, z)` and must be unique.
+
+### void addBelInput(IdString bel, IdString name, IdString wire);
+### void addBelOutput(IdString bel, IdString name, IdString wire);
+### void addBelInout(IdString bel, IdString name, IdString wire);
+
+Adds an input, output or inout pin to a bel, with an associated wire. Note that both `bel` and `wire` must have been created before calling this function.
+
+### void addGroupBel(IdString group, IdString bel);
+### void addGroupWire(IdString group, IdString wire);
+### void addGroupPip(IdString group, IdString pip);
+### void addGroupGroup(IdString group, IdString grp);
+
+Add a bel, wire, pip or subgroup to a group, which will be created if it doesn't already exist. Groups are purely for visual presentation purposes in the user interface and are not used by any place-and-route algorithms.
+
+### void addDecalGraphic(DecalId decal, const GraphicElement &graphic);
+
+Add a graphic element to a _decal_, a reusable drawing that may be used to represent multiple wires, pips, bels or groups in the UI (with different offsets). The decal will be created if it doesn't already exist
+
+### void setWireDecal(WireId wire, DecalXY decalxy);
+### void setPipDecal(PipId pip, DecalXY decalxy);
+### void setBelDecal(BelId bel, DecalXY decalxy);
+### void setGroupDecal(GroupId group, DecalXY decalxy);
+
+Sets the decal ID and offset for a wire, bel, pip or group in the UI.
+
+### void setWireAttr(IdString wire, IdString key, const std::string &value);
+### void setPipAttr(IdString pip, IdString key, const std::string &value);
+### void setBelAttr(IdString bel, IdString key, const std::string &value);
+
+Sets an attribute on a wire, pip or bel. Attributes are displayed in the tree view in the UI, but have no bearing on place-and-route itself.
+
+### void setLutK(int K);
+
+Sets the number of input pins a LUT in the architecture has. Only affects the generic packer, if a custom packer or no packer is used this value has no effect - there is no need for the architecture to have LUTs at all in this case.
+
+### void setDelayScaling(double scale, double offset);
+
+Set the linear scaling vs distance and fixed offset (both values in nanoseconds) for routing delay estimates.
+
+### void addCellTimingClock(IdString cell, IdString port);
+
+Set the timing class of a port on a particular cell to a clock input.
+
+_NOTE: All cell timing functions apply to an individual named cell and not a cell type. This is because
+cell-specific configuration might affect timing, e.g. whether or not the register is used for a slice._
+
+### void addCellTimingDelay(IdString cell, IdString fromPort, IdString toPort, DelayInfo delay);
+
+Specify the combinational delay between two ports of a cell, and set the timing class of
+ those ports as combinational input/output.
+
+### void addCellTimingSetupHold(IdString cell, IdString port, IdString clock, DelayInfo setup, DelayInfo hold);
+
+Specify setup and hold timings for a port of a cell, and set the timing class of that port as register input.
+
+### void addCellTimingClockToOut(IdString cell, IdString port, IdString clock, DelayInfo clktoq);
+
+Specify clock-to-out time for a port of a cell, and set the timing class of that port as register output.
+
+## Generic Packer
+
+The generic packer combines K-input LUTs (`LUT` cells) and simple D-type flip flops (`DFF` cells) (posedge clock only, no set/reset or enable) into a `GENERIC_SLICE` cell. It also inserts `GENERIC_IOB`s onto any top level IO pins without an IO buffer.
+
+Thus, the architecture should provide bels with the following ports in order to use the generic packer:
+
+ - `GENERIC_SLICE` bels with `CLK` input, `I[0]` .. `I[K-1]` LUT inputs and `Q` LUT/FF output (N.B. both LUT and FF outputs are not available at the same time)
+ - `GENERIC_IOB` bels with `I` output buffer input, `EN` output enable input, and `O` input buffer output.
+
+See [prims.v](../generic/synth/prims.v) for Verilog simulation models for all these cells.
+
+[synth_generic.tcl](../generic/synth/synth_generic.tcl) can be used with Yosys to perform synthesis to the generic `LUT` and `DFF` cells which the generic packer supports. Invoke it using `tcl synth_generic.tcl K out.json` where _K_ is the number of LUT inputs and _out.json_ the name of the JSON file to write.
+
+## Validity Checks
+
+The following constraints are enforced by the generic architecture during placement.
+
+ - `GENERIC_SLICE` bels may only have one clock signal per tile (xy location)
+ - If the `PACK_GROUP` attribute is set to a non-zero value on cells, then only cells with the same `PACK_GROUP` attribute (or `PACK_GROUP` negative or unset) may share a tile. This could be set by the Python API or during synthesis.
+
+## Implementation Example
+
+An artificial, procedural architecture is included in the [generic/examples](../generic/examples) folder. [simple.py](../generic/examples/simple.py) sets up the architecture, and [report.py](../generic/examples/report.py) saves post-place-and-route design to a text file (in place of bitstream generation). [simple.sh](../generic/examples/simple.sh) can be used to synthesise and place-and-route a simple blinky for this architecture.
diff --git a/ecp5/arch.cc b/ecp5/arch.cc
index 9da8abdf..91db8d81 100644
--- a/ecp5/arch.cc
+++ b/ecp5/arch.cc
@@ -458,7 +458,7 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const
int dx = abs(src_loc.first - dst_loc.first), dy = abs(src_loc.second - dst_loc.second);
- return (130 - 25 * args.speed) *
+ return (120 - 22 * args.speed) *
(6 + std::max(dx - 5, 0) + std::max(dy - 5, 0) + 2 * (std::min(dx, 5) + std::min(dy, 5)));
}
@@ -487,7 +487,7 @@ delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
int dx = abs(driver_loc.x - sink_loc.x), dy = abs(driver_loc.y - sink_loc.y);
- return (130 - 25 * args.speed) *
+ return (120 - 22 * args.speed) *
(6 + std::max(dx - 5, 0) + std::max(dy - 5, 0) + 2 * (std::min(dx, 5) + std::min(dy, 5)));
}
@@ -504,6 +504,8 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay
}
}
+delay_t Arch::getRipupDelayPenalty() const { return 400; }
+
// -----------------------------------------------------------------------
bool Arch::place()
@@ -512,7 +514,7 @@ bool Arch::place()
if (placer == "heap") {
PlacerHeapCfg cfg(getCtx());
- cfg.criticalityExponent = 7;
+ cfg.criticalityExponent = 4;
cfg.ioBufTypes.insert(id_TRELLIS_IO);
if (!placer_heap(getCtx(), cfg))
return false;
diff --git a/ecp5/arch.h b/ecp5/arch.h
index 3de06a42..cee071e7 100644
--- a/ecp5/arch.h
+++ b/ecp5/arch.h
@@ -942,7 +942,7 @@ struct Arch : BaseCtx
delay_t estimateDelay(WireId src, WireId dst) const;
delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const;
delay_t getDelayEpsilon() const { return 20; }
- delay_t getRipupDelayPenalty() const { return 400; }
+ delay_t getRipupDelayPenalty() const;
float getDelayNS(delay_t v) const { return v * 0.001; }
DelayInfo getDelayFromNS(float ns) const
{
diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc
index a9c82524..d549a727 100644
--- a/ecp5/bitstream.cc
+++ b/ecp5/bitstream.cc
@@ -134,7 +134,7 @@ inline int chtohex(char c)
return hex.find(c);
}
-std::vector<bool> parse_init_str(const std::string &str, int length)
+std::vector<bool> parse_init_str(const std::string &str, int length, const char *cellname)
{
// Parse a string that may be binary or hex
std::vector<bool> result;
@@ -161,7 +161,8 @@ std::vector<bool> parse_init_str(const std::string &str, int length)
log_error("hex string value too long, expected up to %d bits and found %d.\n", length, int(str.length()));
for (int i = 0; i < int(str.length()); i++) {
char c = str.at((str.size() - i) - 1);
- NPNR_ASSERT(c == '0' || c == '1' || c == 'X' || c == 'x');
+ if (c != '0' && c != '1' && c != 'X' && c != 'x')
+ log_error("Found illegal character '%c' while processing parameters for cell '%s'\n", c, cellname);
result.at(i) = (c == '1');
}
}
@@ -970,7 +971,7 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
for (int i = 0; i <= 0x3F; i++) {
IdString param = ctx->id("INITVAL_" +
fmt_str(std::hex << std::uppercase << std::setw(2) << std::setfill('0') << i));
- auto value = parse_init_str(str_or_default(ci->params, param, "0"), 320);
+ auto value = parse_init_str(str_or_default(ci->params, param, "0"), 320, ci->name.c_str(ctx));
for (int j = 0; j < 16; j++) {
// INIT parameter consists of 16 18-bit words with 2-bit padding
int ofs = 20 * j;
@@ -1078,17 +1079,21 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
tg.config.add_enum(dsp + ".MASKPAT_SOURCE",
str_or_default(ci->params, ctx->id("MASKPAT_SOURCE"), "STATIC"));
tg.config.add_word(dsp + ".MASK01",
- parse_init_str(str_or_default(ci->params, ctx->id("MASK01"), "0x00000000000000"), 56));
+ parse_init_str(str_or_default(ci->params, ctx->id("MASK01"), "0x00000000000000"), 56,
+ ci->name.c_str(ctx)));
tg.config.add_enum(dsp + ".CLK0_DIV", str_or_default(ci->params, ctx->id("CLK0_DIV"), "ENABLED"));
tg.config.add_enum(dsp + ".CLK1_DIV", str_or_default(ci->params, ctx->id("CLK1_DIV"), "ENABLED"));
tg.config.add_enum(dsp + ".CLK2_DIV", str_or_default(ci->params, ctx->id("CLK2_DIV"), "ENABLED"));
tg.config.add_enum(dsp + ".CLK3_DIV", str_or_default(ci->params, ctx->id("CLK3_DIV"), "ENABLED"));
tg.config.add_word(dsp + ".MCPAT",
- parse_init_str(str_or_default(ci->params, ctx->id("MCPAT"), "0x00000000000000"), 56));
+ parse_init_str(str_or_default(ci->params, ctx->id("MCPAT"), "0x00000000000000"), 56,
+ ci->name.c_str(ctx)));
tg.config.add_word(dsp + ".MASKPAT",
- parse_init_str(str_or_default(ci->params, ctx->id("MASKPAT"), "0x00000000000000"), 56));
+ parse_init_str(str_or_default(ci->params, ctx->id("MASKPAT"), "0x00000000000000"), 56,
+ ci->name.c_str(ctx)));
tg.config.add_word(dsp + ".RNDPAT",
- parse_init_str(str_or_default(ci->params, ctx->id("RNDPAT"), "0x00000000000000"), 56));
+ parse_init_str(str_or_default(ci->params, ctx->id("RNDPAT"), "0x00000000000000"), 56,
+ ci->name.c_str(ctx)));
tg.config.add_enum(dsp + ".GSR", str_or_default(ci->params, ctx->id("GSR"), "ENABLED"));
tg.config.add_enum(dsp + ".RESETMODE", str_or_default(ci->params, ctx->id("RESETMODE"), "SYNC"));
tg.config.add_enum(dsp + ".FORCE_ZERO_BARREL_SHIFT",
diff --git a/ecp5/family.cmake b/ecp5/family.cmake
index 799851b2..ca7dc9e9 100644
--- a/ecp5/family.cmake
+++ b/ecp5/family.cmake
@@ -6,18 +6,18 @@ if (NOT EXTERNAL_CHIPDB)
set(TRELLIS_ROOT "/usr/local/share/trellis")
endif()
- file(GLOB found_pytrellis ${TRELLIS_ROOT}/libtrellis/pytrellis.*
- /usr/lib/pytrellis.*
- /usr/lib64/pytrellis.*
- /usr/lib/trellis/pytrellis.*
- /usr/lib64/trellis/pytrellis.*)
+ if (NOT DEFINED PYTRELLIS_LIBDIR)
+ find_library(PYTRELLIS pytrellis.so
+ PATHS ${TRELLIS_ROOT}/libtrellis
+ PATH_SUFFIXES trellis
+ DOC "Location of pytrellis library")
- if ("${found_pytrellis}" STREQUAL "")
- message(FATAL_ERROR "failed to locate pytrellis library!")
- endif()
+ if ("${PYTRELLIS}" STREQUAL "PYTRELLIS-NOTFOUND")
+ message(FATAL_ERROR "Failed to locate pytrellis library!")
+ endif()
- list(GET found_pytrellis 0 PYTRELLIS_LIB)
- get_filename_component(PYTRELLIS_LIBDIR ${PYTRELLIS_LIB} DIRECTORY)
+ get_filename_component(PYTRELLIS_LIBDIR ${PYTRELLIS} DIRECTORY)
+ endif()
set(DB_PY ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/trellis_import.py)
@@ -27,9 +27,9 @@ if (NOT EXTERNAL_CHIPDB)
target_include_directories(ecp5_chipdb PRIVATE ${family}/)
if (CMAKE_HOST_WIN32)
- set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=\"${PYTRELLIS_LIBDIR}\;${TRELLIS_ROOT}/util/common\;${TRELLIS_ROOT}/timing/util\"")
+ set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=\"${PYTRELLIS_LIBDIR}\;${TRELLIS_ROOT}/util/common\;${TRELLIS_ROOT}/timing/util\"")
else()
- set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=${PYTRELLIS_LIBDIR}\:${TRELLIS_ROOT}/util/common:${TRELLIS_ROOT}/timing/util")
+ set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=${PYTRELLIS_LIBDIR}\:${TRELLIS_ROOT}/util/common:${TRELLIS_ROOT}/timing/util")
endif()
if (MSVC)
@@ -38,15 +38,15 @@ if (NOT EXTERNAL_CHIPDB)
foreach (dev ${devices})
set(DEV_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.bin)
set(DEV_CC_BBA_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.bba)
- set(DEV_CONSTIDS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/constids.inc)
+ set(DEV_CONSTIDS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/constids.inc)
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
- COMMAND ${ENV_CMD} python3 ${DB_PY} -p ${DEV_CONSTIDS_INC} ${dev} > ${DEV_CC_BBA_DB}
- DEPENDS ${DB_PY}
- )
+ COMMAND ${ENV_CMD} python3 ${DB_PY} -p ${DEV_CONSTIDS_INC} ${dev} > ${DEV_CC_BBA_DB}
+ DEPENDS ${DB_PY}
+ )
add_custom_command(OUTPUT ${DEV_CC_DB}
- COMMAND bbasm ${DEV_CC_BBA_DB} ${DEV_CC_DB}
- DEPENDS bbasm ${DEV_CC_BBA_DB}
- )
+ COMMAND bbasm ${DEV_CC_BBA_DB} ${DEV_CC_DB}
+ DEPENDS bbasm ${DEV_CC_BBA_DB}
+ )
target_sources(ecp5_chipdb PRIVATE ${DEV_CC_DB})
set_source_files_properties(${DEV_CC_DB} PROPERTIES HEADER_FILE_ONLY TRUE)
foreach (target ${family_targets})
@@ -58,17 +58,17 @@ if (NOT EXTERNAL_CHIPDB)
foreach (dev ${devices})
set(DEV_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.cc)
set(DEV_CC_BBA_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.bba)
- set(DEV_CONSTIDS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/constids.inc)
+ set(DEV_CONSTIDS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/constids.inc)
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
- COMMAND ${ENV_CMD} python3 ${DB_PY} -p ${DEV_CONSTIDS_INC} ${dev} > ${DEV_CC_BBA_DB}.new
- COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
- DEPENDS ${DB_PY}
- )
+ COMMAND ${ENV_CMD} python3 ${DB_PY} -p ${DEV_CONSTIDS_INC} ${dev} > ${DEV_CC_BBA_DB}.new
+ COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
+ DEPENDS ${DB_PY}
+ )
add_custom_command(OUTPUT ${DEV_CC_DB}
- COMMAND bbasm --c ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new
- COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB}
- DEPENDS bbasm ${DEV_CC_BBA_DB}
- )
+ COMMAND bbasm --c ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new
+ COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB}
+ DEPENDS bbasm ${DEV_CC_BBA_DB}
+ )
target_sources(ecp5_chipdb PRIVATE ${DEV_CC_DB})
foreach (target ${family_targets})
target_sources(${target} PRIVATE $<TARGET_OBJECTS:ecp5_chipdb>)
diff --git a/ecp5/pack.cc b/ecp5/pack.cc
index 1b07c2ae..7f00de1f 100644
--- a/ecp5/pack.cc
+++ b/ecp5/pack.cc
@@ -1390,6 +1390,19 @@ class Ecp5Packer
}
}
+ // Miscellaneous packer tasks
+ void pack_misc()
+ {
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type == id_USRMCLK) {
+ rename_port(ctx, ci, ctx->id("USRMCLKI"), id_PADDO);
+ rename_port(ctx, ci, ctx->id("USRMCLKTS"), id_PADDT);
+ rename_port(ctx, ci, ctx->id("USRMCLKO"), id_PADDI);
+ }
+ }
+ }
+
// Preplace PLL
void preplace_plls()
{
@@ -2371,6 +2384,7 @@ class Ecp5Packer
pack_ebr();
pack_dsps();
pack_dcus();
+ pack_misc();
preplace_plls();
pack_constants();
pack_dram();
diff --git a/generic/arch.cc b/generic/arch.cc
index aca81559..5617fa63 100644
--- a/generic/arch.cc
+++ b/generic/arch.cc
@@ -17,6 +17,7 @@
*
*/
+#include <iostream>
#include <math.h>
#include "nextpnr.h"
#include "placer1.h"
@@ -191,9 +192,53 @@ void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pi
void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bels.at(bel).attrs[key] = value; }
+void Arch::setLutK(int K) { args.K = K; }
+
+void Arch::setDelayScaling(double scale, double offset)
+{
+ args.delayScale = scale;
+ args.delayOffset = offset;
+}
+
+void Arch::addCellTimingClock(IdString cell, IdString port) { cellTiming[cell].portClasses[port] = TMG_CLOCK_INPUT; }
+
+void Arch::addCellTimingDelay(IdString cell, IdString fromPort, IdString toPort, DelayInfo delay)
+{
+ if (get_or_default(cellTiming[cell].portClasses, fromPort, TMG_IGNORE) == TMG_IGNORE)
+ cellTiming[cell].portClasses[fromPort] = TMG_COMB_INPUT;
+ if (get_or_default(cellTiming[cell].portClasses, toPort, TMG_IGNORE) == TMG_IGNORE)
+ cellTiming[cell].portClasses[toPort] = TMG_COMB_OUTPUT;
+ cellTiming[cell].combDelays[CellDelayKey{fromPort, toPort}] = delay;
+}
+
+void Arch::addCellTimingSetupHold(IdString cell, IdString port, IdString clock, DelayInfo setup, DelayInfo hold)
+{
+ TimingClockingInfo ci;
+ ci.clock_port = clock;
+ ci.edge = RISING_EDGE;
+ ci.setup = setup;
+ ci.hold = hold;
+ cellTiming[cell].clockingInfo[port].push_back(ci);
+ cellTiming[cell].portClasses[port] = TMG_REGISTER_INPUT;
+}
+
+void Arch::addCellTimingClockToOut(IdString cell, IdString port, IdString clock, DelayInfo clktoq)
+{
+ TimingClockingInfo ci;
+ ci.clock_port = clock;
+ ci.edge = RISING_EDGE;
+ ci.clockToQ = clktoq;
+ cellTiming[cell].clockingInfo[port].push_back(ci);
+ cellTiming[cell].portClasses[port] = TMG_REGISTER_OUTPUT;
+}
+
// ---------------------------------------------------------------
-Arch::Arch(ArchArgs args) : chipName("generic"), args(args) {}
+Arch::Arch(ArchArgs args) : chipName("generic"), args(args)
+{
+ // Dummy for empty decals
+ decal_graphics[IdString()];
+}
void IdString::initialize_arch(const BaseCtx *ctx) {}
@@ -260,7 +305,13 @@ IdString Arch::getBelType(BelId bel) const { return bels.at(bel).type; }
const std::map<IdString, std::string> &Arch::getBelAttrs(BelId bel) const { return bels.at(bel).attrs; }
-WireId Arch::getBelPinWire(BelId bel, IdString pin) const { return bels.at(bel).pins.at(pin).wire; }
+WireId Arch::getBelPinWire(BelId bel, IdString pin) const
+{
+ const auto &bdata = bels.at(bel);
+ if (!bdata.pins.count(pin))
+ log_error("bel '%s' has no pin '%s'\n", bel.c_str(this), pin.c_str(this));
+ return bdata.pins.at(pin).wire;
+}
PortType Arch::getBelPinType(BelId bel, IdString pin) const { return bels.at(bel).pins.at(pin).type; }
@@ -422,7 +473,7 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const
const WireInfo &d = wires.at(dst);
int dx = abs(s.x - d.x);
int dy = abs(s.y - d.y);
- return (dx + dy) * grid_distance_to_delay;
+ return (dx + dy) * args.delayScale + args.delayOffset;
}
delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
@@ -431,9 +482,9 @@ delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
auto driver_loc = getBelLocation(driver.cell->bel);
auto sink_loc = getBelLocation(sink.cell->bel);
- int dx = abs(driver_loc.x - driver_loc.x);
- int dy = abs(sink_loc.y - sink_loc.y);
- return (dx + dy) * grid_distance_to_delay;
+ int dx = abs(sink_loc.x - driver_loc.x);
+ int dy = abs(sink_loc.y - driver_loc.y);
+ return (dx + dy) * args.delayScale + args.delayOffset;
}
bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; }
@@ -455,7 +506,14 @@ bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); }
// ---------------------------------------------------------------
-const std::vector<GraphicElement> &Arch::getDecalGraphics(DecalId decal) const { return decal_graphics.at(decal); }
+const std::vector<GraphicElement> &Arch::getDecalGraphics(DecalId decal) const
+{
+ if (!decal_graphics.count(decal)) {
+ std::cerr << "No decal named " << decal.str(this) << std::endl;
+ log_error("No decal named %s!\n", decal.c_str(this));
+ }
+ return decal_graphics.at(decal);
+}
DecalXY Arch::getBelDecal(BelId bel) const { return bels.at(bel).decalxy; }
@@ -469,24 +527,103 @@ DecalXY Arch::getGroupDecal(GroupId group) const { return groups.at(group).decal
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const
{
- return false;
+ if (!cellTiming.count(cell->name))
+ return false;
+ const auto &tmg = cellTiming.at(cell->name);
+ auto fnd = tmg.combDelays.find(CellDelayKey{fromPort, toPort});
+ if (fnd != tmg.combDelays.end()) {
+ delay = fnd->second;
+ return true;
+ } else {
+ return false;
+ }
}
// Get the port class, also setting clockPort if applicable
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
{
- return TMG_IGNORE;
+ if (!cellTiming.count(cell->name))
+ return TMG_IGNORE;
+ const auto &tmg = cellTiming.at(cell->name);
+ if (tmg.clockingInfo.count(port))
+ clockInfoCount = int(tmg.clockingInfo.at(port).size());
+ else
+ clockInfoCount = 0;
+ return get_or_default(tmg.portClasses, port, TMG_IGNORE);
}
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
{
- NPNR_ASSERT_FALSE("no clocking info for generic");
+ NPNR_ASSERT(cellTiming.count(cell->name));
+ const auto &tmg = cellTiming.at(cell->name);
+ NPNR_ASSERT(tmg.clockingInfo.count(port));
+ return tmg.clockingInfo.at(port).at(index);
+}
+
+bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
+{
+ std::vector<const CellInfo *> cells;
+ cells.push_back(cell);
+ Loc loc = getBelLocation(bel);
+ for (auto tbel : getBelsByTile(loc.x, loc.y)) {
+ if (tbel == bel)
+ continue;
+ CellInfo *bound = getBoundBelCell(tbel);
+ if (bound != nullptr)
+ cells.push_back(bound);
+ }
+ return cellsCompatible(cells.data(), int(cells.size()));
}
-bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; }
-bool Arch::isBelLocationValid(BelId bel) const { return true; }
+bool Arch::isBelLocationValid(BelId bel) const
+{
+ std::vector<const CellInfo *> cells;
+ Loc loc = getBelLocation(bel);
+ for (auto tbel : getBelsByTile(loc.x, loc.y)) {
+ CellInfo *bound = getBoundBelCell(tbel);
+ if (bound != nullptr)
+ cells.push_back(bound);
+ }
+ return cellsCompatible(cells.data(), int(cells.size()));
+}
const std::string Arch::defaultPlacer = "sa";
const std::vector<std::string> Arch::availablePlacers = {"sa"};
+void Arch::assignArchInfo()
+{
+ for (auto &cell : getCtx()->cells) {
+ CellInfo *ci = cell.second.get();
+ if (ci->type == id("GENERIC_SLICE")) {
+ ci->is_slice = true;
+ ci->slice_clk = get_net_or_empty(ci, id("CLK"));
+ } else {
+ ci->is_slice = false;
+ }
+ ci->user_group = int_or_default(ci->attrs, id("PACK_GROUP"), -1);
+ }
+}
+
+bool Arch::cellsCompatible(const CellInfo **cells, int count) const
+{
+ const NetInfo *clk = nullptr;
+ int group = -1;
+ for (int i = 0; i < count; i++) {
+ const CellInfo *ci = cells[i];
+ if (ci->is_slice && ci->slice_clk != nullptr) {
+ if (clk == nullptr)
+ clk = ci->slice_clk;
+ else if (clk != ci->slice_clk)
+ return false;
+ }
+ if (ci->user_group != -1) {
+ if (group == -1)
+ group = ci->user_group;
+ else if (group != ci->user_group)
+ return false;
+ }
+ }
+ return true;
+}
+
NEXTPNR_NAMESPACE_END
diff --git a/generic/arch.h b/generic/arch.h
index 5b5d8c55..e9d3593c 100644
--- a/generic/arch.h
+++ b/generic/arch.h
@@ -25,6 +25,11 @@ NEXTPNR_NAMESPACE_BEGIN
struct ArchArgs
{
+ // Number of LUT inputs
+ int K = 4;
+ // y = mx + c relationship between distance and delay for interconnect
+ // delay estimates
+ double delayScale = 0.1, delayOffset = 0;
};
struct WireInfo;
@@ -81,6 +86,33 @@ struct GroupInfo
DecalXY decalxy;
};
+struct CellDelayKey
+{
+ IdString from, to;
+ inline bool operator==(const CellDelayKey &other) const { return from == other.from && to == other.to; }
+};
+
+NEXTPNR_NAMESPACE_END
+namespace std {
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX CellDelayKey>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX CellDelayKey &dk) const noexcept
+ {
+ std::size_t seed = std::hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(dk.from);
+ seed ^= std::hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(dk.to) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+ return seed;
+ }
+};
+} // namespace std
+NEXTPNR_NAMESPACE_BEGIN
+
+struct CellTiming
+{
+ std::unordered_map<IdString, TimingPortClass> portClasses;
+ std::unordered_map<CellDelayKey, DelayInfo> combDelays;
+ std::unordered_map<IdString, std::vector<TimingClockingInfo>> clockingInfo;
+};
+
struct Arch : BaseCtx
{
std::string chipName;
@@ -101,7 +133,7 @@ struct Arch : BaseCtx
std::vector<std::vector<int>> tileBelDimZ;
std::vector<std::vector<int>> tilePipDimZ;
- float grid_distance_to_delay;
+ std::unordered_map<IdString, CellTiming> cellTiming;
void addWire(IdString name, IdString type, int x, int y);
void addPip(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay, Loc loc);
@@ -127,6 +159,14 @@ struct Arch : BaseCtx
void setPipAttr(IdString pip, IdString key, const std::string &value);
void setBelAttr(IdString bel, IdString key, const std::string &value);
+ void setLutK(int K);
+ void setDelayScaling(double scale, double offset);
+
+ void addCellTimingClock(IdString cell, IdString port);
+ void addCellTimingDelay(IdString cell, IdString fromPort, IdString toPort, DelayInfo delay);
+ void addCellTimingSetupHold(IdString cell, IdString port, IdString clock, DelayInfo setup, DelayInfo hold);
+ void addCellTimingClockToOut(IdString cell, IdString port, IdString clock, DelayInfo clktoq);
+
// ---------------------------------------------------------------
// Common Arch API. Every arch must provide the following methods.
@@ -208,8 +248,8 @@ struct Arch : BaseCtx
delay_t estimateDelay(WireId src, WireId dst) const;
delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const;
- delay_t getDelayEpsilon() const { return 0.01; }
- delay_t getRipupDelayPenalty() const { return 1.0; }
+ delay_t getDelayEpsilon() const { return 0.001; }
+ delay_t getRipupDelayPenalty() const { return 0.015; }
float getDelayNS(delay_t v) const { return v; }
DelayInfo getDelayFromNS(float ns) const
@@ -222,7 +262,7 @@ struct Arch : BaseCtx
uint32_t getDelayChecksum(delay_t v) const { return 0; }
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
- bool pack() { return true; }
+ bool pack();
bool place();
bool route();
@@ -243,6 +283,11 @@ struct Arch : BaseCtx
static const std::string defaultPlacer;
static const std::vector<std::string> availablePlacers;
+
+ // ---------------------------------------------------------------
+ // Internal usage
+ void assignArchInfo();
+ bool cellsCompatible(const CellInfo **cells, int count) const;
};
NEXTPNR_NAMESPACE_END
diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc
index 186b2c13..8526e409 100644
--- a/generic/arch_pybindings.cc
+++ b/generic/arch_pybindings.cc
@@ -23,18 +23,215 @@
#include "arch_pybindings.h"
#include "nextpnr.h"
#include "pybindings.h"
+#include "pywrappers.h"
NEXTPNR_NAMESPACE_BEGIN
+namespace PythonConversion {
+template <> struct string_converter<const IdString &>
+{
+ const IdString &from_str(Context *ctx, std::string name) { NPNR_ASSERT_FALSE("unsupported"); }
+
+ std::string to_str(Context *ctx, const IdString &id) { return id.str(ctx); }
+};
+} // namespace PythonConversion
void arch_wrap_python()
{
using namespace PythonConversion;
+
auto arch_cls = class_<Arch, Arch *, bases<BaseCtx>, boost::noncopyable>("Arch", init<ArchArgs>());
+
+ auto dxy_cls = class_<ContextualWrapper<DecalXY>>("DecalXY_", no_init);
+ readwrite_wrapper<DecalXY, decltype(&DecalXY::decal), &DecalXY::decal, conv_to_str<DecalId>,
+ conv_from_str<DecalId>>::def_wrap(dxy_cls, "decal");
+ readwrite_wrapper<DecalXY, decltype(&DecalXY::x), &DecalXY::x, pass_through<float>, pass_through<float>>::def_wrap(
+ dxy_cls, "x");
+ readwrite_wrapper<DecalXY, decltype(&DecalXY::y), &DecalXY::y, pass_through<float>, pass_through<float>>::def_wrap(
+ dxy_cls, "y");
+
auto ctx_cls = class_<Context, Context *, bases<Arch>, boost::noncopyable>("Context", no_init)
.def("checksum", &Context::checksum)
.def("pack", &Context::pack)
.def("place", &Context::place)
.def("route", &Context::route);
+
+ class_<BelPin>("BelPin").def_readwrite("bel", &BelPin::bel).def_readwrite("pin", &BelPin::pin);
+
+ class_<DelayInfo>("DelayInfo").def("maxDelay", &DelayInfo::maxDelay).def("minDelay", &DelayInfo::minDelay);
+
+ fn_wrapper_1a<Context, decltype(&Context::getBelType), &Context::getBelType, conv_to_str<IdString>,
+ conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelType");
+ fn_wrapper_1a<Context, decltype(&Context::checkBelAvail), &Context::checkBelAvail, pass_through<bool>,
+ conv_from_str<BelId>>::def_wrap(ctx_cls, "checkBelAvail");
+ fn_wrapper_1a<Context, decltype(&Context::getBelChecksum), &Context::getBelChecksum, pass_through<uint32_t>,
+ conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelChecksum");
+ fn_wrapper_3a_v<Context, decltype(&Context::bindBel), &Context::bindBel, conv_from_str<BelId>,
+ addr_and_unwrap<CellInfo>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindBel");
+ fn_wrapper_1a_v<Context, decltype(&Context::unbindBel), &Context::unbindBel, conv_from_str<BelId>>::def_wrap(
+ ctx_cls, "unbindBel");
+ fn_wrapper_1a<Context, decltype(&Context::getBoundBelCell), &Context::getBoundBelCell, deref_and_wrap<CellInfo>,
+ conv_from_str<BelId>>::def_wrap(ctx_cls, "getBoundBelCell");
+ fn_wrapper_1a<Context, decltype(&Context::getConflictingBelCell), &Context::getConflictingBelCell,
+ deref_and_wrap<CellInfo>, conv_from_str<BelId>>::def_wrap(ctx_cls, "getConflictingBelCell");
+ fn_wrapper_0a<Context, decltype(&Context::getBels), &Context::getBels,
+ wrap_context<const std::vector<BelId> &>>::def_wrap(ctx_cls, "getBels");
+
+ fn_wrapper_2a<Context, decltype(&Context::getBelPinWire), &Context::getBelPinWire, conv_to_str<WireId>,
+ conv_from_str<BelId>, conv_from_str<IdString>>::def_wrap(ctx_cls, "getBelPinWire");
+ fn_wrapper_1a<Context, decltype(&Context::getWireBelPins), &Context::getWireBelPins,
+ wrap_context<const std::vector<BelPin> &>, conv_from_str<WireId>>::def_wrap(ctx_cls,
+ "getWireBelPins");
+
+ fn_wrapper_1a<Context, decltype(&Context::getWireChecksum), &Context::getWireChecksum, pass_through<uint32_t>,
+ conv_from_str<WireId>>::def_wrap(ctx_cls, "getWireChecksum");
+ fn_wrapper_3a_v<Context, decltype(&Context::bindWire), &Context::bindWire, conv_from_str<WireId>,
+ addr_and_unwrap<NetInfo>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindWire");
+ fn_wrapper_1a_v<Context, decltype(&Context::unbindWire), &Context::unbindWire, conv_from_str<WireId>>::def_wrap(
+ ctx_cls, "unbindWire");
+ fn_wrapper_1a<Context, decltype(&Context::checkWireAvail), &Context::checkWireAvail, pass_through<bool>,
+ conv_from_str<WireId>>::def_wrap(ctx_cls, "checkWireAvail");
+ fn_wrapper_1a<Context, decltype(&Context::getBoundWireNet), &Context::getBoundWireNet, deref_and_wrap<NetInfo>,
+ conv_from_str<WireId>>::def_wrap(ctx_cls, "getBoundWireNet");
+ fn_wrapper_1a<Context, decltype(&Context::getConflictingWireNet), &Context::getConflictingWireNet,
+ deref_and_wrap<NetInfo>, conv_from_str<WireId>>::def_wrap(ctx_cls, "getConflictingWireNet");
+
+ fn_wrapper_0a<Context, decltype(&Context::getWires), &Context::getWires,
+ wrap_context<const std::vector<WireId> &>>::def_wrap(ctx_cls, "getWires");
+
+ fn_wrapper_0a<Context, decltype(&Context::getPips), &Context::getPips,
+ wrap_context<const std::vector<PipId> &>>::def_wrap(ctx_cls, "getPips");
+ fn_wrapper_1a<Context, decltype(&Context::getPipChecksum), &Context::getPipChecksum, pass_through<uint32_t>,
+ conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipChecksum");
+ fn_wrapper_3a_v<Context, decltype(&Context::bindPip), &Context::bindPip, conv_from_str<PipId>,
+ addr_and_unwrap<NetInfo>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindPip");
+ fn_wrapper_1a_v<Context, decltype(&Context::unbindPip), &Context::unbindPip, conv_from_str<PipId>>::def_wrap(
+ ctx_cls, "unbindPip");
+ fn_wrapper_1a<Context, decltype(&Context::checkPipAvail), &Context::checkPipAvail, pass_through<bool>,
+ conv_from_str<PipId>>::def_wrap(ctx_cls, "checkPipAvail");
+ fn_wrapper_1a<Context, decltype(&Context::getBoundPipNet), &Context::getBoundPipNet, deref_and_wrap<NetInfo>,
+ conv_from_str<PipId>>::def_wrap(ctx_cls, "getBoundPipNet");
+ fn_wrapper_1a<Context, decltype(&Context::getConflictingPipNet), &Context::getConflictingPipNet,
+ deref_and_wrap<NetInfo>, conv_from_str<PipId>>::def_wrap(ctx_cls, "getConflictingPipNet");
+
+ fn_wrapper_1a<Context, decltype(&Context::getPipsDownhill), &Context::getPipsDownhill,
+ wrap_context<const std::vector<PipId> &>, conv_from_str<WireId>>::def_wrap(ctx_cls,
+ "getPipsDownhill");
+ fn_wrapper_1a<Context, decltype(&Context::getPipsUphill), &Context::getPipsUphill,
+ wrap_context<const std::vector<PipId> &>, conv_from_str<WireId>>::def_wrap(ctx_cls, "getPipsUphill");
+ fn_wrapper_1a<Context, decltype(&Context::getWireAliases), &Context::getWireAliases,
+ wrap_context<const std::vector<PipId> &>, conv_from_str<WireId>>::def_wrap(ctx_cls, "getWireAliases");
+
+ fn_wrapper_1a<Context, decltype(&Context::getPipSrcWire), &Context::getPipSrcWire, conv_to_str<WireId>,
+ conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipSrcWire");
+ fn_wrapper_1a<Context, decltype(&Context::getPipDstWire), &Context::getPipDstWire, conv_to_str<WireId>,
+ conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipDstWire");
+ fn_wrapper_1a<Context, decltype(&Context::getPipDelay), &Context::getPipDelay, pass_through<DelayInfo>,
+ conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipDelay");
+
+ fn_wrapper_1a<Context, decltype(&Context::getDelayFromNS), &Context::getDelayFromNS, pass_through<DelayInfo>,
+ pass_through<double>>::def_wrap(ctx_cls, "getDelayFromNS");
+
+ fn_wrapper_0a<Context, decltype(&Context::getChipName), &Context::getChipName, pass_through<std::string>>::def_wrap(
+ ctx_cls, "getChipName");
+ fn_wrapper_0a<Context, decltype(&Context::archId), &Context::archId, conv_to_str<IdString>>::def_wrap(ctx_cls,
+ "archId");
+
+ fn_wrapper_3a<Context, decltype(&Context::constructDecalXY), &Context::constructDecalXY, wrap_context<DecalXY>,
+ conv_from_str<DecalId>, pass_through<float>, pass_through<float>>::def_wrap(ctx_cls, "DecalXY");
+
+ typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap;
+ typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap;
+
+ readonly_wrapper<Context, decltype(&Context::cells), &Context::cells, wrap_context<CellMap &>>::def_wrap(ctx_cls,
+ "cells");
+ readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls,
+ "nets");
+
+ fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>,
+ pass_through<float>>::def_wrap(ctx_cls, "addClock");
+
+ // Generic arch construction API
+ fn_wrapper_4a_v<Context, decltype(&Context::addWire), &Context::addWire, conv_from_str<IdString>,
+ conv_from_str<IdString>, pass_through<int>, pass_through<int>>::def_wrap(ctx_cls, "addWire",
+ (arg("name"), "type", "x",
+ "y"));
+ fn_wrapper_6a_v<Context, decltype(&Context::addPip), &Context::addPip, conv_from_str<IdString>,
+ conv_from_str<IdString>, conv_from_str<IdString>, conv_from_str<IdString>, pass_through<DelayInfo>,
+ pass_through<Loc>>::def_wrap(ctx_cls, "addPip",
+ (arg("name"), "type", "srcWire", "dstWire", "delay", "loc"));
+ fn_wrapper_5a_v<Context, decltype(&Context::addAlias), &Context::addAlias, conv_from_str<IdString>,
+ conv_from_str<IdString>, conv_from_str<IdString>, conv_from_str<IdString>,
+ pass_through<DelayInfo>>::def_wrap(ctx_cls, "addAlias",
+ (arg("name"), "type", "srcWire", "dstWire", "delay"));
+
+ fn_wrapper_4a_v<Context, decltype(&Context::addBel), &Context::addBel, conv_from_str<IdString>,
+ conv_from_str<IdString>, pass_through<Loc>, pass_through<bool>>::def_wrap(ctx_cls, "addBel",
+ (arg("name"), "type",
+ "loc", "gb"));
+ fn_wrapper_3a_v<Context, decltype(&Context::addBelInput), &Context::addBelInput, conv_from_str<IdString>,
+ conv_from_str<IdString>, conv_from_str<IdString>>::def_wrap(ctx_cls, "addBelInput",
+ (arg("bel"), "name", "wire"));
+ fn_wrapper_3a_v<Context, decltype(&Context::addBelOutput), &Context::addBelOutput, conv_from_str<IdString>,
+ conv_from_str<IdString>, conv_from_str<IdString>>::def_wrap(ctx_cls, "addBelOutput",
+ (arg("bel"), "name", "wire"));
+ fn_wrapper_3a_v<Context, decltype(&Context::addBelInout), &Context::addBelInout, conv_from_str<IdString>,
+ conv_from_str<IdString>, conv_from_str<IdString>>::def_wrap(ctx_cls, "addBelInout",
+ (arg("bel"), "name", "wire"));
+
+ fn_wrapper_2a_v<Context, decltype(&Context::addGroupBel), &Context::addGroupBel, conv_from_str<IdString>,
+ conv_from_str<IdString>>::def_wrap(ctx_cls, "addGroupBel", (arg("group"), "bel"));
+ fn_wrapper_2a_v<Context, decltype(&Context::addGroupWire), &Context::addGroupWire, conv_from_str<IdString>,
+ conv_from_str<IdString>>::def_wrap(ctx_cls, "addGroupWire", (arg("group"), "wire"));
+ fn_wrapper_2a_v<Context, decltype(&Context::addGroupPip), &Context::addGroupPip, conv_from_str<IdString>,
+ conv_from_str<IdString>>::def_wrap(ctx_cls, "addGroupPip", (arg("group"), "pip"));
+ fn_wrapper_2a_v<Context, decltype(&Context::addGroupGroup), &Context::addGroupPip, conv_from_str<IdString>,
+ conv_from_str<IdString>>::def_wrap(ctx_cls, "addGroupGroup", (arg("group"), "grp"));
+
+ fn_wrapper_2a_v<Context, decltype(&Context::addDecalGraphic), &Context::addDecalGraphic, conv_from_str<DecalId>,
+ pass_through<GraphicElement>>::def_wrap(ctx_cls, "addDecalGraphic", (arg("decal"), "graphic"));
+ fn_wrapper_2a_v<Context, decltype(&Context::setWireDecal), &Context::setWireDecal, conv_from_str<DecalId>,
+ unwrap_context<DecalXY>>::def_wrap(ctx_cls, "setWireDecal", (arg("wire"), "decalxy"));
+ fn_wrapper_2a_v<Context, decltype(&Context::setPipDecal), &Context::setPipDecal, conv_from_str<DecalId>,
+ unwrap_context<DecalXY>>::def_wrap(ctx_cls, "setPipDecal", (arg("pip"), "decalxy"));
+ fn_wrapper_2a_v<Context, decltype(&Context::setBelDecal), &Context::setBelDecal, conv_from_str<DecalId>,
+ unwrap_context<DecalXY>>::def_wrap(ctx_cls, "setBelDecal", (arg("bel"), "decalxy"));
+ fn_wrapper_2a_v<Context, decltype(&Context::setGroupDecal), &Context::setGroupDecal, conv_from_str<DecalId>,
+ unwrap_context<DecalXY>>::def_wrap(ctx_cls, "setGroupDecal", (arg("group"), "decalxy"));
+
+ fn_wrapper_3a_v<Context, decltype(&Context::setWireAttr), &Context::setWireAttr, conv_from_str<DecalId>,
+ conv_from_str<IdString>, pass_through<std::string>>::def_wrap(ctx_cls, "setWireAttr",
+ (arg("wire"), "key", "value"));
+ fn_wrapper_3a_v<Context, decltype(&Context::setBelAttr), &Context::setBelAttr, conv_from_str<DecalId>,
+ conv_from_str<IdString>, pass_through<std::string>>::def_wrap(ctx_cls, "setBelAttr",
+ (arg("bel"), "key", "value"));
+ fn_wrapper_3a_v<Context, decltype(&Context::setPipAttr), &Context::setPipAttr, conv_from_str<DecalId>,
+ conv_from_str<IdString>, pass_through<std::string>>::def_wrap(ctx_cls, "setPipAttr",
+ (arg("pip"), "key", "value"));
+
+ fn_wrapper_1a_v<Context, decltype(&Context::setLutK), &Context::setLutK, pass_through<int>>::def_wrap(
+ ctx_cls, "setLutK", arg("K"));
+ fn_wrapper_2a_v<Context, decltype(&Context::setDelayScaling), &Context::setDelayScaling, pass_through<double>,
+ pass_through<double>>::def_wrap(ctx_cls, "setDelayScaling", (arg("scale"), "offset"));
+
+ fn_wrapper_2a_v<Context, decltype(&Context::addCellTimingClock), &Context::addCellTimingClock,
+ conv_from_str<IdString>, conv_from_str<IdString>>::def_wrap(ctx_cls, "addCellTimingClock",
+ (arg("cell"), "port"));
+ fn_wrapper_4a_v<Context, decltype(&Context::addCellTimingDelay), &Context::addCellTimingDelay,
+ conv_from_str<IdString>, conv_from_str<IdString>, conv_from_str<IdString>,
+ pass_through<DelayInfo>>::def_wrap(ctx_cls, "addCellTimingDelay",
+ (arg("cell"), "fromPort", "toPort", "delay"));
+ fn_wrapper_5a_v<Context, decltype(&Context::addCellTimingSetupHold), &Context::addCellTimingSetupHold,
+ conv_from_str<IdString>, conv_from_str<IdString>, conv_from_str<IdString>, pass_through<DelayInfo>,
+ pass_through<DelayInfo>>::def_wrap(ctx_cls, "addCellTimingSetupHold",
+ (arg("cell"), "port", "clock", "setup", "hold"));
+ fn_wrapper_4a_v<Context, decltype(&Context::addCellTimingClockToOut), &Context::addCellTimingClockToOut,
+ conv_from_str<IdString>, conv_from_str<IdString>, conv_from_str<IdString>,
+ pass_through<DelayInfo>>::def_wrap(ctx_cls, "addCellTimingClockToOut",
+ (arg("cell"), "port", "clock", "clktoq"));
+
+ WRAP_MAP_UPTR(CellMap, "IdCellMap");
+ WRAP_MAP_UPTR(NetMap, "IdNetMap");
+ WRAP_VECTOR(const std::vector<IdString>, conv_to_str<IdString>);
}
NEXTPNR_NAMESPACE_END
diff --git a/generic/archdefs.h b/generic/archdefs.h
index 2452aa17..978c9c9b 100644
--- a/generic/archdefs.h
+++ b/generic/archdefs.h
@@ -55,8 +55,18 @@ typedef IdString DecalId;
struct ArchNetInfo
{
};
+
+struct NetInfo;
+
struct ArchCellInfo
{
+ // Custom grouping set via "PACK_GROUP" attribute. All cells with the same group
+ // value may share a tile (-1 = don't care, default if not set)
+ int user_group;
+ // Is a slice type primitive
+ bool is_slice;
+ // Only packing rule for slice type primitives is a single clock per tile
+ const NetInfo *slice_clk;
};
NEXTPNR_NAMESPACE_END
diff --git a/generic/cells.cc b/generic/cells.cc
new file mode 100644
index 00000000..14b368b2
--- /dev/null
+++ b/generic/cells.cc
@@ -0,0 +1,139 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2019 David Shah <david@symbioticeda.com>
+ *
+ * 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 "cells.h"
+#include "design_utils.h"
+#include "log.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+void add_port(const Context *ctx, CellInfo *cell, std::string name, PortType dir)
+{
+ IdString id = ctx->id(name);
+ NPNR_ASSERT(cell->ports.count(id) == 0);
+ cell->ports[id] = PortInfo{id, nullptr, dir};
+}
+
+std::unique_ptr<CellInfo> create_generic_cell(Context *ctx, IdString type, std::string name)
+{
+ static int auto_idx = 0;
+ std::unique_ptr<CellInfo> new_cell = std::unique_ptr<CellInfo>(new CellInfo());
+ if (name.empty()) {
+ new_cell->name = ctx->id("$nextpnr_" + type.str(ctx) + "_" + std::to_string(auto_idx++));
+ } else {
+ new_cell->name = ctx->id(name);
+ }
+ new_cell->type = type;
+ if (type == ctx->id("GENERIC_SLICE")) {
+ new_cell->params[ctx->id("K")] = std::to_string(ctx->args.K);
+ new_cell->params[ctx->id("INIT")] = "0";
+ new_cell->params[ctx->id("FF_USED")] = "0";
+
+ for (int i = 0; i < ctx->args.K; i++)
+ add_port(ctx, new_cell.get(), "I[" + std::to_string(i) + "]", PORT_IN);
+
+ add_port(ctx, new_cell.get(), "CLK", PORT_IN);
+
+ add_port(ctx, new_cell.get(), "Q", PORT_OUT);
+ } else if (type == ctx->id("GENERIC_IOB")) {
+ new_cell->params[ctx->id("INPUT_USED")] = "0";
+ new_cell->params[ctx->id("OUTPUT_USED")] = "0";
+ new_cell->params[ctx->id("ENABLE_USED")] = "0";
+
+ add_port(ctx, new_cell.get(), "PAD", PORT_INOUT);
+ add_port(ctx, new_cell.get(), "I", PORT_IN);
+ add_port(ctx, new_cell.get(), "EN", PORT_IN);
+ add_port(ctx, new_cell.get(), "O", PORT_OUT);
+ } else {
+ log_error("unable to create generic cell of type %s", type.c_str(ctx));
+ }
+ return new_cell;
+}
+
+void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff)
+{
+ lc->params[ctx->id("INIT")] = lut->params[ctx->id("INIT")];
+
+ int lut_k = int_or_default(lut->params, ctx->id("K"), 4);
+ NPNR_ASSERT(lut_k <= ctx->args.K);
+
+ for (int i = 0; i < lut_k; i++) {
+ IdString port = ctx->id("I[" + std::to_string(i) + "]");
+ replace_port(lut, port, lc, port);
+ }
+
+ if (no_dff) {
+ replace_port(lut, ctx->id("Q"), lc, ctx->id("Q"));
+ lc->params[ctx->id("FF_USED")] = "0";
+ }
+}
+
+void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut)
+{
+ lc->params[ctx->id("FF_USED")] = "1";
+ replace_port(dff, ctx->id("CLK"), lc, ctx->id("CLK"));
+
+ if (pass_thru_lut) {
+ lc->params[ctx->id("INIT")] = "2";
+ replace_port(dff, ctx->id("D"), lc, ctx->id("I[0]"));
+ }
+
+ replace_port(dff, ctx->id("Q"), lc, ctx->id("Q"));
+}
+
+void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *iob, std::unordered_set<IdString> &todelete_cells)
+{
+ if (nxio->type == ctx->id("$nextpnr_ibuf")) {
+ iob->params[ctx->id("INPUT_USED")] = "1";
+ replace_port(nxio, ctx->id("O"), iob, ctx->id("O"));
+ } else if (nxio->type == ctx->id("$nextpnr_obuf")) {
+ iob->params[ctx->id("OUTPUT_USED")] = "1";
+ replace_port(nxio, ctx->id("I"), iob, ctx->id("I"));
+ } else if (nxio->type == ctx->id("$nextpnr_iobuf")) {
+ // N.B. tristate will be dealt with below
+ iob->params[ctx->id("INPUT_USED")] = "1";
+ iob->params[ctx->id("OUTPUT_USED")] = "1";
+ replace_port(nxio, ctx->id("I"), iob, ctx->id("I"));
+ replace_port(nxio, ctx->id("O"), iob, ctx->id("O"));
+ } else {
+ NPNR_ASSERT(false);
+ }
+ NetInfo *donet = iob->ports.at(ctx->id("I")).net;
+ CellInfo *tbuf = net_driven_by(
+ ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
+ ctx->id("Y"));
+ if (tbuf) {
+ iob->params[ctx->id("ENABLE_USED")] = "1";
+ replace_port(tbuf, ctx->id("A"), iob, ctx->id("I"));
+ replace_port(tbuf, ctx->id("E"), iob, ctx->id("EN"));
+
+ if (donet->users.size() > 1) {
+ for (auto user : donet->users)
+ log_info(" remaining tristate user: %s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx));
+ log_error("unsupported tristate IO pattern for IO buffer '%s', "
+ "instantiate GENERIC_IOB manually to ensure correct behaviour\n",
+ nxio->name.c_str(ctx));
+ }
+ ctx->nets.erase(donet->name);
+ todelete_cells.insert(tbuf->name);
+ }
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/generic/cells.h b/generic/cells.h
new file mode 100644
index 00000000..646d738d
--- /dev/null
+++ b/generic/cells.h
@@ -0,0 +1,55 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2019 David Shah <david@symbioticeda.com>
+ *
+ * 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 "nextpnr.h"
+
+#ifndef GENERIC_CELLS_H
+#define GENERIC_CELLS_H
+
+NEXTPNR_NAMESPACE_BEGIN
+
+// Create a generic arch cell and return it
+// Name will be automatically assigned if not specified
+std::unique_ptr<CellInfo> create_generic_cell(Context *ctx, IdString type, std::string name = "");
+
+// Return true if a cell is a LUT
+inline bool is_lut(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("LUT"); }
+
+// Return true if a cell is a flipflop
+inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("DFF"); }
+
+inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("GENERIC_SLICE"); }
+
+// Convert a LUT primitive to (part of) an GENERIC_SLICE, swapping ports
+// as needed. Set no_dff if a DFF is not being used, so that the output
+// can be reconnected
+void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff = true);
+
+// Convert a DFF primitive to (part of) an GENERIC_SLICE, setting parameters
+// and reconnecting signals as necessary. If pass_thru_lut is True, the LUT will
+// be configured as pass through and D connected to I0, otherwise D will be
+// ignored
+void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut = false);
+
+// Convert a nextpnr IO buffer to a GENERIC_IOB
+void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *sbio, std::unordered_set<IdString> &todelete_cells);
+
+NEXTPNR_NAMESPACE_END
+
+#endif
diff --git a/generic/examples/.gitignore b/generic/examples/.gitignore
new file mode 100644
index 00000000..38e95de5
--- /dev/null
+++ b/generic/examples/.gitignore
@@ -0,0 +1,3 @@
+blinky.fasm
+__pycache__
+*.pyc
diff --git a/generic/examples/README.md b/generic/examples/README.md
new file mode 100644
index 00000000..9fd106d9
--- /dev/null
+++ b/generic/examples/README.md
@@ -0,0 +1,14 @@
+# Generic Architecture Example
+
+This contains a simple, artificial, example of the nextpnr generic API.
+
+ - simple.py procedurally generates a simple FPGA architecture with IO at the edges,
+ logic slices in all other tiles, and interconnect only between adjacent tiles
+
+ - simple_timing.py annotates cells with timing data (this is a separate script that must be run after packing)
+
+ - write_fasm.py uses the nextpnr Python API to write a FASM file for a design
+
+ - bitstream.py uses write_fasm.py to create a FASM ("FPGA assembly") file for the place-and-routed design
+
+ - Run simple.sh to build an example design on the FPGA above \ No newline at end of file
diff --git a/generic/examples/__init__.py b/generic/examples/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/generic/examples/__init__.py
diff --git a/generic/examples/bitstream.py b/generic/examples/bitstream.py
new file mode 100644
index 00000000..1ab94f0c
--- /dev/null
+++ b/generic/examples/bitstream.py
@@ -0,0 +1,17 @@
+from write_fasm import *
+from simple_config import K
+
+# Need to tell FASM generator how to write parameters
+# (celltype, parameter) -> ParameterConfig
+param_map = {
+ ("GENERIC_SLICE", "K"): ParameterConfig(write=False),
+ ("GENERIC_SLICE", "INIT"): ParameterConfig(write=True, numeric=True, width=2**K),
+ ("GENERIC_SLICE", "FF_USED"): ParameterConfig(write=True, numeric=True, width=1),
+
+ ("GENERIC_IOB", "INPUT_USED"): ParameterConfig(write=True, numeric=True, width=1),
+ ("GENERIC_IOB", "OUTPUT_USED"): ParameterConfig(write=True, numeric=True, width=1),
+ ("GENERIC_IOB", "ENABLE_USED"): ParameterConfig(write=True, numeric=True, width=1),
+}
+
+with open("blinky.fasm", "w") as f:
+ write_fasm(ctx, param_map, f) \ No newline at end of file
diff --git a/generic/examples/blinky.v b/generic/examples/blinky.v
new file mode 100644
index 00000000..b7cb1b86
--- /dev/null
+++ b/generic/examples/blinky.v
@@ -0,0 +1,9 @@
+module top(input clk, output reg [7:0] leds);
+
+reg [25:0] ctr;
+always @(posedge clk)
+ ctr <= ctr + 1'b1;
+
+assign leds = ctr[25:18];
+
+endmodule \ No newline at end of file
diff --git a/generic/examples/simple.py b/generic/examples/simple.py
new file mode 100644
index 00000000..9339b68a
--- /dev/null
+++ b/generic/examples/simple.py
@@ -0,0 +1,72 @@
+from simple_config import *
+
+def is_io(x, y):
+ return x == 0 or x == X-1 or y == 0 or y == Y-1
+
+for x in range(X):
+ for y in range(Y):
+ # Bel port wires
+ for z in range(N):
+ ctx.addWire(name="X%dY%dZ%d_CLK" % (x, y, z), type="BEL_CLK", x=x, y=y)
+ ctx.addWire(name="X%dY%dZ%d_Q" % (x, y, z), type="BEL_Q", x=x, y=y)
+ for i in range(K):
+ ctx.addWire(name="X%dY%dZ%d_I%d" % (x, y, z, i), type="BEL_I", x=x, y=y)
+ # Local wires
+ for l in range(Wl):
+ ctx.addWire(name="X%dY%d_LOCAL%d" % (x, y, l), type="LOCAL", x=x, y=y)
+ # Create bels
+ if is_io(x, y):
+ if x == y:
+ continue
+ for z in range(2):
+ ctx.addBel(name="X%dY%d_IO%d" % (x, y, z), type="GENERIC_IOB", loc=Loc(x, y, z), gb=False)
+ ctx.addBelInput(bel="X%dY%d_IO%d" % (x, y, z), name="I", wire="X%dY%dZ%d_I0" % (x, y, z))
+ ctx.addBelInput(bel="X%dY%d_IO%d" % (x, y, z), name="EN", wire="X%dY%dZ%d_I1" % (x, y, z))
+ ctx.addBelOutput(bel="X%dY%d_IO%d" % (x, y, z), name="O", wire="X%dY%dZ%d_Q" % (x, y, z))
+ else:
+ for z in range(N):
+ ctx.addBel(name="X%dY%d_SLICE%d" % (x, y, z), type="GENERIC_SLICE", loc=Loc(x, y, z), gb=False)
+ ctx.addBelInput(bel="X%dY%d_SLICE%d" % (x, y, z), name="CLK", wire="X%dY%dZ%d_CLK" % (x, y, z))
+ for k in range(K):
+ ctx.addBelInput(bel="X%dY%d_SLICE%d" % (x, y, z), name="I[%d]" % k, wire="X%dY%dZ%d_I%d" % (x, y, z, k))
+ ctx.addBelOutput(bel="X%dY%d_SLICE%d" % (x, y, z), name="Q", wire="X%dY%dZ%d_Q" % (x, y, z))
+
+for x in range(X):
+ for y in range(Y):
+ # Pips driving bel input wires
+ # Bel input wires are driven by every Si'th local with an offset
+ def create_input_pips(dst, offset, skip):
+ for i in range(offset % skip, Wl, skip):
+ src = "X%dY%d_LOCAL%d" % (x, y, i)
+ ctx.addPip(name="X%dY%d.%s.%s" % (x, y, src, dst), type="BEL_INPUT",
+ srcWire=src, dstWire=dst, delay=ctx.getDelayFromNS(0.05), loc=Loc(x, y, 0))
+ for z in range(N):
+ create_input_pips("X%dY%dZ%d_CLK" % (x, y, z), 0, Si)
+ for k in range(K):
+ create_input_pips("X%dY%dZ%d_I%d" % (x, y, z, k), k % Si, Si)
+
+ # Pips from bel outputs to locals
+ def create_output_pips(dst, offset, skip):
+ for i in range(offset % skip, N, skip):
+ src = "X%dY%dZ%d_Q" % (x, y, i)
+ ctx.addPip(name="X%dY%d.%s.%s" % (x, y, src, dst), type="BEL_OUTPUT",
+ srcWire=src, dstWire=dst, delay=ctx.getDelayFromNS(0.05), loc=Loc(x, y, 0))
+ # Pips from neighbour locals to locals
+ def create_neighbour_pips(dst, nx, ny, offset, skip):
+ if nx < 0 or nx >= X or ny < 0 or ny >= Y:
+ return
+ for i in range(offset % skip, Wl, skip):
+ src = "X%dY%d_LOCAL%d" % (nx, ny, i)
+ ctx.addPip(name="X%dY%d.%s.%s" % (x, y, src, dst), type="NEIGHBOUR",
+ srcWire=src, dstWire=dst, delay=ctx.getDelayFromNS(0.05), loc=Loc(x, y, 0))
+ for l in range(Wl):
+ dst = "X%dY%d_LOCAL%d" % (x, y, l)
+ create_output_pips(dst, l % Sq, Sq)
+ create_neighbour_pips(dst, x-1, y-1, (l + 1) % Sl, Sl)
+ create_neighbour_pips(dst, x-1, y, (l + 2) % Sl, Sl)
+ create_neighbour_pips(dst, x-1, y+1, (l + 2) % Sl, Sl)
+ create_neighbour_pips(dst, x, y-1, (l + 3) % Sl, Sl)
+ create_neighbour_pips(dst, x, y+1, (l + 4) % Sl, Sl)
+ create_neighbour_pips(dst, x+1, y-1, (l + 5) % Sl, Sl)
+ create_neighbour_pips(dst, x+1, y, (l + 6) % Sl, Sl)
+ create_neighbour_pips(dst, x+1, y+1, (l + 7) % Sl, Sl) \ No newline at end of file
diff --git a/generic/examples/simple.sh b/generic/examples/simple.sh
new file mode 100755
index 00000000..8ae903f9
--- /dev/null
+++ b/generic/examples/simple.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+set -ex
+yosys -p "tcl ../synth/synth_generic.tcl 4 blinky.json" blinky.v
+${NEXTPNR:-../../nextpnr-generic} --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py
diff --git a/generic/examples/simple_config.py b/generic/examples/simple_config.py
new file mode 100644
index 00000000..dfb38f1c
--- /dev/null
+++ b/generic/examples/simple_config.py
@@ -0,0 +1,15 @@
+# Grid size including IOBs at edges
+X = 12
+Y = 12
+# SLICEs per tile
+N = 8
+# LUT input count
+K = 4
+# Number of local wires
+Wl = N*(K+1) + 8
+# 1/Fc for bel input wire pips
+Si = 4
+# 1/Fc for Q to local wire pips
+Sq = 4
+# ~1/Fc local to neighbour local wire pips
+Sl = 8 \ No newline at end of file
diff --git a/generic/examples/simple_timing.py b/generic/examples/simple_timing.py
new file mode 100644
index 00000000..a955c8d7
--- /dev/null
+++ b/generic/examples/simple_timing.py
@@ -0,0 +1,15 @@
+for cname, cell in ctx.cells:
+ if cell.type != "GENERIC_SLICE":
+ continue
+ if cname in ("$PACKER_GND", "$PACKER_VCC"):
+ continue
+ K = int(cell.params["K"])
+ if cell.params["FF_USED"] == "1":
+ ctx.addCellTimingClock(cell=cname, port="CLK")
+ for i in range(K):
+ ctx.addCellTimingSetupHold(cell=cname, port="I[%d]" % i, clock="CLK",
+ setup=ctx.getDelayFromNS(0.2), hold=ctx.getDelayFromNS(0))
+ ctx.addCellTimingClockToOut(cell=cname, port="Q", clock="CLK", clktoq=ctx.getDelayFromNS(0.2))
+ else:
+ for i in range(K):
+ ctx.addCellTimingDelay(cell=cname, fromPort="I[%d]" % i, toPort="Q", delay=ctx.getDelayFromNS(0.2)) \ No newline at end of file
diff --git a/generic/examples/write_fasm.py b/generic/examples/write_fasm.py
new file mode 100644
index 00000000..1f279b63
--- /dev/null
+++ b/generic/examples/write_fasm.py
@@ -0,0 +1,52 @@
+from collections import namedtuple
+
+"""
+ write: set to True to enable writing this parameter to FASM
+
+ numeric: set to True to write this parameter as a bit array (width>1) or
+ single bit (width==1) named after the parameter. Otherwise this
+ parameter will be written as `name.value`
+
+ width: width of numeric parameter (ignored for non-numeric parameters)
+
+ alias: an alternative name for this parameter (parameter name used if alias
+ is None)
+"""
+ParameterConfig = namedtuple('ParameterConfig', 'write numeric width alias')
+
+# FIXME use defaults= once Python 3.7 is standard
+ParameterConfig.__new__.__defaults__ = (False, True, 1, None)
+
+
+"""
+Write a design as FASM
+
+ ctx: nextpnr context
+ paramCfg: map from (celltype, parametername) -> ParameterConfig describing how to write parameters
+ f: output file
+"""
+def write_fasm(ctx, paramCfg, f):
+ for nname, net in sorted(ctx.nets, key=lambda x: str(x[1].name)):
+ print("# Net %s" % nname, file=f)
+ for wire, pip in sorted(net.wires, key=lambda x: str(x[1])):
+ if pip.pip != "":
+ print("%s" % pip.pip, file=f)
+ print("", file=f)
+ for cname, cell in sorted(ctx.cells, key=lambda x: str(x[1].name)):
+ print("# Cell %s at %s" % (cname, cell.bel), file=f)
+ for param, val in sorted(cell.params, key=lambda x: str(x)):
+ cfg = paramCfg[(cell.type, param)]
+ if not cfg.write:
+ continue
+ fasm_name = cfg.alias if cfg.alias is not None else param
+ if cfg.numeric:
+ if cfg.width == 1:
+ if int(val) != 0:
+ print("%s.%s" % (cell.bel, fasm_name), file=f)
+ else:
+ # Parameters with width >32 are direct binary, otherwise denary
+ binval = val if cfg.width > 32 else "{:0{}b}".format(int(val), cfg.width)
+ print("%s.%s[%d:0] = %d'b%s" % (cell.bel, fasm_name, cfg.width-1, cfg.width, binval), file=f)
+ else:
+ print("%s.%s.%s" % (cell.bel, fasm_name, val), file=f)
+ print("", file=f) \ No newline at end of file
diff --git a/generic/main.cc b/generic/main.cc
index 08b0b348..c203f35c 100644
--- a/generic/main.cc
+++ b/generic/main.cc
@@ -49,7 +49,7 @@ po::options_description GenericCommandHandler::getArchOptions()
return specific;
}
-void GenericCommandHandler::customBitstream(Context *ctx) { log_error("Here is when bitstream gets created"); }
+void GenericCommandHandler::customBitstream(Context *ctx) {}
std::unique_ptr<Context> GenericCommandHandler::createContext()
{
diff --git a/generic/pack.cc b/generic/pack.cc
new file mode 100644
index 00000000..26ae9182
--- /dev/null
+++ b/generic/pack.cc
@@ -0,0 +1,293 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018-19 David Shah <david@symbioticeda.com>
+ *
+ * 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 <algorithm>
+#include <iterator>
+#include <unordered_set>
+#include "cells.h"
+#include "design_utils.h"
+#include "log.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+// Pack LUTs and LUT-FF pairs
+static void pack_lut_lutffs(Context *ctx)
+{
+ log_info("Packing LUT-FFs..\n");
+
+ std::unordered_set<IdString> packed_cells;
+ std::vector<std::unique_ptr<CellInfo>> new_cells;
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ctx->verbose)
+ log_info("cell '%s' is of type '%s'\n", ci->name.c_str(ctx), ci->type.c_str(ctx));
+ if (is_lut(ctx, ci)) {
+ std::unique_ptr<CellInfo> packed =
+ create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), ci->name.str(ctx) + "_LC");
+ std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(packed->attrs, packed->attrs.begin()));
+ packed_cells.insert(ci->name);
+ if (ctx->verbose)
+ log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx));
+ // See if we can pack into a DFF
+ // TODO: LUT cascade
+ NetInfo *o = ci->ports.at(ctx->id("Q")).net;
+ CellInfo *dff = net_only_drives(ctx, o, is_ff, ctx->id("D"), true);
+ auto lut_bel = ci->attrs.find(ctx->id("BEL"));
+ bool packed_dff = false;
+ if (dff) {
+ if (ctx->verbose)
+ log_info("found attached dff %s\n", dff->name.c_str(ctx));
+ auto dff_bel = dff->attrs.find(ctx->id("BEL"));
+ if (lut_bel != ci->attrs.end() && dff_bel != dff->attrs.end() && lut_bel->second != dff_bel->second) {
+ // Locations don't match, can't pack
+ } else {
+ lut_to_lc(ctx, ci, packed.get(), false);
+ dff_to_lc(ctx, dff, packed.get(), false);
+ ctx->nets.erase(o->name);
+ if (dff_bel != dff->attrs.end())
+ packed->attrs[ctx->id("BEL")] = dff_bel->second;
+ packed_cells.insert(dff->name);
+ if (ctx->verbose)
+ log_info("packed cell %s into %s\n", dff->name.c_str(ctx), packed->name.c_str(ctx));
+ packed_dff = true;
+ }
+ }
+ if (!packed_dff) {
+ lut_to_lc(ctx, ci, packed.get(), true);
+ }
+ new_cells.push_back(std::move(packed));
+ }
+ }
+ for (auto pcell : packed_cells) {
+ ctx->cells.erase(pcell);
+ }
+ for (auto &ncell : new_cells) {
+ ctx->cells[ncell->name] = std::move(ncell);
+ }
+}
+
+// Pack FFs not packed as LUTFFs
+static void pack_nonlut_ffs(Context *ctx)
+{
+ log_info("Packing non-LUT FFs..\n");
+
+ std::unordered_set<IdString> packed_cells;
+ std::vector<std::unique_ptr<CellInfo>> new_cells;
+
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (is_ff(ctx, ci)) {
+ std::unique_ptr<CellInfo> packed =
+ create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), ci->name.str(ctx) + "_DFFLC");
+ std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(packed->attrs, packed->attrs.begin()));
+ if (ctx->verbose)
+ log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx));
+ packed_cells.insert(ci->name);
+ dff_to_lc(ctx, ci, packed.get(), true);
+ new_cells.push_back(std::move(packed));
+ }
+ }
+ for (auto pcell : packed_cells) {
+ ctx->cells.erase(pcell);
+ }
+ for (auto &ncell : new_cells) {
+ ctx->cells[ncell->name] = std::move(ncell);
+ }
+}
+
+static bool net_is_constant(const Context *ctx, NetInfo *net, bool &value)
+{
+ if (net == nullptr)
+ return false;
+ if (net->name == ctx->id("$PACKER_GND_NET") || net->name == ctx->id("$PACKER_VCC_NET")) {
+ value = (net->name == ctx->id("$PACKER_VCC_NET"));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// Merge a net into a constant net
+static void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constnet, bool constval)
+{
+ orig->driver.cell = nullptr;
+ for (auto user : orig->users) {
+ if (user.cell != nullptr) {
+ CellInfo *uc = user.cell;
+ if (ctx->verbose)
+ log_info("%s user %s\n", orig->name.c_str(ctx), uc->name.c_str(ctx));
+ if ((is_lut(ctx, uc) || is_lc(ctx, uc)) && (user.port.str(ctx).at(0) == 'I') && !constval) {
+ uc->ports[user.port].net = nullptr;
+ } else {
+ uc->ports[user.port].net = constnet;
+ constnet->users.push_back(user);
+ }
+ }
+ }
+ orig->users.clear();
+}
+
+// Pack constants (simple implementation)
+static void pack_constants(Context *ctx)
+{
+ log_info("Packing constants..\n");
+
+ std::unique_ptr<CellInfo> gnd_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_GND");
+ gnd_cell->params[ctx->id("INIT")] = "0";
+ std::unique_ptr<NetInfo> gnd_net = std::unique_ptr<NetInfo>(new NetInfo);
+ gnd_net->name = ctx->id("$PACKER_GND_NET");
+ gnd_net->driver.cell = gnd_cell.get();
+ gnd_net->driver.port = ctx->id("Q");
+ gnd_cell->ports.at(ctx->id("Q")).net = gnd_net.get();
+
+ std::unique_ptr<CellInfo> vcc_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_VCC");
+ vcc_cell->params[ctx->id("INIT")] = "1";
+ std::unique_ptr<NetInfo> vcc_net = std::unique_ptr<NetInfo>(new NetInfo);
+ vcc_net->name = ctx->id("$PACKER_VCC_NET");
+ vcc_net->driver.cell = vcc_cell.get();
+ vcc_net->driver.port = ctx->id("Q");
+ vcc_cell->ports.at(ctx->id("Q")).net = vcc_net.get();
+
+ std::vector<IdString> dead_nets;
+
+ bool gnd_used = false;
+
+ for (auto net : sorted(ctx->nets)) {
+ NetInfo *ni = net.second;
+ if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("GND")) {
+ IdString drv_cell = ni->driver.cell->name;
+ set_net_constant(ctx, ni, gnd_net.get(), false);
+ gnd_used = true;
+ dead_nets.push_back(net.first);
+ ctx->cells.erase(drv_cell);
+ } else if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("VCC")) {
+ IdString drv_cell = ni->driver.cell->name;
+ set_net_constant(ctx, ni, vcc_net.get(), true);
+ dead_nets.push_back(net.first);
+ ctx->cells.erase(drv_cell);
+ }
+ }
+
+ if (gnd_used) {
+ ctx->cells[gnd_cell->name] = std::move(gnd_cell);
+ ctx->nets[gnd_net->name] = std::move(gnd_net);
+ }
+ // Vcc cell always inserted for now, as it may be needed during carry legalisation (TODO: trim later if actually
+ // never used?)
+ ctx->cells[vcc_cell->name] = std::move(vcc_cell);
+ ctx->nets[vcc_net->name] = std::move(vcc_net);
+
+ for (auto dn : dead_nets) {
+ ctx->nets.erase(dn);
+ }
+}
+
+static bool is_nextpnr_iob(Context *ctx, CellInfo *cell)
+{
+ return cell->type == ctx->id("$nextpnr_ibuf") || cell->type == ctx->id("$nextpnr_obuf") ||
+ cell->type == ctx->id("$nextpnr_iobuf");
+}
+
+static bool is_generic_iob(const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("GENERIC_IOB"); }
+
+// Pack IO buffers
+static void pack_io(Context *ctx)
+{
+ std::unordered_set<IdString> packed_cells;
+ std::unordered_set<IdString> delete_nets;
+
+ std::vector<std::unique_ptr<CellInfo>> new_cells;
+ log_info("Packing IOs..\n");
+
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (is_nextpnr_iob(ctx, ci)) {
+ CellInfo *iob = nullptr;
+ if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
+ iob = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_generic_iob, ctx->id("PAD"), true, ci);
+
+ } else if (ci->type == ctx->id("$nextpnr_obuf")) {
+ NetInfo *net = ci->ports.at(ctx->id("I")).net;
+ iob = net_only_drives(ctx, net, is_generic_iob, ctx->id("PAD"), true, ci);
+ }
+ if (iob != nullptr) {
+ // Trivial case, GENERIC_IOB used. Just destroy the net and the
+ // iobuf
+ log_info("%s feeds GENERIC_IOB %s, removing %s %s.\n", ci->name.c_str(ctx), iob->name.c_str(ctx),
+ ci->type.c_str(ctx), ci->name.c_str(ctx));
+ NetInfo *net = iob->ports.at(ctx->id("PAD")).net;
+ if (((ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) &&
+ net->users.size() > 1) ||
+ (ci->type == ctx->id("$nextpnr_obuf") && (net->users.size() > 2 || net->driver.cell != nullptr)))
+ log_error("PAD of %s '%s' connected to more than a single top level IO.\n", iob->type.c_str(ctx),
+ iob->name.c_str(ctx));
+
+ if (net != nullptr) {
+ delete_nets.insert(net->name);
+ iob->ports.at(ctx->id("PAD")).net = nullptr;
+ }
+ if (ci->type == ctx->id("$nextpnr_iobuf")) {
+ NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
+ if (net2 != nullptr) {
+ delete_nets.insert(net2->name);
+ }
+ }
+ } else {
+ // Create a GENERIC_IOB buffer
+ std::unique_ptr<CellInfo> ice_cell =
+ create_generic_cell(ctx, ctx->id("GENERIC_IOB"), ci->name.str(ctx) + "$iob");
+ nxio_to_iob(ctx, ci, ice_cell.get(), packed_cells);
+ new_cells.push_back(std::move(ice_cell));
+ iob = new_cells.back().get();
+ }
+ packed_cells.insert(ci->name);
+ std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(iob->attrs, iob->attrs.begin()));
+ }
+ }
+ for (auto pcell : packed_cells) {
+ ctx->cells.erase(pcell);
+ }
+ for (auto dnet : delete_nets) {
+ ctx->nets.erase(dnet);
+ }
+ for (auto &ncell : new_cells) {
+ ctx->cells[ncell->name] = std::move(ncell);
+ }
+}
+
+// Main pack function
+bool Arch::pack()
+{
+ Context *ctx = getCtx();
+ try {
+ log_break();
+ pack_constants(ctx);
+ pack_io(ctx);
+ pack_lut_lutffs(ctx);
+ pack_nonlut_ffs(ctx);
+ ctx->assignArchInfo();
+ log_info("Checksum: 0x%08x\n", ctx->checksum());
+ return true;
+ } catch (log_execution_error_exception) {
+ return false;
+ }
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/generic/synth/cells_map.v b/generic/synth/cells_map.v
new file mode 100644
index 00000000..a6027534
--- /dev/null
+++ b/generic/synth/cells_map.v
@@ -0,0 +1,10 @@
+module \$lut (A, Y);
+ parameter WIDTH = 0;
+ parameter LUT = 0;
+ input [WIDTH-1:0] A;
+ output Y;
+
+ LUT #(.K(`LUT_K), .INIT(LUT)) _TECHMAP_REPLACE_ (.I(A), .Q(Y));
+endmodule
+
+module \$_DFF_P_ (input D, C, output Q); DFF _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(C)); endmodule
diff --git a/generic/synth/prims.v b/generic/synth/prims.v
new file mode 100644
index 00000000..95fcfac7
--- /dev/null
+++ b/generic/synth/prims.v
@@ -0,0 +1,59 @@
+// LUT and DFF are combined to a GENERIC_SLICE
+
+module LUT #(
+ parameter K = 4,
+ parameter [2**K-1:0] INIT = 0,
+) (
+ input [K-1:0] I,
+ output Q
+);
+ assign Q = INIT[I];
+endmodule
+
+module DFF (
+ input CLK, D,
+ output reg Q
+);
+ always @(posedge CLK)
+ Q <= D;
+endmodule
+
+module GENERIC_SLICE #(
+ parameter K = 4,
+ parameter [2**K-1:0] INIT = 0,
+ parameter FF_USED = 1'b0
+) (
+ input CLK,
+ input [K-1:0] I,
+ output Q
+);
+
+ wire lut_q;
+ LUT #(.K(K), .INIT(INIT)) lut_i(.I(I), .Q(lut_q));
+
+ generate if (FF_USED)
+ DFF dff_i(.CLK(CLK), .D(lut_q), .Q(Q));
+ else
+ assign Q = lut_q;
+ endgenerate
+endmodule
+
+module GENERIC_IOB #(
+ parameter INPUT_USED = 1'b0,
+ parameter OUTPUT_USED = 1'b0,
+ parameter ENABLE_USED = 1'b0
+) (
+ inout PAD,
+ input I, EN,
+ output O
+);
+ generate if (OUTPUT_USED && ENABLE_USED)
+ assign PAD = EN ? I : 1'bz;
+ else if (OUTPUT_USED)
+ assign PAD = I;
+ endgenerate
+
+ generate if (INPUT_USED)
+ assign O = PAD;
+ endgenerate
+endmodule \ No newline at end of file
diff --git a/generic/synth/synth_generic.tcl b/generic/synth/synth_generic.tcl
new file mode 100644
index 00000000..e5d88e0d
--- /dev/null
+++ b/generic/synth/synth_generic.tcl
@@ -0,0 +1,24 @@
+# Usage
+# tcl synth_generic.tcl {K} {out.json}
+
+set LUT_K 4
+if {$argc > 0} { set LUT_K [lindex $argv 0] }
+yosys read_verilog -lib [file dirname [file normalize $argv0]]/prims.v
+yosys hierarchy -check
+yosys proc
+yosys flatten
+yosys tribuf -logic
+yosys deminout
+yosys synth -run coarse
+yosys memory_map
+yosys opt -full
+yosys techmap -map +/techmap.v
+yosys opt -fast
+yosys abc -lut $LUT_K -dress
+yosys clean
+yosys techmap -D LUT_K=$LUT_K -map [file dirname [file normalize $argv0]]/cells_map.v
+yosys clean
+yosys hierarchy -check
+yosys stat
+
+if {$argc > 1} { yosys write_json [lindex $argv 1] }
diff --git a/gui/application.cc b/gui/application.cc
index 7751e6f1..33a106bc 100644
--- a/gui/application.cc
+++ b/gui/application.cc
@@ -21,9 +21,11 @@
#include "application.h"
#include <QMessageBox>
+#include <QOpenGLContext>
#include <QSurfaceFormat>
#include <QTextStream>
#include <exception>
+#include "log.h"
NEXTPNR_NAMESPACE_BEGIN
@@ -37,12 +39,28 @@ BOOL WINAPI WinHandler(DWORD dwCtrlType)
}
#endif
-Application::Application(int &argc, char **argv) : QApplication(argc, argv)
+Application::Application(int &argc, char **argv, bool noantialiasing) : QApplication(argc, argv)
{
QSurfaceFormat fmt;
- fmt.setSamples(10);
+ if (!noantialiasing)
+ fmt.setSamples(10);
fmt.setProfile(QSurfaceFormat::CoreProfile);
+ // macOS is very picky about this version matching
+ // the version of openGL used in ImGuiRenderer
+ fmt.setMajorVersion(3);
+ fmt.setMinorVersion(2);
QSurfaceFormat::setDefaultFormat(fmt);
+
+ QOpenGLContext glContext;
+ fmt = glContext.format();
+ if (fmt.majorVersion() < 3) {
+ printf("Could not get OpenGL 3.0 context. Aborting.\n");
+ log_abort();
+ }
+ if (fmt.minorVersion() < 2) {
+ printf("Could not get OpenGL 3.2 context - trying anyway...\n ");
+ }
+
#ifdef _WIN32
SetConsoleCtrlHandler((PHANDLER_ROUTINE)WinHandler, TRUE);
#endif
@@ -53,7 +71,7 @@ bool Application::notify(QObject *receiver, QEvent *event)
bool retVal = true;
try {
retVal = QApplication::notify(receiver, event);
- } catch (assertion_failure ex) {
+ } catch (const assertion_failure &ex) {
QString msg;
QTextStream out(&msg);
out << ex.filename.c_str() << " at " << ex.line << "\n";
diff --git a/gui/application.h b/gui/application.h
index 321f6b65..ad5de62f 100644
--- a/gui/application.h
+++ b/gui/application.h
@@ -29,7 +29,7 @@ NEXTPNR_NAMESPACE_BEGIN
class Application : public QApplication
{
public:
- Application(int &argc, char **argv);
+ Application(int &argc, char **argv, bool noantialiasing);
bool notify(QObject *receiver, QEvent *event);
};
diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc
index 5eab20ed..f2929d6e 100644
--- a/gui/fpgaviewwidget.cc
+++ b/gui/fpgaviewwidget.cc
@@ -59,20 +59,6 @@ FPGAViewWidget::FPGAViewWidget(QWidget *parent)
rendererArgs_->gridChanged = false;
rendererArgs_->zoomOutbound = true;
- auto fmt = format();
- fmt.setMajorVersion(3);
- fmt.setMinorVersion(2);
- setFormat(fmt);
-
- fmt = format();
- if (fmt.majorVersion() < 3) {
- printf("Could not get OpenGL 3.0 context. Aborting.\n");
- log_abort();
- }
- if (fmt.minorVersion() < 2) {
- printf("Could not get OpenGL 3.2 context - trying anyway...\n ");
- }
-
connect(&paintTimer_, SIGNAL(timeout()), this, SLOT(update()));
paintTimer_.start(1000 / 20); // paint GL 20 times per second
diff --git a/gui/generic/mainwindow.cc b/gui/generic/mainwindow.cc
index 12912cc9..54d1f2c8 100644
--- a/gui/generic/mainwindow.cc
+++ b/gui/generic/mainwindow.cc
@@ -19,6 +19,9 @@
#include "mainwindow.h"
+#include <QMessageBox>
+#include <cstdlib>
+
static void initMainResource() { Q_INIT_RESOURCE(nextpnr); }
NEXTPNR_NAMESPACE_BEGIN
@@ -26,14 +29,8 @@ NEXTPNR_NAMESPACE_BEGIN
MainWindow::MainWindow(std::unique_ptr<Context> context, ArchArgs args, QWidget *parent)
: BaseMainWindow(std::move(context), args, parent)
{
- initMainResource();
-
- std::string title = "nextpnr-generic - [EMPTY]";
- setWindowTitle(title.c_str());
-
- connect(this, &BaseMainWindow::contextChanged, this, &MainWindow::newContext);
-
- createMenu();
+ QMessageBox::critical(0, "Error - FIXME", "No GUI support for nextpnr-generic");
+ std::exit(1);
}
MainWindow::~MainWindow() {}
diff --git a/gui/lineshader.h b/gui/lineshader.h
index 98042051..4c54bf46 100644
--- a/gui/lineshader.h
+++ b/gui/lineshader.h
@@ -172,10 +172,10 @@ class LineShader
LineShader(QObject *parent) : parent_(parent), program_(nullptr) {}
static constexpr const char *vertexShaderSource_ =
- "#version 110\n"
- "attribute highp vec2 position;\n"
- "attribute highp vec2 normal;\n"
- "attribute highp float miter;\n"
+ "#version 150\n"
+ "in highp vec2 position;\n"
+ "in highp vec2 normal;\n"
+ "in highp float miter;\n"
"uniform highp float thickness;\n"
"uniform highp mat4 projection;\n"
"void main() {\n"
@@ -183,10 +183,11 @@ class LineShader
" gl_Position = projection * vec4(p, 0.0, 1.0);\n"
"}\n";
- static constexpr const char *fragmentShaderSource_ = "#version 110\n"
+ static constexpr const char *fragmentShaderSource_ = "#version 150\n"
"uniform lowp vec4 color;\n"
+ "out vec4 Out_Color;\n"
"void main() {\n"
- " gl_FragColor = color;\n"
+ " Out_Color = color;\n"
"}\n";
// Must be called on initialization.
diff --git a/gui/pythontab.cc b/gui/pythontab.cc
index 827f1907..c83f1ece 100644
--- a/gui/pythontab.cc
+++ b/gui/pythontab.cc
@@ -96,9 +96,10 @@ void PythonTab::newContext(Context *ctx)
console->clear();
pyinterpreter_preinit();
- init_python("nextpnr", !initialized);
+ init_python("nextpnr", true);
pyinterpreter_initialize();
pyinterpreter_aquire();
+ init_python("nextpnr", false);
python_export_global("ctx", ctx);
pyinterpreter_release();
diff --git a/ice40/arch.cc b/ice40/arch.cc
index d536ad35..80e1fb4c 100644
--- a/ice40/arch.cc
+++ b/ice40/arch.cc
@@ -1045,6 +1045,14 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in
return TMG_COMB_INPUT;
} else if (cell->type == id_SB_WARMBOOT) {
return TMG_ENDPOINT;
+ } else if (cell->type == id_SB_LED_DRV_CUR) {
+ if (port == id_LEDPU)
+ return TMG_IGNORE;
+ return TMG_ENDPOINT;
+ } else if (cell->type == id_SB_RGB_DRV) {
+ if (port == id_RGB0 || port == id_RGB1 || port == id_RGB2 || port == id_RGBPU)
+ return TMG_IGNORE;
+ return TMG_ENDPOINT;
} else if (cell->type == id_SB_RGBA_DRV) {
if (port == id_RGB0 || port == id_RGB1 || port == id_RGB2)
return TMG_IGNORE;
diff --git a/ice40/archdefs.h b/ice40/archdefs.h
index 956fcb4c..89591af5 100644
--- a/ice40/archdefs.h
+++ b/ice40/archdefs.h
@@ -159,6 +159,10 @@ struct ArchCellInfo
{
bool forPadIn;
} gbInfo;
+ struct
+ {
+ bool ledCurConnected;
+ } ledInfo;
};
};
diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc
index 9b85dff5..7632b443 100644
--- a/ice40/bitstream.cc
+++ b/ice40/bitstream.cc
@@ -610,6 +610,14 @@ void write_asc(const Context *ctx, std::ostream &out)
set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_1", write_mode & 0x2);
set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_2", read_mode & 0x1);
set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_3", read_mode & 0x2);
+ } else if (cell.second->type == ctx->id("SB_LED_DRV_CUR")) {
+ set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "LED_DRV_CUR_EN", true,
+ "IpConfig.");
+ } else if (cell.second->type == ctx->id("SB_RGB_DRV")) {
+ const std::vector<std::pair<std::string, int>> rgb_params = {
+ {"RGB0_CURRENT", 6}, {"RGB1_CURRENT", 6}, {"RGB2_CURRENT", 6}};
+ configure_extra_cell(config, ctx, cell.second.get(), rgb_params, true, std::string("IpConfig."));
+ set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "RGB_DRV_EN", true, "IpConfig.");
} else if (cell.second->type == ctx->id("SB_RGBA_DRV")) {
const std::vector<std::pair<std::string, int>> rgba_params = {
{"CURRENT_MODE", 1}, {"RGB0_CURRENT", 6}, {"RGB1_CURRENT", 6}, {"RGB2_CURRENT", 6}};
diff --git a/ice40/cells.cc b/ice40/cells.cc
index 5744fe50..a2abcea4 100644
--- a/ice40/cells.cc
+++ b/ice40/cells.cc
@@ -260,6 +260,22 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri
add_port(ctx, new_cell.get(), "RGB0", PORT_OUT);
add_port(ctx, new_cell.get(), "RGB1", PORT_OUT);
add_port(ctx, new_cell.get(), "RGB2", PORT_OUT);
+ } else if (type == ctx->id("SB_LED_DRV_CUR")) {
+ add_port(ctx, new_cell.get(), "EN", PORT_IN);
+ add_port(ctx, new_cell.get(), "LEDPU", PORT_OUT);
+ } else if (type == ctx->id("SB_RGB_DRV")) {
+ new_cell->params[ctx->id("RGB0_CURRENT")] = "0b000000";
+ new_cell->params[ctx->id("RGB1_CURRENT")] = "0b000000";
+ new_cell->params[ctx->id("RGB2_CURRENT")] = "0b000000";
+
+ add_port(ctx, new_cell.get(), "RGBPU", PORT_IN);
+ add_port(ctx, new_cell.get(), "RGBLEDEN", PORT_IN);
+ add_port(ctx, new_cell.get(), "RGB0PWM", PORT_IN);
+ add_port(ctx, new_cell.get(), "RGB1PWM", PORT_IN);
+ add_port(ctx, new_cell.get(), "RGB2PWM", PORT_IN);
+ add_port(ctx, new_cell.get(), "RGB0", PORT_OUT);
+ add_port(ctx, new_cell.get(), "RGB1", PORT_OUT);
+ add_port(ctx, new_cell.get(), "RGB2", PORT_OUT);
} else if (type == ctx->id("SB_LEDDA_IP")) {
add_port(ctx, new_cell.get(), "LEDDCS", PORT_IN);
add_port(ctx, new_cell.get(), "LEDDCLK", PORT_IN);
diff --git a/ice40/cells.h b/ice40/cells.h
index ec4d560d..3d9358da 100644
--- a/ice40/cells.h
+++ b/ice40/cells.h
@@ -76,6 +76,13 @@ inline bool is_sb_mac16(const BaseCtx *ctx, const CellInfo *cell) { return cell-
inline bool is_sb_rgba_drv(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_RGBA_DRV"); }
+inline bool is_sb_rgb_drv(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_RGB_DRV"); }
+
+inline bool is_sb_led_drv_cur(const BaseCtx *ctx, const CellInfo *cell)
+{
+ return cell->type == ctx->id("SB_LED_DRV_CUR");
+}
+
inline bool is_sb_ledda_ip(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_LEDDA_IP"); }
inline bool is_sb_i2c(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_I2C"); }
diff --git a/ice40/chipdb.py b/ice40/chipdb.py
index 42ca6ac1..cc7be01f 100644
--- a/ice40/chipdb.py
+++ b/ice40/chipdb.py
@@ -81,6 +81,8 @@ constids["SPI"] = constids["SB_SPI"]
constids["LEDDA_IP"] = constids["SB_LEDDA_IP"]
constids["RGBA_DRV"] = constids["SB_RGBA_DRV"]
constids["SPRAM"] = constids["ICESTORM_SPRAM"]
+constids["LED_DRV_CUR"] = constids["SB_LED_DRV_CUR"]
+constids["RGB_DRV"] = constids["SB_RGB_DRV"]
with open(args.gfxh) as f:
state = 0
diff --git a/ice40/constids.inc b/ice40/constids.inc
index 366a3a9d..6aa5c4c0 100644
--- a/ice40/constids.inc
+++ b/ice40/constids.inc
@@ -355,6 +355,9 @@ X(PWMOUT0)
X(PWMOUT1)
X(PWMOUT2)
+X(LEDPU)
+X(EN)
+X(RGBPU)
X(CURREN)
X(RGB0PWM)
X(RGB1PWM)
@@ -438,6 +441,8 @@ X(IO_I3C)
X(SB_LEDDA_IP)
X(SB_RGBA_DRV)
X(ICESTORM_SPRAM)
+X(SB_LED_DRV_CUR)
+X(SB_RGB_DRV)
// cell parameters
X(DFF_ENABLE)
diff --git a/ice40/pack.cc b/ice40/pack.cc
index 2ba0bb5a..f520b295 100644
--- a/ice40/pack.cc
+++ b/ice40/pack.cc
@@ -450,7 +450,8 @@ static void pack_io(Context *ctx)
} else if (ci->type == ctx->id("$nextpnr_obuf")) {
NetInfo *net = ci->ports.at(ctx->id("I")).net;
sb = net_only_drives(ctx, net, is_ice_iob, ctx->id("PACKAGE_PIN"), true, ci);
- if (net && net->driver.cell && is_sb_rgba_drv(ctx, net->driver.cell))
+ if (net && net->driver.cell &&
+ (is_sb_rgba_drv(ctx, net->driver.cell) || is_sb_rgb_drv(ctx, net->driver.cell)))
rgb = net->driver.cell;
}
if (sb != nullptr) {
@@ -476,7 +477,8 @@ static void pack_io(Context *ctx)
}
}
} else if (rgb != nullptr) {
- log_info("%s use by SB_RGBA_DRV %s, not creating SB_IO\n", ci->name.c_str(ctx), rgb->name.c_str(ctx));
+ log_info("%s use by SB_RGBA_DRV/SB_RGB_DRV %s, not creating SB_IO\n", ci->name.c_str(ctx),
+ rgb->name.c_str(ctx));
disconnect_port(ctx, ci, ctx->id("I"));
packed_cells.insert(ci->name);
continue;
@@ -1038,6 +1040,27 @@ static void pack_special(Context *ctx)
std::unordered_set<IdString> packed_cells;
std::vector<std::unique_ptr<CellInfo>> new_cells;
+ // Handle LED_DRV_CUR first to set the ledCurConnected flag before RGB_DRV is handled below.
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (is_sb_led_drv_cur(ctx, ci)) {
+ /* Force placement (no choices anyway) */
+ cell_place_unique(ctx, ci);
+
+ NetInfo *ledpu_net = ci->ports.at(ctx->id("LEDPU")).net;
+ for (auto &user : ledpu_net->users) {
+ if (!is_sb_rgb_drv(ctx, user.cell)) {
+ log_error("SB_LED_DRV_CUR LEDPU port can only be connected to SB_RGB_DRV!\n");
+ } else {
+ user.cell->ledInfo.ledCurConnected = true;
+ user.cell->ports.at(user.port).net = nullptr;
+ }
+ }
+ ci->ports.erase(ctx->id("LEDPU"));
+ ctx->nets.erase(ledpu_net->name);
+ }
+ }
+
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (is_sb_lfosc(ctx, ci)) {
@@ -1061,9 +1084,14 @@ static void pack_special(Context *ctx)
create_ice_cell(ctx, ctx->id("ICESTORM_HFOSC"), ci->name.str(ctx) + "_OSC");
packed_cells.insert(ci->name);
cell_place_unique(ctx, packed.get());
+ packed->params[ctx->id("TRIM_EN")] = str_or_default(ci->params, ctx->id("TRIM_EN"), "0b0");
packed->params[ctx->id("CLKHF_DIV")] = str_or_default(ci->params, ctx->id("CLKHF_DIV"), "0b00");
replace_port(ci, ctx->id("CLKHFEN"), packed.get(), ctx->id("CLKHFEN"));
replace_port(ci, ctx->id("CLKHFPU"), packed.get(), ctx->id("CLKHFPU"));
+ for (int i = 0; i < 10; i++) {
+ auto port = ctx->id("TRIM" + std::to_string(i));
+ replace_port(ci, port, packed.get(), port);
+ }
if (bool_or_default(ci->attrs, ctx->id("ROUTE_THROUGH_FABRIC"))) {
replace_port(ci, ctx->id("CLKHF"), packed.get(), ctx->id("CLKHF_FABRIC"));
} else {
@@ -1108,7 +1136,7 @@ static void pack_special(Context *ctx)
replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname));
}
new_cells.push_back(std::move(packed));
- } else if (is_sb_rgba_drv(ctx, ci)) {
+ } else if (is_sb_rgba_drv(ctx, ci) || is_sb_rgb_drv(ctx, ci)) {
/* Force placement (no choices anyway) */
cell_place_unique(ctx, ci);
@@ -1120,14 +1148,20 @@ static void pack_special(Context *ctx)
if (net == nullptr)
continue;
+
if ((pi.name != ctx->id("RGB0")) && (pi.name != ctx->id("RGB1")) && (pi.name != ctx->id("RGB2")))
continue;
if (net->users.size() > 0)
- log_error("SB_RGBA_DRV port connected to more than just package pin !\n");
+ log_error("SB_RGB_DRV/SB_RGBA_DRV port connected to more than just package pin !\n");
ctx->nets.erase(net->name);
}
+
+ if (is_sb_rgb_drv(ctx, ci) && !ci->ledInfo.ledCurConnected)
+ log_error("Port RGBPU of SB_RGB_DRV should be driven by port LEDPU of SB_LED_DRV_CUR!\n");
+
+ ci->ports.erase(ctx->id("RGBPU"));
ci->ports.erase(ctx->id("RGB0"));
ci->ports.erase(ctx->id("RGB1"));
ci->ports.erase(ctx->id("RGB2"));
diff --git a/json/jsonparse.cc b/json/jsonparse.cc
index 5d77c101..d463d8ce 100644
--- a/json/jsonparse.cc
+++ b/json/jsonparse.cc
@@ -342,7 +342,7 @@ static int const_net_idx = 0;
template <typename F>
void json_import_ports(Context *ctx, const string &modname, const std::vector<IdString> &netnames,
const string &obj_name, const string &port_name, JsonNode *dir_node, JsonNode *wire_group_node,
- F visitor)
+ bool upto, int start_offset, F visitor)
{
// Examine a port of a cell or the design. For every bit of the port,
// the connected net will be processed and `visitor` will be called
@@ -411,8 +411,11 @@ void json_import_ports(Context *ctx, const string &modname, const std::vector<Id
wire_node = wire_group_node->data_array[index];
//
// Pick a name for this port
+ int ndx = index + start_offset;
+ if (upto)
+ ndx = start_offset + wire_group_node->data_array.size() - index - 1;
if (is_bus)
- this_port.name = ctx->id(port_info.name.str(ctx) + "[" + std::to_string(index) + "]");
+ this_port.name = ctx->id(port_info.name.str(ctx) + "[" + std::to_string(ndx) + "]");
else
this_port.name = port_info.name;
this_port.type = port_info.type;
@@ -589,7 +592,7 @@ void json_import_cell(Context *ctx, string modname, const std::vector<IdString>
dir_node = pdir_node->data_dict.at(port_name);
wire_group_node = connections->data_dict.at(port_name);
- json_import_ports(ctx, modname, netnames, cell->name.str(ctx), port_name, dir_node, wire_group_node,
+ json_import_ports(ctx, modname, netnames, cell->name.str(ctx), port_name, dir_node, wire_group_node, false, 0,
[&cell, ctx](PortType type, const std::string &name, NetInfo *net) {
cell->ports[ctx->id(name)] = PortInfo{ctx->id(name), net, type};
PortRef pr;
@@ -685,8 +688,20 @@ void json_import_toplevel_port(Context *ctx, const string &modname, const std::v
{
JsonNode *dir_node = node->data_dict.at("direction");
JsonNode *nets_node = node->data_dict.at("bits");
+ bool upto = false;
+ int start_offset = 0;
+ if (node->data_dict.count("upto") != 0) {
+ JsonNode *val = node->data_dict.at("upto");
+ if (val->type == 'N')
+ upto = val->data_number != 0;
+ }
+ if (node->data_dict.count("offset") != 0) {
+ JsonNode *val = node->data_dict.at("offset");
+ if (val->type == 'N')
+ start_offset = val->data_number;
+ }
json_import_ports(
- ctx, modname, netnames, "Top Level IO", portname, dir_node, nets_node,
+ ctx, modname, netnames, "Top Level IO", portname, dir_node, nets_node, upto, start_offset,
[ctx](PortType type, const std::string &name, NetInfo *net) { insert_iobuf(ctx, net, type, name); });
}
@@ -697,11 +712,22 @@ void json_import(Context *ctx, string modname, JsonNode *node)
log_info("Importing module %s\n", modname.c_str());
+ JsonNode *ports_parent = nullptr;
+ if (node->data_dict.count("ports") > 0)
+ ports_parent = node->data_dict.at("ports");
+
// Multiple labels might refer to the same net. For now we resolve conflicts thus:
+ // - (toplevel) ports are always preferred
// - names with fewer $ are always prefered
// - between equal $ counts, fewer .s are prefered
// - ties are resolved alphabetically
- auto prefer_netlabel = [](const std::string &a, const std::string &b) {
+ auto prefer_netlabel = [ports_parent](const std::string &a, const std::string &b) {
+ if (ports_parent != nullptr) {
+ if (ports_parent->data_dict.count(a))
+ return true;
+ if (ports_parent->data_dict.count(b))
+ return false;
+ }
if (b.empty())
return true;
long a_dollars = std::count(a.begin(), a.end(), '$'), b_dollars = std::count(b.begin(), b.end(), '$');
@@ -726,6 +752,18 @@ void json_import(Context *ctx, string modname, JsonNode *node)
here = cell_parent->data_dict.at(cell_parent->data_dict_keys[nnid]);
std::string basename = cell_parent->data_dict_keys[nnid];
+ bool upto = false;
+ int start_offset = 0;
+ if (here->data_dict.count("upto") != 0) {
+ JsonNode *val = here->data_dict.at("upto");
+ if (val->type == 'N')
+ upto = val->data_number != 0;
+ }
+ if (here->data_dict.count("offset") != 0) {
+ JsonNode *val = here->data_dict.at("offset");
+ if (val->type == 'N')
+ start_offset = val->data_number;
+ }
if (here->data_dict.count("bits")) {
JsonNode *bits = here->data_dict.at("bits");
assert(bits->type == 'A');
@@ -734,8 +772,11 @@ void json_import(Context *ctx, string modname, JsonNode *node)
int netid = bits->data_array.at(i)->data_number;
if (netid >= int(netlabels.size()))
netlabels.resize(netid + 1);
+ int ndx = i + start_offset;
+ if (upto)
+ ndx = start_offset + num_bits - i - 1;
std::string name =
- basename + (num_bits == 1 ? "" : std::string("[") + std::to_string(i) + std::string("]"));
+ basename + (num_bits == 1 ? "" : std::string("[") + std::to_string(ndx) + std::string("]"));
if (prefer_netlabel(name, netlabels.at(netid)))
netlabels.at(netid) = name;
}
@@ -758,9 +799,7 @@ void json_import(Context *ctx, string modname, JsonNode *node)
}
}
- if (node->data_dict.count("ports")) {
- JsonNode *ports_parent = node->data_dict.at("ports");
-
+ if (ports_parent != nullptr) {
// N.B. ports must be imported after cells for tristate behaviour
// to be correct
// Loop through all ports, first non-tristate then tristate to handle
diff --git a/tests b/tests
-Subproject 0d369eb3fe3425fa74c0f6309268a012aac5040
+Subproject 5182fd4bec49a568cc3fa37d62d9f9a82f28091