himbächel: Initial implementation

Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
gatecat 2023-04-20 19:49:36 +02:00 committed by myrtle
parent e3529d3356
commit 57b923a603
28 changed files with 3176 additions and 4 deletions

27
.github/ci/build_himbaechel.sh vendored Normal file
View File

@ -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
}

View File

@ -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

View File

@ -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})

26
docs/himbaechel.md Normal file
View File

@ -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`.

View File

View File

@ -0,0 +1,52 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <micko@yosyshq.com>
*
* 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 <QMessageBox>
#include <cstdlib>
static void initMainResource() { Q_INIT_RESOURCE(nextpnr); }
NEXTPNR_NAMESPACE_BEGIN
MainWindow::MainWindow(std::unique_ptr<Context> 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

View File

@ -0,0 +1,45 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <micko@yosyshq.com>
*
* 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> 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

View File

@ -0,0 +1,2 @@
<RCC>
</RCC>

1
himbaechel/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__pycache__

264
himbaechel/arch.cc Normal file
View File

@ -0,0 +1,264 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021 gatecat <gatecat@ds0.me>
*
* 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<ChipInfoPOD> *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<const char *>(blob_file.data());
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(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<IdString> Arch::getBelPins(BelId bel) const
{
std::vector<IdString> 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<std::string> Arch::availablePlacers = {"sa", "heap"};
const std::string Arch::defaultRouter = "router1";
const std::vector<std::string> Arch::availableRouters = {"router1", "router2"};
NEXTPNR_NAMESPACE_END

682
himbaechel/arch.h Normal file
View File

@ -0,0 +1,682 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021-2023 gatecat <gatecat@ds0.me>
*
*
* 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 <boost/iostreams/device/mapped_file.hpp>
#include <iostream>
#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 <typename Tid, typename Tdata, RelSlice<Tdata> 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<Tid, Tdata, ptr> &other) const
{
return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile;
}
bool operator==(const TileObjIterator<Tid, Tdata, ptr> &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 <typename Tid, typename Tdata, RelSlice<Tdata> TileTypePOD::*ptr> struct TileObjRange
{
using iterator = TileObjIterator<Tid, Tdata, ptr>;
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 <RelSlice<int32_t> 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<ptr> &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 <RelSlice<int32_t> TileWireDataPOD::*ptr> struct UpDownhillPipRange
{
using iterator = UpdownhillPipIterator<ptr>;
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<std::string, std::string> options;
};
typedef TileObjRange<BelId, BelDataPOD, &TileTypePOD::bels> BelRange;
typedef TileObjRange<PipId, PipDataPOD, &TileTypePOD::pips> 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<IdString>;
using CellBelPinRangeT = const std::vector<IdString> &;
// Wires
using AllWiresRangeT = WireRange;
using DownhillPipRangeT = DownhillPipRange;
using UphillPipRangeT = UphillPipRange;
using WireBelPinRangeT = BelPinRange;
// Pips
using AllPipsRangeT = AllPipRange;
};
struct Arch : BaseArch<ArchRanges>
{
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<HimbaechelAPI> 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<IdString> 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<IdString> &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<std::string> availablePlacers;
static const std::string defaultRouter;
static const std::vector<std::string> 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<IdString> tile_name;
dict<IdString, int> tile_name2idx;
};
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,73 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020-23 gatecat <gatecat@ds0.me>
*
* 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_<ArchArgs>(m, "ArchArgs").def_readwrite("chipdb", &ArchArgs::chipdb);
py::class_<BelId>(m, "BelId").def_readwrite("index", &BelId::index);
py::class_<WireId>(m, "WireId").def_readwrite("index", &WireId::index);
py::class_<PipId>(m, "PipId").def_readwrite("index", &PipId::index);
auto arch_cls = py::class_<Arch, BaseCtx>(m, "Arch").def(py::init<ArchArgs>());
auto ctx_cls = py::class_<Context, Arch>(m, "Context")
.def("checksum", &Context::checksum)
.def("pack", &Context::pack)
.def("place", &Context::place)
.def("route", &Context::route);
typedef dict<IdString, std::unique_ptr<CellInfo>> CellMap;
typedef dict<IdString, std::unique_ptr<NetInfo>> NetMap;
typedef dict<IdString, IdString> AliasMap;
typedef dict<IdString, HierarchicalCell> HierarchyMap;
auto belpin_cls = py::class_<ContextualWrapper<BelPin>>(m, "BelPin");
readonly_wrapper<BelPin, decltype(&BelPin::bel), &BelPin::bel, conv_to_str<BelId>>::def_wrap(belpin_cls, "bel");
readonly_wrapper<BelPin, decltype(&BelPin::pin), &BelPin::pin, conv_to_str<IdString>>::def_wrap(belpin_cls, "pin");
typedef const std::vector<BelBucketId> &BelBucketRange;
typedef const std::vector<BelId> &BelRangeForBelBucket;
#include "arch_pybindings_shared.h"
WRAP_RANGE(m, Bel, conv_to_str<BelId>);
WRAP_RANGE(m, Wire, conv_to_str<WireId>);
WRAP_RANGE(m, AllPip, conv_to_str<PipId>);
WRAP_RANGE(m, UphillPip, conv_to_str<PipId>);
WRAP_RANGE(m, DownhillPip, conv_to_str<PipId>);
WRAP_RANGE(m, BelPin, wrap_context<BelPin>);
WRAP_MAP_UPTR(m, CellMap, "IdCellMap");
WRAP_MAP_UPTR(m, NetMap, "IdNetMap");
WRAP_MAP(m, HierarchyMap, wrap_context<HierarchicalCell &>, "HierarchyMap");
}
NEXTPNR_NAMESPACE_END
#endif // NO_PYTHON

View File

@ -0,0 +1,98 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020-23 gatecat <gatecat@ds0.me>
*
* 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>
{
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>
{
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<const WireId>
{
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>
{
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>
{
BelPin from_str(Context *ctx, std::string name)
{
NPNR_ASSERT_FALSE("string_converter<BelPin>::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

106
himbaechel/archdefs.h Normal file
View File

@ -0,0 +1,106 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2023 gatecat <gatecat@ds0.me>
*
* 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 <array>
#include <cstdint>
#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<IdString, std::vector<IdString>> cell_bel_pins;
};
NEXTPNR_NAMESPACE_END
#endif

235
himbaechel/chipdb.h Normal file
View File

@ -0,0 +1,235 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021 gatecat <gatecat@ds0.me>
*
* 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<BelPinPOD> pins;
RelPtr<uint8_t> 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<int32_t> pips_uphill;
RelSlice<int32_t> pips_downhill;
RelSlice<BelPinRefPOD> 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<RelTileWireRefPOD> tile_wires;
int32_t timing_index;
});
NPNR_PACKED_STRUCT(struct TileTypePOD {
int32_t type_name;
RelSlice<BelDataPOD> bels;
RelSlice<TileWireDataPOD> wires;
RelSlice<PipDataPOD> pips;
RelPtr<uint8_t> 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<RelNodeRefPOD> 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<uint8_t> extra_data;
});
NPNR_PACKED_STRUCT(struct PackageInfoPOD {
int32_t name;
RelSlice<PadInfoPOD> pads;
});
NPNR_PACKED_STRUCT(struct TileInstPOD {
int32_t name_prefix;
int32_t type;
int32_t shape;
RelPtr<uint8_t> 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<CellPinCombArcPOD> comb_arcs;
RelSlice<CellPinRegArcPOD> reg_arcs;
});
NPNR_PACKED_STRUCT(struct CellTimingPOD {
int32_t type;
int32_t variant;
RelSlice<CellPinTimingPOD> pins;
});
NPNR_PACKED_STRUCT(struct SpeedGradePOD {
int32_t name;
RelSlice<BelPinTimingPOD> bel_pin_classes;
RelSlice<PipTimingPOD> pip_classes;
RelSlice<NodeTimingPOD> node_classes;
RelSlice<CellTimingPOD> cell_types;
});
NPNR_PACKED_STRUCT(struct ConstIDDataPOD {
int32_t known_id_count;
RelSlice<RelPtr<char>> bba_ids;
});
NPNR_PACKED_STRUCT(struct ChipInfoPOD {
int32_t magic;
int32_t version;
int32_t width, height;
RelPtr<char> uarch;
RelPtr<char> name;
RelPtr<char> generator;
RelSlice<TileTypePOD> tile_types;
RelSlice<TileInstPOD> tile_insts;
RelSlice<NodeShapePOD> node_shapes;
RelSlice<TileRoutingShapePOD> tile_shapes;
RelSlice<PackageInfoPOD> packages;
RelSlice<SpeedGradePOD> speed_grades;
RelPtr<ConstIDDataPOD> extra_constids;
RelPtr<uint8_t> extra_data;
});
NEXTPNR_NAMESPACE_END
#endif

7
himbaechel/family.cmake Normal file
View File

@ -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)

View File

@ -0,0 +1,105 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021-23 gatecat <gatecat@ds0.me>
*
* 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<IdString> HimbaechelAPI::getCellTypes() const
{
std::vector<IdString> 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<HimbaechelAPI> HimbaechelArch::create(const std::string &name,
const dict<std::string, std::string> &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

127
himbaechel/himbaechel_api.h Normal file
View File

@ -0,0 +1,127 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021-23 gatecat <gatecat@ds0.me>
*
* 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<IdString> 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<HimbaechelAPI> create(const dict<std::string, std::string> &args) = 0;
static std::string list();
static std::unique_ptr<HimbaechelAPI> create(const std::string &name, const dict<std::string, std::string> &args);
};
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,74 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021 gatecat <gatecat@ds0.me>
*
* 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

View File

@ -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)

View File

@ -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()

View File

@ -0,0 +1,141 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021 gatecat <gatecat@ds0.me>
*
* 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<CellTypePort> &top_ports)
{
std::vector<IdString> 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<CellTypePort> &src_ports, const pool<CellTypePort> &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<IdString, Property> &vcc_params,
const dict<IdString, Property> &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<IdString> trim_cells;
std::vector<IdString> 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

View File

@ -0,0 +1,75 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021 gatecat <gatecat@ds0.me>
*
* 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<CellTypePort> &top_ports);
// Constrain cells with certain port connection patterns together with a fixed z-offset
int constrain_cell_pairs(const pool<CellTypePort> &src_ports, const pool<CellTypePort> &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<IdString, Property> &vcc_params = {},
const dict<IdString, Property> &gnd_params = {});
};
NEXTPNR_NAMESPACE_END
#endif

97
himbaechel/main.cc Normal file
View File

@ -0,0 +1,97 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Claire Xenia Wolf <claire@yosyshq.com>
*
* 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 <fstream>
#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<Context> createContext(dict<std::string, Property> &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<std::string>(), uarch_help.c_str());
specific.add_options()("chipdb", po::value<std::string>(), "path to chip database file");
specific.add_options()("vopt,o", po::value<std::vector<std::string>>(), "options to pass to the himbächel uarch");
return specific;
}
void HimbaechelCommandHandler::customBitstream(Context *ctx) {}
std::unique_ptr<Context> HimbaechelCommandHandler::createContext(dict<std::string, Property> &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<std::string>();
chipArgs.chipdb = vm["chipdb"].as<std::string>();
if (vm.count("vopt")) {
std::vector<std::string> options = vm["vopt"].as<std::vector<std::string>>();
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<Context>(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

View File

@ -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

View File

@ -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)

View File

@ -0,0 +1,144 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2023 gatecat <gatecat@ds0.me>
*
* 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<CellTypePort> top_ports{
CellTypePort(id_INBUF, id_PAD),
CellTypePort(id_OUTBUF, id_PAD),
};
h.remove_nextpnr_iobs(top_ports);
// Replace constants with LUTs
const dict<IdString, Property> vcc_params = {{id_INIT, Property(0xFFFF, 16)}};
const dict<IdString, Property> 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<CellTypePort>{{id_LUT4, id_F}}, pool<CellTypePort>{{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<ExampleCellInfo> 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<HimbaechelAPI> create(const dict<std::string, std::string> &args)
{
return std::make_unique<ExampleImpl>();
}
} exampleArch;
} // namespace
NEXTPNR_NAMESPACE_END

View File

@ -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()