aboutsummaryrefslogtreecommitdiffstats
path: root/machxo2
diff options
context:
space:
mode:
Diffstat (limited to 'machxo2')
-rw-r--r--machxo2/.gitignore1
-rw-r--r--machxo2/CMakeLists.txt91
-rw-r--r--machxo2/README.md105
-rw-r--r--machxo2/arch.cc494
-rw-r--r--machxo2/arch.h699
-rw-r--r--machxo2/arch_pybindings.cc79
-rw-r--r--machxo2/arch_pybindings.h98
-rw-r--r--machxo2/archdefs.h177
-rw-r--r--machxo2/bitstream.cc249
-rw-r--r--machxo2/bitstream.h32
-rw-r--r--machxo2/cells.cc180
-rw-r--r--machxo2/cells.h56
-rw-r--r--machxo2/config.cc357
-rw-r--r--machxo2/config.h128
-rw-r--r--machxo2/constids.inc118
-rw-r--r--machxo2/examples/.gitignore11
-rw-r--r--machxo2/examples/README.md110
-rw-r--r--machxo2/examples/blinky.v17
-rw-r--r--machxo2/examples/blinky_ext.v19
-rw-r--r--machxo2/examples/blinky_tb.v38
-rw-r--r--machxo2/examples/demo.sh22
-rw-r--r--machxo2/examples/mitertest.sh85
-rw-r--r--machxo2/examples/rgbcount.v33
-rw-r--r--machxo2/examples/simple.sh34
-rw-r--r--machxo2/examples/simtest.sh39
-rw-r--r--machxo2/examples/tinyfpga.v28
-rw-r--r--machxo2/examples/uart.v209
-rw-r--r--machxo2/facade_import.py368
-rw-r--r--machxo2/family.cmake53
-rw-r--r--machxo2/main.cc122
-rw-r--r--machxo2/pack.cc296
31 files changed, 4348 insertions, 0 deletions
diff --git a/machxo2/.gitignore b/machxo2/.gitignore
new file mode 100644
index 00000000..48c9c5ce
--- /dev/null
+++ b/machxo2/.gitignore
@@ -0,0 +1 @@
+chipdb/
diff --git a/machxo2/CMakeLists.txt b/machxo2/CMakeLists.txt
new file mode 100644
index 00000000..ab4eded6
--- /dev/null
+++ b/machxo2/CMakeLists.txt
@@ -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()
diff --git a/machxo2/README.md b/machxo2/README.md
new file mode 100644
index 00000000..55cc5763
--- /dev/null
+++ b/machxo2/README.md
@@ -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.
diff --git a/machxo2/arch.cc b/machxo2/arch.cc
new file mode 100644
index 00000000..2938f1ba
--- /dev/null
+++ b/machxo2/arch.cc
@@ -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
diff --git a/machxo2/arch.h b/machxo2/arch.h
new file mode 100644
index 00000000..f1642490
--- /dev/null
+++ b/machxo2/arch.h
@@ -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
diff --git a/machxo2/arch_pybindings.cc b/machxo2/arch_pybindings.cc
new file mode 100644
index 00000000..fa0f9535
--- /dev/null
+++ b/machxo2/arch_pybindings.cc
@@ -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
diff --git a/machxo2/arch_pybindings.h b/machxo2/arch_pybindings.h
new file mode 100644
index 00000000..62f66406
--- /dev/null
+++ b/machxo2/arch_pybindings.h
@@ -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
diff --git a/machxo2/archdefs.h b/machxo2/archdefs.h
new file mode 100644
index 00000000..844a87b6
--- /dev/null
+++ b/machxo2/archdefs.h
@@ -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
diff --git a/machxo2/bitstream.cc b/machxo2/bitstream.cc
new file mode 100644
index 00000000..37363b09
--- /dev/null
+++ b/machxo2/bitstream.cc
@@ -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
diff --git a/machxo2/bitstream.h b/machxo2/bitstream.h
new file mode 100644
index 00000000..e54e134a
--- /dev/null
+++ b/machxo2/bitstream.h
@@ -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
diff --git a/machxo2/cells.cc b/machxo2/cells.cc
new file mode 100644
index 00000000..03ba0a41
--- /dev/null
+++ b/machxo2/cells.cc
@@ -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
diff --git a/machxo2/cells.h b/machxo2/cells.h
new file mode 100644
index 00000000..a6de219e
--- /dev/null
+++ b/machxo2/cells.h
@@ -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
diff --git a/machxo2/config.cc b/machxo2/config.cc
new file mode 100644
index 00000000..2e17ce24
--- /dev/null
+++ b/machxo2/config.cc
@@ -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
diff --git a/machxo2/config.h b/machxo2/config.h
new file mode 100644
index 00000000..9e09d721
--- /dev/null
+++ b/machxo2/config.h
@@ -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
diff --git a/machxo2/constids.inc b/machxo2/constids.inc
new file mode 100644
index 00000000..b2ff51ae
--- /dev/null
+++ b/machxo2/constids.inc
@@ -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)
diff --git a/machxo2/examples/.gitignore b/machxo2/examples/.gitignore
new file mode 100644
index 00000000..8a87cc8d
--- /dev/null
+++ b/machxo2/examples/.gitignore
@@ -0,0 +1,11 @@
+*_simtest*
+*.vcd
+*.png
+*.log
+*.smt2
+pack*.v
+place*.v
+pnr*.v
+abc.history
+*.txt
+*.bit
diff --git a/machxo2/examples/README.md b/machxo2/examples/README.md
new file mode 100644
index 00000000..3542da70
--- /dev/null
+++ b/machxo2/examples/README.md
@@ -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.
diff --git a/machxo2/examples/blinky.v b/machxo2/examples/blinky.v
new file mode 100644
index 00000000..57bad543
--- /dev/null
+++ b/machxo2/examples/blinky.v
@@ -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
diff --git a/machxo2/examples/blinky_ext.v b/machxo2/examples/blinky_ext.v
new file mode 100644
index 00000000..a8bdd588
--- /dev/null
+++ b/machxo2/examples/blinky_ext.v
@@ -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
diff --git a/machxo2/examples/blinky_tb.v b/machxo2/examples/blinky_tb.v
new file mode 100644
index 00000000..f9925e6f
--- /dev/null
+++ b/machxo2/examples/blinky_tb.v
@@ -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
diff --git a/machxo2/examples/demo.sh b/machxo2/examples/demo.sh
new file mode 100644
index 00000000..00cb0cd0
--- /dev/null
+++ b/machxo2/examples/demo.sh
@@ -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
diff --git a/machxo2/examples/mitertest.sh b/machxo2/examples/mitertest.sh
new file mode 100644
index 00000000..cfae28b7
--- /dev/null
+++ b/machxo2/examples/mitertest.sh
@@ -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
diff --git a/machxo2/examples/rgbcount.v b/machxo2/examples/rgbcount.v
new file mode 100644
index 00000000..bf5c7518
--- /dev/null
+++ b/machxo2/examples/rgbcount.v
@@ -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
diff --git a/machxo2/examples/simple.sh b/machxo2/examples/simple.sh
new file mode 100644
index 00000000..1da60933
--- /dev/null
+++ b/machxo2/examples/simple.sh
@@ -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"
diff --git a/machxo2/examples/simtest.sh b/machxo2/examples/simtest.sh
new file mode 100644
index 00000000..2c7f6f30
--- /dev/null
+++ b/machxo2/examples/simtest.sh
@@ -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
diff --git a/machxo2/examples/tinyfpga.v b/machxo2/examples/tinyfpga.v
new file mode 100644
index 00000000..bd26d8eb
--- /dev/null
+++ b/machxo2/examples/tinyfpga.v
@@ -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
diff --git a/machxo2/examples/uart.v b/machxo2/examples/uart.v
new file mode 100644
index 00000000..f1d95bd8
--- /dev/null
+++ b/machxo2/examples/uart.v
@@ -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
diff --git a/machxo2/facade_import.py b/machxo2/facade_import.py
new file mode 100644
index 00000000..5bb2d78b
--- /dev/null
+++ b/machxo2/facade_import.py
@@ -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()
diff --git a/machxo2/family.cmake b/machxo2/family.cmake
new file mode 100644
index 00000000..76c5df93
--- /dev/null
+++ b/machxo2/family.cmake
@@ -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()
diff --git a/machxo2/main.cc b/machxo2/main.cc
new file mode 100644
index 00000000..961fe9ae
--- /dev/null
+++ b/machxo2/main.cc
@@ -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
diff --git a/machxo2/pack.cc b/machxo2/pack.cc
new file mode 100644
index 00000000..5a6cd97b
--- /dev/null
+++ b/machxo2/pack.cc
@@ -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