From f5bfd557b625d1b5349c4a8305eccec30bafbe69 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 31 Mar 2019 12:34:26 +0100 Subject: python: Infrastructure for generic arch Python API Signed-off-by: David Shah --- common/pybindings.cc | 31 ++++++++++++++++ common/pywrappers.h | 48 ++++++++++++++++++++++++ generic/arch_pybindings.cc | 91 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+) diff --git a/common/pybindings.cc b/common/pybindings.cc index eee78b5e..dad8e5c7 100644 --- a/common/pybindings.cc +++ b/common/pybindings.cc @@ -81,6 +81,34 @@ template <> struct string_converter } // namespace PythonConversion +struct loc_from_tuple +{ + loc_from_tuple() { converter::registry::push_back(&convertible, &construct, boost::python::type_id()); } + + static void *convertible(PyObject *obj_ptr) + { + if (!PyTuple_Check(obj_ptr)) + return 0; + return obj_ptr; + } + + static void construct(PyObject *obj_ptr, converter::rvalue_from_python_stage1_data *data) + { + int val[3]; + for (int i = 0; i < 3; i++) { + PyObject *pyo = PyTuple_GetItem(obj_ptr, i); + if (!pyo) + throw_error_already_set(); + NPNR_ASSERT(PyLong_Check(pyo)); + val[i] = int(PyLong_AsLong(pyo)); + } + + void *storage = ((converter::rvalue_from_python_storage *)data)->storage.bytes; + new (storage) Loc(val[0], val[1], val[2]); + data->convertible = storage; + } +}; + BOOST_PYTHON_MODULE(MODULE_NAME) { register_exception_translator(&translate_assertfail); @@ -108,6 +136,9 @@ BOOST_PYTHON_MODULE(MODULE_NAME) class_("BaseCtx", no_init); + auto loc_cls = + class_("Loc").def_readwrite("x", &Loc::x).def_readwrite("y", &Loc::y).def_readwrite("z", &Loc::z); + auto ci_cls = class_>("CellInfo", no_init); readwrite_wrapper, conv_from_str>::def_wrap(ci_cls, "name"); diff --git a/common/pywrappers.h b/common/pywrappers.h index 427c3623..b564eb59 100644 --- a/common/pywrappers.h +++ b/common/pywrappers.h @@ -288,6 +288,29 @@ struct fn_wrapper_3a_v template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } }; +// Four parameters, no return +template +struct fn_wrapper_4a_v +{ + using class_type = typename WrapIfNotContext::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(cls); + Class &base = get_base(cls); + return (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3), + arg4_conv()(ctx, arg4)); + } + + template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } +}; + // Five parameters, no return template @@ -312,6 +335,31 @@ struct fn_wrapper_5a_v template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } }; +// Six parameters, no return +template +struct fn_wrapper_6a_v +{ + using class_type = typename WrapIfNotContext::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(cls); + Class &base = get_base(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 static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } +}; + // Wrapped getter template struct readonly_wrapper { diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc index 186b2c13..5eabd42a 100644 --- a/generic/arch_pybindings.cc +++ b/generic/arch_pybindings.cc @@ -23,6 +23,7 @@ #include "arch_pybindings.h" #include "nextpnr.h" #include "pybindings.h" +#include "pywrappers.h" NEXTPNR_NAMESPACE_BEGIN @@ -35,6 +36,96 @@ void arch_wrap_python() .def("pack", &Context::pack) .def("place", &Context::place) .def("route", &Context::route); + + class_("BelPin").def_readwrite("bel", &BelPin::bel).def_readwrite("pin", &BelPin::pin); + + fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getBelType"); + fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "checkBelAvail"); + fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getBelChecksum"); + fn_wrapper_3a_v, + addr_and_unwrap, pass_through>::def_wrap(ctx_cls, "bindBel"); + fn_wrapper_1a_v>::def_wrap( + ctx_cls, "unbindBel"); + fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getBoundBelCell"); + fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getConflictingBelCell"); + fn_wrapper_0a &>>::def_wrap(ctx_cls, "getBels"); + + fn_wrapper_2a, + conv_from_str, conv_from_str>::def_wrap(ctx_cls, "getBelPinWire"); + fn_wrapper_1a &>, conv_from_str>::def_wrap(ctx_cls, + "getWireBelPins"); + + fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getWireChecksum"); + fn_wrapper_3a_v, + addr_and_unwrap, pass_through>::def_wrap(ctx_cls, "bindWire"); + fn_wrapper_1a_v>::def_wrap( + ctx_cls, "unbindWire"); + fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "checkWireAvail"); + fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getBoundWireNet"); + fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getConflictingWireNet"); + + fn_wrapper_0a &>>::def_wrap(ctx_cls, "getWires"); + + fn_wrapper_0a &>>::def_wrap(ctx_cls, "getPips"); + fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getPipChecksum"); + fn_wrapper_3a_v, + addr_and_unwrap, pass_through>::def_wrap(ctx_cls, "bindPip"); + fn_wrapper_1a_v>::def_wrap( + ctx_cls, "unbindPip"); + fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "checkPipAvail"); + fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getBoundPipNet"); + fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getConflictingPipNet"); + + fn_wrapper_1a &>, conv_from_str>::def_wrap(ctx_cls, + "getPipsDownhill"); + fn_wrapper_1a &>, conv_from_str>::def_wrap(ctx_cls, "getPipsUphill"); + fn_wrapper_1a &>, conv_from_str>::def_wrap(ctx_cls, "getWireAliases"); + + fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getPipSrcWire"); + fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getPipDstWire"); + fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getPipDelay"); + + fn_wrapper_0a>::def_wrap( + ctx_cls, "getChipName"); + fn_wrapper_0a>::def_wrap(ctx_cls, + "archId"); + + typedef std::unordered_map> CellMap; + typedef std::unordered_map> NetMap; + + readonly_wrapper>::def_wrap(ctx_cls, + "cells"); + readonly_wrapper>::def_wrap(ctx_cls, + "nets"); + + fn_wrapper_2a_v, + pass_through>::def_wrap(ctx_cls, "addClock"); + + WRAP_MAP_UPTR(CellMap, "IdCellMap"); + WRAP_MAP_UPTR(NetMap, "IdNetMap"); } NEXTPNR_NAMESPACE_END -- cgit v1.2.3 From 30f0c582e4b809dd860d34c8c4c598a91374b029 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 31 Mar 2019 17:28:45 +0100 Subject: python: Named argument support Signed-off-by: David Shah --- common/pybindings.cc | 1 + common/pywrappers.h | 30 ++++++++++++++++++++++++++++++ generic/arch_pybindings.cc | 1 + 3 files changed, 32 insertions(+) diff --git a/common/pybindings.cc b/common/pybindings.cc index dad8e5c7..547d9b62 100644 --- a/common/pybindings.cc +++ b/common/pybindings.cc @@ -241,6 +241,7 @@ void init_python(const char *executable, bool first) Py_Initialize(); if (first) 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(); diff --git a/common/pywrappers.h b/common/pywrappers.h index b564eb59..b9ed807d 100644 --- a/common/pywrappers.h +++ b/common/pywrappers.h @@ -250,6 +250,11 @@ template struct f } template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } + + template 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 +272,11 @@ template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } + + template static void def_wrap(WrapCls cls_, const char *name, const Ta &a) + { + cls_.def(name, wrapped_fn, a); + } }; // Three parameters, no return @@ -286,6 +296,11 @@ struct fn_wrapper_3a_v } template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } + + template static void def_wrap(WrapCls cls_, const char *name, const Ta &a) + { + cls_.def(name, wrapped_fn, a); + } }; // Four parameters, no return @@ -309,6 +324,11 @@ struct fn_wrapper_4a_v } template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } + + template static void def_wrap(WrapCls cls_, const char *name, const Ta &a) + { + cls_.def(name, wrapped_fn, a); + } }; // Five parameters, no return @@ -333,6 +353,11 @@ struct fn_wrapper_5a_v } template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } + + template static void def_wrap(WrapCls cls_, const char *name, const Ta &a) + { + cls_.def(name, wrapped_fn, a); + } }; // Six parameters, no return @@ -358,6 +383,11 @@ struct fn_wrapper_6a_v } template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } + + template static void def_wrap(WrapCls cls_, const char *name, const Ta &a) + { + cls_.def(name, wrapped_fn, a); + } }; // Wrapped getter diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc index 5eabd42a..04620cd5 100644 --- a/generic/arch_pybindings.cc +++ b/generic/arch_pybindings.cc @@ -30,6 +30,7 @@ NEXTPNR_NAMESPACE_BEGIN void arch_wrap_python() { using namespace PythonConversion; + auto arch_cls = class_, boost::noncopyable>("Arch", init()); auto ctx_cls = class_, boost::noncopyable>("Context", no_init) .def("checksum", &Context::checksum) -- cgit v1.2.3 From fd3ad755988ee185427479595a8c368c3f1b9519 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 31 Mar 2019 17:44:44 +0100 Subject: generic: Python bindings for arch construction Signed-off-by: David Shah --- generic/arch_pybindings.cc | 56 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc index 04620cd5..407dc4d4 100644 --- a/generic/arch_pybindings.cc +++ b/generic/arch_pybindings.cc @@ -125,6 +125,62 @@ void arch_wrap_python() fn_wrapper_2a_v, pass_through>::def_wrap(ctx_cls, "addClock"); + // Generic arch construction API + fn_wrapper_4a_v, + conv_from_str, pass_through, pass_through>::def_wrap(ctx_cls, "addWire", + (arg("name"), "type", "x", + "y")); + fn_wrapper_6a_v, + conv_from_str, conv_from_str, conv_from_str, pass_through, + pass_through>::def_wrap(ctx_cls, "addPip", + (arg("name"), "type", "srcWire", "dstWire", "delay", "loc")); + fn_wrapper_5a_v, + conv_from_str, conv_from_str, conv_from_str, + pass_through>::def_wrap(ctx_cls, "addAlias", + (arg("name"), "type", "srcWire", "dstWire", "delay")); + + fn_wrapper_4a_v, + conv_from_str, pass_through, pass_through>::def_wrap(ctx_cls, "addBel", + (arg("name"), "type", + "loc", "gb")); + fn_wrapper_3a_v, + conv_from_str, conv_from_str>::def_wrap(ctx_cls, "addBelInput", + (arg("bel"), "name", "wire")); + fn_wrapper_3a_v, + conv_from_str, conv_from_str>::def_wrap(ctx_cls, "addBelOutput", + (arg("bel"), "name", "wire")); + fn_wrapper_3a_v, + conv_from_str, conv_from_str>::def_wrap(ctx_cls, "addBelInout", + (arg("bel"), "name", "wire")); + + fn_wrapper_2a_v, + conv_from_str>::def_wrap(ctx_cls, "addGroupBel", (arg("group"), "bel")); + fn_wrapper_2a_v, + conv_from_str>::def_wrap(ctx_cls, "addGroupWire", (arg("group"), "wire")); + fn_wrapper_2a_v, + conv_from_str>::def_wrap(ctx_cls, "addGroupPip", (arg("group"), "pip")); + fn_wrapper_2a_v, + conv_from_str>::def_wrap(ctx_cls, "addGroupGroup", (arg("group"), "grp")); + + fn_wrapper_2a_v, + pass_through>::def_wrap(ctx_cls, "addDecalGraphic", (arg("decal"), "graphic")); + fn_wrapper_2a_v, + pass_through>::def_wrap(ctx_cls, "setWireDecal", (arg("wire"), "decalxy")); + fn_wrapper_2a_v, + pass_through>::def_wrap(ctx_cls, "setPipDecal", (arg("pip"), "decalxy")); + fn_wrapper_2a_v, + pass_through>::def_wrap(ctx_cls, "setBelDecal", (arg("bel"), "decalxy")); + fn_wrapper_2a_v, + pass_through>::def_wrap(ctx_cls, "setGroupDecal", (arg("group"), "decalxy")); + + fn_wrapper_3a_v, + conv_from_str, pass_through>::def_wrap(ctx_cls, "setWireAttr", (arg("wire"), "key", "value")); + fn_wrapper_3a_v, + conv_from_str, pass_through>::def_wrap(ctx_cls, "setBelAttr", (arg("bel"), "key", "value")); + fn_wrapper_3a_v, + conv_from_str, pass_through>::def_wrap(ctx_cls, "setPipAttr", (arg("pip"), "key", "value")); + + WRAP_MAP_UPTR(CellMap, "IdCellMap"); WRAP_MAP_UPTR(NetMap, "IdNetMap"); } -- cgit v1.2.3 From 50fd8aa01fde3426ff74fcf9b0126a24f279efca Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 31 Mar 2019 17:54:52 +0100 Subject: generic: Place a single SLICE Signed-off-by: David Shah --- common/pybindings.cc | 35 +++++------------------------------ generic/arch_pybindings.cc | 10 ++++++---- generic/examples/simple.py | 3 +++ generic/examples/simple.v | 15 +++++++++++++++ 4 files changed, 29 insertions(+), 34 deletions(-) create mode 100644 generic/examples/simple.py create mode 100644 generic/examples/simple.v diff --git a/common/pybindings.cc b/common/pybindings.cc index 547d9b62..bf5382eb 100644 --- a/common/pybindings.cc +++ b/common/pybindings.cc @@ -81,34 +81,6 @@ template <> struct string_converter } // namespace PythonConversion -struct loc_from_tuple -{ - loc_from_tuple() { converter::registry::push_back(&convertible, &construct, boost::python::type_id()); } - - static void *convertible(PyObject *obj_ptr) - { - if (!PyTuple_Check(obj_ptr)) - return 0; - return obj_ptr; - } - - static void construct(PyObject *obj_ptr, converter::rvalue_from_python_stage1_data *data) - { - int val[3]; - for (int i = 0; i < 3; i++) { - PyObject *pyo = PyTuple_GetItem(obj_ptr, i); - if (!pyo) - throw_error_already_set(); - NPNR_ASSERT(PyLong_Check(pyo)); - val[i] = int(PyLong_AsLong(pyo)); - } - - void *storage = ((converter::rvalue_from_python_storage *)data)->storage.bytes; - new (storage) Loc(val[0], val[1], val[2]); - data->convertible = storage; - } -}; - BOOST_PYTHON_MODULE(MODULE_NAME) { register_exception_translator(&translate_assertfail); @@ -136,8 +108,11 @@ BOOST_PYTHON_MODULE(MODULE_NAME) class_("BaseCtx", no_init); - auto loc_cls = - class_("Loc").def_readwrite("x", &Loc::x).def_readwrite("y", &Loc::y).def_readwrite("z", &Loc::z); + auto loc_cls = class_("Loc") + .def(init()) + .def_readwrite("x", &Loc::x) + .def_readwrite("y", &Loc::y) + .def_readwrite("z", &Loc::z); auto ci_cls = class_>("CellInfo", no_init); readwrite_wrapper, diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc index 407dc4d4..014b7758 100644 --- a/generic/arch_pybindings.cc +++ b/generic/arch_pybindings.cc @@ -174,12 +174,14 @@ void arch_wrap_python() pass_through>::def_wrap(ctx_cls, "setGroupDecal", (arg("group"), "decalxy")); fn_wrapper_3a_v, - conv_from_str, pass_through>::def_wrap(ctx_cls, "setWireAttr", (arg("wire"), "key", "value")); + conv_from_str, pass_through>::def_wrap(ctx_cls, "setWireAttr", + (arg("wire"), "key", "value")); fn_wrapper_3a_v, - conv_from_str, pass_through>::def_wrap(ctx_cls, "setBelAttr", (arg("bel"), "key", "value")); + conv_from_str, pass_through>::def_wrap(ctx_cls, "setBelAttr", + (arg("bel"), "key", "value")); fn_wrapper_3a_v, - conv_from_str, pass_through>::def_wrap(ctx_cls, "setPipAttr", (arg("pip"), "key", "value")); - + conv_from_str, pass_through>::def_wrap(ctx_cls, "setPipAttr", + (arg("pip"), "key", "value")); WRAP_MAP_UPTR(CellMap, "IdCellMap"); WRAP_MAP_UPTR(NetMap, "IdNetMap"); diff --git a/generic/examples/simple.py b/generic/examples/simple.py new file mode 100644 index 00000000..da41dc5b --- /dev/null +++ b/generic/examples/simple.py @@ -0,0 +1,3 @@ +ctx.addBel(name="SLICE_X1Y1", type="SLICE_LUT4", loc=Loc(1, 1, 0), gb=False) +ctx.addBel(name="IO0_I", type="$nextpnr_ibuf", loc=Loc(0, 0, 0), gb=False) +ctx.addBel(name="IO1_O", type="$nextpnr_obuf", loc=Loc(1, 0, 0), gb=False) \ No newline at end of file diff --git a/generic/examples/simple.v b/generic/examples/simple.v new file mode 100644 index 00000000..6d337101 --- /dev/null +++ b/generic/examples/simple.v @@ -0,0 +1,15 @@ +(* blackbox *) +module SLICE_LUT4( + input I0, I1, I2, I3, + input CLK, + output Q +); +parameter INIT = 16'h0000; +parameter FF_USED = 1'b0; +endmodule + +module top(input a, output q); + +SLICE_LUT4 sl_i(.I0(a), .Q(q)); + +endmodule \ No newline at end of file -- cgit v1.2.3 From f88ddf85b296e486731a79ec2c6fc698169a0657 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Apr 2019 17:44:27 +0100 Subject: generic: Add simple primitive library Signed-off-by: David Shah --- generic/synth/cells_map.v | 12 ++++++++++ generic/synth/prims.v | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 generic/synth/cells_map.v create mode 100644 generic/synth/prims.v diff --git a/generic/synth/cells_map.v b/generic/synth/cells_map.v new file mode 100644 index 00000000..adbccb52 --- /dev/null +++ b/generic/synth/cells_map.v @@ -0,0 +1,12 @@ +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_N_ (input D, C, output Q); DFF _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(C)); 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 -- cgit v1.2.3 From 99c3713293f4cc56ab933b952ec522ade7526eb8 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Apr 2019 18:02:08 +0100 Subject: generic: Add synth_generic.tcl Signed-off-by: David Shah --- generic/synth/blink.v | 9 +++++++++ generic/synth/cells_map.v | 4 +--- generic/synth/synth_generic.tcl | 24 ++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 generic/synth/blink.v create mode 100644 generic/synth/synth_generic.tcl diff --git a/generic/synth/blink.v b/generic/synth/blink.v new file mode 100644 index 00000000..b7cb1b86 --- /dev/null +++ b/generic/synth/blink.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/synth/cells_map.v b/generic/synth/cells_map.v index adbccb52..a6027534 100644 --- a/generic/synth/cells_map.v +++ b/generic/synth/cells_map.v @@ -7,6 +7,4 @@ module \$lut (A, Y); LUT #(.K(`LUT_K), .INIT(LUT)) _TECHMAP_REPLACE_ (.I(A), .Q(Y)); endmodule - -module \$_DFF_N_ (input D, C, output Q); DFF _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(C)); endmodule -module \$_DFF_P_ (input D, C, output Q); DFF _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(!C)); endmodule +module \$_DFF_P_ (input D, C, output Q); DFF _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(C)); endmodule diff --git a/generic/synth/synth_generic.tcl b/generic/synth/synth_generic.tcl new file mode 100644 index 00000000..c5950788 --- /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 +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] } -- cgit v1.2.3 From ca918078bfe6c4b1a279c7df7c59fb9de0f9710a Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Apr 2019 19:13:16 +0100 Subject: generic: Add a simple packer for generic SLICEs and IOBs Signed-off-by: David Shah --- generic/arch.cc | 72 ++++++++++- generic/arch.h | 15 ++- generic/archdefs.h | 10 ++ generic/cells.cc | 139 +++++++++++++++++++++ generic/cells.h | 55 +++++++++ generic/examples/simple.py | 19 ++- generic/pack.cc | 293 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 597 insertions(+), 6 deletions(-) create mode 100644 generic/cells.cc create mode 100644 generic/cells.h create mode 100644 generic/pack.cc diff --git a/generic/arch.cc b/generic/arch.cc index aca81559..a0123d9e 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -191,6 +191,14 @@ 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; +} + // --------------------------------------------------------------- Arch::Arch(ArchArgs args) : chipName("generic"), args(args) {} @@ -483,10 +491,70 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port NPNR_ASSERT_FALSE("no clocking info for generic"); } -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 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 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 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..58e5faa4 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; @@ -127,6 +132,9 @@ 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); + // --------------------------------------------------------------- // Common Arch API. Every arch must provide the following methods. @@ -222,7 +230,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 +251,11 @@ struct Arch : BaseCtx static const std::string defaultPlacer; static const std::vector availablePlacers; + + // --------------------------------------------------------------- + // Internal usage + void assignArchInfo(); + bool cellsCompatible(const CellInfo **cells, int count) const; }; 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 + * + * 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 create_generic_cell(Context *ctx, IdString type, std::string name) +{ + static int auto_idx = 0; + std::unique_ptr new_cell = std::unique_ptr(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 &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 + * + * 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 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 &todelete_cells); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/generic/examples/simple.py b/generic/examples/simple.py index da41dc5b..17808fc7 100644 --- a/generic/examples/simple.py +++ b/generic/examples/simple.py @@ -1,3 +1,16 @@ -ctx.addBel(name="SLICE_X1Y1", type="SLICE_LUT4", loc=Loc(1, 1, 0), gb=False) -ctx.addBel(name="IO0_I", type="$nextpnr_ibuf", loc=Loc(0, 0, 0), gb=False) -ctx.addBel(name="IO1_O", type="$nextpnr_obuf", loc=Loc(1, 0, 0), gb=False) \ No newline at end of file +X = 12 +Y = 12 + +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): + 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) + else: + ctx.addBel(name="X%dY%d_SLICE%d" % (x, y, z), type="GENERIC_SLICE", loc=Loc(x, y, z), gb=False) diff --git a/generic/pack.cc b/generic/pack.cc new file mode 100644 index 00000000..4d4a76a8 --- /dev/null +++ b/generic/pack.cc @@ -0,0 +1,293 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018-19 David Shah + * + * 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 +#include +#include +#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 packed_cells; + std::vector> 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 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 packed_cells; + std::vector> new_cells; + + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_ff(ctx, ci)) { + std::unique_ptr 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 gnd_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_GND"); + gnd_cell->params[ctx->id("INIT")] = "0"; + std::unique_ptr gnd_net = std::unique_ptr(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 vcc_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_VCC"); + vcc_cell->params[ctx->id("INIT")] = "1"; + std::unique_ptr vcc_net = std::unique_ptr(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 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 packed_cells; + std::unordered_set delete_nets; + + std::vector> 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 ice_cell = + create_generic_cell(ctx, ctx->id("GENERIC_IOB"), ci->name.str(ctx) + "$sb_io"); + 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 -- cgit v1.2.3 From 6a383cd4c57db1f8bab6416daffdb24c0eb093c6 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Apr 2019 19:48:51 +0100 Subject: generic: Simple procedural example works Signed-off-by: David Shah --- generic/arch.cc | 12 ++++++-- generic/arch.h | 2 -- generic/arch_pybindings.cc | 5 ++++ generic/examples/simple.py | 75 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 87 insertions(+), 7 deletions(-) diff --git a/generic/arch.cc b/generic/arch.cc index a0123d9e..01f7ef55 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -268,7 +268,13 @@ IdString Arch::getBelType(BelId bel) const { return bels.at(bel).type; } const std::map &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; } @@ -430,7 +436,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 @@ -441,7 +447,7 @@ delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const 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; + return (dx + dy) * args.delayScale + args.delayOffset; } bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; } diff --git a/generic/arch.h b/generic/arch.h index 58e5faa4..02017c8d 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -106,8 +106,6 @@ struct Arch : BaseCtx std::vector> tileBelDimZ; std::vector> tilePipDimZ; - float grid_distance_to_delay; - void addWire(IdString name, IdString type, int x, int y); void addPip(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay, Loc loc); void addAlias(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay); diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc index 014b7758..950e7e34 100644 --- a/generic/arch_pybindings.cc +++ b/generic/arch_pybindings.cc @@ -40,6 +40,8 @@ void arch_wrap_python() class_("BelPin").def_readwrite("bel", &BelPin::bel).def_readwrite("pin", &BelPin::pin); + class_("DelayInfo").def("maxDelay", &DelayInfo::maxDelay).def("minDelay", &DelayInfo::minDelay); + fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getBelType"); fn_wrapper_1a, @@ -109,6 +111,9 @@ void arch_wrap_python() fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getPipDelay"); + fn_wrapper_1a, + pass_through>::def_wrap(ctx_cls, "getDelayFromNS"); + fn_wrapper_0a>::def_wrap( ctx_cls, "getChipName"); fn_wrapper_0a>::def_wrap(ctx_cls, diff --git a/generic/examples/simple.py b/generic/examples/simple.py index 17808fc7..b8ca3f78 100644 --- a/generic/examples/simple.py +++ b/generic/examples/simple.py @@ -1,16 +1,87 @@ +# Grid size including IOBs at edges X = 12 Y = 12 +# SLICEs per tile +Z = 8 +# LUT input count +K = 4 +# Number of local wires +L = Z*(K+1) + 8 +# "Sparsity" of bel input wire pips +Si = 4 +# "Sparsity" of Q to local wire pips +Sq = 4 +# "Sparsity" of local to neighbour local wire pips +Sl = 8 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(Z): + 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(L): + 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: - ctx.addBel(name="X%dY%d_SLICE%d" % (x, y, z), type="GENERIC_SLICE", loc=Loc(x, y, z), gb=False) + for z in range(Z): + 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, L, 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(Z): + 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, Z, 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, L, 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(L): + 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 -- cgit v1.2.3 From 32327b761ab8b8c438bd91d6c32f061ffaed3454 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Apr 2019 20:16:29 +0100 Subject: generic: Simple working example Signed-off-by: David Shah --- common/pywrappers.h | 33 +++++++++++++++++++++++++-------- generic/arch_pybindings.cc | 9 +++++++++ generic/examples/.gitignore | 1 + generic/examples/README.md | 11 +++++++++++ generic/examples/blinky.v | 9 +++++++++ generic/examples/report.py | 13 +++++++++++++ generic/examples/simple.sh | 4 ++++ generic/examples/simple.v | 15 --------------- generic/main.cc | 2 +- generic/pack.cc | 2 +- generic/synth/blink.v | 9 --------- generic/synth/synth_generic.tcl | 2 +- 12 files changed, 75 insertions(+), 35 deletions(-) create mode 100644 generic/examples/.gitignore create mode 100644 generic/examples/README.md create mode 100644 generic/examples/blinky.v create mode 100644 generic/examples/report.py create mode 100755 generic/examples/simple.sh delete mode 100644 generic/examples/simple.v delete mode 100644 generic/synth/blink.v diff --git a/common/pywrappers.h b/common/pywrappers.h index b9ed807d..1d970985 100644 --- a/common/pywrappers.h +++ b/common/pywrappers.h @@ -155,11 +155,15 @@ template struct fn_ using class_type = typename WrapIfNotContext::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(cls); Class &base = get_base(cls); - return rv_conv()(ctx, (base.*fn)()); + try { + return object(rv_conv()(ctx, (base.*fn)())); + } catch (bad_wrap &) { + return object(); + } } template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } @@ -172,11 +176,15 @@ template (cls); Class &base = get_base(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 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(cls); Class &base = get_base(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 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(cls); Class &base = get_base(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 static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc index 950e7e34..f4e7c564 100644 --- a/generic/arch_pybindings.cc +++ b/generic/arch_pybindings.cc @@ -26,6 +26,14 @@ #include "pywrappers.h" NEXTPNR_NAMESPACE_BEGIN +namespace PythonConversion { +template <> struct string_converter +{ + 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() { @@ -190,6 +198,7 @@ void arch_wrap_python() WRAP_MAP_UPTR(CellMap, "IdCellMap"); WRAP_MAP_UPTR(NetMap, "IdNetMap"); + WRAP_VECTOR(const std::vector, conv_to_str); } NEXTPNR_NAMESPACE_END diff --git a/generic/examples/.gitignore b/generic/examples/.gitignore new file mode 100644 index 00000000..83d79a7d --- /dev/null +++ b/generic/examples/.gitignore @@ -0,0 +1 @@ +blinky.txt diff --git a/generic/examples/README.md b/generic/examples/README.md new file mode 100644 index 00000000..5eb0ea72 --- /dev/null +++ b/generic/examples/README.md @@ -0,0 +1,11 @@ +# 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 + + - report.py stores design information after place-and-route to blinky.txt in place + of real bitstream generation + + - Run blinky.sh to build an example design on the FPGA above \ 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/report.py b/generic/examples/report.py new file mode 100644 index 00000000..c43367fa --- /dev/null +++ b/generic/examples/report.py @@ -0,0 +1,13 @@ +with open("blinky.txt", "w") as f: + for nname, net in ctx.nets: + print("# Net %s" % nname, file=f) + # FIXME: Pip ordering + for wire, pip in net.wires: + if pip.pip != "": + print("%s" % pip.pip, file=f) + print("", file=f) + for cname, cell in ctx.cells: + print("# Cell %s at %s" % (cname, cell.bel), file=f) + for param, val in cell.params: + print("%s.%s %s" % (cell.bel, param, val), file=f) + print("", file=f) \ No newline at end of file diff --git a/generic/examples/simple.sh b/generic/examples/simple.sh new file mode 100755 index 00000000..ed800639 --- /dev/null +++ b/generic/examples/simple.sh @@ -0,0 +1,4 @@ +#!/usr/bin/bash +set -ex +yosys -p "tcl ../synth/synth_generic.tcl 4 blinky.json" blinky.v +../../nextpnr-generic --pre-pack simple.py --json blinky.json --post-route report.py \ No newline at end of file diff --git a/generic/examples/simple.v b/generic/examples/simple.v deleted file mode 100644 index 6d337101..00000000 --- a/generic/examples/simple.v +++ /dev/null @@ -1,15 +0,0 @@ -(* blackbox *) -module SLICE_LUT4( - input I0, I1, I2, I3, - input CLK, - output Q -); -parameter INIT = 16'h0000; -parameter FF_USED = 1'b0; -endmodule - -module top(input a, output q); - -SLICE_LUT4 sl_i(.I0(a), .Q(q)); - -endmodule \ 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 GenericCommandHandler::createContext() { diff --git a/generic/pack.cc b/generic/pack.cc index 4d4a76a8..26ae9182 100644 --- a/generic/pack.cc +++ b/generic/pack.cc @@ -252,7 +252,7 @@ static void pack_io(Context *ctx) } else { // Create a GENERIC_IOB buffer std::unique_ptr ice_cell = - create_generic_cell(ctx, ctx->id("GENERIC_IOB"), ci->name.str(ctx) + "$sb_io"); + 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(); diff --git a/generic/synth/blink.v b/generic/synth/blink.v deleted file mode 100644 index b7cb1b86..00000000 --- a/generic/synth/blink.v +++ /dev/null @@ -1,9 +0,0 @@ -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/synth/synth_generic.tcl b/generic/synth/synth_generic.tcl index c5950788..e5d88e0d 100644 --- a/generic/synth/synth_generic.tcl +++ b/generic/synth/synth_generic.tcl @@ -14,7 +14,7 @@ yosys memory_map yosys opt -full yosys techmap -map +/techmap.v yosys opt -fast -yosys abc -lut $LUT_K +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 -- cgit v1.2.3 From f2c911bf07ece78677b01bf464018337f5770ba7 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 2 Apr 2019 12:38:58 +0100 Subject: generic: Adding API usage documentation Signed-off-by: David Shah --- docs/generic.md | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 docs/generic.md diff --git a/docs/generic.md b/docs/generic.md new file mode 100644 index 00000000..2f884274 --- /dev/null +++ b/docs/generic.md @@ -0,0 +1,99 @@ +# 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. + +## 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. -- cgit v1.2.3 From a05593da624d08e6b5c0356448238639479dd250 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 2 Apr 2019 15:27:48 +0100 Subject: generic: Add a few more bindings Signed-off-by: David Shah --- generic/arch_pybindings.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc index f4e7c564..eae78c9a 100644 --- a/generic/arch_pybindings.cc +++ b/generic/arch_pybindings.cc @@ -196,6 +196,11 @@ void arch_wrap_python() conv_from_str, pass_through>::def_wrap(ctx_cls, "setPipAttr", (arg("pip"), "key", "value")); + fn_wrapper_1a_v>::def_wrap( + ctx_cls, "setLutK", arg("K")); + fn_wrapper_2a_v, + pass_through>::def_wrap(ctx_cls, "setDelayScaling", (arg("scale"), "offset")); + WRAP_MAP_UPTR(CellMap, "IdCellMap"); WRAP_MAP_UPTR(NetMap, "IdNetMap"); WRAP_VECTOR(const std::vector, conv_to_str); -- cgit v1.2.3 From 6fffe24177f9b99d6c332c18e343648cf33d4397 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 3 Apr 2019 16:08:33 +0100 Subject: generic: GUI Python bindings Signed-off-by: David Shah --- common/nextpnr.cc | 8 ++++++++ common/nextpnr.h | 6 ++++++ common/pybindings.cc | 22 ++++++++++++++++++++-- generic/arch.cc | 14 ++++++++++++-- generic/arch_pybindings.cc | 20 ++++++++++++++++---- generic/examples/simple.py | 7 ++++++- gui/pythontab.cc | 3 ++- 7 files changed, 70 insertions(+), 10 deletions(-) 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..79dbbaca 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 bf5382eb..e1fc8534 100644 --- a/common/pybindings.cc +++ b/common/pybindings.cc @@ -87,7 +87,26 @@ BOOST_PYTHON_MODULE(MODULE_NAME) using namespace PythonConversion; + enum_("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_("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") + .def(init( + (args("type"), "style", "x1", "y1", "x2", "y2", "z"))) .def_readwrite("type", &GraphicElement::type) .def_readwrite("x1", &GraphicElement::x1) .def_readwrite("y1", &GraphicElement::y1) @@ -214,8 +233,7 @@ 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)); + PyImport_ImportModule(TOSTRING(MODULE_NAME)); PyRun_SimpleString("from " TOSTRING(MODULE_NAME) " import *"); } catch (boost::python::error_already_set const &) { // Parse and output the exception diff --git a/generic/arch.cc b/generic/arch.cc index 01f7ef55..08146b65 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -18,6 +18,7 @@ */ #include +#include #include "nextpnr.h" #include "placer1.h" #include "router1.h" @@ -201,7 +202,10 @@ void Arch::setDelayScaling(double scale, double offset) // --------------------------------------------------------------- -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) {} @@ -469,7 +473,13 @@ bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); } // --------------------------------------------------------------- -const std::vector &Arch::getDecalGraphics(DecalId decal) const { return decal_graphics.at(decal); } +const std::vector &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; } diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc index eae78c9a..5eb2f2c8 100644 --- a/generic/arch_pybindings.cc +++ b/generic/arch_pybindings.cc @@ -40,6 +40,15 @@ void arch_wrap_python() using namespace PythonConversion; auto arch_cls = class_, boost::noncopyable>("Arch", init()); + + auto dxy_cls = class_>("DecalXY_", no_init); + readwrite_wrapper, + conv_from_str>::def_wrap(dxy_cls, "decal"); + readwrite_wrapper, pass_through>::def_wrap( + dxy_cls, "x"); + readwrite_wrapper, pass_through>::def_wrap( + dxy_cls, "y"); + auto ctx_cls = class_, boost::noncopyable>("Context", no_init) .def("checksum", &Context::checksum) .def("pack", &Context::pack) @@ -127,6 +136,9 @@ void arch_wrap_python() fn_wrapper_0a>::def_wrap(ctx_cls, "archId"); + fn_wrapper_3a, + conv_from_str, pass_through, pass_through>::def_wrap(ctx_cls, "DecalXY"); + typedef std::unordered_map> CellMap; typedef std::unordered_map> NetMap; @@ -178,13 +190,13 @@ void arch_wrap_python() fn_wrapper_2a_v, pass_through>::def_wrap(ctx_cls, "addDecalGraphic", (arg("decal"), "graphic")); fn_wrapper_2a_v, - pass_through>::def_wrap(ctx_cls, "setWireDecal", (arg("wire"), "decalxy")); + unwrap_context>::def_wrap(ctx_cls, "setWireDecal", (arg("wire"), "decalxy")); fn_wrapper_2a_v, - pass_through>::def_wrap(ctx_cls, "setPipDecal", (arg("pip"), "decalxy")); + unwrap_context>::def_wrap(ctx_cls, "setPipDecal", (arg("pip"), "decalxy")); fn_wrapper_2a_v, - pass_through>::def_wrap(ctx_cls, "setBelDecal", (arg("bel"), "decalxy")); + unwrap_context>::def_wrap(ctx_cls, "setBelDecal", (arg("bel"), "decalxy")); fn_wrapper_2a_v, - pass_through>::def_wrap(ctx_cls, "setGroupDecal", (arg("group"), "decalxy")); + unwrap_context>::def_wrap(ctx_cls, "setGroupDecal", (arg("group"), "decalxy")); fn_wrapper_3a_v, conv_from_str, pass_through>::def_wrap(ctx_cls, "setWireAttr", diff --git a/generic/examples/simple.py b/generic/examples/simple.py index b8ca3f78..2628c041 100644 --- a/generic/examples/simple.py +++ b/generic/examples/simple.py @@ -14,6 +14,10 @@ Sq = 4 # "Sparsity" of local to neighbour local wire pips Sl = 8 +# Create graphic elements +# Bels +ctx.addDecalGraphic("bel", GraphicElement(type=TYPE_BOX, style=STYLE_INACTIVE, x1=0, y1=0, x2=0.2, y2=(1/(Z+1))-0.02, z=0)) + def is_io(x, y): return x == 0 or x == X-1 or y == 0 or y == Y-1 @@ -37,7 +41,7 @@ for x in range(X): 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)) - + ctx.setBelDecal(bel="X%dY%d_IO%d" % (x, y, z), decalxy=ctx.DecalXY("bel", 0.6, z * (1/(Z+1)))) else: for z in range(Z): ctx.addBel(name="X%dY%d_SLICE%d" % (x, y, z), type="GENERIC_SLICE", loc=Loc(x, y, z), gb=False) @@ -45,6 +49,7 @@ for x in range(X): 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)) + ctx.setBelDecal(bel="X%dY%d_SLICE%d" % (x, y, z), decalxy=ctx.DecalXY("bel", 0.6, z * (1/(Z+1)))) for x in range(X): for y in range(Y): 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(); -- cgit v1.2.3 From 3f98084021b64420c36c171cc1245248d6968f03 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 4 Apr 2019 15:40:48 +0100 Subject: generic: Improve example Signed-off-by: David Shah --- generic/examples/README.md | 2 +- generic/examples/simple.py | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/generic/examples/README.md b/generic/examples/README.md index 5eb0ea72..4641f542 100644 --- a/generic/examples/README.md +++ b/generic/examples/README.md @@ -8,4 +8,4 @@ This contains a simple, artificial, example of the nextpnr generic API. - report.py stores design information after place-and-route to blinky.txt in place of real bitstream generation - - Run blinky.sh to build an example design on the FPGA above \ No newline at end of file + - Run simple.sh to build an example design on the FPGA above \ No newline at end of file diff --git a/generic/examples/simple.py b/generic/examples/simple.py index 2628c041..f87a6049 100644 --- a/generic/examples/simple.py +++ b/generic/examples/simple.py @@ -2,21 +2,21 @@ X = 12 Y = 12 # SLICEs per tile -Z = 8 +N = 8 # LUT input count K = 4 # Number of local wires -L = Z*(K+1) + 8 -# "Sparsity" of bel input wire pips +Wl = N*(K+1) + 8 +# 1/Fc for bel input wire pips Si = 4 -# "Sparsity" of Q to local wire pips +# 1/Fc for Q to local wire pips Sq = 4 -# "Sparsity" of local to neighbour local wire pips +# ~1/Fc local to neighbour local wire pips Sl = 8 # Create graphic elements # Bels -ctx.addDecalGraphic("bel", GraphicElement(type=TYPE_BOX, style=STYLE_INACTIVE, x1=0, y1=0, x2=0.2, y2=(1/(Z+1))-0.02, z=0)) +ctx.addDecalGraphic("bel", GraphicElement(type=TYPE_BOX, style=STYLE_INACTIVE, x1=0, y1=0, x2=0.2, y2=(1/(N+1))-0.02, z=0)) def is_io(x, y): return x == 0 or x == X-1 or y == 0 or y == Y-1 @@ -24,13 +24,13 @@ def is_io(x, y): for x in range(X): for y in range(Y): # Bel port wires - for z in range(Z): + 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(L): + 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): @@ -41,33 +41,33 @@ for x in range(X): 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)) - ctx.setBelDecal(bel="X%dY%d_IO%d" % (x, y, z), decalxy=ctx.DecalXY("bel", 0.6, z * (1/(Z+1)))) + ctx.setBelDecal(bel="X%dY%d_IO%d" % (x, y, z), decalxy=ctx.DecalXY("bel", 0.6, z * (1/(N+1)))) else: - for z in range(Z): + 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)) - ctx.setBelDecal(bel="X%dY%d_SLICE%d" % (x, y, z), decalxy=ctx.DecalXY("bel", 0.6, z * (1/(Z+1)))) + ctx.setBelDecal(bel="X%dY%d_SLICE%d" % (x, y, z), decalxy=ctx.DecalXY("bel", 0.6, z * (1/(N+1)))) 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, L, 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(Z): + 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, Z, 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)) @@ -75,11 +75,11 @@ for x in range(X): 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, L, skip): + 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(L): + 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) -- cgit v1.2.3 From f0cd51e6bc58f3dfd1185fd53ad970ba634359f2 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 4 Apr 2019 16:30:47 +0100 Subject: generic: Cell timing support Signed-off-by: David Shah --- common/nextpnr.h | 6 ++-- docs/generic.md | 20 ++++++++++++ generic/arch.cc | 65 +++++++++++++++++++++++++++++++++++---- generic/arch.h | 34 ++++++++++++++++++++ generic/arch_pybindings.cc | 20 ++++++++++-- generic/examples/README.md | 2 ++ generic/examples/simple.sh | 2 +- generic/examples/simple_timing.py | 15 +++++++++ 8 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 generic/examples/simple_timing.py diff --git a/common/nextpnr.h b/common/nextpnr.h index 79dbbaca..fc49300e 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -181,9 +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) {}; + 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 diff --git a/docs/generic.md b/docs/generic.md index 2f884274..d6ddbfb6 100644 --- a/docs/generic.md +++ b/docs/generic.md @@ -74,6 +74,26 @@ Sets the number of input pins a LUT in the architecture has. Only affects the ge 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. diff --git a/generic/arch.cc b/generic/arch.cc index 08146b65..772d3534 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -17,8 +17,8 @@ * */ -#include #include +#include #include "nextpnr.h" #include "placer1.h" #include "router1.h" @@ -200,9 +200,42 @@ void Arch::setDelayScaling(double scale, double offset) 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()]; } @@ -473,7 +506,8 @@ bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); } // --------------------------------------------------------------- -const std::vector &Arch::getDecalGraphics(DecalId decal) const { +const std::vector &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)); @@ -493,18 +527,37 @@ 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 diff --git a/generic/arch.h b/generic/arch.h index 02017c8d..92a2c331 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -86,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 +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX CellDelayKey &dk) const noexcept + { + std::size_t seed = std::hash()(dk.from); + seed ^= std::hash()(dk.to) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } +}; +} // namespace std +NEXTPNR_NAMESPACE_BEGIN + +struct CellTiming +{ + std::unordered_map portClasses; + std::unordered_map combDelays; + std::unordered_map> clockingInfo; +}; + struct Arch : BaseCtx { std::string chipName; @@ -106,6 +133,8 @@ struct Arch : BaseCtx std::vector> tileBelDimZ; std::vector> tilePipDimZ; + std::unordered_map 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); void addAlias(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay); @@ -133,6 +162,11 @@ struct Arch : BaseCtx 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. diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc index 5eb2f2c8..8526e409 100644 --- a/generic/arch_pybindings.cc +++ b/generic/arch_pybindings.cc @@ -42,8 +42,8 @@ void arch_wrap_python() auto arch_cls = class_, boost::noncopyable>("Arch", init()); auto dxy_cls = class_>("DecalXY_", no_init); - readwrite_wrapper, - conv_from_str>::def_wrap(dxy_cls, "decal"); + readwrite_wrapper, + conv_from_str>::def_wrap(dxy_cls, "decal"); readwrite_wrapper, pass_through>::def_wrap( dxy_cls, "x"); readwrite_wrapper, pass_through>::def_wrap( @@ -213,6 +213,22 @@ void arch_wrap_python() fn_wrapper_2a_v, pass_through>::def_wrap(ctx_cls, "setDelayScaling", (arg("scale"), "offset")); + fn_wrapper_2a_v, conv_from_str>::def_wrap(ctx_cls, "addCellTimingClock", + (arg("cell"), "port")); + fn_wrapper_4a_v, conv_from_str, conv_from_str, + pass_through>::def_wrap(ctx_cls, "addCellTimingDelay", + (arg("cell"), "fromPort", "toPort", "delay")); + fn_wrapper_5a_v, conv_from_str, conv_from_str, pass_through, + pass_through>::def_wrap(ctx_cls, "addCellTimingSetupHold", + (arg("cell"), "port", "clock", "setup", "hold")); + fn_wrapper_4a_v, conv_from_str, conv_from_str, + pass_through>::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, conv_to_str); diff --git a/generic/examples/README.md b/generic/examples/README.md index 4641f542..dd154a51 100644 --- a/generic/examples/README.md +++ b/generic/examples/README.md @@ -4,6 +4,8 @@ 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) - report.py stores design information after place-and-route to blinky.txt in place of real bitstream generation diff --git a/generic/examples/simple.sh b/generic/examples/simple.sh index ed800639..2e8d6180 100755 --- a/generic/examples/simple.sh +++ b/generic/examples/simple.sh @@ -1,4 +1,4 @@ #!/usr/bin/bash set -ex yosys -p "tcl ../synth/synth_generic.tcl 4 blinky.json" blinky.v -../../nextpnr-generic --pre-pack simple.py --json blinky.json --post-route report.py \ No newline at end of file +../../nextpnr-generic --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route report.py \ 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 -- cgit v1.2.3 From f12a209391984835e011e8ac12c4424397917942 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 4 Apr 2019 16:46:05 +0100 Subject: generic: Router param tweaks Signed-off-by: David Shah --- generic/arch.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generic/arch.h b/generic/arch.h index 92a2c331..e9d3593c 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -248,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 -- cgit v1.2.3 From 659c9325591845806143865563081a6644a3b900 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 4 Apr 2019 16:58:43 +0100 Subject: generic: Fix predictDelay Signed-off-by: David Shah --- generic/arch.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generic/arch.cc b/generic/arch.cc index 772d3534..5617fa63 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -482,8 +482,8 @@ 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); + 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; } -- cgit v1.2.3 From 90ceb829f347372975310d40d2bb6bade0352890 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 17 Apr 2019 10:57:19 +0100 Subject: pybindings: Fix use of import in user scripts Signed-off-by: David Shah --- common/pybindings.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/common/pybindings.cc b/common/pybindings.cc index e1fc8534..f8f366cf 100644 --- a/common/pybindings.cc +++ b/common/pybindings.cc @@ -25,6 +25,7 @@ #include "jsonparse.h" #include "nextpnr.h" +#include #include #include #include @@ -233,6 +234,12 @@ void init_python(const char *executable, bool first) PyImport_AppendInittab(TOSTRING(MODULE_NAME), PYINIT_MODULE_NAME); Py_SetProgramName(program); Py_Initialize(); + + // 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 &) { -- cgit v1.2.3 From 48c4c1ed0561e643a591ed7fca21c69954abd8d2 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 17 Apr 2019 11:00:23 +0100 Subject: generic/examples: Add FASM writer Python script Signed-off-by: David Shah --- generic/examples/.gitignore | 4 ++- generic/examples/README.md | 5 ++-- generic/examples/__init__.py | 0 generic/examples/bitstream.py | 17 +++++++++++++ generic/examples/report.py | 13 ---------- generic/examples/simple.py | 22 +---------------- generic/examples/simple.sh | 2 +- generic/examples/simple_config.py | 15 +++++++++++ generic/examples/write_fasm.py | 52 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 92 insertions(+), 38 deletions(-) create mode 100644 generic/examples/__init__.py create mode 100644 generic/examples/bitstream.py delete mode 100644 generic/examples/report.py create mode 100644 generic/examples/simple_config.py create mode 100644 generic/examples/write_fasm.py diff --git a/generic/examples/.gitignore b/generic/examples/.gitignore index 83d79a7d..38e95de5 100644 --- a/generic/examples/.gitignore +++ b/generic/examples/.gitignore @@ -1 +1,3 @@ -blinky.txt +blinky.fasm +__pycache__ +*.pyc diff --git a/generic/examples/README.md b/generic/examples/README.md index dd154a51..9fd106d9 100644 --- a/generic/examples/README.md +++ b/generic/examples/README.md @@ -7,7 +7,8 @@ This contains a simple, artificial, example of the nextpnr generic API. - simple_timing.py annotates cells with timing data (this is a separate script that must be run after packing) - - report.py stores design information after place-and-route to blinky.txt in place - of real bitstream generation + - 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 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/report.py b/generic/examples/report.py deleted file mode 100644 index c43367fa..00000000 --- a/generic/examples/report.py +++ /dev/null @@ -1,13 +0,0 @@ -with open("blinky.txt", "w") as f: - for nname, net in ctx.nets: - print("# Net %s" % nname, file=f) - # FIXME: Pip ordering - for wire, pip in net.wires: - if pip.pip != "": - print("%s" % pip.pip, file=f) - print("", file=f) - for cname, cell in ctx.cells: - print("# Cell %s at %s" % (cname, cell.bel), file=f) - for param, val in cell.params: - print("%s.%s %s" % (cell.bel, param, val), file=f) - print("", file=f) \ No newline at end of file diff --git a/generic/examples/simple.py b/generic/examples/simple.py index f87a6049..9339b68a 100644 --- a/generic/examples/simple.py +++ b/generic/examples/simple.py @@ -1,22 +1,4 @@ -# 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 - -# Create graphic elements -# Bels -ctx.addDecalGraphic("bel", GraphicElement(type=TYPE_BOX, style=STYLE_INACTIVE, x1=0, y1=0, x2=0.2, y2=(1/(N+1))-0.02, z=0)) +from simple_config import * def is_io(x, y): return x == 0 or x == X-1 or y == 0 or y == Y-1 @@ -41,7 +23,6 @@ for x in range(X): 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)) - ctx.setBelDecal(bel="X%dY%d_IO%d" % (x, y, z), decalxy=ctx.DecalXY("bel", 0.6, z * (1/(N+1)))) 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) @@ -49,7 +30,6 @@ for x in range(X): 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)) - ctx.setBelDecal(bel="X%dY%d_SLICE%d" % (x, y, z), decalxy=ctx.DecalXY("bel", 0.6, z * (1/(N+1)))) for x in range(X): for y in range(Y): diff --git a/generic/examples/simple.sh b/generic/examples/simple.sh index 2e8d6180..576a6418 100755 --- a/generic/examples/simple.sh +++ b/generic/examples/simple.sh @@ -1,4 +1,4 @@ #!/usr/bin/bash set -ex yosys -p "tcl ../synth/synth_generic.tcl 4 blinky.json" blinky.v -../../nextpnr-generic --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route report.py \ No newline at end of file +../../nextpnr-generic --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py \ No newline at end of file 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/write_fasm.py b/generic/examples/write_fasm.py new file mode 100644 index 00000000..fb55c41d --- /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: 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 -- cgit v1.2.3 From ede81dc095b4766e3771a59bb71536e769e12c42 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 17 Apr 2019 11:03:06 +0100 Subject: generic: Disable GUI as it isn't supported yet Signed-off-by: David Shah --- gui/generic/mainwindow.cc | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) 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 +#include + static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } NEXTPNR_NAMESPACE_BEGIN @@ -26,14 +29,8 @@ NEXTPNR_NAMESPACE_BEGIN MainWindow::MainWindow(std::unique_ptr 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() {} -- cgit v1.2.3 From 9fa13b5adcb4bfb193645fee0091c5c51c88c17b Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 17 Apr 2019 11:12:58 +0100 Subject: pybindings: make errors in Python scripts stop nextpnr execution Signed-off-by: David Shah --- common/pybindings.cc | 8 ++++++-- generic/examples/write_fasm.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/common/pybindings.cc b/common/pybindings.cc index f8f366cf..60f87e27 100644 --- a/common/pybindings.cc +++ b/common/pybindings.cc @@ -23,6 +23,7 @@ #include "pybindings.h" #include "arch_pybindings.h" #include "jsonparse.h" +#include "log.h" #include "nextpnr.h" #include @@ -267,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/generic/examples/write_fasm.py b/generic/examples/write_fasm.py index fb55c41d..1f279b63 100644 --- a/generic/examples/write_fasm.py +++ b/generic/examples/write_fasm.py @@ -22,7 +22,7 @@ ParameterConfig.__new__.__defaults__ = (False, True, 1, None) Write a design as FASM ctx: nextpnr context - paramCfg: ParameterConfig describing how to write parameters + paramCfg: map from (celltype, parametername) -> ParameterConfig describing how to write parameters f: output file """ def write_fasm(ctx, paramCfg, f): -- cgit v1.2.3 From c33da42365d36f740ed2b618235efcd4c93701f0 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 17 Apr 2019 11:15:35 +0100 Subject: ci: Run generic example simple.sh Signed-off-by: David Shah --- .cirrus.yml | 3 ++- generic/examples/simple.sh | 4 ++-- 2 files changed, 4 insertions(+), 3 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/generic/examples/simple.sh b/generic/examples/simple.sh index 576a6418..8ae903f9 100755 --- a/generic/examples/simple.sh +++ b/generic/examples/simple.sh @@ -1,4 +1,4 @@ -#!/usr/bin/bash +#!/usr/bin/env bash set -ex yosys -p "tcl ../synth/synth_generic.tcl 4 blinky.json" blinky.v -../../nextpnr-generic --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py \ No newline at end of file +${NEXTPNR:-../../nextpnr-generic} --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py -- cgit v1.2.3 From 87a24460813b9f52189323352554a1c352836ee2 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 17 Apr 2019 13:27:37 +0100 Subject: ci: Bump Yosys version for tcl argument support Signed-off-by: David Shah --- .cirrus/Dockerfile.ubuntu16.04 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 -- cgit v1.2.3