diff --git a/CMakeLists.txt b/CMakeLists.txt index d9c18554..b24e6255 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ endif() set(PROGRAM_PREFIX "" CACHE STRING "Name prefix for executables") # List of families to build -set(FAMILIES generic ice40 ecp5) +set(FAMILIES generic ice40 ecp5 gowin) set(ARCH "" CACHE STRING "Architecture family for nextpnr build") set_property(CACHE ARCH PROPERTY STRINGS ${FAMILIES}) @@ -99,9 +99,9 @@ if (MSVC) else() set(CMAKE_CXX_FLAGS_DEBUG "-Wall -fPIC -ggdb -pipe") if (USE_OPENMP) - set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g -pipe -fopenmp") + set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O0 -g -pipe -fopenmp") else() - set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g -pipe") + set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O0 -g -pipe") endif() endif() diff --git a/common/timing_opt.cc b/common/timing_opt.cc index 309eedf2..eae15fb2 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -59,7 +59,7 @@ template <> struct hash> return seed; } }; -#ifndef ARCH_GENERIC +#if !defined(ARCH_GENERIC) && !defined(ARCH_GOWIN) template <> struct hash> { std::size_t diff --git a/gowin/CMakeLists.txt b/gowin/CMakeLists.txt new file mode 100644 index 00000000..80293c4c --- /dev/null +++ b/gowin/CMakeLists.txt @@ -0,0 +1,54 @@ +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} -o ${device_bba}.new + # atomically update + COMMAND ${CMAKE_COMMAND} -E rename ${device_bba}.new ${device_bba} + DEPENDS + ${GOWIN_BBA_EXECUTABLE} + ${PREVIOUS_CHIPDB_TARGET} + 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..588fdae4 --- /dev/null +++ b/gowin/arch.cc @@ -0,0 +1,810 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * + * 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 "nextpnr.h" +#include "embed.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) +{ + 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.x + 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::setLutK(int K) { args.K = K; } + +void Arch::setDelayScaling(double scale, double offset) +{ + args.delayScale = scale; + args.delayOffset = offset; +} + +void Arch::addCellTimingClock(IdString cell, IdString port) { cellTiming[cell].portClasses[port] = TMG_CLOCK_INPUT; } + +void Arch::addCellTimingDelay(IdString cell, IdString fromPort, IdString toPort, DelayInfo delay) +{ + if (get_or_default(cellTiming[cell].portClasses, fromPort, TMG_IGNORE) == TMG_IGNORE) + cellTiming[cell].portClasses[fromPort] = TMG_COMB_INPUT; + if (get_or_default(cellTiming[cell].portClasses, toPort, TMG_IGNORE) == TMG_IGNORE) + cellTiming[cell].portClasses[toPort] = TMG_COMB_OUTPUT; + cellTiming[cell].combDelays[CellDelayKey{fromPort, toPort}] = delay; +} + +void Arch::addCellTimingSetupHold(IdString cell, IdString port, IdString clock, DelayInfo setup, DelayInfo hold) +{ + TimingClockingInfo ci; + ci.clock_port = clock; + ci.edge = RISING_EDGE; + ci.setup = setup; + ci.hold = hold; + cellTiming[cell].clockingInfo[port].push_back(ci); + cellTiming[cell].portClasses[port] = TMG_REGISTER_INPUT; +} + +void Arch::addCellTimingClockToOut(IdString cell, IdString port, IdString clock, DelayInfo clktoq) +{ + TimingClockingInfo ci; + ci.clock_port = clock; + ci.edge = RISING_EDGE; + ci.clockToQ = clktoq; + cellTiming[cell].clockingInfo[port].push_back(ci); + cellTiming[cell].portClasses[port] = TMG_REGISTER_OUTPUT; +} + +// --------------------------------------------------------------- + +//TODO represent wires more intelligently. +IdString Arch::wireToGlobal(int row, int col, const DatabasePOD* db, IdString wire) { + 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)); + int rootrow, rootcol; + switch (direction) + { + case 'N': + rootrow = row + segment; + rootcol = col; + break; + case 'S': + rootrow = row - segment; + rootcol = col; + break; + case 'E': + rootrow = row; + rootcol = col - segment; + break; + case 'W': + rootrow = row; + rootcol = 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(rootrow < 0) { + rootrow = -1-rootrow; + direction = 'N'; + } else if(rootcol < 0) { + rootcol = -1-rootcol; + direction = 'W'; + } else if(rootrow >= db->rows) { + rootrow = 2*db->rows-1-rootrow; + direction = 'S'; + } else if(rootcol >= db->cols) { + rootcol = 2*db->cols-1-rootcol; + direction = 'E'; + } + snprintf(buf, 32, "R%dC%d_%c%d", rootrow+1, rootcol+1, direction, num); + return id(buf); +} + +Arch::Arch(ArchArgs args) : args(args) +{ + family = "GW1N-9"; + device = "GW1NR-9"; + speed = "C6/E5"; // or whatever + package = "QFN48"; // or something + + // 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 db + 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(); + for(unsigned int j=0; j < tile->num_pips; j++) { + const PairPOD pip = tile->pips[j]; + IdString gdestname = wireToGlobal(row, col, db, pip.dest_id); + if(wires.count(gdestname) == 0) addWire(gdestname, pip.dest_id, col, row); + IdString gsrcname = wireToGlobal(row, col, db, pip.src_id); + if(wires.count(gsrcname) == 0) addWire(gsrcname, pip.src_id, col, row); + } + for(unsigned int j=0; j < tile->num_clock_pips; j++) { + const PairPOD pip = tile->clock_pips[j]; + IdString gdestname = wireToGlobal(row, col, db, pip.dest_id); + if(wires.count(gdestname) == 0) addWire(gdestname, pip.dest_id, col, row); + IdString gsrcname = wireToGlobal(row, col, db, pip.src_id); + if(wires.count(gsrcname) == 0) addWire(gsrcname, pip.src_id, col, row); + } + } + // Dummy for empty decals + decal_graphics[IdString()]; +} + +void IdString::initialize_arch(const BaseCtx *ctx) {} + +// --------------------------------------------------------------- + +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 +{ + // FIXME + return 0; +} + +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 +{ + // FIXME + return 0; +} + +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("GENERIC_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("GENERIC_IOB")); + 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("Generic 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("iCE40 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) { + CellInfo *ci = cell.second.get(); + if (ci->type == id("GENERIC_SLICE")) { + ci->is_slice = true; + ci->slice_clk = get_net_or_empty(ci, id("CLK")); + } else { + ci->is_slice = false; + } + ci->user_group = int_or_default(ci->attrs, id("PACK_GROUP"), -1); + } +} + +bool Arch::cellsCompatible(const CellInfo **cells, int count) const +{ + const NetInfo *clk = nullptr; + int group = -1; + for (int i = 0; i < count; i++) { + const CellInfo *ci = cells[i]; + if (ci->is_slice && ci->slice_clk != nullptr) { + if (clk == nullptr) + clk = ci->slice_clk; + else if (clk != ci->slice_clk) + return false; + } + if (ci->user_group != -1) { + if (group == -1) + group = ci->user_group; + else if (group != ci->user_group) + return false; + } + } + return true; +} + +NEXTPNR_NAMESPACE_END diff --git a/gowin/arch.h b/gowin/arch.h new file mode 100644 index 00000000..177fb121 --- /dev/null +++ b/gowin/arch.h @@ -0,0 +1,371 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * + * 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); } + + 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 DatabasePOD { + RelPtr family; + uint32_t version; + uint16_t rows; + uint16_t cols; + RelPtr> grid; + uint32_t num_aliases; + RelPtr aliases; + uint16_t num_constids; + uint16_t num_ids; + RelPtr> id_strs; +}); + +struct ArchArgs +{ + std::string device; + // Number of LUT inputs + int K = 4; + // y = mx + c relationship between distance and delay for interconnect + // delay estimates + double delayScale = 0.1, delayOffset = 0; +}; + +struct WireInfo; + +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; + std::string package; + std::string 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 setLutK(int K); + void setDelayScaling(double scale, double offset); + + void addCellTimingClock(IdString cell, IdString port); + void addCellTimingDelay(IdString cell, IdString fromPort, IdString toPort, DelayInfo delay); + void addCellTimingSetupHold(IdString cell, IdString port, IdString clock, DelayInfo setup, DelayInfo hold); + void addCellTimingClockToOut(IdString cell, IdString port, IdString clock, DelayInfo clktoq); + + IdString wireToGlobal(int row, int col, const DatabasePOD* db, IdString wire); + + // --------------------------------------------------------------- + // 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.001; } + delay_t getRipupDelayPenalty() const { return 0.015; } + float getDelayNS(delay_t v) const { return v; } + + DelayInfo getDelayFromNS(float ns) const + { + DelayInfo del; + del.delay = 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..7f7229fd --- /dev/null +++ b/gowin/arch_pybindings.cc @@ -0,0 +1,237 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford 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"); + + // Generic arch construction API + fn_wrapper_4a_v, + conv_from_str, pass_through, pass_through>::def_wrap(ctx_cls, "addWire", + "name"_a, "type"_a, "x"_a, + "y"_a); + fn_wrapper_6a_v, + conv_from_str, conv_from_str, conv_from_str, pass_through, + pass_through>::def_wrap(ctx_cls, "addPip", "name"_a, "type"_a, "srcWire"_a, "dstWire"_a, + "delay"_a, "loc"_a); + + fn_wrapper_4a_v, + conv_from_str, pass_through, pass_through>::def_wrap(ctx_cls, "addBel", + "name"_a, "type"_a, + "loc"_a, "gb"_a); + fn_wrapper_3a_v, + conv_from_str, conv_from_str>::def_wrap(ctx_cls, "addBelInput", "bel"_a, + "name"_a, "wire"_a); + fn_wrapper_3a_v, + conv_from_str, conv_from_str>::def_wrap(ctx_cls, "addBelOutput", "bel"_a, + "name"_a, "wire"_a); + fn_wrapper_3a_v, + conv_from_str, conv_from_str>::def_wrap(ctx_cls, "addBelInout", "bel"_a, + "name"_a, "wire"_a); + + fn_wrapper_2a_v, + conv_from_str>::def_wrap(ctx_cls, "addGroupBel", "group"_a, "bel"_a); + fn_wrapper_2a_v, + conv_from_str>::def_wrap(ctx_cls, "addGroupWire", "group"_a, "wire"_a); + fn_wrapper_2a_v, + conv_from_str>::def_wrap(ctx_cls, "addGroupPip", "group"_a, "pip"_a); + fn_wrapper_2a_v, + conv_from_str>::def_wrap(ctx_cls, "addGroupGroup", "group"_a, "grp"_a); + + fn_wrapper_2a_v, + pass_through>::def_wrap(ctx_cls, "addDecalGraphic", (py::arg("decal"), "graphic")); + fn_wrapper_2a_v, + unwrap_context>::def_wrap(ctx_cls, "setWireDecal", "wire"_a, "decalxy"_a); + fn_wrapper_2a_v, + unwrap_context>::def_wrap(ctx_cls, "setPipDecal", "pip"_a, "decalxy"_a); + fn_wrapper_2a_v, + unwrap_context>::def_wrap(ctx_cls, "setBelDecal", "bel"_a, "decalxy"_a); + fn_wrapper_2a_v, + unwrap_context>::def_wrap(ctx_cls, "setGroupDecal", "group"_a, "decalxy"_a); + + fn_wrapper_3a_v, + conv_from_str, pass_through>::def_wrap(ctx_cls, "setWireAttr", "wire"_a, + "key"_a, "value"_a); + fn_wrapper_3a_v, + conv_from_str, pass_through>::def_wrap(ctx_cls, "setBelAttr", "bel"_a, + "key"_a, "value"_a); + fn_wrapper_3a_v, + conv_from_str, pass_through>::def_wrap(ctx_cls, "setPipAttr", "pip"_a, + "key"_a, "value"_a); + + fn_wrapper_1a_v>::def_wrap( + ctx_cls, "setLutK", "K"_a); + fn_wrapper_2a_v, + pass_through>::def_wrap(ctx_cls, "setDelayScaling", "scale"_a, "offset"_a); + + fn_wrapper_2a_v, conv_from_str>::def_wrap(ctx_cls, "addCellTimingClock", "cell"_a, + "port"_a); + fn_wrapper_4a_v, conv_from_str, conv_from_str, + pass_through>::def_wrap(ctx_cls, "addCellTimingDelay", "cell"_a, "fromPort"_a, + "toPort"_a, "delay"_a); + fn_wrapper_5a_v, conv_from_str, conv_from_str, pass_through, + pass_through>::def_wrap(ctx_cls, "addCellTimingSetupHold", "cell"_a, "port"_a, "clock"_a, + "setup"_a, "hold"_a); + fn_wrapper_4a_v, conv_from_str, conv_from_str, + pass_through>::def_wrap(ctx_cls, "addCellTimingClockToOut", "cell"_a, "port"_a, + "clock"_a, "clktoq"_a); + + 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..f7f07529 --- /dev/null +++ b/gowin/arch_pybindings.h @@ -0,0 +1,31 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford 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..978c9c9b --- /dev/null +++ b/gowin/archdefs.h @@ -0,0 +1,72 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * + * 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 delay = 0; + + delay_t minRaiseDelay() const { return delay; } + delay_t maxRaiseDelay() const { return delay; } + + delay_t minFallDelay() const { return delay; } + delay_t maxFallDelay() const { return delay; } + + delay_t minDelay() const { return delay; } + delay_t maxDelay() const { return delay; } + + DelayInfo operator+(const DelayInfo &other) const + { + DelayInfo ret; + ret.delay = this->delay + other.delay; + return ret; + } +}; + +typedef IdString BelId; +typedef IdString WireId; +typedef IdString PipId; +typedef IdString GroupId; +typedef IdString DecalId; + +struct ArchNetInfo +{ +}; + +struct NetInfo; + +struct ArchCellInfo +{ + // Custom grouping set via "PACK_GROUP" attribute. All cells with the same group + // value may share a tile (-1 = don't care, default if not set) + int user_group; + // Is a slice type primitive + bool is_slice; + // Only packing rule for slice type primitives is a single clock per tile + const NetInfo *slice_clk; +}; + +NEXTPNR_NAMESPACE_END diff --git a/gowin/cells.cc b/gowin/cells.cc new file mode 100644 index 00000000..c4421f90 --- /dev/null +++ b/gowin/cells.cc @@ -0,0 +1,147 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "cells.h" +#include "design_utils.h" +#include "log.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +void add_port(const Context *ctx, CellInfo *cell, std::string name, PortType dir) +{ + IdString id = ctx->id(name); + NPNR_ASSERT(cell->ports.count(id) == 0); + cell->ports[id] = PortInfo{id, nullptr, dir}; +} + +std::unique_ptr create_generic_cell(Context *ctx, IdString type, std::string name) +{ + static int auto_idx = 0; + std::unique_ptr new_cell = std::unique_ptr(new CellInfo()); + if (name.empty()) { + new_cell->name = ctx->id("$nextpnr_" + type.str(ctx) + "_" + std::to_string(auto_idx++)); + } else { + new_cell->name = ctx->id(name); + } + new_cell->type = type; + if (type == ctx->id("GENERIC_SLICE")) { + new_cell->params[ctx->id("K")] = ctx->args.K; + new_cell->params[ctx->id("INIT")] = 0; + new_cell->params[ctx->id("FF_USED")] = 0; + + for (int i = 0; i < ctx->args.K; i++) + add_port(ctx, new_cell.get(), "I[" + std::to_string(i) + "]", PORT_IN); + + add_port(ctx, new_cell.get(), "CLK", PORT_IN); + + add_port(ctx, new_cell.get(), "F", PORT_OUT); + add_port(ctx, new_cell.get(), "Q", PORT_OUT); + } else if (type == ctx->id("GENERIC_IOB")) { + new_cell->params[ctx->id("INPUT_USED")] = 0; + new_cell->params[ctx->id("OUTPUT_USED")] = 0; + new_cell->params[ctx->id("ENABLE_USED")] = 0; + + add_port(ctx, new_cell.get(), "PAD", PORT_INOUT); + add_port(ctx, new_cell.get(), "I", PORT_IN); + add_port(ctx, new_cell.get(), "EN", PORT_IN); + add_port(ctx, new_cell.get(), "O", PORT_OUT); + } else { + log_error("unable to create generic cell of type %s", type.c_str(ctx)); + } + return new_cell; +} + +void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff) +{ + lc->params[ctx->id("INIT")] = lut->params[ctx->id("INIT")]; + + int lut_k = int_or_default(lut->params, ctx->id("K"), 4); + NPNR_ASSERT(lut_k <= ctx->args.K); + + for (int i = 0; i < lut_k; i++) { + IdString port = ctx->id("I[" + std::to_string(i) + "]"); + replace_port(lut, port, lc, port); + } + + if (no_dff) { + lc->params[ctx->id("FF_USED")] = 0; + replace_port(lut, ctx->id("Q"), lc, ctx->id("F")); + } +} + +void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut) +{ + lc->params[ctx->id("FF_USED")] = 1; + replace_port(dff, ctx->id("CLK"), lc, ctx->id("CLK")); + + if (pass_thru_lut) { + // Fill LUT with alternating 10 + const int init_size = 1 << lc->params[ctx->id("K")].as_int64(); + std::string init; + init.reserve(init_size); + for (int i = 0; i < init_size; i += 2) + init.append("10"); + lc->params[ctx->id("INIT")] = Property::from_string(init); + + replace_port(dff, ctx->id("D"), lc, ctx->id("I[0]")); + } + + replace_port(dff, ctx->id("Q"), lc, ctx->id("Q")); +} + +void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *iob, std::unordered_set &todelete_cells) +{ + if (nxio->type == ctx->id("$nextpnr_ibuf")) { + iob->params[ctx->id("INPUT_USED")] = 1; + replace_port(nxio, ctx->id("O"), iob, ctx->id("O")); + } else if (nxio->type == ctx->id("$nextpnr_obuf")) { + iob->params[ctx->id("OUTPUT_USED")] = 1; + replace_port(nxio, ctx->id("I"), iob, ctx->id("I")); + } else if (nxio->type == ctx->id("$nextpnr_iobuf")) { + // N.B. tristate will be dealt with below + iob->params[ctx->id("INPUT_USED")] = 1; + iob->params[ctx->id("OUTPUT_USED")] = 1; + replace_port(nxio, ctx->id("I"), iob, ctx->id("I")); + replace_port(nxio, ctx->id("O"), iob, ctx->id("O")); + } else { + NPNR_ASSERT(false); + } + NetInfo *donet = iob->ports.at(ctx->id("I")).net; + CellInfo *tbuf = net_driven_by( + ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); }, + ctx->id("Y")); + if (tbuf) { + iob->params[ctx->id("ENABLE_USED")] = 1; + replace_port(tbuf, ctx->id("A"), iob, ctx->id("I")); + replace_port(tbuf, ctx->id("E"), iob, ctx->id("EN")); + + if (donet->users.size() > 1) { + for (auto user : donet->users) + log_info(" remaining tristate user: %s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx)); + log_error("unsupported tristate IO pattern for IO buffer '%s', " + "instantiate GENERIC_IOB manually to ensure correct behaviour\n", + nxio->name.c_str(ctx)); + } + ctx->nets.erase(donet->name); + todelete_cells.insert(tbuf->name); + } +} + +NEXTPNR_NAMESPACE_END diff --git a/gowin/cells.h b/gowin/cells.h new file mode 100644 index 00000000..646d738d --- /dev/null +++ b/gowin/cells.h @@ -0,0 +1,55 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "nextpnr.h" + +#ifndef GENERIC_CELLS_H +#define GENERIC_CELLS_H + +NEXTPNR_NAMESPACE_BEGIN + +// Create a generic arch cell and return it +// Name will be automatically assigned if not specified +std::unique_ptr create_generic_cell(Context *ctx, IdString type, std::string name = ""); + +// Return true if a cell is a LUT +inline bool is_lut(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("LUT"); } + +// Return true if a cell is a flipflop +inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("DFF"); } + +inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("GENERIC_SLICE"); } + +// Convert a LUT primitive to (part of) an GENERIC_SLICE, swapping ports +// as needed. Set no_dff if a DFF is not being used, so that the output +// can be reconnected +void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff = true); + +// Convert a DFF primitive to (part of) an GENERIC_SLICE, setting parameters +// and reconnecting signals as necessary. If pass_thru_lut is True, the LUT will +// be configured as pass through and D connected to I0, otherwise D will be +// ignored +void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut = false); + +// Convert a nextpnr IO buffer to a GENERIC_IOB +void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *sbio, std::unordered_set &todelete_cells); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/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..020d8882 --- /dev/null +++ b/gowin/main.cc @@ -0,0 +1,79 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * + * 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 "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()("family", po::value(), "device family"); + specific.add_options()("package", po::value(), "device package"); + specific.add_options()("speed", po::value(), "device speed grade"); + specific.add_options()("pdc", po::value(), "physical constraints file"); + return specific; +} + +std::unique_ptr GowinCommandHandler::createContext(std::unordered_map &values) +{ + ArchArgs chipArgs; + chipArgs.device = vm["device"].as(); + return std::unique_ptr(new Context(chipArgs)); +} + +void GowinCommandHandler::customAfterLoad(Context *ctx) +{ + // if (vm.count("pdc")) { + // std::string filename = vm["pdc"].as(); + // std::ifstream in(filename); + // if (!in) + // log_error("Failed to open input PDC file %s.\n", filename.c_str()); + // ctx->read_pdc(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..43157b6c --- /dev/null +++ b/gowin/pack.cc @@ -0,0 +1,288 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018-19 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include +#include +#include "cells.h" +#include "design_utils.h" +#include "log.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +// Pack LUTs and LUT-FF pairs +static void pack_lut_lutffs(Context *ctx) +{ + log_info("Packing LUT-FFs..\n"); + + std::unordered_set packed_cells; + std::vector> new_cells; + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ctx->verbose) + log_info("cell '%s' is of type '%s'\n", ci->name.c_str(ctx), ci->type.c_str(ctx)); + if (is_lut(ctx, ci)) { + std::unique_ptr packed = + create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), ci->name.str(ctx) + "_LC"); + std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(packed->attrs, packed->attrs.begin())); + packed_cells.insert(ci->name); + if (ctx->verbose) + log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx)); + // See if we can pack into a DFF + // TODO: LUT cascade + NetInfo *o = ci->ports.at(ctx->id("Q")).net; + CellInfo *dff = net_only_drives(ctx, o, is_ff, ctx->id("D"), true); + auto lut_bel = ci->attrs.find(ctx->id("BEL")); + bool packed_dff = false; + if (dff) { + if (ctx->verbose) + log_info("found attached dff %s\n", dff->name.c_str(ctx)); + auto dff_bel = dff->attrs.find(ctx->id("BEL")); + if (lut_bel != ci->attrs.end() && dff_bel != dff->attrs.end() && lut_bel->second != dff_bel->second) { + // Locations don't match, can't pack + } else { + lut_to_lc(ctx, ci, packed.get(), false); + dff_to_lc(ctx, dff, packed.get(), false); + ctx->nets.erase(o->name); + if (dff_bel != dff->attrs.end()) + packed->attrs[ctx->id("BEL")] = dff_bel->second; + packed_cells.insert(dff->name); + if (ctx->verbose) + log_info("packed cell %s into %s\n", dff->name.c_str(ctx), packed->name.c_str(ctx)); + packed_dff = true; + } + } + if (!packed_dff) { + lut_to_lc(ctx, ci, packed.get(), true); + } + new_cells.push_back(std::move(packed)); + } + } + for (auto pcell : packed_cells) { + ctx->cells.erase(pcell); + } + for (auto &ncell : new_cells) { + ctx->cells[ncell->name] = std::move(ncell); + } +} + +// Pack FFs not packed as LUTFFs +static void pack_nonlut_ffs(Context *ctx) +{ + log_info("Packing non-LUT FFs..\n"); + + std::unordered_set packed_cells; + std::vector> new_cells; + + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_ff(ctx, ci)) { + std::unique_ptr packed = + create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), ci->name.str(ctx) + "_DFFLC"); + std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(packed->attrs, packed->attrs.begin())); + if (ctx->verbose) + log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx)); + packed_cells.insert(ci->name); + dff_to_lc(ctx, ci, packed.get(), true); + new_cells.push_back(std::move(packed)); + } + } + for (auto pcell : packed_cells) { + ctx->cells.erase(pcell); + } + for (auto &ncell : new_cells) { + ctx->cells[ncell->name] = std::move(ncell); + } +} + +// Merge a net into a constant net +static void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constnet, bool constval) +{ + orig->driver.cell = nullptr; + for (auto user : orig->users) { + if (user.cell != nullptr) { + CellInfo *uc = user.cell; + if (ctx->verbose) + log_info("%s user %s\n", orig->name.c_str(ctx), uc->name.c_str(ctx)); + if ((is_lut(ctx, uc) || is_lc(ctx, uc)) && (user.port.str(ctx).at(0) == 'I') && !constval) { + uc->ports[user.port].net = nullptr; + } else { + uc->ports[user.port].net = constnet; + constnet->users.push_back(user); + } + } + } + orig->users.clear(); +} + +// Pack constants (simple implementation) +static void pack_constants(Context *ctx) +{ + log_info("Packing constants..\n"); + + std::unique_ptr gnd_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_GND"); + gnd_cell->params[ctx->id("INIT")] = Property(0, 1 << ctx->args.K); + 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("GENERIC_SLICE"), "$PACKER_VCC"); + // Fill with 1s + vcc_cell->params[ctx->id("INIT")] = Property(Property::S1).extract(0, (1 << ctx->args.K), 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(Context *ctx, CellInfo *cell) +{ + return cell->type == ctx->id("$nextpnr_ibuf") || cell->type == ctx->id("$nextpnr_obuf") || + cell->type == ctx->id("$nextpnr_iobuf"); +} + +static bool is_generic_iob(const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("GENERIC_IOB"); } + +// Pack IO buffers +static void pack_io(Context *ctx) +{ + std::unordered_set packed_cells; + std::unordered_set delete_nets; + + std::vector> new_cells; + log_info("Packing IOs..\n"); + + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_nextpnr_iob(ctx, ci)) { + CellInfo *iob = nullptr; + if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) { + iob = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_generic_iob, ctx->id("PAD"), true, ci); + + } else if (ci->type == ctx->id("$nextpnr_obuf")) { + NetInfo *net = ci->ports.at(ctx->id("I")).net; + iob = net_only_drives(ctx, net, is_generic_iob, ctx->id("PAD"), true, ci); + } + if (iob != nullptr) { + // Trivial case, GENERIC_IOB used. Just destroy the net and the + // iobuf + log_info("%s feeds GENERIC_IOB %s, removing %s %s.\n", ci->name.c_str(ctx), iob->name.c_str(ctx), + ci->type.c_str(ctx), ci->name.c_str(ctx)); + NetInfo *net = iob->ports.at(ctx->id("PAD")).net; + if (((ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) && + net->users.size() > 1) || + (ci->type == ctx->id("$nextpnr_obuf") && (net->users.size() > 2 || net->driver.cell != nullptr))) + log_error("PAD of %s '%s' connected to more than a single top level IO.\n", iob->type.c_str(ctx), + iob->name.c_str(ctx)); + + if (net != nullptr) { + delete_nets.insert(net->name); + iob->ports.at(ctx->id("PAD")).net = nullptr; + } + if (ci->type == ctx->id("$nextpnr_iobuf")) { + NetInfo *net2 = ci->ports.at(ctx->id("I")).net; + if (net2 != nullptr) { + delete_nets.insert(net2->name); + } + } + } else if (bool_or_default(ctx->settings, ctx->id("disable_iobs"))) { + // No IO buffer insertion; just remove nextpnr_[io]buf + for (auto &p : ci->ports) + disconnect_port(ctx, ci, p.first); + } else { + // Create a GENERIC_IOB buffer + std::unique_ptr ice_cell = + create_generic_cell(ctx, ctx->id("GENERIC_IOB"), ci->name.str(ctx) + "$iob"); + nxio_to_iob(ctx, ci, ice_cell.get(), packed_cells); + new_cells.push_back(std::move(ice_cell)); + iob = new_cells.back().get(); + } + packed_cells.insert(ci->name); + if (iob != nullptr) + std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(iob->attrs, iob->attrs.begin())); + } + } + for (auto pcell : packed_cells) { + ctx->cells.erase(pcell); + } + for (auto dnet : delete_nets) { + ctx->nets.erase(dnet); + } + for (auto &ncell : new_cells) { + ctx->cells[ncell->name] = std::move(ncell); + } +} + +// Main pack function +bool Arch::pack() +{ + Context *ctx = getCtx(); + try { + log_break(); + pack_constants(ctx); + pack_io(ctx); + pack_lut_lutffs(ctx); + pack_nonlut_ffs(ctx); + ctx->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