Merge pull request #261 from YosysHQ/pygeneric

Python API for generic architecture
This commit is contained in:
David Shah 2019-04-19 17:40:55 +01:00 committed by GitHub
commit 5344bc3b65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1473 additions and 40 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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());
}
}

View File

@ -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

119
docs/generic.md Normal file
View File

@ -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.

View File

@ -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 { return true; }
bool Arch::isBelLocationValid(BelId bel) const { return true; }
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::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

View File

@ -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

View File

@ -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

View File

@ -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

139
generic/cells.cc Normal file
View File

@ -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

55
generic/cells.h Normal file
View File

@ -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

3
generic/examples/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
blinky.fasm
__pycache__
*.pyc

View File

@ -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

View File

View File

@ -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)

View File

@ -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

View File

@ -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)

4
generic/examples/simple.sh Executable file
View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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()
{

293
generic/pack.cc Normal file
View File

@ -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

10
generic/synth/cells_map.v Normal file
View File

@ -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

59
generic/synth/prims.v Normal file
View File

@ -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

View File

@ -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] }

View File

@ -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() {}

View File

@ -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();