Merge pull request #707 from gatecat/cyclonev

mistral: Initial Cyclone V support
This commit is contained in:
gatecat 2021-05-15 22:37:19 +01:00 committed by GitHub
commit 47b4e42b1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 4222 additions and 9 deletions

View File

@ -6,13 +6,12 @@ task:
dockerfile: .cirrus/Dockerfile.ubuntu20.04
submodule_script: git submodule sync --recursive && git submodule update --init --recursive
build_script: mkdir build && cd build && cmake .. -DARCH=all+alpha -DOXIDE_INSTALL_PREFIX=$HOME/.cargo -DBUILD_TESTS=on -DBUILD_GUI=on -DWERROR=on && make -j3
build_script: mkdir build && cd build && cmake .. -DARCH='ecp5;generic;gowin;ice40;machxo2;nexus' -DOXIDE_INSTALL_PREFIX=$HOME/.cargo -DBUILD_TESTS=on -DBUILD_GUI=on -DWERROR=on && make -j3
test_generic_script: cd build && ./nextpnr-generic-test
flow_test_generic_script: export NPNR=$(pwd)/build/nextpnr-generic && cd tests/generic/flow && ./run.sh
test_ice40_script: cd build && ./nextpnr-ice40-test
smoketest_ice40_script: export NEXTPNR=$(pwd)/build/nextpnr-ice40 && cd ice40/smoketest/attosoc && ./smoketest.sh
test_ecp5_script: cd build && ./nextpnr-ecp5-test
test_fpga_interchange_script: cd build && ./nextpnr-fpga_interchange-test
smoketest_generic_script: export NEXTPNR=$(pwd)/build/nextpnr-generic && cd generic/examples && ./simple.sh && ./simtest.sh
regressiontest_ice40_script: make -j $(nproc) -C tests/ice40/regressions NPNR=$(pwd)/build/nextpnr-ice40
regressiontest_ecp5_script: make -j $(nproc) -C tests/ecp5/regressions NPNR=$(pwd)/build/nextpnr-ecp5

24
.github/ci/build_mistral.sh vendored Normal file
View File

@ -0,0 +1,24 @@
#!/bin/bash
function get_dependencies {
# Fetch mistral
mkdir -p ${MISTRAL_PATH}
git clone --recursive https://github.com/Ravenslofty/mistral.git ${MISTRAL_PATH}
pushd ${MISTRAL_PATH}
git reset --hard ${MISTRAL_REVISION}
popd
}
function build_nextpnr {
mkdir build
pushd build
cmake .. -DARCH=mistral -DMISTRAL_ROOT=${MISTRAL_PATH}
make nextpnr-mistral -j`nproc`
popd
}
function run_archcheck {
pushd build
./nextpnr-mistral --mistral ${MISTRAL_PATH} --device 5CEBA2F17A7 --test
popd
}

33
.github/workflows/mistral_ci.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: Mistral CI tests
on: [push, pull_request]
jobs:
Build-nextpnr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: actions/setup-python@v2
- 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 clang bison flex swig
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: Execute build nextpnr
env:
MISTRAL_PATH: ${{ github.workspace }}/deps/mistral
MISTRAL_REVISION: 7d4e6d2cca1ec05de3be0c9fef6acaed8089d329
run: |
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
source ./.github/ci/build_mistral.sh
get_dependencies
build_nextpnr
run_archcheck

View File

@ -95,9 +95,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)
set(FAMILIES generic ice40 ecp5 nexus gowin fpga_interchange machxo2 mistral)
set(STABLE_FAMILIES generic ice40 ecp5)
set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange machxo2)
set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange machxo2 mistral)
set(ARCH "" CACHE STRING "Architecture family for nextpnr build")
set_property(CACHE ARCH PROPERTY STRINGS ${FAMILIES})

View File

@ -9,6 +9,7 @@ Currently nextpnr supports:
* Lattice ECP5 devices supported by [Project Trellis](https://github.com/YosysHQ/prjtrellis)
* Lattice Nexus devices supported by [Project Oxide](https://github.com/gatecat/prjoxide)
* Gowin LittleBee devices supported by [Project Apicula](https://github.com/YosysHQ/apicula)
* *(experimental)* Cyclone V devices supported by [Mistral](https://github.com/Ravenslofty/mistral)
* *(experimental)* a "generic" back-end for user-defined architectures
There is some work in progress towards [support for Xilinx devices](https://github.com/gatecat/nextpnr-xilinx/) but it is not upstream and not intended for end users at the present time. We hope to see more FPGA families supported in the future. We would love your help in developing this awesome new project!

View File

@ -28,6 +28,12 @@
USING_NEXTPNR_NAMESPACE
#ifndef ARCH_MISTRAL
// The LRU cache to reduce memory usage during the connectivity check relies on getPips() having some spacial locality,
// which the current CycloneV arch impl doesn't have. This may be fixed in the future, though.
#define USING_LRU_CACHE
#endif
namespace {
void archcheck_names(const Context *ctx)
@ -248,6 +254,11 @@ void archcheck_conn(const Context *ctx)
log_info("Checking all wires...\n");
#ifndef USING_LRU_CACHE
std::unordered_map<PipId, WireId> pips_downhill;
std::unordered_map<PipId, WireId> pips_uphill;
#endif
for (WireId wire : ctx->getWires()) {
for (BelPin belpin : ctx->getWireBelPins(wire)) {
WireId wire2 = ctx->getBelPinWire(belpin.bel, belpin.pin);
@ -257,11 +268,19 @@ void archcheck_conn(const Context *ctx)
for (PipId pip : ctx->getPipsDownhill(wire)) {
WireId wire2 = ctx->getPipSrcWire(pip);
log_assert(wire == wire2);
#ifndef USING_LRU_CACHE
auto result = pips_downhill.emplace(pip, wire);
log_assert(result.second);
#endif
}
for (PipId pip : ctx->getPipsUphill(wire)) {
WireId wire2 = ctx->getPipDstWire(pip);
log_assert(wire == wire2);
#ifndef USING_LRU_CACHE
auto result = pips_uphill.emplace(pip, wire);
log_assert(result.second);
#endif
}
}
@ -285,7 +304,7 @@ void archcheck_conn(const Context *ctx)
log_assert(found_belpin);
}
}
#ifdef USING_LRU_CACHE
// This cache is used to meet two goals:
// - Avoid linear scan by invoking getPipsDownhill/getPipsUphill directly.
// - Avoid having pip -> wire maps for the entire part.
@ -295,16 +314,25 @@ void archcheck_conn(const Context *ctx)
// pip -> wire, assuming that pips are returned from getPips with some
// chip locality.
LruWireCacheMap pip_cache(ctx, /*cache_size=*/64 * 1024);
#endif
log_info("Checking all PIPs...\n");
for (PipId pip : ctx->getPips()) {
WireId src_wire = ctx->getPipSrcWire(pip);
if (src_wire != WireId()) {
#ifdef USING_LRU_CACHE
log_assert(pip_cache.isPipDownhill(pip, src_wire));
#else
log_assert(pips_downhill.at(pip) == src_wire);
#endif
}
WireId dst_wire = ctx->getPipDstWire(pip);
if (dst_wire != WireId()) {
#ifdef USING_LRU_CACHE
log_assert(pip_cache.isPipUphill(pip, dst_wire));
#else
log_assert(pips_uphill.at(pip) == dst_wire);
#endif
}
}
}

View File

@ -75,7 +75,7 @@ typename std::enable_if<std::is_same<Tret, Tc>::value, Tret>::type return_if_mat
}
template <typename Tret, typename Tc>
typename std::enable_if<!std::is_same<Tret, Tc>::value, Tret>::type return_if_match(Tret r)
typename std::enable_if<!std::is_same<Tret, Tc>::value, Tc>::type return_if_match(Tret r)
{
NPNR_ASSERT_FALSE("default implementations of cell type and bel bucket range functions only available when the "
"respective range types are 'const std::vector&'");

View File

@ -58,4 +58,23 @@ std::string IdStringList::str(const Context *ctx) const
return s;
}
IdStringList IdStringList::concat(IdStringList a, IdStringList b)
{
IdStringList result(a.size() + b.size());
for (size_t i = 0; i < a.size(); i++)
result.ids[i] = a[i];
for (size_t i = 0; i < b.size(); i++)
result.ids[a.size() + i] = b[i];
return result;
}
IdStringList IdStringList::slice(size_t s, size_t e) const
{
NPNR_ASSERT(e >= s);
IdStringList result(e - s);
for (size_t i = 0; i < result.size(); i++)
result.ids[i] = ids[s + i];
return result;
}
NEXTPNR_NAMESPACE_END

View File

@ -64,6 +64,9 @@ struct IdStringList
}
return false;
}
static IdStringList concat(IdStringList a, IdStringList b);
IdStringList slice(size_t s, size_t e) const;
};
NEXTPNR_NAMESPACE_END

View File

@ -710,10 +710,12 @@ struct Router2
if (is_bb && !hit_test_pip(nd.bb, ctx->getPipLocation(dh)))
continue;
if (!ctx->checkPipAvailForNet(dh, net)) {
#if 0
ROUTE_LOG_DBG("Skipping pip %s because it is bound to net '%s' not net '%s'\n", ctx->nameOfPip(dh),
ctx->getBoundPipNet(dh) != nullptr ? ctx->getBoundPipNet(dh)->name.c_str(ctx)
: "<not a net>",
net->name.c_str(ctx));
#endif
continue;
}
#endif
@ -833,9 +835,6 @@ struct Router2
// Ripup failed arcs to start with
// Check if arc is already legally routed
if (check_arc_routing(net, i, j)) {
#if 0
ROUTE_LOG_DBG("Arc '%s' (user %zu, arc %zu) already routed skipping.\n", ctx->nameOf(net), i, j);
#endif
continue;
}
@ -902,10 +901,14 @@ struct Router2
++net_data.fail_count;
if ((net_data.fail_count % 3) == 0) {
// Every three times a net fails to route, expand the bounding box to increase the search space
#ifndef ARCH_MISTRAL
// This patch seems to make thing worse for CycloneV, as it slows down the resolution of TD congestion,
// disable it
net_data.bb.x0 = std::max(net_data.bb.x0 - 1, 0);
net_data.bb.y0 = std::max(net_data.bb.y0 - 1, 0);
net_data.bb.x1 = std::min(net_data.bb.x1 + 1, ctx->getGridDimX());
net_data.bb.y1 = std::min(net_data.bb.y1 + 1, ctx->getGridDimY());
#endif
}
}
}

1
gui/mistral/family.cmake Normal file
View File

@ -0,0 +1 @@
target_include_directories(gui_mistral PRIVATE ${MISTRAL_ROOT}/lib)

86
gui/mistral/mainwindow.cc Normal file
View File

@ -0,0 +1,86 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.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 <QAction>
#include <QFileDialog>
#include <QFileInfo>
#include <QIcon>
#include <QInputDialog>
#include <QLineEdit>
#include <QSet>
#include <fstream>
#include "design_utils.h"
#include "log.h"
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();
std::string title = "nextpnr-mistral - [EMPTY]";
setWindowTitle(title.c_str());
connect(this, &BaseMainWindow::contextChanged, this, &MainWindow::newContext);
createMenu();
}
MainWindow::~MainWindow() {}
void MainWindow::createMenu()
{
// Add arch specific actions
// Add actions in menus
}
void MainWindow::new_proj()
{
QStringList arch;
// TODO: better device picker
arch.push_back("5CSEBA6U23I7");
bool ok;
QString item = QInputDialog::getItem(this, "Select new context", "Chip:", arch, 0, false, &ok);
if (ok && !item.isEmpty()) {
ArchArgs chipArgs;
chipArgs.device = item.toStdString();
ctx = std::unique_ptr<Context>(new Context(chipArgs));
actionLoadJSON->setEnabled(true);
Q_EMIT contextChanged(ctx.get());
}
}
void MainWindow::newContext(Context *ctx)
{
std::string title = "nextpnr-nexus - " + ctx->getChipName();
setWindowTitle(title.c_str());
}
void MainWindow::onDisableActions() {}
void MainWindow::onUpdateActions() {}
NEXTPNR_NAMESPACE_END

49
gui/mistral/mainwindow.h Normal file
View File

@ -0,0 +1,49 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.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();
void onDisableActions() override;
void onUpdateActions() override;
protected Q_SLOTS:
void new_proj() override;
void newContext(Context *ctx);
};
NEXTPNR_NAMESPACE_END
#endif // MAINWINDOW_H

2
gui/mistral/nextpnr.qrc Normal file
View File

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

487
mistral/arch.cc Normal file
View File

@ -0,0 +1,487 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021 Lofty <dan.ravensloft@gmail.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 <algorithm>
#include "log.h"
#include "nextpnr.h"
#include "placer1.h"
#include "placer_heap.h"
#include "router1.h"
#include "router2.h"
#include "timing.h"
#include "util.h"
#include "cyclonev.h"
NEXTPNR_NAMESPACE_BEGIN
using namespace mistral;
void IdString::initialize_arch(const BaseCtx *ctx)
{
#define X(t) initialize_add(ctx, #t, ID_##t);
#include "constids.inc"
#undef X
}
Arch::Arch(ArchArgs args)
{
this->args = args;
this->cyclonev = mistral::CycloneV::get_model(args.device, args.mistral_root);
NPNR_ASSERT(this->cyclonev != nullptr);
// Setup fast identifier maps
for (int i = 0; i < 1024; i++) {
IdString int_id = id(stringf("%d", i));
int2id.push_back(int_id);
id2int[int_id] = i;
}
for (int t = int(CycloneV::NONE); t <= int(CycloneV::DCMUX); t++) {
IdString rnode_id = id(CycloneV::rnode_type_names[t]);
rn_t2id.push_back(rnode_id);
id2rn_t[rnode_id] = CycloneV::rnode_type_t(t);
}
log_info("Initialising bels...\n");
bels_by_tile.resize(cyclonev->get_tile_sx() * cyclonev->get_tile_sy());
for (int x = 0; x < cyclonev->get_tile_sx(); x++) {
for (int y = 0; y < cyclonev->get_tile_sy(); y++) {
CycloneV::pos_t pos = cyclonev->xy2pos(x, y);
for (CycloneV::block_type_t bel : cyclonev->pos_get_bels(pos)) {
switch (bel) {
case CycloneV::block_type_t::LAB:
create_lab(x, y);
break;
default:
continue;
}
}
}
}
for (auto gpio_pos : cyclonev->gpio_get_pos())
create_gpio(CycloneV::pos2x(gpio_pos), CycloneV::pos2y(gpio_pos));
for (auto cmuxh_pos : cyclonev->cmuxh_get_pos())
create_clkbuf(CycloneV::pos2x(cmuxh_pos), CycloneV::pos2y(cmuxh_pos));
// This import takes about 5s, perhaps long term we can speed it up, e.g. defer to Mistral more...
log_info("Initialising routing graph...\n");
int pip_count = 0;
for (const auto &mux : cyclonev->dest_node_to_rmux) {
const auto &rmux = cyclonev->rmux_info[mux.second];
WireId dst_wire(mux.first);
for (const auto &src : rmux.sources) {
if (CycloneV::rn2t(src) == CycloneV::NONE)
continue;
WireId src_wire(src);
wires[dst_wire].wires_uphill.push_back(src_wire);
wires[src_wire].wires_downhill.push_back(dst_wire);
++pip_count;
}
}
log_info(" imported %d wires and %d pips\n", int(wires.size()), pip_count);
BaseArch::init_cell_types();
BaseArch::init_bel_buckets();
}
int Arch::getTileBelDimZ(int x, int y) const
{
// This seems like a reasonable upper bound
return 256;
}
BelId Arch::getBelByName(IdStringList name) const
{
BelId bel;
NPNR_ASSERT(name.size() == 4);
int x = id2int.at(name[1]);
int y = id2int.at(name[2]);
int z = id2int.at(name[3]);
bel.pos = CycloneV::xy2pos(x, y);
bel.z = z;
NPNR_ASSERT(name[0] == getBelType(bel));
return bel;
}
IdStringList Arch::getBelName(BelId bel) const
{
int x = CycloneV::pos2x(bel.pos);
int y = CycloneV::pos2y(bel.pos);
int z = bel.z & 0xFF;
std::array<IdString, 4> ids{
getBelType(bel),
int2id.at(x),
int2id.at(y),
int2id.at(z),
};
return IdStringList(ids);
}
bool Arch::isBelLocationValid(BelId bel) const
{
auto &data = bel_data(bel);
if (data.type == id_MISTRAL_COMB) {
return is_alm_legal(data.lab_data.lab, data.lab_data.alm) && check_lab_input_count(data.lab_data.lab);
} else if (data.type == id_MISTRAL_FF) {
return is_alm_legal(data.lab_data.lab, data.lab_data.alm) && check_lab_input_count(data.lab_data.lab) &&
is_lab_ctrlset_legal(data.lab_data.lab);
}
return true;
}
void Arch::update_bel(BelId bel)
{
auto &data = bel_data(bel);
if (data.type == id_MISTRAL_COMB || data.type == id_MISTRAL_FF) {
update_alm_input_count(data.lab_data.lab, data.lab_data.alm);
}
}
WireId Arch::getWireByName(IdStringList name) const
{
// non-mistral wires
auto found_npnr = npnr_wirebyname.find(name);
if (found_npnr != npnr_wirebyname.end())
return found_npnr->second;
// mistral wires
NPNR_ASSERT(name.size() == 4);
CycloneV::rnode_type_t ty = id2rn_t.at(name[0]);
int x = id2int.at(name[1]);
int y = id2int.at(name[2]);
int z = id2int.at(name[3]);
return WireId(CycloneV::rnode(ty, x, y, z));
}
IdStringList Arch::getWireName(WireId wire) const
{
if (wire.is_nextpnr_created()) {
// non-mistral wires
std::array<IdString, 4> ids{
id_WIRE,
int2id.at(CycloneV::rn2x(wire.node)),
int2id.at(CycloneV::rn2y(wire.node)),
wires.at(wire).name_override,
};
return IdStringList(ids);
} else {
std::array<IdString, 4> ids{
rn_t2id.at(CycloneV::rn2t(wire.node)),
int2id.at(CycloneV::rn2x(wire.node)),
int2id.at(CycloneV::rn2y(wire.node)),
int2id.at(CycloneV::rn2z(wire.node)),
};
return IdStringList(ids);
}
}
PipId Arch::getPipByName(IdStringList name) const
{
WireId src = getWireByName(name.slice(0, 4));
WireId dst = getWireByName(name.slice(4, 8));
NPNR_ASSERT(src != WireId());
NPNR_ASSERT(dst != WireId());
return PipId(src.node, dst.node);
}
IdStringList Arch::getPipName(PipId pip) const
{
return IdStringList::concat(getWireName(getPipSrcWire(pip)), getWireName(getPipDstWire(pip)));
}
std::vector<BelId> Arch::getBelsByTile(int x, int y) const
{
// This should probably be redesigned, but it's a hack.
std::vector<BelId> bels;
if (x >= 0 && x < cyclonev->get_tile_sx() && y >= 0 && y < cyclonev->get_tile_sy()) {
for (size_t i = 0; i < bels_by_tile.at(pos2idx(x, y)).size(); i++)
bels.push_back(BelId(CycloneV::xy2pos(x, y), i));
}
return bels;
}
IdString Arch::getBelType(BelId bel) const { return bel_data(bel).type; }
std::vector<IdString> Arch::getBelPins(BelId bel) const
{
std::vector<IdString> pins;
for (auto &p : bel_data(bel).pins)
pins.push_back(p.first);
return pins;
}
bool Arch::isValidBelForCellType(IdString cell_type, BelId bel) const
{
// Any combinational cell type can - theoretically - be placed at a combinational ALM bel
// The precise legality mechanics will be dealt with in isBelLocationValid.
IdString bel_type = getBelType(bel);
if (bel_type == id_MISTRAL_COMB)
return is_comb_cell(cell_type);
else if (bel_type == id_MISTRAL_IO)
return is_io_cell(cell_type);
else if (bel_type == id_MISTRAL_CLKENA)
return is_clkbuf_cell(cell_type);
else
return bel_type == cell_type;
}
BelBucketId Arch::getBelBucketForCellType(IdString cell_type) const
{
if (is_comb_cell(cell_type))
return id_MISTRAL_COMB;
else if (is_io_cell(cell_type))
return id_MISTRAL_IO;
else if (is_clkbuf_cell(cell_type))
return id_MISTRAL_CLKENA;
else
return cell_type;
}
BelId Arch::bel_by_block_idx(int x, int y, IdString type, int block_index) const
{
auto &bels = bels_by_tile.at(pos2idx(x, y));
for (size_t i = 0; i < bels.size(); i++) {
auto &bel_data = bels.at(i);
if (bel_data.type == type && bel_data.block_index == block_index)
return BelId(CycloneV::xy2pos(x, y), i);
}
return BelId();
}
BelId Arch::add_bel(int x, int y, IdString name, IdString type)
{
auto &bels = bels_by_tile.at(pos2idx(x, y));
BelId id = BelId(CycloneV::xy2pos(x, y), bels.size());
all_bels.push_back(id);
bels.emplace_back();
auto &bel = bels.back();
bel.name = name;
bel.type = type;
// TODO: buckets (for example LABs and MLABs in the same bucket)
bel.bucket = type;
return id;
}
WireId Arch::add_wire(int x, int y, IdString name, uint64_t flags)
{
std::array<IdString, 4> ids{
id_WIRE,
int2id.at(x),
int2id.at(y),
name,
};
IdStringList full_name(ids);
auto existing = npnr_wirebyname.find(full_name);
if (existing != npnr_wirebyname.end()) {
// Already exists, don't create anything
return existing->second;
} else {
// Determine a unique ID for the wire
int z = 0;
WireId id;
while (wires.count(id = WireId(CycloneV::rnode(CycloneV::rnode_type_t((z >> 10) + 128), x, y, (z & 0x3FF)))))
z++;
wires[id].name_override = name;
wires[id].flags = flags;
npnr_wirebyname[full_name] = id;
return id;
}
}
void Arch::reserve_route(WireId src, WireId dst)
{
auto &dst_data = wires.at(dst);
int idx = -1;
for (int i = 0; i < int(dst_data.wires_uphill.size()); i++) {
if (dst_data.wires_uphill.at(i) == src) {
idx = i;
break;
}
}
NPNR_ASSERT(idx != -1);
dst_data.flags = WireInfo::RESERVED_ROUTE | unsigned(idx);
}
bool Arch::wires_connected(WireId src, WireId dst) const
{
PipId pip(src.node, dst.node);
return getBoundPipNet(pip) != nullptr;
}
PipId Arch::add_pip(WireId src, WireId dst)
{
wires[src].wires_downhill.push_back(dst);
wires[dst].wires_uphill.push_back(src);
return PipId(src.node, dst.node);
}
void Arch::add_bel_pin(BelId bel, IdString pin, PortType dir, WireId wire)
{
auto &b = bel_data(bel);
NPNR_ASSERT(!b.pins.count(pin));
b.pins[pin].dir = dir;
b.pins[pin].wire = wire;
BelPin bel_pin;
bel_pin.bel = bel;
bel_pin.pin = pin;
wires[wire].bel_pins.push_back(bel_pin);
}
void Arch::assign_default_pinmap(CellInfo *cell)
{
for (auto &port : cell->ports) {
auto &pinmap = cell->pin_data[port.first].bel_pins;
if (!pinmap.empty())
continue; // already mapped
if (is_comb_cell(cell->type) && comb_pinmap.count(port.first))
pinmap.push_back(comb_pinmap.at(port.first)); // default comb mapping for placer purposes
else
pinmap.push_back(port.first); // default: assume bel pin named the same as cell pin
}
}
void Arch::assignArchInfo()
{
for (auto cell : sorted(cells)) {
CellInfo *ci = cell.second;
if (is_comb_cell(ci->type))
assign_comb_info(ci);
else if (ci->type == id_MISTRAL_FF)
assign_ff_info(ci);
assign_default_pinmap(ci);
}
}
delay_t Arch::estimateDelay(WireId src, WireId dst) const
{
int x0 = CycloneV::rn2x(src.node);
int y0 = CycloneV::rn2y(src.node);
int x1 = CycloneV::rn2x(dst.node);
int y1 = CycloneV::rn2y(dst.node);
return 100 * std::abs(y1 - y0) + 100 * std::abs(x1 - x0) + 100;
}
ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const
{
ArcBounds bounds;
int src_x = CycloneV::rn2x(src.node);
int src_y = CycloneV::rn2y(src.node);
int dst_x = CycloneV::rn2x(dst.node);
int dst_y = CycloneV::rn2y(dst.node);
bounds.x0 = std::min(src_x, dst_x);
bounds.y0 = std::min(src_y, dst_y);
bounds.x1 = std::max(src_x, dst_x);
bounds.y1 = std::max(src_y, dst_y);
return bounds;
}
delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
{
if (net_info->driver.cell == nullptr || net_info->driver.cell->bel == BelId())
return 100;
if (sink.cell->bel == BelId())
return 100;
Loc src_loc = getBelLocation(net_info->driver.cell->bel);
Loc dst_loc = getBelLocation(sink.cell->bel);
return std::abs(dst_loc.y - src_loc.y) * 100 + std::abs(dst_loc.x - src_loc.x) * 100 + 100;
}
bool Arch::place()
{
std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
if (placer == "heap") {
PlacerHeapCfg cfg(getCtx());
cfg.ioBufTypes.insert(id_MISTRAL_IO);
cfg.ioBufTypes.insert(id_MISTRAL_IB);
cfg.ioBufTypes.insert(id_MISTRAL_OB);
cfg.cellGroups.emplace_back();
cfg.cellGroups.back().insert({id_MISTRAL_COMB});
cfg.cellGroups.back().insert({id_MISTRAL_FF});
cfg.beta = 0.5; // TODO: find a good value of beta for sensible ALM spreading
cfg.criticalityExponent = 7;
if (!placer_heap(getCtx(), cfg))
return false;
} else if (placer == "sa") {
if (!placer1(getCtx(), Placer1Cfg(getCtx())))
return false;
} else {
log_error("Mistral architecture does not support placer '%s'\n", placer.c_str());
}
getCtx()->attrs[getCtx()->id("step")] = std::string("place");
archInfoToAttributes();
return true;
}
bool Arch::route()
{
assign_budget(getCtx(), true);
lab_pre_route();
std::string router = str_or_default(settings, id("router"), defaultRouter);
bool result;
if (router == "router1") {
result = router1(getCtx(), Router1Cfg(getCtx()));
} else if (router == "router2") {
router2(getCtx(), Router2Cfg(getCtx()));
result = true;
} else {
log_error("Mistral architecture does not support router '%s'\n", router.c_str());
}
getCtx()->attrs[getCtx()->id("step")] = std::string("route");
archInfoToAttributes();
return result;
}
#ifdef WITH_HEAP
const std::string Arch::defaultPlacer = "heap";
#else
const std::string Arch::defaultPlacer = "sa";
#endif
const std::vector<std::string> Arch::availablePlacers = {"sa",
#ifdef WITH_HEAP
"heap"
#endif
};
const std::string Arch::defaultRouter = "router2";
const std::vector<std::string> Arch::availableRouters = {"router1", "router2"};
NEXTPNR_NAMESPACE_END

550
mistral/arch.h Normal file
View File

@ -0,0 +1,550 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021 Lofty <dan.ravensloft@gmail.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 MISTRAL_ARCH_H
#define MISTRAL_ARCH_H
#include <set>
#include <sstream>
#include "base_arch.h"
#include "nextpnr_types.h"
#include "relptr.h"
#include "cyclonev.h"
NEXTPNR_NAMESPACE_BEGIN
struct ArchArgs
{
std::string device;
std::string mistral_root;
};
// These structures are used for fast ALM validity checking
struct ALMInfo
{
// Wires, so bitstream gen can determine connectivity
std::array<WireId, 2> comb_out;
std::array<WireId, 2> sel_clk, sel_ena, sel_aclr, sel_ef;
std::array<WireId, 4> ff_in, ff_out;
// Pointers to bels
std::array<BelId, 2> lut_bels;
std::array<BelId, 4> ff_bels;
bool l6_mode = false;
// Which CLK/ENA and ACLR is chosen for each half
std::array<int, 2> clk_ena_idx, aclr_idx;
// For keeping track of how many inputs are currently being used, for the LAB routeability check
int unique_input_count = 0;
};
struct LABInfo
{
std::array<ALMInfo, 10> alms;
// Control set wires
std::array<WireId, 3> clk_wires, ena_wires;
std::array<WireId, 2> aclr_wires;
WireId sclr_wire, sload_wire;
// TODO: LAB configuration (control set etc)
std::array<bool, 2> aclr_used;
};
struct PinInfo
{
WireId wire;
PortType dir;
};
struct BelInfo
{
IdString name;
IdString type;
IdString bucket;
CellInfo *bound = nullptr;
// For cases where we need to determine an original block index, due to multiple bels at the same tile this
// might not be the same as the nextpnr z-coordinate
int block_index;
std::unordered_map<IdString, PinInfo> pins;
// Info for different kinds of bels
union
{
// This enables fast lookup of the associated ALM, etc
struct
{
uint32_t lab; // index into the list of LABs
uint8_t alm; // ALM index inside LAB
uint8_t idx; // LUT or FF index inside ALM
} lab_data;
};
};
// We maintain our own wire data based on mistral's. This gets us the bidirectional linking that nextpnr needs,
// and also makes it easy to add wires and pips for our own purposes like LAB internal routing, global clock
// sources, etc.
struct WireInfo
{
// name_override is only used for nextpnr-created wires
// otherwise; this is empty and a name is created according to mistral rules
IdString name_override;
// these are transformed on-the-fly to PipId by the iterator, to save space (WireId is half the size of PipId)
std::vector<WireId> wires_downhill;
std::vector<WireId> wires_uphill;
std::vector<BelPin> bel_pins;
// flags for special wires (currently unused)
uint64_t flags;
// if the RESERVED_ROUTE mask is set in flags, then only wires_uphill[flags&0xFF] may drive this wire - used for
// control set preallocations
static const uint64_t RESERVED_ROUTE = 0x100;
};
// This transforms a WireIds, and adds the mising half of the pair to create a PipId
using WireVecIterator = std::vector<WireId>::const_iterator;
struct UpDownhillPipIterator
{
WireVecIterator base;
WireId other_wire;
bool is_uphill;
UpDownhillPipIterator(WireVecIterator base, WireId other_wire, bool is_uphill)
: base(base), other_wire(other_wire), is_uphill(is_uphill){};
bool operator!=(const UpDownhillPipIterator &other) { return base != other.base; }
UpDownhillPipIterator operator++()
{
++base;
return *this;
}
UpDownhillPipIterator operator++(int)
{
UpDownhillPipIterator prior(*this);
++(*this);
return prior;
}
PipId operator*() { return is_uphill ? PipId(base->node, other_wire.node) : PipId(other_wire.node, base->node); }
};
struct UpDownhillPipRange
{
UpDownhillPipIterator b, e;
UpDownhillPipRange(const std::vector<WireId> &v, WireId other_wire, bool is_uphill)
: b(v.cbegin(), other_wire, is_uphill), e(v.cend(), other_wire, is_uphill){};
UpDownhillPipIterator begin() const { return b; }
UpDownhillPipIterator end() const { return e; }
};
// This iterates over the list of wires, and for each wire yields its uphill pips, as an efficient way of going over
// all the pips in the device
using WireMapIterator = std::unordered_map<WireId, WireInfo>::const_iterator;
struct AllPipIterator
{
WireMapIterator base, end;
int uphill_idx;
AllPipIterator(WireMapIterator base, WireMapIterator end, int uphill_idx)
: base(base), end(end), uphill_idx(uphill_idx){};
bool operator!=(const AllPipIterator &other) { return base != other.base || uphill_idx != other.uphill_idx; }
AllPipIterator operator++()
{
// Increment uphill list index by one
++uphill_idx;
// We've reached the end of the current wire. Keep incrementing the wire of interest until we find one with
// uphill pips, or we reach the end of the list of wires
while (base != end && uphill_idx >= int(base->second.wires_uphill.size())) {
uphill_idx = 0;
++base;
}
return *this;
}
AllPipIterator operator++(int)
{
AllPipIterator prior(*this);
++(*this);
return prior;
}
PipId operator*() { return PipId(base->second.wires_uphill.at(uphill_idx).node, base->first.node); }
};
struct AllPipRange
{
AllPipIterator b, e;
AllPipRange(const std::unordered_map<WireId, WireInfo> &wires)
: b(wires.cbegin(), wires.cend(), -1), e(wires.cend(), wires.cend(), 0)
{
// Starting the begin iterator at index -1 and incrementing it ensures we skip over the first wire if it has no
// uphill pips
++b;
};
AllPipIterator begin() const { return b; }
AllPipIterator end() const { return e; }
};
// This transforms a map to a range of keys, used as the wire iterator
template <typename T> struct key_range
{
key_range(const T &t) : b(t.cbegin()), e(t.cend()){};
typename T::const_iterator b, e;
struct xformed_iterator : public T::const_iterator
{
explicit xformed_iterator(typename T::const_iterator base) : T::const_iterator(base){};
typename T::key_type operator*() { return this->T::const_iterator::operator*().first; }
};
xformed_iterator begin() const { return xformed_iterator(b); }
xformed_iterator end() const { return xformed_iterator(e); }
};
using AllWireRange = key_range<std::unordered_map<WireId, WireInfo>>;
struct ArchRanges : BaseArchRanges
{
using ArchArgsT = ArchArgs;
// Bels
using AllBelsRangeT = const std::vector<BelId> &;
using TileBelsRangeT = std::vector<BelId>;
using BelPinsRangeT = std::vector<IdString>;
using CellBelPinRangeT = const std::vector<IdString> &;
// Wires
using AllWiresRangeT = AllWireRange;
using DownhillPipRangeT = UpDownhillPipRange;
using UphillPipRangeT = UpDownhillPipRange;
using WireBelPinRangeT = const std::vector<BelPin> &;
// Pips
using AllPipsRangeT = AllPipRange;
};
// This enum captures different 'styles' of cell pins
// This is a combination of the modes available for a pin (tied high, low or inverted)
// and the default value to set it to not connected
enum CellPinStyle
{
PINOPT_NONE = 0x0, // no options, just signal as-is
PINOPT_LO = 0x1, // can be tied low
PINOPT_HI = 0x2, // can be tied high
PINOPT_INV = 0x4, // can be inverted
PINOPT_LOHI = 0x3, // can be tied low or high
PINOPT_LOHIINV = 0x7, // can be tied low or high; or inverted
PINOPT_MASK = 0x7,
PINDEF_NONE = 0x00, // leave disconnected
PINDEF_0 = 0x10, // connect to 0 if not used
PINDEF_1 = 0x20, // connect to 1 if not used
PINDEF_MASK = 0x30,
PINGLB_CLK = 0x100, // pin is a 'clock' for global purposes
PINGLB_MASK = 0x100,
PINSTYLE_NONE = 0x000, // default
PINSTYLE_COMB = 0x017, // combinational signal, defaults low, can be inverted and tied
PINSTYLE_CLK = 0x107, // CLK type signal, invertible and defaults to disconnected
PINSTYLE_CE = 0x027, // CE type signal, invertible and defaults to enabled
PINSTYLE_RST = 0x017, // RST type signal, invertible and defaults to not reset
PINSTYLE_DEDI = 0x000, // dedicated signals, leave alone
PINSTYLE_INP = 0x001, // general inputs, no inversion/tieing but defaults low
PINSTYLE_PU = 0x022, // signals that float high and default high
PINSTYLE_CARRY = 0x001, // carry chains can be floating or 0?
};
struct Arch : BaseArch<ArchRanges>
{
ArchArgs args;
mistral::CycloneV *cyclonev;
Arch(ArchArgs args);
ArchArgs archArgs() const { return args; }
std::string getChipName() const override { return std::string{"TODO: getChipName"}; }
// -------------------------------------------------
int getGridDimX() const override { return cyclonev->get_tile_sx(); }
int getGridDimY() const override { return cyclonev->get_tile_sy(); }
int getTileBelDimZ(int x, int y) const override; // arch.cc
char getNameDelimiter() const override { return '.'; }
// -------------------------------------------------
BelId getBelByName(IdStringList name) const override; // arch.cc
IdStringList getBelName(BelId bel) const override; // arch.cc
const std::vector<BelId> &getBels() const override { return all_bels; }
std::vector<BelId> getBelsByTile(int x, int y) const override;
Loc getBelLocation(BelId bel) const override
{
return Loc(CycloneV::pos2x(bel.pos), CycloneV::pos2y(bel.pos), bel.z);
}
BelId getBelByLocation(Loc loc) const override
{
if (loc.x < 0 || loc.x >= cyclonev->get_tile_sx())
return BelId();
if (loc.y < 0 || loc.y >= cyclonev->get_tile_sy())
return BelId();
auto &bels = bels_by_tile.at(pos2idx(loc.x, loc.y));
if (loc.z < 0 || loc.z >= int(bels.size()))
return BelId();
return BelId(CycloneV::xy2pos(loc.x, loc.y), loc.z);
}
IdString getBelType(BelId bel) const override; // arch.cc
WireId getBelPinWire(BelId bel, IdString pin) const override
{
auto &pins = bel_data(bel).pins;
auto found = pins.find(pin);
if (found == pins.end())
return WireId();
else
return found->second.wire;
}
PortType getBelPinType(BelId bel, IdString pin) const override { return bel_data(bel).pins.at(pin).dir; }
std::vector<IdString> getBelPins(BelId bel) const override;
bool isBelLocationValid(BelId bel) const override;
void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) override
{
auto &data = bel_data(bel);
NPNR_ASSERT(data.bound == nullptr);
data.bound = cell;
cell->bel = bel;
cell->belStrength = strength;
update_bel(bel);
}
void unbindBel(BelId bel) override
{
auto &data = bel_data(bel);
NPNR_ASSERT(data.bound != nullptr);
data.bound->bel = BelId();
data.bound->belStrength = STRENGTH_NONE;
data.bound = nullptr;
update_bel(bel);
}
bool checkBelAvail(BelId bel) const override { return bel_data(bel).bound == nullptr; }
CellInfo *getBoundBelCell(BelId bel) const override { return bel_data(bel).bound; }
CellInfo *getConflictingBelCell(BelId bel) const override { return bel_data(bel).bound; }
void update_bel(BelId bel);
BelId bel_by_block_idx(int x, int y, IdString type, int block_index) const;
// -------------------------------------------------
WireId getWireByName(IdStringList name) const override;
IdStringList getWireName(WireId wire) const override;
DelayQuad getWireDelay(WireId wire) const override { return DelayQuad(0); }
const std::vector<BelPin> &getWireBelPins(WireId wire) const override { return wires.at(wire).bel_pins; }
AllWireRange getWires() const override { return AllWireRange(wires); }
bool wires_connected(WireId src, WireId dst) const;
// Only allow src, and not any other wire, to drive dst
void reserve_route(WireId src, WireId dst);
// -------------------------------------------------
PipId getPipByName(IdStringList name) const override;
AllPipRange getPips() const override { return AllPipRange(wires); }
Loc getPipLocation(PipId pip) const override { return Loc(CycloneV::rn2x(pip.dst), CycloneV::rn2y(pip.dst), 0); }
IdStringList getPipName(PipId pip) const override;
WireId getPipSrcWire(PipId pip) const override { return WireId(pip.src); };
WireId getPipDstWire(PipId pip) const override { return WireId(pip.dst); };
DelayQuad getPipDelay(PipId pip) const override { return DelayQuad(100); }
UpDownhillPipRange getPipsDownhill(WireId wire) const override
{
return UpDownhillPipRange(wires.at(wire).wires_downhill, wire, false);
}
UpDownhillPipRange getPipsUphill(WireId wire) const override
{
return UpDownhillPipRange(wires.at(wire).wires_uphill, wire, true);
}
bool checkPipAvail(PipId pip) const override
{
// Check reserved routes
WireId dst(pip.dst);
const auto &dst_data = wires.at(dst);
if ((dst_data.flags & WireInfo::RESERVED_ROUTE) != 0) {
if (WireId(pip.src) != dst_data.wires_uphill.at(dst_data.flags & 0xFF))
return false;
}
return BaseArch::checkPipAvail(pip);
}
bool checkPipAvailForNet(PipId pip, NetInfo *net) const override
{
if (!checkPipAvail(pip))
return false;
return BaseArch::checkPipAvailForNet(pip, net);
}
// -------------------------------------------------
delay_t estimateDelay(WireId src, WireId dst) const override;
delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override;
delay_t getDelayEpsilon() const override { return 10; };
delay_t getRipupDelayPenalty() const override { return 100; };
float getDelayNS(delay_t v) const override { return float(v) / 1000.0f; };
delay_t getDelayFromNS(float ns) const override { return delay_t(ns * 1000.0f); };
uint32_t getDelayChecksum(delay_t v) const override { return v; };
ArcBounds getRouteBoundingBox(WireId src, WireId dst) const override;
// -------------------------------------------------
const std::vector<IdString> &getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const override
{
return cell_info->pin_data.at(pin).bel_pins;
}
bool isValidBelForCellType(IdString cell_type, BelId bel) const override;
BelBucketId getBelBucketForCellType(IdString cell_type) const override;
// -------------------------------------------------
void assignArchInfo() override;
bool pack() override;
bool place() override;
bool route() override;
// -------------------------------------------------
// Functions for device setup
BelId add_bel(int x, int y, IdString name, IdString type);
WireId add_wire(int x, int y, IdString name, uint64_t flags = 0);
PipId add_pip(WireId src, WireId dst);
void add_bel_pin(BelId bel, IdString pin, PortType dir, WireId wire);
WireId get_port(CycloneV::block_type_t bt, int x, int y, int bi, CycloneV::port_type_t port, int pi = -1) const
{
return WireId(cyclonev->pnode_to_rnode(CycloneV::pnode(bt, x, y, port, bi, pi)));
}
void create_lab(int x, int y); // lab.cc
void create_gpio(int x, int y); // io.cc
void create_clkbuf(int x, int y); // globals.cc
// -------------------------------------------------
bool is_comb_cell(IdString cell_type) const; // lab.cc
bool is_alm_legal(uint32_t lab, uint8_t alm) const; // lab.cc
bool is_lab_ctrlset_legal(uint32_t lab) const; // lab.cc
bool check_lab_input_count(uint32_t lab) const; // lab.cc
void assign_comb_info(CellInfo *cell) const; // lab.cc
void assign_ff_info(CellInfo *cell) const; // lab.cc
void lab_pre_route(); // lab.cc
void assign_control_sets(uint32_t lab); // lab.cc
void reassign_alm_inputs(uint32_t lab, uint8_t alm); // lab.cc
void update_alm_input_count(uint32_t lab, uint8_t alm); // lab.cc
uint64_t compute_lut_mask(uint32_t lab, uint8_t alm); // lab.cc
// -------------------------------------------------
bool is_io_cell(IdString cell_type) const; // io.cc
BelId get_io_pin_bel(const CycloneV::pin_info_t *pin) const; // io.cc
// -------------------------------------------------
bool is_clkbuf_cell(IdString cell_type) const; // globals.cc
// -------------------------------------------------
static const std::string defaultPlacer;
static const std::vector<std::string> availablePlacers;
static const std::string defaultRouter;
static const std::vector<std::string> availableRouters;
std::unordered_map<WireId, WireInfo> wires;
// List of LABs
std::vector<LABInfo> labs;
// WIP to link without failure
std::vector<BelPin> empty_belpin_list;
// Conversion between numbers and rnode types and IdString, for fast wire name implementation
std::vector<IdString> int2id;
std::unordered_map<IdString, int> id2int;
std::vector<IdString> rn_t2id;
std::unordered_map<IdString, CycloneV::rnode_type_t> id2rn_t;
// This structure is only used for nextpnr-created wires
std::unordered_map<IdStringList, WireId> npnr_wirebyname;
std::vector<std::vector<BelInfo>> bels_by_tile;
std::vector<BelId> all_bels;
size_t pos2idx(int x, int y) const
{
NPNR_ASSERT(x >= 0 && x < int(cyclonev->get_tile_sx()));
NPNR_ASSERT(y >= 0 && y < int(cyclonev->get_tile_sy()));
return y * cyclonev->get_tile_sx() + x;
}
size_t pos2idx(CycloneV::pos_t pos) const { return pos2idx(CycloneV::pos2x(pos), CycloneV::pos2y(pos)); }
BelInfo &bel_data(BelId bel) { return bels_by_tile.at(pos2idx(bel.pos)).at(bel.z); }
const BelInfo &bel_data(BelId bel) const { return bels_by_tile.at(pos2idx(bel.pos)).at(bel.z); }
// -------------------------------------------------
void assign_default_pinmap(CellInfo *cell);
static const std::unordered_map<IdString, IdString> comb_pinmap;
// -------------------------------------------------
typedef std::unordered_map<IdString, CellPinStyle> CellPinsData; // pins.cc
static const std::unordered_map<IdString, CellPinsData> cell_pins_db; // pins.cc
CellPinStyle get_cell_pin_style(const CellInfo *cell, IdString port) const; // pins.cc
// -------------------------------------------------
// List of IO constraints, used by QSF parser
std::unordered_map<IdString, std::unordered_map<IdString, Property>> io_attr;
void read_qsf(std::istream &in); // qsf.cc
// -------------------------------------------------
void init_base_bitstream(); // base_bitstream.cc
void build_bitstream(); // bitstream.cc
};
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,87 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Claire Xen <claire@symbioticeda.com>
* Copyright (C) 2018 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("device", &ArchArgs::device);
py::class_<BelId>(m, "BelId").def_readwrite("pos", &BelId::pos).def_readwrite("z", &BelId::z);
py::class_<WireId>(m, "WireId").def_readwrite("node", &WireId::node);
py::class_<PipId>(m, "PipId").def_readwrite("src", &PipId::src).def_readwrite("dst", &PipId::dst);
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);
fn_wrapper_2a<Context, decltype(&Context::wires_connected), &Context::wires_connected, pass_through<bool>,
conv_from_str<WireId>, conv_from_str<WireId>>::def_wrap(ctx_cls, "wires_connected");
fn_wrapper_2a<Context, decltype(&Context::compute_lut_mask), &Context::compute_lut_mask, pass_through<uint64_t>,
pass_through<uint32_t>, pass_through<uint8_t>>::def_wrap(ctx_cls, "compute_lut_mask");
typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap;
typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap;
typedef std::unordered_map<IdString, IdString> AliasMap;
typedef std::unordered_map<IdString, HierarchicalCell> HierarchyMap;
auto belpin_cls = 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<BelId> &BelRange;
typedef UpDownhillPipRange UphillPipRange;
typedef UpDownhillPipRange DownhillPipRange;
typedef AllWireRange WireRange;
typedef const std::vector<BelPin> &BelPinRange;
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, AllWire, conv_to_str<WireId>);
WRAP_RANGE(m, AllPip, conv_to_str<PipId>);
WRAP_RANGE(m, UpDownhillPip, 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

125
mistral/arch_pybindings.h Normal file
View File

@ -0,0 +1,125 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Claire Xen <claire@symbioticeda.com>
* Copyright (C) 2018 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->getBelByNameStr(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<const BelId &>
{
const BelId &from_str(Context *ctx, std::string name) { NPNR_ASSERT_FALSE("unreachable"); }
std::string to_str(Context *ctx, const 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->getWireByNameStr(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->getWireByNameStr(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->getPipByNameStr(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);
}
};
template <> struct string_converter<const 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, const 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

33
mistral/archdefs.cc Normal file
View File

@ -0,0 +1,33 @@
/*
* 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 "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
CellPinState ArchCellInfo::get_pin_state(IdString pin) const
{
auto fnd = pin_data.find(pin);
if (fnd != pin_data.end())
return fnd->second.state;
else
return PIN_SIG;
}
NEXTPNR_NAMESPACE_END

236
mistral/archdefs.h Normal file
View File

@ -0,0 +1,236 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021 Lofty <dan.ravensloft@gmail.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 MISTRAL_ARCHDEFS_H
#define MISTRAL_ARCHDEFS_H
#include <boost/functional/hash.hpp>
#include "base_clusterinfo.h"
#include "cyclonev.h"
#include "idstring.h"
#include "nextpnr_assertions.h"
#include "nextpnr_namespaces.h"
NEXTPNR_NAMESPACE_BEGIN
using mistral::CycloneV;
typedef int delay_t;
// https://bugreports.qt.io/browse/QTBUG-80789
#ifndef Q_MOC_RUN
enum ConstIds
{
ID_NONE
#define X(t) , ID_##t
#include "constids.inc"
#undef X
};
#define X(t) static constexpr auto id_##t = IdString(ID_##t);
#include "constids.inc"
#undef X
#endif
struct DelayInfo
{
delay_t delay = 0;
delay_t minRaiseDelay() const { return delay; }
delay_t maxRaiseDelay() const { return delay; }
delay_t minFallDelay() const { return delay; }
delay_t maxFallDelay() const { return delay; }
delay_t minDelay() const { return delay; }
delay_t maxDelay() const { return delay; }
DelayInfo operator+(const DelayInfo &other) const
{
DelayInfo ret;
ret.delay = this->delay + other.delay;
return ret;
}
};
struct BelId
{
BelId() = default;
BelId(CycloneV::pos_t _pos, uint16_t _z) : pos{_pos}, z{_z} {}
// pos_t is used for X/Y, nextpnr-cyclonev uses its own Z coordinate system.
CycloneV::pos_t pos = 0;
uint16_t z = 0;
bool operator==(const BelId &other) const { return pos == other.pos && z == other.z; }
bool operator!=(const BelId &other) const { return pos != other.pos || z != other.z; }
bool operator<(const BelId &other) const { return pos < other.pos || (pos == other.pos && z < other.z); }
};
static constexpr auto invalid_rnode = std::numeric_limits<CycloneV::rnode_t>::max();
struct WireId
{
WireId() = default;
explicit WireId(CycloneV::rnode_t node) : node(node){};
CycloneV::rnode_t node = invalid_rnode;
// Wires created by nextpnr have rnode type >= 128
bool is_nextpnr_created() const
{
NPNR_ASSERT(node != invalid_rnode);
return CycloneV::rn2t(node) >= 128;
}
bool operator==(const WireId &other) const { return node == other.node; }
bool operator!=(const WireId &other) const { return node != other.node; }
bool operator<(const WireId &other) const { return node < other.node; }
};
struct PipId
{
PipId() = default;
PipId(CycloneV::rnode_t src, CycloneV::rnode_t dst) : src(src), dst(dst){};
CycloneV::rnode_t src = invalid_rnode, dst = invalid_rnode;
bool operator==(const PipId &other) const { return src == other.src && dst == other.dst; }
bool operator!=(const PipId &other) const { return src != other.src || dst != other.dst; }
bool operator<(const PipId &other) const { return dst < other.dst || (dst == other.dst && src < other.src); }
};
typedef IdString DecalId;
typedef IdString GroupId;
typedef IdString BelBucketId;
typedef IdString ClusterId;
struct ArchNetInfo
{
bool is_global = false;
};
enum CellPinState
{
PIN_SIG = 0,
PIN_0 = 1,
PIN_1 = 2,
PIN_INV = 3,
};
struct ArchPinInfo
{
// Used to represent signals that are either tied to implicit constants (rather than explicitly routed constants);
// or are inverted
CellPinState state = PIN_SIG;
// The physical bel pins that this logical pin maps to
std::vector<IdString> bel_pins;
};
struct NetInfo;
// Structures for representing how FF control sets are stored and validity-checked
struct ControlSig
{
const NetInfo *net;
bool inverted;
bool connected() const { return net != nullptr; }
bool operator==(const ControlSig &other) const { return net == other.net && inverted == other.inverted; }
bool operator!=(const ControlSig &other) const { return net == other.net && inverted == other.inverted; }
};
struct FFControlSet
{
ControlSig clk, ena, aclr, sclr, sload;
bool operator==(const FFControlSet &other) const
{
return clk == other.clk && ena == other.ena && aclr == other.aclr && sclr == other.sclr && sload == other.sload;
}
bool operator!=(const FFControlSet &other) const { return !(*this == other); }
};
struct ArchCellInfo : BaseClusterInfo
{
union
{
struct
{
// Store the nets here for fast validity checking (avoids too many map lookups in a hot path)
std::array<const NetInfo *, 7> lut_in;
const NetInfo *comb_out;
int lut_input_count;
int used_lut_input_count; // excluding those null/constant
int lut_bits_count;
// for the LAB routeability check (see the detailed description in lab.cc); usually the same signal feeding
// multiple ALMs in a LAB is counted multiple times, due to not knowing which routing resources it will need
// in each case. But carry chains where we know how things will pack are allowed to share across ALMs as a
// special case, primarily to support adders/subtractors with a 'B invert' control signal shared across all
// ALMs.
int chain_shared_input_count;
bool is_carry, is_shared, is_extended;
bool carry_start, carry_end;
} combInfo;
struct
{
FFControlSet ctrlset;
const NetInfo *sdata, *datain;
} ffInfo;
};
std::unordered_map<IdString, ArchPinInfo> pin_data;
CellPinState get_pin_state(IdString pin) const;
};
NEXTPNR_NAMESPACE_END
namespace std {
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX BelId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept
{
return hash<uint32_t>()((static_cast<uint32_t>(bel.pos) << 16) | bel.z);
}
};
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX WireId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept
{
return hash<uint32_t>()(wire.node);
}
};
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX PipId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept
{
return hash<uint64_t>()((uint64_t(pip.dst) << 32) | pip.src);
}
};
} // namespace std
#endif

100
mistral/base_bitstream.cc Normal file
View File

@ -0,0 +1,100 @@
/*
* 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 "log.h"
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
namespace {
// Device-specific default config for the sx120f die
void default_sx120f(CycloneV *cv)
{
// Default PMA config?
cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 11), CycloneV::FFPLL_IQCLK_DIRECTION, 0, CycloneV::TRISTATE);
cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 11), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::TRISTATE);
cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 23), CycloneV::FFPLL_IQCLK_DIRECTION, 0, CycloneV::DOWN);
cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 23), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::UP);
cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FFPLL_IQCLK_DIRECTION, 0, CycloneV::UP);
cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::UP);
cv->bmux_b_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FPLL_DRV_EN, 0, 0);
cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::HCLK_TOP_OUT_DRIVER, 0, CycloneV::TRISTATE);
// Default PLL config
cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN0, 0, 1);
cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN0_PRECOMP, 0, 1);
cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN1, 0, 1);
cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN1_PRECOMP, 0, 1);
cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_BG_KICKSTART, 0, 1);
cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_VBGMON_POWERDOWN, 0, 1);
// Default TERM config
cv->bmux_b_set(CycloneV::TERM, CycloneV::xy2pos(89, 34), CycloneV::INTOSC_2_EN, 0, 0);
// TODO: what if these pins are used? where do these come from
for (int z = 0; z < 4; z++) {
cv->bmux_m_set(CycloneV::GPIO, CycloneV::xy2pos(89, 43), CycloneV::IOCSR_STD, z, CycloneV::NVR_LOW);
cv->bmux_m_set(CycloneV::GPIO, CycloneV::xy2pos(89, 66), CycloneV::IOCSR_STD, z, CycloneV::NVR_LOW);
}
for (int y : {38, 44, 51, 58, 65, 73, 79}) {
// TODO: Why only these upper DQS? is there a pattern?
cv->bmux_b_set(CycloneV::DQS16, CycloneV::xy2pos(89, y), CycloneV::RB_2X_CLK_DQS_INV, 0, 1);
cv->bmux_b_set(CycloneV::DQS16, CycloneV::xy2pos(89, y), CycloneV::RB_ACLR_LFIFO_EN, 0, 1);
cv->bmux_b_set(CycloneV::DQS16, CycloneV::xy2pos(89, y), CycloneV::RB_LFIFO_BYPASS, 0, 0);
}
// Discover these mux values using
// grep 'i [_A-Z0-9.]* 1' empty.bt
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 12), 69), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 13), 4), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 34), 69), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 35), 4), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 37), 31), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 40), 43), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 46), 69), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 47), 43), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 53), 69), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 54), 4), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 73), 68), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 18), 66), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 20), 8), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 27), 69), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 28), 43), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 59), 66), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 61), 8), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 68), 69), true);
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 69), 43), true);
for (int z = 10; z <= 45; z++)
cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(51, 80), z), true);
}
} // namespace
void Arch::init_base_bitstream()
{
switch (cyclonev->current_model()->variant.die.type) {
case CycloneV::SX120F:
default_sx120f(cyclonev);
break;
default:
log_error("FIXME: die type %s currently unsupported for bitgen.\n",
cyclonev->current_model()->variant.die.name);
}
}
NEXTPNR_NAMESPACE_END

361
mistral/bitstream.cc Normal file
View File

@ -0,0 +1,361 @@
/*
* 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 "log.h"
#include "nextpnr.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
namespace {
struct MistralBitgen
{
MistralBitgen(Context *ctx) : ctx(ctx), cv(ctx->cyclonev){};
Context *ctx;
CycloneV *cv;
void init()
{
ctx->init_base_bitstream();
// Default options
cv->opt_b_set(CycloneV::ALLOW_DEVICE_WIDE_OUTPUT_ENABLE_DIS, true);
cv->opt_n_set(CycloneV::CRC_DIVIDE_ORDER, 8);
cv->opt_b_set(CycloneV::CVP_CONF_DONE_EN, true);
cv->opt_b_set(CycloneV::DEVICE_WIDE_RESET_EN, true);
cv->opt_n_set(CycloneV::DRIVE_STRENGTH, 8);
cv->opt_b_set(CycloneV::IOCSR_READY_FROM_CSR_DONE_EN, true);
cv->opt_b_set(CycloneV::NCEO_DIS, true);
cv->opt_b_set(CycloneV::OCT_DONE_DIS, true);
cv->opt_r_set(CycloneV::OPT_A, 0x1dff);
cv->opt_r_set(CycloneV::OPT_B, 0xffffff402dffffffULL);
cv->opt_b_set(CycloneV::RELEASE_CLEARS_BEFORE_TRISTATES_DIS, true);
cv->opt_b_set(CycloneV::RETRY_CONFIG_ON_ERROR_EN, true);
cv->opt_r_set(CycloneV::START_UP_CLOCK, 0x3F);
// Default inversion
write_default_inv();
}
void write_default_inv()
{
// Some PNODEs are inverted by default. Set them up here.
for (const auto &pn2r : cv->get_all_p2r()) {
const auto &pn = pn2r.first;
auto pt = CycloneV::pn2pt(pn);
auto pi = CycloneV::pn2pi(pn);
switch (CycloneV::pn2bt(pn)) {
case CycloneV::HMC: {
// HMC OE are inverted to set OE=0, i.e. unused pins floating
// TODO: handle the case when we are using the HMC or HMC bypass
std::string name(CycloneV::port_type_names[pt]);
if (name.compare(0, 5, "IOINT") != 0 || name.compare(name.size() - 2, 2, "OE") != 0)
continue;
cv->inv_set(pn2r.second, true);
break;
};
// HPS IO - TODO: what about when we actually support the HPS primitives?
case CycloneV::HPS_BOOT: {
switch (pt) {
case CycloneV::CSEL_EN:
case CycloneV::BSEL_EN:
case CycloneV::BOOT_FROM_FPGA_READY:
case CycloneV::BOOT_FROM_FPGA_ON_FAILURE:
cv->inv_set(pn2r.second, true);
break;
case CycloneV::CSEL:
if (pi < 2)
cv->inv_set(pn2r.second, true);
break;
case CycloneV::BSEL:
if (pi < 3)
cv->inv_set(pn2r.second, true);
break;
default:
break;
};
break;
};
case CycloneV::HPS_CROSS_TRIGGER: {
if (pt == CycloneV::CLK_EN)
cv->inv_set(pn2r.second, true);
break;
};
case CycloneV::HPS_TEST: {
if (pt == CycloneV::CFG_DFX_BYPASS_ENABLE)
cv->inv_set(pn2r.second, true);
break;
};
case CycloneV::GPIO: {
// Ignore GPIO used by the design
BelId bel = ctx->bel_by_block_idx(CycloneV::pn2x(pn), CycloneV::pn2y(pn), id_MISTRAL_IO,
CycloneV::pn2bi(pn));
if (bel != BelId() && ctx->getBoundBelCell(bel) != nullptr)
continue;
// Bonded IO invert OEIN.1 which disables the output buffer and floats the IO
// Unbonded IO invert OEIN.0 which enables the output buffer, and {DATAIN.[0123]} to drive a constant
// GND, presumably for power/EMI reasons
bool is_bonded = cv->pin_find_pnode(pn) != nullptr;
if (is_bonded && (pt != CycloneV::OEIN || pi != 1))
continue;
if (!is_bonded && (pt != CycloneV::DATAIN) && (pt != CycloneV::OEIN || pi != 0))
continue;
cv->inv_set(pn2r.second, true);
break;
};
case CycloneV::FPLL: {
if (pt == CycloneV::EXTSWITCH || (pt == CycloneV::CLKEN && pi < 2))
cv->inv_set(pn2r.second, true);
break;
};
default:
break;
}
}
}
void write_dqs()
{
for (auto pos : cv->dqs16_get_pos()) {
int x = CycloneV::pos2x(pos), y = CycloneV::pos2y(pos);
// DQS bypass for used output pins
for (int z = 0; z < 16; z++) {
int ioy = y + (z / 4) - 2;
if (ioy < 0 || ioy >= int(cv->get_tile_sy()))
continue;
BelId bel = ctx->bel_by_block_idx(x, ioy, id_MISTRAL_IO, z % 4);
if (bel == BelId())
continue;
CellInfo *ci = ctx->getBoundBelCell(bel);
if (ci == nullptr || (ci->type != id_MISTRAL_IO && ci->type != id_MISTRAL_OB))
continue; // not an output
cv->bmux_m_set(CycloneV::DQS16, pos, CycloneV::INPUT_REG4_SEL, z, CycloneV::SEL_LOCKED_DPA);
cv->bmux_r_set(CycloneV::DQS16, pos, CycloneV::RB_T9_SEL_EREG_CFF_DELAY, z, 0x1f);
}
}
}
void write_routing()
{
for (auto net : sorted(ctx->nets)) {
NetInfo *ni = net.second;
for (auto wire : sorted_ref(ni->wires)) {
PipId pip = wire.second.pip;
if (pip == PipId())
continue;
WireId src = ctx->getPipSrcWire(pip), dst = ctx->getPipDstWire(pip);
// Only write out routes that are entirely in the Mistral domain. Everything else is dealt with
// specially
if (src.is_nextpnr_created() || dst.is_nextpnr_created())
continue;
cv->rnode_link(src.node, dst.node);
}
}
}
void write_io_cell(CellInfo *ci, int x, int y, int bi)
{
bool is_output =
(ci->type == id_MISTRAL_OB || (ci->type == id_MISTRAL_IO && get_net_or_empty(ci, id_OE) != nullptr));
auto pos = CycloneV::xy2pos(x, y);
// TODO: configurable pull, IO standard, etc
cv->bmux_b_set(CycloneV::GPIO, pos, CycloneV::USE_WEAK_PULLUP, bi, false);
if (is_output) {
cv->bmux_m_set(CycloneV::GPIO, pos, CycloneV::DRIVE_STRENGTH, bi, CycloneV::V3P3_LVTTL_16MA_LVCMOS_2MA);
cv->bmux_m_set(CycloneV::GPIO, pos, CycloneV::IOCSR_STD, bi, CycloneV::DIS);
}
// There seem to be two mirrored OEIN inversion bits for constant OE for inputs/outputs. This might be to
// prevent a single bitflip from turning inputs to outputs and messing up other devices on the boards, notably
// ECP5 does similar. OEIN.0 inverted for outputs; OEIN.1 for inputs
cv->inv_set(cv->pnode_to_rnode(CycloneV::pnode(CycloneV::GPIO, pos, CycloneV::OEIN, bi, is_output ? 0 : 1)),
true);
}
void write_clkbuf_cell(CellInfo *ci, int x, int y, int bi)
{
(void)ci; // currently unused
auto pos = CycloneV::xy2pos(x, y);
cv->bmux_r_set(CycloneV::CMUXHG, pos, CycloneV::INPUT_SELECT, bi, 0x1b); // hardcode to general routing
cv->bmux_m_set(CycloneV::CMUXHG, pos, CycloneV::TESTSYN_ENOUT_SELECT, bi, CycloneV::PRE_SYNENB);
}
void write_cells()
{
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
Loc loc = ctx->getBelLocation(ci->bel);
int bi = ctx->bel_data(ci->bel).block_index;
if (ctx->is_io_cell(ci->type))
write_io_cell(ci, loc.x, loc.y, bi);
else if (ctx->is_clkbuf_cell(ci->type))
write_clkbuf_cell(ci, loc.x, loc.y, bi);
}
}
bool write_alm(uint32_t lab, uint8_t alm)
{
auto &alm_data = ctx->labs.at(lab).alms.at(alm);
std::array<CellInfo *, 2> luts{ctx->getBoundBelCell(alm_data.lut_bels[0]),
ctx->getBoundBelCell(alm_data.lut_bels[1])};
std::array<CellInfo *, 4> ffs{
ctx->getBoundBelCell(alm_data.ff_bels[0]), ctx->getBoundBelCell(alm_data.ff_bels[1]),
ctx->getBoundBelCell(alm_data.ff_bels[2]), ctx->getBoundBelCell(alm_data.ff_bels[3])};
// Skip empty ALMs
if (std::all_of(luts.begin(), luts.end(), [](CellInfo *c) { return !c; }) &&
std::all_of(ffs.begin(), ffs.end(), [](CellInfo *c) { return !c; }))
return false;
auto pos = alm_data.lut_bels[0].pos;
// Combinational mode - TODO: flop feedback
cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::MODE, alm, alm_data.l6_mode ? CycloneV::L6 : CycloneV::L5);
// LUT function
cv->bmux_r_set(CycloneV::LAB, pos, CycloneV::LUT_MASK, alm, ctx->compute_lut_mask(lab, alm));
// DFF/LUT output selection
const std::array<CycloneV::bmux_type_t, 6> mux_settings{CycloneV::TDFF0, CycloneV::TDFF1, CycloneV::TDFF1L,
CycloneV::BDFF0, CycloneV::BDFF1, CycloneV::BDFF1L};
const std::array<CycloneV::port_type_t, 6> mux_port{CycloneV::FFT0, CycloneV::FFT1, CycloneV::FFT1L,
CycloneV::FFB0, CycloneV::FFB1, CycloneV::FFB1L};
for (int i = 0; i < 6; i++) {
if (ctx->wires_connected(alm_data.comb_out[i / 3], ctx->get_port(CycloneV::LAB, CycloneV::pos2x(pos),
CycloneV::pos2y(pos), alm, mux_port[i])))
cv->bmux_m_set(CycloneV::LAB, pos, mux_settings[i], alm, CycloneV::NLUT);
}
bool is_carry = (luts[0] && luts[0]->combInfo.is_carry) || (luts[1] && luts[1]->combInfo.is_carry);
if (is_carry)
cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::ARITH_SEL, alm, CycloneV::ADDER);
// The carry in/out enable bits
if (is_carry && alm == 0 && !luts[0]->combInfo.carry_start)
cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::TTO_DIS, 0, true);
if (is_carry && alm == 5)
cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::BTO_DIS, 0, true);
// Flipflop configuration
const std::array<CycloneV::bmux_type_t, 2> ef_sel{CycloneV::TEF_SEL, CycloneV::BEF_SEL};
// This isn't a typo; the *PKREG* bits really are mirrored.
const std::array<CycloneV::bmux_type_t, 4> pkreg{CycloneV::TPKREG1, CycloneV::TPKREG0, CycloneV::BPKREG1,
CycloneV::BPKREG0};
const std::array<CycloneV::bmux_type_t, 2> clk_sel{CycloneV::TCLK_SEL, CycloneV::BCLK_SEL},
clr_sel{CycloneV::TCLR_SEL, CycloneV::BCLR_SEL}, sclr_dis{CycloneV::TSCLR_DIS, CycloneV::BSCLR_DIS},
sload_en{CycloneV::TSLOAD_EN, CycloneV::BSLOAD_EN};
const std::array<CycloneV::bmux_type_t, 3> clk_choice{CycloneV::CLK0, CycloneV::CLK1, CycloneV::CLK2};
const std::array<CycloneV::bmux_type_t, 3> clk_inv{CycloneV::CLK0_INV, CycloneV::CLK1_INV, CycloneV::CLK2_INV},
en_en{CycloneV::EN0_EN, CycloneV::EN1_EN, CycloneV::EN2_EN},
en_ninv{CycloneV::EN0_NINV, CycloneV::EN1_NINV, CycloneV::EN2_NINV};
const std::array<CycloneV::bmux_type_t, 2> aclr_inv{CycloneV::ACLR0_INV, CycloneV::ACLR1_INV};
for (int i = 0; i < 2; i++) {
// EF selection mux
if (ctx->wires_connected(ctx->getBelPinWire(alm_data.lut_bels[i], i ? id_F0 : id_F1), alm_data.sel_ef[i]))
cv->bmux_m_set(CycloneV::LAB, pos, ef_sel[i], alm, CycloneV::bmux_type_t::F);
}
for (int i = 0; i < 4; i++) {
CellInfo *ff = ffs[i];
if (!ff)
continue;
// PKREG (input selection)
if (ctx->wires_connected(alm_data.sel_ef[i / 2], alm_data.ff_in[i]))
cv->bmux_b_set(CycloneV::LAB, pos, pkreg[i], alm, true);
// Control set
// CLK+ENA
int ce_idx = alm_data.clk_ena_idx[i / 2];
cv->bmux_m_set(CycloneV::LAB, pos, clk_sel[i / 2], alm, clk_choice[ce_idx]);
if (ff->ffInfo.ctrlset.clk.inverted)
cv->bmux_b_set(CycloneV::LAB, pos, clk_inv[ce_idx], 0, true);
if (get_net_or_empty(ff, id_ENA) != nullptr) { // not using ffInfo.ctrlset, this has a fake net always to
// ensure different constants don't collide
cv->bmux_b_set(CycloneV::LAB, pos, en_en[ce_idx], 0, true);
cv->bmux_b_set(CycloneV::LAB, pos, en_ninv[ce_idx], 0, ff->ffInfo.ctrlset.ena.inverted);
} else {
cv->bmux_b_set(CycloneV::LAB, pos, en_en[ce_idx], 0, false);
}
// ACLR
int aclr_idx = alm_data.aclr_idx[i / 2];
cv->bmux_b_set(CycloneV::LAB, pos, clr_sel[i / 2], alm, aclr_idx == 1);
if (ff->ffInfo.ctrlset.aclr.inverted)
cv->bmux_b_set(CycloneV::LAB, pos, aclr_inv[aclr_idx], 0, true);
// SCLR
if (ff->ffInfo.ctrlset.sclr.net != nullptr) {
cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::SCLR_INV, 0, ff->ffInfo.ctrlset.sclr.inverted);
} else {
cv->bmux_b_set(CycloneV::LAB, pos, sclr_dis[i / 2], alm, true);
}
// SLOAD
if (ff->ffInfo.ctrlset.sload.net != nullptr) {
cv->bmux_b_set(CycloneV::LAB, pos, sload_en[i / 2], alm, true);
cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::SLOAD_INV, 0, ff->ffInfo.ctrlset.sload.inverted);
}
}
return true;
}
void write_ff_routing(uint32_t lab)
{
auto &lab_data = ctx->labs.at(lab);
auto pos = lab_data.alms.at(0).lut_bels[0].pos;
const std::array<CycloneV::bmux_type_t, 2> aclr_inp{CycloneV::ACLR0_SEL, CycloneV::ACLR1_SEL};
for (int i = 0; i < 2; i++) {
// Quartus seems to set unused ACLRs to CLKI2...
if (!lab_data.aclr_used[i])
cv->bmux_m_set(CycloneV::LAB, pos, aclr_inp[i], 0, CycloneV::CLKI2);
else
cv->bmux_m_set(CycloneV::LAB, pos, aclr_inp[i], 0, (i == 1) ? CycloneV::GIN0 : CycloneV::GIN1);
}
for (int i = 0; i < 3; i++) {
// Check for fabric->clock routing
if (ctx->wires_connected(ctx->get_port(CycloneV::LAB, CycloneV::pos2x(pos), CycloneV::pos2y(pos), -1,
CycloneV::DATAIN, 0),
lab_data.clk_wires[i]))
cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::CLKA_SEL, 0, CycloneV::GIN2);
}
}
void write_labs()
{
for (size_t lab = 0; lab < ctx->labs.size(); lab++) {
bool used = false;
for (uint8_t alm = 0; alm < 10; alm++)
used |= write_alm(lab, alm);
if (used)
write_ff_routing(lab);
}
}
void run()
{
cv->clear();
init();
write_routing();
write_dqs();
write_cells();
write_labs();
}
};
} // namespace
void Arch::build_bitstream()
{
MistralBitgen gen(getCtx());
gen.run();
}
NEXTPNR_NAMESPACE_END

78
mistral/constids.inc Normal file
View File

@ -0,0 +1,78 @@
X(MISTRAL_COMB)
X(MISTRAL_FF)
X(LAB)
X(MLAB)
X(MISTRAL_IO)
X(I)
X(O)
X(OE)
X(PAD)
X(MISTRAL_IB)
X(MISTRAL_OB)
X(A)
X(B)
X(C)
X(D)
X(E)
X(F)
X(E0)
X(F0)
X(E1)
X(F1)
X(DATAIN)
X(FFT0)
X(FFT1)
X(FFB0)
X(FFB1)
X(FFT1L)
X(FFB1L)
X(CLKIN)
X(ACLR)
X(CLK)
X(ENA)
X(SCLR)
X(SLOAD)
X(SDATA)
X(Q)
X(COMBOUT)
X(SUM_OUT)
X(CI)
X(SHAREIN)
X(CO)
X(SHAREOUT)
X(CARRY_START)
X(SHARE_START)
X(CARRY_END)
X(SHARE_END)
X(MISTRAL_ALUT6)
X(MISTRAL_ALUT5)
X(MISTRAL_ALUT4)
X(MISTRAL_ALUT3)
X(MISTRAL_ALUT2)
X(MISTRAL_NOT)
X(MISTRAL_BUF)
X(MISTRAL_CONST)
X(MISTRAL_ALUT_ARITH)
X(LUT)
X(LUT0)
X(LUT1)
X(D0)
X(D1)
X(SO)
X(WIRE)
X(GND)
X(VCC)
X(Y)
X(LOC)
X(MISTRAL_CLKENA)
X(MISTRAL_CLKBUF)

14
mistral/family.cmake Normal file
View File

@ -0,0 +1,14 @@
set(MISTRAL_ROOT "" CACHE STRING "Mistral install path")
aux_source_directory(${MISTRAL_ROOT}/lib MISTRAL_LIB_FILES)
add_library(mistral STATIC ${MISTRAL_LIB_FILES})
target_compile_options(mistral PRIVATE -Wno-maybe-uninitialized)
find_package(LibLZMA REQUIRED)
foreach(family_target ${family_targets})
target_include_directories(${family_target} PRIVATE ${MISTRAL_ROOT}/lib ${LIBLZMA_INCLUDE_DIRS})
target_link_libraries(${family_target} PRIVATE mistral ${LIBLZMA_LIBRARIES})
# Currently required to avoid issues with mistral (LTO means the warnings can end up in nextpnr)
target_link_options(${family_target} PRIVATE -Wno-maybe-uninitialized)
endforeach()

46
mistral/globals.cc Normal file
View File

@ -0,0 +1,46 @@
/*
* 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 "log.h"
#include "nextpnr.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
void Arch::create_clkbuf(int x, int y)
{
for (int z = 0; z < 4; z++) {
if (z != 2)
continue; // TODO: why do other Zs not work?
// For now we only consider the input path from general routing, other inputs like dedicated clock pins are
// still a TODO
BelId bel = add_bel(x, y, id(stringf("CLKBUF[%d]", z)), id_MISTRAL_CLKENA);
add_bel_pin(bel, id_A, PORT_IN, get_port(CycloneV::CMUXHG, x, y, -1, CycloneV::CLKIN, z));
add_bel_pin(bel, id_Q, PORT_OUT, get_port(CycloneV::CMUXHG, x, y, z, CycloneV::CLKOUT));
// TODO: enable pin
bel_data(bel).block_index = z;
}
}
bool Arch::is_clkbuf_cell(IdString cell_type) const
{
return cell_type == id_MISTRAL_CLKENA || cell_type == id_MISTRAL_CLKBUF;
}
NEXTPNR_NAMESPACE_END

61
mistral/io.cc Normal file
View File

@ -0,0 +1,61 @@
/*
* 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 "log.h"
#include "nextpnr.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
void Arch::create_gpio(int x, int y)
{
for (int z = 0; z < 4; z++) {
// Notional pad wire
WireId pad = add_wire(x, y, id(stringf("PAD[%d]", z)));
BelId bel = add_bel(x, y, id(stringf("IO[%d]", z)), id_MISTRAL_IO);
add_bel_pin(bel, id_PAD, PORT_INOUT, pad);
// FIXME: is the port index of zero always correct?
add_bel_pin(bel, id_I, PORT_IN, get_port(CycloneV::GPIO, x, y, z, CycloneV::DATAIN, 0));
add_bel_pin(bel, id_OE, PORT_IN, get_port(CycloneV::GPIO, x, y, z, CycloneV::OEIN, 0));
add_bel_pin(bel, id_O, PORT_OUT, get_port(CycloneV::GPIO, x, y, z, CycloneV::DATAOUT, 0));
bel_data(bel).block_index = z;
}
}
bool Arch::is_io_cell(IdString cell_type) const
{
// Return true if a cell is an IO buffer cell type
switch (cell_type.index) {
case ID_MISTRAL_IB:
case ID_MISTRAL_OB:
case ID_MISTRAL_IO:
return true;
default:
return false;
}
}
BelId Arch::get_io_pin_bel(const CycloneV::pin_info_t *pin) const
{
auto pad = pin->pad;
CycloneV::pos_t pos = (pad & 0x3FFF);
return bel_by_block_idx(CycloneV::pos2x(pos), CycloneV::pos2y(pos), id_MISTRAL_IO, (pad >> 14));
}
NEXTPNR_NAMESPACE_END

969
mistral/lab.cc Normal file
View File

@ -0,0 +1,969 @@
/*
* 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 "design_utils.h"
#include "log.h"
#include "nextpnr.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
// This file contains functions related to our custom LAB structure, including creating the LAB bels; checking the
// legality of LABs; and manipulating LUT inputs and equations
// LAB/ALM structure creation functions
namespace {
static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx)
{
auto &lab = arch->labs.at(lab_idx);
auto &alm = lab.alms.at(z);
// Create the combinational part of ALMs.
// There are two of these, for the two LUT outputs, and these also contain the carry chain and associated logic
// Each one has all 8 ALM inputs as input pins. In many cases only a subset of these are used; depending on mode;
// and the bel-cell pin mappings are used to handle this post-placement without losing flexibility
for (int i = 0; i < 2; i++) {
// Carry/share wires are a bit tricky due to all the different permutations
WireId carry_in, share_in;
WireId carry_out, share_out;
if (z == 0 && i == 0) {
carry_in = arch->add_wire(x, y, id_CI);
share_in = arch->add_wire(x, y, id_SHAREIN);
if (y < (arch->getGridDimY() - 1)) {
// Carry is split at tile boundary (TTO_DIS bit), add a PIP to represent this.
// TODO: what about BTO_DIS, in the middle of the LAB?
arch->add_pip(arch->add_wire(x, y + 1, id_CO), carry_in);
arch->add_pip(arch->add_wire(x, y + 1, id_SHAREOUT), share_in);
}
} else {
// Output from last combinational unit
carry_in = arch->add_wire(x, y, arch->id(stringf("CARRY[%d]", (z * 2 + i) - 1)));
share_in = arch->add_wire(x, y, arch->id(stringf("SHARE[%d]", (z * 2 + i) - 1)));
}
if (z == 9 && i == 1) {
carry_out = arch->add_wire(x, y, id_CO);
share_out = arch->add_wire(x, y, id_SHAREOUT);
} else {
carry_out = arch->add_wire(x, y, arch->id(stringf("CARRY[%d]", z * 2 + i)));
share_out = arch->add_wire(x, y, arch->id(stringf("SHARE[%d]", z * 2 + i)));
}
BelId bel = arch->add_bel(x, y, arch->id(stringf("ALM%d_COMB%d", z, i)), id_MISTRAL_COMB);
// LUT/MUX inputs
arch->add_bel_pin(bel, id_A, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::A));
arch->add_bel_pin(bel, id_B, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::B));
arch->add_bel_pin(bel, id_C, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::C));
arch->add_bel_pin(bel, id_D, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::D));
arch->add_bel_pin(bel, id_E0, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::E0));
arch->add_bel_pin(bel, id_E1, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::E1));
arch->add_bel_pin(bel, id_F0, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::F0));
arch->add_bel_pin(bel, id_F1, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::F1));
// Carry/share chain
arch->add_bel_pin(bel, id_CI, PORT_IN, carry_in);
arch->add_bel_pin(bel, id_SHAREIN, PORT_IN, share_in);
arch->add_bel_pin(bel, id_CO, PORT_OUT, carry_out);
arch->add_bel_pin(bel, id_SHAREOUT, PORT_OUT, share_out);
// Combinational output
alm.comb_out[i] = arch->add_wire(x, y, arch->id(stringf("COMBOUT[%d]", z * 2 + i)));
arch->add_bel_pin(bel, id_COMBOUT, PORT_OUT, alm.comb_out[i]);
// Assign indexing
alm.lut_bels.at(i) = bel;
auto &b = arch->bel_data(bel);
b.lab_data.lab = lab_idx;
b.lab_data.alm = z;
b.lab_data.idx = i;
}
// Create the control set and E/F selection - which is per pair of FF
for (int i = 0; i < 2; i++) {
// Wires
alm.sel_clk[i] = arch->add_wire(x, y, arch->id(stringf("CLK%c[%d]", i ? 'B' : 'T', z)));
alm.sel_ena[i] = arch->add_wire(x, y, arch->id(stringf("ENA%c[%d]", i ? 'B' : 'T', z)));
alm.sel_aclr[i] = arch->add_wire(x, y, arch->id(stringf("ACLR%c[%d]", i ? 'B' : 'T', z)));
alm.sel_ef[i] = arch->add_wire(x, y, arch->id(stringf("%cEF[%d]", i ? 'B' : 'T', z)));
// Muxes - three CLK/ENA per LAB, two ACLR
for (int j = 0; j < 3; j++) {
arch->add_pip(lab.clk_wires[j], alm.sel_clk[i]);
arch->add_pip(lab.ena_wires[j], alm.sel_ena[i]);
if (j < 2)
arch->add_pip(lab.aclr_wires[j], alm.sel_aclr[i]);
}
// E/F pips
// Note that the F choice is mirrored, F from the other half is picked
arch->add_pip(arch->get_port(CycloneV::LAB, x, y, z, i ? CycloneV::E1 : CycloneV::E0), alm.sel_ef[i]);
arch->add_pip(arch->get_port(CycloneV::LAB, x, y, z, i ? CycloneV::F0 : CycloneV::F1), alm.sel_ef[i]);
}
// Create the flipflops and associated routing
const CycloneV::port_type_t outputs[4] = {CycloneV::FFT0, CycloneV::FFT1, CycloneV::FFB0, CycloneV::FFB1};
const CycloneV::port_type_t l_outputs[4] = {CycloneV::FFT1L, CycloneV::FFB1L};
for (int i = 0; i < 4; i++) {
// FF input, selected by *PKREG*
alm.ff_in[i] = arch->add_wire(x, y, arch->id(stringf("FFIN[%d]", (z * 4) + i)));
arch->add_pip(alm.comb_out[i / 2], alm.ff_in[i]);
arch->add_pip(alm.sel_ef[i / 2], alm.ff_in[i]);
// FF bel
BelId bel = arch->add_bel(x, y, arch->id(stringf("ALM%d_FF%d", z, i)), id_MISTRAL_FF);
arch->add_bel_pin(bel, id_CLK, PORT_IN, alm.sel_clk[i / 2]);
arch->add_bel_pin(bel, id_ENA, PORT_IN, alm.sel_ena[i / 2]);
arch->add_bel_pin(bel, id_ACLR, PORT_IN, alm.sel_aclr[i / 2]);
arch->add_bel_pin(bel, id_SCLR, PORT_IN, lab.sclr_wire);
arch->add_bel_pin(bel, id_SLOAD, PORT_IN, lab.sload_wire);
arch->add_bel_pin(bel, id_DATAIN, PORT_IN, alm.ff_in[i]);
arch->add_bel_pin(bel, id_SDATA, PORT_IN, alm.sel_ef[i / 2]);
// FF output
alm.ff_out[i] = arch->add_wire(x, y, arch->id(stringf("FFOUT[%d]", (z * 4) + i)));
arch->add_bel_pin(bel, id_Q, PORT_OUT, alm.ff_out[i]);
// Output mux (*DFF*)
WireId out = arch->get_port(CycloneV::LAB, x, y, z, outputs[i]);
arch->add_pip(alm.ff_out[i], out);
arch->add_pip(alm.comb_out[i / 2], out);
// 'L' output mux where applicable
if (i == 1 || i == 3) {
WireId l_out = arch->get_port(CycloneV::LAB, x, y, z, l_outputs[i / 2]);
arch->add_pip(alm.ff_out[i], l_out);
arch->add_pip(alm.comb_out[i / 2], l_out);
}
lab.alms.at(z).ff_bels.at(i) = bel;
auto &b = arch->bel_data(bel);
b.lab_data.lab = lab_idx;
b.lab_data.alm = z;
b.lab_data.idx = i;
}
}
} // namespace
void Arch::create_lab(int x, int y)
{
uint32_t lab_idx = labs.size();
labs.emplace_back();
auto &lab = labs.back();
// Create common control set configuration. This is actually a subset of what's possible, but errs on the side of
// caution due to incomplete documentation
// Clocks - hardcode to CLKA choices, as both CLKA and CLKB coming from general routing causes unexpected
// permutations
for (int i = 0; i < 3; i++) {
lab.clk_wires[i] = add_wire(x, y, id(stringf("CLK%d", i)));
add_pip(get_port(CycloneV::LAB, x, y, -1, CycloneV::CLKIN, 0), lab.clk_wires[i]); // dedicated routing
add_pip(get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 0), lab.clk_wires[i]); // general routing
}
// Enables - while it looks from the config like there are choices for these, it seems like EN0_SEL actually selects
// SCLR not ENA0 and EN1_SEL actually selects SLOAD?
lab.ena_wires[0] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 2);
lab.ena_wires[1] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 3);
lab.ena_wires[2] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 0);
// ACLRs - only consider general routing for now
lab.aclr_wires[0] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 3);
lab.aclr_wires[1] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 2);
// SCLR and SLOAD - as above it seems like these might be selectable using the "EN*_SEL" bits but play it safe for
// now
lab.sclr_wire = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 3);
lab.sload_wire = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 1);
for (int i = 0; i < 10; i++) {
create_alm(this, x, y, i, lab_idx);
}
}
// Cell handling and annotation functions
namespace {
ControlSig get_ctrlsig(const Context *ctx, const CellInfo *cell, IdString port, bool explicit_const = false)
{
ControlSig result;
result.net = get_net_or_empty(cell, port);
if (result.net == nullptr && explicit_const) {
// For ENA, 1 (and 0) are explicit control set choices even though they aren't routed, as "no ENA" still
// consumes a clock+ENA pair
CellPinState st = PIN_1;
result.net = ctx->nets.at((st == PIN_1) ? ctx->id("$PACKER_VCC_NET") : ctx->id("$PACKER_GND_NET")).get();
}
if (cell->pin_data.count(port))
result.inverted = cell->pin_data.at(port).state == PIN_INV;
else
result.inverted = false;
return result;
}
} // namespace
bool Arch::is_comb_cell(IdString cell_type) const
{
// Return true if a cell is a combinational cell type, to be a placed at a MISTRAL_COMB location
switch (cell_type.index) {
case ID_MISTRAL_ALUT6:
case ID_MISTRAL_ALUT5:
case ID_MISTRAL_ALUT4:
case ID_MISTRAL_ALUT3:
case ID_MISTRAL_ALUT2:
case ID_MISTRAL_NOT:
case ID_MISTRAL_CONST:
case ID_MISTRAL_ALUT_ARITH:
return true;
default:
return false;
}
}
void Arch::assign_comb_info(CellInfo *cell) const
{
cell->combInfo.is_carry = false;
cell->combInfo.is_shared = false;
cell->combInfo.is_extended = false;
cell->combInfo.carry_start = false;
cell->combInfo.carry_end = false;
cell->combInfo.chain_shared_input_count = 0;
if (cell->type == id_MISTRAL_ALUT_ARITH) {
cell->combInfo.is_carry = true;
cell->combInfo.lut_input_count = 5;
cell->combInfo.lut_bits_count = 32;
// This is a special case in terms of naming
const std::array<IdString, 5> arith_pins{id_A, id_B, id_C, id_D0, id_D1};
{
int i = 0;
for (auto pin : arith_pins) {
cell->combInfo.lut_in[i++] = get_net_or_empty(cell, pin);
}
}
const NetInfo *ci = get_net_or_empty(cell, id_CI);
const NetInfo *co = get_net_or_empty(cell, id_CO);
cell->combInfo.comb_out = get_net_or_empty(cell, id_SO);
cell->combInfo.carry_start = (ci == nullptr) || (ci->driver.cell == nullptr);
cell->combInfo.carry_end = (co == nullptr) || (co->users.empty());
// Compute cross-ALM routing sharing - only check the z=0 case inside ALMs
if (cell->constr_z > 0 && ((cell->constr_z % 2) == 0) && ci) {
const CellInfo *prev = ci->driver.cell;
if (prev != nullptr) {
for (int i = 0; i < 5; i++) {
const NetInfo *a = get_net_or_empty(cell, arith_pins[i]);
if (a == nullptr)
continue;
const NetInfo *b = get_net_or_empty(prev, arith_pins[i]);
if (a == b)
++cell->combInfo.chain_shared_input_count;
}
}
}
} else {
cell->combInfo.lut_input_count = 0;
switch (cell->type.index) {
case ID_MISTRAL_ALUT6:
++cell->combInfo.lut_input_count;
cell->combInfo.lut_in[5] = get_net_or_empty(cell, id_F);
[[fallthrough]];
case ID_MISTRAL_ALUT5:
++cell->combInfo.lut_input_count;
cell->combInfo.lut_in[4] = get_net_or_empty(cell, id_E);
[[fallthrough]];
case ID_MISTRAL_ALUT4:
++cell->combInfo.lut_input_count;
cell->combInfo.lut_in[3] = get_net_or_empty(cell, id_D);
[[fallthrough]];
case ID_MISTRAL_ALUT3:
++cell->combInfo.lut_input_count;
cell->combInfo.lut_in[2] = get_net_or_empty(cell, id_C);
[[fallthrough]];
case ID_MISTRAL_ALUT2:
++cell->combInfo.lut_input_count;
cell->combInfo.lut_in[1] = get_net_or_empty(cell, id_B);
[[fallthrough]];
case ID_MISTRAL_BUF: // used to route through to FFs etc
case ID_MISTRAL_NOT: // used for inverters that map to LUTs
++cell->combInfo.lut_input_count;
cell->combInfo.lut_in[0] = get_net_or_empty(cell, id_A);
[[fallthrough]];
case ID_MISTRAL_CONST:
// MISTRAL_CONST is a nextpnr-inserted cell type for 0-input, constant-generating LUTs
break;
default:
log_error("unexpected combinational cell type %s\n", getCtx()->nameOf(cell->type));
}
// Note that this relationship won't hold for extended mode, when that is supported
cell->combInfo.lut_bits_count = (1 << cell->combInfo.lut_input_count);
}
cell->combInfo.used_lut_input_count = 0;
for (int i = 0; i < cell->combInfo.lut_input_count; i++)
if (cell->combInfo.lut_in[i])
++cell->combInfo.used_lut_input_count;
}
void Arch::assign_ff_info(CellInfo *cell) const
{
cell->ffInfo.ctrlset.clk = get_ctrlsig(getCtx(), cell, id_CLK);
cell->ffInfo.ctrlset.ena = get_ctrlsig(getCtx(), cell, id_ENA, true);
cell->ffInfo.ctrlset.aclr = get_ctrlsig(getCtx(), cell, id_ACLR);
cell->ffInfo.ctrlset.sclr = get_ctrlsig(getCtx(), cell, id_SCLR);
cell->ffInfo.ctrlset.sload = get_ctrlsig(getCtx(), cell, id_SLOAD);
// If SCLR is used, but SLOAD isn't, then it seems like we need to pretend as if SLOAD is connected GND (so set
// [BT]SLOAD_EN inside the ALMs, and clear SLOAD_INV)
if (cell->ffInfo.ctrlset.sclr.net != nullptr && cell->ffInfo.ctrlset.sload.net == nullptr) {
cell->ffInfo.ctrlset.sload.net = nets.at(id("$PACKER_GND_NET")).get();
cell->ffInfo.ctrlset.sload.inverted = false;
}
cell->ffInfo.sdata = get_net_or_empty(cell, id_SDATA);
cell->ffInfo.datain = get_net_or_empty(cell, id_DATAIN);
}
// Validity checking functions
bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const
{
auto &alm_data = labs.at(lab).alms.at(alm);
// Get cells into an array for fast access
std::array<const CellInfo *, 2> luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])};
std::array<const CellInfo *, 4> ffs{getBoundBelCell(alm_data.ff_bels[0]), getBoundBelCell(alm_data.ff_bels[1]),
getBoundBelCell(alm_data.ff_bels[2]), getBoundBelCell(alm_data.ff_bels[3])};
int used_lut_bits = 0;
int total_lut_inputs = 0;
// TODO: for more complex modes like extended/arithmetic, it might not always be possible for any LUT input to map
// to any of the ALM half inputs particularly shared and extended mode will need more thought and probably for this
// to be revisited
for (int i = 0; i < 2; i++) {
if (!luts[i])
continue;
total_lut_inputs += luts[i]->combInfo.lut_input_count;
used_lut_bits += luts[i]->combInfo.lut_bits_count;
}
// An ALM only has 64 bits of storage. In theory some of these cases might be legal because of overlap between the
// two functions, but the current placer is unlikely to stumble upon these cases frequently without anything to
// guide it, and the cost of checking them here almost certainly outweighs any marginal benefit in supporting them,
// at least for now.
if (used_lut_bits > 64)
return false;
if (total_lut_inputs > 8) {
NPNR_ASSERT(luts[0] && luts[1]); // something has gone badly wrong if this fails!
// Make sure that LUT inputs are not overprovisioned
int shared_lut_inputs = 0;
// Even though this N^2 search looks inefficient, it's unlikely a set lookup or similar is going to be much
// better given the low N.
for (int i = 0; i < luts[1]->combInfo.lut_input_count; i++) {
const NetInfo *sig = luts[1]->combInfo.lut_in[i];
for (int j = 0; j < luts[0]->combInfo.lut_input_count; j++) {
if (sig == luts[0]->combInfo.lut_in[j]) {
++shared_lut_inputs;
break;
}
}
}
if ((total_lut_inputs - shared_lut_inputs) > 8)
return false;
}
bool carry_mode = false;
// No mixing of carry and non-carry
if (luts[0] && luts[1] && luts[0]->combInfo.is_carry != luts[1]->combInfo.is_carry)
return false;
// For each ALM half; check FF control set sharing and input routeability
for (int i = 0; i < 2; i++) {
// There are two ways to route from the fabric into FF data - either routing through a LUT or using the E/F
// signals and SLOAD=1 (*PKREF*)
bool route_thru_lut_avail = !luts[i] && !carry_mode && (total_lut_inputs < 8) && (used_lut_bits < 64);
// E/F is available if this LUT is using 3 or fewer inputs - this is conservative and sharing can probably
// improve this situation. (1 - i) because the F input to EF_SEL is mirrored.
bool ef_available = (!luts[1 - i] || (luts[1 - i]->combInfo.used_lut_input_count <= 2));
// Control set checking
bool found_ff = false;
FFControlSet ctrlset;
for (int j = 0; j < 2; j++) {
const CellInfo *ff = ffs[i * 2 + j];
if (!ff)
continue;
if (j == 1)
return false; // TODO: why are these FFs broken?
if (found_ff) {
// Two FFs in the same half with an incompatible control set
if (ctrlset != ff->ffInfo.ctrlset)
return false;
} else {
ctrlset = ff->ffInfo.ctrlset;
}
// SDATA must use the E/F input
// TODO: rare case of two FFs with the same SDATA in the same ALM half
if (ff->ffInfo.sdata) {
if (!ef_available)
return false;
ef_available = false;
}
// Find a way of routing the input through fabric, if it's not driven by the LUT
if (ff->ffInfo.datain && (!luts[i] || (ff->ffInfo.datain != luts[i]->combInfo.comb_out))) {
if (route_thru_lut_avail)
route_thru_lut_avail = false;
else if (ef_available)
ef_available = false;
else
return false;
}
found_ff = true;
}
}
return true;
}
void Arch::update_alm_input_count(uint32_t lab, uint8_t alm)
{
// TODO: duplication with above
auto &alm_data = labs.at(lab).alms.at(alm);
// Get cells into an array for fast access
std::array<const CellInfo *, 2> luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])};
std::array<const CellInfo *, 4> ffs{getBoundBelCell(alm_data.ff_bels[0]), getBoundBelCell(alm_data.ff_bels[1]),
getBoundBelCell(alm_data.ff_bels[2]), getBoundBelCell(alm_data.ff_bels[3])};
int total_inputs = 0;
int total_lut_inputs = 0;
for (int i = 0; i < 2; i++) {
if (!luts[i])
continue;
total_lut_inputs += luts[i]->combInfo.used_lut_input_count - luts[i]->combInfo.chain_shared_input_count;
}
int shared_lut_inputs = 0;
if (luts[0] && luts[1]) {
for (int i = 0; i < luts[1]->combInfo.lut_input_count; i++) {
const NetInfo *sig = luts[1]->combInfo.lut_in[i];
if (!sig)
continue;
for (int j = 0; j < luts[0]->combInfo.lut_input_count; j++) {
if (sig == luts[0]->combInfo.lut_in[j]) {
++shared_lut_inputs;
break;
}
}
if (shared_lut_inputs >= 2) {
// only 2 inputs have guaranteed sharing, without routeability based LUT permutation at least
break;
}
}
}
total_inputs = std::max(0, total_lut_inputs - shared_lut_inputs);
for (int i = 0; i < 4; i++) {
const CellInfo *ff = ffs[i];
if (!ff)
continue;
if (ff->ffInfo.sdata)
++total_inputs;
// FF input doesn't consume routing resources if driven by associated LUT
if (ff->ffInfo.datain && (!luts[i / 2] || ff->ffInfo.datain != luts[i / 2]->combInfo.comb_out))
++total_inputs;
}
alm_data.unique_input_count = total_inputs;
}
bool Arch::check_lab_input_count(uint32_t lab) const
{
// There are only 46 TD signals available to route signals from general routing to the ALM input. Currently, we
// check the total sum of ALM inputs is less than 42; 46 minus 4 FF control inputs. This is a conservative check for
// several reasons, because LD signals are also available for feedback routing from ALM output to input, and because
// TD signals may be shared if the same net routes to multiple ALMs. But these cases will need careful handling and
// LUT permutation during routing to be useful; and in any event conservative LAB packing will help nextpnr's
// currently perfunctory place and route algorithms to achieve satisfactory runtimes.
int count = 0;
auto &lab_data = labs.at(lab);
for (int i = 0; i < 10; i++) {
count += lab_data.alms.at(i).unique_input_count;
}
return (count <= 42);
}
namespace {
bool check_assign_sig(ControlSig &sig_set, const ControlSig &sig)
{
if (sig.net == nullptr) {
return true;
} else if (sig_set == sig) {
return true;
} else if (sig_set.net == nullptr) {
sig_set = sig;
return true;
} else {
return false;
}
};
template <size_t N> bool check_assign_sig(std::array<ControlSig, N> &sig_set, const ControlSig &sig)
{
if (sig.net == nullptr)
return true;
for (size_t i = 0; i < N; i++)
if (sig_set[i] == sig) {
return true;
} else if (sig_set[i].net == nullptr) {
sig_set[i] = sig;
return true;
}
return false;
};
// DATAIN mapping rules - which LAB DATAIN signals can be used for ENA and ACLR
static constexpr std::array<int, 3> ena_datain{2, 3, 0};
static constexpr std::array<int, 2> aclr_datain{3, 2};
struct LabCtrlSetWorker
{
ControlSig clk{}, sload{}, sclr{};
std::array<ControlSig, 2> aclr{};
std::array<ControlSig, 3> ena{};
std::array<ControlSig, 4> datain{};
bool run(const Arch *arch, uint32_t lab)
{
// Strictly speaking the constraint is up to 2 unique CLK and 3 CLK+ENA pairs. For now we simplify this to 1 CLK
// and 3 ENA though.
for (uint8_t alm = 0; alm < 10; alm++) {
for (uint8_t i = 0; i < 4; i++) {
const CellInfo *ff = arch->getBoundBelCell(arch->labs.at(lab).alms.at(alm).ff_bels.at(i));
if (ff == nullptr)
continue;
if (!check_assign_sig(clk, ff->ffInfo.ctrlset.clk))
return false;
if (!check_assign_sig(sload, ff->ffInfo.ctrlset.sload))
return false;
if (!check_assign_sig(sclr, ff->ffInfo.ctrlset.sclr))
return false;
if (!check_assign_sig(aclr, ff->ffInfo.ctrlset.aclr))
return false;
if (!check_assign_sig(ena, ff->ffInfo.ctrlset.ena))
return false;
}
}
// Check for overuse of the shared, LAB-wide datain signals
if (clk.net != nullptr && !clk.net->is_global)
if (!check_assign_sig(datain[0], clk)) // CLK only needs DATAIN[0] if it's not global
return false;
if (!check_assign_sig(datain[1], sload))
return false;
if (!check_assign_sig(datain[3], sclr))
return false;
for (const auto &aclr_sig : aclr) {
// Check both possibilities that ACLR can map to
// TODO: ACLR could be global, too
if (check_assign_sig(datain[aclr_datain[0]], aclr_sig))
continue;
if (check_assign_sig(datain[aclr_datain[1]], aclr_sig))
continue;
// Failed to find any free ACLR-capable DATAIN
return false;
}
for (const auto &ena_sig : ena) {
// Check all 3 possibilities that ACLR can map to
// TODO: ACLR could be global, too
if (check_assign_sig(datain[ena_datain[0]], ena_sig))
continue;
if (check_assign_sig(datain[ena_datain[1]], ena_sig))
continue;
if (check_assign_sig(datain[ena_datain[2]], ena_sig))
continue;
// Failed to find any free ENA-capable DATAIN
return false;
}
return true;
}
};
}; // namespace
bool Arch::is_lab_ctrlset_legal(uint32_t lab) const
{
LabCtrlSetWorker worker;
return worker.run(this, lab);
}
void Arch::lab_pre_route()
{
log_info("Preparing LABs for routing...\n");
for (uint32_t lab = 0; lab < labs.size(); lab++) {
assign_control_sets(lab);
for (uint8_t alm = 0; alm < 10; alm++) {
reassign_alm_inputs(lab, alm);
}
}
}
void Arch::assign_control_sets(uint32_t lab)
{
// Set up reservations for checkPipAvail for control set signals
// This will be needed because clock and CE are routed together and must be kept together, there isn't free choice
// e.g. CLK0 & ENA0 must be use for one control set, and CLK1 & ENA1 for another, they can't be mixed and matched
// Similarly for how inverted & noninverted variants must be kept separate
LabCtrlSetWorker worker;
bool legal = worker.run(this, lab);
NPNR_ASSERT(legal);
auto &lab_data = labs.at(lab);
for (int j = 0; j < 2; j++) {
lab_data.aclr_used[j] = false;
}
for (uint8_t alm = 0; alm < 10; alm++) {
auto &alm_data = lab_data.alms.at(alm);
for (uint8_t i = 0; i < 4; i++) {
BelId ff_bel = alm_data.ff_bels.at(i);
const CellInfo *ff = getBoundBelCell(ff_bel);
if (ff == nullptr)
continue;
ControlSig ena_sig = ff->ffInfo.ctrlset.ena;
WireId clk_wire = getBelPinWire(ff_bel, id_CLK);
WireId ena_wire = getBelPinWire(ff_bel, id_ENA);
for (int j = 0; j < 3; j++) {
if (ena_sig == worker.datain[ena_datain[j]]) {
if (getCtx()->debug) {
log_info("Assigned CLK/ENA set %d to FF %s (%s)\n", i, nameOf(ff), getCtx()->nameOfBel(ff_bel));
}
// TODO: lock clock according to ENA choice, too, when we support two clocks per ALM
reserve_route(lab_data.clk_wires[0], clk_wire);
reserve_route(lab_data.ena_wires[j], ena_wire);
alm_data.clk_ena_idx[i / 2] = j;
break;
}
}
ControlSig aclr_sig = ff->ffInfo.ctrlset.aclr;
WireId aclr_wire = getBelPinWire(ff_bel, id_ACLR);
for (int j = 0; j < 2; j++) {
// TODO: could be global ACLR, too
if (aclr_sig == worker.datain[aclr_datain[j]]) {
if (getCtx()->debug) {
log_info("Assigned ACLR set %d to FF %s (%s)\n", i, nameOf(ff), getCtx()->nameOfBel(ff_bel));
}
reserve_route(lab_data.aclr_wires[j], aclr_wire);
lab_data.aclr_used[j] = (aclr_sig.net != nullptr);
alm_data.aclr_idx[i / 2] = j;
break;
}
}
}
}
}
namespace {
// Gets the name of logical LUT pin i for a given cell
static IdString get_lut_pin(CellInfo *cell, int i)
{
const std::array<IdString, 6> log_pins{id_A, id_B, id_C, id_D, id_E, id_F};
const std::array<IdString, 5> log_pins_arith{id_A, id_B, id_C, id_D0, id_D1};
return (cell->type == id_MISTRAL_ALUT_ARITH) ? log_pins_arith.at(i) : log_pins.at(i);
}
static void assign_lut6_inputs(CellInfo *cell, int lut)
{
std::array<IdString, 6> phys_pins{id_A, id_B, id_C, id_D, (lut == 1) ? id_E1 : id_E0, (lut == 1) ? id_F1 : id_F0};
int phys_idx = 0;
for (int i = 0; i < 6; i++) {
IdString log = get_lut_pin(cell, i);
if (!cell->ports.count(log) || cell->ports.at(log).net == nullptr)
continue;
cell->pin_data[log].bel_pins.clear();
cell->pin_data[log].bel_pins.push_back(phys_pins.at(phys_idx++));
}
}
} // namespace
void Arch::reassign_alm_inputs(uint32_t lab, uint8_t alm)
{
// Based on the usage of LUTs inside the ALM, set up cell-bel pin map for the combinational cells in the ALM
// so that each physical bel pin is only used for one net; and the logical functions can be implemented correctly.
// This function should also insert route-through LUTs to legalise flipflop inputs as needed.
auto &alm_data = labs.at(lab).alms.at(alm);
alm_data.l6_mode = false;
std::array<CellInfo *, 2> luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])};
std::array<CellInfo *, 4> ffs{getBoundBelCell(alm_data.ff_bels[0]), getBoundBelCell(alm_data.ff_bels[1]),
getBoundBelCell(alm_data.ff_bels[2]), getBoundBelCell(alm_data.ff_bels[3])};
for (int i = 0; i < 2; i++) {
// Currently we treat LUT6s as a special case, as they never share inputs
if (luts[i] != nullptr && luts[i]->type == id_MISTRAL_ALUT6) {
alm_data.l6_mode = true;
NPNR_ASSERT(luts[1 - i] == nullptr); // only allow one LUT6 per ALM and no other LUTs
assign_lut6_inputs(luts[i], i);
}
}
if (!alm_data.l6_mode) {
// In L5 mode; which is what we use in this case
// - A and B are shared
// - C, E0, and F0 are exclusive to the top LUT5 secion
// - D, E1, and F1 are exclusive to the bottom LUT5 section
// First find up to two shared inputs
std::unordered_map<IdString, int> shared_nets;
if (luts[0] && luts[1]) {
for (int i = 0; i < luts[0]->combInfo.lut_input_count; i++) {
for (int j = 0; j < luts[1]->combInfo.lut_input_count; j++) {
if (luts[0]->combInfo.lut_in[i] == nullptr)
continue;
if (luts[0]->combInfo.lut_in[i] != luts[1]->combInfo.lut_in[j])
continue;
IdString net = luts[0]->combInfo.lut_in[i]->name;
if (shared_nets.count(net))
continue;
int idx = int(shared_nets.size());
shared_nets[net] = idx;
if (shared_nets.size() >= 2)
goto shared_search_done;
}
}
shared_search_done:;
}
// A and B can be used for half-specific nets if not assigned to shared nets
bool a_avail = shared_nets.size() == 0, b_avail = shared_nets.size() <= 1;
// Do the actual port assignment
for (int i = 0; i < 2; i++) {
if (!luts[i])
continue;
// Work out which physical ports are available
std::vector<IdString> avail_phys_ports;
// D/C always available and dedicated to the half, in L5 mode
avail_phys_ports.push_back((i == 1) ? id_D : id_C);
// In arithmetic mode, Ei can only be used for D0 and Fi can only be used for D1
// otherwise, these are general and dedicated to one half
if (!luts[i]->combInfo.is_carry) {
avail_phys_ports.push_back((i == 1) ? id_E1 : id_E0);
avail_phys_ports.push_back((i == 1) ? id_F1 : id_F0);
}
// A and B might be used for shared signals, or already used by the other half
if (b_avail)
avail_phys_ports.push_back(id_B);
if (a_avail)
avail_phys_ports.push_back(id_A);
int phys_idx = 0;
for (int j = 0; j < luts[i]->combInfo.lut_input_count; j++) {
IdString log = get_lut_pin(luts[i], j);
auto &bel_pins = luts[i]->pin_data[log].bel_pins;
bel_pins.clear();
NetInfo *net = get_net_or_empty(luts[i], log);
if (net == nullptr) {
// Disconnected inputs don't need to be allocated a pin, because the router won't be routing these
continue;
} else if (shared_nets.count(net->name)) {
// This pin is to be allocated one of the shared nets
bel_pins.push_back(shared_nets.at(net->name) ? id_B : id_A);
} else if (log == id_D0) {
// Arithmetic
bel_pins.push_back((i == 1) ? id_E1 : id_E0); // reserved
} else if (log == id_D1) {
bel_pins.push_back((i == 1) ? id_F1 : id_F0); // reserved
} else {
// Allocate from the general pool of available physical pins
IdString phys = avail_phys_ports.at(phys_idx++);
bel_pins.push_back(phys);
// Mark A/B unavailable for the other LUT, if needed
if (phys == id_A)
a_avail = false;
else if (phys == id_B)
b_avail = false;
}
}
}
}
// FF route-through insertion
for (int i = 0; i < 2; i++) {
// FF route-through will never be inserted if LUT is used
if (luts[i])
continue;
for (int j = 0; j < 2; j++) {
CellInfo *ff = ffs[i * 2 + j];
if (!ff || !ff->ffInfo.datain || alm_data.l6_mode)
continue;
CellInfo *rt_lut = createCell(id(stringf("%s$ROUTETHRU", nameOf(ff))), id_MISTRAL_BUF);
rt_lut->addInput(id_A);
rt_lut->addOutput(id_Q);
// Disconnect the original data input to the FF, and connect it to the route-thru LUT instead
NetInfo *datain = get_net_or_empty(ff, id_DATAIN);
disconnect_port(getCtx(), ff, id_DATAIN);
connect_port(getCtx(), datain, rt_lut, id_A);
connect_ports(getCtx(), rt_lut, id_Q, ff, id_DATAIN);
// Assign route-thru LUT physical ports, input goes to the first half-specific input
rt_lut->pin_data[id_A].bel_pins.push_back(i ? id_D : id_C);
rt_lut->pin_data[id_Q].bel_pins.push_back(id_COMBOUT);
assign_comb_info(rt_lut);
// Place the route-thru LUT at the relevant combinational bel
bindBel(alm_data.lut_bels[i], rt_lut, STRENGTH_STRONG);
break;
}
}
// TODO: in the future, as well as the reassignment here we will also have pseudo PIPs in front of the ALM so that
// the router can permute LUTs for routeability; too. Here we will need to lock out some of those PIPs depending on
// the usage of the ALM, as not all inputs are always interchangeable.
// Get cells into an array for fast access
}
// This default cell-bel pin mapping is used to provide estimates during placement only. It will have errors and
// overlaps and a correct mapping will be resolved twixt placement and routing
const std::unordered_map<IdString, IdString> Arch::comb_pinmap = {
{id_A, id_F0}, // fastest input first
{id_B, id_E0}, {id_C, id_D}, {id_D, id_C}, {id_D0, id_C}, {id_D1, id_B},
{id_E, id_B}, {id_F, id_A}, {id_Q, id_COMBOUT}, {id_SO, id_COMBOUT},
};
namespace {
// gets the value of the ith LUT init property of a given cell
uint64_t get_lut_init(const CellInfo *cell, int i)
{
if (cell->type == id_MISTRAL_NOT) {
return 1;
} else if (cell->type == id_MISTRAL_BUF) {
return 2;
} else {
IdString prop;
if (cell->type == id_MISTRAL_ALUT_ARITH)
prop = (i == 1) ? id_LUT1 : id_LUT0;
else
prop = id_LUT;
auto fnd = cell->params.find(prop);
if (fnd == cell->params.end())
return 0;
else
return fnd->second.as_int64();
}
}
// gets the state of a physical pin when evaluating the a given bit of LUT init for
bool get_phys_pin_val(bool l6_mode, bool arith_mode, int bit, IdString pin)
{
switch (pin.index) {
case ID_A:
return (bit >> 0) & 0x1;
case ID_B:
return (bit >> 1) & 0x1;
case ID_C:
return (l6_mode && bit >= 32) ? ((bit >> 3) & 0x1) : ((bit >> 2) & 0x1);
case ID_D:
return (l6_mode && bit < 32) ? ((bit >> 3) & 0x1) : ((bit >> 2) & 0x1);
case ID_E0:
case ID_E1:
return l6_mode ? ((bit >> 5) & 0x1) : ((bit >> 3) & 0x1);
case ID_F0:
case ID_F1:
return arith_mode ? ((bit >> 3) & 0x1) : ((bit >> 4) & 0x1);
default:
NPNR_ASSERT_FALSE("unknown physical pin!");
}
}
} // namespace
uint64_t Arch::compute_lut_mask(uint32_t lab, uint8_t alm)
{
uint64_t mask = 0;
auto &alm_data = labs.at(lab).alms.at(alm);
std::array<CellInfo *, 2> luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])};
for (int i = 0; i < 2; i++) {
CellInfo *lut = luts[i];
if (!lut)
continue;
int offset = ((i == 1) && !alm_data.l6_mode) ? 32 : 0;
bool arith = lut->combInfo.is_carry;
for (int j = 0; j < (alm_data.l6_mode ? 64 : 32); j++) {
// Evaluate LUT function at this point
uint64_t init = get_lut_init(lut, (arith && j >= 16) ? 1 : 0);
int index = 0;
for (int k = 0; k < lut->combInfo.lut_input_count; k++) {
IdString log_pin = get_lut_pin(lut, k);
int init_idx = k;
if (arith) {
// D0 only affects lower half; D1 upper half
if (k == 3 && j >= 16)
continue;
if (k == 4) {
if (j < 16)
continue;
else
init_idx = 3;
}
}
CellPinState state = lut->get_pin_state(log_pin);
if (state == PIN_0)
continue;
else if (state == PIN_1)
index |= (1 << init_idx);
// Ignore if no associated physical pin
if (get_net_or_empty(lut, log_pin) == nullptr || lut->pin_data.at(log_pin).bel_pins.empty())
continue;
// ALM inputs appear to be inverted by default (TODO: check!)
// so only invert if an inverter has _not_ been folded into the pin
bool inverted = (state != PIN_INV);
// Depermute physical pin
IdString phys_pin = lut->pin_data.at(log_pin).bel_pins.at(0);
if (get_phys_pin_val(alm_data.l6_mode, arith, j, phys_pin) != inverted)
index |= (1 << init_idx);
}
if ((init >> index) & 0x1) {
mask |= (1ULL << uint64_t(j + offset));
}
}
}
// TODO: always inverted, or just certain paths?
mask = ~mask;
#if 1
if (getCtx()->debug) {
auto pos = alm_data.lut_bels[0].pos;
log("ALM %03d.%03d.%d\n", CycloneV::pos2x(pos), CycloneV::pos2y(pos), alm);
for (int i = 0; i < 2; i++) {
log(" LUT%d: ", i);
if (luts[i]) {
log("%s:%s", nameOf(luts[i]), nameOf(luts[i]->type));
for (auto &pin : luts[i]->pin_data) {
if (!luts[i]->ports.count(pin.first) || luts[i]->ports.at(pin.first).type != PORT_IN)
continue;
log(" %s:", nameOf(pin.first));
if (pin.second.state == PIN_0)
log("0");
else if (pin.second.state == PIN_1)
log("1");
else if (pin.second.state == PIN_INV)
log("~");
for (auto bp : pin.second.bel_pins)
log("%s", nameOf(bp));
}
} else {
log("<null>");
}
log("\n");
}
log("INIT: %016lx\n", mask);
log("\n");
}
#endif
return mask;
}
NEXTPNR_NAMESPACE_END

105
mistral/main.cc Normal file
View File

@ -0,0 +1,105 @@
/*
* 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.
*
*/
#ifdef MAIN_EXECUTABLE
#include <fstream>
#include "command.h"
#include "design_utils.h"
#include "jsonwrite.h"
#include "log.h"
#include "timing.h"
USING_NEXTPNR_NAMESPACE
class MistralCommandHandler : public CommandHandler
{
public:
MistralCommandHandler(int argc, char **argv);
virtual ~MistralCommandHandler(){};
std::unique_ptr<Context> createContext(std::unordered_map<std::string, Property> &values) override;
void setupArchContext(Context *ctx) override{};
void customBitstream(Context *ctx) override;
void customAfterLoad(Context *ctx) override;
protected:
po::options_description getArchOptions() override;
};
MistralCommandHandler::MistralCommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {}
po::options_description MistralCommandHandler::getArchOptions()
{
po::options_description specific("Architecture specific options");
specific.add_options()("mistral", po::value<std::string>(), "path to mistral root");
specific.add_options()("device", po::value<std::string>(), "device name (e.g. 5CSEBA6U23I7)");
specific.add_options()("qsf", po::value<std::string>(), "path to QSF constraints file");
specific.add_options()("rbf", po::value<std::string>(), "RBF bitstream to write");
return specific;
}
void MistralCommandHandler::customBitstream(Context *ctx)
{
if (vm.count("rbf")) {
std::string filename = vm["rbf"].as<std::string>();
ctx->build_bitstream();
std::vector<uint8_t> data;
ctx->cyclonev->rbf_save(data);
std::ofstream out(filename, std::ios::binary);
if (!out)
log_error("Failed to open output RBF file %s.\n", filename.c_str());
out.write(reinterpret_cast<const char *>(data.data()), data.size());
}
}
std::unique_ptr<Context> MistralCommandHandler::createContext(std::unordered_map<std::string, Property> &values)
{
ArchArgs chipArgs;
if (!vm.count("mistral")) {
log_error("mistral must be specified on the command line\n");
}
if (!vm.count("device")) {
log_error("device must be specified on the command line (e.g. --device 5CSEBA6U23I7)\n");
}
chipArgs.mistral_root = vm["mistral"].as<std::string>();
chipArgs.device = vm["device"].as<std::string>();
auto ctx = std::unique_ptr<Context>(new Context(chipArgs));
return ctx;
}
void MistralCommandHandler::customAfterLoad(Context *ctx)
{
if (vm.count("qsf")) {
std::string filename = vm["qsf"].as<std::string>();
std::ifstream in(filename);
if (!in)
log_error("Failed to open input QSF file %s.\n", filename.c_str());
ctx->read_qsf(in);
}
}
int main(int argc, char *argv[])
{
MistralCommandHandler handler(argc, argv);
return handler.exec();
}
#endif

365
mistral/pack.cc Normal file
View File

@ -0,0 +1,365 @@
/*
* 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 "design_utils.h"
#include "log.h"
#include "nextpnr.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
namespace {
struct MistralPacker
{
MistralPacker(Context *ctx) : ctx(ctx){};
Context *ctx;
NetInfo *gnd_net, *vcc_net;
void init_constant_nets()
{
CellInfo *gnd_drv = ctx->createCell(ctx->id("$PACKER_GND_DRV"), id_MISTRAL_CONST);
gnd_drv->params[id_LUT] = 0;
gnd_drv->addOutput(id_Q);
CellInfo *vcc_drv = ctx->createCell(ctx->id("$PACKER_VCC_DRV"), id_MISTRAL_CONST);
vcc_drv->params[id_LUT] = 1;
vcc_drv->addOutput(id_Q);
gnd_net = ctx->createNet(ctx->id("$PACKER_GND_NET"));
vcc_net = ctx->createNet(ctx->id("$PACKER_VCC_NET"));
connect_port(ctx, gnd_net, gnd_drv, id_Q);
connect_port(ctx, vcc_net, vcc_drv, id_Q);
}
CellPinState get_pin_needed_muxval(CellInfo *cell, IdString port)
{
NetInfo *net = get_net_or_empty(cell, port);
if (net == nullptr || net->driver.cell == nullptr) {
// Pin is disconnected
// If a mux value exists already, honour it
CellPinState exist_mux = cell->get_pin_state(port);
if (exist_mux != PIN_SIG)
return exist_mux;
// Otherwise, look up the default value and use that
CellPinStyle pin_style = ctx->get_cell_pin_style(cell, port);
if ((pin_style & PINDEF_MASK) == PINDEF_0)
return PIN_0;
else if ((pin_style & PINDEF_MASK) == PINDEF_1)
return PIN_1;
else
return PIN_SIG;
}
// Look to see if the driver is an inverter or constant
IdString drv_type = net->driver.cell->type;
if (drv_type == id_MISTRAL_NOT)
return PIN_INV;
else if (drv_type == id_GND)
return PIN_0;
else if (drv_type == id_VCC)
return PIN_1;
else
return PIN_SIG;
}
void uninvert_port(CellInfo *cell, IdString port)
{
// Rewire a port so it is driven by the input to an inverter
NetInfo *net = get_net_or_empty(cell, port);
NPNR_ASSERT(net != nullptr && net->driver.cell != nullptr && net->driver.cell->type == id_MISTRAL_NOT);
CellInfo *inv = net->driver.cell;
disconnect_port(ctx, cell, port);
NetInfo *inv_a = get_net_or_empty(inv, id_A);
if (inv_a != nullptr) {
connect_port(ctx, inv_a, cell, port);
}
}
void process_inv_constants(CellInfo *cell)
{
// TODO: we might need to create missing inputs here in some cases so we can tie them to the correct constant?
// Fold inverters and constants into a cell
for (auto &port : cell->ports) {
// Iterate over all inputs
if (port.second.type != PORT_IN)
continue;
IdString port_name = port.first;
CellPinState req_mux = get_pin_needed_muxval(cell, port_name);
if (req_mux == PIN_SIG) {
// No special setting required, ignore
continue;
}
CellPinStyle pin_style = ctx->get_cell_pin_style(cell, port_name);
if (req_mux == PIN_INV) {
// Pin is inverted. If there is a hard inverter; then use it
if (pin_style & PINOPT_INV) {
uninvert_port(cell, port_name);
cell->pin_data[port_name].state = PIN_INV;
}
} else if (req_mux == PIN_0 || req_mux == PIN_1) {
// Pin is tied to a constant
// If there is a hard constant option; use it
if ((pin_style & int(req_mux)) == req_mux) {
disconnect_port(ctx, cell, port_name);
cell->pin_data[port_name].state = req_mux;
} else {
disconnect_port(ctx, cell, port_name);
// There is no hard constant, we need to connect it to the relevant soft-constant net
connect_port(ctx, (req_mux == PIN_1) ? vcc_net : gnd_net, cell, port_name);
}
}
}
}
void trim_design()
{
// Remove unused inverters and high/low drivers
std::vector<IdString> trim_cells;
std::vector<IdString> trim_nets;
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (ci->type != id_MISTRAL_NOT && ci->type != id_GND && ci->type != id_VCC)
continue;
IdString port = (ci->type == id_MISTRAL_NOT) ? id_Q : id_Y;
NetInfo *out = get_net_or_empty(ci, port);
if (out == nullptr) {
trim_cells.push_back(ci->name);
continue;
}
if (!out->users.empty())
continue;
disconnect_port(ctx, ci, id_A);
trim_cells.push_back(ci->name);
trim_nets.push_back(out->name);
}
for (IdString rem_net : trim_nets)
ctx->nets.erase(rem_net);
for (IdString rem_cell : trim_cells)
ctx->cells.erase(rem_cell);
}
void pack_constants()
{
// Iterate through cells
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
// Skip certain cells at this point
if (ci->type != id_MISTRAL_NOT && ci->type != id_GND && ci->type != id_VCC)
process_inv_constants(cell.second);
}
// Special case - SDATA can only be trimmed if SLOAD is low
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (ci->type != id_MISTRAL_FF)
continue;
if (ci->get_pin_state(id_SLOAD) != PIN_0)
continue;
disconnect_port(ctx, ci, id_SDATA);
}
// Remove superfluous inverters and constant drivers
trim_design();
}
void prepare_io()
{
// Find the actual IO buffer corresponding to a port; and copy attributes across to it
// Note that this relies on Yosys to do IO buffer inference, to avoid tristate issues once we get to synthesised
// JSON. In all cases the nextpnr-inserted IO buffers are removed as redundant.
for (auto &port : sorted_ref(ctx->ports)) {
if (!ctx->cells.count(port.first))
log_error("Port '%s' doesn't seem to have a corresponding top level IO\n", ctx->nameOf(port.first));
CellInfo *ci = ctx->cells.at(port.first).get();
PortRef top_port;
top_port.cell = nullptr;
bool is_npnr_iob = false;
if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
// Might have an input buffer (IB etc) connected to it
is_npnr_iob = true;
NetInfo *o = get_net_or_empty(ci, id_O);
if (o == nullptr)
;
else if (o->users.size() > 1)
log_error("Top level pin '%s' has multiple input buffers\n", ctx->nameOf(port.first));
else if (o->users.size() == 1)
top_port = o->users.at(0);
}
if (ci->type == ctx->id("$nextpnr_obuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
// Might have an output buffer (OB etc) connected to it
is_npnr_iob = true;
NetInfo *i = get_net_or_empty(ci, id_I);
if (i != nullptr && i->driver.cell != nullptr) {
if (top_port.cell != nullptr)
log_error("Top level pin '%s' has multiple input/output buffers\n", ctx->nameOf(port.first));
top_port = i->driver;
}
// Edge case of a bidirectional buffer driving an output pin
if (i->users.size() > 2) {
log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first));
} else if (i->users.size() == 2) {
if (top_port.cell != nullptr)
log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first));
for (auto &usr : i->users) {
if (usr.cell->type == ctx->id("$nextpnr_obuf") || usr.cell->type == ctx->id("$nextpnr_iobuf"))
continue;
top_port = usr;
break;
}
}
}
if (!is_npnr_iob)
log_error("Port '%s' doesn't seem to have a corresponding top level IO (internal cell type mismatch)\n",
ctx->nameOf(port.first));
if (top_port.cell == nullptr) {
log_info("Trimming port '%s' as it is unused.\n", ctx->nameOf(port.first));
} else {
// Copy attributes to real IO buffer
if (ctx->io_attr.count(port.first)) {
for (auto &kv : ctx->io_attr.at(port.first)) {
top_port.cell->attrs[kv.first] = kv.second;
}
}
// Make sure that top level net is set correctly
port.second.net = top_port.cell->ports.at(top_port.port).net;
}
// Now remove the nextpnr-inserted buffer
disconnect_port(ctx, ci, id_I);
disconnect_port(ctx, ci, id_O);
ctx->cells.erase(port.first);
}
}
void pack_io()
{
// Step 0: deal with top level inserted IO buffers
prepare_io();
// Stage 1: apply constraints
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
// Iterate through all IO buffer primitives
if (!ctx->is_io_cell(ci->type))
continue;
// We need all IO constrained at the moment, unconstrained IO are rare enough not to care
if (!ci->attrs.count(id_LOC))
log_error("Found unconstrained IO '%s', these are currently unsupported\n", ctx->nameOf(ci));
// Convert package pin constraint to bel constraint
std::string loc = ci->attrs.at(id_LOC).as_string();
if (loc.compare(0, 4, "PIN_") != 0)
log_error("Expecting PIN_-prefixed pin for IO '%s', got '%s'\n", ctx->nameOf(ci), loc.c_str());
auto pin_info = ctx->cyclonev->pin_find_name(loc.substr(4));
if (pin_info == nullptr)
log_error("IO '%s' is constrained to invalid pin '%s'\n", ctx->nameOf(ci), loc.c_str());
BelId bel = ctx->get_io_pin_bel(pin_info);
if (bel == BelId()) {
log_error("IO '%s' is constrained to pin %s which is not a supported IO pin.\n", ctx->nameOf(ci),
loc.c_str());
} else {
log_info("Constraining IO '%s' to pin %s (bel %s)\n", ctx->nameOf(ci), loc.c_str(),
ctx->nameOfBel(bel));
ctx->bindBel(bel, ci, STRENGTH_LOCKED);
}
}
}
void constrain_carries()
{
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (ci->type != id_MISTRAL_ALUT_ARITH)
continue;
const NetInfo *cin = get_net_or_empty(ci, id_CI);
if (cin != nullptr && cin->driver.cell != nullptr)
continue; // not the start of a chain
std::vector<CellInfo *> chain;
CellInfo *cursor = ci;
while (true) {
chain.push_back(cursor);
const NetInfo *co = get_net_or_empty(cursor, id_CO);
if (co == nullptr || co->users.empty())
break;
if (co->users.size() > 1)
log_error("Carry net %s has more than one sink!\n", ctx->nameOf(co));
auto &usr = co->users.at(0);
if (usr.port != id_CI)
log_error("Carry net %s drives port %s, expected CI\n", ctx->nameOf(co), ctx->nameOf(usr.port));
cursor = usr.cell;
}
chain.at(0)->constr_abs_z = true;
chain.at(0)->constr_z = 0;
chain.at(0)->cluster = chain.at(0)->name;
for (int i = 1; i < int(chain.size()); i++) {
chain.at(i)->constr_x = 0;
chain.at(i)->constr_y = -(i / 20);
// 2 COMB, 4 FF per ALM
chain.at(i)->constr_z = ((i / 2) % 10) * 6 + (i % 2);
chain.at(i)->constr_abs_z = true;
chain.at(i)->cluster = chain.at(0)->name;
chain.at(0)->constr_children.push_back(chain.at(i));
}
if (ctx->debug) {
log_info("Chain: \n");
for (int i = 0; i < int(chain.size()); i++) {
auto &c = chain.at(i);
log_info(" i=%d cell=%s dy=%d z=%d ci=%s co=%s\n", i, ctx->nameOf(c), c->constr_y, c->constr_z,
ctx->nameOf(get_net_or_empty(c, id_CI)), ctx->nameOf(get_net_or_empty(c, id_CO)));
}
}
}
// Check we reached all the cells in the above pass
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (ci->type != id_MISTRAL_ALUT_ARITH)
continue;
if (ci->cluster == ClusterId())
log_error("Failed to include arith cell '%s' in any chain (CI=%s)\n", ctx->nameOf(ci),
ctx->nameOf(get_net_or_empty(ci, id_CI)));
}
}
void run()
{
init_constant_nets();
pack_constants();
pack_io();
constrain_carries();
}
};
}; // namespace
bool Arch::pack()
{
MistralPacker packer(getCtx());
packer.run();
assignArchInfo();
return true;
}
NEXTPNR_NAMESPACE_END

67
mistral/pins.cc Normal file
View File

@ -0,0 +1,67 @@
/*
* 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 "log.h"
#include "nextpnr.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
const std::unordered_map<IdString, Arch::CellPinsData> Arch::cell_pins_db = {
// For combinational cells, inversion and tieing can be implemented by manipulating the LUT function
{id_MISTRAL_ALUT2, {{{}, PINSTYLE_COMB}}},
{id_MISTRAL_ALUT3, {{{}, PINSTYLE_COMB}}},
{id_MISTRAL_ALUT4, {{{}, PINSTYLE_COMB}}},
{id_MISTRAL_ALUT5, {{{}, PINSTYLE_COMB}}},
{id_MISTRAL_ALUT6, {{{}, PINSTYLE_COMB}}},
{id_MISTRAL_ALUT_ARITH,
{// Leave carry chain alone, other than disconnecting a ground constant
{id_CI, PINSTYLE_CARRY},
{{}, PINSTYLE_COMB}}},
{id_MISTRAL_FF,
{
{id_CLK, PINSTYLE_CLK},
{id_ENA, PINSTYLE_CE},
{id_ACLR, PINSTYLE_RST},
{id_SCLR, PINSTYLE_RST},
{id_SLOAD, PINSTYLE_RST},
{id_SDATA, PINSTYLE_DEDI},
{id_DATAIN, PINSTYLE_INP},
}},
};
CellPinStyle Arch::get_cell_pin_style(const CellInfo *cell, IdString port) const
{
// Look up the pin style in the cell database
auto fnd_cell = cell_pins_db.find(cell->type);
if (fnd_cell == cell_pins_db.end())
return PINSTYLE_NONE;
auto fnd_port = fnd_cell->second.find(port);
if (fnd_port != fnd_cell->second.end())
return fnd_port->second;
// If there isn't an exact port match, then the empty IdString
// represents a wildcard default match
auto fnd_default = fnd_cell->second.find({});
if (fnd_default != fnd_cell->second.end())
return fnd_default->second;
return PINSTYLE_NONE;
}
NEXTPNR_NAMESPACE_END

281
mistral/qsf.cc Normal file
View File

@ -0,0 +1,281 @@
/*
* 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 "log.h"
#include "nextpnr.h"
#include "util.h"
#include <iterator>
NEXTPNR_NAMESPACE_BEGIN
namespace {
struct QsfOption
{
std::string name; // name, excluding the initial '-'
int arg_count; // number of arguments that follow the option
bool required; // error out if this option isn't passed
};
typedef std::unordered_map<std::string, std::vector<std::string>> option_map_t;
struct QsfCommand
{
std::string name; // name of the command
std::vector<QsfOption> options; // list of "-options"
int pos_arg_count; // number of positional arguments expected to follow the command, -1 for any
std::function<void(Context *ctx, const option_map_t &options, const std::vector<std::string> &pos_args)> func;
};
void set_location_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector<std::string> &pos_args)
{
ctx->io_attr[ctx->id(options.at("to").at(0))][id_LOC] = pos_args.at(0);
}
void set_instance_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector<std::string> &pos_args)
{
ctx->io_attr[ctx->id(options.at("to").at(0))][ctx->id(options.at("name").at(0))] = pos_args.at(0);
}
void set_global_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector<std::string> &pos_args)
{
// TODO
}
static const std::vector<QsfCommand> commands = {
{"set_location_assignment", {{"to", 1, true}}, 1, set_location_assignment_cmd},
{"set_instance_assignment",
{{"to", 1, true}, {"name", 1, true}, {"section_id", 1, false}},
1,
set_instance_assignment_cmd},
{"set_global_assignment",
{{"name", 1, true}, {"section_id", 1, false}, {"rise", 0, false}, {"fall", 0, false}},
1,
set_global_assignment_cmd},
};
struct QsfParser
{
std::string buf;
int pos = 0;
int lineno = 0;
Context *ctx;
QsfParser(const std::string &buf, Context *ctx) : buf(buf), ctx(ctx){};
inline bool eof() const { return pos == int(buf.size()); }
inline char peek() const { return buf.at(pos); }
inline char get()
{
char c = buf.at(pos++);
if (c == '\n')
++lineno;
return c;
}
std::string get(int n)
{
std::string s = buf.substr(pos, n);
pos += n;
return s;
}
// If next char matches c, take it from the stream and return true
bool check_get(char c)
{
if (peek() == c) {
get();
return true;
} else {
return false;
}
}
// If next char matches any in chars, take it from the stream and return true
bool check_get_any(const std::string &chrs)
{
char c = peek();
if (chrs.find(c) != std::string::npos) {
get();
return true;
} else {
return false;
}
}
inline void skip_blank(bool nl = false)
{
while (!eof() && check_get_any(nl ? " \t\n\r" : " \t"))
;
}
// Return true if end of line (or file)
inline bool skip_check_eol()
{
skip_blank(false);
if (eof())
return true;
char c = peek();
// Comments count as end of line
if (c == '#') {
get();
while (!eof() && peek() != '\n' && peek() != '\r')
get();
return true;
}
if (c == ';') {
// Forced end of line
get();
return true;
}
return (c == '\n' || c == '\r');
}
// We need to distinguish between quoted and unquoted strings, the former don't count as options
struct StringVal
{
std::string str;
bool is_quoted = false;
};
inline StringVal get_str()
{
StringVal s;
skip_blank(false);
if (eof())
return {"", false};
bool in_quotes = false, in_braces = false, escaped = false;
char c = get();
if (c == '"') {
in_quotes = true;
s.is_quoted = true;
} else if (c == '{') {
in_braces = true;
s.is_quoted = true;
} else {
s.str += c;
}
while (!eof()) {
char c = peek();
if (!in_quotes && !in_braces && !escaped && (std::isblank(c) || c == '\n' || c == '\r')) {
break;
}
get();
if (escaped) {
s.str += c;
escaped = false;
} else if ((in_quotes && c == '"') || (in_braces && c == '}')) {
break;
} else if (c == '\\') {
escaped = true;
} else {
s.str += c;
}
}
return s;
}
std::vector<StringVal> get_arguments()
{
std::vector<StringVal> args;
while (!skip_check_eol()) {
args.push_back(get_str());
}
skip_blank(true);
return args;
}
void evaluate(const std::vector<StringVal> &args)
{
if (args.empty())
return;
auto cmd_name = args.at(0).str;
auto fnd_cmd =
std::find_if(commands.begin(), commands.end(), [&](const QsfCommand &c) { return c.name == cmd_name; });
if (fnd_cmd == commands.end()) {
log_warning("Ignoring unknown command '%s' (line %d)\n", cmd_name.c_str(), lineno);
return;
}
option_map_t opt;
std::vector<std::string> pos_args;
for (size_t i = 1; i < args.size(); i++) {
auto arg = args.at(i);
if (arg.str.at(0) == '-' && !arg.is_quoted) {
for (auto &opt_data : fnd_cmd->options) {
if (arg.str.compare(1, std::string::npos, opt_data.name) != 0)
continue;
opt[opt_data.name]; // create empty entry, even if 0 arguments
for (int j = 0; j < opt_data.arg_count; j++) {
++i;
if (i >= args.size())
log_error("Unexpected end of argument list to option '%s' (line %d)\n", arg.str.c_str(),
lineno);
opt[opt_data.name].push_back(args.at(i).str);
}
goto done;
}
log_error("Unknown option '%s' to command '%s' (line %d)\n", arg.str.c_str(), cmd_name.c_str(), lineno);
done:;
} else {
// positional argument
pos_args.push_back(arg.str);
}
}
// Check positional argument count
if (int(pos_args.size()) != fnd_cmd->pos_arg_count && fnd_cmd->pos_arg_count != -1) {
log_error("Expected %d positional arguments to command '%s', got %d (line %d)\n", fnd_cmd->pos_arg_count,
cmd_name.c_str(), int(pos_args.size()), lineno);
}
// Check required options
for (auto &opt_data : fnd_cmd->options) {
if (opt_data.required && !opt.count(opt_data.name))
log_error("Missing required option '%s' to command '%s' (line %d)\n", opt_data.name.c_str(),
cmd_name.c_str(), lineno);
}
// Execute
fnd_cmd->func(ctx, opt, pos_args);
}
void operator()()
{
while (!eof()) {
skip_blank(true);
auto args = get_arguments();
evaluate(args);
}
}
};
}; // namespace
void Arch::read_qsf(std::istream &in)
{
std::string buf(std::istreambuf_iterator<char>(in), {});
QsfParser(buf, getCtx())();
}
NEXTPNR_NAMESPACE_END