Merge pull request #578 from YosysHQ/machxo2-rebase

machxo2, rebased and updated
This commit is contained in:
gatecat 2021-02-15 09:39:56 +00:00 committed by GitHub
commit 065f46daeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 4511 additions and 3 deletions

View File

@ -47,7 +47,7 @@ RUN set -e -x ;\
cd /usr/local/src ;\
git clone --recursive https://github.com/YosysHQ/prjtrellis.git ;\
cd prjtrellis ;\
git reset --hard 7831b54f619d6695855525fca776543b7c827704 ;\
git reset --hard 210a0a72757d57b278ac7397ae6b14729f149b10 ;\
cd libtrellis ;\
cmake . ;\
make -j $(nproc) ;\

1
.gitignore vendored
View File

@ -6,6 +6,7 @@
/nextpnr-nexus*
/nextpnr-fpga_interchange*
/nextpnr-gowin*
/nextpnr-machxo2*
cmake-build-*/
Makefile
cmake_install.cmake

View File

@ -66,9 +66,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)
set(FAMILIES generic ice40 ecp5 nexus gowin fpga_interchange machxo2)
set(STABLE_FAMILIES generic ice40 ecp5)
set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange)
set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange machxo2)
set(ARCH "" CACHE STRING "Architecture family for nextpnr build")
set_property(CACHE ARCH PROPERTY STRINGS ${FAMILIES})
@ -297,6 +297,7 @@ file(GLOB_RECURSE CLANGFORMAT_FILES *.cc *.h)
string(REGEX REPLACE "[^;]*/ice40/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
string(REGEX REPLACE "[^;]*/ecp5/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
string(REGEX REPLACE "[^;]*nexus/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
string(REGEX REPLACE "[^;]*/machxo2/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
string(REGEX REPLACE "[^;]*/3rdparty[^;]*" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
string(REGEX REPLACE "[^;]*/generated[^;]*" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")

0
gui/machxo2/family.cmake Normal file
View File

107
gui/machxo2/mainwindow.cc Normal file
View File

@ -0,0 +1,107 @@
/*
* 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-xo2 - [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()
{
QMap<QString, int> arch;
if (Arch::is_available(ArchArgs::LCMXO2_256HC))
arch.insert("LCMXO2-256HC", ArchArgs::LCMXO2_256HC);
if (Arch::is_available(ArchArgs::LCMXO2_640HC))
arch.insert("LCMXO2-640HC", ArchArgs::LCMXO2_640HC);
if (Arch::is_available(ArchArgs::LCMXO2_1200HC))
arch.insert("LCMXO2-1200HC", ArchArgs::LCMXO2_1200HC);
if (Arch::is_available(ArchArgs::LCMXO2_2000HC))
arch.insert("LCMXO2-2000HC", ArchArgs::LCMXO2_2000HC);
if (Arch::is_available(ArchArgs::LCMXO2_4000HC))
arch.insert("LCMXO2-4000HC", ArchArgs::LCMXO2_4000HC);
if (Arch::is_available(ArchArgs::LCMXO2_7000HC))
arch.insert("LCMXO2-7000HC", ArchArgs::LCMXO2_7000HC);
bool ok;
QString item = QInputDialog::getItem(this, "Select new context", "Chip:", arch.keys(), 0, false, &ok);
if (ok && !item.isEmpty()) {
ArchArgs chipArgs;
chipArgs.type = (ArchArgs::ArchArgsTypes)arch.value(item);
QStringList packages;
for (auto package : Arch::get_supported_packages(chipArgs.type))
packages.append(QLatin1String(package.data(), package.size()));
QString package = QInputDialog::getItem(this, "Select package", "Package:", packages, 0, false, &ok);
if (ok && !item.isEmpty()) {
handler->clear();
currentProj = "";
disableActions();
chipArgs.package = package.toStdString().c_str();
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-xo2 - " + ctx->getChipName();
setWindowTitle(title.c_str());
}
void MainWindow::onDisableActions() {}
void MainWindow::onUpdateActions() {}
NEXTPNR_NAMESPACE_END

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

@ -0,0 +1,49 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "../basewindow.h"
NEXTPNR_NAMESPACE_BEGIN
class MainWindow : public BaseMainWindow
{
Q_OBJECT
public:
explicit MainWindow(std::unique_ptr<Context> context, CommandHandler *handler, QWidget *parent = 0);
virtual ~MainWindow();
public:
void createMenu();
void onDisableActions() override;
void onUpdateActions() override;
protected Q_SLOTS:
void new_proj() override;
void newContext(Context *ctx);
};
NEXTPNR_NAMESPACE_END
#endif // MAINWINDOW_H

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

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

1
machxo2/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
chipdb/

91
machxo2/CMakeLists.txt Normal file
View File

@ -0,0 +1,91 @@
cmake_minimum_required(VERSION 3.5)
project(chipdb-machxo2 NONE)
# set(ALL_MACHXO2_DEVICES 256 640 1200 2000 4000 7000)
set(ALL_MACHXO2_DEVICES 1200)
set(MACHXO2_DEVICES 1200 CACHE STRING
"Include support for these MachXO2 devices (available: ${ALL_MACHXO2_DEVICES})")
message(STATUS "Enabled MachXO2 devices: ${MACHXO2_DEVICES}")
if(DEFINED MACHXO2_CHIPDB)
add_custom_target(chipdb-machxo2-bbas ALL)
else()
find_package(PythonInterp 3.5 REQUIRED)
# shared among all families
set(SERIALIZE_CHIPDBS TRUE CACHE BOOL
"Serialize device data preprocessing to minimize memory use")
set(TRELLIS_PROGRAM_PREFIX "" CACHE STRING
"Trellis name prefix")
if(TRELLIS_PROGRAM_PREFIX)
message(STATUS "Trellis program prefix: ${TRELLIS_PROGRAM_PREFIX}")
endif()
set(TRELLIS_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX} CACHE STRING
"Trellis install prefix")
message(STATUS "Trellis install prefix: ${TRELLIS_INSTALL_PREFIX}")
if(NOT DEFINED TRELLIS_LIBDIR)
if(WIN32)
set(pytrellis_lib pytrellis.pyd)
else()
set(pytrellis_lib pytrellis${CMAKE_SHARED_LIBRARY_SUFFIX})
endif()
find_path(TRELLIS_LIBDIR ${pytrellis_lib}
HINTS ${TRELLIS_INSTALL_PREFIX}/lib/${TRELLIS_PROGRAM_PREFIX}trellis
PATHS ${CMAKE_SYSTEM_LIBRARY_PATH} ${CMAKE_LIBRARY_PATH}
PATH_SUFFIXES ${TRELLIS_PROGRAM_PREFIX}trellis
DOC "Location of the pytrellis library")
if(NOT TRELLIS_LIBDIR)
message(FATAL_ERROR "Failed to locate the pytrellis library")
endif()
endif()
message(STATUS "Trellis library directory: ${TRELLIS_LIBDIR}")
if(NOT DEFINED TRELLIS_DATADIR)
set(TRELLIS_DATADIR ${TRELLIS_INSTALL_PREFIX}/share/${TRELLIS_PROGRAM_PREFIX}trellis)
endif()
message(STATUS "Trellis data directory: ${TRELLIS_DATADIR}")
set(all_device_bbas)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chipdb)
foreach(device ${MACHXO2_DEVICES})
if(NOT device IN_LIST ALL_MACHXO2_DEVICES)
message(FATAL_ERROR "Device ${device} is not a supported MachXO2 device")
endif()
set(device_bba chipdb/chipdb-${device}.bba)
add_custom_command(
OUTPUT ${device_bba}
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/facade_import.py
-L ${TRELLIS_LIBDIR}
-L ${TRELLIS_DATADIR}/util/common
-L ${TRELLIS_DATADIR}/timing/util
-p ${CMAKE_CURRENT_SOURCE_DIR}/constids.inc
${device}
> ${device_bba}.new
# atomically update
COMMAND ${CMAKE_COMMAND} -E rename ${device_bba}.new ${device_bba}
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/facade_import.py
${CMAKE_CURRENT_SOURCE_DIR}/constids.inc
${PREVIOUS_CHIPDB_TARGET}
VERBATIM)
list(APPEND all_device_bbas ${device_bba})
if(SERIALIZE_CHIPDBS)
set(PREVIOUS_CHIPDB_TARGET ${CMAKE_CURRENT_BINARY_DIR}/${device_bba})
endif()
endforeach()
add_custom_target(chipdb-machxo2-bbas ALL DEPENDS ${all_device_bbas})
get_directory_property(has_parent PARENT_DIRECTORY)
if(has_parent)
set(MACHXO2_CHIPDB ${CMAKE_CURRENT_BINARY_DIR}/chipdb PARENT_SCOPE)
# serialize chipdb build across multiple architectures
set(PREVIOUS_CHIPDB_TARGET chipdb-machxo2-bbas PARENT_SCOPE)
else()
message(STATUS "Build nextpnr with -DMACHXO2_CHIPDB=${CMAKE_CURRENT_BINARY_DIR}")
endif()
endif()

105
machxo2/README.md Normal file
View File

@ -0,0 +1,105 @@
# `nextpnr-machxo2`
_Experimental_ FOSS Place And Route backend for the Lattice MachXO2 family of
FPGAs. Fuzzing takes place as a subproject of [`prjtrellis`](https://github.com/YosysHQ/prjtrellis).
Known to work:
* Basic routing from pads to SLICEs and back!
* Basic packing of one type of FF and LUT into _half_ of a SLICE!
* Using the internal oscillator `OSCH` as a clock
* `LOGIC` SLICE mode
Things that probably work but are untested:
* Any non-3.3V I/O standard that doesn't use bank VREFs.
Things remaining to do include (but not limited to):
* More intelligent and efficient packing
* Global Routing (exists in database/sim models, `nextpnr-machxo2` doesn't use
it yet)
* Secondary High Fanout Nets
* Edge Clocks (clock pads work, but not routed to global routing yet)
* PLLs
* Synchronous Release Global Set/Reset Interface (`SGSR`)
* Embedded Function Block (`EFB`)
* All DDR-related functionality
* Bank VREFs
* Embedded Block RAM (`EBR`)
* `CCU2` and `DPRAM` SLICE modes
## Quick Start
The following commands are known to work on a near-fresh Linux Mint system
(thank you [securelyfitz](https://twitter.com/securelyfitz)!):
### Prerequisites
```
sudo apt install cmake clang-format libboost-all-dev build-essential
qt5-default libeigen3-dev build-essential clang bison flex libreadline-dev
gawk tcl-dev libffi-dev git graphviz xdot pkg-config python3
libboost-system-dev libboost-python-dev libboost-filesystem-dev zlib1g-dev
python3-setuptools python3-serial
```
### Installation
Use an empty directory to hold all the cloned repositories. Upstream repos
can be used as well (e.g. [`YosysHQ/prjtrellis`](https://github.com/YosysHQ/prjtrellis),
etc.).
```
git clone git@github.com:cr1901/prjtrellis.git
cd prjtrellis
git checkout facade
git submodule update --init --recursive
cd libtrellis
cmake -DCMAKE_INSTALL_PREFIX=/usr
make -j 8
sudo make install
cd ../../
git clone git@github.com:cr1901/yosys.git
cd yosys/
git checkout machxo2
make config-gcc
make
sudo make install
cd ../
git clone git@github.com:tinyfpga/TinyFPGA-A-Programmer.git
cd TinyFPGA-A-Programmer/
sudo python setup.py install
cd ../
git clone git@github.com:cr1901/nextpnr.git
cd nextpnr
git checkout machxo2
git submodule update --init --recursive
cmake . -DARCH=machxo2 -DBUILD_GUI=OFF -DTRELLIS_INSTALL_PREFIX=/usr -DBUILD_PYTHON=OFF -DBUILD_HEAP=OFF
make
```
Although uncommon, the `facade` and `machxo2` branches of the above repos are
occassionally rebased; use `git pull -f` if necessary.
### Demo
If you have a [TinyFPGA Ax2](https://store.tinyfpga.com/products/tinyfpga-a2) board
with the [TinyFPGA Programmer](https://store.tinyfpga.com/products/tinyfpga-programmer),
the following script will build a blinky bitstream and load it onto the
MachXO2; the gateware will flash the LED!
```
cd machxo2/examples/
sh demo.sh tinyfpga
```
The `tinyfpga.v` code used in `demo.sh` is slightly modified from the
[user's guide](https://tinyfpga.com/a-series-guide.html) to accommodate
`(* LOC = "pin" *)` constraints and the built-in user LED.

494
machxo2/arch.cc Normal file
View File

@ -0,0 +1,494 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Claire Xen <claire@symbioticeda.com>
* Copyright (C) 2021 William D. Jones <wjones@wdj-consulting.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 <iostream>
#include <math.h>
#include "embed.h"
#include "nextpnr.h"
#include "placer1.h"
#include "placer_heap.h"
#include "router1.h"
#include "router2.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
// -----------------------------------------------------------------------
void IdString::initialize_arch(const BaseCtx *ctx)
{
#define X(t) initialize_add(ctx, #t, ID_##t);
#include "constids.inc"
#undef X
}
// ---------------------------------------------------------------
static const ChipInfoPOD *get_chip_info(ArchArgs::ArchArgsTypes chip)
{
std::string chipdb;
if (chip == ArchArgs::LCMXO2_256HC) {
chipdb = "machxo2/chipdb-256.bin";
} else if (chip == ArchArgs::LCMXO2_640HC) {
chipdb = "machxo2/chipdb-640.bin";
} else if (chip == ArchArgs::LCMXO2_1200HC) {
chipdb = "machxo2/chipdb-1200.bin";
} else if (chip == ArchArgs::LCMXO2_2000HC) {
chipdb = "machxo2/chipdb-2000.bin";
} else if (chip == ArchArgs::LCMXO2_4000HC) {
chipdb = "machxo2/chipdb-4000.bin";
} else if (chip == ArchArgs::LCMXO2_7000HC) {
chipdb = "machxo2/chipdb-7000.bin";
} else {
log_error("Unknown chip\n");
}
auto ptr = reinterpret_cast<const RelPtr<ChipInfoPOD> *>(get_chipdb(chipdb));
if (ptr == nullptr)
return nullptr;
return ptr->get();
}
// ---------------------------------------------------------------
Arch::Arch(ArchArgs args) : args(args)
{
chip_info = get_chip_info(args.type);
if (chip_info == nullptr)
log_error("Unsupported MachXO2 chip type.\n");
if (chip_info->const_id_count != DB_CONST_ID_COUNT)
log_error("Chip database 'bba' and nextpnr code are out of sync; please rebuild (or contact distribution "
"maintainer)!\n");
package_info = nullptr;
for (int i = 0; i < chip_info->num_packages; i++) {
if (args.package == chip_info->package_info[i].name.get()) {
package_info = &(chip_info->package_info[i]);
break;
}
}
if (!package_info)
log_error("Unsupported package '%s' for '%s'.\n", args.package.c_str(), getChipName().c_str());
BaseArch::init_cell_types();
BaseArch::init_bel_buckets();
for (int i = 0; i < chip_info->width; i++)
x_ids.push_back(id(stringf("X%d", i)));
for (int i = 0; i < chip_info->height; i++)
y_ids.push_back(id(stringf("Y%d", i)));
for (int i = 0; i < chip_info->width; i++) {
IdString x_id = id(stringf("X%d", i));
x_ids.push_back(x_id);
id_to_x[x_id] = i;
}
for (int i = 0; i < chip_info->height; i++) {
IdString y_id = id(stringf("Y%d", i));
y_ids.push_back(y_id);
id_to_y[y_id] = i;
}
}
bool Arch::is_available(ArchArgs::ArchArgsTypes chip) { return get_chip_info(chip) != nullptr; }
std::vector<std::string> Arch::get_supported_packages(ArchArgs::ArchArgsTypes chip)
{
const ChipInfoPOD *chip_info = get_chip_info(chip);
std::vector<std::string> pkgs;
for (int i = 0; i < chip_info->num_packages; i++) {
pkgs.push_back(chip_info->package_info[i].name.get());
}
return pkgs;
}
std::string Arch::getChipName() const
{
if (args.type == ArchArgs::LCMXO2_256HC) {
return "LCMXO2-256HC";
} else if (args.type == ArchArgs::LCMXO2_640HC) {
return "LCMXO2-640HC";
} else if (args.type == ArchArgs::LCMXO2_1200HC) {
return "LCMXO2-1200HC";
} else if (args.type == ArchArgs::LCMXO2_2000HC) {
return "LCMXO2-2000HC";
} else if (args.type == ArchArgs::LCMXO2_4000HC) {
return "LCMXO2-4000HC";
} else if (args.type == ArchArgs::LCMXO2_7000HC) {
return "LCMXO2-7000HC";
} else {
log_error("Unknown chip\n");
}
}
std::string Arch::get_full_chip_name() const
{
std::string name = getChipName();
name += "-";
switch (args.speed) {
case ArchArgs::SPEED_1:
name += "1";
break;
case ArchArgs::SPEED_2:
name += "2";
break;
case ArchArgs::SPEED_3:
name += "3";
case ArchArgs::SPEED_4:
name += "4";
break;
case ArchArgs::SPEED_5:
name += "5";
break;
case ArchArgs::SPEED_6:
name += "6";
break;
}
name += args.package;
return name;
}
IdString Arch::archArgsToId(ArchArgs args) const
{
if (args.type == ArchArgs::LCMXO2_256HC) {
return id("lcmxo2_256hc");
} else if (args.type == ArchArgs::LCMXO2_640HC) {
return id("lcmxo2_640hc");
} else if (args.type == ArchArgs::LCMXO2_1200HC) {
return id("lcmxo2_1200hc");
} else if (args.type == ArchArgs::LCMXO2_2000HC) {
return id("lcmxo2_2000hc");
} else if (args.type == ArchArgs::LCMXO2_4000HC) {
return id("lcmxo2_4000hc");
} else if (args.type == ArchArgs::LCMXO2_7000HC) {
return id("lcmxo2_7000hc");
}
return IdString();
}
// ---------------------------------------------------------------
BelId Arch::getBelByName(IdStringList name) const
{
if (name.size() != 3)
return BelId();
BelId ret;
Location loc;
loc.x = id_to_x.at(name[0]);
loc.y = id_to_y.at(name[1]);
ret.location = loc;
const TileTypePOD *loci = tile_info(ret);
for (int i = 0; i < loci->num_bels; i++) {
if (std::strcmp(loci->bel_data[i].name.get(), name[2].c_str(this)) == 0) {
ret.index = i;
return ret;
}
}
return BelId();
}
BelId Arch::getBelByLocation(Loc loc) const
{
BelId ret;
if (loc.x >= chip_info->width || loc.y >= chip_info->height)
return BelId();
ret.location.x = loc.x;
ret.location.y = loc.y;
const TileTypePOD *tilei = tile_info(ret);
for (int i = 0; i < tilei->num_bels; i++) {
if (tilei->bel_data[i].z == loc.z) {
ret.index = i;
return ret;
}
}
return BelId();
}
BelRange Arch::getBelsByTile(int x, int y) const
{
BelRange br;
br.b.cursor_tile = y * chip_info->width + x;
br.e.cursor_tile = y * chip_info->width + x;
br.b.cursor_index = 0;
br.e.cursor_index = chip_info->tiles[y * chip_info->width + x].num_bels - 1;
br.b.chip = chip_info;
br.e.chip = chip_info;
if (br.e.cursor_index == -1)
++br.e.cursor_index;
else
++br.e;
return br;
}
bool Arch::getBelGlobalBuf(BelId bel) const { return false; }
WireId Arch::getBelPinWire(BelId bel, IdString pin) const
{
NPNR_ASSERT(bel != BelId());
int num_bel_wires = tile_info(bel)->bel_data[bel.index].num_bel_wires;
const BelWirePOD *bel_wires = &*tile_info(bel)->bel_data[bel.index].bel_wires;
for (int i = 0; i < num_bel_wires; i++)
if (bel_wires[i].port == pin.index) {
WireId ret;
ret.location.x = bel_wires[i].rel_wire_loc.x;
ret.location.y = bel_wires[i].rel_wire_loc.y;
ret.index = bel_wires[i].wire_index;
return ret;
}
return WireId();
}
PortType Arch::getBelPinType(BelId bel, IdString pin) const
{
NPNR_ASSERT(bel != BelId());
int num_bel_wires = tile_info(bel)->bel_data[bel.index].num_bel_wires;
const BelWirePOD *bel_wires = &*tile_info(bel)->bel_data[bel.index].bel_wires;
for (int i = 0; i < num_bel_wires; i++)
if (bel_wires[i].port == pin.index)
return PortType(bel_wires[i].dir);
return PORT_INOUT;
}
std::vector<IdString> Arch::getBelPins(BelId bel) const
{
std::vector<IdString> ret;
NPNR_ASSERT(bel != BelId());
int num_bel_wires = tile_info(bel)->bel_data[bel.index].num_bel_wires;
const BelWirePOD *bel_wires = &*tile_info(bel)->bel_data[bel.index].bel_wires;
for (int i = 0; i < num_bel_wires; i++) {
IdString id(bel_wires[i].port);
ret.push_back(id);
}
return ret;
}
// ---------------------------------------------------------------
BelId Arch::getPackagePinBel(const std::string &pin) const
{
for (int i = 0; i < package_info->num_pins; i++) {
if (package_info->pin_data[i].name.get() == pin) {
BelId bel;
bel.location = package_info->pin_data[i].abs_loc;
bel.index = package_info->pin_data[i].bel_index;
return bel;
}
}
return BelId();
}
// ---------------------------------------------------------------
WireId Arch::getWireByName(IdStringList name) const
{
if (name.size() != 3)
return WireId();
WireId ret;
Location loc;
loc.x = id_to_x.at(name[0]);
loc.y = id_to_y.at(name[1]);
ret.location = loc;
const TileTypePOD *loci = tile_info(ret);
for (int i = 0; i < loci->num_wires; i++) {
if (std::strcmp(loci->wire_data[i].name.get(), name[2].c_str(this)) == 0) {
ret.index = i;
return ret;
}
}
return WireId();
}
// ---------------------------------------------------------------
PipId Arch::getPipByName(IdStringList name) const
{
if (name.size() != 3)
return PipId();
auto it = pip_by_name.find(name);
if (it != pip_by_name.end())
return it->second;
PipId ret;
Location loc;
std::string basename;
loc.x = id_to_x.at(name[0]);
loc.y = id_to_y.at(name[1]);
ret.location = loc;
const TileTypePOD *loci = tile_info(ret);
for (int i = 0; i < loci->num_pips; i++) {
PipId curr;
curr.location = loc;
curr.index = i;
pip_by_name[getPipName(curr)] = curr;
}
if (pip_by_name.find(name) == pip_by_name.end())
NPNR_ASSERT_FALSE_STR("no pip named " + name.str(getCtx()));
return pip_by_name[name];
}
IdStringList Arch::getPipName(PipId pip) const
{
auto &pip_data = tile_info(pip)->pips_data[pip.index];
WireId src = getPipSrcWire(pip), dst = getPipDstWire(pip);
const char *src_name = tile_info(src)->wire_data[src.index].name.get();
const char *dst_name = tile_info(dst)->wire_data[dst.index].name.get();
std::string pip_name =
stringf("%d_%d_%s->%d_%d_%s", pip_data.src.x - pip.location.x, pip_data.src.y - pip.location.y, src_name,
pip_data.dst.x - pip.location.x, pip_data.dst.y - pip.location.y, dst_name);
std::array<IdString, 3> ids{x_ids.at(pip.location.x), y_ids.at(pip.location.y), id(pip_name)};
return IdStringList(ids);
}
// ---------------------------------------------------------------
delay_t Arch::estimateDelay(WireId src, WireId dst) const
{
// Taxicab distance multiplied by pipDelay (0.01) and fake wireDelay (0.01).
// TODO: This function will not work well for entrance to global routing,
// as the entrances are located physically far from the DCCAs.
return (abs(dst.location.x - src.location.x) + abs(dst.location.y - src.location.y)) * (0.01 + 0.01);
}
delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
{
BelId src = net_info->driver.cell->bel;
BelId dst = sink.cell->bel;
NPNR_ASSERT(src != BelId());
NPNR_ASSERT(dst != BelId());
// TODO: Same deal applies here as with estimateDelay.
return (abs(dst.location.x - src.location.x) + abs(dst.location.y - src.location.y)) * (0.01 + 0.01);
}
ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const
{
ArcBounds bb;
bb.x0 = std::min(src.location.x, dst.location.x);
bb.y0 = std::min(src.location.y, dst.location.y);
bb.x1 = std::max(src.location.x, dst.location.x);
bb.y1 = std::max(src.location.y, dst.location.y);
return bb;
}
// ---------------------------------------------------------------
bool Arch::place()
{
std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
if (placer == "sa") {
bool retVal = placer1(getCtx(), Placer1Cfg(getCtx()));
getCtx()->settings[getCtx()->id("place")] = 1;
archInfoToAttributes();
return retVal;
} else if (placer == "heap") {
PlacerHeapCfg cfg(getCtx());
cfg.ioBufTypes.insert(id_FACADE_IO);
bool retVal = placer_heap(getCtx(), cfg);
getCtx()->settings[getCtx()->id("place")] = 1;
archInfoToAttributes();
return retVal;
} else {
log_error("MachXO2 architecture does not support placer '%s'\n", placer.c_str());
}
}
bool Arch::route()
{
std::string router = str_or_default(settings, id("router"), defaultRouter);
bool result;
if (router == "router1") {
result = router1(getCtx(), Router1Cfg(getCtx()));
} else if (router == "router2") {
router2(getCtx(), Router2Cfg(getCtx()));
result = true;
} else {
log_error("MachXO2 architecture does not support router '%s'\n", router.c_str());
}
getCtx()->settings[getCtx()->id("route")] = 1;
archInfoToAttributes();
return result;
}
// ---------------------------------------------------------------
bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
{
// FIXME: Unlike ECP5, SLICEs in a given tile do not share a clock, so
// any SLICE Cell is valid for any BEL, even if some cells are already
// bound to BELs in the tile. However, this may need to be filled in once
// more than one LUT4 and DFF type is supported.
return true;
}
bool Arch::isBelLocationValid(BelId bel) const
{
// FIXME: Same deal as isValidBelForCell.
return true;
}
#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 = "router1";
const std::vector<std::string> Arch::availableRouters = {"router1", "router2"};
bool Arch::cells_compatible(const CellInfo **cells, int count) const { return false; }
std::vector<std::pair<std::string, std::string>> Arch::get_tiles_at_location(int row, int col)
{
std::vector<std::pair<std::string, std::string>> ret;
auto &tileloc = chip_info->tile_info[row * chip_info->width + col];
for (int i = 0; i < tileloc.num_tiles; i++) {
ret.push_back(std::make_pair(tileloc.tile_names[i].name.get(),
chip_info->tiletype_names[tileloc.tile_names[i].type_idx].get()));
}
return ret;
}
NEXTPNR_NAMESPACE_END

699
machxo2/arch.h Normal file
View File

@ -0,0 +1,699 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Claire Xen <claire@symbioticeda.com>
* Copyright (C) 2021 William D. Jones <wjones@wdj-consulting.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 NEXTPNR_H
#error Include "arch.h" via "nextpnr.h" only.
#endif
NEXTPNR_NAMESPACE_BEGIN
/**** Everything in this section must be kept in sync with chipdb.py ****/
template <typename T> struct RelPtr
{
int32_t offset;
// void set(const T *ptr) {
// offset = reinterpret_cast<const char*>(ptr) -
// reinterpret_cast<const char*>(this);
// }
const T *get() const { return reinterpret_cast<const T *>(reinterpret_cast<const char *>(this) + offset); }
const T &operator[](size_t index) const { return get()[index]; }
const T &operator*() const { return *(get()); }
const T *operator->() const { return get(); }
};
// FIXME: All "rel locs" are actually absolute, naming typo in facade_import.
// Does not affect runtime functionality.
NPNR_PACKED_STRUCT(struct BelWirePOD {
LocationPOD rel_wire_loc;
int32_t wire_index;
int32_t port;
int32_t dir; // FIXME: Corresponds to "type" in ECP5.
});
NPNR_PACKED_STRUCT(struct BelInfoPOD {
RelPtr<char> name;
int32_t type;
int32_t z;
int32_t num_bel_wires;
RelPtr<BelWirePOD> bel_wires;
});
NPNR_PACKED_STRUCT(struct PipLocatorPOD {
LocationPOD rel_loc;
int32_t index;
});
NPNR_PACKED_STRUCT(struct BelPortPOD {
LocationPOD rel_bel_loc;
int32_t bel_index;
int32_t port;
});
NPNR_PACKED_STRUCT(struct PipInfoPOD {
LocationPOD src;
LocationPOD dst;
int32_t src_idx;
int32_t dst_idx;
int32_t timing_class;
int16_t tile_type;
int8_t pip_type;
int8_t padding;
});
NPNR_PACKED_STRUCT(struct WireInfoPOD {
RelPtr<char> name;
int32_t tile_wire;
int32_t num_uphill;
int32_t num_downhill;
RelPtr<PipLocatorPOD> pips_uphill;
RelPtr<PipLocatorPOD> pips_downhill;
int32_t num_bel_pins;
RelPtr<BelPortPOD> bel_pins;
});
NPNR_PACKED_STRUCT(struct TileTypePOD {
int32_t num_bels;
int32_t num_wires;
int32_t num_pips;
RelPtr<BelInfoPOD> bel_data;
RelPtr<WireInfoPOD> wire_data;
RelPtr<PipInfoPOD> pips_data;
});
NPNR_PACKED_STRUCT(struct PackagePinPOD {
RelPtr<char> name;
LocationPOD abs_loc;
int32_t bel_index;
});
NPNR_PACKED_STRUCT(struct PackageInfoPOD {
RelPtr<char> name;
int32_t num_pins;
RelPtr<PackagePinPOD> pin_data;
});
NPNR_PACKED_STRUCT(struct PIOInfoPOD {
LocationPOD abs_loc;
int32_t bel_index;
RelPtr<char> function_name;
int16_t bank;
int16_t dqsgroup;
});
NPNR_PACKED_STRUCT(struct TileNamePOD {
RelPtr<char> name;
int16_t type_idx;
int16_t padding;
});
NPNR_PACKED_STRUCT(struct TileInfoPOD {
int32_t num_tiles;
RelPtr<TileNamePOD> tile_names;
});
NPNR_PACKED_STRUCT(struct ChipInfoPOD {
int32_t width, height;
int32_t num_tiles;
int32_t num_packages, num_pios;
int32_t const_id_count;
RelPtr<TileTypePOD> tiles;
RelPtr<RelPtr<char>> tiletype_names;
RelPtr<PackageInfoPOD> package_info;
RelPtr<PIOInfoPOD> pio_info;
RelPtr<TileInfoPOD> tile_info;
});
/************************ End of chipdb section. ************************/
// Iterators
// Iterate over Bels across tiles.
struct BelIterator
{
const ChipInfoPOD *chip;
int cursor_index;
int cursor_tile;
BelIterator operator++()
{
cursor_index++;
while (cursor_tile < chip->num_tiles && cursor_index >= chip->tiles[cursor_tile].num_bels) {
cursor_index = 0;
cursor_tile++;
}
return *this;
}
BelIterator operator++(int)
{
BelIterator prior(*this);
++(*this);
return prior;
}
bool operator!=(const BelIterator &other) const
{
return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile;
}
bool operator==(const BelIterator &other) const
{
return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile;
}
BelId operator*() const
{
BelId ret;
ret.location.x = cursor_tile % chip->width;
ret.location.y = cursor_tile / chip->width;
ret.index = cursor_index;
return ret;
}
};
struct BelRange
{
BelIterator b, e;
BelIterator begin() const { return b; }
BelIterator end() const { return e; }
};
// Iterate over Downstream/Upstream Bels for a Wire.
struct BelPinIterator
{
const BelPortPOD *ptr = nullptr;
Location wire_loc;
void operator++() { ptr++; }
bool operator!=(const BelPinIterator &other) const { return ptr != other.ptr; }
BelPin operator*() const
{
BelPin ret;
ret.bel.index = ptr->bel_index;
ret.bel.location = ptr->rel_bel_loc;
ret.pin.index = ptr->port;
return ret;
}
};
struct BelPinRange
{
BelPinIterator b, e;
BelPinIterator begin() const { return b; }
BelPinIterator end() const { return e; }
};
// Iterator over Wires across tiles.
struct WireIterator
{
const ChipInfoPOD *chip;
int cursor_index;
int cursor_tile;
WireIterator operator++()
{
cursor_index++;
while (cursor_tile < chip->num_tiles && cursor_index >= chip->tiles[cursor_tile].num_wires) {
cursor_index = 0;
cursor_tile++;
}
return *this;
}
WireIterator operator++(int)
{
WireIterator prior(*this);
++(*this);
return prior;
}
bool operator!=(const WireIterator &other) const
{
return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile;
}
bool operator==(const WireIterator &other) const
{
return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile;
}
WireId operator*() const
{
WireId ret;
ret.location.x = cursor_tile % chip->width;
ret.location.y = cursor_tile / chip->width;
ret.index = cursor_index;
return ret;
}
};
struct WireRange
{
WireIterator b, e;
WireIterator begin() const { return b; }
WireIterator end() const { return e; }
};
// Iterator over Pips across tiles.
struct AllPipIterator
{
const ChipInfoPOD *chip;
int cursor_index;
int cursor_tile;
AllPipIterator operator++()
{
cursor_index++;
while (cursor_tile < chip->num_tiles && cursor_index >= chip->tiles[cursor_tile].num_pips) {
cursor_index = 0;
cursor_tile++;
}
return *this;
}
AllPipIterator operator++(int)
{
AllPipIterator prior(*this);
++(*this);
return prior;
}
bool operator!=(const AllPipIterator &other) const
{
return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile;
}
bool operator==(const AllPipIterator &other) const
{
return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile;
}
PipId operator*() const
{
PipId ret;
ret.location.x = cursor_tile % chip->width;
ret.location.y = cursor_tile / chip->width;
ret.index = cursor_index;
return ret;
}
};
struct AllPipRange
{
AllPipIterator b, e;
AllPipIterator begin() const { return b; }
AllPipIterator end() const { return e; }
};
// Iterate over Downstream/Upstream Pips for a Wire.
struct PipIterator
{
const PipLocatorPOD *cursor = nullptr;
Location wire_loc;
void operator++() { cursor++; }
bool operator!=(const PipIterator &other) const { return cursor != other.cursor; }
PipId operator*() const
{
PipId ret;
ret.index = cursor->index;
ret.location = cursor->rel_loc;
return ret;
}
};
struct PipRange
{
PipIterator b, e;
PipIterator begin() const { return b; }
PipIterator end() const { return e; }
};
// -----------------------------------------------------------------------
struct ArchArgs
{
enum ArchArgsTypes
{
NONE,
LCMXO2_256HC,
LCMXO2_640HC,
LCMXO2_1200HC,
LCMXO2_2000HC,
LCMXO2_4000HC,
LCMXO2_7000HC,
} type = NONE;
std::string package;
enum SpeedGrade
{
SPEED_1 = 0,
SPEED_2,
SPEED_3,
SPEED_4,
SPEED_5,
SPEED_6,
} speed = SPEED_4;
};
struct ArchRanges : BaseArchRanges
{
using ArchArgsT = ArchArgs;
// Bels
using AllBelsRangeT = BelRange;
using TileBelsRangeT = BelRange;
using BelPinsRangeT = std::vector<IdString>;
// Wires
using AllWiresRangeT = WireRange;
using DownhillPipRangeT = PipRange;
using UphillPipRangeT = PipRange;
using WireBelPinRangeT = BelPinRange;
// Pips
using AllPipsRangeT = AllPipRange;
};
struct Arch : BaseArch<ArchRanges>
{
const ChipInfoPOD *chip_info;
const PackageInfoPOD *package_info;
mutable std::unordered_map<IdStringList, PipId> pip_by_name;
// fast access to X and Y IdStrings for building object names
std::vector<IdString> x_ids, y_ids;
// inverse of the above for name->object mapping
std::unordered_map<IdString, int> id_to_x, id_to_y;
// Helpers
template <typename Id> const TileTypePOD *tile_info(Id &id) const
{
return &(chip_info->tiles[id.location.y * chip_info->width + id.location.x]);
}
int get_bel_flat_index(BelId bel) const
{
return (bel.location.y * chip_info->width + bel.location.x) * max_loc_bels + bel.index;
}
// ---------------------------------------------------------------
// Common Arch API. Every arch must provide the following methods.
// General
ArchArgs args;
Arch(ArchArgs args);
static bool is_available(ArchArgs::ArchArgsTypes chip);
static std::vector<std::string> get_supported_packages(ArchArgs::ArchArgsTypes chip);
std::string getChipName() const override;
// Extra helper
std::string get_full_chip_name() const;
IdString archId() const override { return id("machxo2"); }
ArchArgs archArgs() const override { return args; }
IdString archArgsToId(ArchArgs args) const override;
static const int max_loc_bels = 20;
int getGridDimX() const override { return chip_info->width; }
int getGridDimY() const override { return chip_info->height; }
int getTileBelDimZ(int x, int y) const override { return max_loc_bels; }
// TODO: Make more precise? The CENTER MUX having config bits across
// tiles can complicate this?
int getTilePipDimZ(int x, int y) const override { return 2; }
char getNameDelimiter() const override { return '/'; }
// Bels
BelId getBelByName(IdStringList name) const override;
IdStringList getBelName(BelId bel) const override
{
NPNR_ASSERT(bel != BelId());
std::array<IdString, 3> ids{x_ids.at(bel.location.x), y_ids.at(bel.location.y),
id(tile_info(bel)->bel_data[bel.index].name.get())};
return IdStringList(ids);
}
Loc getBelLocation(BelId bel) const override
{
NPNR_ASSERT(bel != BelId());
Loc loc;
loc.x = bel.location.x;
loc.y = bel.location.y;
loc.z = tile_info(bel)->bel_data[bel.index].z;
return loc;
}
BelId getBelByLocation(Loc loc) const override;
BelRange getBelsByTile(int x, int y) const override;
bool getBelGlobalBuf(BelId bel) const override;
BelRange getBels() const override
{
BelRange range;
range.b.cursor_tile = 0;
range.b.cursor_index = -1;
range.b.chip = chip_info;
++range.b; //-1 and then ++ deals with the case of no Bels in the first tile
range.e.cursor_tile = chip_info->width * chip_info->height;
range.e.cursor_index = 0;
range.e.chip = chip_info;
return range;
}
IdString getBelType(BelId bel) const override
{
NPNR_ASSERT(bel != BelId());
IdString id;
id.index = tile_info(bel)->bel_data[bel.index].type;
return id;
}
WireId getBelPinWire(BelId bel, IdString pin) const override;
PortType getBelPinType(BelId bel, IdString pin) const override;
std::vector<IdString> getBelPins(BelId bel) const override;
// Package
BelId getPackagePinBel(const std::string &pin) const;
// Wires
WireId getWireByName(IdStringList name) const override;
IdStringList getWireName(WireId wire) const override
{
NPNR_ASSERT(wire != WireId());
std::array<IdString, 3> ids{x_ids.at(wire.location.x), y_ids.at(wire.location.y),
id(tile_info(wire)->wire_data[wire.index].name.get())};
return IdStringList(ids);
}
DelayInfo getWireDelay(WireId wire) const override { return DelayInfo(); }
WireRange getWires() const override
{
WireRange range;
range.b.cursor_tile = 0;
range.b.cursor_index = -1;
range.b.chip = chip_info;
++range.b; //-1 and then ++ deals with the case of no wries in the first tile
range.e.cursor_tile = chip_info->width * chip_info->height;
range.e.cursor_index = 0;
range.e.chip = chip_info;
return range;
}
BelPinRange getWireBelPins(WireId wire) const override
{
BelPinRange range;
NPNR_ASSERT(wire != WireId());
range.b.ptr = tile_info(wire)->wire_data[wire.index].bel_pins.get();
range.b.wire_loc = wire.location;
range.e.ptr = range.b.ptr + tile_info(wire)->wire_data[wire.index].num_bel_pins;
range.e.wire_loc = wire.location;
return range;
}
// Pips
PipId getPipByName(IdStringList name) const override;
IdStringList getPipName(PipId pip) const override;
AllPipRange getPips() const override
{
AllPipRange range;
range.b.cursor_tile = 0;
range.b.cursor_index = -1;
range.b.chip = chip_info;
++range.b; //-1 and then ++ deals with the case of no Bels in the first tile
range.e.cursor_tile = chip_info->width * chip_info->height;
range.e.cursor_index = 0;
range.e.chip = chip_info;
return range;
}
Loc getPipLocation(PipId pip) const override
{
Loc loc;
loc.x = pip.location.x;
loc.y = pip.location.y;
// FIXME: Some Pip's config bits span across tiles. Will Z
// be affected by this?
loc.z = 0;
return loc;
}
WireId getPipSrcWire(PipId pip) const override
{
WireId wire;
NPNR_ASSERT(pip != PipId());
wire.index = tile_info(pip)->pips_data[pip.index].src_idx;
wire.location = tile_info(pip)->pips_data[pip.index].src;
return wire;
}
WireId getPipDstWire(PipId pip) const override
{
WireId wire;
NPNR_ASSERT(pip != PipId());
wire.index = tile_info(pip)->pips_data[pip.index].dst_idx;
wire.location = tile_info(pip)->pips_data[pip.index].dst;
return wire;
}
DelayInfo getPipDelay(PipId pip) const override
{
DelayInfo delay;
delay.delay = 0.01;
return delay;
}
PipRange getPipsDownhill(WireId wire) const override
{
PipRange range;
NPNR_ASSERT(wire != WireId());
range.b.cursor = tile_info(wire)->wire_data[wire.index].pips_downhill.get();
range.b.wire_loc = wire.location;
range.e.cursor = range.b.cursor + tile_info(wire)->wire_data[wire.index].num_downhill;
range.e.wire_loc = wire.location;
return range;
}
PipRange getPipsUphill(WireId wire) const override
{
PipRange range;
NPNR_ASSERT(wire != WireId());
range.b.cursor = tile_info(wire)->wire_data[wire.index].pips_uphill.get();
range.b.wire_loc = wire.location;
range.e.cursor = range.b.cursor + tile_info(wire)->wire_data[wire.index].num_uphill;
range.e.wire_loc = wire.location;
return range;
}
// Extra Pip helpers.
int8_t get_pip_class(PipId pip) const { return tile_info(pip)->pips_data[pip.index].pip_type; }
std::string get_pip_tilename(PipId pip) const
{
auto &tileloc = chip_info->tile_info[pip.location.y * chip_info->width + pip.location.x];
for (int i = 0; i < tileloc.num_tiles; i++) {
if (tileloc.tile_names[i].type_idx == tile_info(pip)->pips_data[pip.index].tile_type)
return tileloc.tile_names[i].name.get();
}
NPNR_ASSERT_FALSE("failed to find Pip tile");
}
// Delay
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 0.001; }
delay_t getRipupDelayPenalty() const override { return 0.015; }
float getDelayNS(delay_t v) const override { return v; }
DelayInfo getDelayFromNS(float ns) const override
{
DelayInfo del;
del.delay = ns;
return del;
}
uint32_t getDelayChecksum(delay_t v) const override { return v; }
ArcBounds getRouteBoundingBox(WireId src, WireId dst) const override;
// Flow
bool pack() override;
bool place() override;
bool route() override;
// Placer
bool isValidBelForCell(CellInfo *cell, BelId bel) const override;
bool isBelLocationValid(BelId bel) const override;
static const std::string defaultPlacer;
static const std::vector<std::string> availablePlacers;
static const std::string defaultRouter;
static const std::vector<std::string> availableRouters;
// ---------------------------------------------------------------
// Internal usage
bool cells_compatible(const CellInfo **cells, int count) const;
std::vector<std::pair<std::string, std::string>> get_tiles_at_location(int row, int col);
std::string get_tile_by_type_and_loc(int row, int col, std::string type) const
{
auto &tileloc = chip_info->tile_info[row * chip_info->width + col];
for (int i = 0; i < tileloc.num_tiles; i++) {
if (chip_info->tiletype_names[tileloc.tile_names[i].type_idx].get() == type)
return tileloc.tile_names[i].name.get();
}
NPNR_ASSERT_FALSE_STR("no tile at (" + std::to_string(col) + ", " + std::to_string(row) + ") with type " +
type);
}
std::string get_tile_by_type_and_loc(int row, int col, const std::set<std::string> &type) const
{
auto &tileloc = chip_info->tile_info[row * chip_info->width + col];
for (int i = 0; i < tileloc.num_tiles; i++) {
if (type.count(chip_info->tiletype_names[tileloc.tile_names[i].type_idx].get()))
return tileloc.tile_names[i].name.get();
}
NPNR_ASSERT_FALSE_STR("no tile at (" + std::to_string(col) + ", " + std::to_string(row) + ") with type in set");
}
std::string get_tile_by_type(std::string type) const
{
for (int i = 0; i < chip_info->height * chip_info->width; i++) {
auto &tileloc = chip_info->tile_info[i];
for (int j = 0; j < tileloc.num_tiles; j++)
if (chip_info->tiletype_names[tileloc.tile_names[j].type_idx].get() == type)
return tileloc.tile_names[j].name.get();
}
NPNR_ASSERT_FALSE_STR("no tile with type " + type);
}
};
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,79 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Claire Xen <claire@symbioticeda.com>
* Copyright (C) 2018 David Shah <david@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 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("type", &ArchArgs::type);
py::class_<BelId>(m, "BelId").def_readwrite("index", &BelId::index);
py::class_<WireId>(m, "WireId").def_readwrite("index", &WireId::index);
py::class_<PipId>(m, "PipId").def_readwrite("index", &PipId::index);
auto arch_cls = py::class_<Arch, BaseCtx>(m, "Arch").def(py::init<ArchArgs>());
auto ctx_cls = py::class_<Context, Arch>(m, "Context")
.def("checksum", &Context::checksum)
.def("pack", &Context::pack)
.def("place", &Context::place)
.def("route", &Context::route);
fn_wrapper_2a<Context, decltype(&Context::isValidBelForCell), &Context::isValidBelForCell, pass_through<bool>,
addr_and_unwrap<CellInfo>, conv_from_str<BelId>>::def_wrap(ctx_cls, "isValidBelForCell");
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 PipRange UphillPipRange;
typedef const PipRange DownhillPipRange;
typedef const std::vector<BelBucketId> &BelBucketRange;
typedef const std::vector<BelId> &BelRangeForBelBucket;
#include "arch_pybindings_shared.h"
WRAP_RANGE(m, Bel, conv_to_str<BelId>);
WRAP_RANGE(m, Wire, conv_to_str<WireId>);
WRAP_RANGE(m, AllPip, conv_to_str<PipId>);
WRAP_RANGE(m, Pip, 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

98
machxo2/arch_pybindings.h Normal file
View File

@ -0,0 +1,98 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Claire Xen <claire@symbioticeda.com>
* Copyright (C) 2018 David Shah <david@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 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<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);
}
};
} // namespace PythonConversion
NEXTPNR_NAMESPACE_END
#endif
#endif

177
machxo2/archdefs.h Normal file
View File

@ -0,0 +1,177 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Claire Xen <claire@symbioticeda.com>
* Copyright (C) 2021 William D. Jones <wjones@wdj-consulting.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 NEXTPNR_H
#error Include "archdefs.h" via "nextpnr.h" only.
#endif
NEXTPNR_NAMESPACE_BEGIN
typedef float delay_t;
struct DelayInfo
{
delay_t delay = 0;
delay_t minRaiseDelay() const { return delay; }
delay_t maxRaiseDelay() const { return delay; }
delay_t minFallDelay() const { return delay; }
delay_t maxFallDelay() const { return delay; }
delay_t minDelay() const { return delay; }
delay_t maxDelay() const { return delay; }
DelayInfo operator+(const DelayInfo &other) const
{
DelayInfo ret;
ret.delay = this->delay + other.delay;
return ret;
}
};
enum ConstIds
{
ID_NONE
#define X(t) , ID_##t
#include "constids.inc"
#undef X
,
DB_CONST_ID_COUNT
};
#define X(t) static constexpr auto id_##t = IdString(ID_##t);
#include "constids.inc"
#undef X
NPNR_PACKED_STRUCT(struct LocationPOD { int16_t x, y; });
struct Location
{
int16_t x = -1, y = -1;
Location() : x(-1), y(-1){};
Location(int16_t x, int16_t y) : x(x), y(y){};
Location(const LocationPOD &pod) : x(pod.x), y(pod.y){};
Location(const Location &loc) : x(loc.x), y(loc.y){};
bool operator==(const Location &other) const { return x == other.x && y == other.y; }
bool operator!=(const Location &other) const { return x != other.x || y != other.y; }
bool operator<(const Location &other) const { return y == other.y ? x < other.x : y < other.y; }
};
inline Location operator+(const Location &a, const Location &b) { return Location(a.x + b.x, a.y + b.y); }
struct BelId
{
Location location;
int32_t index = -1;
bool operator==(const BelId &other) const { return index == other.index && location == other.location; }
bool operator!=(const BelId &other) const { return index != other.index || location != other.location; }
bool operator<(const BelId &other) const
{
return location == other.location ? index < other.index : location < other.location;
}
};
struct WireId
{
Location location;
int32_t index = -1;
bool operator==(const WireId &other) const { return index == other.index && location == other.location; }
bool operator!=(const WireId &other) const { return index != other.index || location != other.location; }
bool operator<(const WireId &other) const
{
return location == other.location ? index < other.index : location < other.location;
}
};
struct PipId
{
Location location;
int32_t index = -1;
bool operator==(const PipId &other) const { return index == other.index && location == other.location; }
bool operator!=(const PipId &other) const { return index != other.index || location != other.location; }
bool operator<(const PipId &other) const
{
return location == other.location ? index < other.index : location < other.location;
}
};
typedef IdString GroupId;
typedef IdString DecalId;
typedef IdString BelBucketId;
struct ArchNetInfo
{
};
struct NetInfo;
struct ArchCellInfo
{
};
NEXTPNR_NAMESPACE_END
namespace std {
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX Location>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX Location &loc) const noexcept
{
std::size_t seed = std::hash<int>()(loc.x);
seed ^= std::hash<int>()(loc.y) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
};
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX BelId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept
{
std::size_t seed = std::hash<NEXTPNR_NAMESPACE_PREFIX Location>()(bel.location);
seed ^= std::hash<int>()(bel.index) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
};
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX WireId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept
{
std::size_t seed = std::hash<NEXTPNR_NAMESPACE_PREFIX Location>()(wire.location);
seed ^= std::hash<int>()(wire.index) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
};
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX PipId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept
{
std::size_t seed = std::hash<NEXTPNR_NAMESPACE_PREFIX Location>()(pip.location);
seed ^= std::hash<int>()(pip.index) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
};
} // namespace std

249
machxo2/bitstream.cc Normal file
View File

@ -0,0 +1,249 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
* Copyright (C) 2021 William D. Jones <wjones@wdj-consulting.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 <fstream>
#include "bitstream.h"
#include "config.h"
#include "nextpnr.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
// These seem simple enough to do inline for now.
namespace BaseConfigs {
void config_empty_lcmxo2_1200hc(ChipConfig &cc)
{
cc.chip_name = "LCMXO2-1200HC";
cc.tiles["EBR_R6C11:EBR1"].add_unknown(0, 12);
cc.tiles["EBR_R6C15:EBR1"].add_unknown(0, 12);
cc.tiles["EBR_R6C18:EBR1"].add_unknown(0, 12);
cc.tiles["EBR_R6C21:EBR1"].add_unknown(0, 12);
cc.tiles["EBR_R6C2:EBR1"].add_unknown(0, 12);
cc.tiles["EBR_R6C5:EBR1"].add_unknown(0, 12);
cc.tiles["EBR_R6C8:EBR1"].add_unknown(0, 12);
cc.tiles["PT4:CFG0"].add_unknown(5, 30);
cc.tiles["PT4:CFG0"].add_unknown(5, 32);
cc.tiles["PT4:CFG0"].add_unknown(5, 36);
cc.tiles["PT7:CFG3"].add_unknown(5, 18);
}
} // namespace BaseConfigs
// Convert an absolute wire name to a relative Trellis one
static std::string get_trellis_wirename(Context *ctx, Location loc, WireId wire)
{
std::string basename = ctx->tile_info(wire)->wire_data[wire.index].name.get();
std::string prefix2 = basename.substr(0, 2);
std::string prefix7 = basename.substr(0, 7);
int max_col = ctx->chip_info->width - 1;
// Handle MachXO2's wonderful naming quirks for wires in left/right tiles, whose
// relative coords push them outside the bounds of the chip.
auto is_pio_wire = [](std::string name) {
return (name.find("DI") != std::string::npos || name.find("JDI") != std::string::npos ||
name.find("PADD") != std::string::npos || name.find("INDD") != std::string::npos ||
name.find("IOLDO") != std::string::npos || name.find("IOLTO") != std::string::npos ||
name.find("JCE") != std::string::npos || name.find("JCLK") != std::string::npos ||
name.find("JLSR") != std::string::npos || name.find("JONEG") != std::string::npos ||
name.find("JOPOS") != std::string::npos || name.find("JTS") != std::string::npos ||
name.find("JIN") != std::string::npos || name.find("JIP") != std::string::npos ||
// Connections to global mux
name.find("JINCK") != std::string::npos);
};
if (prefix2 == "G_" || prefix2 == "L_" || prefix2 == "R_" || prefix2 == "U_" || prefix2 == "D_" ||
prefix7 == "BRANCH_")
return basename;
if (loc == wire.location) {
// TODO: JINCK is not currently handled by this.
if (is_pio_wire(basename)) {
if (wire.location.x == 0)
return "W1_" + basename;
else if (wire.location.x == max_col)
return "E1_" + basename;
}
return basename;
}
std::string rel_prefix;
if (wire.location.y < loc.y)
rel_prefix += "N" + std::to_string(loc.y - wire.location.y);
if (wire.location.y > loc.y)
rel_prefix += "S" + std::to_string(wire.location.y - loc.y);
if (wire.location.x > loc.x)
rel_prefix += "E" + std::to_string(wire.location.x - loc.x);
if (wire.location.x < loc.x)
rel_prefix += "W" + std::to_string(loc.x - wire.location.x);
return rel_prefix + "_" + basename;
}
static void set_pip(Context *ctx, ChipConfig &cc, PipId pip)
{
std::string tile = ctx->get_pip_tilename(pip);
std::string source = get_trellis_wirename(ctx, pip.location, ctx->getPipSrcWire(pip));
std::string sink = get_trellis_wirename(ctx, pip.location, ctx->getPipDstWire(pip));
cc.tiles[tile].add_arc(sink, source);
}
static std::vector<bool> int_to_bitvector(int val, int size)
{
std::vector<bool> bv;
for (int i = 0; i < size; i++) {
bv.push_back((val & (1 << i)) != 0);
}
return bv;
}
static std::vector<bool> str_to_bitvector(std::string str, int size)
{
std::vector<bool> bv;
bv.resize(size, 0);
if (str.substr(0, 2) != "0b")
log_error("error parsing value '%s', expected 0b prefix\n", str.c_str());
for (int i = 0; i < int(str.size()) - 2; i++) {
char c = str.at((str.size() - i) - 1);
NPNR_ASSERT(c == '0' || c == '1');
bv.at(i) = (c == '1');
}
return bv;
}
std::string intstr_or_default(const std::unordered_map<IdString, Property> &ct, const IdString &key,
std::string def = "0")
{
auto found = ct.find(key);
if (found == ct.end())
return def;
else {
if (found->second.is_string)
return found->second.as_string();
else
return std::to_string(found->second.as_int64());
}
};
// Get the PIC tile corresponding to a PIO bel
static std::string get_pic_tile(Context *ctx, BelId bel)
{
static const std::set<std::string> pio_l = {"PIC_L0", "PIC_LS0", "PIC_L0_VREF3"};
static const std::set<std::string> pio_r = {"PIC_R0", "PIC_RS0"};
std::string pio_name = ctx->tile_info(bel)->bel_data[bel.index].name.get();
if (bel.location.y == 0) {
return ctx->get_tile_by_type_and_loc(0, bel.location.x, "PIC_T0");
} else if (bel.location.y == ctx->chip_info->height - 1) {
return ctx->get_tile_by_type_and_loc(bel.location.y, bel.location.x, "PIC_B0");
} else if (bel.location.x == 0) {
return ctx->get_tile_by_type_and_loc(bel.location.y, 0, pio_l);
} else if (bel.location.x == ctx->chip_info->width - 1) {
return ctx->get_tile_by_type_and_loc(bel.location.y, bel.location.x, pio_r);
} else {
NPNR_ASSERT_FALSE("bad PIO location");
}
}
void write_bitstream(Context *ctx, std::string text_config_file)
{
ChipConfig cc;
switch (ctx->args.type) {
case ArchArgs::LCMXO2_1200HC:
BaseConfigs::config_empty_lcmxo2_1200hc(cc);
break;
default:
NPNR_ASSERT_FALSE("Unsupported device type");
}
cc.metadata.push_back("Part: " + ctx->get_full_chip_name());
// Add all set, configurable pips to the config
for (auto pip : ctx->getPips()) {
if (ctx->getBoundPipNet(pip) != nullptr) {
if (ctx->get_pip_class(pip) == 0) { // ignore fixed pips
set_pip(ctx, cc, pip);
}
}
}
// TODO: Bank Voltages
// Configure slices
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ci->bel == BelId()) {
log_warning("found unplaced cell '%s' during bitstream gen. Not writing to bitstream.\n",
ci->name.c_str(ctx));
continue;
}
BelId bel = ci->bel;
if (ci->type == id_FACADE_SLICE) {
std::string tname = ctx->get_tile_by_type_and_loc(bel.location.y, bel.location.x, "PLC");
std::string slice = ctx->tile_info(bel)->bel_data[bel.index].name.get();
NPNR_ASSERT(slice.substr(0, 5) == "SLICE");
int int_index = slice[5] - 'A';
NPNR_ASSERT(int_index >= 0 && int_index < 4);
int lut0_init = int_or_default(ci->params, ctx->id("LUT0_INITVAL"));
int lut1_init = int_or_default(ci->params, ctx->id("LUT1_INITVAL"));
cc.tiles[tname].add_word(slice + ".K0.INIT", int_to_bitvector(lut0_init, 16));
cc.tiles[tname].add_word(slice + ".K1.INIT", int_to_bitvector(lut1_init, 16));
cc.tiles[tname].add_enum(slice + ".MODE", str_or_default(ci->params, ctx->id("MODE"), "LOGIC"));
cc.tiles[tname].add_enum(slice + ".GSR", str_or_default(ci->params, ctx->id("GSR"), "ENABLED"));
cc.tiles[tname].add_enum("LSR" + std::to_string(int_index) + ".SRMODE",
str_or_default(ci->params, ctx->id("SRMODE"), "LSR_OVER_CE"));
cc.tiles[tname].add_enum(slice + ".CEMUX", intstr_or_default(ci->params, ctx->id("CEMUX"), "1"));
cc.tiles[tname].add_enum("CLK" + std::to_string(int_index) + ".CLKMUX",
intstr_or_default(ci->params, ctx->id("CLKMUX"), "0"));
cc.tiles[tname].add_enum("LSR" + std::to_string(int_index) + ".LSRMUX",
str_or_default(ci->params, ctx->id("LSRMUX"), "LSR"));
cc.tiles[tname].add_enum("LSR" + std::to_string(int_index) + ".LSRONMUX",
intstr_or_default(ci->params, ctx->id("LSRONMUX"), "LSRMUX"));
cc.tiles[tname].add_enum(slice + ".REGMODE", str_or_default(ci->params, ctx->id("REGMODE"), "FF"));
cc.tiles[tname].add_enum(slice + ".REG0.SD", intstr_or_default(ci->params, ctx->id("REG0_SD"), "0"));
cc.tiles[tname].add_enum(slice + ".REG1.SD", intstr_or_default(ci->params, ctx->id("REG1_SD"), "0"));
cc.tiles[tname].add_enum(slice + ".REG0.REGSET",
str_or_default(ci->params, ctx->id("REG0_REGSET"), "RESET"));
cc.tiles[tname].add_enum(slice + ".REG1.REGSET",
str_or_default(ci->params, ctx->id("REG1_REGSET"), "RESET"));
} else if (ci->type == ctx->id("FACADE_IO")) {
std::string pio = ctx->tile_info(bel)->bel_data[bel.index].name.get();
std::string iotype = str_or_default(ci->attrs, ctx->id("IO_TYPE"), "LVCMOS33");
std::string dir = str_or_default(ci->params, ctx->id("DIR"), "INPUT");
std::string pic_tile = get_pic_tile(ctx, bel);
cc.tiles[pic_tile].add_enum(pio + ".BASE_TYPE", dir + "_" + iotype);
} else if (ci->type == ctx->id("OSCH")) {
std::string freq = str_or_default(ci->params, ctx->id("NOM_FREQ"), "2.08");
cc.tiles[ctx->get_tile_by_type("CFG1")].add_enum("OSCH.MODE", "OSCH");
cc.tiles[ctx->get_tile_by_type("CFG1")].add_enum("OSCH.NOM_FREQ", freq);
}
}
// Configure chip
if (!text_config_file.empty()) {
std::ofstream out_config(text_config_file);
out_config << cc;
}
}
NEXTPNR_NAMESPACE_END

32
machxo2/bitstream.h Normal file
View File

@ -0,0 +1,32 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
* Copyright (C) 2021 William D. Jones <wjones@wdj-consulting.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 BITSTREAM_H
#define BITSTREAM_H
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
void write_bitstream(Context *ctx, std::string text_config_file = "");
NEXTPNR_NAMESPACE_END
#endif // BITSTREAM_H

180
machxo2/cells.cc Normal file
View File

@ -0,0 +1,180 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2019 David Shah <david@symbioticeda.com>
* Copyright (C) 2021 William D. Jones <wjones@wdj-consulting.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 "cells.h"
#include "design_utils.h"
#include "log.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
void add_port(const Context *ctx, CellInfo *cell, std::string name, PortType dir)
{
IdString id = ctx->id(name);
NPNR_ASSERT(cell->ports.count(id) == 0);
cell->ports[id] = PortInfo{id, nullptr, dir};
}
void add_port(const Context *ctx, CellInfo *cell, IdString id, PortType dir)
{
NPNR_ASSERT(cell->ports.count(id) == 0);
cell->ports[id] = PortInfo{id, nullptr, dir};
}
std::unique_ptr<CellInfo> create_machxo2_cell(Context *ctx, IdString type, std::string name)
{
static int auto_idx = 0;
std::unique_ptr<CellInfo> new_cell = std::unique_ptr<CellInfo>(new CellInfo());
if (name.empty()) {
new_cell->name = ctx->id("$nextpnr_" + type.str(ctx) + "_" + std::to_string(auto_idx++));
} else {
new_cell->name = ctx->id(name);
}
new_cell->type = type;
if (type == id_FACADE_SLICE) {
new_cell->params[id_MODE] = std::string("LOGIC");
new_cell->params[id_GSR] = std::string("ENABLED");
new_cell->params[id_SRMODE] = std::string("LSR_OVER_CE");
new_cell->params[id_CEMUX] = std::string("1");
new_cell->params[id_CLKMUX] = std::string("0");
new_cell->params[id_LSRMUX] = std::string("LSR");
new_cell->params[id_LSRONMUX] = std::string("LSRMUX");
new_cell->params[id_LUT0_INITVAL] = Property(0xFFFF, 16);
new_cell->params[id_LUT1_INITVAL] = Property(0xFFFF, 16);
new_cell->params[id_REGMODE] = std::string("FF");
new_cell->params[id_REG0_SD] = std::string("1");
new_cell->params[id_REG1_SD] = std::string("1");
new_cell->params[id_REG0_REGSET] = std::string("SET");
new_cell->params[id_REG1_REGSET] = std::string("SET");
new_cell->params[id_CCU2_INJECT1_0] = std::string("YES");
new_cell->params[id_CCU2_INJECT1_1] = std::string("YES");
new_cell->params[id_WREMUX] = std::string("INV");
add_port(ctx, new_cell.get(), id_A0, PORT_IN);
add_port(ctx, new_cell.get(), id_B0, PORT_IN);
add_port(ctx, new_cell.get(), id_C0, PORT_IN);
add_port(ctx, new_cell.get(), id_D0, PORT_IN);
add_port(ctx, new_cell.get(), id_A1, PORT_IN);
add_port(ctx, new_cell.get(), id_B1, PORT_IN);
add_port(ctx, new_cell.get(), id_C1, PORT_IN);
add_port(ctx, new_cell.get(), id_D1, PORT_IN);
add_port(ctx, new_cell.get(), id_M0, PORT_IN);
add_port(ctx, new_cell.get(), id_M1, PORT_IN);
add_port(ctx, new_cell.get(), id_FCI, PORT_IN);
add_port(ctx, new_cell.get(), id_FXA, PORT_IN);
add_port(ctx, new_cell.get(), id_FXB, PORT_IN);
add_port(ctx, new_cell.get(), id_CLK, PORT_IN);
add_port(ctx, new_cell.get(), id_LSR, PORT_IN);
add_port(ctx, new_cell.get(), id_CE, PORT_IN);
add_port(ctx, new_cell.get(), id_DI0, PORT_IN);
add_port(ctx, new_cell.get(), id_DI1, PORT_IN);
add_port(ctx, new_cell.get(), id_WD0, PORT_IN);
add_port(ctx, new_cell.get(), id_WD1, PORT_IN);
add_port(ctx, new_cell.get(), id_WAD0, PORT_IN);
add_port(ctx, new_cell.get(), id_WAD1, PORT_IN);
add_port(ctx, new_cell.get(), id_WAD2, PORT_IN);
add_port(ctx, new_cell.get(), id_WAD3, PORT_IN);
add_port(ctx, new_cell.get(), id_WRE, PORT_IN);
add_port(ctx, new_cell.get(), id_WCK, PORT_IN);
add_port(ctx, new_cell.get(), id_F0, PORT_OUT);
add_port(ctx, new_cell.get(), id_Q0, PORT_OUT);
add_port(ctx, new_cell.get(), id_F1, PORT_OUT);
add_port(ctx, new_cell.get(), id_Q1, PORT_OUT);
add_port(ctx, new_cell.get(), id_FCO, PORT_OUT);
add_port(ctx, new_cell.get(), id_OFX0, PORT_OUT);
add_port(ctx, new_cell.get(), id_OFX1, PORT_OUT);
add_port(ctx, new_cell.get(), id_WDO0, PORT_OUT);
add_port(ctx, new_cell.get(), id_WDO1, PORT_OUT);
add_port(ctx, new_cell.get(), id_WDO2, PORT_OUT);
add_port(ctx, new_cell.get(), id_WDO3, PORT_OUT);
add_port(ctx, new_cell.get(), id_WADO0, PORT_OUT);
add_port(ctx, new_cell.get(), id_WADO1, PORT_OUT);
add_port(ctx, new_cell.get(), id_WADO2, PORT_OUT);
add_port(ctx, new_cell.get(), id_WADO3, PORT_OUT);
} else if (type == id_FACADE_IO) {
new_cell->params[id_DIR] = std::string("INPUT");
new_cell->attrs[ctx->id("IO_TYPE")] = std::string("LVCMOS33");
add_port(ctx, new_cell.get(), "PAD", PORT_INOUT);
add_port(ctx, new_cell.get(), "I", PORT_IN);
add_port(ctx, new_cell.get(), "EN", PORT_IN);
add_port(ctx, new_cell.get(), "O", PORT_OUT);
} else if (type == id_LUT4) {
new_cell->params[id_INIT] = Property(0, 16);
add_port(ctx, new_cell.get(), id_A, PORT_IN);
add_port(ctx, new_cell.get(), id_B, PORT_IN);
add_port(ctx, new_cell.get(), id_C, PORT_IN);
add_port(ctx, new_cell.get(), id_D, PORT_IN);
add_port(ctx, new_cell.get(), id_Z, PORT_OUT);
} else {
log_error("unable to create MachXO2 cell of type %s", type.c_str(ctx));
}
return new_cell;
}
void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff)
{
lc->params[ctx->id("LUT0_INITVAL")] = lut->params[ctx->id("INIT")];
for (std::string i : {"A", "B", "C", "D"}) {
IdString lut_port = ctx->id(i);
IdString lc_port = ctx->id(i + "0");
replace_port(lut, lut_port, lc, lc_port);
}
replace_port(lut, ctx->id("Z"), lc, ctx->id("F0"));
}
void dff_to_lc(Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut)
{
// FIXME: This will have to change once we support FFs with reset value of 1.
lc->params[ctx->id("REG0_REGSET")] = std::string("RESET");
replace_port(dff, ctx->id("CLK"), lc, ctx->id("CLK"));
replace_port(dff, ctx->id("LSR"), lc, ctx->id("LSR"));
replace_port(dff, ctx->id("Q"), lc, ctx->id("Q0"));
// If a register's DI port is fed by a constant, options for placing are
// limited. Use the LUT to get around this.
if (pass_thru_lut) {
lc->params[ctx->id("LUT0_INITVAL")] = Property(0xAAAA, 16);
;
replace_port(dff, ctx->id("DI"), lc, ctx->id("A0"));
connect_ports(ctx, lc, ctx->id("F0"), lc, ctx->id("DI0"));
} else {
replace_port(dff, ctx->id("DI"), lc, ctx->id("DI0"));
}
}
void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *iob, std::unordered_set<IdString> &todelete_cells) {}
NEXTPNR_NAMESPACE_END

56
machxo2/cells.h Normal file
View File

@ -0,0 +1,56 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2019 David Shah <david@symbioticeda.com>
* Copyright (C) 2021 William D. Jones <wjones@wdj-consulting.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 "nextpnr.h"
#ifndef MACHXO2_CELLS_H
#define MACHXO2_CELLS_H
NEXTPNR_NAMESPACE_BEGIN
// Create a MachXO2 arch cell and return it
// Name will be automatically assigned if not specified
std::unique_ptr<CellInfo> create_machxo2_cell(Context *ctx, IdString type, std::string name = "");
// Return true if a cell is a LUT
inline bool is_lut(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("LUT4"); }
// Return true if a cell is a flipflop
inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("FACADE_FF"); }
inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("FACADE_SLICE"); }
// Convert a LUT primitive to (part of) an GENERIC_SLICE, swapping ports
// as needed. Set no_dff if a DFF is not being used, so that the output
// can be reconnected
void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff = true);
// Convert a DFF primitive to (part of) an GENERIC_SLICE, setting parameters
// and reconnecting signals as necessary. If pass_thru_lut is True, the LUT will
// be configured as pass through and D connected to I0, otherwise D will be
// ignored
void dff_to_lc(Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut = false);
// Convert a nextpnr IO buffer to a GENERIC_IOB
void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *sbio, std::unordered_set<IdString> &todelete_cells);
NEXTPNR_NAMESPACE_END
#endif

357
machxo2/config.cc Normal file
View File

@ -0,0 +1,357 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
* Copyright (C) 2021 William D. Jones <wjones@wdj-consulting.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 "config.h"
#include <boost/range/adaptor/reversed.hpp>
#include <iomanip>
#include "log.h"
NEXTPNR_NAMESPACE_BEGIN
#define fmt(x) (static_cast<const std::ostringstream &>(std::ostringstream() << x).str())
inline std::string to_string(const std::vector<bool> &bv)
{
std::ostringstream os;
for (auto bit : boost::adaptors::reverse(bv))
os << (bit ? '1' : '0');
return os.str();
}
inline std::istream &operator>>(std::istream &in, std::vector<bool> &bv)
{
bv.clear();
std::string s;
in >> s;
for (auto c : boost::adaptors::reverse(s)) {
assert((c == '0') || (c == '1'));
bv.push_back((c == '1'));
}
return in;
}
struct ConfigBit
{
int frame;
int bit;
bool inv;
};
static ConfigBit cbit_from_str(const std::string &s)
{
size_t idx = 0;
ConfigBit b;
if (s[idx] == '!') {
b.inv = true;
++idx;
} else {
b.inv = false;
}
NPNR_ASSERT(s[idx] == 'F');
++idx;
size_t b_pos = s.find('B');
NPNR_ASSERT(b_pos != std::string::npos);
b.frame = stoi(s.substr(idx, b_pos - idx));
b.bit = stoi(s.substr(b_pos + 1));
return b;
}
inline std::string to_string(ConfigBit b)
{
std::ostringstream ss;
if (b.inv)
ss << "!";
ss << "F" << b.frame;
ss << "B" << b.bit;
return ss.str();
}
// Skip whitespace, optionally including newlines
inline void skip_blank(std::istream &in, bool nl = false)
{
int c = in.peek();
while (in && (((c == ' ') || (c == '\t')) || (nl && ((c == '\n') || (c == '\r'))))) {
in.get();
c = in.peek();
}
}
// Return true if end of line (or file)
inline bool skip_check_eol(std::istream &in)
{
skip_blank(in, false);
if (!in)
return false;
int c = in.peek();
// Comments count as end of line
if (c == '#') {
in.get();
c = in.peek();
while (in && c != EOF && c != '\n') {
in.get();
c = in.peek();
}
return true;
}
return (c == EOF || c == '\n');
}
// Skip past blank lines and comments
inline void skip(std::istream &in)
{
skip_blank(in, true);
while (in && (in.peek() == '#')) {
// Skip comment line
skip_check_eol(in);
skip_blank(in, true);
}
}
// Return true if at the end of a record (or file)
inline bool skip_check_eor(std::istream &in)
{
skip(in);
int c = in.peek();
return (c == EOF || c == '.');
}
// Return true if at the end of file
inline bool skip_check_eof(std::istream &in)
{
skip(in);
int c = in.peek();
return (c == EOF);
}
std::ostream &operator<<(std::ostream &out, const ConfigArc &arc)
{
out << "arc: " << arc.sink << " " << arc.source << std::endl;
return out;
}
std::istream &operator>>(std::istream &in, ConfigArc &arc)
{
in >> arc.sink;
in >> arc.source;
return in;
}
std::ostream &operator<<(std::ostream &out, const ConfigWord &cw)
{
out << "word: " << cw.name << " " << to_string(cw.value) << std::endl;
return out;
}
std::istream &operator>>(std::istream &in, ConfigWord &cw)
{
in >> cw.name;
in >> cw.value;
return in;
}
std::ostream &operator<<(std::ostream &out, const ConfigEnum &cw)
{
out << "enum: " << cw.name << " " << cw.value << std::endl;
return out;
}
std::istream &operator>>(std::istream &in, ConfigEnum &ce)
{
in >> ce.name;
in >> ce.value;
return in;
}
std::ostream &operator<<(std::ostream &out, const ConfigUnknown &cu)
{
out << "unknown: " << to_string(ConfigBit{cu.frame, cu.bit, false}) << std::endl;
return out;
}
std::istream &operator>>(std::istream &in, ConfigUnknown &cu)
{
std::string s;
in >> s;
ConfigBit c = cbit_from_str(s);
cu.frame = c.frame;
cu.bit = c.bit;
assert(!c.inv);
return in;
}
std::ostream &operator<<(std::ostream &out, const TileConfig &tc)
{
for (const auto &arc : tc.carcs)
out << arc;
for (const auto &cword : tc.cwords)
out << cword;
for (const auto &cenum : tc.cenums)
out << cenum;
for (const auto &cunk : tc.cunknowns)
out << cunk;
return out;
}
std::istream &operator>>(std::istream &in, TileConfig &tc)
{
tc.carcs.clear();
tc.cwords.clear();
tc.cenums.clear();
while (!skip_check_eor(in)) {
std::string type;
in >> type;
if (type == "arc:") {
ConfigArc a;
in >> a;
tc.carcs.push_back(a);
} else if (type == "word:") {
ConfigWord w;
in >> w;
tc.cwords.push_back(w);
} else if (type == "enum:") {
ConfigEnum e;
in >> e;
tc.cenums.push_back(e);
} else if (type == "unknown:") {
ConfigUnknown u;
in >> u;
tc.cunknowns.push_back(u);
} else {
NPNR_ASSERT_FALSE_STR("unexpected token " + type + " while reading config text");
}
}
return in;
}
void TileConfig::add_arc(const std::string &sink, const std::string &source) { carcs.push_back({sink, source}); }
void TileConfig::add_word(const std::string &name, const std::vector<bool> &value) { cwords.push_back({name, value}); }
void TileConfig::add_enum(const std::string &name, const std::string &value) { cenums.push_back({name, value}); }
void TileConfig::add_unknown(int frame, int bit) { cunknowns.push_back({frame, bit}); }
std::string TileConfig::to_string() const
{
std::stringstream ss;
ss << *this;
return ss.str();
}
TileConfig TileConfig::from_string(const std::string &str)
{
std::stringstream ss(str);
TileConfig tc;
ss >> tc;
return tc;
}
bool TileConfig::empty() const { return carcs.empty() && cwords.empty() && cenums.empty() && cunknowns.empty(); }
std::ostream &operator<<(std::ostream &out, const ChipConfig &cc)
{
out << ".device " << cc.chip_name << std::endl << std::endl;
for (const auto &meta : cc.metadata)
out << ".comment " << meta << std::endl;
for (const auto &sc : cc.sysconfig)
out << ".sysconfig " << sc.first << " " << sc.second << std::endl;
out << std::endl;
for (const auto &tile : cc.tiles) {
if (!tile.second.empty()) {
out << ".tile " << tile.first << std::endl;
out << tile.second;
out << std::endl;
}
}
for (const auto &bram : cc.bram_data) {
out << ".bram_init " << bram.first << std::endl;
std::ios_base::fmtflags f(out.flags());
for (size_t i = 0; i < bram.second.size(); i++) {
out << std::setw(3) << std::setfill('0') << std::hex << bram.second.at(i);
if (i % 8 == 7)
out << std::endl;
else
out << " ";
}
out.flags(f);
out << std::endl;
}
for (const auto &tg : cc.tilegroups) {
out << ".tile_group";
for (const auto &tile : tg.tiles) {
out << " " << tile;
}
out << std::endl;
out << tg.config;
out << std::endl;
}
return out;
}
std::istream &operator>>(std::istream &in, ChipConfig &cc)
{
while (!skip_check_eof(in)) {
std::string verb;
in >> verb;
if (verb == ".device") {
in >> cc.chip_name;
} else if (verb == ".comment") {
std::string line;
getline(in, line);
cc.metadata.push_back(line);
} else if (verb == ".sysconfig") {
std::string key, value;
in >> key >> value;
cc.sysconfig[key] = value;
} else if (verb == ".tile") {
std::string tilename;
in >> tilename;
TileConfig tc;
in >> tc;
cc.tiles[tilename] = tc;
} else if (verb == ".tile_group") {
TileGroup tg;
std::string line;
getline(in, line);
std::stringstream ss2(line);
std::string tile;
while (ss2) {
ss2 >> tile;
tg.tiles.push_back(tile);
}
in >> tg.config;
cc.tilegroups.push_back(tg);
} else if (verb == ".bram_init") {
uint16_t bram;
in >> bram;
std::ios_base::fmtflags f(in.flags());
while (!skip_check_eor(in)) {
uint16_t value;
in >> std::hex >> value;
cc.bram_data[bram].push_back(value);
}
in.flags(f);
} else {
log_error("unrecognised config entry %s\n", verb.c_str());
}
}
return in;
}
NEXTPNR_NAMESPACE_END

128
machxo2/config.h Normal file
View File

@ -0,0 +1,128 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
* Copyright (C) 2021 William D. Jones <wjones@wdj-consulting.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 MACHXO2_CONFIG_H
#define MACHXO2_CONFIG_H
#include <map>
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
// This represents configuration at "FASM" level, in terms of routing arcs and non-routing configuration settings -
// either words or enums.
// A connection in a tile
struct ConfigArc
{
std::string sink;
std::string source;
inline bool operator==(const ConfigArc &other) const { return other.source == source && other.sink == sink; }
};
std::ostream &operator<<(std::ostream &out, const ConfigArc &arc);
std::istream &operator>>(std::istream &in, ConfigArc &arc);
// A configuration setting in a tile that takes one or more bits (such as LUT init)
struct ConfigWord
{
std::string name;
std::vector<bool> value;
inline bool operator==(const ConfigWord &other) const { return other.name == name && other.value == value; }
};
std::ostream &operator<<(std::ostream &out, const ConfigWord &cw);
std::istream &operator>>(std::istream &in, ConfigWord &cw);
// A configuration setting in a tile that takes an enumeration value (such as IO type)
struct ConfigEnum
{
std::string name;
std::string value;
inline bool operator==(const ConfigEnum &other) const { return other.name == name && other.value == value; }
};
std::ostream &operator<<(std::ostream &out, const ConfigEnum &ce);
std::istream &operator>>(std::istream &in, ConfigEnum &ce);
// An unknown bit, specified by position only
struct ConfigUnknown
{
int frame, bit;
inline bool operator==(const ConfigUnknown &other) const { return other.frame == frame && other.bit == bit; }
};
std::ostream &operator<<(std::ostream &out, const ConfigUnknown &tc);
std::istream &operator>>(std::istream &in, ConfigUnknown &ce);
struct TileConfig
{
std::vector<ConfigArc> carcs;
std::vector<ConfigWord> cwords;
std::vector<ConfigEnum> cenums;
std::vector<ConfigUnknown> cunknowns;
int total_known_bits = 0;
void add_arc(const std::string &sink, const std::string &source);
void add_word(const std::string &name, const std::vector<bool> &value);
void add_enum(const std::string &name, const std::string &value);
void add_unknown(int frame, int bit);
std::string to_string() const;
static TileConfig from_string(const std::string &str);
bool empty() const;
};
std::ostream &operator<<(std::ostream &out, const TileConfig &tc);
std::istream &operator>>(std::istream &in, TileConfig &ce);
// A group of tiles to configure at once for a particular feature that is split across tiles
// TileGroups are currently for non-routing configuration only
struct TileGroup
{
std::vector<std::string> tiles;
TileConfig config;
};
// This represents the configuration of a chip at a high level
class ChipConfig
{
public:
std::string chip_name;
std::vector<std::string> metadata;
std::map<std::string, TileConfig> tiles;
std::vector<TileGroup> tilegroups;
std::map<std::string, std::string> sysconfig;
std::map<uint16_t, std::vector<uint16_t>> bram_data;
};
std::ostream &operator<<(std::ostream &out, const ChipConfig &cc);
std::istream &operator>>(std::istream &in, ChipConfig &cc);
NEXTPNR_NAMESPACE_END
#endif

118
machxo2/constids.inc Normal file
View File

@ -0,0 +1,118 @@
X(FACADE_SLICE)
X(A0)
X(B0)
X(C0)
X(D0)
X(A1)
X(B1)
X(C1)
X(D1)
X(M0)
X(M1)
X(FCI)
X(FXA)
X(FXB)
X(CLK)
X(LSR)
X(CE)
X(DI0)
X(DI1)
X(WD0)
X(WD1)
X(WAD0)
X(WAD1)
X(WAD2)
X(WAD3)
X(WRE)
X(WCK)
X(F0)
X(Q0)
X(F1)
X(Q1)
X(FCO)
X(OFX0)
X(OFX1)
X(WDO0)
X(WDO1)
X(WDO2)
X(WDO3)
X(WADO0)
X(WADO1)
X(WADO2)
X(WADO3)
X(MODE)
X(GSR)
X(SRMODE)
X(CEMUX)
X(CLKMUX)
X(LSRMUX)
X(LSRONMUX)
X(LUT0_INITVAL)
X(LUT1_INITVAL)
X(REGMODE)
X(REG0_SD)
X(REG1_SD)
X(REG0_REGSET)
X(REG1_REGSET)
X(CCU2_INJECT1_0)
X(CCU2_INJECT1_1)
X(WREMUX)
X(FACADE_FF)
X(DI)
X(Q)
X(REGSET)
X(FACADE_IO)
X(PAD)
X(I)
X(EN)
X(O)
X(DIR)
X(LUT4)
X(A)
X(B)
X(C)
X(D)
X(Z)
X(INIT)
X(PFUMX)
X(ALUT)
X(BLUT)
X(L6MUX21)
X(SD)
X(T)
X(IOLDO)
X(IOLTO)
X(OSCH)
X(STDBY)
X(OSC)
X(SEDSTDBY)
X(DCCA)
X(CLKI)
X(CLKO)
X(DCMA)
X(CLK0)
X(CLK1)
X(SEL)
X(DCMOUT)

11
machxo2/examples/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
*_simtest*
*.vcd
*.png
*.log
*.smt2
pack*.v
place*.v
pnr*.v
abc.history
*.txt
*.bit

110
machxo2/examples/README.md Normal file
View File

@ -0,0 +1,110 @@
# MachXO2 Architecture Example
This directory contains a simple example of running `nextpnr-machxo2`:
* `simple.sh` produces nextpnr output in the files `{pack,place,pnr}*.json`,
as well as pre-pnr and post-pnr diagrams in `{pack,place,pnr}*.{dot, png}`.
* `simtest.sh` extends `simple.sh` by generating `{pack,place,pnr}*.v` from
`{pack,place,pnr}*.json`. The script calls the [`iverilog`](http://iverilog.icarus.com)
compiler and `vvp` runtime to compare the behavior of `{pack,place,pnr}*.v`
and the original Verilog input (using a testbench `*_tb.v`). This is known as
post-place-and-route simulation.
* `mitertest.sh` is similar to `simtest.sh`, but more comprehensive. This
script creates a [miter circuit](https://www21.in.tum.de/~lammich/2015_SS_Seminar_SAT/resources/Equivalence_Checking_11_30_08.pdf)
to compare the output port values of `{pack,place,pnr}*.v` against the
original Verilog code _when both modules are fed the same values on their input
ports._
All possible inputs and resulting outputs can be tested in reasonable time by
using `yosys`' built-in SAT solver or [`z3`](https://github.com/Z3Prover/z3),
an external SMT solver.
* `demo.sh` creates bitstreams for [TinyFPGA Ax](https://tinyfpga.com/a-series-guide.html)
and writes the resulting bitstream to MachXO2's internal flash using
[`tinyproga`](https://github.com/tinyfpga/TinyFPGA-A-Programmer).
As `nextpnr-machxo2` is developed the contents `simple.sh`, `simtest.sh`,
`mitertest.sh`, and `demo.sh` are subject to change.
## How To Run
Each script requires a prefix that matches one of the self-contained Verilog
examples in this directory. For instance, to create a bitstream from
`tinyfpga.v`, use `demo.sh tinyfpga` (the `*` glob used throughout this file
is filled with the the prefix).
Each of `simple.sh`, `simtest.sh`, and `mitertest.sh` runs yosys and nextpnr
to validate a Verilog design in various ways. They require an additional `mode`
argument- `pack`, `place`, or `pnr`- which stops `nextpnr-machxo2` after the
specified phase and writes out a JSON file of the results in
`{pack,place,pnr}*.json`; `pnr` runs all of the Pack, Place, and Route phases.
`mitertest.sh` requires an third option- `sat` or `smt`- to choose between
verifying the miter with either yosys' built-in SAT solver, or an external
SMT solver.
Each script will exit if it finds an input Verilog example it knows it can't
handle. To keep file count lower, all yosys scripts are written inline inside
the `sh` scripts using the `-p` option.
### Clean
To clean output files from _all_ scripts, run:
```
rm -rf *.dot *.json *.png *.vcd *.smt2 *.log *.txt *.bit {pack,place,pnr}*.v *_simtest*
```
## Known Issues
In principle, `mitertest.sh` should work in `sat` or `smt` mode with all
example Verilog files which don't use the internal oscillator (OSCH) or other
hard IP. However, as of this writing, only `blinky.v` passes correctly for a
few reasons:
1. The sim models for MachXO2 primitives used by the `gate` module contain
`initial` values _by design_, as it matches chip behavior. Without any of
the following in the `gold` module (like `blinky_ext.v` currently):
* An external reset signal
* Internal power-on reset signal (e.g. `reg int_rst = 1'd1;`)
* `initial` values to manually set registers
the `gold` and `gate` modules will inherently not match.
Examples using an internal power-on reset (e.g. `uart.v`) also have issues
that I haven't debugged yet in both `sat` and `smt` mode.
2. To keep the `gold`/`gate` generation simpler, examples are currently
assumed to _not_ instantiate MachXO2 simulation primitives directly
(`FACADE_IO`, `FACADE_FF`, etc).
3. `synth_machxo2` runs `deminout` on `inouts` when generating the `gate`
module. This is not handled yet when generating the `gold` module.
## Verilog Examples
* `blinky.v`/`blinky_tb.v`- A blinky example meant for simulation.
* `tinyfpga.v`- Blink the LED on TinyFPA Ax.
* `rgbcount.v`- Blink an RGB LED using TinyFPGA Ax, more closely-based on
[the TinyFPGA Ax guide](https://tinyfpga.com/a-series-guide.html).
* `blinky_ext.v`- Blink the LED on TinyFPA Ax using an external pin (pin 6).
* `uart.v`- UART loopback demo at 19200 baud. Requires the following pins:
* Pin 1- RX LED
* Pin 2- TX (will echo RX)
* Pin 3- RX
* Pin 4- TX LED
* Pin 5- Load LED
* Pin 6- 12 MHz clock input
* Pin 7- Take LED
* Pin 8- Empty LED
## Environment Variables For Scripts
* `YOSYS`- Set to the location of the `yosys` binary to test. Defaults to the
`yosys` on the path. You may want to set this to a `yosys` binary in your
source tree if doing development.
* `NEXTPNR`- Set to the location of the `nextpnr-machxo2` binary to test.
Defaults to the `nextpnr-machxo2` binary at the root of the `nextpnr` source
tree. This should be set, for instance, if doing an out-of-tree build of
`nextpnr-machxo2`.
* `CELLS_SIM`- Set to the location of `machxo2/cells_sim.v` simulation models.
Defaults to whatever `yosys-config` associated with the above `YOSYS` binary
returns. You may want to set this to `/path/to/yosys/src/share/machxo2/cells_sim.v`
if doing development; `yosys-config` cannot find these "before-installation"
simulation models.
* `TRELLIS_DB`- Set to the location of the Project Trellis database to use.
Defaults to nothing, which means `ecppack` will use whatever database is on
its path.

17
machxo2/examples/blinky.v Normal file
View File

@ -0,0 +1,17 @@
module top(input clk, rst, output [7:0] leds);
// TODO: Test miter circuit without reset value. SAT and SMT diverge without
// reset value (SAT succeeds, SMT fails). I haven't figured out the correct
// init set of options to make SAT fail.
// "sat -verify -prove-asserts -set-init-def -seq 1 miter" causes assertion
// failure in yosys.
reg [7:0] ctr = 8'h00;
always @(posedge clk)
if (rst)
ctr <= 8'h00;
else
ctr <= ctr + 1'b1;
assign leds = ctr;
endmodule

View File

@ -0,0 +1,19 @@
// Modified from:
// https://github.com/tinyfpga/TinyFPGA-A-Series/tree/master/template_a2
module top (
(* LOC="13" *)
output pin1,
(* LOC="21" *)
input clk
);
reg [23:0] led_timer;
always @(posedge clk) begin
led_timer <= led_timer + 1;
end
// left side of board
assign pin1 = led_timer[23];
endmodule

View File

@ -0,0 +1,38 @@
`timescale 1ns / 1ps
module blinky_tb;
reg clk = 1'b0, rst = 1'b0;
reg [7:0] ctr_gold = 8'h00;
wire [7:0] ctr_gate;
top dut_i(.clk(clk), .rst(rst), .leds(ctr_gate));
task oneclk;
begin
clk = 1'b1;
#10;
clk = 1'b0;
#10;
end
endtask
initial begin
$dumpfile("blinky_simtest.vcd");
$dumpvars(0, blinky_tb);
#100;
rst = 1'b1;
repeat (5) oneclk;
#5
rst = 1'b0;
#5
repeat (500) begin
if (ctr_gold !== ctr_gate) begin
$display("mismatch gold=%b gate=%b", ctr_gold, ctr_gate);
$stop;
end
oneclk;
ctr_gold = ctr_gold + 1'b1;
end
$finish;
end
endmodule

22
machxo2/examples/demo.sh Normal file
View File

@ -0,0 +1,22 @@
#!/bin/sh
if [ $# -lt 1 ]; then
echo "Usage: $0 prefix"
exit -1
fi
if ! grep -q "(\*.*LOC.*\*)" $1.v; then
echo "$1.v does not have LOC constraints for tinyfpga_a."
exit -2
fi
if [ ! -z ${TRELLIS_DB+x} ]; then
DB_ARG="--db $TRELLIS_DB"
fi
set -ex
${YOSYS:-yosys} -p "synth_machxo2 -json $1.json" $1.v
${NEXTPNR:-../../nextpnr-machxo2} --1200 --package QFN32 --no-iobs --json $1.json --textcfg $1.txt
ecppack --compress $DB_ARG $1.txt $1.bit
tinyproga -b $1.bit

View File

@ -0,0 +1,85 @@
#!/usr/bin/env bash
if [ $# -lt 3 ]; then
echo "Usage: $0 prefix nextpnr_mode solve_mode"
exit -1
fi
if grep -q "OSCH" $1.v; then
echo "$1.v uses blackbox primitive OSCH and cannot be simulated."
exit -2
fi
case $2 in
"pack")
NEXTPNR_MODE="--pack-only"
;;
"place")
NEXTPNR_MODE="--no-route"
;;
"pnr")
NEXTPNR_MODE=""
;;
*)
echo "nextpnr_mode string must be \"pack\", \"place\", or \"pnr\""
exit -3
;;
esac
case $3 in
"sat")
SAT=1
;;
"smt")
SMT=1
;;
*)
echo "solve_mode string must be \"sat\", or \"smt\""
exit -4
;;
esac
do_sat() {
${YOSYS:-yosys} -l ${2}${1}_miter_sat.log -p "read_verilog ${1}.v
rename top gold
read_verilog ${2}${1}.v
rename top gate
read_verilog +/machxo2/cells_sim.v
miter -equiv -make_assert -flatten gold gate ${2}${1}_miter
hierarchy -top ${2}${1}_miter
sat -verify -prove-asserts -tempinduct ${2}${1}_miter"
}
do_smt() {
${YOSYS:-yosys} -l ${2}${1}_miter_smt.log -p "read_verilog ${1}.v
rename top gold
read_verilog ${2}${1}.v
rename top gate
read_verilog +/machxo2/cells_sim.v
miter -equiv -make_assert gold gate ${2}${1}_miter
hierarchy -top ${2}${1}_miter; proc;
opt_clean
write_verilog ${2}${1}_miter.v
write_smt2 ${2}${1}_miter.smt2"
yosys-smtbmc -s z3 --dump-vcd ${2}${1}_miter_bmc.vcd ${2}${1}_miter.smt2
yosys-smtbmc -s z3 -i --dump-vcd ${2}${1}_miter_tmp.vcd ${2}${1}_miter.smt2
}
set -ex
${YOSYS:-yosys} -p "read_verilog ${1}.v
synth_machxo2 -noiopad -json ${1}.json"
${NEXTPNR:-../../nextpnr-machxo2} $NEXTPNR_MODE --1200 --package QFN32 --no-iobs --json ${1}.json --write ${2}${1}.json
${YOSYS:-yosys} -p "read_verilog -lib +/machxo2/cells_sim.v
read_json ${2}${1}.json
clean -purge
write_verilog -noattr -norename ${2}${1}.v"
if [ $3 = "sat" ]; then
do_sat $1 $2
elif [ $3 = "smt" ]; then
do_smt $1 $2
fi

View File

@ -0,0 +1,33 @@
// Modified from:
// https://github.com/tinyfpga/TinyFPGA-A-Series/tree/master/template_a2
// https://tinyfpga.com/a-series-guide.html used as a basis.
module top (
(* LOC="21" *)
inout pin6,
(* LOC="26" *)
inout pin9_jtgnb,
(* LOC="27" *)
inout pin10_sda,
);
wire clk;
OSCH #(
.NOM_FREQ("2.08")
) internal_oscillator_inst (
.STDBY(1'b0),
.OSC(clk)
);
reg [23:0] led_timer;
always @(posedge clk) begin
led_timer <= led_timer + 1;
end
// left side of board
assign pin9_jtgnb = led_timer[23];
assign pin10_sda = led_timer[22];
assign pin6 = led_timer[21];
endmodule

View File

@ -0,0 +1,34 @@
#!/usr/bin/env bash
if [ $# -lt 2 ]; then
echo "Usage: $0 prefix mode"
exit -1
fi
case $2 in
"pack")
NEXTPNR_MODE="--pack-only"
;;
"place")
NEXTPNR_MODE="--no-route"
;;
"pnr")
NEXTPNR_MODE=""
;;
*)
echo "Mode string must be \"pack\", \"place\", or \"pnr\""
exit -2
;;
esac
set -ex
${YOSYS:-yosys} -p "read_verilog ${1}.v
synth_machxo2 -json ${1}.json
show -format png -prefix ${1}"
${NEXTPNR:-../../nextpnr-machxo2} $NEXTPNR_MODE --1200 --package QFN32 --no-iobs --json ${1}.json --write ${2}${1}.json
${YOSYS:-yosys} -p "read_verilog -lib +/machxo2/cells_sim.v
read_json ${2}${1}.json
clean -purge
show -format png -prefix ${2}${1}
write_verilog -noattr -norename ${2}${1}.v"

View File

@ -0,0 +1,39 @@
#!/usr/bin/env bash
if [ $# -lt 2 ]; then
echo "Usage: $0 prefix mode"
exit -1
fi
case $2 in
"pack")
NEXTPNR_MODE="--pack-only"
;;
"place")
NEXTPNR_MODE="--no-route"
;;
"pnr")
NEXTPNR_MODE=""
;;
*)
echo "Mode string must be \"pack\", \"place\", or \"pnr\""
exit -2
;;
esac
if [ ! -f ${1}_tb.v ]; then
echo "No testbench file (${1}_tb.v) found for ${1}.v"
exit -3
fi
set -ex
${YOSYS:-yosys} -p "read_verilog ${1}.v
synth_machxo2 -json ${1}.json"
${NEXTPNR:-../../nextpnr-machxo2} $NEXTPNR_MODE --1200 --package QFN32 --no-iobs --json ${1}.json --write ${2}${1}.json
${YOSYS:-yosys} -p "read_verilog -lib +/machxo2/cells_sim.v
read_json ${2}${1}.json
clean -purge
write_verilog -noattr -norename ${2}${1}.v"
iverilog -o ${1}_simtest ${CELLS_SIM:-`${YOSYS:yosys}-config --datdir/machxo2/cells_sim.v`} ${1}_tb.v ${2}${1}.v
vvp -N ./${1}_simtest

View File

@ -0,0 +1,28 @@
// Modified from:
// https://github.com/tinyfpga/TinyFPGA-A-Series/tree/master/template_a2
// https://tinyfpga.com/a-series-guide.html used as a basis.
module top (
(* LOC="13" *)
inout pin1
);
wire clk;
OSCH #(
.NOM_FREQ("16.63")
) internal_oscillator_inst (
.STDBY(1'b0),
.OSC(clk)
);
reg [23:0] led_timer;
always @(posedge clk) begin
led_timer <= led_timer + 1;
end
// left side of board
assign pin1 = led_timer[23];
endmodule

209
machxo2/examples/uart.v Normal file
View File

@ -0,0 +1,209 @@
/* Example UART derived from: https://github.com/cr1901/migen_uart.
Requires 12MHz clock and runs at 19,200 baud. */
/* Machine-generated using Migen */
module top(
(* LOC = "14" *)
output tx,
(* LOC = "16" *)
input rx,
(* LOC = "13" *)
output rx_led,
(* LOC = "17" *)
output tx_led,
(* LOC = "20" *)
output load_led,
(* LOC = "23" *)
output take_led,
(* LOC = "25" *)
output empty_led,
(* LOC = "21" *)
input clk
);
wire [7:0] out_data;
wire [7:0] in_data;
reg wr = 1'd0;
reg rd = 1'd0;
wire tx_empty;
wire rx_empty;
wire tx_ov;
wire rx_ov;
wire sout_load;
wire [7:0] sout_out_data;
wire sout_shift;
reg sout_empty = 1'd1;
reg sout_overrun = 1'd0;
reg [3:0] sout_count = 4'd0;
reg [9:0] sout_reg = 10'd0;
reg sout_tx;
wire sin_rx;
wire sin_shift;
wire sin_take;
reg [7:0] sin_in_data = 8'd0;
wire sin_edge;
reg sin_empty = 1'd1;
reg sin_busy = 1'd0;
reg sin_overrun = 1'd0;
reg sin_sync_rx = 1'd0;
reg [8:0] sin_reg = 9'd0;
reg sin_rx_prev = 1'd0;
reg [3:0] sin_count = 4'd0;
wire out_active;
wire in_active;
reg shift_out_strobe = 1'd0;
reg shift_in_strobe = 1'd0;
reg [9:0] in_counter = 10'd0;
reg [9:0] out_counter = 10'd0;
wire sys_clk;
wire sys_rst;
wire por_clk;
reg int_rst = 1'd1;
// synthesis translate_off
reg dummy_s;
initial dummy_s <= 1'd0;
// synthesis translate_on
assign tx_led = (~tx);
assign rx_led = (~rx);
assign load_led = sout_load;
assign take_led = sin_take;
assign empty_led = sin_empty;
assign out_data = in_data;
assign in_data = sin_in_data;
assign sout_out_data = out_data;
assign sin_take = rd;
assign sout_load = wr;
assign tx = sout_tx;
assign sin_rx = rx;
assign tx_empty = sout_empty;
assign rx_empty = sin_empty;
assign tx_ov = sout_overrun;
assign rx_ov = sin_overrun;
assign sout_shift = shift_out_strobe;
assign sin_shift = shift_in_strobe;
assign out_active = (~sout_empty);
assign in_active = sin_busy;
// synthesis translate_off
reg dummy_d;
// synthesis translate_on
always @(*) begin
sout_tx <= 1'd0;
if (sout_empty) begin
sout_tx <= 1'd1;
end else begin
sout_tx <= sout_reg[0];
end
// synthesis translate_off
dummy_d <= dummy_s;
// synthesis translate_on
end
assign sin_edge = ((sin_rx_prev == 1'd1) & (sin_sync_rx == 1'd0));
assign sys_clk = clk;
assign por_clk = clk;
assign sys_rst = int_rst;
always @(posedge por_clk) begin
int_rst <= 1'd0;
end
always @(posedge sys_clk) begin
wr <= 1'd0;
rd <= 1'd0;
if ((~sin_empty)) begin
wr <= 1'd1;
rd <= 1'd1;
end
if (sout_load) begin
if (sout_empty) begin
sout_reg[0] <= 1'd0;
sout_reg[8:1] <= sout_out_data;
sout_reg[9] <= 1'd1;
sout_empty <= 1'd0;
sout_overrun <= 1'd0;
sout_count <= 1'd0;
end else begin
sout_overrun <= 1'd1;
end
end
if (((~sout_empty) & sout_shift)) begin
sout_reg[8:0] <= sout_reg[9:1];
sout_reg[9] <= 1'd0;
if ((sout_count == 4'd9)) begin
sout_empty <= 1'd1;
sout_count <= 1'd0;
end else begin
sout_count <= (sout_count + 1'd1);
end
end
sin_sync_rx <= sin_rx;
sin_rx_prev <= sin_sync_rx;
if (sin_take) begin
sin_empty <= 1'd1;
sin_overrun <= 1'd0;
end
if (((~sin_busy) & sin_edge)) begin
sin_busy <= 1'd1;
end
if ((sin_shift & sin_busy)) begin
sin_reg[8] <= sin_sync_rx;
sin_reg[7:0] <= sin_reg[8:1];
if ((sin_count == 4'd9)) begin
sin_in_data <= sin_reg[8:1];
sin_count <= 1'd0;
sin_busy <= 1'd0;
if ((~sin_empty)) begin
sin_overrun <= 1'd1;
end else begin
sin_empty <= 1'd0;
end
end else begin
sin_count <= (sin_count + 1'd1);
end
end
out_counter <= 1'd0;
in_counter <= 1'd0;
if (in_active) begin
shift_in_strobe <= 1'd0;
in_counter <= (in_counter + 1'd1);
if ((in_counter == 9'd311)) begin
shift_in_strobe <= 1'd1;
end
if ((in_counter == 10'd623)) begin
in_counter <= 1'd0;
end
end
if (out_active) begin
shift_out_strobe <= 1'd0;
out_counter <= (out_counter + 1'd1);
if ((out_counter == 10'd623)) begin
out_counter <= 1'd0;
shift_out_strobe <= 1'd1;
end
end
if (sys_rst) begin
wr <= 1'd0;
rd <= 1'd0;
sout_empty <= 1'd1;
sout_overrun <= 1'd0;
sout_count <= 4'd0;
sout_reg <= 10'd0;
sin_in_data <= 8'd0;
sin_empty <= 1'd1;
sin_busy <= 1'd0;
sin_overrun <= 1'd0;
sin_sync_rx <= 1'd0;
sin_reg <= 9'd0;
sin_rx_prev <= 1'd0;
sin_count <= 4'd0;
shift_out_strobe <= 1'd0;
shift_in_strobe <= 1'd0;
in_counter <= 10'd0;
out_counter <= 10'd0;
end
end
endmodule

368
machxo2/facade_import.py Normal file
View File

@ -0,0 +1,368 @@
#!/usr/bin/env python3
import argparse
import json
import sys
from os import path
tiletype_names = dict()
parser = argparse.ArgumentParser(description="import MachXO2 routing and bels from Project Trellis")
parser.add_argument("device", type=str, help="target device")
parser.add_argument("-p", "--constids", type=str, help="path to constids.inc")
parser.add_argument("-g", "--gfxh", type=str, help="path to gfx.h (unused)")
parser.add_argument("-L", "--libdir", type=str, action="append", help="extra Python library path")
args = parser.parse_args()
sys.path += args.libdir
import pytrellis
import database
# Get the index for a tiletype
def get_tiletype_index(name):
if name in tiletype_names:
return tiletype_names[name]
idx = len(tiletype_names)
tiletype_names[name] = idx
return idx
constids = dict()
class BinaryBlobAssembler:
def l(self, name, ltype = None, export = False):
if ltype is None:
print("label %s" % (name,))
else:
print("label %s %s" % (name, ltype))
def r(self, name, comment):
if comment is None:
print("ref %s" % (name,))
else:
print("ref %s %s" % (name, comment))
def s(self, s, comment):
assert "|" not in s
print("str |%s| %s" % (s, comment))
def u8(self, v, comment):
if comment is None:
print("u8 %d" % (v,))
else:
print("u8 %d %s" % (v, comment))
def u16(self, v, comment):
if comment is None:
print("u16 %d" % (v,))
else:
print("u16 %d %s" % (v, comment))
def u32(self, v, comment):
if comment is None:
print("u32 %d" % (v,))
else:
print("u32 %d %s" % (v, comment))
def pre(self, s):
print("pre %s" % s)
def post(self, s):
print("post %s" % s)
def push(self, name):
print("push %s" % name)
def pop(self):
print("pop")
def get_bel_index(rg, loc, name):
tile = rg.tiles[loc]
idx = 0
for bel in tile.bels:
if rg.to_str(bel.name) == name:
return idx
idx += 1
# FIXME: I/O pins can be missing in various rows. Is there a nice way to
# assert on each device size?
return None
packages = {}
pindata = []
def process_pio_db(rg, device):
piofile = path.join(database.get_db_root(), "MachXO2", dev_names[device], "iodb.json")
with open(piofile, 'r') as f:
piodb = json.load(f)
for pkgname, pkgdata in sorted(piodb["packages"].items()):
pins = []
for name, pinloc in sorted(pkgdata.items()):
x = pinloc["col"]
y = pinloc["row"]
if x == 0 or x == max_col:
# FIXME: Oversight in read_pinout.py. We use 0-based
# columns for 0 and max row, but we otherwise extract
# the names from the CSV, and...
loc = pytrellis.Location(x, y)
else:
# Lattice uses 1-based columns!
loc = pytrellis.Location(x - 1, y)
pio = "PIO" + pinloc["pio"]
bel_idx = get_bel_index(rg, loc, pio)
if bel_idx is not None:
pins.append((name, loc, bel_idx))
packages[pkgname] = pins
for metaitem in piodb["pio_metadata"]:
x = metaitem["col"]
y = metaitem["row"]
if x == 0 or x == max_col:
loc = pytrellis.Location(x, y)
else:
loc = pytrellis.Location(x - 1, y)
pio = "PIO" + metaitem["pio"]
bank = metaitem["bank"]
if "function" in metaitem:
pinfunc = metaitem["function"]
else:
pinfunc = None
dqs = -1
if "dqs" in metaitem:
pass
# tdqs = metaitem["dqs"]
# if tdqs[0] == "L":
# dqs = 0
# elif tdqs[0] == "R":
# dqs = 2048
# suffix_size = 0
# while tdqs[-(suffix_size+1)].isdigit():
# suffix_size += 1
# dqs |= int(tdqs[-suffix_size:])
bel_idx = get_bel_index(rg, loc, pio)
if bel_idx is not None:
pindata.append((loc, bel_idx, bank, pinfunc, dqs))
def write_database(dev_name, chip, rg, endianness):
def write_loc(loc, sym_name):
bba.u16(loc.x, "%s.x" % sym_name)
bba.u16(loc.y, "%s.y" % sym_name)
# Use Lattice naming conventions, so convert to 1-based col indexing.
def get_wire_name(loc, idx):
tile = rg.tiles[loc]
return "R{}C{}_{}".format(loc.y, loc.x + 1, rg.to_str(tile.wires[idx].name))
# Before doing anything, ensure sorted routing graph iteration matches
# y, x
loc_iter = list(sorted(rg.tiles, key=lambda l : (l.y, l.x)))
i = 1 # Drop (-2, -2) location.
for y in range(0, max_row+1):
for x in range(0, max_col+1):
l = loc_iter[i]
assert((y, x) == (l.y, l.x))
i = i + 1
bba = BinaryBlobAssembler()
bba.pre('#include "nextpnr.h"')
bba.pre('#include "embed.h"')
bba.pre('NEXTPNR_NAMESPACE_BEGIN')
bba.post('EmbeddedFile chipdb_file_%s("machxo2/chipdb-%s.bin", chipdb_blob_%s);' % (dev_name, dev_name, dev_name))
bba.post('NEXTPNR_NAMESPACE_END')
bba.push("chipdb_blob_%s" % args.device)
bba.r("chip_info", "chip_info")
# Nominally should be in order, but support situations where python
# decides to iterate over rg.tiles out-of-order.
for l in loc_iter:
t = rg.tiles[l]
# Do not include special globals location for now.
if (l.x, l.y) == (-2, -2):
continue
if len(t.arcs) > 0:
bba.l("loc%d_%d_pips" % (l.y, l.x), "PipInfoPOD")
for arc in t.arcs:
write_loc(arc.srcWire.rel, "src")
write_loc(arc.sinkWire.rel, "dst")
bba.u32(arc.srcWire.id, "src_idx {}".format(get_wire_name(arc.srcWire.rel, arc.srcWire.id)))
bba.u32(arc.sinkWire.id, "dst_idx {}".format(get_wire_name(arc.sinkWire.rel, arc.sinkWire.id)))
src_name = get_wire_name(arc.srcWire.rel, arc.srcWire.id)
snk_name = get_wire_name(arc.sinkWire.rel, arc.sinkWire.id)
# TODO: ECP5 timing-model-specific. Reuse for MachXO2?
# bba.u32(get_pip_class(src_name, snk_name), "timing_class")
bba.u32(0, "timing_class")
bba.u16(get_tiletype_index(rg.to_str(arc.tiletype)), "tile_type")
cls = arc.cls
bba.u8(arc.cls, "pip_type")
bba.u8(0, "padding")
if len(t.wires) > 0:
for wire_idx in range(len(t.wires)):
wire = t.wires[wire_idx]
if len(wire.arcsDownhill) > 0:
bba.l("loc%d_%d_wire%d_downpips" % (l.y, l.x, wire_idx), "PipLocatorPOD")
for dp in wire.arcsDownhill:
write_loc(dp.rel, "rel_loc")
bba.u32(dp.id, "index")
if len(wire.arcsUphill) > 0:
bba.l("loc%d_%d_wire%d_uppips" % (l.y, l.x, wire_idx), "PipLocatorPOD")
for up in wire.arcsUphill:
write_loc(up.rel, "rel_loc")
bba.u32(up.id, "index")
if len(wire.belPins) > 0:
bba.l("loc%d_%d_wire%d_belpins" % (l.y, l.x, wire_idx), "BelPortPOD")
for bp in wire.belPins:
write_loc(bp.bel.rel, "rel_bel_loc")
bba.u32(bp.bel.id, "bel_index")
bba.u32(constids[rg.to_str(bp.pin)], "port")
bba.l("loc%d_%d_wires" % (l.y, l.x), "WireInfoPOD")
for wire_idx in range(len(t.wires)):
wire = t.wires[wire_idx]
bba.s(rg.to_str(wire.name), "name")
# TODO: Padding until GUI support is added.
# bba.u32(constids[wire_type(ddrg.to_str(wire.name))], "type")
# if ("TILE_WIRE_" + ddrg.to_str(wire.name)) in gfx_wire_ids:
# bba.u32(gfx_wire_ids["TILE_WIRE_" + ddrg.to_str(wire.name)], "tile_wire")
# else:
bba.u32(0, "tile_wire")
bba.u32(len(wire.arcsUphill), "num_uphill")
bba.u32(len(wire.arcsDownhill), "num_downhill")
bba.r("loc%d_%d_wire%d_uppips" % (l.y, l.x, wire_idx) if len(wire.arcsUphill) > 0 else None, "pips_uphill")
bba.r("loc%d_%d_wire%d_downpips" % (l.y, l.x, wire_idx) if len(wire.arcsDownhill) > 0 else None, "pips_downhill")
bba.u32(len(wire.belPins), "num_bel_pins")
bba.r("loc%d_%d_wire%d_belpins" % (l.y, l.x, wire_idx) if len(wire.belPins) > 0 else None, "bel_pins")
if len(t.bels) > 0:
for bel_idx in range(len(t.bels)):
bel = t.bels[bel_idx]
bba.l("loc%d_%d_bel%d_wires" % (l.y, l.x, bel_idx), "BelWirePOD")
for pin in bel.wires:
write_loc(pin.wire.rel, "rel_wire_loc")
bba.u32(pin.wire.id, "wire_index")
bba.u32(constids[rg.to_str(pin.pin)], "port")
bba.u32(int(pin.dir), "dir")
bba.l("loc%d_%d_bels" % (l.y, l.x), "BelInfoPOD")
for bel_idx in range(len(t.bels)):
bel = t.bels[bel_idx]
bba.s(rg.to_str(bel.name), "name")
bba.u32(constids[rg.to_str(bel.type)], "type")
bba.u32(bel.z, "z")
bba.u32(len(bel.wires), "num_bel_wires")
bba.r("loc%d_%d_bel%d_wires" % (l.y, l.x, bel_idx), "bel_wires")
bba.l("tiles", "TileTypePOD")
for l in loc_iter:
t = rg.tiles[l]
if (l.y, l.x) == (-2, -2):
continue
bba.u32(len(t.bels), "num_bels")
bba.u32(len(t.wires), "num_wires")
bba.u32(len(t.arcs), "num_pips")
bba.r("loc%d_%d_bels" % (l.y, l.x) if len(t.bels) > 0 else None, "bel_data")
bba.r("loc%d_%d_wires" % (l.y, l.x) if len(t.wires) > 0 else None, "wire_data")
bba.r("loc%d_%d_pips" % (l.y, l.x) if len(t.arcs) > 0 else None, "pips_data")
for y in range(0, max_row+1):
for x in range(0, max_col+1):
bba.l("tile_info_%d_%d" % (x, y), "TileNamePOD")
for tile in chip.get_tiles_by_position(y, x):
bba.s(tile.info.name, "name")
bba.u16(get_tiletype_index(tile.info.type), "type_idx")
bba.u16(0, "padding")
bba.l("tiles_info", "TileInfoPOD")
for y in range(0, max_row+1):
for x in range(0, max_col+1):
bba.u32(len(chip.get_tiles_by_position(y, x)), "num_tiles")
bba.r("tile_info_%d_%d" % (x, y), "tile_names")
for package, pkgdata in sorted(packages.items()):
bba.l("package_data_%s" % package, "PackagePinPOD")
for pin in pkgdata:
name, loc, bel_idx = pin
bba.s(name, "name")
write_loc(loc, "abs_loc")
bba.u32(bel_idx, "bel_index")
bba.l("package_data", "PackageInfoPOD")
for package, pkgdata in sorted(packages.items()):
bba.s(package, "name")
bba.u32(len(pkgdata), "num_pins")
bba.r("package_data_%s" % package, "pin_data")
bba.l("pio_info", "PIOInfoPOD")
for pin in pindata:
loc, bel_idx, bank, func, dqs = pin
write_loc(loc, "abs_loc")
bba.u32(bel_idx, "bel_index")
if func is not None and func != "WRITEN":
bba.s(func, "function_name")
else:
bba.r(None, "function_name")
# TODO: io_grouping? And DQS.
bba.u16(bank, "bank")
bba.u16(dqs, "dqsgroup")
bba.l("tiletype_names", "RelPtr<char>")
for tt, idx in sorted(tiletype_names.items(), key=lambda x: x[1]):
bba.s(tt, "name")
bba.l("chip_info")
bba.u32(max_col + 1, "width")
bba.u32(max_row + 1, "height")
bba.u32((max_col + 1) * (max_row + 1), "num_tiles")
bba.u32(len(packages), "num_packages")
bba.u32(len(pindata), "num_pios")
bba.u32(const_id_count, "const_id_count")
bba.r("tiles", "tiles")
bba.r("tiletype_names", "tiletype_names")
bba.r("package_data", "package_info")
bba.r("pio_info", "pio_info")
bba.r("tiles_info", "tile_info")
bba.pop()
dev_names = {"1200": "LCMXO2-1200HC"}
def main():
global max_row, max_col, const_id_count
pytrellis.load_database(database.get_db_root())
args = parser.parse_args()
const_id_count = 1 # count ID_NONE
with open(args.constids) as f:
for line in f:
line = line.replace("(", " ")
line = line.replace(")", " ")
line = line.split()
if len(line) == 0:
continue
assert len(line) == 2
assert line[0] == "X"
idx = len(constids) + 1
constids[line[1]] = idx
const_id_count += 1
constids["SLICE"] = constids["FACADE_SLICE"]
constids["PIO"] = constids["FACADE_IO"]
chip = pytrellis.Chip(dev_names[args.device])
rg = pytrellis.make_optimized_chipdb(chip)
max_row = chip.get_max_row()
max_col = chip.get_max_col()
process_pio_db(rg, args.device)
bba = write_database(args.device, chip, rg, "le")
if __name__ == "__main__":
main()

53
machxo2/family.cmake Normal file
View File

@ -0,0 +1,53 @@
add_subdirectory(${family})
message(STATUS "Using MachXO2 chipdb: ${MACHXO2_CHIPDB}")
set(chipdb_sources)
set(chipdb_binaries)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${family}/chipdb)
foreach(device ${MACHXO2_DEVICES})
set(chipdb_bba ${MACHXO2_CHIPDB}/chipdb-${device}.bba)
set(chipdb_bin ${family}/chipdb/chipdb-${device}.bin)
set(chipdb_cc ${family}/chipdb/chipdb-${device}.cc)
if(BBASM_MODE STREQUAL "binary")
add_custom_command(
OUTPUT ${chipdb_bin}
COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${chipdb_bba} ${chipdb_bin}
DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba})
list(APPEND chipdb_binaries ${chipdb_bin})
elseif(BBASM_MODE STREQUAL "embed")
add_custom_command(
OUTPUT ${chipdb_cc} ${chipdb_bin}
COMMAND bbasm ${BBASM_ENDIAN_FLAG} --e ${chipdb_bba} ${chipdb_cc} ${chipdb_bin}
DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba})
list(APPEND chipdb_sources ${chipdb_cc})
list(APPEND chipdb_binaries ${chipdb_bin})
elseif(BBASM_MODE STREQUAL "string")
add_custom_command(
OUTPUT ${chipdb_cc}
COMMAND bbasm ${BBASM_ENDIAN_FLAG} --c ${chipdb_bba} ${chipdb_cc}
DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba})
list(APPEND chipdb_sources ${chipdb_cc})
endif()
endforeach()
if(WIN32)
set(chipdb_rc ${CMAKE_CURRENT_BINARY_DIR}/${family}/resource/chipdb.rc)
list(APPEND chipdb_sources ${chipdb_rc})
file(WRITE ${chipdb_rc})
foreach(device ${MACHXO2_DEVICES})
file(APPEND ${chipdb_rc}
"${family}/chipdb-${device}.bin RCDATA \"${CMAKE_CURRENT_BINARY_DIR}/${family}/chipdb/chipdb-${device}.bin\"")
endforeach()
endif()
add_custom_target(chipdb-${family}-bins DEPENDS ${chipdb_sources} ${chipdb_binaries})
add_library(chipdb-${family} OBJECT ${MACHXO2_CHIPDB} ${chipdb_sources})
add_dependencies(chipdb-${family} chipdb-${family}-bins)
target_compile_options(chipdb-${family} PRIVATE -g0 -O0 -w)
target_compile_definitions(chipdb-${family} PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family})
target_include_directories(chipdb-${family} PRIVATE ${family})
foreach(family_target ${family_targets})
target_sources(${family_target} PRIVATE $<TARGET_OBJECTS:chipdb-${family}>)
endforeach()

122
machxo2/main.cc Normal file
View File

@ -0,0 +1,122 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Claire Xen <claire@symbioticeda.com>
* Copyright (C) 2021 William D. Jones <wjones@wdj-consulting.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifdef MAIN_EXECUTABLE
#include <fstream>
#include "bitstream.h"
#include "command.h"
#include "design_utils.h"
#include "log.h"
#include "timing.h"
USING_NEXTPNR_NAMESPACE
class MachXO2CommandHandler : public CommandHandler
{
public:
MachXO2CommandHandler(int argc, char **argv);
virtual ~MachXO2CommandHandler(){};
std::unique_ptr<Context> createContext(std::unordered_map<std::string, Property> &values) override;
void setupArchContext(Context *ctx) override{};
void customBitstream(Context *ctx) override;
protected:
po::options_description getArchOptions() override;
};
MachXO2CommandHandler::MachXO2CommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {}
po::options_description MachXO2CommandHandler::getArchOptions()
{
po::options_description specific("Architecture specific options");
if (Arch::is_available(ArchArgs::LCMXO2_256HC))
specific.add_options()("256", "set device type to LCMXO2-256HC");
if (Arch::is_available(ArchArgs::LCMXO2_640HC))
specific.add_options()("640", "set device type to LCMXO2-640HC");
if (Arch::is_available(ArchArgs::LCMXO2_1200HC))
specific.add_options()("1200", "set device type to LCMXO2-1200HC");
if (Arch::is_available(ArchArgs::LCMXO2_2000HC))
specific.add_options()("2000", "set device type to LCMXO2-2000HC");
if (Arch::is_available(ArchArgs::LCMXO2_4000HC))
specific.add_options()("4000", "set device type to LCMXO2-4000HC");
if (Arch::is_available(ArchArgs::LCMXO2_7000HC))
specific.add_options()("7000", "set device type to LCMXO2-7000HC");
specific.add_options()("package", po::value<std::string>(), "select device package");
specific.add_options()("speed", po::value<int>(), "select device speedgrade (1 to 6 inclusive)");
specific.add_options()("override-basecfg", po::value<std::string>(),
"base chip configuration in Trellis text format");
specific.add_options()("textcfg", po::value<std::string>(), "textual configuration in Trellis format to write");
// specific.add_options()("lpf", po::value<std::vector<std::string>>(), "LPF pin constraint file(s)");
specific.add_options()("no-iobs", "disable automatic IO buffer insertion (unimplemented- always enabled)");
return specific;
}
void MachXO2CommandHandler::customBitstream(Context *ctx)
{
std::string textcfg;
if (vm.count("textcfg"))
textcfg = vm["textcfg"].as<std::string>();
write_bitstream(ctx, textcfg);
}
std::unique_ptr<Context> MachXO2CommandHandler::createContext(std::unordered_map<std::string, Property> &values)
{
ArchArgs chipArgs;
chipArgs.type = ArchArgs::NONE;
if (vm.count("256"))
chipArgs.type = ArchArgs::LCMXO2_256HC;
if (vm.count("640"))
chipArgs.type = ArchArgs::LCMXO2_640HC;
if (vm.count("1200"))
chipArgs.type = ArchArgs::LCMXO2_1200HC;
if (vm.count("2000"))
chipArgs.type = ArchArgs::LCMXO2_2000HC;
if (vm.count("4000"))
chipArgs.type = ArchArgs::LCMXO2_4000HC;
if (vm.count("7000"))
chipArgs.type = ArchArgs::LCMXO2_7000HC;
if (vm.count("package"))
chipArgs.package = vm["package"].as<std::string>();
if (values.find("arch.name") != values.end()) {
std::string arch_name = values["arch.name"].as_string();
if (arch_name != "machxo2")
log_error("Unsuported architecture '%s'.\n", arch_name.c_str());
}
auto ctx = std::unique_ptr<Context>(new Context(chipArgs));
if (vm.count("no-iobs"))
ctx->settings[ctx->id("disable_iobs")] = Property::State::S1;
return ctx;
}
int main(int argc, char *argv[])
{
MachXO2CommandHandler handler(argc, argv);
return handler.exec();
}
#endif

296
machxo2/pack.cc Normal file
View File

@ -0,0 +1,296 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018-19 David Shah <david@symbioticeda.com>
* Copyright (C) 2021 William D. Jones <wjones@wdj-consulting.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 <iterator>
#include <unordered_set>
#include "cells.h"
#include "design_utils.h"
#include "log.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
// Pack LUTs and LUT-FF pairs
static void pack_lut_lutffs(Context *ctx)
{
log_info("Packing LUT-FFs..\n");
std::unordered_set<IdString> packed_cells;
std::vector<std::unique_ptr<CellInfo>> new_cells;
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (ctx->verbose)
log_info("cell '%s' is of type '%s'\n", ci->name.c_str(ctx), ci->type.c_str(ctx));
if (is_lut(ctx, ci)) {
std::unique_ptr<CellInfo> packed = create_machxo2_cell(ctx, id_FACADE_SLICE, ci->name.str(ctx) + "_LC");
std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(packed->attrs, packed->attrs.begin()));
packed_cells.insert(ci->name);
if (ctx->verbose)
log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx));
// See if we can pack into a DFF. Both LUT4 and FF outputs are
// available for a given slice, so we can pack a FF even if the
// LUT4 drives more than one FF.
NetInfo *o = ci->ports.at(id_Z).net;
CellInfo *dff = net_only_drives(ctx, o, is_ff, id_DI, false);
auto lut_bel = ci->attrs.find(ctx->id("BEL"));
bool packed_dff = false;
if (dff) {
if (ctx->verbose)
log_info("found attached dff %s\n", dff->name.c_str(ctx));
auto dff_bel = dff->attrs.find(ctx->id("BEL"));
if (lut_bel != ci->attrs.end() && dff_bel != dff->attrs.end() && lut_bel->second != dff_bel->second) {
// Locations don't match, can't pack
} else {
lut_to_lc(ctx, ci, packed.get(), false);
dff_to_lc(ctx, dff, packed.get(), false);
if (dff_bel != dff->attrs.end())
packed->attrs[ctx->id("BEL")] = dff_bel->second;
packed_cells.insert(dff->name);
if (ctx->verbose)
log_info("packed cell %s into %s\n", dff->name.c_str(ctx), packed->name.c_str(ctx));
packed_dff = true;
}
}
if (!packed_dff) {
lut_to_lc(ctx, ci, packed.get(), true);
}
new_cells.push_back(std::move(packed));
}
}
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
}
static void pack_remaining_ffs(Context *ctx)
{
log_info("Packing remaining FFs..\n");
std::unordered_set<IdString> packed_cells;
std::vector<std::unique_ptr<CellInfo>> new_cells;
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (is_ff(ctx, ci)) {
if (ctx->verbose)
log_info("cell '%s' of type '%s remains unpacked'\n", ci->name.c_str(ctx), ci->type.c_str(ctx));
std::unique_ptr<CellInfo> packed = create_machxo2_cell(ctx, id_FACADE_SLICE, ci->name.str(ctx) + "_LC");
std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(packed->attrs, packed->attrs.begin()));
auto dff_bel = ci->attrs.find(ctx->id("BEL"));
dff_to_lc(ctx, ci, packed.get(), false);
if (dff_bel != ci->attrs.end())
packed->attrs[ctx->id("BEL")] = dff_bel->second;
packed_cells.insert(ci->name);
if (ctx->verbose)
log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx));
new_cells.push_back(std::move(packed));
}
}
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
}
// Merge a net into a constant net
static void set_net_constant(Context *ctx, NetInfo *orig, NetInfo *constnet, bool constval)
{
(void)constval;
std::unordered_set<IdString> packed_cells;
std::vector<std::unique_ptr<CellInfo>> new_cells;
orig->driver.cell = nullptr;
for (auto user : orig->users) {
if (user.cell != nullptr) {
CellInfo *uc = user.cell;
if (ctx->verbose)
log_info("%s user %s\n", orig->name.c_str(ctx), uc->name.c_str(ctx));
if (uc->type == id_FACADE_FF && user.port == id_DI) {
log_info("FACADE_FF %s is driven by a constant\n", uc->name.c_str(ctx));
std::unique_ptr<CellInfo> lc = create_machxo2_cell(ctx, id_FACADE_SLICE, uc->name.str(ctx) + "_CONST");
std::copy(uc->attrs.begin(), uc->attrs.end(), std::inserter(lc->attrs, lc->attrs.begin()));
dff_to_lc(ctx, uc, lc.get(), true);
packed_cells.insert(uc->name);
lc->ports[id_A0].net = constnet;
user.cell = lc.get();
user.port = id_A0;
new_cells.push_back(std::move(lc));
} else {
uc->ports[user.port].net = constnet;
}
constnet->users.push_back(user);
}
}
orig->users.clear();
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
}
// Pack constants (based on simple implementation in generic).
// VCC/GND cells provided by nextpnr automatically.
static void pack_constants(Context *ctx)
{
log_info("Packing constants..\n");
std::unique_ptr<CellInfo> const_cell = create_machxo2_cell(ctx, id_FACADE_SLICE, "$PACKER_CONST");
const_cell->params[id_LUT0_INITVAL] = Property(0, 16);
const_cell->params[id_LUT1_INITVAL] = Property(0xFFFF, 16);
std::unique_ptr<NetInfo> gnd_net = std::unique_ptr<NetInfo>(new NetInfo);
gnd_net->name = ctx->id("$PACKER_GND_NET");
gnd_net->driver.cell = const_cell.get();
gnd_net->driver.port = id_F0;
const_cell->ports.at(id_F0).net = gnd_net.get();
std::unique_ptr<NetInfo> vcc_net = std::unique_ptr<NetInfo>(new NetInfo);
vcc_net->name = ctx->id("$PACKER_VCC_NET");
vcc_net->driver.cell = const_cell.get();
vcc_net->driver.port = id_F1;
const_cell->ports.at(id_F1).net = vcc_net.get();
std::vector<IdString> dead_nets;
for (auto net : sorted(ctx->nets)) {
NetInfo *ni = net.second;
if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("GND")) {
IdString drv_cell = ni->driver.cell->name;
set_net_constant(ctx, ni, gnd_net.get(), false);
dead_nets.push_back(net.first);
ctx->cells.erase(drv_cell);
} else if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("VCC")) {
IdString drv_cell = ni->driver.cell->name;
set_net_constant(ctx, ni, vcc_net.get(), true);
dead_nets.push_back(net.first);
ctx->cells.erase(drv_cell);
}
}
ctx->cells[const_cell->name] = std::move(const_cell);
ctx->nets[gnd_net->name] = std::move(gnd_net);
ctx->nets[vcc_net->name] = std::move(vcc_net);
for (auto dn : dead_nets) {
ctx->nets.erase(dn);
}
}
static bool is_nextpnr_iob(Context *ctx, CellInfo *cell)
{
return cell->type == ctx->id("$nextpnr_ibuf") || cell->type == ctx->id("$nextpnr_obuf") ||
cell->type == ctx->id("$nextpnr_iobuf");
}
static bool is_facade_iob(const Context *ctx, const CellInfo *cell) { return cell->type == id_FACADE_IO; }
// Pack IO buffers- Right now, all this does is remove $nextpnr_[io]buf cells.
// User is expected to manually instantiate FACADE_IO with BEL/IO_TYPE
// attributes.
static void pack_io(Context *ctx)
{
std::unordered_set<IdString> packed_cells;
log_info("Packing IOs..\n");
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (is_nextpnr_iob(ctx, ci)) {
for (auto &p : ci->ports)
disconnect_port(ctx, ci, p.first);
packed_cells.insert(ci->name);
} else if (is_facade_iob(ctx, ci)) {
// If FACADE_IO has LOC attribute, convert the LOC (pin) to a BEL
// attribute and place FACADE_IO at resulting BEL location. A BEL
// attribute already on a FACADE_IO is an error. Attributes on
// the pin attached to the PAD of FACADE_IO are ignored by this
// packing phase.
auto loc_attr_cell = ci->attrs.find(ctx->id("LOC"));
auto bel_attr_cell = ci->attrs.find(ctx->id("BEL"));
if (loc_attr_cell != ci->attrs.end()) {
if (bel_attr_cell != ci->attrs.end()) {
log_error("IO buffer %s has both a BEL attribute and LOC attribute.\n", ci->name.c_str(ctx));
}
log_info("found LOC attribute on IO buffer %s.\n", ci->name.c_str(ctx));
std::string pin = loc_attr_cell->second.as_string();
BelId pinBel = ctx->getPackagePinBel(pin);
if (pinBel == BelId()) {
log_error("IO buffer '%s' constrained to pin '%s', which does not exist for package '%s'.\n",
ci->name.c_str(ctx), pin.c_str(), ctx->args.package.c_str());
} else {
log_info("pin '%s' constrained to Bel '%s'.\n", ci->name.c_str(ctx), ctx->nameOfBel(pinBel));
}
ci->attrs[ctx->id("BEL")] = ctx->getBelName(pinBel).str(ctx);
}
}
}
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
}
// Main pack function
bool Arch::pack()
{
Context *ctx = getCtx();
try {
log_break();
pack_constants(ctx);
pack_io(ctx);
pack_lut_lutffs(ctx);
pack_remaining_ffs(ctx);
ctx->settings[ctx->id("pack")] = 1;
ctx->assignArchInfo();
log_info("Checksum: 0x%08x\n", ctx->checksum());
return true;
} catch (log_execution_error_exception) {
return false;
}
}
NEXTPNR_NAMESPACE_END