aboutsummaryrefslogtreecommitdiffstats
path: root/ecp5
diff options
context:
space:
mode:
authorMiodrag Milanovic <mmicko@gmail.com>2019-12-28 13:54:06 +0100
committerMiodrag Milanovic <mmicko@gmail.com>2019-12-28 13:54:06 +0100
commit796d6489953927105d3b0ed22308f29676b168fa (patch)
treebc0f470642c0943713c441aa7c3e9e310cb23ccc /ecp5
parent50f87a6024859d197eefa8de0b0b616b1e03e239 (diff)
parent0d43aff2682d91817ea4a1fb5dff6e169ae9a659 (diff)
downloadnextpnr-796d6489953927105d3b0ed22308f29676b168fa.tar.gz
nextpnr-796d6489953927105d3b0ed22308f29676b168fa.tar.bz2
nextpnr-796d6489953927105d3b0ed22308f29676b168fa.zip
Merge remote-tracking branch 'origin/master' into mmicko/ecp5_gui
Diffstat (limited to 'ecp5')
-rw-r--r--ecp5/arch.cc12
-rw-r--r--ecp5/arch.h8
-rw-r--r--ecp5/arch_place.cc24
-rw-r--r--ecp5/arch_pybindings.cc2
-rw-r--r--ecp5/archdefs.h6
-rw-r--r--ecp5/bitstream.cc22
-rw-r--r--ecp5/cells.cc48
-rw-r--r--ecp5/constids.inc8
-rw-r--r--ecp5/family.cmake4
-rw-r--r--ecp5/globals.cc4
-rw-r--r--ecp5/pack.cc304
-rw-r--r--ecp5/synth/.gitignore3
-rw-r--r--ecp5/synth/blinky.v77
-rw-r--r--ecp5/synth/blinky.ys2
-rw-r--r--ecp5/synth/ulx3s_empty.config439
-rwxr-xr-xecp5/trellis_import.py8
16 files changed, 412 insertions, 559 deletions
diff --git a/ecp5/arch.cc b/ecp5/arch.cc
index e678d3b3..994e8660 100644
--- a/ecp5/arch.cc
+++ b/ecp5/arch.cc
@@ -117,6 +117,9 @@ Arch::Arch(ArchArgs args) : args(args)
log_error("Unsupported ECP5 chip type.\n");
}
#endif
+ if (chip_info->const_id_count != DB_CONST_ID_COUNT)
+ log_error("Chip database 'bba' and nextpnr code are out of sync; please rebuild (or contact distribution "
+ "maintainer)!\n");
package_info = nullptr;
for (int i = 0; i < chip_info->num_packages; i++) {
if (args.package == chip_info->package_info[i].name.get()) {
@@ -475,7 +478,13 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const
}
};
- auto src_loc = est_location(src), dst_loc = est_location(dst);
+ auto src_loc = est_location(src);
+ std::pair<int, int> dst_loc;
+ if (wire_loc_overrides.count(dst)) {
+ dst_loc = wire_loc_overrides.at(dst);
+ } else {
+ dst_loc = est_location(dst);
+ }
int dx = abs(src_loc.first - dst_loc.first), dy = abs(src_loc.second - dst_loc.second);
@@ -560,6 +569,7 @@ bool Arch::place()
bool Arch::route()
{
+ setupWireLocations();
route_ecp5_globals(getCtx());
assignArchInfo();
assign_budget(getCtx(), true);
diff --git a/ecp5/arch.h b/ecp5/arch.h
index a0254965..b3e36e52 100644
--- a/ecp5/arch.h
+++ b/ecp5/arch.h
@@ -196,6 +196,7 @@ NPNR_PACKED_STRUCT(struct ChipInfoPOD {
int32_t num_tiles;
int32_t num_location_types;
int32_t num_packages, num_pios;
+ int32_t const_id_count;
RelPtr<LocationTypePOD> locations;
RelPtr<int32_t> location_type;
RelPtr<GlobalInfoPOD> location_glbinfo;
@@ -1034,7 +1035,7 @@ struct Arch : BaseCtx
if (chip_info->tiletype_names[tileloc.tile_names[j].type_idx].get() == type)
return tileloc.tile_names[j].name.get();
}
- NPNR_ASSERT_FALSE_STR("no with type " + type);
+ NPNR_ASSERT_FALSE_STR("no tile with type " + type);
}
GlobalInfoPOD globalInfoAtLoc(Location loc);
@@ -1054,6 +1055,11 @@ struct Arch : BaseCtx
// Special case for delay estimates due to its physical location
// being far from the logical location of its primitive
WireId gsrclk_wire;
+ // Improves directivity of routing to DSP inputs, avoids issues
+ // with different routes to the same physical reset wire causing
+ // conflicts and slow routing
+ std::unordered_map<WireId, std::pair<int, int>> wire_loc_overrides;
+ void setupWireLocations();
mutable std::unordered_map<DelayKey, std::pair<bool, DelayInfo>> celldelay_cache;
diff --git a/ecp5/arch_place.cc b/ecp5/arch_place.cc
index d5c345af..6057605b 100644
--- a/ecp5/arch_place.cc
+++ b/ecp5/arch_place.cc
@@ -196,4 +196,28 @@ void Arch::permute_luts()
}
}
+void Arch::setupWireLocations()
+{
+ wire_loc_overrides.clear();
+ for (auto cell : sorted(cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->bel == BelId())
+ continue;
+ if (ci->type == id_MULT18X18D || ci->type == id_DCUA) {
+ for (auto &port : ci->ports) {
+ if (port.second.type != PORT_IN || port.second.net == nullptr)
+ continue;
+ WireId pw = getBelPinWire(ci->bel, port.first);
+ if (pw == WireId())
+ continue;
+ for (auto uh : getPipsUphill(pw)) {
+ WireId pip_src = getPipSrcWire(uh);
+ wire_loc_overrides[pw] = std::make_pair(pip_src.location.x, pip_src.location.y);
+ break;
+ }
+ }
+ }
+ }
+}
+
NEXTPNR_NAMESPACE_END
diff --git a/ecp5/arch_pybindings.cc b/ecp5/arch_pybindings.cc
index da6d3e50..cd5e31c3 100644
--- a/ecp5/arch_pybindings.cc
+++ b/ecp5/arch_pybindings.cc
@@ -49,6 +49,7 @@ void arch_wrap_python()
typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap;
typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap;
typedef std::unordered_map<IdString, IdString> AliasMap;
+ typedef std::unordered_map<IdString, HierarchicalCell> HierarchyMap;
auto belpin_cls = class_<ContextualWrapper<BelPin>>("BelPin", no_init);
readonly_wrapper<BelPin, decltype(&BelPin::bel), &BelPin::bel, conv_to_str<BelId>>::def_wrap(belpin_cls, "bel");
@@ -64,6 +65,7 @@ void arch_wrap_python()
WRAP_MAP_UPTR(CellMap, "IdCellMap");
WRAP_MAP_UPTR(NetMap, "IdNetMap");
+ WRAP_MAP(HierarchyMap, wrap_context<HierarchicalCell &>, "HierarchyMap");
}
NEXTPNR_NAMESPACE_END
diff --git a/ecp5/archdefs.h b/ecp5/archdefs.h
index d155d672..b176eec0 100644
--- a/ecp5/archdefs.h
+++ b/ecp5/archdefs.h
@@ -52,17 +52,23 @@ struct DelayInfo
// -----------------------------------------------------------------------
+// https://bugreports.qt.io/browse/QTBUG-80789
+
+#ifndef Q_MOC_RUN
enum ConstIds
{
ID_NONE
#define X(t) , ID_##t
#include "constids.inc"
#undef X
+ ,
+ DB_CONST_ID_COUNT
};
#define X(t) static constexpr auto id_##t = IdString(ID_##t);
#include "constids.inc"
#undef X
+#endif
NPNR_PACKED_STRUCT(struct LocationPOD { int16_t x, y; });
diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc
index acab95dd..66bd639a 100644
--- a/ecp5/bitstream.cc
+++ b/ecp5/bitstream.cc
@@ -759,6 +759,10 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
str_or_default(ci->params, ctx->id("REG0_REGSET"), "RESET"));
cc.tiles[tname].add_enum(slice + ".REG1.REGSET",
str_or_default(ci->params, ctx->id("REG1_REGSET"), "RESET"));
+ cc.tiles[tname].add_enum(slice + ".REG0.LSRMODE",
+ str_or_default(ci->params, ctx->id("REG0_LSRMODE"), "LSR"));
+ cc.tiles[tname].add_enum(slice + ".REG1.LSRMODE",
+ str_or_default(ci->params, ctx->id("REG1_LSRMODE"), "LSR"));
cc.tiles[tname].add_enum(slice + ".CEMUX", str_or_default(ci->params, ctx->id("CEMUX"), "1"));
if (ci->sliceInfo.using_dff) {
@@ -865,6 +869,16 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
if (ci->attrs.count(ctx->id("DIFFRESISTOR")))
cc.tiles[pio_tile].add_enum(pio + ".DIFFRESISTOR",
str_or_default(ci->attrs, ctx->id("DIFFRESISTOR"), "OFF"));
+ if (ci->attrs.count(ctx->id("DRIVE"))) {
+ static bool drive_3v3_warning_done = false;
+ if (iotype == "LVCMOS33") {
+ cc.tiles[pio_tile].add_enum(pio + ".DRIVE", str_or_default(ci->attrs, ctx->id("DRIVE"), "8"));
+ } else {
+ if (!drive_3v3_warning_done)
+ log_warning("Trellis limitation: DRIVE can only be set on 3V3 IO pins.\n");
+ drive_3v3_warning_done = true;
+ }
+ }
if (ci->attrs.count(ctx->id("TERMINATION"))) {
auto vccio = get_vccio(ioType_from_str(iotype));
switch (vccio) {
@@ -1283,6 +1297,9 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
else
cc.tiles[pic_tile].add_enum(prim + "." + param.first.str(ctx), param.second.as_string());
}
+ if (get_net_or_empty(ci, id_LOADN) != nullptr) {
+ cc.tiles[pic_tile].add_enum(prim + ".LOADNMUX", "LOADN");
+ }
} else if (ci->type == id_DCUA) {
TileGroup tg;
tg.tiles = get_dcu_tiles(ctx, ci->bel);
@@ -1384,8 +1401,9 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
Loc loc = ctx->getBelLocation(ci->bel);
bool u = loc.y<15, r = loc.x> 15;
std::string tiletype = fmt_str("DDRDLL_" << (u ? 'U' : 'L') << (r ? 'R' : 'L'));
- if (ctx->args.type == ArchArgs::LFE5U_25F || ctx->args.type == ArchArgs::LFE5UM_25F ||
- ctx->args.type == ArchArgs::LFE5UM5G_25F)
+ if ((ctx->args.type == ArchArgs::LFE5U_25F || ctx->args.type == ArchArgs::LFE5UM_25F ||
+ ctx->args.type == ArchArgs::LFE5UM5G_25F) &&
+ u)
tiletype += "A";
std::string tile = ctx->getTileByType(tiletype);
cc.tiles[tile].add_enum("DDRDLL.MODE", "DDRDLLA");
diff --git a/ecp5/cells.cc b/ecp5/cells.cc
index 37b6ac8b..c630c2c3 100644
--- a/ecp5/cells.cc
+++ b/ecp5/cells.cc
@@ -233,6 +233,8 @@ static void replace_port_safe(bool has_ff, CellInfo *ff, IdString ff_port, CellI
void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool driven_by_lut)
{
+ if (lc->hierpath == IdString())
+ lc->hierpath = ff->hierpath;
bool has_ff = lc->ports.at(ctx->id("Q0")).net != nullptr || lc->ports.at(ctx->id("Q1")).net != nullptr;
std::string reg = "REG" + std::to_string(index);
set_param_safe(has_ff, lc, ctx->id("SRMODE"), str_or_default(ff->params, ctx->id("SRMODE"), "LSR_OVER_CE"));
@@ -243,6 +245,7 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive
lc->params[ctx->id(reg + "_SD")] = std::string(driven_by_lut ? "1" : "0");
lc->params[ctx->id(reg + "_REGSET")] = str_or_default(ff->params, ctx->id("REGSET"), "RESET");
+ lc->params[ctx->id(reg + "_LSRMODE")] = str_or_default(ff->params, ctx->id("LSRMODE"), "LSR");
replace_port_safe(has_ff, ff, ctx->id("CLK"), lc, ctx->id("CLK"));
if (ff->ports.find(ctx->id("LSR")) != ff->ports.end())
replace_port_safe(has_ff, ff, ctx->id("LSR"), lc, ctx->id("LSR"));
@@ -250,15 +253,28 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive
replace_port_safe(has_ff, ff, ctx->id("CE"), lc, ctx->id("CE"));
replace_port(ff, ctx->id("Q"), lc, ctx->id("Q" + std::to_string(index)));
- if (driven_by_lut) {
- replace_port(ff, ctx->id("DI"), lc, ctx->id("DI" + std::to_string(index)));
+ if (get_net_or_empty(ff, ctx->id("M")) != nullptr) {
+ // PRLD FFs that use both M and DI
+ NPNR_ASSERT(!driven_by_lut);
+ // As M is used; must route DI through a new LUT
+ lc->params[ctx->id(reg + "_SD")] = std::string("1");
+ lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] = Property(0xFF00, 16);
+ replace_port(ff, ctx->id("DI"), lc, ctx->id("D" + std::to_string(index)));
+ replace_port(ff, ctx->id("M"), lc, ctx->id("M" + std::to_string(index)));
+ connect_ports(ctx, lc, ctx->id("F" + std::to_string(index)), lc, ctx->id("DI" + std::to_string(index)));
} else {
- replace_port(ff, ctx->id("DI"), lc, ctx->id("M" + std::to_string(index)));
+ if (driven_by_lut) {
+ replace_port(ff, ctx->id("DI"), lc, ctx->id("DI" + std::to_string(index)));
+ } else {
+ replace_port(ff, ctx->id("DI"), lc, ctx->id("M" + std::to_string(index)));
+ }
}
}
void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index)
{
+ if (lc->hierpath == IdString())
+ lc->hierpath = lut->hierpath;
lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] =
get_or_default(lut->params, ctx->id("INIT"), Property(0, 16));
replace_port(lut, ctx->id("A"), lc, ctx->id("A" + std::to_string(index)));
@@ -270,6 +286,8 @@ void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index)
void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc)
{
+ if (lc->hierpath == IdString())
+ lc->hierpath = ccu->hierpath;
lc->params[ctx->id("MODE")] = std::string("CCU2");
lc->params[ctx->id("LUT0_INITVAL")] = get_or_default(ccu->params, ctx->id("INIT0"), Property(0, 16));
lc->params[ctx->id("LUT1_INITVAL")] = get_or_default(ccu->params, ctx->id("INIT1"), Property(0, 16));
@@ -297,6 +315,8 @@ void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc)
void dram_to_ramw(Context *ctx, CellInfo *ram, CellInfo *lc)
{
+ if (lc->hierpath == IdString())
+ lc->hierpath = ram->hierpath;
lc->params[ctx->id("MODE")] = std::string("RAMW");
replace_port(ram, ctx->id("WAD[0]"), lc, ctx->id("D0"));
replace_port(ram, ctx->id("WAD[1]"), lc, ctx->id("B0"));
@@ -328,6 +348,8 @@ static unsigned get_dram_init(const Context *ctx, const CellInfo *ram, int bit)
void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw, int index)
{
+ if (lc->hierpath == IdString())
+ lc->hierpath = ram->hierpath;
lc->params[ctx->id("MODE")] = std::string("DPRAM");
lc->params[ctx->id("WREMUX")] = str_or_default(ram->params, ctx->id("WREMUX"), "WRE");
lc->params[ctx->id("WCKMUX")] = str_or_default(ram->params, ctx->id("WCKMUX"), "WCK");
@@ -416,7 +438,25 @@ void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vector<std::u
} else {
NPNR_ASSERT(false);
}
- NetInfo *donet = trio->ports.at(ctx->id("I")).net;
+ NetInfo *donet = trio->ports.at(ctx->id("I")).net, *dinet = trio->ports.at(ctx->id("O")).net;
+
+ // Rename I/O nets to avoid conflicts
+ if (donet != nullptr && donet->name == nxio->name)
+ rename_net(ctx, donet, ctx->id(donet->name.str(ctx) + "$TRELLIS_IO_OUT"));
+ if (dinet != nullptr && dinet->name == nxio->name)
+ rename_net(ctx, dinet, ctx->id(dinet->name.str(ctx) + "$TRELLIS_IO_IN"));
+
+ // Create a new top port net for accurate IO timing analysis and simulation netlists
+ if (ctx->ports.count(nxio->name)) {
+ IdString tn_netname = nxio->name;
+ NPNR_ASSERT(!ctx->nets.count(tn_netname));
+ std::unique_ptr<NetInfo> toplevel_net{new NetInfo};
+ toplevel_net->name = tn_netname;
+ connect_port(ctx, toplevel_net.get(), trio, ctx->id("B"));
+ ctx->ports[nxio->name].net = toplevel_net.get();
+ ctx->nets[tn_netname] = std::move(toplevel_net);
+ }
+
CellInfo *tbuf = net_driven_by(
ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
ctx->id("Y"));
diff --git a/ecp5/constids.inc b/ecp5/constids.inc
index b38e23ef..e5ec1c3e 100644
--- a/ecp5/constids.inc
+++ b/ecp5/constids.inc
@@ -1329,3 +1329,11 @@ X(WIRE_TYPE_G_HPBX)
X(WIRE_TYPE_G_VPTX)
X(WIRE_TYPE_L_HPBX)
X(WIRE_TYPE_R_HPBX)
+
+X(IOLOGIC_MODE_IDDRX1F)
+X(IOLOGIC_MODE_IDDRX2F)
+X(IOLOGIC_MODE_IREG)
+X(IOLOGIC_MODE_ODDRX1F)
+X(IOLOGIC_MODE_ODDRX2F)
+X(IOLOGIC_MODE_OREG)
+X(IOLOGIC_MODE_TSREG)
diff --git a/ecp5/family.cmake b/ecp5/family.cmake
index b0b520d7..860e648d 100644
--- a/ecp5/family.cmake
+++ b/ecp5/family.cmake
@@ -49,7 +49,7 @@ if (NOT EXTERNAL_CHIPDB)
else()
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
COMMAND ${ENV_CMD} ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_CONSTIDS_INC} -g ${DEV_GFXH} ${dev} > ${DEV_CC_BBA_DB}
- DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DB_PY} ${PREV_DEV_CC_BBA_DB}
+ DEPENDS ${DB_PY} ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${PREV_DEV_CC_BBA_DB}
)
add_custom_command(OUTPUT ${DEV_CC_DB}
COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}
@@ -82,7 +82,7 @@ if (NOT EXTERNAL_CHIPDB)
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
COMMAND ${ENV_CMD} ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_CONSTIDS_INC} -g ${DEV_GFXH} ${dev} > ${DEV_CC_BBA_DB}.new
COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
- DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DB_PY} ${PREV_DEV_CC_BBA_DB}
+ DEPENDS ${DB_PY} ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${PREV_DEV_CC_BBA_DB}
)
add_custom_command(OUTPUT ${DEV_CC_DB}
COMMAND bbasm --c ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new
diff --git a/ecp5/globals.cc b/ecp5/globals.cc
index da2ba8f0..c0f4b504 100644
--- a/ecp5/globals.cc
+++ b/ecp5/globals.cc
@@ -58,7 +58,7 @@ class Ecp5GlobalRouter
if (user.cell->type == id_DCUA && (user.port == id_CH0_FF_RXI_CLK || user.port == id_CH1_FF_RXI_CLK ||
user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK))
return true;
- if ((user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) && user.port == id_CLK)
+ if ((user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) && (user.port == id_CLK))
return true;
return false;
}
@@ -74,6 +74,8 @@ class Ecp5GlobalRouter
clockCount[ni->name]++;
if (user.cell->type == id_DCUA)
clockCount[ni->name] += 100;
+ if (user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC)
+ clockCount[ni->name] += 10;
}
}
// log_info("clkcount %s: %d\n", ni->name.c_str(ctx),clockCount[ni->name]);
diff --git a/ecp5/pack.cc b/ecp5/pack.cc
index 244a79d5..3867ab3d 100644
--- a/ecp5/pack.cc
+++ b/ecp5/pack.cc
@@ -56,6 +56,52 @@ class Ecp5Packer
new_cells.clear();
}
+ // Print logic usgage
+ int available_slices = 0;
+ void print_logic_usage()
+ {
+ int total_luts = 0, total_ffs = 0;
+ int total_ramluts = 0, total_ramwluts = 0;
+ for (auto bel : ctx->getBels()) {
+ if (ctx->getBelType(bel) == id_TRELLIS_SLICE) {
+ available_slices += 1;
+ total_luts += 2;
+ total_ffs += 2;
+ Loc l = ctx->getBelLocation(bel);
+ if (l.z == 0 || l.z == 1)
+ total_ramluts += 2;
+ if (l.z == 2)
+ total_ramwluts += 2;
+ }
+ }
+ int used_lgluts = 0, used_cyluts = 0, used_ramluts = 0, used_ramwluts = 0, used_ffs = 0;
+ for (auto &cell : ctx->cells) {
+ CellInfo *ci = cell.second.get();
+ if (is_lut(ctx, ci))
+ ++used_lgluts;
+ if (is_carry(ctx, ci))
+ used_cyluts += 2;
+ if (is_dpram(ctx, ci)) {
+ used_ramluts += 4;
+ used_ramwluts += 2;
+ }
+ if (is_ff(ctx, ci))
+ used_ffs += 2;
+ }
+ log_info("Logic utilisation before packing:\n");
+ auto pc = [](int used, int total) { return 100 * used / total; };
+ int used_luts = used_lgluts + used_cyluts + used_ramluts + used_ramwluts;
+ log_info(" Total LUT4s: %5d/%5d %5d%%\n", used_luts, total_luts, pc(used_luts, total_luts));
+ log_info(" logic LUTs: %5d/%5d %5d%%\n", used_lgluts, total_luts, pc(used_lgluts, total_luts));
+ log_info(" carry LUTs: %5d/%5d %5d%%\n", used_cyluts, total_luts, pc(used_cyluts, total_luts));
+ log_info(" RAM LUTs: %5d/%5d %5d%%\n", used_ramluts, total_ramluts, pc(used_ramluts, total_ramluts));
+ log_info(" RAMW LUTs: %5d/%5d %5d%%\n", used_ramwluts, total_ramwluts,
+ pc(used_ramwluts, total_ramwluts));
+ log_break();
+ log_info(" Total DFFs: %5d/%5d %5d%%\n", used_ffs, total_ffs, pc(used_ffs, total_ffs));
+ log_break();
+ }
+
// Find FFs associated with LUTs, or LUT expansion muxes
void find_lutff_pairs()
{
@@ -66,7 +112,8 @@ class Ecp5Packer
NetInfo *znet = ci->ports.at(ctx->id("Z")).net;
if (znet != nullptr) {
CellInfo *ff = net_only_drives(ctx, znet, is_ff, ctx->id("DI"), false);
- if (ff != nullptr) {
+ // Can't combine preload FF with LUT due to conflict on M
+ if (ff != nullptr && get_net_or_empty(ff, ctx->id("M")) == nullptr) {
lutffPairs[ci->name] = ff->name;
fflutPairs[ff->name] = ci->name;
}
@@ -75,6 +122,58 @@ class Ecp5Packer
}
}
+ // Check if a flipflop is available in a slice
+ bool is_ff_available(CellInfo *slice, int ff)
+ {
+ if (get_net_or_empty(slice, (ff == 1) ? id_Q1 : id_Q0) != nullptr)
+ return false;
+ if (get_net_or_empty(slice, (ff == 1) ? id_M1 : id_M0) != nullptr)
+ return false;
+ return true;
+ }
+
+ // Check if a flipflop can be added to a slice
+ bool can_add_ff_to_slice(CellInfo *slice, CellInfo *ff)
+ {
+ std::string clkmux = str_or_default(ff->params, ctx->id("CLKMUX"), "CLK");
+ std::string lsrmux = str_or_default(ff->params, ctx->id("LSRMUX"), "LSR");
+
+ bool has_dpram = str_or_default(slice->params, ctx->id("MODE"), "LOGIC") == "DPRAM";
+ if (has_dpram) {
+ std::string wckmux = str_or_default(slice->params, ctx->id("WCKMUX"), "WCK");
+ std::string wremux = str_or_default(slice->params, ctx->id("WREMUX"), "WRE");
+ if (wckmux != clkmux && !(wckmux == "WCK" && clkmux == "CLK"))
+ return false;
+ if (wremux != lsrmux && !(wremux == "WRE" && lsrmux == "LSR"))
+ return false;
+ }
+ bool has_ff0 = get_net_or_empty(slice, id_Q0) != nullptr;
+ bool has_ff1 = get_net_or_empty(slice, id_Q1) != nullptr;
+ if (!has_ff0 && !has_ff1)
+ return true;
+ if (str_or_default(ff->params, ctx->id("GSR"), "DISABLED") !=
+ str_or_default(slice->params, ctx->id("GSR"), "DISABLED"))
+ return false;
+ if (str_or_default(ff->params, ctx->id("SRMODE"), "LSR_OVER_CE") !=
+ str_or_default(slice->params, ctx->id("SRMODE"), "LSR_OVER_CE"))
+ return false;
+ if (str_or_default(ff->params, ctx->id("CEMUX"), "1") != str_or_default(slice->params, ctx->id("CEMUX"), "1"))
+ return false;
+ if (str_or_default(ff->params, ctx->id("LSRMUX"), "LSR") !=
+ str_or_default(slice->params, ctx->id("LSRMUX"), "LSR"))
+ return false;
+ if (str_or_default(ff->params, ctx->id("CLKMUX"), "CLK") !=
+ str_or_default(slice->params, ctx->id("CLKMUX"), "CLK"))
+ return false;
+ if (net_or_nullptr(ff, ctx->id("CLK")) != net_or_nullptr(slice, ctx->id("CLK")))
+ return false;
+ if (net_or_nullptr(ff, ctx->id("CE")) != net_or_nullptr(slice, ctx->id("CE")))
+ return false;
+ if (net_or_nullptr(ff, ctx->id("LSR")) != net_or_nullptr(slice, ctx->id("LSR")))
+ return false;
+ return true;
+ }
+
const NetInfo *net_or_nullptr(CellInfo *cell, IdString port)
{
auto fnd = cell->ports.find(port);
@@ -134,6 +233,8 @@ class Ecp5Packer
// Return true if a FF can be added to a DPRAM slice
bool can_pack_ff_dram(CellInfo *dpram, CellInfo *ff)
{
+ if (get_net_or_empty(ff, ctx->id("M")) != nullptr)
+ return false; // skip PRLD FFs due to M/DI conflict
std::string wckmux = str_or_default(dpram->params, ctx->id("WCKMUX"), "WCK");
std::string clkmux = str_or_default(ff->params, ctx->id("CLKMUX"), "CLK");
if (wckmux != clkmux && !(wckmux == "WCK" && clkmux == "CLK"))
@@ -362,8 +463,7 @@ class Ecp5Packer
for (auto &port : ci->ports)
disconnect_port(ctx, ci, port.first);
} else if (trio != nullptr) {
- // Trivial case, TRELLIS_IO used. Just destroy the net and the
- // iobuf
+ // Trivial case, TRELLIS_IO used. Just remove the IOBUF
log_info("%s feeds TRELLIS_IO %s, removing %s %s.\n", ci->name.c_str(ctx), trio->name.c_str(ctx),
ci->type.c_str(ctx), ci->name.c_str(ctx));
@@ -384,14 +484,6 @@ class Ecp5Packer
std::swap(net->clkconstr, onet->clkconstr);
}
}
- ctx->nets.erase(net->name);
- trio->ports.at(ctx->id("B")).net = nullptr;
- }
- if (ci->type == ctx->id("$nextpnr_iobuf")) {
- NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
- if (net2 != nullptr) {
- ctx->nets.erase(net2->name);
- }
}
} else if (drives_top_port(ionet, tp)) {
log_info("%s feeds %s %s.%s, removing %s %s.\n", ci->name.c_str(ctx), tp.cell->type.c_str(ctx),
@@ -414,7 +506,8 @@ class Ecp5Packer
new_cells.push_back(std::move(tr_cell));
trio = new_cells.back().get();
}
-
+ for (auto port : ci->ports)
+ disconnect_port(ctx, ci, port.first);
packed_cells.insert(ci->name);
if (trio != nullptr) {
for (const auto &attr : ci->attrs)
@@ -1033,17 +1126,117 @@ class Ecp5Packer
flush_cells();
}
+ // Find a cell that meets some criterea near an origin cell
+ // Used for packing an FF into a nearby SLICE
+ template <typename TFunc> CellInfo *find_nearby_cell(CellInfo *origin, TFunc Func)
+ {
+ std::unordered_set<CellInfo *> visited_cells;
+ std::queue<CellInfo *> to_visit;
+ visited_cells.insert(origin);
+ to_visit.push(origin);
+ int iter = 0;
+ while (!to_visit.empty() && iter < 10000) {
+ CellInfo *cursor = to_visit.front();
+ to_visit.pop();
+ if (Func(cursor))
+ return cursor;
+ for (const auto &port : cursor->ports) {
+ NetInfo *pn = port.second.net;
+ if (pn == nullptr)
+ continue;
+ // Skip high-fanout nets that are unlikely to be relevant
+ if (pn->users.size() > 25)
+ continue;
+ // Add other ports on this net if not already visited
+ auto visit_port = [&](const PortRef &port) {
+ if (port.cell == nullptr)
+ return;
+ if (visited_cells.count(port.cell))
+ return;
+ // If not already visited; add the cell of this port to the queue
+ to_visit.push(port.cell);
+ visited_cells.insert(port.cell);
+ };
+ visit_port(pn->driver);
+ for (const auto &usr : pn->users)
+ visit_port(usr);
+ }
+ ++iter;
+ }
+ return nullptr;
+ }
+
// Pack flipflops that weren't paired with a LUT
+ float dense_pack_mode_thresh = 0.95f;
void pack_remaining_ffs()
{
+ // Enter dense flipflop packing mode once utilisation exceeds a threshold (default: 95%)
+ int used_slices = 0;
+ for (auto &cell : ctx->cells)
+ if (cell.second->type == id_TRELLIS_SLICE)
+ ++used_slices;
+
log_info("Packing unpaired FFs into a SLICE...\n");
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (is_ff(ctx, ci)) {
+ bool pack_dense = used_slices > (dense_pack_mode_thresh * available_slices);
+ bool requires_m = get_net_or_empty(ci, ctx->id("M")) != nullptr;
+ if (pack_dense && !requires_m) {
+ // If dense packing threshold exceeded; always try and pack the FF into an existing slice
+ // Find a SLICE with space "near" the flipflop in the netlist
+ std::vector<CellInfo *> ltile;
+ CellInfo *target = find_nearby_cell(ci, [&](CellInfo *cursor) {
+ if (cursor->type != id_TRELLIS_SLICE)
+ return false;
+ if (!cursor->constr_children.empty() || cursor->constr_parent != nullptr) {
+ auto &constr_children = (cursor->constr_parent != nullptr)
+ ? cursor->constr_parent->constr_children
+ : cursor->constr_children;
+ // Skip big chains for performance
+ if (constr_children.size() > 8)
+ return false;
+ // Have to check the whole of the tile for legality when dealing with chains, not just slice
+ ltile.clear();
+ if (cursor->constr_parent != nullptr)
+ ltile.push_back(cursor->constr_parent);
+ else
+ ltile.push_back(cursor);
+ for (auto c : constr_children)
+ ltile.push_back(c);
+ if (!can_add_ff_to_tile(ltile, cursor))
+ return false;
+ }
+ if (!can_add_ff_to_slice(cursor, ci))
+ return false;
+ for (int i = 0; i < 2; i++)
+ if (is_ff_available(cursor, i))
+ return true;
+ return false;
+ });
+
+ // If found, add the FF to this slice instead of creating a new one
+ if (target != nullptr) {
+ for (int i = 0; i < 2; i++) {
+ if (is_ff_available(target, i)) {
+ ff_to_slice(ctx, ci, target, i, false);
+ goto ff_packed;
+ }
+ }
+ }
+
+ if (false) {
+ ff_packed:
+ packed_cells.insert(ci->name);
+ continue;
+ }
+ }
+
std::unique_ptr<CellInfo> slice =
create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "_SLICE");
ff_to_slice(ctx, ci, slice.get(), 0, false);
new_cells.push_back(std::move(slice));
+ ++used_slices;
packed_cells.insert(ci->name);
}
}
@@ -1986,7 +2179,8 @@ class Ecp5Packer
iol->params[ctx->id("DELAY.DEL_VALUE")] =
lookup_delay(str_or_default(ci->params, ctx->id("DEL_MODE"), "USER_DEFINED"));
if (ci->params.count(ctx->id("DEL_VALUE")) &&
- std::string(ci->params.at(ctx->id("DEL_VALUE")).as_string()).substr(0, 5) != "DELAY")
+ (!ci->params.at(ctx->id("DEL_VALUE")).is_string ||
+ std::string(ci->params.at(ctx->id("DEL_VALUE")).as_string()).substr(0, 5) != "DELAY"))
iol->params[ctx->id("DELAY.DEL_VALUE")] = ci->params.at(ctx->id("DEL_VALUE"));
if (ci->ports.count(id_LOADN))
replace_port(ci, id_LOADN, iol, id_LOADN);
@@ -2353,6 +2547,7 @@ class Ecp5Packer
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (ci->type == id_ECLKBRIDGECS) {
+ Loc loc;
NetInfo *i0 = get_net_or_empty(ci, id_CLK0), *i1 = get_net_or_empty(ci, id_CLK1),
*o = get_net_or_empty(ci, id_ECSOUT);
for (NetInfo *input : {i0, i1}) {
@@ -2366,24 +2561,53 @@ class Ecp5Packer
for (auto bel : ctx->getBels()) {
if (ctx->getBelType(bel) != id_ECLKBRIDGECS)
continue;
- Loc loc = ctx->getBelLocation(bel);
+ loc = ctx->getBelLocation(bel);
if (loc.x == user_loc.x) {
ci->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);
- if (o != nullptr)
- for (auto user2 : o->users) {
- // Set side hint to ensure edge clock choice is routeable
- if (user2.cell->type == id_ECLKSYNCB && user2.port == id_ECLKI) {
- NetInfo *synco = get_net_or_empty(user2.cell, id_ECLKO);
- if (synco != nullptr)
- bridge_side_hint[synco] = (loc.x > 1) ? 0 : 1;
- }
- }
goto eclkbridge_done;
}
}
}
+ if (input->driver.cell != nullptr) {
+ CellInfo *drv = input->driver.cell;
+ if (!drv->attrs.count(ctx->id("BEL")))
+ continue;
+ Loc drv_loc = ctx->getBelLocation(
+ ctx->getBelByName(ctx->id(drv->attrs.at(ctx->id("BEL")).as_string())));
+ BelId closest;
+ int closest_x = -1; // aim for same side of chip
+ for (auto bel : ctx->getBels()) {
+ if (ctx->getBelType(bel) != id_ECLKBRIDGECS)
+ continue;
+ loc = ctx->getBelLocation(bel);
+ if (closest_x == -1 || std::abs(loc.x - drv_loc.x) < std::abs(closest_x - drv_loc.x)) {
+ closest_x = loc.x;
+ closest = bel;
+ }
+ }
+ NPNR_ASSERT(closest != BelId());
+ loc = ctx->getBelLocation(closest);
+ ci->attrs[ctx->id("BEL")] = ctx->getBelName(closest).str(ctx);
+ goto eclkbridge_done;
+ }
+ }
+ // If all else fails, place randomly
+ for (auto bel : ctx->getBels()) {
+ if (ctx->getBelType(bel) != id_ECLKBRIDGECS)
+ continue;
+ loc = ctx->getBelLocation(bel);
+ ci->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);
}
eclkbridge_done:
+ if (o != nullptr)
+ for (auto user2 : o->users) {
+ // Set side hint to ensure edge clock choice is routeable
+ if (user2.cell->type == id_ECLKSYNCB && user2.port == id_ECLKI) {
+ NetInfo *synco = get_net_or_empty(user2.cell, id_ECLKO);
+ if (synco != nullptr)
+ bridge_side_hint[synco] = (loc.x > 1) ? 0 : 1;
+ }
+ }
continue;
}
}
@@ -2468,12 +2692,37 @@ class Ecp5Packer
const NetInfo *clk = net_or_nullptr(ci, id_CLK);
if (clk == nullptr)
log_error("DDRDLLA '%s' has disconnected port CLK\n", ci->name.c_str(ctx));
+
+ bool left_bank_users = false, right_bank_users = false;
+ // Check which side the delay codes (DDRDEL) are used on
+ const NetInfo *ddrdel = net_or_nullptr(ci, id_DDRDEL);
+ if (ddrdel != nullptr) {
+ for (auto &usr : ddrdel->users) {
+ const CellInfo *uc = usr.cell;
+ if (uc->type != id_DQSBUFM || !uc->attrs.count(ctx->id("BEL")))
+ continue;
+ BelId dqsb_bel = ctx->getBelByName(ctx->id(uc->attrs.at(ctx->id("BEL")).as_string()));
+ Loc dqsb_loc = ctx->getBelLocation(dqsb_bel);
+ if (dqsb_loc.x > 15)
+ right_bank_users = true;
+ if (dqsb_loc.x < 15)
+ left_bank_users = true;
+ }
+ }
+
+ if (left_bank_users && right_bank_users)
+ log_error("DDRDLLA '%s' has DDRDEL uses on both sides of the chip.\n", ctx->nameOf(ci));
+
for (auto &eclk : eclks) {
if (eclk.second.unbuf == clk) {
for (auto bel : ctx->getBels()) {
if (ctx->getBelType(bel) != id_DDRDLL)
continue;
Loc loc = ctx->getBelLocation(bel);
+ if (loc.x > 15 && left_bank_users)
+ continue;
+ if (loc.x < 15 && right_bank_users)
+ continue;
int ddrdll_bank = -1;
if (loc.x < 15 && loc.y < 15)
ddrdll_bank = 7;
@@ -2485,6 +2734,7 @@ class Ecp5Packer
ddrdll_bank = 3;
if (eclk.first.first != ddrdll_bank)
continue;
+ log_info("Constraining DDRDLLA '%s' to bel '%s'\n", ctx->nameOf(ci), ctx->nameOfBel(bel));
ci->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);
make_eclk(ci->ports.at(id_CLK), ci, bel, eclk.first.first);
goto ddrdll_done;
@@ -2583,7 +2833,7 @@ class Ecp5Packer
std::unordered_set<IdString> changed_cells;
for (auto net : changed_nets)
for (auto &user : ctx->nets.at(net)->users)
- if (user.port == id_CLKI || user.port == id_ECLKI)
+ if (user.port == id_CLKI || user.port == id_ECLKI || user.port == id_CLK0 || user.port == id_CLK1)
changed_cells.insert(user.cell->name);
changed_nets.clear();
for (auto cell : sorted(changed_cells)) {
@@ -2600,6 +2850,9 @@ class Ecp5Packer
copy_constraint(ci, id_CLKI, id_CDIVX, ratio);
} else if (ci->type == id_ECLKSYNCB || ci->type == id_TRELLIS_ECLKBUF) {
copy_constraint(ci, id_ECLKI, id_ECLKO, 1);
+ } else if (ci->type == id_ECLKBRIDGECS) {
+ copy_constraint(ci, id_CLK0, id_ECSOUT, 1);
+ copy_constraint(ci, id_CLK1, id_ECSOUT, 1);
} else if (ci->type == id_DCCA) {
copy_constraint(ci, id_CLKI, id_CLKO, 1);
} else if (ci->type == id_EHXPLLL) {
@@ -2657,14 +2910,15 @@ class Ecp5Packer
void pack()
{
prepack_checks();
+ print_logic_usage();
pack_io();
pack_dqsbuf();
+ preplace_plls();
pack_iologic();
pack_ebr();
pack_dsps();
pack_dcus();
pack_misc();
- preplace_plls();
pack_constants();
pack_dram();
pack_carries();
diff --git a/ecp5/synth/.gitignore b/ecp5/synth/.gitignore
deleted file mode 100644
index f4dfa215..00000000
--- a/ecp5/synth/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-*.bit
-*_out.config
-
diff --git a/ecp5/synth/blinky.v b/ecp5/synth/blinky.v
deleted file mode 100644
index 9c6b187b..00000000
--- a/ecp5/synth/blinky.v
+++ /dev/null
@@ -1,77 +0,0 @@
-module top(input clk_pin, input btn_pin, output [7:0] led_pin, output gpio0_pin);
-
- wire clk;
- wire [7:0] led;
- wire btn;
- wire gpio0;
-
- (* LOC="G2" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("INPUT")) clk_buf (.B(clk_pin), .O(clk));
-
- (* LOC="R1" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("INPUT")) btn_buf (.B(btn_pin), .O(btn));
-
- (* LOC="B2" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_0 (.B(led_pin[0]), .I(led[0]));
- (* LOC="C2" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_1 (.B(led_pin[1]), .I(led[1]));
- (* LOC="C1" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_2 (.B(led_pin[2]), .I(led[2]));
- (* LOC="D2" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_3 (.B(led_pin[3]), .I(led[3]));
-
- (* LOC="D1" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_4 (.B(led_pin[4]), .I(led[4]));
- (* LOC="E2" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_5 (.B(led_pin[5]), .I(led[5]));
- (* LOC="E1" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_6 (.B(led_pin[6]), .I(led[6]));
- (* LOC="H3" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_7 (.B(led_pin[7]), .I(led[7]));
-
-
- (* LOC="L2" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) gpio0_buf (.B(gpio0_pin), .I(gpio0));
-
- localparam ctr_width = 24;
- localparam ctr_max = 2**ctr_width - 1;
- reg [ctr_width-1:0] ctr = 0;
- reg [9:0] pwm_ctr = 0;
- reg dir = 0;
-
- always@(posedge clk) begin
- ctr <= btn ? ctr : (dir ? ctr - 1'b1 : ctr + 1'b1);
- if (ctr[ctr_width-1 : ctr_width-3] == 0 && dir == 1)
- dir <= 1'b0;
- else if (ctr[ctr_width-1 : ctr_width-3] == 7 && dir == 0)
- dir <= 1'b1;
- pwm_ctr <= pwm_ctr + 1'b1;
- end
-
- reg [9:0] brightness [0:7];
- localparam bright_max = 2**10 - 1;
- reg [7:0] led_reg;
-
- genvar i;
- generate
- for (i = 0; i < 8; i=i+1) begin
- always @ (posedge clk) begin
- if (ctr[ctr_width-1 : ctr_width-3] == i)
- brightness[i] <= bright_max;
- else if (ctr[ctr_width-1 : ctr_width-3] == (i - 1))
- brightness[i] <= ctr[ctr_width-4:ctr_width-13];
- else if (ctr[ctr_width-1 : ctr_width-3] == (i + 1))
- brightness[i] <= bright_max - ctr[ctr_width-4:ctr_width-13];
- else
- brightness[i] <= 0;
- led_reg[i] <= pwm_ctr < brightness[i];
- end
- end
- endgenerate
-
- assign led = led_reg;
-
- // Tie GPIO0, keep board from rebooting
- assign gpio0 = 1'b1;
-
-endmodule
diff --git a/ecp5/synth/blinky.ys b/ecp5/synth/blinky.ys
deleted file mode 100644
index fb359380..00000000
--- a/ecp5/synth/blinky.ys
+++ /dev/null
@@ -1,2 +0,0 @@
-read_verilog blinky.v
-synth_ecp5 -noccu2 -nomux -nodram -json blinky.json
diff --git a/ecp5/synth/ulx3s_empty.config b/ecp5/synth/ulx3s_empty.config
deleted file mode 100644
index 815e7f0d..00000000
--- a/ecp5/synth/ulx3s_empty.config
+++ /dev/null
@@ -1,439 +0,0 @@
-.device LFE5U-45F
-
-.tile CIB_R10C3:PVT_COUNT2
-unknown: F2B0
-unknown: F3B0
-unknown: F5B0
-unknown: F11B0
-unknown: F13B0
-
-.tile CIB_R5C1:CIB_PLL1
-enum: CIB.JA3MUX 0
-enum: CIB.JB3MUX 0
-
-
-.tile CIB_R5C89:CIB_PLL1
-enum: CIB.JA3MUX 0
-enum: CIB.JB3MUX 0
-
-
-.tile CIB_R70C3:CIB_PLL3
-enum: CIB.JA3MUX 0
-enum: CIB.JB3MUX 0
-
-
-.tile CIB_R70C42:VCIB_DCU0
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C43:VCIB_DCUA
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C44:VCIB_DCUB
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C45:VCIB_DCUC
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C46:VCIB_DCUD
-enum: CIB.JA1MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C47:VCIB_DCUF
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C48:VCIB_DCU3
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C49:VCIB_DCU2
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C50:VCIB_DCUG
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C51:VCIB_DCUH
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C52:VCIB_DCUI
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C53:VCIB_DCU1
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-
-
-.tile CIB_R70C69:VCIB_DCU0
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C6:CIB_EFB0
-enum: CIB.JB3MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C70:VCIB_DCUA
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C71:VCIB_DCUB
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C72:VCIB_DCUC
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C73:VCIB_DCUD
-enum: CIB.JA1MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C74:VCIB_DCUF
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C75:VCIB_DCU3
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C76:VCIB_DCU2
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C77:VCIB_DCUG
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C78:VCIB_DCUH
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C79:VCIB_DCUI
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C7:CIB_EFB1
-enum: CIB.JA3MUX 0
-enum: CIB.JA4MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA6MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB4MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB6MUX 0
-enum: CIB.JC3MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC5MUX 0
-enum: CIB.JD3MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD5MUX 0
-
-
-.tile CIB_R70C80:VCIB_DCU1
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-
-
-.tile CIB_R70C87:CIB_PLL3
-enum: CIB.JA3MUX 0
-enum: CIB.JB3MUX 0
-
-
-.tile MIB_R10C40:CMUX_UL_0
-arc: G_DCS0CLK0 G_VPFN0000
-
-
-.tile MIB_R10C41:CMUX_UR_0
-arc: G_DCS0CLK1 G_VPFN0000
-
-
-.tile MIB_R58C40:CMUX_LL_0
-arc: G_DCS1CLK0 G_VPFN0000
-
-
-.tile MIB_R58C41:CMUX_LR_0
-arc: G_DCS1CLK1 G_VPFN0000
-
-
-.tile MIB_R71C4:EFB0_PICB0
-unknown: F54B1
-unknown: F56B1
-unknown: F82B1
-unknown: F94B1
-
-.tile MIB_R71C3:BANKREF8
-unknown: F18B0
-
diff --git a/ecp5/trellis_import.py b/ecp5/trellis_import.py
index 9f8a9482..c8589b6c 100755
--- a/ecp5/trellis_import.py
+++ b/ecp5/trellis_import.py
@@ -320,6 +320,8 @@ def process_timing_data():
max_delay = min(entry["rising"][2], entry["falling"][2])
delays.append((constids[from_pin], constids[to_pin], min_delay, max_delay))
elif entry["type"] == "SetupHold":
+ if type(entry["pin"]) is list:
+ continue
pin = constids[entry["pin"]]
clock = constids[entry["clock"][1]]
min_setup = entry["setup"][0]
@@ -580,6 +582,7 @@ def write_database(dev_name, chip, ddrg, endianness):
bba.u32(len(location_types), "num_location_types")
bba.u32(len(packages), "num_packages")
bba.u32(len(pindata), "num_pios")
+ bba.u32(const_id_count, "const_id_count")
bba.r("locations", "locations")
bba.r("location_types", "location_type")
@@ -596,11 +599,12 @@ def write_database(dev_name, chip, ddrg, endianness):
dev_names = {"25k": "LFE5UM5G-25F", "45k": "LFE5UM5G-45F", "85k": "LFE5UM5G-85F"}
def main():
- global max_row, max_col
+ global max_row, max_col, const_id_count
pytrellis.load_database(database.get_db_root())
args = parser.parse_args()
# Read port pin file
+ const_id_count = 1 # count ID_NONE
with open(args.constids) as f:
for line in f:
line = line.replace("(", " ")
@@ -612,7 +616,7 @@ def main():
assert line[0] == "X"
idx = len(constids) + 1
constids[line[1]] = idx
-
+ const_id_count += 1
constids["SLICE"] = constids["TRELLIS_SLICE"]
constids["PIO"] = constids["TRELLIS_IO"]