Merge pull request #707 from gatecat/cyclonev
mistral: Initial Cyclone V support
This commit is contained in:
commit
47b4e42b1c
@ -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
24
.github/ci/build_mistral.sh
vendored
Normal 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
33
.github/workflows/mistral_ci.yml
vendored
Normal 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
|
@ -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})
|
||||
|
@ -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!
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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&'");
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
1
gui/mistral/family.cmake
Normal file
@ -0,0 +1 @@
|
||||
target_include_directories(gui_mistral PRIVATE ${MISTRAL_ROOT}/lib)
|
86
gui/mistral/mainwindow.cc
Normal file
86
gui/mistral/mainwindow.cc
Normal 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
49
gui/mistral/mainwindow.h
Normal 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
2
gui/mistral/nextpnr.qrc
Normal file
@ -0,0 +1,2 @@
|
||||
<RCC>
|
||||
</RCC>
|
487
mistral/arch.cc
Normal file
487
mistral/arch.cc
Normal 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
550
mistral/arch.h
Normal 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
|
87
mistral/arch_pybindings.cc
Normal file
87
mistral/arch_pybindings.cc
Normal 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
125
mistral/arch_pybindings.h
Normal 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
33
mistral/archdefs.cc
Normal 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
236
mistral/archdefs.h
Normal 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
100
mistral/base_bitstream.cc
Normal 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
361
mistral/bitstream.cc
Normal 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
78
mistral/constids.inc
Normal 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
14
mistral/family.cmake
Normal 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
46
mistral/globals.cc
Normal 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
61
mistral/io.cc
Normal 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
969
mistral/lab.cc
Normal 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
105
mistral/main.cc
Normal 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
365
mistral/pack.cc
Normal 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
67
mistral/pins.cc
Normal 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
281
mistral/qsf.cc
Normal 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
|
Loading…
Reference in New Issue
Block a user