diff --git a/.github/ci/build_himbaechel.sh b/.github/ci/build_himbaechel.sh new file mode 100644 index 0000000000..e8adaf8d79 --- /dev/null +++ b/.github/ci/build_himbaechel.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +function get_dependencies { + : +} + +function build_nextpnr { + mkdir build + pushd build + cmake .. -DARCH=himbaechel + make nextpnr-himbaechel bbasm -j`nproc` + # We'd ideally use pypy3 for speed (as works locally), but the version + # our CI Ubuntu provides doesn't like some of the typing stuff + python3 ../himbaechel/uarch/example/example_arch_gen.py ./example.bba + ./bba/bbasm --l ./example.bba ./example.bin + popd +} + +function run_tests { + : +} + +function run_archcheck { + pushd build + ./nextpnr-himbaechel --uarch example --chipdb ./example.bin --test + popd +} diff --git a/.github/workflows/arch_ci.yml b/.github/workflows/arch_ci.yml index db13c4cb11..72150bc6ae 100644 --- a/.github/workflows/arch_ci.yml +++ b/.github/workflows/arch_ci.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - arch: [mistral, ice40, ecp5, generic, nexus, machxo2, gowin] + arch: [mistral, ice40, ecp5, generic, nexus, machxo2, gowin, himbaechel] runs-on: ubuntu-latest env: DEPS_PATH: ${{ github.workspace }}/deps @@ -30,7 +30,7 @@ jobs: - name: Install run: | sudo apt-get update - sudo apt-get install git make cmake libboost-all-dev python3-dev libeigen3-dev tcl-dev lzma-dev libftdi-dev clang bison flex swig qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools iverilog + sudo apt-get install git make cmake libboost-all-dev python3-dev pypy3 libeigen3-dev tcl-dev lzma-dev libftdi-dev clang bison flex swig qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools iverilog - name: Cache yosys installation uses: actions/cache@v3 diff --git a/CMakeLists.txt b/CMakeLists.txt index 4018cc3efe..b2e8f00693 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,9 +94,9 @@ endif() set(PROGRAM_PREFIX "" CACHE STRING "Name prefix for executables") # List of families to build -set(FAMILIES generic ice40 ecp5 nexus gowin fpga_interchange machxo2 mistral) +set(FAMILIES generic ice40 ecp5 nexus gowin fpga_interchange machxo2 mistral himbaechel) set(STABLE_FAMILIES generic ice40 ecp5) -set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange machxo2 mistral) +set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange machxo2 mistral himbaechel) set(ARCH "" CACHE STRING "Architecture family for nextpnr build") set_property(CACHE ARCH PROPERTY STRINGS ${FAMILIES}) diff --git a/docs/himbaechel.md b/docs/himbaechel.md new file mode 100644 index 0000000000..aec222b95b --- /dev/null +++ b/docs/himbaechel.md @@ -0,0 +1,26 @@ +# Himbächel - a series of bigger arches + +[Viaduct](./viaduct.md) enables custom architectures to be easily prototyped using a C++ API to build in-memory databases; with most of the flexibility of nextpnr's validity checking capabilities to hand. This is the recommended way to get started quickly with FPGAs up to about 20k logic elements, where no other particularly unusal requirements exist. + +However, building the routing graph in-memory at every startup and storing it flat doesn't scale at all well with larger FPGAs (say, 100k LEs or bigger). +So, we take advantage of nextpnr's support for complex, non-flat routing graph structures and define a deduplication approach that's designed to work better even for very large fabrics - the database size is unlikely to exceed about 100MB even for million-LUT scale devices, compared to multiple gigabytes for a flat database. + +Python scripting is defined that allows the user to describe a semi-flattened routing graph and build the deduplicated database binary _at compile time_. Pips - routing switches - are described per tile type rather than flat; however, the connectivity between tiles ("nodes") are described flat and automatically deduplicated during database build. + +## Getting Started + +Most of what's written in the [viaduct docs](./viaduct.md) also applies to bootstrapping a Himbächel arch - this also provides a migration path for an existing Viaduct architecture. Just replace `viaduct` with `himbaechel` and `ViaductAPI` with `HimbaechelAPI` - the set of validity checking and custom flow "hooks" that you have access to is designed to be otherwise as close as possible. + +However, the key difference is that you will need to generate a "binary blob" chip database. `himbaechel_dbgen/bba.py` provides a framework for this. The typical steps for using this API would be as follows: + - Create a `Chip` instance + - For each unique "tile type" in the design (e.g. logic, BRAM, IO - in some cases multiple variants of these may be multiple tile types): + - Create it using `Chip.create_tile_type` + - Add local wires (connectivity between tiles is dealt with later) using `TileType.create_wire` + - Add bels (like LUTs and FFs) using `TileType.create_bel`, and pins to those bels using `TileType.add_bel_pin` + - Add pips using `TileType.create_pip` + - For each grid location, use `Chip.set_tile_type` to set its tile type. Every location must have a tile type set, even if it's just an empty "NULL" tile + - Whenever wires span multiple tiles (i.e. all wires with a length greater than zero), combine the per-tile local wires into a single node using `Chip.add_node` for each case. + - Write out the `.bba` file using `Chip.write_bba` + - Compile it into a binary that nextpnr can load using `./bba/bbasm --l my_chipdb.bba my_chipdb.bin` + +An example Python generator to copy from is located in `uarch/example/example_arch_gen.py`. diff --git a/gui/himbaechel/family.cmake b/gui/himbaechel/family.cmake new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gui/himbaechel/mainwindow.cpp b/gui/himbaechel/mainwindow.cpp new file mode 100644 index 0000000000..e9d280eb98 --- /dev/null +++ b/gui/himbaechel/mainwindow.cpp @@ -0,0 +1,52 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * 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 "mainwindow.h" + +#include +#include + +static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } + +NEXTPNR_NAMESPACE_BEGIN + +MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) + : BaseMainWindow(std::move(context), handler, parent) +{ + initMainResource(); +} + +MainWindow::~MainWindow() {} + +void MainWindow::newContext(Context *ctx) +{ + std::string title = "nextpnr-himbächel - " + ctx->getChipName(); + setWindowTitle(title.c_str()); +} + +void MainWindow::createMenu() {} + +void MainWindow::new_proj() +{ + QMessageBox::critical(0, "Error", + "Creating a new project not supported in himbächel mode, please re-start from command line."); + std::exit(1); +} + +NEXTPNR_NAMESPACE_END diff --git a/gui/himbaechel/mainwindow.h b/gui/himbaechel/mainwindow.h new file mode 100644 index 0000000000..7cdb238b06 --- /dev/null +++ b/gui/himbaechel/mainwindow.h @@ -0,0 +1,45 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * 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 MAINWINDOW_H +#define MAINWINDOW_H + +#include "../basewindow.h" + +NEXTPNR_NAMESPACE_BEGIN + +class MainWindow : public BaseMainWindow +{ + Q_OBJECT + + public: + explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); + virtual ~MainWindow(); + + public: + void createMenu(); + + protected Q_SLOTS: + void new_proj() override; + void newContext(Context *ctx); +}; + +NEXTPNR_NAMESPACE_END + +#endif // MAINWINDOW_H diff --git a/gui/himbaechel/nextpnr.qrc b/gui/himbaechel/nextpnr.qrc new file mode 100644 index 0000000000..03585ec030 --- /dev/null +++ b/gui/himbaechel/nextpnr.qrc @@ -0,0 +1,2 @@ + + diff --git a/himbaechel/.gitignore b/himbaechel/.gitignore new file mode 100644 index 0000000000..bee8a64b79 --- /dev/null +++ b/himbaechel/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/himbaechel/arch.cc b/himbaechel/arch.cc new file mode 100644 index 0000000000..6f5b303eed --- /dev/null +++ b/himbaechel/arch.cc @@ -0,0 +1,264 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat + * + * 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 "arch.h" +#include "archdefs.h" +#include "chipdb.h" +#include "log.h" +#include "nextpnr.h" + +#include "util.h" +#include "placer1.h" +#include "placer_heap.h" +#include "router1.h" +#include "router2.h" + + + +NEXTPNR_NAMESPACE_BEGIN + +static const ChipInfoPOD *get_chip_info(const RelPtr *ptr) { return ptr->get(); } + +Arch::Arch(ArchArgs args) +{ + try { + blob_file.open(args.chipdb); + if (args.chipdb.empty() || !blob_file.is_open()) + log_error("Unable to read chipdb %s\n", args.chipdb.c_str()); + const char *blob = reinterpret_cast(blob_file.data()); + chip_info = get_chip_info(reinterpret_cast *>(blob)); + } catch (...) { + log_error("Unable to read chipdb %s\n", args.chipdb.c_str()); + } + // Check consistency of blob + if (chip_info->magic != 0x00ca7ca7) + log_error("chipdb %s does not look like a valid himbächel database!\n", args.chipdb.c_str()); + std::string blob_uarch(chip_info->uarch.get()); + if (blob_uarch != args.uarch) + log_error("database device uarch '%s' does not match selected device uarch '%s'.\n", blob_uarch.c_str(), + args.uarch.c_str()); + // Load uarch + uarch = HimbaechelArch::create(args.uarch, args.options); + if (!uarch) { + std::string available = HimbaechelArch::list(); + log_error("unable to load device uarch '%s', available options: %s\n", args.uarch.c_str(), available.c_str()); + } + uarch->init_constids(this); + // Setup constids from database + for (int i = 0; i < chip_info->extra_constids->bba_ids.ssize(); i++) { + IdString::initialize_add(this, chip_info->extra_constids->bba_ids[i].get(), + i + chip_info->extra_constids->known_id_count); + } + init_tiles(); +} + +void Arch::init_tiles() +{ + for (int y = 0; y < chip_info->height; y++) { + for (int x = 0; x < chip_info->width; x++) { + int tile = y * chip_info->width + x; + auto &inst = chip_info->tile_insts[tile]; + IdString name = idf("%sX%dY%d", IdString(inst.name_prefix).c_str(this), x, y); + NPNR_ASSERT(int(tile_name.size()) == tile); + tile_name.push_back(name); + tile_name2idx[name] = tile; + } + } +} + +void Arch::late_init() +{ + BaseArch::init_cell_types(); + BaseArch::init_bel_buckets(); +} + +BelId Arch::getBelByName(IdStringList name) const +{ + NPNR_ASSERT(name.size() == 2); + int tile = tile_name2idx.at(name[0]); + const auto &tdata = chip_tile_info(chip_info, tile); + for (int bel = 0; bel < tdata.bels.ssize(); bel++) { + if (IdString(tdata.bels[bel].name) == name[1]) + return BelId(tile, bel); + } + return BelId(); +} + +IdStringList Arch::getBelName(BelId bel) const +{ + return IdStringList::concat(tile_name.at(bel.tile), IdString(chip_bel_info(chip_info, bel).name)); +} + +WireId Arch::getBelPinWire(BelId bel, IdString pin) const +{ + // TODO: binary search + auto &info = chip_bel_info(chip_info, bel); + for (auto &bel_pin : info.pins) { + if (IdString(bel_pin.name) == pin) + return normalise_wire(bel.tile, bel_pin.wire); + } + return WireId(); +} + +PortType Arch::getBelPinType(BelId bel, IdString pin) const +{ + auto &info = chip_bel_info(chip_info, bel); + for (auto &bel_pin : info.pins) { + if (IdString(bel_pin.name) == pin) + return PortType(bel_pin.type); + } + NPNR_ASSERT_FALSE("bel pin not found"); +} + +std::vector Arch::getBelPins(BelId bel) const +{ + std::vector result; + auto &info = chip_bel_info(chip_info, bel); + result.reserve(info.pins.size()); + for (auto &bel_pin : info.pins) + result.emplace_back(bel_pin.name); + return result; +} + +bool Arch::pack() +{ + log_break(); + uarch->pack(); + getCtx()->assignArchInfo(); + getCtx()->settings[id("pack")] = 1; + log_info("Checksum: 0x%08x\n", getCtx()->checksum()); + return true; +} + +bool Arch::place() +{ + bool retVal = false; + uarch->prePlace(); + std::string placer = str_or_default(settings, id("placer"), defaultPlacer); + if (placer == "heap") { + PlacerHeapCfg cfg(getCtx()); + uarch->configurePlacerHeap(cfg); + cfg.ioBufTypes.insert(id("GENERIC_IOB")); + retVal = placer_heap(getCtx(), cfg); + } else if (placer == "sa") { + retVal = placer1(getCtx(), Placer1Cfg(getCtx())); + } else { + log_error("Himbächel architecture does not support placer '%s'\n", placer.c_str()); + } + uarch->postPlace(); + getCtx()->settings[getCtx()->id("place")] = 1; + archInfoToAttributes(); + return retVal; +} + +bool Arch::route() +{ + uarch->preRoute(); + 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("Himbächel architecture does not support router '%s'\n", router.c_str()); + } + uarch->postRoute(); + getCtx()->settings[getCtx()->id("route")] = 1; + archInfoToAttributes(); + return result; +} + +void Arch::assignArchInfo() +{ + int cell_idx = 0, net_idx = 0; + for (auto &cell : cells) { + CellInfo *ci = cell.second.get(); + ci->flat_index = cell_idx++; + for (auto &port : ci->ports) { + // Default 1:1 cell:bel mapping + if (!ci->cell_bel_pins.count(port.first)) + ci->cell_bel_pins[port.first].push_back(port.first); + } + } + for (auto &net : nets) { + net.second->flat_index = net_idx++; + } +} + +WireId Arch::getWireByName(IdStringList name) const +{ + NPNR_ASSERT(name.size() == 2); + int tile = tile_name2idx.at(name[0]); + const auto &tdata = chip_tile_info(chip_info, tile); + for (int wire = 0; wire < tdata.wires.ssize(); wire++) { + if (IdString(tdata.wires[wire].name) == name[1]) + return WireId(tile, wire); + } + return WireId(); +} + +IdStringList Arch::getWireName(WireId wire) const +{ + return IdStringList::concat(tile_name.at(wire.tile), IdString(chip_wire_info(chip_info, wire).name)); +} + +PipId Arch::getPipByName(IdStringList name) const +{ + NPNR_ASSERT(name.size() == 3); + int tile = tile_name2idx.at(name[0]); + const auto &tdata = chip_tile_info(chip_info, tile); + for (int pip = 0; pip < tdata.pips.ssize(); pip++) { + if (IdString(tdata.wires[tdata.pips[pip].dst_wire].name) == name[1] && + IdString(tdata.wires[tdata.pips[pip].src_wire].name) == name[2]) + return PipId(tile, pip); + } + return PipId(); +} + +IdStringList Arch::getPipName(PipId pip) const +{ + auto &tdata = chip_tile_info(chip_info, pip.tile); + auto &pdata = tdata.pips[pip.index]; + return IdStringList::concat(tile_name.at(pip.tile), + IdStringList::concat(IdString(tdata.wires[pdata.dst_wire].name), + IdString(tdata.wires[pdata.src_wire].name))); +} + +IdString Arch::getPipType(PipId pip) const { return IdString(); } + +std::string Arch::getChipName() const { return chip_info->name.get(); } + +IdString Arch::archArgsToId(ArchArgs args) const +{ + // TODO + return IdString(); +} + +void IdString::initialize_arch(const BaseCtx *ctx) {} + +const std::string Arch::defaultPlacer = "heap"; + +const std::vector Arch::availablePlacers = {"sa", "heap"}; + +const std::string Arch::defaultRouter = "router1"; +const std::vector Arch::availableRouters = {"router1", "router2"}; + +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/arch.h b/himbaechel/arch.h new file mode 100644 index 0000000000..e5a4541826 --- /dev/null +++ b/himbaechel/arch.h @@ -0,0 +1,682 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021-2023 gatecat + * + * + * 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 HIMBAECHEL_ARCH_H +#define HIMBAECHEL_ARCH_H + +#include +#include + +#include "base_arch.h" +#include "chipdb.h" +#include "himbaechel_api.h" +#include "nextpnr_namespaces.h" +#include "nextpnr_types.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +inline const TileTypePOD &chip_tile_info(const ChipInfoPOD *chip, int tile) +{ + return chip->tile_types[chip->tile_insts[tile].type]; +} +inline const BelDataPOD &chip_bel_info(const ChipInfoPOD *chip, BelId bel) +{ + return chip_tile_info(chip, bel.tile).bels[bel.index]; +} +inline const TileWireDataPOD &chip_wire_info(const ChipInfoPOD *chip, WireId wire) +{ + return chip_tile_info(chip, wire.tile).wires[wire.index]; +} +inline const PipDataPOD &chip_pip_info(const ChipInfoPOD *chip, PipId pip) +{ + return chip_tile_info(chip, pip.tile).pips[pip.index]; +} +inline const TileRoutingShapePOD &chip_tile_shape(const ChipInfoPOD *chip, int tile) +{ + return chip->tile_shapes[chip->tile_insts[tile].shape]; +} +inline uint32_t node_shape_idx(const RelNodeRefPOD &node_entry) +{ + return uint16_t(node_entry.dy) | (uint32_t(node_entry.wire) << 16); +} +inline const NodeShapePOD &chip_node_shape(const ChipInfoPOD *chip, int tile, int node) +{ + auto &node_entry = chip->tile_shapes[chip->tile_insts[tile].shape].wire_to_node[node]; + NPNR_ASSERT(node_entry.dx_mode == RelNodeRefPOD::MODE_IS_ROOT); + uint32_t node_shape = node_shape_idx(node_entry); + return chip->node_shapes[node_shape]; +} +inline void tile_xy(const ChipInfoPOD *chip, int tile, int &x, int &y) +{ + x = tile % chip->width; + y = tile / chip->width; +} +inline int tile_by_xy(const ChipInfoPOD *chip, int x, int y) { return y * chip->width + x; } +inline int rel_tile(const ChipInfoPOD *chip, int base, int dx, int dy) +{ + int x = base % chip->width; + int y = base / chip->width; + if (dx == RelNodeRefPOD::MODE_ROW_CONST) { + return y * chip->width; + } else if (dx == RelNodeRefPOD::MODE_GLB_CONST) { + return 0; + } else { + return (x + dx) + (y + dy) * chip->width; + } +} + +inline bool is_root_wire(const ChipInfoPOD *chip, int tile, int index) +{ + auto &shape = chip_tile_shape(chip, tile); + if (index >= shape.wire_to_node.ssize()) + return true; + auto &node_entry = shape.wire_to_node[index]; + return (node_entry.dx_mode == RelNodeRefPOD::MODE_IS_ROOT || node_entry.dx_mode == RelNodeRefPOD::MODE_TILE_WIRE); +} + +inline bool is_nodal_wire(const ChipInfoPOD *chip, int tile, int index) +{ + auto &shape = chip_tile_shape(chip, tile); + if (index >= shape.wire_to_node.ssize()) + return false; + auto &node_entry = shape.wire_to_node[index]; + return (node_entry.dx_mode == RelNodeRefPOD::MODE_IS_ROOT); +} + +// Shared code between bel and pip iterators +template TileTypePOD::*ptr> struct TileObjIterator +{ + const ChipInfoPOD *chip; + int cursor_tile; + int cursor_index; + bool single_tile; + + TileObjIterator(const ChipInfoPOD *chip, int tile, int index, bool single_tile = false) + : chip(chip), cursor_tile(tile), cursor_index(index), single_tile(single_tile) + { + } + + TileObjIterator operator++() + { + cursor_index++; + if (!single_tile) { + while (cursor_tile < chip->tile_insts.ssize() && + cursor_index >= (chip_tile_info(chip, cursor_tile).*ptr).ssize()) { + cursor_index = 0; + cursor_tile++; + } + } + return *this; + } + TileObjIterator operator++(int) + { + TileObjIterator prior(*this); + ++(*this); + return prior; + } + + bool operator!=(const TileObjIterator &other) const + { + return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile; + } + + bool operator==(const TileObjIterator &other) const + { + return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile; + } + + Tid operator*() const + { + Tid ret; + ret.tile = cursor_tile; + ret.index = cursor_index; + return ret; + } +}; + +template TileTypePOD::*ptr> struct TileObjRange +{ + using iterator = TileObjIterator; + explicit TileObjRange(const ChipInfoPOD *chip) : b(chip, 0, -1), e(chip, chip->tile_insts.size(), 0) + { + // this deals with the case of no objects in tile 0 + ++b; + } + TileObjRange(const ChipInfoPOD *chip, int tile) + : b(chip, tile, 0, true), e(chip, tile, (chip_tile_info(chip, tile).*ptr).ssize(), true) + { + } + + iterator b, e; + iterator begin() const { return b; } + iterator end() const { return e; } +}; + +struct TileWireIterator +{ + const ChipInfoPOD *chip; + WireId base; + int node_shape; + int cursor; + + TileWireIterator(const ChipInfoPOD *chip, WireId base, int node_shape, int cursor) + : chip(chip), base(base), node_shape(node_shape), cursor(cursor){}; + + void operator++() { cursor++; } + bool operator!=(const TileWireIterator &other) const { return cursor != other.cursor; } + + // Returns a *denormalised* identifier always pointing to a tile wire rather than a node + WireId operator*() const + { + if (node_shape != -1) { + WireId tw; + const auto &node_wire = chip->node_shapes[node_shape].tile_wires[cursor]; + tw.tile = rel_tile(chip, base.tile, node_wire.dx, node_wire.dy); + tw.index = node_wire.wire; + return tw; + } else { + return base; + } + } +}; + +struct TileWireRange +{ + // is nodal + TileWireRange(const ChipInfoPOD *chip, WireId base, int node_shape) + : b(chip, base, node_shape, -1), e(chip, base, node_shape, chip->node_shapes[node_shape].tile_wires.ssize()) + { + // this deals with more complex iteration possibilities simpler, like the first entry being empty + NPNR_ASSERT(node_shape != -1); + ++b; + }; + + // is not nodal + explicit TileWireRange(WireId w) : b(nullptr, w, -1, 0), e(nullptr, w, -1, 1){}; + + TileWireIterator b, e; + TileWireIterator begin() const { return b; } + TileWireIterator end() const { return e; } +}; + +struct WireIterator +{ + const ChipInfoPOD *chip; + int cursor_tile = 0; + int cursor_index = -1; + + WireIterator(const ChipInfoPOD *chip, int tile, int index) : chip(chip), cursor_tile(tile), cursor_index(index){}; + + WireIterator operator++() + { + // Iterate over tile wires, skipping wires that aren't normalised (i.e. they are part of another wire's node) + do { + cursor_index++; + while (cursor_tile < chip->tile_insts.ssize() && + cursor_index >= chip_tile_info(chip, cursor_tile).wires.ssize()) { + cursor_index = 0; + cursor_tile++; + } + + } while (cursor_tile < chip->tile_insts.ssize() && !is_root_wire(chip, cursor_tile, cursor_index)); + + return *this; + } + WireIterator operator++(int) + { + WireIterator prior(*this); + ++(*this); + return prior; + } + + bool operator!=(const WireIterator &other) const + { + return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile; + } + + bool operator==(const WireIterator &other) const + { + return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile; + } + + WireId operator*() const + { + WireId ret; + ret.tile = cursor_tile; + ret.index = cursor_index; + return ret; + } +}; + +struct WireRange +{ + explicit WireRange(const ChipInfoPOD *chip) : b(chip, 0, -1), e(chip, chip->tile_insts.ssize(), 0) + { + // covers the case of no wires in tile 0 + ++b; + } + WireIterator b, e; + WireIterator begin() const { return b; } + WireIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +template TileWireDataPOD::*ptr> struct UpdownhillPipIterator +{ + const ChipInfoPOD *chip; + TileWireIterator twi, twi_end; + int cursor = -1; + + UpdownhillPipIterator(const ChipInfoPOD *chip, TileWireIterator twi, TileWireIterator twi_end, int cursor) + : chip(chip), twi(twi), twi_end(twi_end), cursor(cursor){}; + + void operator++() + { + cursor++; + while (true) { + if (!(twi != twi_end)) + break; + WireId w = *twi; + if (cursor < (chip_wire_info(chip, w).*ptr).ssize()) + break; + ++twi; + cursor = 0; + } + } + bool operator!=(const UpdownhillPipIterator &other) const + { + return twi != other.twi || cursor != other.cursor; + } + + PipId operator*() const + { + PipId ret; + WireId w = *twi; + ret.tile = w.tile; + ret.index = (chip_wire_info(chip, w).*ptr)[cursor]; + return ret; + } +}; + +template TileWireDataPOD::*ptr> struct UpDownhillPipRange +{ + using iterator = UpdownhillPipIterator; + UpDownhillPipRange(const ChipInfoPOD *chip, const TileWireRange &twr) + : b(chip, twr.begin(), twr.end(), -1), e(chip, twr.end(), twr.end(), 0) + { + ++b; + } + iterator b, e; + iterator begin() const { return b; } + iterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +struct BelPinIterator +{ + const ChipInfoPOD *chip; + TileWireIterator twi, twi_end; + int cursor = -1; + + BelPinIterator(const ChipInfoPOD *chip, TileWireIterator twi, TileWireIterator twi_end, int cursor) + : chip(chip), twi(twi), twi_end(twi_end), cursor(cursor){}; + + void operator++() + { + cursor++; + while (true) { + if (!(twi != twi_end)) + break; + WireId w = *twi; + if (cursor < chip_wire_info(chip, w).bel_pins.ssize()) + break; + ++twi; + cursor = 0; + } + } + bool operator!=(const BelPinIterator &other) const { return twi != other.twi || cursor != other.cursor; } + + BelPin operator*() const + { + BelPin ret; + WireId w = *twi; + auto &bp_data = chip_wire_info(chip, w).bel_pins[cursor]; + ret.bel.tile = w.tile; + ret.bel.index = bp_data.bel; + ret.pin = IdString(bp_data.pin); + return ret; + } +}; + +struct BelPinRange +{ + using iterator = BelPinIterator; + BelPinRange(const ChipInfoPOD *chip, const TileWireRange &twr) + : b(chip, twr.begin(), twr.end(), -1), e(chip, twr.end(), twr.end(), 0) + { + ++b; + } + iterator b, e; + iterator begin() const { return b; } + iterator end() const { return e; } +}; + +}; // namespace + +struct ArchArgs +{ + std::string uarch; + std::string chipdb; + std::string device; + dict options; +}; + +typedef TileObjRange BelRange; +typedef TileObjRange AllPipRange; + +typedef UpDownhillPipRange<&TileWireDataPOD::pips_uphill> UphillPipRange; +typedef UpDownhillPipRange<&TileWireDataPOD::pips_downhill> DownhillPipRange; + +struct ArchRanges : BaseArchRanges +{ + using ArchArgsT = ArchArgs; + // Bels + using AllBelsRangeT = BelRange; + using TileBelsRangeT = BelRange; + using BelPinsRangeT = std::vector; + using CellBelPinRangeT = const std::vector &; + // Wires + using AllWiresRangeT = WireRange; + using DownhillPipRangeT = DownhillPipRange; + using UphillPipRangeT = UphillPipRange; + using WireBelPinRangeT = BelPinRange; + // Pips + using AllPipsRangeT = AllPipRange; +}; + +struct Arch : BaseArch +{ + ArchArgs args; + Arch(ArchArgs args); + ~Arch(){}; + + void late_init(); + + // Database references + boost::iostreams::mapped_file_source blob_file; + const ChipInfoPOD *chip_info; + const PackageInfoPOD *package_info = nullptr; + + // Unlike Viaduct, we are not -generic based and therefore uarch must be non-nullptr + std::unique_ptr uarch; + + std::string getChipName() const override; + ArchArgs archArgs() const override { return args; } + IdString archArgsToId(ArchArgs args) const override; + // ------------------------------------------------- + + int getGridDimX() const override { return chip_info->width; } + int getGridDimY() const override { return chip_info->height; } + int getTileBelDimZ(int, int) const override { return 1024; } // TODO ? + int getTilePipDimZ(int, int) const override { return 1; } // TODO ? + char getNameDelimiter() const override { return '/'; } // TODO ? + + // ------------------------------------------------- + + BelId getBelByName(IdStringList name) const override; + IdStringList getBelName(BelId bel) const override; + BelRange getBels() const override { return BelRange(chip_info); } + Loc getBelLocation(BelId bel) const override + { + Loc loc; + tile_xy(chip_info, bel.tile, loc.x, loc.y); + loc.z = chip_bel_info(chip_info, bel).z; + return loc; + } + BelId getBelByLocation(Loc loc) const override + { + int tile = tile_by_xy(chip_info, loc.x, loc.y); + auto &tile_data = chip_tile_info(chip_info, tile); + for (size_t i = 0; i < tile_data.bels.size(); i++) { + if (tile_data.bels[i].z == loc.z) + return BelId(tile, i); + } + return BelId(); + } + BelRange getBelsByTile(int x, int y) const override { return BelRange(chip_info, tile_by_xy(chip_info, x, y)); } + bool getBelGlobalBuf(BelId bel) const override + { + return chip_bel_info(chip_info, bel).flags & BelDataPOD::FLAG_GLOBAL; + } + IdString getBelType(BelId bel) const override { return IdString(chip_bel_info(chip_info, bel).bel_type); } + + WireId getBelPinWire(BelId bel, IdString pin) const override; + PortType getBelPinType(BelId bel, IdString pin) const override; + std::vector getBelPins(BelId bel) const override; + + bool getBelHidden(BelId bel) const final { return chip_bel_info(chip_info, bel).flags & BelDataPOD::FLAG_HIDDEN; } + + // ------------------------------------------------- + + WireId getWireByName(IdStringList name) const override; + IdStringList getWireName(WireId wire) const override; + IdString getWireType(WireId wire) const override { return IdString(chip_wire_info(chip_info, wire).wire_type); } + DelayQuad getWireDelay(WireId wire) const override { return DelayQuad(0); } // TODO + BelPinRange getWireBelPins(WireId wire) const override { return BelPinRange(chip_info, get_tile_wire_range(wire)); } + WireRange getWires() const override { return WireRange(chip_info); } + bool checkWireAvail(WireId wire) const override + { + if (!uarch->checkWireAvail(wire)) + return false; + return BaseArch::checkWireAvail(wire); + } + void bindWire(WireId wire, NetInfo *net, PlaceStrength strength) override + { + uarch->notifyWireChange(wire, net); + BaseArch::bindWire(wire, net, strength); + } + void unbindWire(WireId wire) override + { + uarch->notifyWireChange(wire, nullptr); + BaseArch::unbindWire(wire); + } + + // ------------------------------------------------- + + PipId getPipByName(IdStringList name) const override; + IdStringList getPipName(PipId pip) const override; + AllPipRange getPips() const override { return AllPipRange(chip_info); } + Loc getPipLocation(PipId pip) const override + { + Loc loc; + tile_xy(chip_info, pip.tile, loc.x, loc.y); + loc.z = 0; + return loc; + } + IdString getPipType(PipId pip) const override; + WireId getPipSrcWire(PipId pip) const override + { + return normalise_wire(pip.tile, chip_pip_info(chip_info, pip).src_wire); + } + WireId getPipDstWire(PipId pip) const override + { + return normalise_wire(pip.tile, chip_pip_info(chip_info, pip).dst_wire); + } + DelayQuad getPipDelay(PipId pip) const override { return DelayQuad(100); } + DownhillPipRange getPipsDownhill(WireId wire) const override + { + return DownhillPipRange(chip_info, get_tile_wire_range(wire)); + } + UphillPipRange getPipsUphill(WireId wire) const override + { + return UphillPipRange(chip_info, get_tile_wire_range(wire)); + } + + bool checkPipAvail(PipId pip) const override + { + if (!uarch->checkPipAvail(pip)) + return false; + return BaseArch::checkPipAvail(pip); + } + + bool checkPipAvailForNet(PipId pip, const NetInfo *net) const override + { + if (!uarch->checkPipAvailForNet(pip, net)) + return false; + return BaseArch::checkPipAvailForNet(pip, net); + } + void bindPip(PipId pip, NetInfo *net, PlaceStrength strength) override + { + uarch->notifyPipChange(pip, net); + BaseArch::bindPip(pip, net, strength); + } + void unbindPip(PipId pip) override + { + uarch->notifyPipChange(pip, nullptr); + BaseArch::unbindPip(pip); + } + + // ------------------------------------------------- + + delay_t estimateDelay(WireId src, WireId dst) const override { return uarch->estimateDelay(src, dst); } + delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const override + { + return uarch->predictDelay(src_bel, src_pin, dst_bel, dst_pin); + } + delay_t getDelayEpsilon() const override { return 20; } // TODO + delay_t getRipupDelayPenalty() const override { return 120; } // TODO + float getDelayNS(delay_t v) const override { return v * 0.001; } + delay_t getDelayFromNS(float ns) const override { return delay_t(ns * 1000); } + uint32_t getDelayChecksum(delay_t v) const override { return v; } + bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const override + { + return false; + } + BoundingBox getRouteBoundingBox(WireId src, WireId dst) const override + { + return uarch->getRouteBoundingBox(src, dst); + } + + // ------------------------------------------------- + + void assignArchInfo() override; + bool isBelLocationValid(BelId bel, bool explain_invalid = false) const override + { + return uarch->isBelLocationValid(bel, explain_invalid); + } + + // ------------------------------------------------ + + const std::vector &getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const override + { + return cell_info->cell_bel_pins.at(pin); + } + void update_cell_bel_pins(CellInfo *cell); + + // ------------------------------------------------ + + void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) override + { + uarch->notifyBelChange(bel, cell); + BaseArch::bindBel(bel, cell, strength); // TODO: faster? + } + + void unbindBel(BelId bel) override + { + uarch->notifyBelChange(bel, nullptr); + BaseArch::unbindBel(bel); // TODO: faster? + // TODO: fast tile status and bind + } + + bool checkBelAvail(BelId bel) const override + { + // TODO: fast tile status and bind + if (!uarch->checkBelAvail(bel)) + return false; + return BaseArch::checkBelAvail(bel); + } + + // getBoundBelCell: BaseArch + + // ------------------------------------------------ + + BelBucketId getBelBucketForCellType(IdString cell_type) const override + { + return uarch->getBelBucketForCellType(cell_type); + } + BelBucketId getBelBucketForBel(BelId bel) const override { return uarch->getBelBucketForBel(bel); } + bool isValidBelForCellType(IdString cell_type, BelId bel) const override + { + return uarch->isValidBelForCellType(cell_type, bel); + } + + // ------------------------------------------------ + + // clusters: BaseArch; for now + // ------------------------------------------------ + + bool pack() override; + bool place() override; + bool route() override; + + static const std::string defaultPlacer; + static const std::vector availablePlacers; + static const std::string defaultRouter; + static const std::vector availableRouters; + + // ------------------------------------------------- + + WireId normalise_wire(int32_t tile, int32_t wire) const + { + auto &ts = chip_tile_shape(chip_info, tile); + if (wire >= ts.wire_to_node.ssize()) + return WireId(tile, wire); + auto &w2n = ts.wire_to_node[wire]; + if (w2n.dx_mode == RelNodeRefPOD::MODE_TILE_WIRE || w2n.dx_mode == RelNodeRefPOD::MODE_IS_ROOT) + return WireId(tile, wire); + return WireId(rel_tile(chip_info, tile, w2n.dx_mode, w2n.dy), w2n.wire); + } + + TileWireRange get_tile_wire_range(WireId wire) const + { + auto &ts = chip_tile_shape(chip_info, wire.tile); + if (wire.index >= ts.wire_to_node.ssize()) + return TileWireRange(wire); + auto &w2n = ts.wire_to_node[wire.index]; + if (w2n.dx_mode != RelNodeRefPOD::MODE_TILE_WIRE) { + NPNR_ASSERT(w2n.dx_mode == RelNodeRefPOD::MODE_IS_ROOT); + return TileWireRange(chip_info, wire, node_shape_idx(w2n)); + } else { + return TileWireRange(wire); + } + } + + // ------------------------------------------------- + void init_tiles(); + std::vector tile_name; + dict tile_name2idx; +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/himbaechel/arch_pybindings.cc b/himbaechel/arch_pybindings.cc new file mode 100644 index 0000000000..246adcd9dc --- /dev/null +++ b/himbaechel/arch_pybindings.cc @@ -0,0 +1,73 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020-23 gatecat + * + * 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" + +NEXTPNR_NAMESPACE_BEGIN + +void arch_wrap_python(py::module &m) +{ + using namespace PythonConversion; + py::class_(m, "ArchArgs").def_readwrite("chipdb", &ArchArgs::chipdb); + + py::class_(m, "BelId").def_readwrite("index", &BelId::index); + + py::class_(m, "WireId").def_readwrite("index", &WireId::index); + + py::class_(m, "PipId").def_readwrite("index", &PipId::index); + + auto arch_cls = py::class_(m, "Arch").def(py::init()); + auto ctx_cls = py::class_(m, "Context") + .def("checksum", &Context::checksum) + .def("pack", &Context::pack) + .def("place", &Context::place) + .def("route", &Context::route); + + typedef dict> CellMap; + typedef dict> NetMap; + typedef dict AliasMap; + typedef dict HierarchyMap; + + auto belpin_cls = py::class_>(m, "BelPin"); + readonly_wrapper>::def_wrap(belpin_cls, "bel"); + readonly_wrapper>::def_wrap(belpin_cls, "pin"); + + typedef const std::vector &BelBucketRange; + typedef const std::vector &BelRangeForBelBucket; +#include "arch_pybindings_shared.h" + + WRAP_RANGE(m, Bel, conv_to_str); + WRAP_RANGE(m, Wire, conv_to_str); + WRAP_RANGE(m, AllPip, conv_to_str); + WRAP_RANGE(m, UphillPip, conv_to_str); + WRAP_RANGE(m, DownhillPip, conv_to_str); + WRAP_RANGE(m, BelPin, wrap_context); + + WRAP_MAP_UPTR(m, CellMap, "IdCellMap"); + WRAP_MAP_UPTR(m, NetMap, "IdNetMap"); + WRAP_MAP(m, HierarchyMap, wrap_context, "HierarchyMap"); +} + +NEXTPNR_NAMESPACE_END + +#endif // NO_PYTHON diff --git a/himbaechel/arch_pybindings.h b/himbaechel/arch_pybindings.h new file mode 100644 index 0000000000..c0dc6a8134 --- /dev/null +++ b/himbaechel/arch_pybindings.h @@ -0,0 +1,98 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020-23 gatecat + * + * 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 + +namespace PythonConversion { + +template <> struct string_converter +{ + BelId from_str(Context *ctx, std::string name) { return ctx->getBelByName(IdStringList::parse(ctx, name)); } + + std::string to_str(Context *ctx, BelId id) + { + if (id == BelId()) + throw bad_wrap(); + return ctx->getBelName(id).str(ctx); + } +}; + +template <> struct string_converter +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(IdStringList::parse(ctx, name)); } + + std::string to_str(Context *ctx, WireId id) + { + if (id == WireId()) + throw bad_wrap(); + return ctx->getWireName(id).str(ctx); + } +}; + +template <> struct string_converter +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(IdStringList::parse(ctx, name)); } + + std::string to_str(Context *ctx, WireId id) + { + if (id == WireId()) + throw bad_wrap(); + return ctx->getWireName(id).str(ctx); + } +}; + +template <> struct string_converter +{ + PipId from_str(Context *ctx, std::string name) { return ctx->getPipByName(IdStringList::parse(ctx, name)); } + + std::string to_str(Context *ctx, PipId id) + { + if (id == PipId()) + throw bad_wrap(); + return ctx->getPipName(id).str(ctx); + } +}; + +template <> struct string_converter +{ + BelPin from_str(Context *ctx, std::string name) + { + NPNR_ASSERT_FALSE("string_converter::from_str not implemented"); + } + + std::string to_str(Context *ctx, BelPin pin) + { + if (pin.bel == BelId()) + throw bad_wrap(); + return ctx->getBelName(pin.bel).str(ctx) + "/" + pin.pin.str(ctx); + } +}; + +} // namespace PythonConversion + +NEXTPNR_NAMESPACE_END +#endif +#endif diff --git a/himbaechel/archdefs.h b/himbaechel/archdefs.h new file mode 100644 index 0000000000..b3ede1e25f --- /dev/null +++ b/himbaechel/archdefs.h @@ -0,0 +1,106 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2023 gatecat + * + * 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 HIMBAECHEL_ARCHDEFS_H +#define HIMBAECHEL_ARCHDEFS_H + +#include +#include + +#include "base_clusterinfo.h" +#include "hashlib.h" +#include "idstring.h" +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +typedef int32_t delay_t; + +struct BelId +{ + int32_t tile = -1; + // PIP index in tile + int32_t index = -1; + + BelId() = default; + BelId(int32_t tile, int32_t index) : tile(tile), index(index){}; + + bool operator==(const BelId &other) const { return tile == other.tile && index == other.index; } + bool operator!=(const BelId &other) const { return tile != other.tile || index != other.index; } + bool operator<(const BelId &other) const + { + return tile < other.tile || (tile == other.tile && index < other.index); + } + unsigned int hash() const { return mkhash(tile, index); } +}; + +struct WireId +{ + int32_t tile = -1; + int32_t index = -1; + + WireId() = default; + WireId(int32_t tile, int32_t index) : tile(tile), index(index){}; + + bool operator==(const WireId &other) const { return tile == other.tile && index == other.index; } + bool operator!=(const WireId &other) const { return tile != other.tile || index != other.index; } + bool operator<(const WireId &other) const + { + return tile < other.tile || (tile == other.tile && index < other.index); + } + unsigned int hash() const { return mkhash(tile, index); } +}; + +struct PipId +{ + int32_t tile = -1; + // PIP index in tile + int32_t index = -1; + + PipId() = default; + PipId(int32_t tile, int32_t index) : tile(tile), index(index){}; + + bool operator==(const PipId &other) const { return tile == other.tile && index == other.index; } + bool operator!=(const PipId &other) const { return tile != other.tile || index != other.index; } + bool operator<(const PipId &other) const + { + return tile < other.tile || (tile == other.tile && index < other.index); + } + unsigned int hash() const { return mkhash(tile, index); } +}; + +typedef IdString DecalId; +typedef IdString GroupId; +typedef IdString BelBucketId; +typedef IdString ClusterId; + +struct ArchNetInfo +{ + int flat_index; +}; + +struct ArchCellInfo : BaseClusterInfo +{ + int flat_index; + dict> cell_bel_pins; +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/himbaechel/chipdb.h b/himbaechel/chipdb.h new file mode 100644 index 0000000000..07cf09ae23 --- /dev/null +++ b/himbaechel/chipdb.h @@ -0,0 +1,235 @@ + +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat + * + * 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 HIMBAECHEL_CHIPDB_H +#define HIMBAECHEL_CHIPDB_H + +#include "archdefs.h" +#include "nextpnr_namespaces.h" +#include "relptr.h" + +NEXTPNR_NAMESPACE_BEGIN + +NPNR_PACKED_STRUCT(struct BelPinPOD { + int32_t name; + int32_t wire; + int32_t type; +}); + +NPNR_PACKED_STRUCT(struct BelDataPOD { + int32_t name; + int32_t bel_type; + + // General placement + int16_t z; + int16_t padding; + + // flags [7..0] are for himbaechel use, [31..8] are for user use + uint32_t flags; + static constexpr uint32_t FLAG_GLOBAL = 0x01; + static constexpr uint32_t FLAG_HIDDEN = 0x02; + + // These are really 64-bits of general data, with some names intended to be vaguely helpful... + int32_t site; + int32_t checker_idx; + + RelSlice pins; + RelPtr extra_data; +}); + +NPNR_PACKED_STRUCT(struct BelPinRefPOD { + int32_t bel; + int32_t pin; +}); + +NPNR_PACKED_STRUCT(struct TileWireDataPOD { + int32_t name; + int32_t wire_type; + int32_t flags; // 32 bits of arbitrary data + RelSlice pips_uphill; + RelSlice pips_downhill; + RelSlice bel_pins; +}); + +NPNR_PACKED_STRUCT(struct PipDataPOD { + int32_t src_wire; + int32_t dst_wire; + + uint32_t type; + uint32_t flags; + int32_t timing_idx; +}); + +NPNR_PACKED_STRUCT(struct RelTileWireRefPOD { + int16_t dx; + int16_t dy; + int16_t wire; +}); + +NPNR_PACKED_STRUCT(struct NodeShapePOD { + RelSlice tile_wires; + int32_t timing_index; +}); + +NPNR_PACKED_STRUCT(struct TileTypePOD { + int32_t type_name; + RelSlice bels; + RelSlice wires; + RelSlice pips; + RelPtr extra_data; +}); + +NPNR_PACKED_STRUCT(struct RelNodeRefPOD { + // wire is entirely internal to a single tile + static constexpr int16_t MODE_TILE_WIRE = 0x7000; + // where this is the root {wire, dy} form the node shape index + static constexpr int16_t MODE_IS_ROOT = 0x7001; + // special cases for the global constant nets + static constexpr int16_t MODE_ROW_CONST = 0x7002; + static constexpr int16_t MODE_GLB_CONST = 0x7003; + // special cases where the user needs to outsmart the deduplication [0x7010, 0x7FFF] + static constexpr int16_t MODE_USR_BEGIN = 0x7010; + int16_t dx_mode; // relative X-coord, or a special value + int16_t dy; // normally, relative Y-coord + uint16_t wire; // normally, node index in tile (x+dx, y+dy) +}); + +NPNR_PACKED_STRUCT(struct TileRoutingShapePOD { + RelSlice wire_to_node; + int32_t timing_index; +}); + +NPNR_PACKED_STRUCT(struct PadInfoPOD { + // package pin name + int32_t package_pin; + // reference to corresponding bel + int32_t tile; + int32_t bel; + // function name + int32_t pad_function; + // index of pin bank + int32_t pad_bank; + // extra pad flags + uint32_t flags; + RelPtr extra_data; +}); + +NPNR_PACKED_STRUCT(struct PackageInfoPOD { + int32_t name; + RelSlice pads; +}); + +NPNR_PACKED_STRUCT(struct TileInstPOD { + int32_t name_prefix; + int32_t type; + int32_t shape; + + RelPtr extra_data; +}); + +NPNR_PACKED_STRUCT(struct TimingValue { + int32_t fast_min; + int32_t fast_max; + int32_t slow_min; + int32_t slow_max; +}); + +NPNR_PACKED_STRUCT(struct BelPinTimingPOD { + TimingValue in_cap; + TimingValue drive_res; + TimingValue delay; +}); + +NPNR_PACKED_STRUCT(struct PipTimingPOD { + TimingValue int_delay; + TimingValue in_cap; + TimingValue out_res; + uint32_t flags; + static const uint32_t UNBUFFERED = 0x1; +}); + +NPNR_PACKED_STRUCT(struct NodeTimingPOD { + TimingValue cap; + TimingValue res; + TimingValue delay; +}); + +NPNR_PACKED_STRUCT(struct CellPinRegArcPOD { + int32_t clock; + int32_t edge; + TimingValue setup; + TimingValue hold; + TimingValue clk_q; +}); + +NPNR_PACKED_STRUCT(struct CellPinCombArcPOD { + int32_t input; + TimingValue delay; +}); + +NPNR_PACKED_STRUCT(struct CellPinTimingPOD { + int32_t pin; + RelSlice comb_arcs; + RelSlice reg_arcs; +}); + +NPNR_PACKED_STRUCT(struct CellTimingPOD { + int32_t type; + int32_t variant; + RelSlice pins; +}); + +NPNR_PACKED_STRUCT(struct SpeedGradePOD { + int32_t name; + RelSlice bel_pin_classes; + RelSlice pip_classes; + RelSlice node_classes; + RelSlice cell_types; +}); + +NPNR_PACKED_STRUCT(struct ConstIDDataPOD { + int32_t known_id_count; + RelSlice> bba_ids; +}); + +NPNR_PACKED_STRUCT(struct ChipInfoPOD { + int32_t magic; + int32_t version; + int32_t width, height; + + RelPtr uarch; + RelPtr name; + RelPtr generator; + + RelSlice tile_types; + RelSlice tile_insts; + RelSlice node_shapes; + RelSlice tile_shapes; + + RelSlice packages; + RelSlice speed_grades; + + RelPtr extra_constids; + + RelPtr extra_data; +}); + +NEXTPNR_NAMESPACE_END +#endif diff --git a/himbaechel/family.cmake b/himbaechel/family.cmake new file mode 100644 index 0000000000..00524623e2 --- /dev/null +++ b/himbaechel/family.cmake @@ -0,0 +1,7 @@ +set(HIMBAECHEL_UARCHES "example") +foreach(uarch ${HIMBAECHEL_UARCHES}) + aux_source_directory(${family}/uarch/${uarch} HM_UARCH_FILES) + foreach(target ${family_targets}) + target_sources(${target} PRIVATE ${HM_UARCH_FILES}) + endforeach() +endforeach(uarch) diff --git a/himbaechel/himbaechel_api.cc b/himbaechel/himbaechel_api.cc new file mode 100644 index 0000000000..e8d403e3a3 --- /dev/null +++ b/himbaechel/himbaechel_api.cc @@ -0,0 +1,105 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021-23 gatecat + * + * 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 "himbaechel_api.h" +#include "log.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +void HimbaechelAPI::init(Context *ctx) { this->ctx = ctx; } + +void HimbaechelAPI::init_constids(Arch *arch) {} + +std::vector HimbaechelAPI::getCellTypes() const +{ + std::vector result; + // TODO + return result; +} + +BelBucketId HimbaechelAPI::getBelBucketForBel(BelId bel) const { return ctx->getBelType(bel); } + +BelBucketId HimbaechelAPI::getBelBucketForCellType(IdString cell_type) const { return cell_type; } + +bool HimbaechelAPI::isValidBelForCellType(IdString cell_type, BelId bel) const +{ + return ctx->getBelType(bel) == cell_type; +} + +delay_t HimbaechelAPI::estimateDelay(WireId src, WireId dst) const +{ + // TODO: configurable lookahead + int sx, sy, dx, dy; + tile_xy(ctx->chip_info, src.tile, sx, sy); + tile_xy(ctx->chip_info, dst.tile, dx, dy); + return 100 * (std::abs(dx - sx) + std::abs(dy - sy) + 2); +} + +delay_t HimbaechelAPI::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const +{ + Loc src_loc = ctx->getBelLocation(src_bel), dst_loc = ctx->getBelLocation(dst_bel); + return 100 * (std::abs(dst_loc.x - src_loc.x) + std::abs(dst_loc.y - src_loc.y)); +} + +BoundingBox HimbaechelAPI::getRouteBoundingBox(WireId src, WireId dst) const +{ + BoundingBox bb; + int sx, sy, dx, dy; + tile_xy(ctx->chip_info, src.tile, sx, sy); + tile_xy(ctx->chip_info, dst.tile, dx, dy); + bb.x0 = std::min(sx, dx); + bb.y0 = std::min(sy, dy); + bb.x1 = std::max(sx, dx); + bb.y1 = std::max(sy, dy); + return bb; +} + +HimbaechelArch *HimbaechelArch::list_head; +HimbaechelArch::HimbaechelArch(const std::string &name) : name(name) +{ + list_next = HimbaechelArch::list_head; + HimbaechelArch::list_head = this; +} +std::string HimbaechelArch::list() +{ + std::string result; + HimbaechelArch *cursor = HimbaechelArch::list_head; + while (cursor) { + if (!result.empty()) + result += ", "; + result += cursor->name; + cursor = cursor->list_next; + } + return result; +} +std::unique_ptr HimbaechelArch::create(const std::string &name, + const dict &args) +{ + HimbaechelArch *cursor = HimbaechelArch::list_head; + while (cursor) { + if (cursor->name != name) { + cursor = cursor->list_next; + continue; + } + return cursor->create(args); + } + return {}; +} +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/himbaechel_api.h b/himbaechel/himbaechel_api.h new file mode 100644 index 0000000000..be62be2af8 --- /dev/null +++ b/himbaechel/himbaechel_api.h @@ -0,0 +1,127 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021-23 gatecat + * + * 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 HIMBAECHEL_API_H +#define HIMBAECHEL_API_H + +#include "nextpnr_namespaces.h" +#include "nextpnr_types.h" + +NEXTPNR_NAMESPACE_BEGIN + +/* +Himbaechel -- a series of bigger arches + +Himbaechel extends on the existing Viaduct API in nextpnr-generic for smaller, lower-impact architectures by a +deduplicated BBA chipdb format as well as API hooks more suited to such size and complexity devices. + +It allows an arch to programmatically build a set of bels (placement locations) and a routing graph at compile time into +a space-efficient (both disk and runtime RAM) deduplicated database with fast lookups; and then hook into nextpnr's flow +and validity checking rules at runtime with custom C++ code. + +To create a Himbaechel 'uarch', the following are required: + - an implementation of HimbaechelAPI. This should define things like rules for how LUTs and FFs can be placed together +in a SLICE. + - "ahead-of-time" Python scripts to programmatically build a routing graph for the device as well as list of placement +locations, in a way that will become space-efficient + - an instance of a struct deriving from HimbaechelArch - this is how the uarch is discovered. Override create(args) to +create an instance of your HimabaechelAPI implementation. + - these should be within C++ files in a new subfolder of 'himbaechel/uarches/'. Add the name of this subfolder to the +list of HIMBAECHEL_UARCHES in family.cmake if building in-tree. + +For an example of how these pieces fit together; see 'himbaechel/uarches/example' which implements a small synthetic +architecture using this framework. + +*/ + +struct Arch; +struct Context; + +struct PlacerHeapCfg; + +struct HimbaechelAPI +{ + virtual void init(Context *ctx); + // If constids are being used, this is used to set them up early before loading the db blob + virtual void init_constids(Arch *arch); + Context *ctx; + bool with_gui = false; + + // --- Bel functions --- + // Called when a bel is placed/unplaced (with cell=nullptr for a unbind) + virtual void notifyBelChange(BelId bel, CellInfo *cell) {} + // This only needs to return false if a bel is disabled for a microarch-specific reason and not just because it's + // bound (which the base generic will deal with) + virtual bool checkBelAvail(BelId bel) const { return true; } + // Mirror the ArchAPI functions - see archapi.md + virtual std::vector getCellTypes() const; + virtual BelBucketId getBelBucketForBel(BelId bel) const; + virtual BelBucketId getBelBucketForCellType(IdString cell_type) const; + virtual bool isValidBelForCellType(IdString cell_type, BelId bel) const; + virtual bool isBelLocationValid(BelId bel, bool explain_invalid = false) const { return true; } + + // --- Wire and pip functions --- + // Called when a wire/pip is placed/unplaced (with net=nullptr for a unbind) + virtual void notifyWireChange(WireId wire, NetInfo *net) {} + virtual void notifyPipChange(PipId pip, NetInfo *net) {} + // These only need to return false if a wire/pip is disabled for a microarch-specific reason and not just because + // it's bound (which the base arch will deal with) + virtual bool checkWireAvail(WireId wire) const { return true; } + virtual bool checkPipAvail(PipId pip) const { return true; } + virtual bool checkPipAvailForNet(PipId pip, const NetInfo *net) const { return checkPipAvail(pip); }; + + // --- Route lookahead --- + virtual delay_t estimateDelay(WireId src, WireId dst) const; + virtual delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const; + virtual BoundingBox getRouteBoundingBox(WireId src, WireId dst) const; + + // Cell->bel pin mapping + virtual bool map_cell_bel_pins(CellInfo *cell) const { return false; } + + // --- Flow hooks --- + virtual void pack(){}; // replaces the pack function + // Called before and after main placement and routing + virtual void prePlace(){}; + virtual void postPlace(){}; + virtual void preRoute(){}; + virtual void postRoute(){}; + + // For custom placer configuration + virtual void configurePlacerHeap(PlacerHeapCfg &cfg){}; + + virtual ~HimbaechelAPI(){}; +}; + +struct HimbaechelArch +{ + static HimbaechelArch *list_head; + HimbaechelArch *list_next = nullptr; + + std::string name; + HimbaechelArch(const std::string &name); + ~HimbaechelArch(){}; + virtual std::unique_ptr create(const dict &args) = 0; + + static std::string list(); + static std::unique_ptr create(const std::string &name, const dict &args); +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/himbaechel/himbaechel_constids.h b/himbaechel/himbaechel_constids.h new file mode 100644 index 0000000000..3a5ec2548f --- /dev/null +++ b/himbaechel/himbaechel_constids.h @@ -0,0 +1,74 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat + * + * 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 HIMBAECHEL_CONSTIDS_H +#define HIMBAECHEL_CONSTIDS_H + +/* +This enables use of 'constids' similar to a 'true' nextpnr arch in a HIMBAECHEL uarch. +To use: + - create a 'constids.inc' file in your uarch folder containing one ID per line; inside X( ) + - set the HIMBAECHEL_CONSTIDS macro to the path to this file relative to the generic arch base + - in your main file; also define GEN_INIT_CONSTIDS to create init_uarch_constids(Context*) which you should call in +init + - include this file +*/ + +#include "nextpnr_namespaces.h" + +#ifdef HIMBAECHEL_MAIN +#include "idstring.h" +#endif + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +#ifndef Q_MOC_RUN +enum ConstIds +{ + ID_NONE +#define X(t) , ID_##t +#include HIMBAECHEL_CONSTIDS +#undef X + , +}; + +#define X(t) static constexpr auto id_##t = IdString(ID_##t); +#include HIMBAECHEL_CONSTIDS +#undef X +#endif + +#ifdef GEN_INIT_CONSTIDS + +void init_uarch_constids(Arch *arch) +{ +#define X(t) IdString::initialize_add(arch, #t, ID_##t); + +#include HIMBAECHEL_CONSTIDS + +#undef X +} + +#endif + +} // namespace + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/himbaechel/himbaechel_dbgen/bba.py b/himbaechel/himbaechel_dbgen/bba.py new file mode 100644 index 0000000000..de9dca31db --- /dev/null +++ b/himbaechel/himbaechel_dbgen/bba.py @@ -0,0 +1,26 @@ +class BBAWriter: + def __init__(self, f): + self.f = f + def pre(self, s): + print(f"pre {s}", file=self.f) + def post(self, s): + print(f"post {s}", file=self.f) + def push(self, s): + print(f"push {s}", file=self.f) + def ref(self, r, comment=""): + print(f"ref {r} {comment}", file=self.f) + def slice(self, r, size, comment=""): + print(f"ref {r} {comment}", file=self.f) + print(f"u32 {size}", file=self.f) + def str(self, s, comment=""): + print(f"str |{s}| {comment}", file=self.f) + def label(self, s): + print(f"label {s}", file=self.f) + def u8(self, n, comment=""): + print(f"u8 {n} {comment}", file=self.f) + def u16(self, n, comment=""): + print(f"u16 {n} {comment}", file=self.f) + def u32(self, n, comment=""): + print(f"u32 {n} {comment}", file=self.f) + def pop(self): + print("pop", file=self.f) diff --git a/himbaechel/himbaechel_dbgen/chip.py b/himbaechel/himbaechel_dbgen/chip.py new file mode 100644 index 0000000000..c3e4a8ae0d --- /dev/null +++ b/himbaechel/himbaechel_dbgen/chip.py @@ -0,0 +1,519 @@ +from dataclasses import dataclass, field +from .bba import BBAWriter +from enum import Enum +from typing import Optional +import abc +import struct, hashlib + +""" +This provides a semi-flattened routing graph that is built into a deduplicated one. + +There are two key elements: + - Tile Types: + these represent a unique kind of grid location in terms of its contents: + - bels (logic functionality like LUTs, FFs, IOs, IP, etc) + - internal wires (excluding connectivity to other tiles) + - pips that switch internal wires + - Nodes + these merge tile-internal wires across wires to create inter-tile connectivity + so, for example, a length-4 wire might connect (x, y, "E4AI") and (x+3, y, "E4AO") +""" + +class BBAStruct(abc.ABC): + def serialise_lists(self, context: str, bba: BBAWriter): + pass + def serialise(self, context: str, bba: BBAWriter): + pass + +@dataclass(eq=True, frozen=True) +class IdString: + index: int = 0 + +class StringPool: + def __init__(self): + self.strs = {"": 0} + self.known_id_count = 1 + + def read_constids(self, file: str): + idx = 1 + with open(file, "r") as f: + for line in f: + l = line.strip() + if not l.startswith("X("): + continue + l = l[2:] + assert l.endswith(")"), l + l = l[:-1].strip() + i = self.id(l) + assert i.index == idx, (i, idx, l) + idx += 1 + self.known_id_count = idx + + def id(self, val: str): + if val in self.strs: + return IdString(self.strs[val]) + else: + idx = len(self.strs) + self.strs[val] = idx + return IdString(idx) + + def serialise_lists(self, context: str, bba: BBAWriter): + bba.label(f"{context}_strs") + for s, idx in sorted(self.strs.items(), key=lambda x: x[1]): # sort by index + if idx < self.known_id_count: + continue + bba.str(s) + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.known_id_count) + bba.slice(f"{context}_strs", len(self.strs) - self.known_id_count) + +@dataclass +class PinType(Enum): + INPUT = 0 + OUTPUT = 1 + INOUT = 2 + +@dataclass +class BelPin(BBAStruct): + name: IdString + wire: int + dir: PinType + + def serialise_lists(self, context: str, bba: BBAWriter): + pass + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.name.index) + bba.u32(self.wire) + bba.u32(self.dir.value) + +BEL_FLAG_GLOBAL = 0x01 +BEL_FLAG_HIDDEN = 0x02 + +@dataclass +class BelData(BBAStruct): + index: int + name: IdString + bel_type: IdString + z: int + + flags: int = 0 + site: int = 0 + checker_idx: int = 0 + + pins: list[BelPin] = field(default_factory=list) + extra_data: object = None + + def serialise_lists(self, context: str, bba: BBAWriter): + # sort pins for fast binary search lookups + self.pins.sort(key=lambda p: p.name.index) + # write pins array + bba.label(f"{context}_pins") + for i, pin in enumerate(self.pins): + pin.serialise(f"{context}_pin{i}", bba) + # extra data (optional) + if self.extra_data is not None: + bba.label(f"{context}_extra_data") + self.extra_data.serialise(f"{context}_extra_data", bba) + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.name.index) + bba.u32(self.bel_type.index) + bba.u16(self.z) + bba.u16(0) + bba.u32(self.flags) + bba.u32(self.site) + bba.u32(self.checker_idx) + bba.slice(f"{context}_pins", len(self.pins)) + if self.extra_data is not None: + bba.ref(f"{context}_extra_data") + else: + bba.u32(0) + +@dataclass +class BelPinRef(BBAStruct): + bel: int + pin: IdString + def serialise_lists(self, context: str, bba: BBAWriter): + pass + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.bel) + bba.u32(self.pin.index) + +@dataclass +class TileWireData: + index: int + name: IdString + wire_type: IdString + flags: int = 0 + + # not serialised, but used to build the global constant networks + const_val: int = -1 + + # these crossreferences will be updated by finalise(), no need to manually update + pips_uphill: list[int] = field(default_factory=list) + pips_downhill: list[int] = field(default_factory=list) + bel_pins: list[BelPinRef] = field(default_factory=list) + + def serialise_lists(self, context: str, bba: BBAWriter): + bba.label(f"{context}_pips_uh") + for pip_idx in self.pips_uphill: + bba.u32(pip_idx) + bba.label(f"{context}_pips_dh") + for pip_idx in self.pips_downhill: + bba.u32(pip_idx) + bba.label(f"{context}_bel_pins") + for i, bel_pin in enumerate(self.bel_pins): + bel_pin.serialise(f"{context}_bp{i}", bba) + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.name.index) + bba.u32(self.wire_type.index) + bba.u32(self.flags) + bba.slice(f"{context}_pips_uh", len(self.pips_uphill)) + bba.slice(f"{context}_pips_dh", len(self.pips_downhill)) + bba.slice(f"{context}_bel_pins", len(self.bel_pins)) + +@dataclass +class PipData(BBAStruct): + index: int + src_wire: int + dst_wire: int + pip_type: IdString = field(default_factory=IdString) + flags: int = 0 + timing_idx: int = -1 + def serialise_lists(self, context: str, bba: BBAWriter): + pass + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.src_wire) + bba.u32(self.dst_wire) + bba.u32(self.pip_type.index) + bba.u32(self.flags) + bba.u32(self.timing_idx) + +@dataclass +class TileType(BBAStruct): + strs: StringPool + type_name: IdString + bels: list[BelData] = field(default_factory=list) + pips: list[PipData] = field(default_factory=list) + wires: list[TileWireData] = field(default_factory=list) + + _wire2idx: dict[IdString, int] = field(default_factory=dict) + + extra_data: object = None + + def create_bel(self, name: str, type: str, z: int): + # Create a new bel of a given name, type and z (index within the tile) in the tile type + bel = BelData(index=len(self.bels), + name=self.strs.id(name), + bel_type=self.strs.id(type), + z=z) + self.bels.append(bel) + return bel + def add_bel_pin(self, bel: BelData, pin: str, wire: str, dir: PinType): + # Add a pin with associated wire to a bel. The wire should exist already. + pin_id = self.strs.id(pin) + wire_idx = self._wire2idx[self.strs.id(wire)] + bel.pins.append(BelPin(pin_id, wire_idx, dir)) + self.wires[wire_idx].bel_pins.append(BelPinRef(bel.index, pin_id)) + + def create_wire(self, name: str, type: str=""): + # Create a new tile wire of a given name and type (optional) in the tile type + wire = TileWireData(index=len(self.wires), + name=self.strs.id(name), + wire_type=self.strs.id(type)) + self._wire2idx[wire.name] = wire.index + self.wires.append(wire) + return wire + def create_pip(self, src: str, dst: str): + # Create a pip between two tile wires in the tile type. Both wires should exist already. + src_idx = self._wire2idx[self.strs.id(src)] + dst_idx = self._wire2idx[self.strs.id(dst)] + pip = PipData(index=len(self.pips), src_wire=src_idx, dst_wire=dst_idx) + self.wires[src_idx].pips_downhill.append(pip.index) + self.wires[dst_idx].pips_uphill.append(pip.index) + self.pips.append(pip) + return pip + def has_wire(self, wire: str): + # Check if a wire has already been created + return self.strs.id(wire) in self._wire2idx + def serialise_lists(self, context: str, bba: BBAWriter): + # list children of members + for i, bel in enumerate(self.bels): + bel.serialise_lists(f"{context}_bel{i}", bba) + for i, wire in enumerate(self.wires): + wire.serialise_lists(f"{context}_wire{i}", bba) + for i, pip in enumerate(self.pips): + pip.serialise_lists(f"{context}_pip{i}", bba) + # lists of members + bba.label(f"{context}_bels") + for i, bel in enumerate(self.bels): + bel.serialise(f"{context}_bel{i}", bba) + bba.label(f"{context}_wires") + for i, wire in enumerate(self.wires): + wire.serialise(f"{context}_wire{i}", bba) + bba.label(f"{context}_pips") + for i, pip in enumerate(self.pips): + pip.serialise(f"{context}_pip{i}", bba) + # extra data (optional) + if self.extra_data is not None: + bba.label(f"{context}_extra_data") + self.extra_data.serialise(f"{context}_extra_data", bba) + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.type_name.index) + bba.slice(f"{context}_bels", len(self.bels)) + bba.slice(f"{context}_wires", len(self.wires)) + bba.slice(f"{context}_pips", len(self.pips)) + if self.extra_data is not None: + bba.ref(f"{context}_extra_data") + else: + bba.u32(0) + +# Pre deduplication (nodes flattened, absolute coords) +@dataclass +class NodeWire: + x: int + y: int + wire: str + +# Post deduplication (node shapes merged, relative coords) +@dataclass +class TileWireRef(BBAStruct): + dx: int + dy: int + wire: int + + def serialise_lists(self, context: str, bba: BBAWriter): + pass + def serialise(self, context: str, bba: BBAWriter): + bba.u16(self.dx) + bba.u16(self.dy) + bba.u16(self.wire) + +@dataclass +class NodeShape(BBAStruct): + wires: list[TileWireRef] = field(default_factory=list) + def key(self): + m = hashlib.sha1() + for wire in self.wires: + m.update(wire.dx.to_bytes(2, 'little', signed=True)) + m.update(wire.dy.to_bytes(2, 'little', signed=True)) + m.update(wire.wire.to_bytes(2, 'little')) + return m.digest() + + def serialise_lists(self, context: str, bba: BBAWriter): + bba.label(f"{context}_wires") + for i, w in enumerate(self.wires): + w.serialise(f"{context}_w{i}", bba) + if len(self.wires) % 2 != 0: + bba.u16(0) # alignment + def serialise(self, context: str, bba: BBAWriter): + bba.slice(f"{context}_wires", len(self.wires)) + bba.u32(-1) # timing index (not yet used) + +MODE_TILE_WIRE = 0x7000 +MODE_IS_ROOT = 0x7001 +MODE_ROW_CONST = 0x7002 +MODE_GLB_CONST = 0x7003 + +@dataclass +class RelNodeRef(BBAStruct): + dx_mode: int = MODE_TILE_WIRE + dy: int = 0 + wire: int = 0 + def serialise_lists(self, context: str, bba: BBAWriter): + pass + def serialise(self, context: str, bba: BBAWriter): + bba.u16(self.dx_mode) + bba.u16(self.dy) + bba.u16(self.wire) + +@dataclass +class TileRoutingShape(BBAStruct): + wire_to_node: list[RelNodeRef] = field(default_factory=list) + def key(self): + m = hashlib.sha1() + for wire in self.wire_to_node: + m.update(wire.dx_mode.to_bytes(2, 'little', signed=True)) + m.update(wire.dy.to_bytes(2, 'little', signed=(wire.dy < 0))) + m.update(wire.wire.to_bytes(2, 'little', signed=True)) + return m.digest() + + def serialise_lists(self, context: str, bba: BBAWriter): + bba.label(f"{context}_w2n") + for i, w in enumerate(self.wire_to_node): + w.serialise(f"{context}_w{i}", bba) + if len(self.wire_to_node) % 2 != 0: + bba.u16(0) # alignment + def serialise(self, context: str, bba: BBAWriter): + bba.slice(f"{context}_w2n", len(self.wire_to_node)) + bba.u32(-1) # timing index + +@dataclass +class TileInst(BBAStruct): + x: int + y: int + type_idx: Optional[int] = None + name_prefix: IdString = field(default_factory=IdString) + loc_type: int = 0 + shape: TileRoutingShape = field(default_factory=TileRoutingShape) + shape_idx: int = -1 + extra_data: object = None + + def serialise_lists(self, context: str, bba: BBAWriter): + if self.extra_data is not None: + self.extra_data.serialise_lists(f"{context}_extra_data", bba) + bba.label(f"{context}_extra_data") + self.extra_data.serialise(f"{context}_extra_data", bba) + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.name_prefix.index) + bba.u32(self.type_idx) + bba.u32(self.shape_idx) + if self.extra_data is not None: + bba.ref(f"{context}_extra_data") + else: + bba.u32(0) +class Chip: + def __init__(self, uarch: str, name: str, width: int, height: int): + self.strs = StringPool() + self.uarch = uarch + self.name = name + self.width = width + self.height = height + self.tile_types = [] + self.tiles = [[TileInst(x, y) for x in range(width)] for y in range(height)] + self.tile_type_idx = dict() + self.node_shapes = [] + self.node_shape_idx = dict() + self.tile_shapes = [] + self.tile_shapes_idx = dict() + self.extra_data = None + def create_tile_type(self, name: str): + tt = TileType(self.strs, self.strs.id(name)) + self.tile_type_idx[name] = len(self.tile_types) + self.tile_types.append(tt) + return tt + def set_tile_type(self, x: int, y: int, type: str): + self.tiles[y][x].type_idx = self.tile_type_idx[type] + def tile_type_at(self, x: int, y: int): + assert self.tiles[y][x].type_idx is not None, f"tile type at ({x}, {y}) must be set" + return self.tile_types[self.tiles[y][x].type_idx] + def add_node(self, wires: list[NodeWire]): + # add a node - joining between multiple tile wires into a single connection (from nextpnr's point of view) + # all the tile wires must exist, and the tile types must be set, first + x0 = wires[0].x + y0 = wires[0].y + # compute node shape + shape = NodeShape() + for w in wires: + wire_id = w.wire if w.wire is IdString else self.strs.id(w.wire) + shape.wires.append(TileWireRef( + dx=w.x-x0, dy=w.y-y0, + wire=self.tile_type_at(w.x, w.y)._wire2idx[wire_id] + )) + # deduplicate node shapes + key = shape.key() + if key in self.node_shape_idx: + shape_idx = self.node_shape_idx[key] + else: + shape_idx = len(self.node_shapes) + self.node_shape_idx[key] = shape_idx + self.node_shapes.append(shape) + # update tile wire to node ref + for i, w in enumerate(wires): + inst = self.tiles[w.y][w.x] + wire_idx = shape.wires[i].wire + # make sure there's actually enough space; first + if wire_idx >= len(inst.shape.wire_to_node): + inst.shape.wire_to_node += [RelNodeRef() for k in range(len(inst.shape.wire_to_node), wire_idx+1)] + if i == 0: + # root of the node. we don't need to back-reference anything because the node is based here + # so we re-use the structure to store the index of the node shape, instead + assert inst.shape.wire_to_node[wire_idx].dx_mode == MODE_TILE_WIRE, "attempting to add wire to multiple nodes!" + inst.shape.wire_to_node[wire_idx] = RelNodeRef(MODE_IS_ROOT, (shape_idx & 0xFFFF), ((shape_idx >> 16) & 0xFFFF)) + else: + # back-reference to the root of the node + dx = x0 - w.x + dy = y0 - w.y + assert dx < MODE_TILE_WIRE, "dx range causes overlap with magic values!" + assert inst.shape.wire_to_node[wire_idx].dx_mode == MODE_TILE_WIRE, "attempting to add wire to multiple nodes!" + inst.shape.wire_to_node[wire_idx] = RelNodeRef(dx, dy, shape.wires[0].wire) + + def flatten_tile_shapes(self): + for row in self.tiles: + for tile in row: + key = tile.shape.key() + if key in self.tile_shapes_idx: + tile.shape_idx = self.tile_shapes_idx[key] + else: + tile.shape_idx = len(self.tile_shapes) + self.tile_shapes.append(tile.shape) + self.tile_shapes_idx[key] = tile.shape_idx + print(f"{len(self.tile_shapes)} unique tile routing shapes") + + def serialise(self, bba: BBAWriter): + self.flatten_tile_shapes() + # TODO: preface, etc + # Lists that make up the database + for i, tt in enumerate(self.tile_types): + tt.serialise_lists(f"tt{i}", bba) + for i, shp in enumerate(self.node_shapes): + shp.serialise_lists(f"nshp{i}", bba) + for i, tsh in enumerate(self.tile_shapes): + tsh.serialise_lists(f"tshp{i}", bba) + for y, row in enumerate(self.tiles): + for x, tinst in enumerate(row): + tinst.serialise_lists(f"tinst_{x}_{y}", bba) + + self.strs.serialise_lists(f"constids", bba) + + bba.label(f"tile_types") + for i, tt in enumerate(self.tile_types): + tt.serialise(f"tt{i}", bba) + bba.label(f"node_shapes") + for i, shp in enumerate(self.node_shapes): + shp.serialise(f"nshp{i}", bba) + bba.label(f"tile_shapes") + for i, tsh in enumerate(self.tile_shapes): + tsh.serialise(f"tshp{i}", bba) + bba.label(f"tile_insts") + for y, row in enumerate(self.tiles): + for x, tinst in enumerate(row): + tinst.serialise(f"tinst_{x}_{y}", bba) + + bba.label(f"constids") + self.strs.serialise(f"constids", bba) + + bba.label("chip_info") + bba.u32(0x00ca7ca7) # magic + bba.u32(1) # version (TODO) + bba.u32(self.width) + bba.u32(self.height) + + bba.str(self.uarch) + bba.str(self.name) + bba.str("python_dbgen") # generator + + bba.slice("tile_types", len(self.tile_types)) + bba.slice("tile_insts", self.width*self.height) + bba.slice("node_shapes", len(self.node_shapes)) + bba.slice("tile_shapes", len(self.tile_shapes)) + # packages: not yet used + bba.u32(0) + bba.u32(0) + # speed grades: not yet used + bba.u32(0) + bba.u32(0) + # db-defined constids + bba.ref("constids") + # extra data: not yet used + bba.u32(0) + + def write_bba(self, filename): + with open(filename, "w") as f: + bba = BBAWriter(f) + bba.pre('#include \"nextpnr.h\"') + bba.pre('NEXTPNR_NAMESPACE_BEGIN') + bba.post('NEXTPNR_NAMESPACE_END') + bba.push('chipdb_blob') + bba.ref('chip_info') + self.serialise(bba) + bba.pop() diff --git a/himbaechel/himbaechel_helpers.cc b/himbaechel/himbaechel_helpers.cc new file mode 100644 index 0000000000..4ce478bf86 --- /dev/null +++ b/himbaechel/himbaechel_helpers.cc @@ -0,0 +1,141 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat + * + * 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 "himbaechel_helpers.h" +#include "design_utils.h" +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +void HimbaechelHelpers::remove_nextpnr_iobs(const pool &top_ports) +{ + std::vector to_remove; + for (auto &cell : ctx->cells) { + auto &ci = *cell.second; + if (!ci.type.in(ctx->id("$nextpnr_ibuf"), ctx->id("$nextpnr_obuf"), ctx->id("$nextpnr_iobuf"))) + continue; + NetInfo *i = ci.getPort(ctx->id("I")); + if (i && i->driver.cell) { + if (!top_ports.count(CellTypePort(i->driver))) + log_error("Top-level port '%s' driven by illegal port %s.%s\n", ctx->nameOf(&ci), + ctx->nameOf(i->driver.cell), ctx->nameOf(i->driver.port)); + } + NetInfo *o = ci.getPort(ctx->id("O")); + if (o) { + for (auto &usr : o->users) { + if (!top_ports.count(CellTypePort(usr))) + log_error("Top-level port '%s' driving illegal port %s.%s\n", ctx->nameOf(&ci), + ctx->nameOf(usr.cell), ctx->nameOf(usr.port)); + } + } + ci.disconnectPort(ctx->id("I")); + ci.disconnectPort(ctx->id("O")); + to_remove.push_back(ci.name); + } + for (IdString cell_name : to_remove) + ctx->cells.erase(cell_name); +} + +int HimbaechelHelpers::constrain_cell_pairs(const pool &src_ports, const pool &sink_ports, + int delta_z, bool allow_fanout) +{ + int constrained = 0; + for (auto &cell : ctx->cells) { + auto &ci = *cell.second; + if (ci.cluster != ClusterId()) + continue; // don't constrain already-constrained cells + bool done = false; + for (auto &port : ci.ports) { + // look for starting source ports + if (port.second.type != PORT_OUT || !port.second.net) + continue; + if (!src_ports.count(CellTypePort(ci.type, port.first))) + continue; + if (!allow_fanout && port.second.net->users.entries() > 1) + continue; + for (auto &usr : port.second.net->users) { + if (!sink_ports.count(CellTypePort(usr))) + continue; + if (usr.cell->cluster != ClusterId()) + continue; + // Add the constraint + ci.cluster = ci.name; + ci.constr_abs_z = false; + ci.constr_children.push_back(usr.cell); + usr.cell->cluster = ci.name; + usr.cell->constr_x = 0; + usr.cell->constr_y = 0; + usr.cell->constr_z = delta_z; + usr.cell->constr_abs_z = false; + ++constrained; + done = true; + break; + } + if (done) + break; + } + } + return constrained; +} + +void HimbaechelHelpers::replace_constants(CellTypePort vcc_driver, CellTypePort gnd_driver, + const dict &vcc_params, + const dict &gnd_params) +{ + CellInfo *vcc_drv = ctx->createCell(ctx->id("$PACKER_VCC_DRV"), vcc_driver.cell_type); + vcc_drv->addOutput(vcc_driver.port); + for (auto &p : vcc_params) + vcc_drv->params[p.first] = p.second; + + CellInfo *gnd_drv = ctx->createCell(ctx->id("$PACKER_GND_DRV"), gnd_driver.cell_type); + gnd_drv->addOutput(gnd_driver.port); + for (auto &p : gnd_params) + gnd_drv->params[p.first] = p.second; + + NetInfo *vcc_net = ctx->createNet(ctx->id("$PACKER_VCC")); + NetInfo *gnd_net = ctx->createNet(ctx->id("$PACKER_GND")); + + vcc_drv->connectPort(vcc_driver.port, vcc_net); + gnd_drv->connectPort(gnd_driver.port, gnd_net); + + std::vector trim_cells; + std::vector trim_nets; + for (auto &net : ctx->nets) { + auto &ni = *net.second; + if (!ni.driver.cell) + continue; + if (ni.driver.cell->type != ctx->id("GND") && ni.driver.cell->type != ctx->id("VCC")) + continue; + NetInfo *replace = (ni.driver.cell->type == ctx->id("VCC")) ? vcc_net : gnd_net; + for (auto &usr : ni.users) { + usr.cell->ports.at(usr.port).net = replace; + usr.cell->ports.at(usr.port).user_idx = replace->users.add(usr); + } + trim_cells.push_back(ni.driver.cell->name); + trim_nets.push_back(ni.name); + } + for (IdString cell_name : trim_cells) + ctx->cells.erase(cell_name); + for (IdString net_name : trim_nets) + ctx->nets.erase(net_name); +} + +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/himbaechel_helpers.h b/himbaechel/himbaechel_helpers.h new file mode 100644 index 0000000000..b2a41b656c --- /dev/null +++ b/himbaechel/himbaechel_helpers.h @@ -0,0 +1,75 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat + * + * 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 HIMBAECHEL_HELPERS_H +#define HIMBAECHEL_HELPERS_H + +#include "nextpnr_namespaces.h" +#include "nextpnr_types.h" + +NEXTPNR_NAMESPACE_BEGIN + +/* +Himbächel -- a series of small arches + +See himbaechel_api.h for more background. + +himbaechel_helpers provides some features for building up arches using the himbächel API +*/ + +// Used to configure various generic pack functions +struct CellTypePort +{ + CellTypePort() : cell_type(), port(){}; + CellTypePort(IdString cell_type, IdString port) : cell_type(cell_type), port(port){}; + explicit CellTypePort(const PortRef &net_port) + : cell_type(net_port.cell ? net_port.cell->type : IdString()), port(net_port.port){}; + inline bool operator==(const CellTypePort &other) const + { + return cell_type == other.cell_type && port == other.port; + } + inline bool operator!=(const CellTypePort &other) const + { + return cell_type != other.cell_type || port != other.port; + } + inline unsigned hash() const { return mkhash(cell_type.hash(), port.hash()); } + IdString cell_type, port; +}; + +struct HimbaechelHelpers +{ + HimbaechelHelpers(){}; + Context *ctx; + void init(Context *ctx) { this->ctx = ctx; } + // Common packing functions + // Remove nextpnr-inserted IO buffers; where IO buffer insertion is done in synthesis + // expects a set of top-level port types + void remove_nextpnr_iobs(const pool &top_ports); + // Constrain cells with certain port connection patterns together with a fixed z-offset + int constrain_cell_pairs(const pool &src_ports, const pool &sink_ports, int delta_z, + bool allow_fanout = true); + // Replace constants with given driving cells + void replace_constants(CellTypePort vcc_driver, CellTypePort gnd_driver, + const dict &vcc_params = {}, + const dict &gnd_params = {}); +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/himbaechel/main.cc b/himbaechel/main.cc new file mode 100644 index 0000000000..64c2c0f3f8 --- /dev/null +++ b/himbaechel/main.cc @@ -0,0 +1,97 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia 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 HimbaechelCommandHandler : public CommandHandler +{ + public: + HimbaechelCommandHandler(int argc, char **argv); + virtual ~HimbaechelCommandHandler(){}; + std::unique_ptr createContext(dict &values) override; + void setupArchContext(Context *ctx) override{}; + void customBitstream(Context *ctx) override; + + protected: + po::options_description getArchOptions() override; +}; + +HimbaechelCommandHandler::HimbaechelCommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {} + +po::options_description HimbaechelCommandHandler::getArchOptions() +{ + std::string all_uarches = HimbaechelArch::list(); + std::string uarch_help = stringf("himbächel micro-arch to use (available: %s)", all_uarches.c_str()); + po::options_description specific("Architecture specific options"); + specific.add_options()("uarch", po::value(), uarch_help.c_str()); + specific.add_options()("chipdb", po::value(), "path to chip database file"); + specific.add_options()("vopt,o", po::value>(), "options to pass to the himbächel uarch"); + + return specific; +} + +void HimbaechelCommandHandler::customBitstream(Context *ctx) {} + +std::unique_ptr HimbaechelCommandHandler::createContext(dict &values) +{ + ArchArgs chipArgs; + if (values.find("arch.name") != values.end()) { + std::string arch_name = values["arch.name"].as_string(); + if (arch_name != "himbaechel") + log_error("Unsupported architecture '%s'.\n", arch_name.c_str()); + } + if (!vm.count("uarch")) + log_error("uarch must be specified\n"); + if (!vm.count("chipdb")) + log_error("chip database path must be specified.\n"); + chipArgs.uarch = vm["uarch"].as(); + chipArgs.chipdb = vm["chipdb"].as(); + if (vm.count("vopt")) { + std::vector options = vm["vopt"].as>(); + for (const auto &opt : options) { + size_t epos = opt.find('='); + if (epos == std::string::npos) + chipArgs.options[opt] = ""; + else + chipArgs.options[opt.substr(0, epos)] = opt.substr(epos + 1); + } + } + auto ctx = std::unique_ptr(new Context(chipArgs)); + if (vm.count("gui")) + ctx->uarch->with_gui = true; + ctx->uarch->init(ctx.get()); + ctx->late_init(); + return ctx; +} + +int main(int argc, char *argv[]) +{ + HimbaechelCommandHandler handler(argc, argv); + return handler.exec(); +} + +#endif diff --git a/himbaechel/uarch/example/blinky.v b/himbaechel/uarch/example/blinky.v new file mode 100644 index 0000000000..e26c2fae56 --- /dev/null +++ b/himbaechel/uarch/example/blinky.v @@ -0,0 +1,16 @@ +module top(input rst, output reg [7:0] leds); + +wire clk; + +(* BEL="X1Y0/IO0" *) INBUF ib_i (.O(clk)); + +reg [7:0] ctr; +always @(posedge clk) + if (rst) + ctr <= 8'h00; + else + ctr <= ctr + 1'b1; + +assign leds = ctr; + +endmodule diff --git a/himbaechel/uarch/example/constids.inc b/himbaechel/uarch/example/constids.inc new file mode 100644 index 0000000000..b40d5be8a0 --- /dev/null +++ b/himbaechel/uarch/example/constids.inc @@ -0,0 +1,14 @@ +X(LUT4) +X(DFF) +X(CLK) +X(D) +X(F) +X(Q) +X(INBUF) +X(OUTBUF) +X(I) +X(EN) +X(O) +X(IOB) +X(PAD) +X(INIT) diff --git a/himbaechel/uarch/example/example.cc b/himbaechel/uarch/example/example.cc new file mode 100644 index 0000000000..5f5d1087dc --- /dev/null +++ b/himbaechel/uarch/example/example.cc @@ -0,0 +1,144 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2023 gatecat + * + * 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 "himbaechel_api.h" +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +#include "himbaechel_helpers.h" + +#define GEN_INIT_CONSTIDS +#define HIMBAECHEL_CONSTIDS "uarch/example/constids.inc" +#include "himbaechel_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +struct ExampleImpl : HimbaechelAPI +{ + + static constexpr int K = 4; + + ~ExampleImpl(){}; + void init_constids(Arch *arch) override { init_uarch_constids(arch); } + void init(Context *ctx) override + { + h.init(ctx); + HimbaechelAPI::init(ctx); + } + + void prePlace() override { assign_cell_info(); } + + void pack() override + { + // Trim nextpnr IOBs - assume IO buffer insertion has been done in synthesis + const pool top_ports{ + CellTypePort(id_INBUF, id_PAD), + CellTypePort(id_OUTBUF, id_PAD), + }; + h.remove_nextpnr_iobs(top_ports); + // Replace constants with LUTs + const dict vcc_params = {{id_INIT, Property(0xFFFF, 16)}}; + const dict gnd_params = {{id_INIT, Property(0x0000, 16)}}; + h.replace_constants(CellTypePort(id_LUT4, id_F), CellTypePort(id_LUT4, id_F), vcc_params, gnd_params); + // Constrain directly connected LUTs and FFs together to use dedicated resources + int lutffs = h.constrain_cell_pairs(pool{{id_LUT4, id_F}}, pool{{id_DFF, id_D}}, 1); + log_info("Constrained %d LUTFF pairs.\n", lutffs); + } + + bool isBelLocationValid(BelId bel, bool explain_invalid) const override + { + Loc l = ctx->getBelLocation(bel); + if (ctx->getBelType(bel).in(id_LUT4, id_DFF)) { + return slice_valid(l.x, l.y, l.z / 2); + } else { + return true; + } + } + + // Bel bucket functions + IdString getBelBucketForCellType(IdString cell_type) const override + { + if (cell_type.in(id_INBUF, id_OUTBUF)) + return id_IOB; + return cell_type; + } + bool isValidBelForCellType(IdString cell_type, BelId bel) const override + { + IdString bel_type = ctx->getBelType(bel); + if (bel_type == id_IOB) + return cell_type.in(id_INBUF, id_OUTBUF); + else + return (bel_type == cell_type); + } + + private: + HimbaechelHelpers h; + + // Validity checking + struct ExampleCellInfo + { + const NetInfo *lut_f = nullptr, *ff_d = nullptr; + bool lut_i3_used = false; + }; + std::vector fast_cell_info; + void assign_cell_info() + { + fast_cell_info.resize(ctx->cells.size()); + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + auto &fc = fast_cell_info.at(ci->flat_index); + if (ci->type == id_LUT4) { + fc.lut_f = ci->getPort(id_F); + fc.lut_i3_used = (ci->getPort(ctx->idf("I[%d]", K - 1)) != nullptr); + } else if (ci->type == id_DFF) { + fc.ff_d = ci->getPort(id_D); + } + } + } + bool slice_valid(int x, int y, int z) const + { + const CellInfo *lut = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z * 2))); + const CellInfo *ff = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z * 2 + 1))); + if (!lut || !ff) + return true; // always valid if only LUT or FF used + const auto &lut_data = fast_cell_info.at(lut->flat_index); + const auto &ff_data = fast_cell_info.at(ff->flat_index); + // In our example arch; the FF D can either be driven from LUT F or LUT I3 + // so either; FF D must equal LUT F or LUT I3 must be unused + if (ff_data.ff_d == lut_data.lut_f) + return true; + if (lut_data.lut_i3_used) + return false; + return true; + } +}; + +struct ExampleArch : HimbaechelArch +{ + ExampleArch() : HimbaechelArch("example"){}; + std::unique_ptr create(const dict &args) + { + return std::make_unique(); + } +} exampleArch; +} // namespace + +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/example/example_arch_gen.py b/himbaechel/uarch/example/example_arch_gen.py new file mode 100644 index 0000000000..026c2551aa --- /dev/null +++ b/himbaechel/uarch/example/example_arch_gen.py @@ -0,0 +1,216 @@ +from os import path +import sys +sys.path.append(path.join(path.dirname(__file__), "../..")) +from himbaechel_dbgen.chip import * + +# Grid size including IOBs at edges +X = 100 +Y = 100 +# LUT input count +K = 4 +# SLICEs per tile +N = 8 +# number of local wires +Wl = N * (K + 1) + 16 +# 1/Fc for bel input wire pips; local wire pips and neighbour pips +Si = 6 +Sq = 6 +Sl = 1 + +dirs = [ # name, dx, dy + ("N", 0, -1), + ("NE", 1, -1), + ("E", 1, 0), + ("SE", 1, 1), + ("S", 0, 1), + ("SW", -1, 1), + ("W", -1, 0), + ("NW", -1, -1) +] + +def create_switch_matrix(tt: TileType, inputs: list[str], outputs: list[str]): + # FIXME: terrible routing matrix, just for a toy example... + # switch wires + for i in range(Wl): + tt.create_wire(f"SWITCH{i}", "SWITCH") + # neighbor wires + for i in range(Wl): + for d, dx, dy in dirs: + tt.create_wire(f"{d}{i}", f"NEIGH_{d}") + # input pips + for i, w in enumerate(inputs): + for j in range((i % Si), Wl, Si): + tt.create_pip(f"SWITCH{j}", w) + # output pips + for i, w in enumerate(outputs): + for j in range((i % Sq), Wl, Sq): + tt.create_pip(w, f"SWITCH{j}") + # neighbour local pips + for i in range(Wl): + for j, (d, dx, dy) in enumerate(dirs): + tt.create_pip(f"{d}{(i + j) % Wl}", f"SWITCH{i}") + # clock "ladder" + if not tt.has_wire("CLK"): + tt.create_wire(f"CLK", "TILE_CLK") + tt.create_wire(f"CLK_PREV", "CLK_ROUTE") + tt.create_pip(f"CLK_PREV", f"CLK") + +def create_logic_tiletype(chip: Chip): + tt = chip.create_tile_type("LOGIC") + # setup wires + inputs = [] + outputs = [] + for i in range(N): + for j in range(K): + inputs.append(f"L{i}_I{j}") + tt.create_wire(f"L{i}_I{j}", "LUT_INPUT") + tt.create_wire(f"L{i}_D", "FF_DATA") + tt.create_wire(f"L{i}_O", "LUT_OUT") + tt.create_wire(f"L{i}_Q", "FF_OUT") + outputs += [f"L{i}_O", f"L{i}_Q"] + tt.create_wire(f"CLK", "TILE_CLK") + # create logic cells + for i in range(N): + # LUT + lut = tt.create_bel(f"L{i}_LUT", "LUT4", z=(i*2 + 0)) + for j in range(K): + tt.add_bel_pin(lut, f"I[{j}]", f"L{i}_I{j}", PinType.INPUT) + tt.add_bel_pin(lut, "F", f"L{i}_O", PinType.OUTPUT) + # FF data can come from LUT output or LUT I3 + tt.create_pip(f"L{i}_O", f"L{i}_D") + tt.create_pip(f"L{i}_I{K-1}", f"L{i}_D") + # FF + ff = tt.create_bel(f"L{i}_FF", "DFF", z=(i*2 + 1)) + tt.add_bel_pin(ff, "D", f"L{i}_D", PinType.INPUT) + tt.add_bel_pin(ff, "CLK", "CLK", PinType.INPUT) + tt.add_bel_pin(ff, "Q", f"L{i}_Q", PinType.OUTPUT) + create_switch_matrix(tt, inputs, outputs) + return tt + +N_io = 2 + +def create_io_tiletype(chip: Chip): + tt = chip.create_tile_type("IO") + # setup wires + inputs = [] + outputs = [] + for i in range(N_io): + tt.create_wire(f"IO{i}_T", "IO_T") + tt.create_wire(f"IO{i}_I", "IO_I") + tt.create_wire(f"IO{i}_O", "IO_O") + tt.create_wire(f"IO{i}_PAD", "IO_PAD") + inputs += [f"IO{i}_T", f"IO{i}_I"] + outputs += [f"IO{i}_O", ] + tt.create_wire(f"CLK", "TILE_CLK") + for i in range(N_io): + io = tt.create_bel(f"IO{i}", "IOB", z=i) + tt.add_bel_pin(io, "I", f"IO{i}_I", PinType.INPUT) + tt.add_bel_pin(io, "T", f"IO{i}_T", PinType.INPUT) + tt.add_bel_pin(io, "O", f"IO{i}_O", PinType.OUTPUT) + tt.add_bel_pin(io, "PAD", f"IO{i}_PAD", PinType.INOUT) + # Actually used in top left IO only + tt.create_wire("GCLK_OUT", "GCLK") + tt.create_pip("IO0_O", "GCLK_OUT") + create_switch_matrix(tt, inputs, outputs) + return tt + +def create_bram_tiletype(chip: Chip): + Aw = 9 + Dw = 16 + + tt = chip.create_tile_type("BRAM") + inputs = [f"RAM_WA{i}" for i in range(Aw)] + inputs += [f"RAM_RA{i}" for i in range(Aw)] + inputs += [f"RAM_WE{i}" for i in range(Dw // 8)] + inputs += [f"RAM_DI{i}" for i in range(Dw)] + outputs = [f"RAM_DO{i}" for i in range(Dw)] + for w in inputs: + tt.create_wire(w, "RAM_IN") + for w in outputs: + tt.create_wire(w, "RAM_OUT") + tt.create_wire(f"CLK", "TILE_CLK") + ram = tt.create_bel(f"RAM", F"BRAM_{2**Aw}X{Dw}", z=0) + tt.add_bel_pin(ram, "CLK", f"CLK", PinType.INPUT) + for i in range(Aw): + tt.add_bel_pin(ram, f"WA[{i}]", f"RAM_WA{i}", PinType.INPUT) + tt.add_bel_pin(ram, f"RA[{i}]", f"RAM_RA{i}", PinType.INPUT) + for i in range(Dw//8): + tt.add_bel_pin(ram, f"WE[{i}]", f"RAM_WE{i}", PinType.INPUT) + for i in range(Dw): + tt.add_bel_pin(ram, f"DI[{i}]", f"RAM_DI{i}", PinType.INPUT) + tt.add_bel_pin(ram, f"DO[{i}]", f"RAM_DO{i}", PinType.OUTPUT) + create_switch_matrix(tt, inputs, outputs) + return tt + +def create_corner_tiletype(ch): + tt = ch.create_tile_type("NULL") + tt.create_wire(f"CLK", "TILE_CLK") + tt.create_wire(f"CLK_PREV", "CLK_ROUTE") + tt.create_pip(f"CLK_PREV", f"CLK") + return tt + +def is_corner(x, y): + return ((x == 0) or (x == (X-1))) and ((y == 0) or (y == (Y-1))) + +def create_nodes(ch): + for y in range(Y): + print(f"generating nodes for row {y}") + for x in range(X): + if not is_corner(x, y): + # connect up actual neighbours + local_nodes = [[NodeWire(x, y, f"SWITCH{i}")] for i in range(Wl)] + for d, dx, dy in dirs: + x1 = x - dx + y1 = y - dy + if x1 < 0 or x1 >= X or y1 < 0 or y1 >= Y or is_corner(x1, y1): + continue + for i in range(Wl): + local_nodes[i].append(NodeWire(x1, y1, f"{d}{i}")) + for n in local_nodes: + ch.add_node(n) + # connect up clock ladder (not intended to be a sensible clock structure) + if y != 1: # special case where the node has 3 wires + if y == 0: + if x == 0: + # clock source: IO + clk_node = [NodeWire(1, 0, "GCLK_OUT")] + else: + # clock source: left + clk_node = [NodeWire(x-1, y, "CLK")] + else: + # clock source: above + clk_node = [NodeWire(x, y-1, "CLK")] + clk_node.append(NodeWire(x, y, "CLK_PREV")) + if y == 0: + clk_node.append(NodeWire(x, y+1, "CLK_PREV")) + ch.add_node(clk_node) + + + +def main(): + ch = Chip("example", "EX1", X, Y) + # Init constant ids + ch.strs.read_constids(path.join(path.dirname(__file__), "constids.inc")) + logic = create_logic_tiletype(ch) + io = create_io_tiletype(ch) + bram = create_bram_tiletype(ch) + null = create_corner_tiletype(ch) + # Setup tile grid + for x in range(X): + for y in range(Y): + if x == 0 or x == X-1: # left/right side IO + if y == 0 or y == Y-1: # corner + ch.set_tile_type(x, y, "NULL") + else: + ch.set_tile_type(x, y, "IO") + elif y == 0 or y == Y-1: # top/bottom side IO + ch.set_tile_type(x, y, "IO") + elif (y % 15) == 7: # BRAM + ch.set_tile_type(x, y, "BRAM") + else: + ch.set_tile_type(x, y, "LOGIC") + # Create nodes between tiles + create_nodes(ch) + ch.write_bba(sys.argv[1]) +if __name__ == '__main__': + main()