From 3611f549024ed8b6a0d714f25d10951351097745 Mon Sep 17 00:00:00 2001 From: Pepijn de Vos Date: Wed, 30 Dec 2020 15:59:55 +0100 Subject: Gowin target (#542) * load wires * add slice bels * add IOB * add aliases * local aliases * broken packing stuff * working packer * add constraints * pnr runs1111 * add timing info * constraints * more constraint stuff * add copyright * remove generic reference * remove parameters * remove generic python api * add newline to end of file * some small refactoring * warn on invalid constraints * don't error on missing cell * comment out debugging print * typo * avoid copy * faster empty idstring * remove intermediate variable * no more deadnames * fix cst warnings * increase ripup and epsilon a bit * take single device parameter * add info to readme * gui stubs * Revert 4d03b681a8634e978bd5af73c97665500047e055 * assign ff_used in assignArchInfo * decrease beta for better routability * try to fix CI --- gowin/CMakeLists.txt | 55 +++ gowin/arch.cc | 1237 ++++++++++++++++++++++++++++++++++++++++++++++ gowin/arch.h | 436 ++++++++++++++++ gowin/arch_pybindings.cc | 163 ++++++ gowin/arch_pybindings.h | 31 ++ gowin/archdefs.h | 94 ++++ gowin/cells.cc | 142 ++++++ gowin/cells.h | 94 ++++ gowin/constids.inc | 429 ++++++++++++++++ gowin/family.cmake | 53 ++ gowin/main.cc | 90 ++++ gowin/pack.cc | 291 +++++++++++ 12 files changed, 3115 insertions(+) create mode 100644 gowin/CMakeLists.txt create mode 100644 gowin/arch.cc create mode 100644 gowin/arch.h create mode 100644 gowin/arch_pybindings.cc create mode 100644 gowin/arch_pybindings.h create mode 100644 gowin/archdefs.h create mode 100644 gowin/cells.cc create mode 100644 gowin/cells.h create mode 100644 gowin/constids.inc create mode 100644 gowin/family.cmake create mode 100644 gowin/main.cc create mode 100644 gowin/pack.cc (limited to 'gowin') diff --git a/gowin/CMakeLists.txt b/gowin/CMakeLists.txt new file mode 100644 index 00000000..17484b29 --- /dev/null +++ b/gowin/CMakeLists.txt @@ -0,0 +1,55 @@ +cmake_minimum_required(VERSION 3.5) +project(chipdb-gowin NONE) + +set(ALL_GOWIN_DEVICES GW1N-1 GW1N-9) +set(GOWIN_DEVICES ${ALL_GOWIN_DEVICES} CACHE STRING + "Include support for these Gowin devices (available: ${ALL_GOWIN_DEVICES})") +message(STATUS "Enabled Gowin devices: ${GOWIN_DEVICES}") + +find_program (GOWIN_BBA_EXECUTABLE gowin_bba) +message(STATUS "gowin_bba executable: ${GOWIN_BBA_EXECUTABLE}") + +if(DEFINED GOWIN_CHIPDB) + add_custom_target(chipdb-gowin-bbas ALL) +else() + find_package(PythonInterp 3.6 REQUIRED) + + # shared among all families + set(SERIALIZE_CHIPDBS TRUE CACHE BOOL + "Serialize device data preprocessing to minimize memory use") + + set(all_device_bbas) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chipdb) + foreach(device ${GOWIN_DEVICES}) + if(NOT device IN_LIST ALL_GOWIN_DEVICES) + message(FATAL_ERROR "Device ${device} is not a supported Gowin device") + endif() + + set(device_bba chipdb/chipdb-${device}.bba) + add_custom_command( + OUTPUT ${device_bba} + COMMAND ${GOWIN_BBA_EXECUTABLE} -d ${device} -i ${CMAKE_CURRENT_SOURCE_DIR}/constids.inc -o ${device_bba}.new + # atomically update + COMMAND ${CMAKE_COMMAND} -E rename ${device_bba}.new ${device_bba} + DEPENDS + ${GOWIN_BBA_EXECUTABLE} + ${PREVIOUS_CHIPDB_TARGET} + ${CMAKE_CURRENT_SOURCE_DIR}/constids.inc + VERBATIM) + list(APPEND all_device_bbas ${device_bba}) + if(SERIALIZE_CHIPDBS) + set(PREVIOUS_CHIPDB_TARGET ${CMAKE_CURRENT_BINARY_DIR}/${device_bba}) + endif() + endforeach() + + add_custom_target(chipdb-gowin-bbas ALL DEPENDS ${all_device_bbas}) + + get_directory_property(has_parent PARENT_DIRECTORY) + if(has_parent) + set(GOWIN_CHIPDB ${CMAKE_CURRENT_BINARY_DIR}/chipdb PARENT_SCOPE) + # serialize chipdb build across multiple architectures + set(PREVIOUS_CHIPDB_TARGET chipdb-gowin-bbas PARENT_SCOPE) + else() + message(STATUS "Build nextpnr with -DGOWIN_CHIPDB=${CMAKE_CURRENT_BINARY_DIR}/chipdb") + endif() +endif() diff --git a/gowin/arch.cc b/gowin/arch.cc new file mode 100644 index 00000000..8b39cd27 --- /dev/null +++ b/gowin/arch.cc @@ -0,0 +1,1237 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Wolf + * Copyright (C) 2020 Pepijn de Vos + * + * 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 "embed.h" +#include "nextpnr.h" +#include "placer1.h" +#include "placer_heap.h" +#include "router1.h" +#include "router2.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +WireInfo &Arch::wire_info(IdString wire) +{ + auto w = wires.find(wire); + if (w == wires.end()) + NPNR_ASSERT_FALSE_STR("no wire named " + wire.str(this)); + return w->second; +} + +PipInfo &Arch::pip_info(IdString pip) +{ + auto p = pips.find(pip); + if (p == pips.end()) + NPNR_ASSERT_FALSE_STR("no pip named " + pip.str(this)); + return p->second; +} + +BelInfo &Arch::bel_info(IdString bel) +{ + auto b = bels.find(bel); + if (b == bels.end()) + NPNR_ASSERT_FALSE_STR("no bel named " + bel.str(this)); + return b->second; +} + +void Arch::addWire(IdString name, IdString type, int x, int y) +{ + // std::cout << name.str(this) << std::endl; + NPNR_ASSERT(wires.count(name) == 0); + WireInfo &wi = wires[name]; + wi.name = name; + wi.type = type; + wi.x = x; + wi.y = y; + + wire_ids.push_back(name); +} + +void Arch::addPip(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay, Loc loc) +{ + NPNR_ASSERT(pips.count(name) == 0); + PipInfo &pi = pips[name]; + pi.name = name; + pi.type = type; + pi.srcWire = srcWire; + pi.dstWire = dstWire; + pi.delay = delay; + pi.loc = loc; + + wire_info(srcWire).downhill.push_back(name); + wire_info(dstWire).uphill.push_back(name); + pip_ids.push_back(name); + + if (int(tilePipDimZ.size()) <= loc.x) + tilePipDimZ.resize(loc.x + 1); + + if (int(tilePipDimZ[loc.x].size()) <= loc.y) + tilePipDimZ[loc.x].resize(loc.y + 1); + + gridDimX = std::max(gridDimX, loc.x + 1); + gridDimY = std::max(gridDimY, loc.y + 1); + tilePipDimZ[loc.x][loc.y] = std::max(tilePipDimZ[loc.x][loc.y], loc.z + 1); +} + +void Arch::addBel(IdString name, IdString type, Loc loc, bool gb) +{ + NPNR_ASSERT(bels.count(name) == 0); + NPNR_ASSERT(bel_by_loc.count(loc) == 0); + BelInfo &bi = bels[name]; + bi.name = name; + bi.type = type; + bi.x = loc.x; + bi.y = loc.y; + bi.z = loc.z; + bi.gb = gb; + + bel_ids.push_back(name); + bel_by_loc[loc] = name; + + if (int(bels_by_tile.size()) <= loc.x) + bels_by_tile.resize(loc.x + 1); + + if (int(bels_by_tile[loc.x].size()) <= loc.y) + bels_by_tile[loc.x].resize(loc.y + 1); + + bels_by_tile[loc.x][loc.y].push_back(name); + + if (int(tileBelDimZ.size()) <= loc.x) + tileBelDimZ.resize(loc.x + 1); + + if (int(tileBelDimZ[loc.x].size()) <= loc.y) + tileBelDimZ[loc.x].resize(loc.y + 1); + + gridDimX = std::max(gridDimX, loc.x + 1); + gridDimY = std::max(gridDimY, loc.x + 1); + tileBelDimZ[loc.x][loc.y] = std::max(tileBelDimZ[loc.x][loc.y], loc.z + 1); +} + +void Arch::addBelInput(IdString bel, IdString name, IdString wire) +{ + NPNR_ASSERT(bel_info(bel).pins.count(name) == 0); + PinInfo &pi = bel_info(bel).pins[name]; + pi.name = name; + pi.wire = wire; + pi.type = PORT_IN; + + wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name}); + wire_info(wire).bel_pins.push_back(BelPin{bel, name}); +} + +void Arch::addBelOutput(IdString bel, IdString name, IdString wire) +{ + NPNR_ASSERT(bel_info(bel).pins.count(name) == 0); + PinInfo &pi = bel_info(bel).pins[name]; + pi.name = name; + pi.wire = wire; + pi.type = PORT_OUT; + + wire_info(wire).uphill_bel_pin = BelPin{bel, name}; + wire_info(wire).bel_pins.push_back(BelPin{bel, name}); +} + +void Arch::addBelInout(IdString bel, IdString name, IdString wire) +{ + NPNR_ASSERT(bel_info(bel).pins.count(name) == 0); + PinInfo &pi = bel_info(bel).pins[name]; + pi.name = name; + pi.wire = wire; + pi.type = PORT_INOUT; + + wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name}); + wire_info(wire).bel_pins.push_back(BelPin{bel, name}); +} + +void Arch::addGroupBel(IdString group, IdString bel) { groups[group].bels.push_back(bel); } + +void Arch::addGroupWire(IdString group, IdString wire) { groups[group].wires.push_back(wire); } + +void Arch::addGroupPip(IdString group, IdString pip) { groups[group].pips.push_back(pip); } + +void Arch::addGroupGroup(IdString group, IdString grp) { groups[group].groups.push_back(grp); } + +void Arch::addDecalGraphic(DecalId decal, const GraphicElement &graphic) +{ + decal_graphics[decal].push_back(graphic); + refreshUi(); +} + +void Arch::setWireDecal(WireId wire, DecalXY decalxy) +{ + wire_info(wire).decalxy = decalxy; + refreshUiWire(wire); +} + +void Arch::setPipDecal(PipId pip, DecalXY decalxy) +{ + pip_info(pip).decalxy = decalxy; + refreshUiPip(pip); +} + +void Arch::setBelDecal(BelId bel, DecalXY decalxy) +{ + bel_info(bel).decalxy = decalxy; + refreshUiBel(bel); +} + +void Arch::setGroupDecal(GroupId group, DecalXY decalxy) +{ + groups[group].decalxy = decalxy; + refreshUiGroup(group); +} + +void Arch::setWireAttr(IdString wire, IdString key, const std::string &value) { wire_info(wire).attrs[key] = value; } + +void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pip_info(pip).attrs[key] = value; } + +void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bel_info(bel).attrs[key] = value; } + +void Arch::setDelayScaling(double scale, double offset) +{ + args.delayScale = scale; + args.delayOffset = offset; +} + +void Arch::addCellTimingClock(IdString cell, IdString port) { cellTiming[cell].portClasses[port] = TMG_CLOCK_INPUT; } + +void Arch::addCellTimingDelay(IdString cell, IdString fromPort, IdString toPort, DelayInfo delay) +{ + if (get_or_default(cellTiming[cell].portClasses, fromPort, TMG_IGNORE) == TMG_IGNORE) + cellTiming[cell].portClasses[fromPort] = TMG_COMB_INPUT; + if (get_or_default(cellTiming[cell].portClasses, toPort, TMG_IGNORE) == TMG_IGNORE) + cellTiming[cell].portClasses[toPort] = TMG_COMB_OUTPUT; + cellTiming[cell].combDelays[CellDelayKey{fromPort, toPort}] = delay; +} + +void Arch::addCellTimingSetupHold(IdString cell, IdString port, IdString clock, DelayInfo setup, DelayInfo hold) +{ + TimingClockingInfo ci; + ci.clock_port = clock; + ci.edge = RISING_EDGE; + ci.setup = setup; + ci.hold = hold; + cellTiming[cell].clockingInfo[port].push_back(ci); + cellTiming[cell].portClasses[port] = TMG_REGISTER_INPUT; +} + +void Arch::addCellTimingClockToOut(IdString cell, IdString port, IdString clock, DelayInfo clktoq) +{ + TimingClockingInfo ci; + ci.clock_port = clock; + ci.edge = RISING_EDGE; + ci.clockToQ = clktoq; + cellTiming[cell].clockingInfo[port].push_back(ci); + cellTiming[cell].portClasses[port] = TMG_REGISTER_OUTPUT; +} + +// --------------------------------------------------------------- + +// TODO represent wires more intelligently. +IdString Arch::wireToGlobal(int &row, int &col, const DatabasePOD *db, IdString &wire) +{ + const std::string &wirename = wire.str(this); + char buf[32]; + if (wirename == "VCC" || wirename == "GND") { + return wire; + } + if (!isdigit(wirename[1]) || !isdigit(wirename[2]) || !isdigit(wirename[3])) { + snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, wirename.c_str()); + return id(buf); + } + char direction = wirename[0]; + int num = std::stoi(wirename.substr(1, 2)); + int segment = std::stoi(wirename.substr(3, 1)); + switch (direction) { + case 'N': + row += segment; + break; + case 'S': + row -= segment; + break; + case 'E': + col -= segment; + break; + case 'W': + col += segment; + break; + default: + snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, wirename.c_str()); + return id(buf); + break; + } + // wires wrap around the edges + // assumes 0-based indexes + if (row < 0) { + row = -1 - row; + direction = 'N'; + } else if (col < 0) { + col = -1 - col; + direction = 'W'; + } else if (row >= db->rows) { + row = 2 * db->rows - 1 - row; + direction = 'S'; + } else if (col >= db->cols) { + col = 2 * db->cols - 1 - col; + direction = 'E'; + } + snprintf(buf, 32, "%c%d0", direction, num); + wire = id(buf); + snprintf(buf, 32, "R%dC%d_%c%d", row + 1, col + 1, direction, num); + return id(buf); +} + +const PairPOD *pairLookup(const PairPOD *list, const size_t len, const int dest) +{ + for (size_t i = 0; i < len; i++) { + const PairPOD *pair = &list[i]; + if (pair->dest_id == dest) { + return pair; + } + } + return nullptr; +} + +bool aliasCompare(GlobalAliasPOD i, GlobalAliasPOD j) +{ + return (i.dest_row < j.dest_row) || (i.dest_row == j.dest_row && i.dest_col < j.dest_col) || + (i.dest_row == j.dest_row && i.dest_col == j.dest_col && i.dest_id < j.dest_id); +} +bool timingCompare(TimingPOD i, TimingPOD j) { return i.name_id < j.name_id; } + +template const T *genericLookup(const T *first, int len, const T val, C compare) +{ + auto res = std::lower_bound(first, first + len, val, compare); + if (res - first != len && !compare(val, *res)) { + return res; + } else { + return nullptr; + } +} + +DelayInfo delayLookup(const TimingPOD *first, int len, IdString name) +{ + TimingPOD needle; + needle.name_id = name.index; + const TimingPOD *timing = genericLookup(first, len, needle, timingCompare); + DelayInfo info; + if (timing != nullptr) { + info.maxFall = std::max(timing->ff, timing->rf) / 1000; + info.minFall = std::min(timing->ff, timing->rf) / 1000; + info.maxRaise = std::max(timing->rr, timing->fr) / 1000; + info.minRaise = std::min(timing->rr, timing->fr) / 1000; + } else { + info.maxFall = 0; + info.minFall = 0; + info.maxRaise = 0; + info.minRaise = 0; + } + return info; +} + +DelayInfo Arch::getWireTypeDelay(IdString wire) +{ + IdString len; + IdString glbsrc; + switch (wire.index) { + case ID_X01: + case ID_X02: + case ID_X03: + case ID_X04: + case ID_X05: + case ID_X06: + case ID_X07: + case ID_X08: + len = id_X0; + break; + case ID_N100: + case ID_N130: + case ID_S100: + case ID_S130: + case ID_E100: + case ID_E130: + case ID_W100: + case ID_W130: + case ID_E110: + case ID_W110: + case ID_E120: + case ID_W120: + case ID_S110: + case ID_N110: + case ID_S120: + case ID_N120: + case ID_SN10: + case ID_SN20: + case ID_EW10: + case ID_EW20: + len = id_FX1; + break; + case ID_N200: + case ID_N210: + case ID_N220: + case ID_N230: + case ID_N240: + case ID_N250: + case ID_N260: + case ID_N270: + case ID_S200: + case ID_S210: + case ID_S220: + case ID_S230: + case ID_S240: + case ID_S250: + case ID_S260: + case ID_S270: + case ID_E200: + case ID_E210: + case ID_E220: + case ID_E230: + case ID_E240: + case ID_E250: + case ID_E260: + case ID_E270: + case ID_W200: + case ID_W210: + case ID_W220: + case ID_W230: + case ID_W240: + case ID_W250: + case ID_W260: + case ID_W270: + len = id_X2; + break; + case ID_N800: + case ID_N810: + case ID_N820: + case ID_N830: + case ID_S800: + case ID_S810: + case ID_S820: + case ID_S830: + case ID_E800: + case ID_E810: + case ID_E820: + case ID_E830: + case ID_W800: + case ID_W810: + case ID_W820: + case ID_W830: + len = id_X8; + break; + case ID_GT00: + case ID_GT10: + glbsrc = id_SPINE_TAP_PCLK; + break; + case ID_GBO0: + case ID_GBO1: + glbsrc = id_TAP_BRANCH_PCLK; + break; + case ID_GB00: + case ID_GB10: + case ID_GB20: + case ID_GB30: + case ID_GB40: + case ID_GB50: + case ID_GB60: + case ID_GB70: + glbsrc = id_BRANCH_PCLK; + break; + default: + if (wire.str(this).rfind("SPINE", 0) == 0) { + glbsrc = ID_CENT_SPINE_PCLK; + } else if (wire.str(this).rfind("UNK", 0) == 0) { + glbsrc = ID_PIO_CENT_PCLK; + } + break; + } + if (len != IdString()) { + return delayLookup(speed->wire.timings.get(), speed->wire.num_timings, len); + } else if (glbsrc != IdString()) { + return delayLookup(speed->glbsrc.timings.get(), speed->glbsrc.num_timings, glbsrc); + } else { + DelayInfo info; + info.maxFall = 0; + info.minFall = 0; + info.maxRaise = 0; + info.minRaise = 0; + return info; + } +} + +void Arch::read_cst(std::istream &in) { + std::regex iobre = std::regex("IO_LOC +\"([^\"]+)\" +([^ ;]+);"); + std::smatch match; + std::string line; + while (!in.eof()) { + std::getline(in, line); + if(!std::regex_match(line, match, iobre)) { + // empty line or comment + if(line.empty() || line.rfind("//", 0) == 0) { + continue; + } else { + log_warning("Invalid constraint: %s\n", line.c_str()); + continue; + } + } + //std::cout << match[1] << " " << match[2] << std::endl; + IdString net = id(match[1]); + IdString pinname = id(match[2]); + const PairPOD *belname = pairLookup(package->pins.get(), package->num_pins, pinname.index); + if ( belname == nullptr) + log_error("Pin %s not found\n", pinname.c_str(this)); + //BelId bel = getBelByName(belname->src_id); + // for (auto cell : sorted(cells)) { + // std::cout << cell.first.str(this) << std::endl; + // } + auto it = cells.find(net); + if (it == cells.end()) { + log_info("Cell %s not found\n", net.c_str(this)); + continue; + } + std::string bel = IdString(belname->src_id).str(this); + it->second->attrs[ID_BEL] = bel; + } +} + +Arch::Arch(ArchArgs args) : args(args) +{ + family = args.family; + device = args.device; + + // Load database + std::string chipdb = stringf("gowin/chipdb-%s.bin", family.c_str()); + auto db = reinterpret_cast(get_chipdb(chipdb)); + if (db == nullptr) + log_error("Failed to load chipdb '%s'\n", chipdb.c_str()); + if (db->family.get() != family) { + log_error("Database is for family '%s' but provided device is family '%s'.\n", db->family.get(), + family.c_str()); + } + // setup id strings + for (size_t i = 0; i < db->num_ids; i++) { + IdString::initialize_add(this, db->id_strs[i].get(), uint32_t(i) + db->num_constids); + } + // setup timing info + speed = nullptr; + for (unsigned int i = 0; i < db->num_speeds; i++) { + const TimingClassPOD *tc = &db->speeds[i]; + // std::cout << IdString(tc->name_id).str(this) << std::endl; + if (IdString(tc->name_id) == id(args.speed)) { + speed = tc->groups.get(); + break; + } + } + if (speed == nullptr) { + log_error("Unsuported speed grade '%s'.\n", args.speed.c_str()); + } + const VariantPOD* variant = nullptr; + for (unsigned int i = 0; i < db->num_variants; i++) { + auto var = &db->variants[i]; + //std::cout << IdString(var->name_id).str(this) << std::endl; + if (IdString(var->name_id) == id(args.device)) { + variant = var; + break; + } + } + if (variant == nullptr) { + log_error("Unsuported device grade '%s'.\n", args.device.c_str()); + } + + package = nullptr; + for (unsigned int i = 0; i < variant->num_packages; i++) { + auto pkg = &variant->packages[i]; + // std::cout << IdString(pkg->name_id).str(this) << std::endl; + if (IdString(pkg->name_id) == id(args.package)) { + package = pkg; + break; + } + // for (int j=0; j < pkg->num_pins; j++) { + // auto pin = pkg->pins[j]; + // std::cout << IdString(pin.src_id).str(this) << " " << IdString(pin.dest_id).str(this) << std::endl; + // } + } + if (package == nullptr) { + log_error("Unsuported package '%s'.\n", args.package.c_str()); + } + // setup db + char buf[32]; + for (int i = 0; i < db->rows * db->cols; i++) { + int row = i / db->cols; + int col = i % db->cols; + const TilePOD *tile = db->grid[i].get(); + // setup wires + const PairPOD *pips[2] = {tile->pips.get(), tile->clock_pips.get()}; + unsigned int num_pips[2] = {tile->num_pips, tile->num_clock_pips}; + for (int p = 0; p < 2; p++) { + for (unsigned int j = 0; j < num_pips[p]; j++) { + const PairPOD pip = pips[p][j]; + int destrow = row; + int destcol = col; + IdString destid = pip.dest_id; + IdString gdestname = wireToGlobal(destrow, destcol, db, destid); + if (wires.count(gdestname) == 0) + addWire(gdestname, pip.dest_id, destcol, destrow); + int srcrow = row; + int srccol = col; + IdString srcid = pip.src_id; + IdString gsrcname = wireToGlobal(srcrow, srccol, db, srcid); + if (wires.count(gsrcname) == 0) + addWire(gsrcname, pip.src_id, srccol, srcrow); + } + } + for (unsigned int j = 0; j < tile->num_bels; j++) { + const BelsPOD *bel = &tile->bels[j]; + IdString belname; + IdString portname; + int z = 0; + bool dff = true; + switch (static_cast(bel->type_id)) { + // fall through the ++ + case ID_LUT7: + z++; + dff = false; + case ID_LUT6: + z++; + dff = false; + case ID_LUT5: + z++; + case ID_LUT4: + z++; + case ID_LUT3: + z++; + case ID_LUT2: + z++; + case ID_LUT1: + z++; + case ID_LUT0: + // common LUT+DFF code + snprintf(buf, 32, "R%dC%d_SLICE%d", row + 1, col + 1, z); + belname = id(buf); + addBel(belname, id_SLICE, Loc(col, row, z), false); + snprintf(buf, 32, "R%dC%d_F%d", row + 1, col + 1, z); + addBelOutput(belname, id_F, id(buf)); + snprintf(buf, 32, "R%dC%d_A%d", row + 1, col + 1, z); + addBelInput(belname, id_A, id(buf)); + snprintf(buf, 32, "R%dC%d_B%d", row + 1, col + 1, z); + addBelInput(belname, id_B, id(buf)); + snprintf(buf, 32, "R%dC%d_C%d", row + 1, col + 1, z); + addBelInput(belname, id_C, id(buf)); + snprintf(buf, 32, "R%dC%d_D%d", row + 1, col + 1, z); + addBelInput(belname, id_D, id(buf)); + if (dff) { + snprintf(buf, 32, "R%dC%d_CLK%d", row + 1, col + 1, z / 2); + addBelInput(belname, id_CLK, id(buf)); + snprintf(buf, 32, "R%dC%d_LSR%d", row + 1, col + 1, z / 2); + addBelInput(belname, id_LSR, id(buf)); + snprintf(buf, 32, "R%dC%d_CE%d", row + 1, col + 1, z / 2); + addBelInput(belname, id_CE, id(buf)); + snprintf(buf, 32, "R%dC%d_Q%d", row + 1, col + 1, z); + addBelOutput(belname, id_Q, id(buf)); + } + break; + case ID_IOBJ: + z++; + case ID_IOBI: + z++; + case ID_IOBH: + z++; + case ID_IOBG: + z++; + case ID_IOBF: + z++; + case ID_IOBE: + z++; + case ID_IOBD: + z++; + case ID_IOBC: + z++; + case ID_IOBB: + z++; + case ID_IOBA: + snprintf(buf, 32, "R%dC%d_IOB%c", row + 1, col + 1, 'A' + z); + belname = id(buf); + addBel(belname, id_IOB, Loc(col, row, z), false); + portname = pairLookup(bel->ports.get(), bel->num_ports, ID_O)->src_id; + snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); + addBelOutput(belname, id_O, id(buf)); + portname = pairLookup(bel->ports.get(), bel->num_ports, ID_I)->src_id; + snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); + addBelInput(belname, id_I, id(buf)); + portname = pairLookup(bel->ports.get(), bel->num_ports, ID_OE)->src_id; + snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); + addBelInput(belname, id_OEN, id(buf)); + break; + + default: + break; + } + } + } + // setup pips + for (int i = 0; i < db->rows * db->cols; i++) { + int row = i / db->cols; + int col = i % db->cols; + const TilePOD *tile = db->grid[i].get(); + const PairPOD *pips[2] = {tile->pips.get(), tile->clock_pips.get()}; + unsigned int num_pips[2] = {tile->num_pips, tile->num_clock_pips}; + for (int p = 0; p < 2; p++) { + for (unsigned int j = 0; j < num_pips[p]; j++) { + const PairPOD pip = pips[p][j]; + int destrow = row; + int destcol = col; + IdString destid = pip.dest_id; + IdString gdestname = wireToGlobal(destrow, destcol, db, destid); + int srcrow = row; + int srccol = col; + IdString srcid = pip.src_id; + IdString gsrcname = wireToGlobal(srcrow, srccol, db, srcid); + + snprintf(buf, 32, "R%dC%d_%s_%s", row + 1, col + 1, IdString(pip.src_id).c_str(this), + IdString(pip.dest_id).c_str(this)); + IdString pipname = id(buf); + DelayInfo delay = getWireTypeDelay(pip.dest_id); + // local alias + auto local_alias = pairLookup(tile->aliases.get(), tile->num_aliases, srcid.index); + // std::cout << "srcid " << srcid.str(this) << std::endl; + if (local_alias != nullptr) { + srcid = local_alias->src_id; + gsrcname = wireToGlobal(srcrow, srccol, db, srcid); + } + // global alias + srcid = pip.src_id; + GlobalAliasPOD alias; + alias.dest_col = srccol; + alias.dest_row = srcrow; + alias.dest_id = srcid.index; + auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare); + if (alias_src != nullptr) { + srccol = alias_src->src_col; + srcrow = alias_src->src_row; + srcid = alias_src->src_id; + gsrcname = wireToGlobal(srcrow, srccol, db, srcid); + // std::cout << buf << std::endl; + } + addPip(pipname, pip.dest_id, gsrcname, gdestname, delay, Loc(col, row, j)); + } + } + } + // Dummy for empty decals + decal_graphics[IdString()]; +} + +void IdString::initialize_arch(const BaseCtx *ctx) +{ +#define X(t) initialize_add(ctx, #t, ID_##t); +#include "constids.inc" +#undef X +} + +// --------------------------------------------------------------- + +BelId Arch::getBelByName(IdString name) const +{ + if (bels.count(name)) + return name; + return BelId(); +} + +IdString Arch::getBelName(BelId bel) const { return bel; } + +Loc Arch::getBelLocation(BelId bel) const +{ + auto &info = bels.at(bel); + return Loc(info.x, info.y, info.z); +} + +BelId Arch::getBelByLocation(Loc loc) const +{ + auto it = bel_by_loc.find(loc); + if (it != bel_by_loc.end()) + return it->second; + return BelId(); +} + +const std::vector &Arch::getBelsByTile(int x, int y) const { return bels_by_tile.at(x).at(y); } + +bool Arch::getBelGlobalBuf(BelId bel) const { return bels.at(bel).gb; } + +uint32_t Arch::getBelChecksum(BelId bel) const +{ + return bel.index; +} + +void Arch::bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) +{ + bels.at(bel).bound_cell = cell; + cell->bel = bel; + cell->belStrength = strength; + refreshUiBel(bel); +} + +void Arch::unbindBel(BelId bel) +{ + bels.at(bel).bound_cell->bel = BelId(); + bels.at(bel).bound_cell->belStrength = STRENGTH_NONE; + bels.at(bel).bound_cell = nullptr; + refreshUiBel(bel); +} + +bool Arch::checkBelAvail(BelId bel) const { return bels.at(bel).bound_cell == nullptr; } + +CellInfo *Arch::getBoundBelCell(BelId bel) const { return bels.at(bel).bound_cell; } + +CellInfo *Arch::getConflictingBelCell(BelId bel) const { return bels.at(bel).bound_cell; } + +const std::vector &Arch::getBels() const { return bel_ids; } + +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 +{ + 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; } + +std::vector Arch::getBelPins(BelId bel) const +{ + std::vector ret; + for (auto &it : bels.at(bel).pins) + ret.push_back(it.first); + return ret; +} + +// --------------------------------------------------------------- + +WireId Arch::getWireByName(IdString name) const +{ + if (wires.count(name)) + return name; + return WireId(); +} + +IdString Arch::getWireName(WireId wire) const { return wire; } + +IdString Arch::getWireType(WireId wire) const { return wires.at(wire).type; } + +const std::map &Arch::getWireAttrs(WireId wire) const { return wires.at(wire).attrs; } + +uint32_t Arch::getWireChecksum(WireId wire) const +{ + // FIXME + return 0; +} + +void Arch::bindWire(WireId wire, NetInfo *net, PlaceStrength strength) +{ + wires.at(wire).bound_net = net; + net->wires[wire].pip = PipId(); + net->wires[wire].strength = strength; + refreshUiWire(wire); +} + +void Arch::unbindWire(WireId wire) +{ + auto &net_wires = wires.at(wire).bound_net->wires; + + auto pip = net_wires.at(wire).pip; + if (pip != PipId()) { + pips.at(pip).bound_net = nullptr; + refreshUiPip(pip); + } + + net_wires.erase(wire); + wires.at(wire).bound_net = nullptr; + refreshUiWire(wire); +} + +bool Arch::checkWireAvail(WireId wire) const { return wires.at(wire).bound_net == nullptr; } + +NetInfo *Arch::getBoundWireNet(WireId wire) const { return wires.at(wire).bound_net; } + +NetInfo *Arch::getConflictingWireNet(WireId wire) const { return wires.at(wire).bound_net; } + +const std::vector &Arch::getWireBelPins(WireId wire) const { return wires.at(wire).bel_pins; } + +const std::vector &Arch::getWires() const { return wire_ids; } + +// --------------------------------------------------------------- + +PipId Arch::getPipByName(IdString name) const +{ + if (pips.count(name)) + return name; + return PipId(); +} + +IdString Arch::getPipName(PipId pip) const { return pip; } + +IdString Arch::getPipType(PipId pip) const { return pips.at(pip).type; } + +const std::map &Arch::getPipAttrs(PipId pip) const { return pips.at(pip).attrs; } + +uint32_t Arch::getPipChecksum(PipId wire) const +{ + return wire.index; +} + +void Arch::bindPip(PipId pip, NetInfo *net, PlaceStrength strength) +{ + WireId wire = pips.at(pip).dstWire; + pips.at(pip).bound_net = net; + wires.at(wire).bound_net = net; + net->wires[wire].pip = pip; + net->wires[wire].strength = strength; + refreshUiPip(pip); + refreshUiWire(wire); +} + +void Arch::unbindPip(PipId pip) +{ + WireId wire = pips.at(pip).dstWire; + wires.at(wire).bound_net->wires.erase(wire); + pips.at(pip).bound_net = nullptr; + wires.at(wire).bound_net = nullptr; + refreshUiPip(pip); + refreshUiWire(wire); +} + +bool Arch::checkPipAvail(PipId pip) const { return pips.at(pip).bound_net == nullptr; } + +NetInfo *Arch::getBoundPipNet(PipId pip) const { return pips.at(pip).bound_net; } + +NetInfo *Arch::getConflictingPipNet(PipId pip) const { return pips.at(pip).bound_net; } + +WireId Arch::getConflictingPipWire(PipId pip) const { return pips.at(pip).bound_net ? pips.at(pip).dstWire : WireId(); } + +const std::vector &Arch::getPips() const { return pip_ids; } + +Loc Arch::getPipLocation(PipId pip) const { return pips.at(pip).loc; } + +WireId Arch::getPipSrcWire(PipId pip) const { return pips.at(pip).srcWire; } + +WireId Arch::getPipDstWire(PipId pip) const { return pips.at(pip).dstWire; } + +DelayInfo Arch::getPipDelay(PipId pip) const { return pips.at(pip).delay; } + +const std::vector &Arch::getPipsDownhill(WireId wire) const { return wires.at(wire).downhill; } + +const std::vector &Arch::getPipsUphill(WireId wire) const { return wires.at(wire).uphill; } + +// --------------------------------------------------------------- + +GroupId Arch::getGroupByName(IdString name) const { return name; } + +IdString Arch::getGroupName(GroupId group) const { return group; } + +std::vector Arch::getGroups() const +{ + std::vector ret; + for (auto &it : groups) + ret.push_back(it.first); + return ret; +} + +const std::vector &Arch::getGroupBels(GroupId group) const { return groups.at(group).bels; } + +const std::vector &Arch::getGroupWires(GroupId group) const { return groups.at(group).wires; } + +const std::vector &Arch::getGroupPips(GroupId group) const { return groups.at(group).pips; } + +const std::vector &Arch::getGroupGroups(GroupId group) const { return groups.at(group).groups; } + +// --------------------------------------------------------------- + +delay_t Arch::estimateDelay(WireId src, WireId dst) const +{ + const WireInfo &s = wires.at(src); + const WireInfo &d = wires.at(dst); + int dx = abs(s.x - d.x); + int dy = abs(s.y - d.y); + return (dx + dy) * args.delayScale + args.delayOffset; +} + +delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +{ + const auto &driver = net_info->driver; + auto driver_loc = getBelLocation(driver.cell->bel); + auto sink_loc = getBelLocation(sink.cell->bel); + + int dx = abs(sink_loc.x - driver_loc.x); + int dy = abs(sink_loc.y - driver_loc.y); + return (dx + dy) * args.delayScale + args.delayOffset; +} + +bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; } + +ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const +{ + ArcBounds bb; + + int src_x = wires.at(src).x; + int src_y = wires.at(src).y; + int dst_x = wires.at(dst).x; + int dst_y = wires.at(dst).y; + + bb.x0 = src_x; + bb.y0 = src_y; + bb.x1 = src_x; + bb.y1 = src_y; + + auto extend = [&](int x, int y) { + bb.x0 = std::min(bb.x0, x); + bb.x1 = std::max(bb.x1, x); + bb.y0 = std::min(bb.y0, y); + bb.y1 = std::max(bb.y1, y); + }; + extend(dst_x, dst_y); + return bb; +} + +// --------------------------------------------------------------- + +bool Arch::place() +{ + std::string placer = str_or_default(settings, id("placer"), defaultPlacer); + if (placer == "heap") { + bool have_iobuf_or_constr = false; + for (auto cell : sorted(cells)) { + CellInfo *ci = cell.second; + if (ci->type == id("IOB") || ci->bel != BelId() || ci->attrs.count(id("BEL"))) { + have_iobuf_or_constr = true; + break; + } + } + bool retVal; + if (!have_iobuf_or_constr) { + log_warning("Unable to use HeAP due to a lack of IO buffers or constrained cells as anchors; reverting to " + "SA.\n"); + retVal = placer1(getCtx(), Placer1Cfg(getCtx())); + } else { + PlacerHeapCfg cfg(getCtx()); + cfg.ioBufTypes.insert(id("IOB")); + cfg.beta = 0.5; + retVal = placer_heap(getCtx(), cfg); + } + getCtx()->settings[getCtx()->id("place")] = 1; + archInfoToAttributes(); + return retVal; + } else if (placer == "sa") { + bool retVal = placer1(getCtx(), Placer1Cfg(getCtx())); + getCtx()->settings[getCtx()->id("place")] = 1; + archInfoToAttributes(); + return retVal; + } else { + log_error("Gowin architecture does not support placer '%s'\n", placer.c_str()); + } +} + +bool Arch::route() +{ + std::string router = str_or_default(settings, id("router"), defaultRouter); + bool result; + if (router == "router1") { + result = router1(getCtx(), Router1Cfg(getCtx())); + } else if (router == "router2") { + router2(getCtx(), Router2Cfg(getCtx())); + result = true; + } else { + log_error("Gowin architecture does not support router '%s'\n", router.c_str()); + } + getCtx()->settings[getCtx()->id("route")] = 1; + archInfoToAttributes(); + return result; +} + +// --------------------------------------------------------------- + +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; } + +DecalXY Arch::getWireDecal(WireId wire) const { return wires.at(wire).decalxy; } + +DecalXY Arch::getPipDecal(PipId pip) const { return pips.at(pip).decalxy; } + +DecalXY Arch::getGroupDecal(GroupId group) const { return groups.at(group).decalxy; } + +// --------------------------------------------------------------- + +bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const +{ + 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 +{ + 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(cellTiming.count(cell->name)); + const auto &tmg = cellTiming.at(cell->name); + NPNR_ASSERT(tmg.clockingInfo.count(port)); + return tmg.clockingInfo.at(port).at(index); +} + +bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const +{ + std::vector 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())); +} + +#ifdef WITH_HEAP +const std::string Arch::defaultPlacer = "heap"; +#else +const std::string Arch::defaultPlacer = "sa"; +#endif + +const std::vector Arch::availablePlacers = {"sa", +#ifdef WITH_HEAP + "heap" +#endif +}; + +const std::string Arch::defaultRouter = "router1"; +const std::vector Arch::availableRouters = {"router1", "router2"}; + +void Arch::assignArchInfo() +{ + for (auto &cell : getCtx()->cells) { + IdString cname = cell.first; + CellInfo *ci = cell.second.get(); + if (ci->type == id("SLICE")) { + ci->is_slice = true; + ci->ff_used = ci->params.at(id_FF_USED).as_bool(); + ci->slice_clk = get_net_or_empty(ci, id("CLK")); + ci->slice_ce = get_net_or_empty(ci, id("CE")); + ci->slice_lsr = get_net_or_empty(ci, id("LSR")); + + // add timing paths + addCellTimingClock(cname, id_CLK); + IdString ports[4] = {id_A, id_B, id_C, id_D}; + for (int i = 0; i < 4; i++) { + DelayInfo setup = delayLookup(speed->dff.timings.get(), speed->dff.num_timings, id_clksetpos); + DelayInfo hold = delayLookup(speed->dff.timings.get(), speed->dff.num_timings, id_clkholdpos); + // DelayInfo setup = getDelayFromNS(0.1); + // DelayInfo hold = getDelayFromNS(0.1); + addCellTimingSetupHold(cname, ports[i], id_CLK, setup, hold); + } + DelayInfo clkout = delayLookup(speed->dff.timings.get(), speed->dff.num_timings, id_clk_qpos); + // DelayInfo clkout = getDelayFromNS(0.1); + addCellTimingClockToOut(cname, id_Q, id_CLK, clkout); + IdString port_delay[4] = {id_a_f, id_b_f, id_c_f, id_d_f}; + for (int i = 0; i < 4; i++) { + DelayInfo delay = delayLookup(speed->lut.timings.get(), speed->lut.num_timings, port_delay[i]); + // DelayInfo delay = getDelayFromNS(0.1); + addCellTimingDelay(cname, ports[i], id_F, delay); + } + + } else { + ci->is_slice = false; + } + } +} + +bool Arch::cellsCompatible(const CellInfo **cells, int count) const +{ + const NetInfo *clk[4] = {nullptr, nullptr, nullptr, nullptr}; + const NetInfo *ce[4] = {nullptr, nullptr, nullptr, nullptr}; + const NetInfo *lsr[4] = {nullptr, nullptr, nullptr, nullptr}; + for (int i = 0; i < count; i++) { + const CellInfo *ci = cells[i]; + if (ci->is_slice && ci->slice_clk != nullptr) { + Loc loc = getBelLocation(ci->bel); + int cls = loc.z / 2; + if (loc.z >= 6 && ci->ff_used) // top slice have no ff + return false; + if (clk[cls] == nullptr) + clk[cls] = ci->slice_clk; + else if (clk[cls] != ci->slice_clk) + return false; + if (ce[cls] == nullptr) + ce[cls] = ci->slice_ce; + else if (ce[cls] != ci->slice_ce) + return false; + if (lsr[cls] == nullptr) + lsr[cls] = ci->slice_lsr; + else if (lsr[cls] != ci->slice_lsr) + return false; + } + } + return true; +} + +NEXTPNR_NAMESPACE_END diff --git a/gowin/arch.h b/gowin/arch.h new file mode 100644 index 00000000..05c9e125 --- /dev/null +++ b/gowin/arch.h @@ -0,0 +1,436 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Wolf + * Copyright (C) 2020 Pepijn de Vos + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef NEXTPNR_H +#error Include "arch.h" via "nextpnr.h" only. +#endif + +NEXTPNR_NAMESPACE_BEGIN + +template struct RelPtr +{ + int32_t offset; + + // void set(const T *ptr) { + // offset = reinterpret_cast(ptr) - + // reinterpret_cast(this); + // } + + const T *get() const { return reinterpret_cast(reinterpret_cast(this) + offset); } + + T *get_mut() const { return const_cast(reinterpret_cast(reinterpret_cast(this) + offset)); } + + const T &operator[](size_t index) const { return get()[index]; } + + const T &operator*() const { return *(get()); } + + const T *operator->() const { return get(); } + + RelPtr(const RelPtr &) = delete; + RelPtr &operator=(const RelPtr &) = delete; +}; + +NPNR_PACKED_STRUCT(struct PairPOD { + uint16_t dest_id; + uint16_t src_id; +}); + +NPNR_PACKED_STRUCT(struct BelsPOD { + uint16_t type_id; + uint16_t num_ports; + RelPtr ports; +}); + +NPNR_PACKED_STRUCT(struct TilePOD /*TidePOD*/ { + uint32_t num_bels; + RelPtr bels; + uint32_t num_pips; + RelPtr pips; + uint32_t num_clock_pips; + RelPtr clock_pips; + uint32_t num_aliases; + RelPtr aliases; +}); + +NPNR_PACKED_STRUCT(struct GlobalAliasPOD { + uint16_t dest_row; + uint16_t dest_col; + uint16_t dest_id; + uint16_t src_row; + uint16_t src_col; + uint16_t src_id; +}); + +NPNR_PACKED_STRUCT(struct TimingPOD { + uint32_t name_id; + // input, output + uint32_t ff; + uint32_t fr; + uint32_t rf; + uint32_t rr; +}); + +NPNR_PACKED_STRUCT(struct TimingGroupPOD { + uint32_t name_id; + uint32_t num_timings; + RelPtr timings; +}); + +NPNR_PACKED_STRUCT(struct TimingGroupsPOD { + TimingGroupPOD lut; + TimingGroupPOD alu; + TimingGroupPOD sram; + TimingGroupPOD dff; + //TimingGroupPOD dl; + //TimingGroupPOD iddroddr; + //TimingGroupPOD pll; + //TimingGroupPOD dll; + TimingGroupPOD bram; + //TimingGroupPOD dsp; + TimingGroupPOD fanout; + TimingGroupPOD glbsrc; + TimingGroupPOD hclk; + TimingGroupPOD iodelay; + //TimingGroupPOD io; + //TimingGroupPOD iregoreg; + TimingGroupPOD wire; +}); + +NPNR_PACKED_STRUCT(struct TimingClassPOD { + uint32_t name_id; + uint32_t num_groups; + RelPtr groups; +}); + +NPNR_PACKED_STRUCT(struct PackagePOD { + uint32_t name_id; + uint32_t num_pins; + RelPtr pins; +}); + +NPNR_PACKED_STRUCT(struct VariantPOD { + uint32_t name_id; + uint32_t num_packages; + RelPtr packages; +}); + +NPNR_PACKED_STRUCT(struct DatabasePOD { + RelPtr family; + uint32_t version; + uint16_t rows; + uint16_t cols; + RelPtr> grid; + uint32_t num_aliases; + RelPtr aliases; + uint32_t num_speeds; + RelPtr speeds; + uint32_t num_variants; + RelPtr variants; + uint16_t num_constids; + uint16_t num_ids; + RelPtr> id_strs; +}); + +struct ArchArgs +{ + std::string device; + std::string family; + std::string speed; + std::string package; + // y = mx + c relationship between distance and delay for interconnect + // delay estimates + double delayScale = 0.4, delayOffset = 0.4; +}; + +struct WireInfo; + +struct PipInfo +{ + IdString name, type; + std::map attrs; + NetInfo *bound_net; + WireId srcWire, dstWire; + DelayInfo delay; + DecalXY decalxy; + Loc loc; +}; + +struct WireInfo +{ + IdString name, type; + std::map attrs; + NetInfo *bound_net; + std::vector downhill, uphill; + BelPin uphill_bel_pin; + std::vector downhill_bel_pins; + std::vector bel_pins; + DecalXY decalxy; + int x, y; +}; + +struct PinInfo +{ + IdString name; + WireId wire; + PortType type; +}; + +struct BelInfo +{ + IdString name, type; + std::map attrs; + CellInfo *bound_cell; + std::unordered_map pins; + DecalXY decalxy; + int x, y, z; + bool gb; +}; + +struct GroupInfo +{ + IdString name; + std::vector bels; + std::vector wires; + std::vector pips; + std::vector groups; + 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 family; + std::string device; + const PackagePOD *package; + const TimingGroupsPOD *speed; + + std::unordered_map wires; + std::unordered_map pips; + std::unordered_map bels; + std::unordered_map groups; + + // These functions include useful errors if not found + WireInfo &wire_info(IdString wire); + PipInfo &pip_info(IdString wire); + BelInfo &bel_info(IdString wire); + + std::vector bel_ids, wire_ids, pip_ids; + + std::unordered_map bel_by_loc; + std::vector>> bels_by_tile; + + std::unordered_map> decal_graphics; + + int gridDimX, gridDimY; + 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 addBel(IdString name, IdString type, Loc loc, bool gb); + void addBelInput(IdString bel, IdString name, IdString wire); + void addBelOutput(IdString bel, IdString name, IdString wire); + void addBelInout(IdString bel, IdString name, IdString wire); + + void addGroupBel(IdString group, IdString bel); + void addGroupWire(IdString group, IdString wire); + void addGroupPip(IdString group, IdString pip); + void addGroupGroup(IdString group, IdString grp); + + void addDecalGraphic(DecalId decal, const GraphicElement &graphic); + void setWireDecal(WireId wire, DecalXY decalxy); + void setPipDecal(PipId pip, DecalXY decalxy); + void setBelDecal(BelId bel, DecalXY decalxy); + void setGroupDecal(GroupId group, DecalXY decalxy); + + 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); + + 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); + + IdString wireToGlobal(int &row, int &col, const DatabasePOD* db, IdString &wire); + DelayInfo getWireTypeDelay(IdString wire); + void read_cst(std::istream &in); + + // --------------------------------------------------------------- + // Common Arch API. Every arch must provide the following methods. + + ArchArgs args; + Arch(ArchArgs args); + + std::string getChipName() const { return device; } + + IdString archId() const { return id("gowin"); } + ArchArgs archArgs() const { return args; } + IdString archArgsToId(ArchArgs args) const { return id("none"); } + + int getGridDimX() const { return gridDimX; } + int getGridDimY() const { return gridDimY; } + int getTileBelDimZ(int x, int y) const { return tileBelDimZ[x][y]; } + int getTilePipDimZ(int x, int y) const { return tilePipDimZ[x][y]; } + + BelId getBelByName(IdString name) const; + IdString getBelName(BelId bel) const; + Loc getBelLocation(BelId bel) const; + BelId getBelByLocation(Loc loc) const; + const std::vector &getBelsByTile(int x, int y) const; + bool getBelGlobalBuf(BelId bel) const; + uint32_t getBelChecksum(BelId bel) const; + void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength); + void unbindBel(BelId bel); + bool checkBelAvail(BelId bel) const; + CellInfo *getBoundBelCell(BelId bel) const; + CellInfo *getConflictingBelCell(BelId bel) const; + const std::vector &getBels() const; + IdString getBelType(BelId bel) const; + const std::map &getBelAttrs(BelId bel) const; + WireId getBelPinWire(BelId bel, IdString pin) const; + PortType getBelPinType(BelId bel, IdString pin) const; + std::vector getBelPins(BelId bel) const; + + WireId getWireByName(IdString name) const; + IdString getWireName(WireId wire) const; + IdString getWireType(WireId wire) const; + const std::map &getWireAttrs(WireId wire) const; + uint32_t getWireChecksum(WireId wire) const; + void bindWire(WireId wire, NetInfo *net, PlaceStrength strength); + void unbindWire(WireId wire); + bool checkWireAvail(WireId wire) const; + NetInfo *getBoundWireNet(WireId wire) const; + WireId getConflictingWireWire(WireId wire) const { return wire; } + NetInfo *getConflictingWireNet(WireId wire) const; + DelayInfo getWireDelay(WireId wire) const { return DelayInfo(); } + const std::vector &getWires() const; + const std::vector &getWireBelPins(WireId wire) const; + + PipId getPipByName(IdString name) const; + IdString getPipName(PipId pip) const; + IdString getPipType(PipId pip) const; + const std::map &getPipAttrs(PipId pip) const; + uint32_t getPipChecksum(PipId pip) const; + void bindPip(PipId pip, NetInfo *net, PlaceStrength strength); + void unbindPip(PipId pip); + bool checkPipAvail(PipId pip) const; + NetInfo *getBoundPipNet(PipId pip) const; + WireId getConflictingPipWire(PipId pip) const; + NetInfo *getConflictingPipNet(PipId pip) const; + const std::vector &getPips() const; + Loc getPipLocation(PipId pip) const; + WireId getPipSrcWire(PipId pip) const; + WireId getPipDstWire(PipId pip) const; + DelayInfo getPipDelay(PipId pip) const; + const std::vector &getPipsDownhill(WireId wire) const; + const std::vector &getPipsUphill(WireId wire) const; + + GroupId getGroupByName(IdString name) const; + IdString getGroupName(GroupId group) const; + std::vector getGroups() const; + const std::vector &getGroupBels(GroupId group) const; + const std::vector &getGroupWires(GroupId group) const; + const std::vector &getGroupPips(GroupId group) const; + const std::vector &getGroupGroups(GroupId group) const; + + 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 0.4; } + float getDelayNS(delay_t v) const { return v; } + + DelayInfo getDelayFromNS(float ns) const + { + DelayInfo del; + del.maxRaise = ns; + del.maxFall = ns; + del.minRaise = ns; + del.minFall = ns; + return del; + } + + uint32_t getDelayChecksum(delay_t v) const { return 0; } + bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; + + ArcBounds getRouteBoundingBox(WireId src, WireId dst) const; + + bool pack(); + bool place(); + bool route(); + + const std::vector &getDecalGraphics(DecalId decal) const; + DecalXY getBelDecal(BelId bel) const; + DecalXY getWireDecal(WireId wire) const; + DecalXY getPipDecal(PipId pip) const; + DecalXY getGroupDecal(GroupId group) const; + + bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; + // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port + TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const; + // Get the TimingClockingInfo of a port + TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const; + + bool isValidBelForCell(CellInfo *cell, BelId bel) const; + bool isBelLocationValid(BelId bel) const; + + static const std::string defaultPlacer; + static const std::vector availablePlacers; + static const std::string defaultRouter; + static const std::vector availableRouters; + + // --------------------------------------------------------------- + // Internal usage + void assignArchInfo(); + bool cellsCompatible(const CellInfo **cells, int count) const; +}; + +NEXTPNR_NAMESPACE_END diff --git a/gowin/arch_pybindings.cc b/gowin/arch_pybindings.cc new file mode 100644 index 00000000..b968f55f --- /dev/null +++ b/gowin/arch_pybindings.cc @@ -0,0 +1,163 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Wolf + * Copyright (C) 2018 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. + * + */ + +#ifndef NO_PYTHON + +#include "arch_pybindings.h" +#include "nextpnr.h" +#include "pybindings.h" +#include "pywrappers.h" + +using namespace pybind11::literals; + +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(py::module &m) +{ + using namespace PythonConversion; + + auto arch_cls = py::class_(m, "Arch").def(py::init()); + + auto dxy_cls = py::class_>(m, "DecalXY_"); + 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 = py::class_(m, "Context") + .def("checksum", &Context::checksum) + .def("pack", &Context::pack) + .def("place", &Context::place) + .def("route", &Context::route); + + py::class_(m, "BelPin").def_readwrite("bel", &BelPin::bel).def_readwrite("pin", &BelPin::pin); + + py::class_(m, "DelayInfo").def("maxDelay", &DelayInfo::maxDelay).def("minDelay", &DelayInfo::minDelay); + + 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, "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_1a, + pass_through>::def_wrap(ctx_cls, "getDelayFromNS"); + + fn_wrapper_0a>::def_wrap( + ctx_cls, "getChipName"); + 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; + typedef std::unordered_map HierarchyMap; + + 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(m, CellMap, "IdCellMap"); + WRAP_MAP_UPTR(m, NetMap, "IdNetMap"); + WRAP_MAP(m, HierarchyMap, wrap_context, "HierarchyMap"); + WRAP_VECTOR(m, const std::vector, conv_to_str); +} + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/gowin/arch_pybindings.h b/gowin/arch_pybindings.h new file mode 100644 index 00000000..ccc443aa --- /dev/null +++ b/gowin/arch_pybindings.h @@ -0,0 +1,31 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Wolf + * Copyright (C) 2018 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. + * + */ +#ifndef ARCH_PYBINDINGS_H +#define ARCH_PYBINDINGS_H +#ifndef NO_PYTHON + +#include "nextpnr.h" +#include "pybindings.h" + +NEXTPNR_NAMESPACE_BEGIN + +NEXTPNR_NAMESPACE_END +#endif +#endif diff --git a/gowin/archdefs.h b/gowin/archdefs.h new file mode 100644 index 00000000..adeb8a0d --- /dev/null +++ b/gowin/archdefs.h @@ -0,0 +1,94 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Wolf + * Copyright (C) 2020 Pepijn de Vos + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef NEXTPNR_H +#error Include "archdefs.h" via "nextpnr.h" only. +#endif + +NEXTPNR_NAMESPACE_BEGIN + +typedef float delay_t; + +struct DelayInfo +{ + delay_t minRaise = 0; + delay_t minFall = 0; + delay_t maxRaise = 0; + delay_t maxFall = 0; + + delay_t minRaiseDelay() const { return minRaise; } + delay_t maxRaiseDelay() const { return maxRaise; } + + delay_t minFallDelay() const { return minFall; } + delay_t maxFallDelay() const { return maxFall; } + + delay_t minDelay() const { return std::min(minFall, minRaise); } + delay_t maxDelay() const { return std::max(maxFall, maxRaise); } + + DelayInfo operator+(const DelayInfo &other) const + { + DelayInfo ret; + ret.minRaise = this->minRaise + other.minRaise; + ret.maxRaise = this->maxRaise + other.maxRaise; + ret.minFall = this->minFall + other.minFall; + ret.maxFall = this->maxFall + other.maxFall; + return ret; + } +}; + +#ifndef Q_MOC_RUN +enum ConstIds +{ + ID_NONE +#define X(t) , ID_##t +#include "constids.inc" +#undef X +}; + +#define X(t) static constexpr auto id_##t = IdString(ID_##t); +#include "constids.inc" +#undef X +#endif + +typedef IdString BelId; +typedef IdString WireId; +typedef IdString PipId; +typedef IdString GroupId; +typedef IdString DecalId; + +struct ArchNetInfo +{ +}; + +struct NetInfo; + +struct ArchCellInfo +{ + // Is the flip-flop of this slice used + bool ff_used; + // 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; + const NetInfo *slice_ce; + const NetInfo *slice_lsr; +}; + +NEXTPNR_NAMESPACE_END diff --git a/gowin/cells.cc b/gowin/cells.cc new file mode 100644 index 00000000..db7154a0 --- /dev/null +++ b/gowin/cells.cc @@ -0,0 +1,142 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 David Shah + * Copyright (C) 2020 Pepijn de Vos + * + * 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" +#include + +NEXTPNR_NAMESPACE_BEGIN + +void add_port(const Context *ctx, CellInfo *cell, IdString id, PortType dir) +{ + 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 == id_SLICE) { + new_cell->params[id_INIT] = 0; + new_cell->params[id_FF_USED] = 0; + new_cell->params[id_FF_TYPE] = id_DFF.str(ctx); + + IdString names[4] = {id_A, id_B, id_C, id_D}; + for (int i = 0; i < 4; i++) { + add_port(ctx, new_cell.get(), names[i], PORT_IN); + } + + add_port(ctx, new_cell.get(), id_CLK, PORT_IN); + + add_port(ctx, new_cell.get(), id_F, PORT_OUT); + add_port(ctx, new_cell.get(), id_Q, PORT_OUT); + add_port(ctx, new_cell.get(), id_CE, PORT_IN); + add_port(ctx, new_cell.get(), id_LSR, PORT_IN); + } else if (type == id_IOB) { + new_cell->params[id_INPUT_USED] = 0; + new_cell->params[id_OUTPUT_USED] = 0; + new_cell->params[id_ENABLE_USED] = 0; + + add_port(ctx, new_cell.get(), id_PAD, PORT_INOUT); + add_port(ctx, new_cell.get(), id_I, PORT_IN); + add_port(ctx, new_cell.get(), id_EN, PORT_IN); + add_port(ctx, new_cell.get(), id_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[id_INIT] = lut->params[id_INIT]; + + IdString sim_names[4] = {id_I0, id_I1, id_I2, id_I3}; + IdString wire_names[4] = {id_A, id_B, id_C, id_D}; + for (int i = 0; i < 4; i++) { + replace_port(lut, sim_names[i], lc, wire_names[i]); + } + + if (no_dff) { + lc->params[id_FF_USED] = 0; + replace_port(lut, id_F, lc, id_F); + } +} + +void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut) +{ + lc->params[id_FF_USED] = 1; + lc->params[id_FF_TYPE] = dff->type.str(ctx); + replace_port(dff, id_CLK, lc, id_CLK); + replace_port(dff, id_CE, lc, id_CE); + replace_port(dff, id_SET, lc, id_LSR); + replace_port(dff, id_RESET, lc, id_LSR); + replace_port(dff, id_CLEAR, lc, id_LSR); + replace_port(dff, id_PRESET, lc, id_LSR); + if (pass_thru_lut) { + // Fill LUT with alternating 10 + const int init_size = 1 << 4; + std::string init; + init.reserve(init_size); + for (int i = 0; i < init_size; i += 2) + init.append("10"); + lc->params[id_INIT] = Property::from_string(init); + + replace_port(dff, id_D, lc, id_A); + } + + replace_port(dff, id_Q, lc, id_Q); +} + +void gwio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *iob, std::unordered_set &todelete_cells) +{ + if (nxio->type == id_IBUF) { + iob->params[id_INPUT_USED] = 1; + replace_port(nxio, id_O, iob, id_O); + } else if (nxio->type == id_OBUF) { + iob->params[id_OUTPUT_USED] = 1; + replace_port(nxio, id_I, iob, id_I); + } else if (nxio->type == id_TBUF) { + iob->params[id_ENABLE_USED] = 1; + iob->params[id_OUTPUT_USED] = 1; + replace_port(nxio, id_I, iob, id_I); + replace_port(nxio, id_OEN, iob, id_OEN); + } else if (nxio->type == id_IOBUF) { + iob->params[id_ENABLE_USED] = 1; + iob->params[id_INPUT_USED] = 1; + iob->params[id_OUTPUT_USED] = 1; + replace_port(nxio, id_I, iob, id_I); + replace_port(nxio, id_O, iob, id_O); + replace_port(nxio, id_OEN, iob, id_OEN); + } else { + NPNR_ASSERT(false); + } +} + +NEXTPNR_NAMESPACE_END diff --git a/gowin/cells.h b/gowin/cells.h new file mode 100644 index 00000000..30b29f5b --- /dev/null +++ b/gowin/cells.h @@ -0,0 +1,94 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 David Shah + * Copyright (C) 2020 Pepijn de Vos + * + * 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) +{ + switch (cell->type.index) { + case ID_LUT1: + case ID_LUT2: + case ID_LUT3: + case ID_LUT4: + return true; + default: + return false; + } +} + +// Return true if a cell is a flipflop +inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) +{ + switch (cell->type.index) { + case ID_DFF: + case ID_DFFE: + case ID_DFFS: + case ID_DFFSE: + case ID_DFFR: + case ID_DFFRE: + case ID_DFFP: + case ID_DFFPE: + case ID_DFFC: + case ID_DFFCE: + case ID_DFFN: + case ID_DFFNE: + case ID_DFFNS: + case ID_DFFNSE: + case ID_DFFNR: + case ID_DFFNRE: + case ID_DFFNP: + case ID_DFFNPE: + case ID_DFFNC: + case ID_DFFNCE: + return true; + default: + return false; + } +} + +inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_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 Gowin IO buffer to a IOB bel +void gwio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *sbio, std::unordered_set &todelete_cells); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/gowin/constids.inc b/gowin/constids.inc new file mode 100644 index 00000000..bf26e9ca --- /dev/null +++ b/gowin/constids.inc @@ -0,0 +1,429 @@ +X(A0) +X(B0) +X(C0) +X(D0) +X(A1) +X(B1) +X(C1) +X(D1) +X(A2) +X(B2) +X(C2) +X(D2) +X(A3) +X(B3) +X(C3) +X(D3) +X(A4) +X(B4) +X(C4) +X(D4) +X(A5) +X(B5) +X(C5) +X(D5) +X(A6) +X(B6) +X(C6) +X(D6) +X(A7) +X(B7) +X(C7) +X(D7) +X(F0) +X(F1) +X(F2) +X(F3) +X(F4) +X(F5) +X(F6) +X(F7) +X(Q0) +X(Q1) +X(Q2) +X(Q3) +X(Q4) +X(Q5) +X(Q6) +X(Q7) +X(OF0) +X(OF1) +X(OF2) +X(OF3) +X(OF4) +X(OF5) +X(OF6) +X(OF7) +X(X01) +X(X02) +X(X03) +X(X04) +X(X05) +X(X06) +X(X07) +X(X08) +X(N100) +X(SN10) +X(SN20) +X(N130) +X(S100) +X(S130) +X(E100) +X(EW10) +X(EW20) +X(E130) +X(W100) +X(W130) +X(N200) +X(N210) +X(N220) +X(N230) +X(N240) +X(N250) +X(N260) +X(N270) +X(S200) +X(S210) +X(S220) +X(S230) +X(S240) +X(S250) +X(S260) +X(S270) +X(E200) +X(E210) +X(E220) +X(E230) +X(E240) +X(E250) +X(E260) +X(E270) +X(W200) +X(W210) +X(W220) +X(W230) +X(W240) +X(W250) +X(W260) +X(W270) +X(N800) +X(N810) +X(N820) +X(N830) +X(S800) +X(S810) +X(S820) +X(S830) +X(E800) +X(E810) +X(E820) +X(E830) +X(W800) +X(W810) +X(W820) +X(W830) +X(CLK0) +X(CLK1) +X(CLK2) +X(LSR0) +X(LSR1) +X(LSR2) +X(CE0) +X(CE1) +X(CE2) +X(SEL0) +X(SEL1) +X(SEL2) +X(SEL3) +X(SEL4) +X(SEL5) +X(SEL6) +X(SEL7) +X(N101) +X(N131) +X(S101) +X(S131) +X(E101) +X(E131) +X(W101) +X(W131) +X(N201) +X(N211) +X(N221) +X(N231) +X(N241) +X(N251) +X(N261) +X(N271) +X(S201) +X(S211) +X(S221) +X(S231) +X(S241) +X(S251) +X(S261) +X(S271) +X(E201) +X(E211) +X(E221) +X(E231) +X(E241) +X(E251) +X(E261) +X(E271) +X(W201) +X(W211) +X(W221) +X(W231) +X(W241) +X(W251) +X(W261) +X(W271) +X(N202) +X(N212) +X(N222) +X(N232) +X(N242) +X(N252) +X(N262) +X(N272) +X(S202) +X(S212) +X(S222) +X(S232) +X(S242) +X(S252) +X(S262) +X(S272) +X(E202) +X(E212) +X(E222) +X(E232) +X(E242) +X(E252) +X(E262) +X(E272) +X(W202) +X(W212) +X(W222) +X(W232) +X(W242) +X(W252) +X(W262) +X(W272) +X(N804) +X(N814) +X(N824) +X(N834) +X(S804) +X(S814) +X(S824) +X(S834) +X(E804) +X(E814) +X(E824) +X(E834) +X(W804) +X(W814) +X(W824) +X(W834) +X(N808) +X(N818) +X(N828) +X(N838) +X(S808) +X(S818) +X(S828) +X(S838) +X(E808) +X(E818) +X(E828) +X(E838) +X(W808) +X(W818) +X(W828) +X(W838) +X(E110) +X(W110) +X(E120) +X(W120) +X(S110) +X(N110) +X(S120) +X(N120) +X(E111) +X(W111) +X(E121) +X(W121) +X(S111) +X(N111) +X(S121) +X(N121) +X(LB01) +X(LB11) +X(LB21) +X(LB31) +X(LB41) +X(LB51) +X(LB61) +X(LB71) +X(GB00) +X(GB10) +X(GB20) +X(GB30) +X(GB40) +X(GB50) +X(GB60) +X(GB70) +X(VCC) +X(VSS) +X(LT00) +X(LT10) +X(LT20) +X(LT30) +X(LT02) +X(LT13) +X(LT01) +X(LT04) +X(LBO0) +X(LBO1) +X(SS00) +X(SS40) +X(GT00) +X(GT10) +X(GBO0) +X(GBO1) +X(DI0) +X(DI1) +X(DI2) +X(DI3) +X(DI4) +X(DI5) +X(DI6) +X(DI7) +X(CIN0) +X(CIN1) +X(CIN2) +X(CIN3) +X(CIN4) +X(CIN5) +X(COUT0) +X(COUT1) +X(COUT2) +X(COUT3) +X(COUT4) +X(COUT5) +// slice items +X(SLICE) +X(CLK) +X(LSR) +X(CE) +X(Q) +X(F) +X(A) +X(B) +X(C) +X(D) +// iob items +X(IOB) +X(I) +X(O) +X(IO) +X(OE) +// bels +X(DFF0) +X(DFF1) +X(DFF2) +X(DFF3) +X(DFF4) +X(DFF5) + +X(LUT0) +X(LUT1) +X(LUT2) +X(LUT3) +X(LUT4) +X(LUT5) +X(LUT6) +X(LUT7) + +X(IOBA) +X(IOBB) +X(IOBC) +X(IOBD) +X(IOBE) +X(IOBF) +X(IOBG) +X(IOBH) +X(IOBI) +X(IOBJ) + +// DFF types +X(DFF) +X(DFFE) +X(DFFS) +X(DFFSE) +X(DFFR) +X(DFFRE) +X(DFFP) +X(DFFPE) +X(DFFC) +X(DFFCE) +X(DFFN) +X(DFFNE) +X(DFFNS) +X(DFFNSE) +X(DFFNR) +X(DFFNRE) +X(DFFNP) +X(DFFNPE) +X(DFFNC) +X(DFFNCE) + +// IOB types +X(IBUF) +X(OBUF) +X(IOBUF) +X(TBUF) + +// primitive attributes +X(INIT) +X(FF_USED) +X(FF_TYPE) +X(INPUT_USED) +X(OUTPUT_USED) +X(ENABLE_USED) +X(BEL) + +// ports +X(EN) +X(E) +X(Y) +X(PAD) +X(RESET) +X(SET) +X(PRESET) +X(CLEAR) +X(I0) +X(I1) +X(I2) +X(I3) +X(OEN) + +// timing +X(X0) +X(FX1) +X(X2) +X(X8) +X(PIO_CENT_PCLK) +X(CENT_SPINE_PCLK) +X(SPINE_TAP_PCLK) +X(TAP_BRANCH_PCLK) +X(BRANCH_PCLK) +X(clksetpos) +X(clkholdpos) +X(clk_qpos) +X(a_f) +X(b_f) +X(c_f) +X(d_f) diff --git a/gowin/family.cmake b/gowin/family.cmake new file mode 100644 index 00000000..be607e6a --- /dev/null +++ b/gowin/family.cmake @@ -0,0 +1,53 @@ +add_subdirectory(${family}) +message(STATUS "Using Gowin chipdb: ${GOWIN_CHIPDB}") + +set(chipdb_sources) +set(chipdb_binaries) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${family}/chipdb) +foreach(device ${GOWIN_DEVICES}) + set(chipdb_bba ${GOWIN_CHIPDB}/chipdb-${device}.bba) + set(chipdb_bin ${family}/chipdb/chipdb-${device}.bin) + set(chipdb_cc ${family}/chipdb/chipdb-${device}.cc) + if(BBASM_MODE STREQUAL "binary") + add_custom_command( + OUTPUT ${chipdb_bin} + COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${chipdb_bba} ${chipdb_bin} + DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba}) + list(APPEND chipdb_binaries ${chipdb_bin}) + elseif(BBASM_MODE STREQUAL "embed") + add_custom_command( + OUTPUT ${chipdb_cc} ${chipdb_bin} + COMMAND bbasm ${BBASM_ENDIAN_FLAG} --e ${chipdb_bba} ${chipdb_cc} ${chipdb_bin} + DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba}) + list(APPEND chipdb_sources ${chipdb_cc}) + list(APPEND chipdb_binaries ${chipdb_bin}) + elseif(BBASM_MODE STREQUAL "string") + add_custom_command( + OUTPUT ${chipdb_cc} + COMMAND bbasm ${BBASM_ENDIAN_FLAG} --c ${chipdb_bba} ${chipdb_cc} + DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba}) + list(APPEND chipdb_sources ${chipdb_cc}) + endif() +endforeach() +if(WIN32) + set(chipdb_rc ${CMAKE_CURRENT_BINARY_DIR}/${family}/resource/chipdb.rc) + list(APPEND chipdb_sources ${chipdb_rc}) + + file(WRITE ${chipdb_rc}) + foreach(device ${GOWIN_DEVICES}) + file(APPEND ${chipdb_rc} + "${family}/chipdb-${device}.bin RCDATA \"${CMAKE_CURRENT_BINARY_DIR}/${family}/chipdb/chipdb-${device}.bin\"") + endforeach() +endif() + +add_custom_target(chipdb-${family}-bins DEPENDS ${chipdb_sources} ${chipdb_binaries}) + +add_library(chipdb-${family} OBJECT ${GOWIN_CHIPDB} ${chipdb_sources}) +add_dependencies(chipdb-${family} chipdb-${family}-bins) +target_compile_options(chipdb-${family} PRIVATE -g0 -O0 -w) +target_compile_definitions(chipdb-${family} PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family}) +target_include_directories(chipdb-${family} PRIVATE ${family}) + +foreach(family_target ${family_targets}) + target_sources(${family_target} PRIVATE $) +endforeach() diff --git a/gowin/main.cc b/gowin/main.cc new file mode 100644 index 00000000..c845821d --- /dev/null +++ b/gowin/main.cc @@ -0,0 +1,90 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Wolf + * Copyright (C) 2020 Pepijn de Vos + * + * 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. + * + */ + +#ifdef MAIN_EXECUTABLE + +#include +#include +#include "command.h" +#include "design_utils.h" +#include "log.h" +#include "timing.h" + +USING_NEXTPNR_NAMESPACE + +class GowinCommandHandler : public CommandHandler +{ + public: + GowinCommandHandler(int argc, char **argv); + virtual ~GowinCommandHandler(){}; + std::unique_ptr createContext(std::unordered_map &values) override; + void setupArchContext(Context *ctx) override{}; + void customAfterLoad(Context *ctx) override; + + protected: + po::options_description getArchOptions() override; +}; + +GowinCommandHandler::GowinCommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {} + +po::options_description GowinCommandHandler::getArchOptions() +{ + po::options_description specific("Architecture specific options"); + specific.add_options()("device", po::value(), "device name"); + specific.add_options()("cst", po::value(), "physical constraints file"); + return specific; +} + +std::unique_ptr GowinCommandHandler::createContext(std::unordered_map &values) +{ + std::regex devicere = std::regex("GW1N([A-Z]*)-(LV|UV)([0-9])([A-Z]{2}[0-9]+)(C[0-9]/I[0-9])"); + std::smatch match; + std::string device = vm["device"].as(); + if(!std::regex_match(device, match, devicere)) { + log_error("Invalid device %s\n", device.c_str()); + } + ArchArgs chipArgs; + char buf[32]; + snprintf(buf, 32, "GW1N%s-%s", match[1].str().c_str(), match[3].str().c_str()); + chipArgs.device = buf; + snprintf(buf, 32, "GW1N-%s", match[3].str().c_str()); + chipArgs.family = buf; + chipArgs.package = match[4]; + chipArgs.speed = match[5]; + return std::unique_ptr(new Context(chipArgs)); +} + +void GowinCommandHandler::customAfterLoad(Context *ctx) +{ + if (vm.count("cst")) { + std::string filename = vm["cst"].as(); + std::ifstream in(filename); + if (!in) + log_error("Failed to open input CST file %s.\n", filename.c_str()); + ctx->read_cst(in); + } +} + +int main(int argc, char *argv[]) +{ + GowinCommandHandler handler(argc, argv); + return handler.exec(); +} +#endif diff --git a/gowin/pack.cc b/gowin/pack.cc new file mode 100644 index 00000000..ce96fd62 --- /dev/null +++ b/gowin/pack.cc @@ -0,0 +1,291 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018-19 David Shah + * Copyright (C) 2020 Pepijn de Vos + * + * 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" +#include + +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", ctx->nameOf(ci), ci->type.c_str(ctx)); + if (is_lut(ctx, ci)) { + std::unique_ptr packed = + create_generic_cell(ctx, ctx->id("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", ctx->nameOf(ci), ctx->nameOf(packed.get())); + // See if we can pack into a DFF + // TODO: LUT cascade + NetInfo *o = ci->ports.at(ctx->id("F")).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", ctx->nameOf(dff)); + 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", ctx->nameOf(dff), ctx->nameOf(packed.get())); + 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("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", ctx->nameOf(ci), ctx->nameOf(packed.get())); + 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); + } +} + +// 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", ctx->nameOf(orig), ctx->nameOf(uc)); + 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("SLICE"), "$PACKER_GND"); + gnd_cell->params[ctx->id("INIT")] = Property(0, 1 << 4); + 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("F"); + gnd_cell->ports.at(ctx->id("F")).net = gnd_net.get(); + + std::unique_ptr vcc_cell = create_generic_cell(ctx, ctx->id("SLICE"), "$PACKER_VCC"); + // Fill with 1s + vcc_cell->params[ctx->id("INIT")] = Property(Property::S1).extract(0, (1 << 4), Property::S1); + 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("F"); + vcc_cell->ports.at(ctx->id("F")).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(const 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_gowin_iob(const Context *ctx, const CellInfo *cell) { + switch (cell->type.index) + { + case ID_IBUF: + case ID_OBUF: + case ID_IOBUF: + case ID_TBUF: + return true; + default: + return false; + } + } + +// 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_gowin_iob(ctx, ci)) { + CellInfo *iob = nullptr; + switch (ci->type.index) + { + case ID_IBUF: + iob = net_driven_by(ctx, ci->ports.at(id_I).net, is_nextpnr_iob, id_O); + break; + case ID_OBUF: + iob = net_only_drives(ctx, ci->ports.at(id_O).net, is_nextpnr_iob, id_I); + break; + case ID_IOBUF: + case ID_TBUF: + log_error("untested tristate stuff"); + break; + default: + break; + } + if (iob != nullptr) { + // delete the $nexpnr_[io]buf + for (auto &p : iob->ports) { + IdString netname = p.second.net->name; + disconnect_port(ctx, iob, p.first); + delete_nets.insert(netname); + } + packed_cells.insert(iob->name); + } + // Create a IOB buffer + std::unique_ptr ice_cell = + create_generic_cell(ctx, id_IOB, ci->name.str(ctx) + "$iob"); + gwio_to_iob(ctx, ci, ice_cell.get(), packed_cells); + new_cells.push_back(std::move(ice_cell)); + auto gwiob = new_cells.back().get(); + + packed_cells.insert(ci->name); + if (iob != nullptr) + std::copy(iob->attrs.begin(), iob->attrs.end(), std::inserter(gwiob->attrs, gwiob->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->settings[ctx->id("pack")] = 1; + 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