Merge pull request #578 from YosysHQ/machxo2-rebase
machxo2, rebased and updated
This commit is contained in:
commit
065f46daeb
@ -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
1
.gitignore
vendored
@ -6,6 +6,7 @@
|
||||
/nextpnr-nexus*
|
||||
/nextpnr-fpga_interchange*
|
||||
/nextpnr-gowin*
|
||||
/nextpnr-machxo2*
|
||||
cmake-build-*/
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
|
@ -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
0
gui/machxo2/family.cmake
Normal file
107
gui/machxo2/mainwindow.cc
Normal file
107
gui/machxo2/mainwindow.cc
Normal 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
49
gui/machxo2/mainwindow.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* nextpnr -- Next Generation Place and Route
|
||||
*
|
||||
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include "../basewindow.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
class MainWindow : public BaseMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(std::unique_ptr<Context> context, CommandHandler *handler, QWidget *parent = 0);
|
||||
virtual ~MainWindow();
|
||||
|
||||
public:
|
||||
void createMenu();
|
||||
|
||||
void onDisableActions() override;
|
||||
void onUpdateActions() override;
|
||||
|
||||
protected Q_SLOTS:
|
||||
void new_proj() override;
|
||||
|
||||
void newContext(Context *ctx);
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
||||
#endif // MAINWINDOW_H
|
2
gui/machxo2/nextpnr.qrc
Normal file
2
gui/machxo2/nextpnr.qrc
Normal file
@ -0,0 +1,2 @@
|
||||
<RCC>
|
||||
</RCC>
|
1
machxo2/.gitignore
vendored
Normal file
1
machxo2/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
chipdb/
|
91
machxo2/CMakeLists.txt
Normal file
91
machxo2/CMakeLists.txt
Normal 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
105
machxo2/README.md
Normal 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
494
machxo2/arch.cc
Normal 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
699
machxo2/arch.h
Normal 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
|
79
machxo2/arch_pybindings.cc
Normal file
79
machxo2/arch_pybindings.cc
Normal 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
98
machxo2/arch_pybindings.h
Normal 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
177
machxo2/archdefs.h
Normal 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
249
machxo2/bitstream.cc
Normal 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
32
machxo2/bitstream.h
Normal 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
180
machxo2/cells.cc
Normal 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
56
machxo2/cells.h
Normal 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
357
machxo2/config.cc
Normal 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
128
machxo2/config.h
Normal 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
118
machxo2/constids.inc
Normal 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
11
machxo2/examples/.gitignore
vendored
Normal 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
110
machxo2/examples/README.md
Normal 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
17
machxo2/examples/blinky.v
Normal 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
|
19
machxo2/examples/blinky_ext.v
Normal file
19
machxo2/examples/blinky_ext.v
Normal 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
|
38
machxo2/examples/blinky_tb.v
Normal file
38
machxo2/examples/blinky_tb.v
Normal 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
22
machxo2/examples/demo.sh
Normal 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
|
85
machxo2/examples/mitertest.sh
Normal file
85
machxo2/examples/mitertest.sh
Normal 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
|
33
machxo2/examples/rgbcount.v
Normal file
33
machxo2/examples/rgbcount.v
Normal 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
|
34
machxo2/examples/simple.sh
Normal file
34
machxo2/examples/simple.sh
Normal 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"
|
39
machxo2/examples/simtest.sh
Normal file
39
machxo2/examples/simtest.sh
Normal 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
|
28
machxo2/examples/tinyfpga.v
Normal file
28
machxo2/examples/tinyfpga.v
Normal 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
209
machxo2/examples/uart.v
Normal 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
368
machxo2/facade_import.py
Normal 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
53
machxo2/family.cmake
Normal 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
122
machxo2/main.cc
Normal 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
296
machxo2/pack.cc
Normal 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
|
Loading…
Reference in New Issue
Block a user