diff --git a/.cirrus/Dockerfile.ubuntu20.04 b/.cirrus/Dockerfile.ubuntu20.04 index 74371214..60bbcc1b 100644 --- a/.cirrus/Dockerfile.ubuntu20.04 +++ b/.cirrus/Dockerfile.ubuntu20.04 @@ -47,7 +47,7 @@ RUN set -e -x ;\ cd /usr/local/src ;\ git clone --recursive https://github.com/YosysHQ/prjtrellis.git ;\ cd prjtrellis ;\ - git reset --hard 7831b54f619d6695855525fca776543b7c827704 ;\ + git reset --hard 210a0a72757d57b278ac7397ae6b14729f149b10 ;\ cd libtrellis ;\ cmake . ;\ make -j $(nproc) ;\ diff --git a/.gitignore b/.gitignore index 9ddd4a4e..eea5bf5c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /nextpnr-nexus* /nextpnr-fpga_interchange* /nextpnr-gowin* +/nextpnr-machxo2* cmake-build-*/ Makefile cmake_install.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bfef987..1b7e147c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,9 +66,9 @@ endif() set(PROGRAM_PREFIX "" CACHE STRING "Name prefix for executables") # List of families to build -set(FAMILIES generic ice40 ecp5 nexus gowin fpga_interchange) +set(FAMILIES generic ice40 ecp5 nexus gowin fpga_interchange machxo2) set(STABLE_FAMILIES generic ice40 ecp5) -set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange) +set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange machxo2) set(ARCH "" CACHE STRING "Architecture family for nextpnr build") set_property(CACHE ARCH PROPERTY STRINGS ${FAMILIES}) @@ -297,6 +297,7 @@ file(GLOB_RECURSE CLANGFORMAT_FILES *.cc *.h) string(REGEX REPLACE "[^;]*/ice40/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}") string(REGEX REPLACE "[^;]*/ecp5/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}") string(REGEX REPLACE "[^;]*nexus/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}") +string(REGEX REPLACE "[^;]*/machxo2/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}") string(REGEX REPLACE "[^;]*/3rdparty[^;]*" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}") string(REGEX REPLACE "[^;]*/generated[^;]*" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}") diff --git a/gui/machxo2/family.cmake b/gui/machxo2/family.cmake new file mode 100644 index 00000000..e69de29b diff --git a/gui/machxo2/mainwindow.cc b/gui/machxo2/mainwindow.cc new file mode 100644 index 00000000..d1266102 --- /dev/null +++ b/gui/machxo2/mainwindow.cc @@ -0,0 +1,107 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "mainwindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "design_utils.h" +#include "log.h" + +static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } + +NEXTPNR_NAMESPACE_BEGIN + +MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) + : BaseMainWindow(std::move(context), handler, parent) +{ + initMainResource(); + + std::string title = "nextpnr-xo2 - [EMPTY]"; + setWindowTitle(title.c_str()); + + connect(this, &BaseMainWindow::contextChanged, this, &MainWindow::newContext); + + createMenu(); +} + +MainWindow::~MainWindow() {} + +void MainWindow::createMenu() +{ + // Add arch specific actions + + // Add actions in menus +} + +void MainWindow::new_proj() +{ + QMap arch; + if (Arch::is_available(ArchArgs::LCMXO2_256HC)) + arch.insert("LCMXO2-256HC", ArchArgs::LCMXO2_256HC); + if (Arch::is_available(ArchArgs::LCMXO2_640HC)) + arch.insert("LCMXO2-640HC", ArchArgs::LCMXO2_640HC); + if (Arch::is_available(ArchArgs::LCMXO2_1200HC)) + arch.insert("LCMXO2-1200HC", ArchArgs::LCMXO2_1200HC); + if (Arch::is_available(ArchArgs::LCMXO2_2000HC)) + arch.insert("LCMXO2-2000HC", ArchArgs::LCMXO2_2000HC); + if (Arch::is_available(ArchArgs::LCMXO2_4000HC)) + arch.insert("LCMXO2-4000HC", ArchArgs::LCMXO2_4000HC); + if (Arch::is_available(ArchArgs::LCMXO2_7000HC)) + arch.insert("LCMXO2-7000HC", ArchArgs::LCMXO2_7000HC); + bool ok; + QString item = QInputDialog::getItem(this, "Select new context", "Chip:", arch.keys(), 0, false, &ok); + if (ok && !item.isEmpty()) { + ArchArgs chipArgs; + chipArgs.type = (ArchArgs::ArchArgsTypes)arch.value(item); + + QStringList packages; + for (auto package : Arch::get_supported_packages(chipArgs.type)) + packages.append(QLatin1String(package.data(), package.size())); + QString package = QInputDialog::getItem(this, "Select package", "Package:", packages, 0, false, &ok); + + if (ok && !item.isEmpty()) { + handler->clear(); + currentProj = ""; + disableActions(); + chipArgs.package = package.toStdString().c_str(); + ctx = std::unique_ptr(new Context(chipArgs)); + actionLoadJSON->setEnabled(true); + + Q_EMIT contextChanged(ctx.get()); + } + } +} + +void MainWindow::newContext(Context *ctx) +{ + std::string title = "nextpnr-xo2 - " + ctx->getChipName(); + setWindowTitle(title.c_str()); +} + +void MainWindow::onDisableActions() {} + +void MainWindow::onUpdateActions() {} + +NEXTPNR_NAMESPACE_END diff --git a/gui/machxo2/mainwindow.h b/gui/machxo2/mainwindow.h new file mode 100644 index 00000000..13556fa1 --- /dev/null +++ b/gui/machxo2/mainwindow.h @@ -0,0 +1,49 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "../basewindow.h" + +NEXTPNR_NAMESPACE_BEGIN + +class MainWindow : public BaseMainWindow +{ + Q_OBJECT + + public: + explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); + virtual ~MainWindow(); + + public: + void createMenu(); + + void onDisableActions() override; + void onUpdateActions() override; + + protected Q_SLOTS: + void new_proj() override; + + void newContext(Context *ctx); +}; + +NEXTPNR_NAMESPACE_END + +#endif // MAINWINDOW_H diff --git a/gui/machxo2/nextpnr.qrc b/gui/machxo2/nextpnr.qrc new file mode 100644 index 00000000..03585ec0 --- /dev/null +++ b/gui/machxo2/nextpnr.qrc @@ -0,0 +1,2 @@ + + 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 + * Copyright (C) 2021 William D. Jones + * + * 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 +#include +#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 *>(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 Arch::get_supported_packages(ArchArgs::ArchArgsTypes chip) +{ + const ChipInfoPOD *chip_info = get_chip_info(chip); + std::vector 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 Arch::getBelPins(BelId bel) const +{ + std::vector 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 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 Arch::availablePlacers = {"sa", +#ifdef WITH_HEAP + "heap" +#endif +}; + +const std::string Arch::defaultRouter = "router1"; +const std::vector Arch::availableRouters = {"router1", "router2"}; + +bool Arch::cells_compatible(const CellInfo **cells, int count) const { return false; } + +std::vector> Arch::get_tiles_at_location(int row, int col) +{ + std::vector> 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 + * Copyright (C) 2021 William D. Jones + * + * 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 struct RelPtr +{ + int32_t offset; + + // void set(const T *ptr) { + // offset = reinterpret_cast(ptr) - + // reinterpret_cast(this); + // } + + const T *get() const { return reinterpret_cast(reinterpret_cast(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 name; + int32_t type; + int32_t z; + int32_t num_bel_wires; + RelPtr 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 name; + int32_t tile_wire; + int32_t num_uphill; + int32_t num_downhill; + RelPtr pips_uphill; + RelPtr pips_downhill; + int32_t num_bel_pins; + RelPtr bel_pins; +}); + +NPNR_PACKED_STRUCT(struct TileTypePOD { + int32_t num_bels; + int32_t num_wires; + int32_t num_pips; + RelPtr bel_data; + RelPtr wire_data; + RelPtr pips_data; +}); + +NPNR_PACKED_STRUCT(struct PackagePinPOD { + RelPtr name; + LocationPOD abs_loc; + int32_t bel_index; +}); + +NPNR_PACKED_STRUCT(struct PackageInfoPOD { + RelPtr name; + int32_t num_pins; + RelPtr pin_data; +}); + +NPNR_PACKED_STRUCT(struct PIOInfoPOD { + LocationPOD abs_loc; + int32_t bel_index; + RelPtr function_name; + int16_t bank; + int16_t dqsgroup; +}); + +NPNR_PACKED_STRUCT(struct TileNamePOD { + RelPtr name; + int16_t type_idx; + int16_t padding; +}); + +NPNR_PACKED_STRUCT(struct TileInfoPOD { + int32_t num_tiles; + RelPtr 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 tiles; + RelPtr> tiletype_names; + RelPtr package_info; + RelPtr pio_info; + RelPtr 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; + // Wires + using AllWiresRangeT = WireRange; + using DownhillPipRangeT = PipRange; + using UphillPipRangeT = PipRange; + using WireBelPinRangeT = BelPinRange; + // Pips + using AllPipsRangeT = AllPipRange; +}; + +struct Arch : BaseArch +{ + const ChipInfoPOD *chip_info; + const PackageInfoPOD *package_info; + + mutable std::unordered_map pip_by_name; + + // fast access to X and Y IdStrings for building object names + std::vector x_ids, y_ids; + // inverse of the above for name->object mapping + std::unordered_map id_to_x, id_to_y; + + // Helpers + template 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 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 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 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 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 availablePlacers; + static const std::string defaultRouter; + static const std::vector availableRouters; + + // --------------------------------------------------------------- + // Internal usage + bool cells_compatible(const CellInfo **cells, int count) const; + + std::vector> 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 &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 + * Copyright (C) 2018 David Shah + * + * 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_(m, "ArchArgs").def_readwrite("type", &ArchArgs::type); + + py::class_(m, "BelId").def_readwrite("index", &BelId::index); + + py::class_(m, "WireId").def_readwrite("index", &WireId::index); + + py::class_(m, "PipId").def_readwrite("index", &PipId::index); + + auto arch_cls = py::class_(m, "Arch").def(py::init()); + auto ctx_cls = py::class_(m, "Context") + .def("checksum", &Context::checksum) + .def("pack", &Context::pack) + .def("place", &Context::place) + .def("route", &Context::route); + + fn_wrapper_2a, + addr_and_unwrap, conv_from_str>::def_wrap(ctx_cls, "isValidBelForCell"); + + typedef std::unordered_map> CellMap; + typedef std::unordered_map> NetMap; + typedef std::unordered_map AliasMap; + typedef std::unordered_map HierarchyMap; + + auto belpin_cls = py::class_>(m, "BelPin"); + readonly_wrapper>::def_wrap(belpin_cls, "bel"); + readonly_wrapper>::def_wrap(belpin_cls, "pin"); + + typedef const PipRange UphillPipRange; + typedef const PipRange DownhillPipRange; + + typedef const std::vector &BelBucketRange; + typedef const std::vector &BelRangeForBelBucket; +#include "arch_pybindings_shared.h" + + WRAP_RANGE(m, Bel, conv_to_str); + WRAP_RANGE(m, Wire, conv_to_str); + WRAP_RANGE(m, AllPip, conv_to_str); + WRAP_RANGE(m, Pip, conv_to_str); + WRAP_RANGE(m, BelPin, wrap_context); + + WRAP_MAP_UPTR(m, CellMap, "IdCellMap"); + WRAP_MAP_UPTR(m, NetMap, "IdNetMap"); + WRAP_MAP(m, HierarchyMap, wrap_context, "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 + * Copyright (C) 2018 David Shah + * + * 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 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 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 +{ + 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 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 from_str(Context *ctx, std::string name) + { + NPNR_ASSERT_FALSE("string_converter::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 + * Copyright (C) 2021 William D. Jones + * + * 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 +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX Location &loc) const noexcept + { + std::size_t seed = std::hash()(loc.x); + seed ^= std::hash()(loc.y) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept + { + std::size_t seed = std::hash()(bel.location); + seed ^= std::hash()(bel.index) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept + { + std::size_t seed = std::hash()(wire.location); + seed ^= std::hash()(wire.index) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept + { + std::size_t seed = std::hash()(pip.location); + seed ^= std::hash()(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 + * Copyright (C) 2021 William D. Jones + * + * 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 + +#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 int_to_bitvector(int val, int size) +{ + std::vector bv; + for (int i = 0; i < size; i++) { + bv.push_back((val & (1 << i)) != 0); + } + return bv; +} + +static std::vector str_to_bitvector(std::string str, int size) +{ + std::vector 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 &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 pio_l = {"PIC_L0", "PIC_LS0", "PIC_L0_VREF3"}; + static const std::set 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 + * Copyright (C) 2021 William D. Jones + * + * 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 + * Copyright (C) 2021 William D. Jones + * + * 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 create_machxo2_cell(Context *ctx, IdString type, std::string name) +{ + static int auto_idx = 0; + std::unique_ptr new_cell = std::unique_ptr(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 &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 + * Copyright (C) 2021 William D. Jones + * + * 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 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 &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 + * Copyright (C) 2021 William D. Jones + * + * 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 +#include +#include "log.h" +NEXTPNR_NAMESPACE_BEGIN + +#define fmt(x) (static_cast(std::ostringstream() << x).str()) + +inline std::string to_string(const std::vector &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 &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 &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 + * Copyright (C) 2021 William D. Jones + * + * 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 +#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 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 carcs; + std::vector cwords; + std::vector cenums; + std::vector 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 &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 tiles; + TileConfig config; +}; + +// This represents the configuration of a chip at a high level +class ChipConfig +{ + public: + std::string chip_name; + std::vector metadata; + std::map tiles; + std::vector tilegroups; + std::map sysconfig; + std::map> 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") + 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 $) +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 + * Copyright (C) 2021 William D. Jones + * + * 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 +#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 createContext(std::unordered_map &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(), "select device package"); + specific.add_options()("speed", po::value(), "select device speedgrade (1 to 6 inclusive)"); + + specific.add_options()("override-basecfg", po::value(), + "base chip configuration in Trellis text format"); + specific.add_options()("textcfg", po::value(), "textual configuration in Trellis format to write"); + + // specific.add_options()("lpf", po::value>(), "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(); + + write_bitstream(ctx, textcfg); +} + +std::unique_ptr MachXO2CommandHandler::createContext(std::unordered_map &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(); + + 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(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 + * Copyright (C) 2021 William D. Jones + * + * 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 +#include +#include +#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 packed_cells; + std::vector> 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 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 packed_cells; + std::vector> 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 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 packed_cells; + std::vector> 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 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 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 gnd_net = std::unique_ptr(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 vcc_net = std::unique_ptr(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 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 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