diff options
| author | gatecat <gatecat@ds0.me> | 2021-02-15 09:39:56 +0000 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-02-15 09:39:56 +0000 | 
| commit | 065f46daeb05a8b12cc663a44410b6da27a8d9e3 (patch) | |
| tree | ca538791141e442c105c47f836069f95ce2f988d /machxo2 | |
| parent | 1b6cdce9251d42236a3db0314e84d6a3e3f06408 (diff) | |
| parent | 9c9a02628d60dca9c9a566b0fcf3bf3dd2c68076 (diff) | |
| download | nextpnr-065f46daeb05a8b12cc663a44410b6da27a8d9e3.tar.gz nextpnr-065f46daeb05a8b12cc663a44410b6da27a8d9e3.tar.bz2 nextpnr-065f46daeb05a8b12cc663a44410b6da27a8d9e3.zip  | |
Merge pull request #578 from YosysHQ/machxo2-rebase
machxo2, rebased and updated
Diffstat (limited to 'machxo2')
31 files changed, 4348 insertions, 0 deletions
diff --git a/machxo2/.gitignore b/machxo2/.gitignore new file mode 100644 index 00000000..48c9c5ce --- /dev/null +++ b/machxo2/.gitignore @@ -0,0 +1 @@ +chipdb/ diff --git a/machxo2/CMakeLists.txt b/machxo2/CMakeLists.txt new file mode 100644 index 00000000..ab4eded6 --- /dev/null +++ b/machxo2/CMakeLists.txt @@ -0,0 +1,91 @@ +cmake_minimum_required(VERSION 3.5) +project(chipdb-machxo2 NONE) + +# set(ALL_MACHXO2_DEVICES 256 640 1200 2000 4000 7000) +set(ALL_MACHXO2_DEVICES 1200) +set(MACHXO2_DEVICES 1200 CACHE STRING +    "Include support for these MachXO2 devices (available: ${ALL_MACHXO2_DEVICES})") +message(STATUS "Enabled MachXO2 devices: ${MACHXO2_DEVICES}") + +if(DEFINED MACHXO2_CHIPDB) +    add_custom_target(chipdb-machxo2-bbas ALL) +else() +    find_package(PythonInterp 3.5 REQUIRED) + +    # shared among all families +    set(SERIALIZE_CHIPDBS TRUE CACHE BOOL +        "Serialize device data preprocessing to minimize memory use") + +    set(TRELLIS_PROGRAM_PREFIX "" CACHE STRING +        "Trellis name prefix") +    if(TRELLIS_PROGRAM_PREFIX) +        message(STATUS "Trellis program prefix: ${TRELLIS_PROGRAM_PREFIX}") +    endif() + +    set(TRELLIS_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX} CACHE STRING +        "Trellis install prefix") +    message(STATUS "Trellis install prefix: ${TRELLIS_INSTALL_PREFIX}") + +    if(NOT DEFINED TRELLIS_LIBDIR) +        if(WIN32) +            set(pytrellis_lib pytrellis.pyd) +        else() +            set(pytrellis_lib pytrellis${CMAKE_SHARED_LIBRARY_SUFFIX}) +        endif() +        find_path(TRELLIS_LIBDIR ${pytrellis_lib} +            HINTS ${TRELLIS_INSTALL_PREFIX}/lib/${TRELLIS_PROGRAM_PREFIX}trellis +            PATHS ${CMAKE_SYSTEM_LIBRARY_PATH} ${CMAKE_LIBRARY_PATH} +            PATH_SUFFIXES ${TRELLIS_PROGRAM_PREFIX}trellis +            DOC "Location of the pytrellis library") +        if(NOT TRELLIS_LIBDIR) +            message(FATAL_ERROR "Failed to locate the pytrellis library") +        endif() +    endif() +    message(STATUS "Trellis library directory: ${TRELLIS_LIBDIR}") + +    if(NOT DEFINED TRELLIS_DATADIR) +        set(TRELLIS_DATADIR ${TRELLIS_INSTALL_PREFIX}/share/${TRELLIS_PROGRAM_PREFIX}trellis) +    endif() +    message(STATUS "Trellis data directory: ${TRELLIS_DATADIR}") + +    set(all_device_bbas) +    file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chipdb) +    foreach(device ${MACHXO2_DEVICES}) +        if(NOT device IN_LIST ALL_MACHXO2_DEVICES) +            message(FATAL_ERROR "Device ${device} is not a supported MachXO2 device") +        endif() + +        set(device_bba chipdb/chipdb-${device}.bba) +        add_custom_command( +            OUTPUT ${device_bba} +            COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/facade_import.py +                -L ${TRELLIS_LIBDIR} +                -L ${TRELLIS_DATADIR}/util/common +                -L ${TRELLIS_DATADIR}/timing/util +                -p ${CMAKE_CURRENT_SOURCE_DIR}/constids.inc +                ${device} +                > ${device_bba}.new +            # atomically update +            COMMAND ${CMAKE_COMMAND} -E rename ${device_bba}.new ${device_bba} +            DEPENDS +                ${CMAKE_CURRENT_SOURCE_DIR}/facade_import.py +                ${CMAKE_CURRENT_SOURCE_DIR}/constids.inc +                ${PREVIOUS_CHIPDB_TARGET} +            VERBATIM) +        list(APPEND all_device_bbas ${device_bba}) +        if(SERIALIZE_CHIPDBS) +            set(PREVIOUS_CHIPDB_TARGET ${CMAKE_CURRENT_BINARY_DIR}/${device_bba}) +        endif() +    endforeach() + +    add_custom_target(chipdb-machxo2-bbas ALL DEPENDS ${all_device_bbas}) + +    get_directory_property(has_parent PARENT_DIRECTORY) +    if(has_parent) +        set(MACHXO2_CHIPDB ${CMAKE_CURRENT_BINARY_DIR}/chipdb PARENT_SCOPE) +        # serialize chipdb build across multiple architectures +        set(PREVIOUS_CHIPDB_TARGET chipdb-machxo2-bbas PARENT_SCOPE) +    else() +        message(STATUS "Build nextpnr with -DMACHXO2_CHIPDB=${CMAKE_CURRENT_BINARY_DIR}") +    endif() +endif() diff --git a/machxo2/README.md b/machxo2/README.md new file mode 100644 index 00000000..55cc5763 --- /dev/null +++ b/machxo2/README.md @@ -0,0 +1,105 @@ +# `nextpnr-machxo2` + +_Experimental_ FOSS Place And Route backend for the Lattice MachXO2 family of +FPGAs. Fuzzing takes place as a subproject of [`prjtrellis`](https://github.com/YosysHQ/prjtrellis). + +Known to work: + +* Basic routing from pads to SLICEs and back! +* Basic packing of one type of FF and LUT into _half_ of a SLICE! +* Using the internal oscillator `OSCH` as a clock +* `LOGIC` SLICE mode + +Things that probably work but are untested: + +* Any non-3.3V I/O standard that doesn't use bank VREFs. + +Things remaining to do include (but not limited to): + +* More intelligent and efficient packing +* Global Routing (exists in database/sim models, `nextpnr-machxo2` doesn't use +  it yet) +* Secondary High Fanout Nets +* Edge Clocks (clock pads work, but not routed to global routing yet) +* PLLs +* Synchronous Release Global Set/Reset Interface (`SGSR`) +* Embedded Function Block (`EFB`) +* All DDR-related functionality +* Bank VREFs +* Embedded Block RAM (`EBR`) +* `CCU2` and `DPRAM` SLICE modes + +## Quick Start + +The following commands are known to work on a near-fresh Linux Mint system +(thank you [securelyfitz](https://twitter.com/securelyfitz)!): + +### Prerequisites + +``` +sudo apt install cmake clang-format libboost-all-dev build-essential +qt5-default libeigen3-dev build-essential clang bison flex libreadline-dev +gawk tcl-dev libffi-dev git graphviz xdot pkg-config python3 +libboost-system-dev libboost-python-dev libboost-filesystem-dev zlib1g-dev +python3-setuptools python3-serial +``` + +### Installation + +Use an empty directory to hold all the cloned repositories. Upstream repos +can be used as well (e.g. [`YosysHQ/prjtrellis`](https://github.com/YosysHQ/prjtrellis), +etc.). + +``` +git clone git@github.com:cr1901/prjtrellis.git +cd prjtrellis +git checkout facade +git submodule update --init --recursive +cd libtrellis +cmake -DCMAKE_INSTALL_PREFIX=/usr +make -j 8 +sudo make install + +cd ../../ + +git clone git@github.com:cr1901/yosys.git +cd yosys/ +git checkout machxo2 +make config-gcc +make +sudo make install + +cd ../ + +git clone git@github.com:tinyfpga/TinyFPGA-A-Programmer.git +cd TinyFPGA-A-Programmer/ +sudo python setup.py install + +cd ../ + +git clone git@github.com:cr1901/nextpnr.git +cd nextpnr +git checkout machxo2 +git submodule update --init --recursive +cmake . -DARCH=machxo2 -DBUILD_GUI=OFF  -DTRELLIS_INSTALL_PREFIX=/usr -DBUILD_PYTHON=OFF -DBUILD_HEAP=OFF +make +``` + +Although uncommon, the `facade` and `machxo2` branches of the above repos are +occassionally rebased; use `git pull -f` if necessary. + +### Demo + +If you have a [TinyFPGA Ax2](https://store.tinyfpga.com/products/tinyfpga-a2) board +with the [TinyFPGA Programmer](https://store.tinyfpga.com/products/tinyfpga-programmer), +the following script will build a blinky bitstream and load it onto the +MachXO2; the gateware will flash the LED! + +``` +cd machxo2/examples/ +sh demo.sh tinyfpga +``` + +The `tinyfpga.v` code used in `demo.sh` is slightly modified from the +[user's guide](https://tinyfpga.com/a-series-guide.html) to accommodate +`(* LOC = "pin" *)` constraints and the built-in user LED. diff --git a/machxo2/arch.cc b/machxo2/arch.cc new file mode 100644 index 00000000..2938f1ba --- /dev/null +++ b/machxo2/arch.cc @@ -0,0 +1,494 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018  Claire Xen <claire@symbioticeda.com> + *  Copyright (C) 2021  William D. Jones <wjones@wdj-consulting.com> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <iostream> +#include <math.h> +#include "embed.h" +#include "nextpnr.h" +#include "placer1.h" +#include "placer_heap.h" +#include "router1.h" +#include "router2.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +// ----------------------------------------------------------------------- + +void IdString::initialize_arch(const BaseCtx *ctx) +{ +#define X(t) initialize_add(ctx, #t, ID_##t); + +#include "constids.inc" + +#undef X +} + +// --------------------------------------------------------------- + +static const ChipInfoPOD *get_chip_info(ArchArgs::ArchArgsTypes chip) +{ +    std::string chipdb; +    if (chip == ArchArgs::LCMXO2_256HC) { +        chipdb = "machxo2/chipdb-256.bin"; +    } else if (chip == ArchArgs::LCMXO2_640HC) { +        chipdb = "machxo2/chipdb-640.bin"; +    } else if (chip == ArchArgs::LCMXO2_1200HC) { +        chipdb = "machxo2/chipdb-1200.bin"; +    } else if (chip == ArchArgs::LCMXO2_2000HC) { +        chipdb = "machxo2/chipdb-2000.bin"; +    } else if (chip == ArchArgs::LCMXO2_4000HC) { +        chipdb = "machxo2/chipdb-4000.bin"; +    } else if (chip == ArchArgs::LCMXO2_7000HC) { +        chipdb = "machxo2/chipdb-7000.bin"; +    } else { +        log_error("Unknown chip\n"); +    } + +    auto ptr = reinterpret_cast<const RelPtr<ChipInfoPOD> *>(get_chipdb(chipdb)); +    if (ptr == nullptr) +        return nullptr; +    return ptr->get(); +} + +// --------------------------------------------------------------- + +Arch::Arch(ArchArgs args) : args(args) +{ +    chip_info = get_chip_info(args.type); +    if (chip_info == nullptr) +        log_error("Unsupported MachXO2 chip type.\n"); +    if (chip_info->const_id_count != DB_CONST_ID_COUNT) +        log_error("Chip database 'bba' and nextpnr code are out of sync; please rebuild (or contact distribution " +                  "maintainer)!\n"); + +    package_info = nullptr; +    for (int i = 0; i < chip_info->num_packages; i++) { +        if (args.package == chip_info->package_info[i].name.get()) { +            package_info = &(chip_info->package_info[i]); +            break; +        } +    } +    if (!package_info) +        log_error("Unsupported package '%s' for '%s'.\n", args.package.c_str(), getChipName().c_str()); + +    BaseArch::init_cell_types(); +    BaseArch::init_bel_buckets(); + +    for (int i = 0; i < chip_info->width; i++) +        x_ids.push_back(id(stringf("X%d", i))); +    for (int i = 0; i < chip_info->height; i++) +        y_ids.push_back(id(stringf("Y%d", i))); + +    for (int i = 0; i < chip_info->width; i++) { +        IdString x_id = id(stringf("X%d", i)); +        x_ids.push_back(x_id); +        id_to_x[x_id] = i; +    } +    for (int i = 0; i < chip_info->height; i++) { +        IdString y_id = id(stringf("Y%d", i)); +        y_ids.push_back(y_id); +        id_to_y[y_id] = i; +    } +} + +bool Arch::is_available(ArchArgs::ArchArgsTypes chip) { return get_chip_info(chip) != nullptr; } + +std::vector<std::string> Arch::get_supported_packages(ArchArgs::ArchArgsTypes chip) +{ +    const ChipInfoPOD *chip_info = get_chip_info(chip); +    std::vector<std::string> pkgs; +    for (int i = 0; i < chip_info->num_packages; i++) { +        pkgs.push_back(chip_info->package_info[i].name.get()); +    } +    return pkgs; +} + +std::string Arch::getChipName() const +{ +    if (args.type == ArchArgs::LCMXO2_256HC) { +        return "LCMXO2-256HC"; +    } else if (args.type == ArchArgs::LCMXO2_640HC) { +        return "LCMXO2-640HC"; +    } else if (args.type == ArchArgs::LCMXO2_1200HC) { +        return "LCMXO2-1200HC"; +    } else if (args.type == ArchArgs::LCMXO2_2000HC) { +        return "LCMXO2-2000HC"; +    } else if (args.type == ArchArgs::LCMXO2_4000HC) { +        return "LCMXO2-4000HC"; +    } else if (args.type == ArchArgs::LCMXO2_7000HC) { +        return "LCMXO2-7000HC"; +    } else { +        log_error("Unknown chip\n"); +    } +} + +std::string Arch::get_full_chip_name() const +{ +    std::string name = getChipName(); +    name += "-"; +    switch (args.speed) { +    case ArchArgs::SPEED_1: +        name += "1"; +        break; +    case ArchArgs::SPEED_2: +        name += "2"; +        break; +    case ArchArgs::SPEED_3: +        name += "3"; +    case ArchArgs::SPEED_4: +        name += "4"; +        break; +    case ArchArgs::SPEED_5: +        name += "5"; +        break; +    case ArchArgs::SPEED_6: +        name += "6"; +        break; +    } +    name += args.package; +    return name; +} + +IdString Arch::archArgsToId(ArchArgs args) const +{ +    if (args.type == ArchArgs::LCMXO2_256HC) { +        return id("lcmxo2_256hc"); +    } else if (args.type == ArchArgs::LCMXO2_640HC) { +        return id("lcmxo2_640hc"); +    } else if (args.type == ArchArgs::LCMXO2_1200HC) { +        return id("lcmxo2_1200hc"); +    } else if (args.type == ArchArgs::LCMXO2_2000HC) { +        return id("lcmxo2_2000hc"); +    } else if (args.type == ArchArgs::LCMXO2_4000HC) { +        return id("lcmxo2_4000hc"); +    } else if (args.type == ArchArgs::LCMXO2_7000HC) { +        return id("lcmxo2_7000hc"); +    } + +    return IdString(); +} + +// --------------------------------------------------------------- + +BelId Arch::getBelByName(IdStringList name) const +{ +    if (name.size() != 3) +        return BelId(); +    BelId ret; +    Location loc; +    loc.x = id_to_x.at(name[0]); +    loc.y = id_to_y.at(name[1]); +    ret.location = loc; +    const TileTypePOD *loci = tile_info(ret); +    for (int i = 0; i < loci->num_bels; i++) { +        if (std::strcmp(loci->bel_data[i].name.get(), name[2].c_str(this)) == 0) { +            ret.index = i; +            return ret; +        } +    } +    return BelId(); +} + +BelId Arch::getBelByLocation(Loc loc) const +{ +    BelId ret; + +    if (loc.x >= chip_info->width || loc.y >= chip_info->height) +        return BelId(); + +    ret.location.x = loc.x; +    ret.location.y = loc.y; + +    const TileTypePOD *tilei = tile_info(ret); +    for (int i = 0; i < tilei->num_bels; i++) { +        if (tilei->bel_data[i].z == loc.z) { +            ret.index = i; +            return ret; +        } +    } + +    return BelId(); +} + +BelRange Arch::getBelsByTile(int x, int y) const +{ +    BelRange br; + +    br.b.cursor_tile = y * chip_info->width + x; +    br.e.cursor_tile = y * chip_info->width + x; +    br.b.cursor_index = 0; +    br.e.cursor_index = chip_info->tiles[y * chip_info->width + x].num_bels - 1; +    br.b.chip = chip_info; +    br.e.chip = chip_info; +    if (br.e.cursor_index == -1) +        ++br.e.cursor_index; +    else +        ++br.e; +    return br; +} + +bool Arch::getBelGlobalBuf(BelId bel) const { return false; } + +WireId Arch::getBelPinWire(BelId bel, IdString pin) const +{ +    NPNR_ASSERT(bel != BelId()); + +    int num_bel_wires = tile_info(bel)->bel_data[bel.index].num_bel_wires; +    const BelWirePOD *bel_wires = &*tile_info(bel)->bel_data[bel.index].bel_wires; + +    for (int i = 0; i < num_bel_wires; i++) +        if (bel_wires[i].port == pin.index) { +            WireId ret; + +            ret.location.x = bel_wires[i].rel_wire_loc.x; +            ret.location.y = bel_wires[i].rel_wire_loc.y; +            ret.index = bel_wires[i].wire_index; + +            return ret; +        } + +    return WireId(); +} + +PortType Arch::getBelPinType(BelId bel, IdString pin) const +{ +    NPNR_ASSERT(bel != BelId()); + +    int num_bel_wires = tile_info(bel)->bel_data[bel.index].num_bel_wires; +    const BelWirePOD *bel_wires = &*tile_info(bel)->bel_data[bel.index].bel_wires; + +    for (int i = 0; i < num_bel_wires; i++) +        if (bel_wires[i].port == pin.index) +            return PortType(bel_wires[i].dir); + +    return PORT_INOUT; +} + +std::vector<IdString> Arch::getBelPins(BelId bel) const +{ +    std::vector<IdString> ret; +    NPNR_ASSERT(bel != BelId()); + +    int num_bel_wires = tile_info(bel)->bel_data[bel.index].num_bel_wires; +    const BelWirePOD *bel_wires = &*tile_info(bel)->bel_data[bel.index].bel_wires; + +    for (int i = 0; i < num_bel_wires; i++) { +        IdString id(bel_wires[i].port); +        ret.push_back(id); +    } + +    return ret; +} + +// --------------------------------------------------------------- + +BelId Arch::getPackagePinBel(const std::string &pin) const +{ +    for (int i = 0; i < package_info->num_pins; i++) { +        if (package_info->pin_data[i].name.get() == pin) { +            BelId bel; +            bel.location = package_info->pin_data[i].abs_loc; +            bel.index = package_info->pin_data[i].bel_index; +            return bel; +        } +    } +    return BelId(); +} + +// --------------------------------------------------------------- + +WireId Arch::getWireByName(IdStringList name) const +{ +    if (name.size() != 3) +        return WireId(); +    WireId ret; +    Location loc; +    loc.x = id_to_x.at(name[0]); +    loc.y = id_to_y.at(name[1]); +    ret.location = loc; +    const TileTypePOD *loci = tile_info(ret); +    for (int i = 0; i < loci->num_wires; i++) { +        if (std::strcmp(loci->wire_data[i].name.get(), name[2].c_str(this)) == 0) { +            ret.index = i; +            return ret; +        } +    } +    return WireId(); +} + +// --------------------------------------------------------------- + +PipId Arch::getPipByName(IdStringList name) const +{ +    if (name.size() != 3) +        return PipId(); +    auto it = pip_by_name.find(name); +    if (it != pip_by_name.end()) +        return it->second; + +    PipId ret; +    Location loc; +    std::string basename; +    loc.x = id_to_x.at(name[0]); +    loc.y = id_to_y.at(name[1]); +    ret.location = loc; +    const TileTypePOD *loci = tile_info(ret); +    for (int i = 0; i < loci->num_pips; i++) { +        PipId curr; +        curr.location = loc; +        curr.index = i; +        pip_by_name[getPipName(curr)] = curr; +    } +    if (pip_by_name.find(name) == pip_by_name.end()) +        NPNR_ASSERT_FALSE_STR("no pip named " + name.str(getCtx())); +    return pip_by_name[name]; +} + +IdStringList Arch::getPipName(PipId pip) const +{ +    auto &pip_data = tile_info(pip)->pips_data[pip.index]; +    WireId src = getPipSrcWire(pip), dst = getPipDstWire(pip); +    const char *src_name = tile_info(src)->wire_data[src.index].name.get(); +    const char *dst_name = tile_info(dst)->wire_data[dst.index].name.get(); +    std::string pip_name = +            stringf("%d_%d_%s->%d_%d_%s", pip_data.src.x - pip.location.x, pip_data.src.y - pip.location.y, src_name, +                    pip_data.dst.x - pip.location.x, pip_data.dst.y - pip.location.y, dst_name); + +    std::array<IdString, 3> ids{x_ids.at(pip.location.x), y_ids.at(pip.location.y), id(pip_name)}; +    return IdStringList(ids); +} + +// --------------------------------------------------------------- + +delay_t Arch::estimateDelay(WireId src, WireId dst) const +{ +    // Taxicab distance multiplied by pipDelay (0.01) and fake wireDelay (0.01). +    // TODO: This function will not work well for entrance to global routing, +    // as the entrances are located physically far from the DCCAs. +    return (abs(dst.location.x - src.location.x) + abs(dst.location.y - src.location.y)) * (0.01 + 0.01); +} + +delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +{ +    BelId src = net_info->driver.cell->bel; +    BelId dst = sink.cell->bel; + +    NPNR_ASSERT(src != BelId()); +    NPNR_ASSERT(dst != BelId()); + +    // TODO: Same deal applies here as with estimateDelay. +    return (abs(dst.location.x - src.location.x) + abs(dst.location.y - src.location.y)) * (0.01 + 0.01); +} + +ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const +{ +    ArcBounds bb; +    bb.x0 = std::min(src.location.x, dst.location.x); +    bb.y0 = std::min(src.location.y, dst.location.y); +    bb.x1 = std::max(src.location.x, dst.location.x); +    bb.y1 = std::max(src.location.y, dst.location.y); +    return bb; +} + +// --------------------------------------------------------------- + +bool Arch::place() +{ +    std::string placer = str_or_default(settings, id("placer"), defaultPlacer); +    if (placer == "sa") { +        bool retVal = placer1(getCtx(), Placer1Cfg(getCtx())); +        getCtx()->settings[getCtx()->id("place")] = 1; +        archInfoToAttributes(); +        return retVal; +    } else if (placer == "heap") { +        PlacerHeapCfg cfg(getCtx()); +        cfg.ioBufTypes.insert(id_FACADE_IO); +        bool retVal = placer_heap(getCtx(), cfg); +        getCtx()->settings[getCtx()->id("place")] = 1; +        archInfoToAttributes(); +        return retVal; +    } else { +        log_error("MachXO2 architecture does not support placer '%s'\n", placer.c_str()); +    } +} + +bool Arch::route() +{ +    std::string router = str_or_default(settings, id("router"), defaultRouter); +    bool result; +    if (router == "router1") { +        result = router1(getCtx(), Router1Cfg(getCtx())); +    } else if (router == "router2") { +        router2(getCtx(), Router2Cfg(getCtx())); +        result = true; +    } else { +        log_error("MachXO2 architecture does not support router '%s'\n", router.c_str()); +    } +    getCtx()->settings[getCtx()->id("route")] = 1; +    archInfoToAttributes(); +    return result; +} + +// --------------------------------------------------------------- +bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const +{ +    // FIXME: Unlike ECP5, SLICEs in a given tile do not share a clock, so +    // any SLICE Cell is valid for any BEL, even if some cells are already +    // bound to BELs in the tile. However, this may need to be filled in once +    // more than one LUT4 and DFF type is supported. +    return true; +} + +bool Arch::isBelLocationValid(BelId bel) const +{ +    // FIXME: Same deal as isValidBelForCell. +    return true; +} + +#ifdef WITH_HEAP +const std::string Arch::defaultPlacer = "heap"; +#else +const std::string Arch::defaultPlacer = "sa"; +#endif + +const std::vector<std::string> Arch::availablePlacers = {"sa", +#ifdef WITH_HEAP +                                                         "heap" +#endif +}; + +const std::string Arch::defaultRouter = "router1"; +const std::vector<std::string> Arch::availableRouters = {"router1", "router2"}; + +bool Arch::cells_compatible(const CellInfo **cells, int count) const { return false; } + +std::vector<std::pair<std::string, std::string>> Arch::get_tiles_at_location(int row, int col) +{ +    std::vector<std::pair<std::string, std::string>> ret; +    auto &tileloc = chip_info->tile_info[row * chip_info->width + col]; +    for (int i = 0; i < tileloc.num_tiles; i++) { +        ret.push_back(std::make_pair(tileloc.tile_names[i].name.get(), +                                     chip_info->tiletype_names[tileloc.tile_names[i].type_idx].get())); +    } +    return ret; +} + +NEXTPNR_NAMESPACE_END diff --git a/machxo2/arch.h b/machxo2/arch.h new file mode 100644 index 00000000..f1642490 --- /dev/null +++ b/machxo2/arch.h @@ -0,0 +1,699 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018  Claire Xen <claire@symbioticeda.com> + *  Copyright (C) 2021  William D. Jones <wjones@wdj-consulting.com> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef NEXTPNR_H +#error Include "arch.h" via "nextpnr.h" only. +#endif + +NEXTPNR_NAMESPACE_BEGIN + +/**** Everything in this section must be kept in sync with chipdb.py ****/ + +template <typename T> struct RelPtr +{ +    int32_t offset; + +    // void set(const T *ptr) { +    //     offset = reinterpret_cast<const char*>(ptr) - +    //              reinterpret_cast<const char*>(this); +    // } + +    const T *get() const { return reinterpret_cast<const T *>(reinterpret_cast<const char *>(this) + offset); } + +    const T &operator[](size_t index) const { return get()[index]; } + +    const T &operator*() const { return *(get()); } + +    const T *operator->() const { return get(); } +}; + +// FIXME: All "rel locs" are actually absolute, naming typo in facade_import. +// Does not affect runtime functionality. + +NPNR_PACKED_STRUCT(struct BelWirePOD { +    LocationPOD rel_wire_loc; +    int32_t wire_index; +    int32_t port; +    int32_t dir; // FIXME: Corresponds to "type" in ECP5. +}); + +NPNR_PACKED_STRUCT(struct BelInfoPOD { +    RelPtr<char> name; +    int32_t type; +    int32_t z; +    int32_t num_bel_wires; +    RelPtr<BelWirePOD> bel_wires; +}); + +NPNR_PACKED_STRUCT(struct PipLocatorPOD { +    LocationPOD rel_loc; +    int32_t index; +}); + +NPNR_PACKED_STRUCT(struct BelPortPOD { +    LocationPOD rel_bel_loc; +    int32_t bel_index; +    int32_t port; +}); + +NPNR_PACKED_STRUCT(struct PipInfoPOD { +    LocationPOD src; +    LocationPOD dst; +    int32_t src_idx; +    int32_t dst_idx; +    int32_t timing_class; +    int16_t tile_type; +    int8_t pip_type; +    int8_t padding; +}); + +NPNR_PACKED_STRUCT(struct WireInfoPOD { +    RelPtr<char> name; +    int32_t tile_wire; +    int32_t num_uphill; +    int32_t num_downhill; +    RelPtr<PipLocatorPOD> pips_uphill; +    RelPtr<PipLocatorPOD> pips_downhill; +    int32_t num_bel_pins; +    RelPtr<BelPortPOD> bel_pins; +}); + +NPNR_PACKED_STRUCT(struct TileTypePOD { +    int32_t num_bels; +    int32_t num_wires; +    int32_t num_pips; +    RelPtr<BelInfoPOD> bel_data; +    RelPtr<WireInfoPOD> wire_data; +    RelPtr<PipInfoPOD> pips_data; +}); + +NPNR_PACKED_STRUCT(struct PackagePinPOD { +    RelPtr<char> name; +    LocationPOD abs_loc; +    int32_t bel_index; +}); + +NPNR_PACKED_STRUCT(struct PackageInfoPOD { +    RelPtr<char> name; +    int32_t num_pins; +    RelPtr<PackagePinPOD> pin_data; +}); + +NPNR_PACKED_STRUCT(struct PIOInfoPOD { +    LocationPOD abs_loc; +    int32_t bel_index; +    RelPtr<char> function_name; +    int16_t bank; +    int16_t dqsgroup; +}); + +NPNR_PACKED_STRUCT(struct TileNamePOD { +    RelPtr<char> name; +    int16_t type_idx; +    int16_t padding; +}); + +NPNR_PACKED_STRUCT(struct TileInfoPOD { +    int32_t num_tiles; +    RelPtr<TileNamePOD> tile_names; +}); + +NPNR_PACKED_STRUCT(struct ChipInfoPOD { +    int32_t width, height; +    int32_t num_tiles; +    int32_t num_packages, num_pios; +    int32_t const_id_count; +    RelPtr<TileTypePOD> tiles; +    RelPtr<RelPtr<char>> tiletype_names; +    RelPtr<PackageInfoPOD> package_info; +    RelPtr<PIOInfoPOD> pio_info; +    RelPtr<TileInfoPOD> tile_info; +}); + +/************************ End of chipdb section. ************************/ + +// Iterators +// Iterate over Bels across tiles. +struct BelIterator +{ +    const ChipInfoPOD *chip; +    int cursor_index; +    int cursor_tile; + +    BelIterator operator++() +    { +        cursor_index++; +        while (cursor_tile < chip->num_tiles && cursor_index >= chip->tiles[cursor_tile].num_bels) { +            cursor_index = 0; +            cursor_tile++; +        } +        return *this; +    } +    BelIterator operator++(int) +    { +        BelIterator prior(*this); +        ++(*this); +        return prior; +    } + +    bool operator!=(const BelIterator &other) const +    { +        return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile; +    } + +    bool operator==(const BelIterator &other) const +    { +        return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile; +    } + +    BelId operator*() const +    { +        BelId ret; +        ret.location.x = cursor_tile % chip->width; +        ret.location.y = cursor_tile / chip->width; +        ret.index = cursor_index; +        return ret; +    } +}; + +struct BelRange +{ +    BelIterator b, e; +    BelIterator begin() const { return b; } +    BelIterator end() const { return e; } +}; + +// Iterate over Downstream/Upstream Bels for a Wire. +struct BelPinIterator +{ +    const BelPortPOD *ptr = nullptr; +    Location wire_loc; +    void operator++() { ptr++; } +    bool operator!=(const BelPinIterator &other) const { return ptr != other.ptr; } + +    BelPin operator*() const +    { +        BelPin ret; +        ret.bel.index = ptr->bel_index; +        ret.bel.location = ptr->rel_bel_loc; +        ret.pin.index = ptr->port; +        return ret; +    } +}; + +struct BelPinRange +{ +    BelPinIterator b, e; +    BelPinIterator begin() const { return b; } +    BelPinIterator end() const { return e; } +}; + +// Iterator over Wires across tiles. +struct WireIterator +{ +    const ChipInfoPOD *chip; +    int cursor_index; +    int cursor_tile; + +    WireIterator operator++() +    { +        cursor_index++; +        while (cursor_tile < chip->num_tiles && cursor_index >= chip->tiles[cursor_tile].num_wires) { +            cursor_index = 0; +            cursor_tile++; +        } +        return *this; +    } +    WireIterator operator++(int) +    { +        WireIterator prior(*this); +        ++(*this); +        return prior; +    } + +    bool operator!=(const WireIterator &other) const +    { +        return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile; +    } + +    bool operator==(const WireIterator &other) const +    { +        return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile; +    } + +    WireId operator*() const +    { +        WireId ret; +        ret.location.x = cursor_tile % chip->width; +        ret.location.y = cursor_tile / chip->width; +        ret.index = cursor_index; +        return ret; +    } +}; + +struct WireRange +{ +    WireIterator b, e; +    WireIterator begin() const { return b; } +    WireIterator end() const { return e; } +}; + +// Iterator over Pips across tiles. +struct AllPipIterator +{ +    const ChipInfoPOD *chip; +    int cursor_index; +    int cursor_tile; + +    AllPipIterator operator++() +    { +        cursor_index++; +        while (cursor_tile < chip->num_tiles && cursor_index >= chip->tiles[cursor_tile].num_pips) { +            cursor_index = 0; +            cursor_tile++; +        } +        return *this; +    } +    AllPipIterator operator++(int) +    { +        AllPipIterator prior(*this); +        ++(*this); +        return prior; +    } + +    bool operator!=(const AllPipIterator &other) const +    { +        return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile; +    } + +    bool operator==(const AllPipIterator &other) const +    { +        return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile; +    } + +    PipId operator*() const +    { +        PipId ret; +        ret.location.x = cursor_tile % chip->width; +        ret.location.y = cursor_tile / chip->width; +        ret.index = cursor_index; +        return ret; +    } +}; + +struct AllPipRange +{ +    AllPipIterator b, e; +    AllPipIterator begin() const { return b; } +    AllPipIterator end() const { return e; } +}; + +// Iterate over Downstream/Upstream Pips for a Wire. +struct PipIterator +{ + +    const PipLocatorPOD *cursor = nullptr; +    Location wire_loc; + +    void operator++() { cursor++; } +    bool operator!=(const PipIterator &other) const { return cursor != other.cursor; } + +    PipId operator*() const +    { +        PipId ret; +        ret.index = cursor->index; +        ret.location = cursor->rel_loc; +        return ret; +    } +}; + +struct PipRange +{ +    PipIterator b, e; +    PipIterator begin() const { return b; } +    PipIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +struct ArchArgs +{ +    enum ArchArgsTypes +    { +        NONE, +        LCMXO2_256HC, +        LCMXO2_640HC, +        LCMXO2_1200HC, +        LCMXO2_2000HC, +        LCMXO2_4000HC, +        LCMXO2_7000HC, +    } type = NONE; +    std::string package; +    enum SpeedGrade +    { +        SPEED_1 = 0, +        SPEED_2, +        SPEED_3, +        SPEED_4, +        SPEED_5, +        SPEED_6, +    } speed = SPEED_4; +}; + +struct ArchRanges : BaseArchRanges +{ +    using ArchArgsT = ArchArgs; +    // Bels +    using AllBelsRangeT = BelRange; +    using TileBelsRangeT = BelRange; +    using BelPinsRangeT = std::vector<IdString>; +    // Wires +    using AllWiresRangeT = WireRange; +    using DownhillPipRangeT = PipRange; +    using UphillPipRangeT = PipRange; +    using WireBelPinRangeT = BelPinRange; +    // Pips +    using AllPipsRangeT = AllPipRange; +}; + +struct Arch : BaseArch<ArchRanges> +{ +    const ChipInfoPOD *chip_info; +    const PackageInfoPOD *package_info; + +    mutable std::unordered_map<IdStringList, PipId> pip_by_name; + +    // fast access to  X and Y IdStrings for building object names +    std::vector<IdString> x_ids, y_ids; +    // inverse of the above for name->object mapping +    std::unordered_map<IdString, int> id_to_x, id_to_y; + +    // Helpers +    template <typename Id> const TileTypePOD *tile_info(Id &id) const +    { +        return &(chip_info->tiles[id.location.y * chip_info->width + id.location.x]); +    } + +    int get_bel_flat_index(BelId bel) const +    { +        return (bel.location.y * chip_info->width + bel.location.x) * max_loc_bels + bel.index; +    } + +    // --------------------------------------------------------------- +    // Common Arch API. Every arch must provide the following methods. + +    // General +    ArchArgs args; +    Arch(ArchArgs args); + +    static bool is_available(ArchArgs::ArchArgsTypes chip); +    static std::vector<std::string> get_supported_packages(ArchArgs::ArchArgsTypes chip); + +    std::string getChipName() const override; +    // Extra helper +    std::string get_full_chip_name() const; + +    IdString archId() const override { return id("machxo2"); } +    ArchArgs archArgs() const override { return args; } +    IdString archArgsToId(ArchArgs args) const override; + +    static const int max_loc_bels = 20; + +    int getGridDimX() const override { return chip_info->width; } +    int getGridDimY() const override { return chip_info->height; } +    int getTileBelDimZ(int x, int y) const override { return max_loc_bels; } +    // TODO: Make more precise? The CENTER MUX having config bits across +    // tiles can complicate this? +    int getTilePipDimZ(int x, int y) const override { return 2; } + +    char getNameDelimiter() const override { return '/'; } + +    // Bels +    BelId getBelByName(IdStringList name) const override; + +    IdStringList getBelName(BelId bel) const override +    { +        NPNR_ASSERT(bel != BelId()); +        std::array<IdString, 3> ids{x_ids.at(bel.location.x), y_ids.at(bel.location.y), +                                    id(tile_info(bel)->bel_data[bel.index].name.get())}; +        return IdStringList(ids); +    } + +    Loc getBelLocation(BelId bel) const override +    { +        NPNR_ASSERT(bel != BelId()); +        Loc loc; +        loc.x = bel.location.x; +        loc.y = bel.location.y; +        loc.z = tile_info(bel)->bel_data[bel.index].z; +        return loc; +    } + +    BelId getBelByLocation(Loc loc) const override; +    BelRange getBelsByTile(int x, int y) const override; +    bool getBelGlobalBuf(BelId bel) const override; + +    BelRange getBels() const override +    { +        BelRange range; +        range.b.cursor_tile = 0; +        range.b.cursor_index = -1; +        range.b.chip = chip_info; +        ++range.b; //-1 and then ++ deals with the case of no Bels in the first tile +        range.e.cursor_tile = chip_info->width * chip_info->height; +        range.e.cursor_index = 0; +        range.e.chip = chip_info; +        return range; +    } + +    IdString getBelType(BelId bel) const override +    { +        NPNR_ASSERT(bel != BelId()); +        IdString id; +        id.index = tile_info(bel)->bel_data[bel.index].type; +        return id; +    } + +    WireId getBelPinWire(BelId bel, IdString pin) const override; +    PortType getBelPinType(BelId bel, IdString pin) const override; +    std::vector<IdString> getBelPins(BelId bel) const override; + +    // Package +    BelId getPackagePinBel(const std::string &pin) const; + +    // Wires +    WireId getWireByName(IdStringList name) const override; + +    IdStringList getWireName(WireId wire) const override +    { +        NPNR_ASSERT(wire != WireId()); +        std::array<IdString, 3> ids{x_ids.at(wire.location.x), y_ids.at(wire.location.y), +                                    id(tile_info(wire)->wire_data[wire.index].name.get())}; +        return IdStringList(ids); +    } + +    DelayInfo getWireDelay(WireId wire) const override { return DelayInfo(); } + +    WireRange getWires() const override +    { +        WireRange range; +        range.b.cursor_tile = 0; +        range.b.cursor_index = -1; +        range.b.chip = chip_info; +        ++range.b; //-1 and then ++ deals with the case of no wries in the first tile +        range.e.cursor_tile = chip_info->width * chip_info->height; +        range.e.cursor_index = 0; +        range.e.chip = chip_info; +        return range; +    } + +    BelPinRange getWireBelPins(WireId wire) const override +    { +        BelPinRange range; +        NPNR_ASSERT(wire != WireId()); +        range.b.ptr = tile_info(wire)->wire_data[wire.index].bel_pins.get(); +        range.b.wire_loc = wire.location; +        range.e.ptr = range.b.ptr + tile_info(wire)->wire_data[wire.index].num_bel_pins; +        range.e.wire_loc = wire.location; +        return range; +    } + +    // Pips +    PipId getPipByName(IdStringList name) const override; +    IdStringList getPipName(PipId pip) const override; + +    AllPipRange getPips() const override +    { +        AllPipRange range; +        range.b.cursor_tile = 0; +        range.b.cursor_index = -1; +        range.b.chip = chip_info; +        ++range.b; //-1 and then ++ deals with the case of no Bels in the first tile +        range.e.cursor_tile = chip_info->width * chip_info->height; +        range.e.cursor_index = 0; +        range.e.chip = chip_info; +        return range; +    } + +    Loc getPipLocation(PipId pip) const override +    { +        Loc loc; +        loc.x = pip.location.x; +        loc.y = pip.location.y; + +        // FIXME: Some Pip's config bits span across tiles. Will Z +        // be affected by this? +        loc.z = 0; +        return loc; +    } + +    WireId getPipSrcWire(PipId pip) const override +    { +        WireId wire; +        NPNR_ASSERT(pip != PipId()); +        wire.index = tile_info(pip)->pips_data[pip.index].src_idx; +        wire.location = tile_info(pip)->pips_data[pip.index].src; +        return wire; +    } + +    WireId getPipDstWire(PipId pip) const override +    { +        WireId wire; +        NPNR_ASSERT(pip != PipId()); +        wire.index = tile_info(pip)->pips_data[pip.index].dst_idx; +        wire.location = tile_info(pip)->pips_data[pip.index].dst; +        return wire; +    } + +    DelayInfo getPipDelay(PipId pip) const override +    { +        DelayInfo delay; + +        delay.delay = 0.01; + +        return delay; +    } + +    PipRange getPipsDownhill(WireId wire) const override +    { +        PipRange range; +        NPNR_ASSERT(wire != WireId()); +        range.b.cursor = tile_info(wire)->wire_data[wire.index].pips_downhill.get(); +        range.b.wire_loc = wire.location; +        range.e.cursor = range.b.cursor + tile_info(wire)->wire_data[wire.index].num_downhill; +        range.e.wire_loc = wire.location; +        return range; +    } + +    PipRange getPipsUphill(WireId wire) const override +    { +        PipRange range; +        NPNR_ASSERT(wire != WireId()); +        range.b.cursor = tile_info(wire)->wire_data[wire.index].pips_uphill.get(); +        range.b.wire_loc = wire.location; +        range.e.cursor = range.b.cursor + tile_info(wire)->wire_data[wire.index].num_uphill; +        range.e.wire_loc = wire.location; +        return range; +    } + +    // Extra Pip helpers. +    int8_t get_pip_class(PipId pip) const { return tile_info(pip)->pips_data[pip.index].pip_type; } + +    std::string get_pip_tilename(PipId pip) const +    { +        auto &tileloc = chip_info->tile_info[pip.location.y * chip_info->width + pip.location.x]; +        for (int i = 0; i < tileloc.num_tiles; i++) { +            if (tileloc.tile_names[i].type_idx == tile_info(pip)->pips_data[pip.index].tile_type) +                return tileloc.tile_names[i].name.get(); +        } +        NPNR_ASSERT_FALSE("failed to find Pip tile"); +    } + +    // Delay +    delay_t estimateDelay(WireId src, WireId dst) const override; +    delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override; +    delay_t getDelayEpsilon() const override { return 0.001; } +    delay_t getRipupDelayPenalty() const override { return 0.015; } +    float getDelayNS(delay_t v) const override { return v; } + +    DelayInfo getDelayFromNS(float ns) const override +    { +        DelayInfo del; +        del.delay = ns; +        return del; +    } + +    uint32_t getDelayChecksum(delay_t v) const override { return v; } + +    ArcBounds getRouteBoundingBox(WireId src, WireId dst) const override; + +    // Flow +    bool pack() override; +    bool place() override; +    bool route() override; + +    // Placer +    bool isValidBelForCell(CellInfo *cell, BelId bel) const override; +    bool isBelLocationValid(BelId bel) const override; + +    static const std::string defaultPlacer; +    static const std::vector<std::string> availablePlacers; +    static const std::string defaultRouter; +    static const std::vector<std::string> availableRouters; + +    // --------------------------------------------------------------- +    // Internal usage +    bool cells_compatible(const CellInfo **cells, int count) const; + +    std::vector<std::pair<std::string, std::string>> get_tiles_at_location(int row, int col); +    std::string get_tile_by_type_and_loc(int row, int col, std::string type) const +    { +        auto &tileloc = chip_info->tile_info[row * chip_info->width + col]; +        for (int i = 0; i < tileloc.num_tiles; i++) { +            if (chip_info->tiletype_names[tileloc.tile_names[i].type_idx].get() == type) +                return tileloc.tile_names[i].name.get(); +        } +        NPNR_ASSERT_FALSE_STR("no tile at (" + std::to_string(col) + ", " + std::to_string(row) + ") with type " + +                              type); +    } + +    std::string get_tile_by_type_and_loc(int row, int col, const std::set<std::string> &type) const +    { +        auto &tileloc = chip_info->tile_info[row * chip_info->width + col]; +        for (int i = 0; i < tileloc.num_tiles; i++) { +            if (type.count(chip_info->tiletype_names[tileloc.tile_names[i].type_idx].get())) +                return tileloc.tile_names[i].name.get(); +        } +        NPNR_ASSERT_FALSE_STR("no tile at (" + std::to_string(col) + ", " + std::to_string(row) + ") with type in set"); +    } + +    std::string get_tile_by_type(std::string type) const +    { +        for (int i = 0; i < chip_info->height * chip_info->width; i++) { +            auto &tileloc = chip_info->tile_info[i]; +            for (int j = 0; j < tileloc.num_tiles; j++) +                if (chip_info->tiletype_names[tileloc.tile_names[j].type_idx].get() == type) +                    return tileloc.tile_names[j].name.get(); +        } +        NPNR_ASSERT_FALSE_STR("no tile with type " + type); +    } +}; + +NEXTPNR_NAMESPACE_END diff --git a/machxo2/arch_pybindings.cc b/machxo2/arch_pybindings.cc new file mode 100644 index 00000000..fa0f9535 --- /dev/null +++ b/machxo2/arch_pybindings.cc @@ -0,0 +1,79 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018  Claire Xen <claire@symbioticeda.com> + *  Copyright (C) 2018  David Shah <david@symbioticeda.com> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef NO_PYTHON + +#include "arch_pybindings.h" +#include "nextpnr.h" +#include "pybindings.h" + +NEXTPNR_NAMESPACE_BEGIN + +void arch_wrap_python(py::module &m) +{ +    using namespace PythonConversion; +    py::class_<ArchArgs>(m, "ArchArgs").def_readwrite("type", &ArchArgs::type); + +    py::class_<BelId>(m, "BelId").def_readwrite("index", &BelId::index); + +    py::class_<WireId>(m, "WireId").def_readwrite("index", &WireId::index); + +    py::class_<PipId>(m, "PipId").def_readwrite("index", &PipId::index); + +    auto arch_cls = py::class_<Arch, BaseCtx>(m, "Arch").def(py::init<ArchArgs>()); +    auto ctx_cls = py::class_<Context, Arch>(m, "Context") +                           .def("checksum", &Context::checksum) +                           .def("pack", &Context::pack) +                           .def("place", &Context::place) +                           .def("route", &Context::route); + +    fn_wrapper_2a<Context, decltype(&Context::isValidBelForCell), &Context::isValidBelForCell, pass_through<bool>, +                  addr_and_unwrap<CellInfo>, conv_from_str<BelId>>::def_wrap(ctx_cls, "isValidBelForCell"); + +    typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap; +    typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap; +    typedef std::unordered_map<IdString, IdString> AliasMap; +    typedef std::unordered_map<IdString, HierarchicalCell> HierarchyMap; + +    auto belpin_cls = py::class_<ContextualWrapper<BelPin>>(m, "BelPin"); +    readonly_wrapper<BelPin, decltype(&BelPin::bel), &BelPin::bel, conv_to_str<BelId>>::def_wrap(belpin_cls, "bel"); +    readonly_wrapper<BelPin, decltype(&BelPin::pin), &BelPin::pin, conv_to_str<IdString>>::def_wrap(belpin_cls, "pin"); + +    typedef const PipRange UphillPipRange; +    typedef const PipRange DownhillPipRange; + +    typedef const std::vector<BelBucketId> &BelBucketRange; +    typedef const std::vector<BelId> &BelRangeForBelBucket; +#include "arch_pybindings_shared.h" + +    WRAP_RANGE(m, Bel, conv_to_str<BelId>); +    WRAP_RANGE(m, Wire, conv_to_str<WireId>); +    WRAP_RANGE(m, AllPip, conv_to_str<PipId>); +    WRAP_RANGE(m, Pip, conv_to_str<PipId>); +    WRAP_RANGE(m, BelPin, wrap_context<BelPin>); + +    WRAP_MAP_UPTR(m, CellMap, "IdCellMap"); +    WRAP_MAP_UPTR(m, NetMap, "IdNetMap"); +    WRAP_MAP(m, HierarchyMap, wrap_context<HierarchicalCell &>, "HierarchyMap"); +} + +NEXTPNR_NAMESPACE_END + +#endif // NO_PYTHON diff --git a/machxo2/arch_pybindings.h b/machxo2/arch_pybindings.h new file mode 100644 index 00000000..62f66406 --- /dev/null +++ b/machxo2/arch_pybindings.h @@ -0,0 +1,98 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018  Claire Xen <claire@symbioticeda.com> + *  Copyright (C) 2018  David Shah <david@symbioticeda.com> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ +#ifndef ARCH_PYBINDINGS_H +#define ARCH_PYBINDINGS_H +#ifndef NO_PYTHON + +#include "nextpnr.h" +#include "pybindings.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace PythonConversion { + +template <> struct string_converter<BelId> +{ +    BelId from_str(Context *ctx, std::string name) { return ctx->getBelByNameStr(name); } + +    std::string to_str(Context *ctx, BelId id) +    { +        if (id == BelId()) +            throw bad_wrap(); +        return ctx->getBelName(id).str(ctx); +    } +}; + +template <> struct string_converter<WireId> +{ +    WireId from_str(Context *ctx, std::string name) { return ctx->getWireByNameStr(name); } + +    std::string to_str(Context *ctx, WireId id) +    { +        if (id == WireId()) +            throw bad_wrap(); +        return ctx->getWireName(id).str(ctx); +    } +}; + +template <> struct string_converter<const WireId> +{ +    WireId from_str(Context *ctx, std::string name) { return ctx->getWireByNameStr(name); } + +    std::string to_str(Context *ctx, WireId id) +    { +        if (id == WireId()) +            throw bad_wrap(); +        return ctx->getWireName(id).str(ctx); +    } +}; + +template <> struct string_converter<PipId> +{ +    PipId from_str(Context *ctx, std::string name) { return ctx->getPipByNameStr(name); } + +    std::string to_str(Context *ctx, PipId id) +    { +        if (id == PipId()) +            throw bad_wrap(); +        return ctx->getPipName(id).str(ctx); +    } +}; + +template <> struct string_converter<BelPin> +{ +    BelPin from_str(Context *ctx, std::string name) +    { +        NPNR_ASSERT_FALSE("string_converter<BelPin>::from_str not implemented"); +    } + +    std::string to_str(Context *ctx, BelPin pin) +    { +        if (pin.bel == BelId()) +            throw bad_wrap(); +        return ctx->getBelName(pin.bel).str(ctx) + "/" + pin.pin.str(ctx); +    } +}; + +} // namespace PythonConversion + +NEXTPNR_NAMESPACE_END +#endif +#endif diff --git a/machxo2/archdefs.h b/machxo2/archdefs.h new file mode 100644 index 00000000..844a87b6 --- /dev/null +++ b/machxo2/archdefs.h @@ -0,0 +1,177 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018  Claire Xen <claire@symbioticeda.com> + *  Copyright (C) 2021  William D. Jones <wjones@wdj-consulting.com> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef NEXTPNR_H +#error Include "archdefs.h" via "nextpnr.h" only. +#endif + +NEXTPNR_NAMESPACE_BEGIN + +typedef float delay_t; + +struct DelayInfo +{ +    delay_t delay = 0; + +    delay_t minRaiseDelay() const { return delay; } +    delay_t maxRaiseDelay() const { return delay; } + +    delay_t minFallDelay() const { return delay; } +    delay_t maxFallDelay() const { return delay; } + +    delay_t minDelay() const { return delay; } +    delay_t maxDelay() const { return delay; } + +    DelayInfo operator+(const DelayInfo &other) const +    { +        DelayInfo ret; +        ret.delay = this->delay + other.delay; +        return ret; +    } +}; + +enum ConstIds +{ +    ID_NONE +#define X(t) , ID_##t +#include "constids.inc" +#undef X +    , +    DB_CONST_ID_COUNT +}; + +#define X(t) static constexpr auto id_##t = IdString(ID_##t); +#include "constids.inc" +#undef X + +NPNR_PACKED_STRUCT(struct LocationPOD { int16_t x, y; }); + +struct Location +{ +    int16_t x = -1, y = -1; +    Location() : x(-1), y(-1){}; +    Location(int16_t x, int16_t y) : x(x), y(y){}; +    Location(const LocationPOD &pod) : x(pod.x), y(pod.y){}; +    Location(const Location &loc) : x(loc.x), y(loc.y){}; + +    bool operator==(const Location &other) const { return x == other.x && y == other.y; } +    bool operator!=(const Location &other) const { return x != other.x || y != other.y; } +    bool operator<(const Location &other) const { return y == other.y ? x < other.x : y < other.y; } +}; + +inline Location operator+(const Location &a, const Location &b) { return Location(a.x + b.x, a.y + b.y); } + +struct BelId +{ +    Location location; +    int32_t index = -1; + +    bool operator==(const BelId &other) const { return index == other.index && location == other.location; } +    bool operator!=(const BelId &other) const { return index != other.index || location != other.location; } +    bool operator<(const BelId &other) const +    { +        return location == other.location ? index < other.index : location < other.location; +    } +}; + +struct WireId +{ +    Location location; +    int32_t index = -1; + +    bool operator==(const WireId &other) const { return index == other.index && location == other.location; } +    bool operator!=(const WireId &other) const { return index != other.index || location != other.location; } +    bool operator<(const WireId &other) const +    { +        return location == other.location ? index < other.index : location < other.location; +    } +}; + +struct PipId +{ +    Location location; +    int32_t index = -1; + +    bool operator==(const PipId &other) const { return index == other.index && location == other.location; } +    bool operator!=(const PipId &other) const { return index != other.index || location != other.location; } +    bool operator<(const PipId &other) const +    { +        return location == other.location ? index < other.index : location < other.location; +    } +}; + +typedef IdString GroupId; +typedef IdString DecalId; +typedef IdString BelBucketId; + +struct ArchNetInfo +{ +}; + +struct NetInfo; + +struct ArchCellInfo +{ +}; + +NEXTPNR_NAMESPACE_END + +namespace std { +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX Location> +{ +    std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX Location &loc) const noexcept +    { +        std::size_t seed = std::hash<int>()(loc.x); +        seed ^= std::hash<int>()(loc.y) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +        return seed; +    } +}; + +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX BelId> +{ +    std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept +    { +        std::size_t seed = std::hash<NEXTPNR_NAMESPACE_PREFIX Location>()(bel.location); +        seed ^= std::hash<int>()(bel.index) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +        return seed; +    } +}; + +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX WireId> +{ +    std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept +    { +        std::size_t seed = std::hash<NEXTPNR_NAMESPACE_PREFIX Location>()(wire.location); +        seed ^= std::hash<int>()(wire.index) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +        return seed; +    } +}; + +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX PipId> +{ +    std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept +    { +        std::size_t seed = std::hash<NEXTPNR_NAMESPACE_PREFIX Location>()(pip.location); +        seed ^= std::hash<int>()(pip.index) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +        return seed; +    } +}; + +} // namespace std diff --git a/machxo2/bitstream.cc b/machxo2/bitstream.cc new file mode 100644 index 00000000..37363b09 --- /dev/null +++ b/machxo2/bitstream.cc @@ -0,0 +1,249 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018  David Shah <david@symbioticeda.com> + *  Copyright (C) 2021  William D. Jones <wjones@wdj-consulting.com> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <fstream> + +#include "bitstream.h" +#include "config.h" +#include "nextpnr.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +// These seem simple enough to do inline for now. +namespace BaseConfigs { +void config_empty_lcmxo2_1200hc(ChipConfig &cc) +{ +    cc.chip_name = "LCMXO2-1200HC"; + +    cc.tiles["EBR_R6C11:EBR1"].add_unknown(0, 12); +    cc.tiles["EBR_R6C15:EBR1"].add_unknown(0, 12); +    cc.tiles["EBR_R6C18:EBR1"].add_unknown(0, 12); +    cc.tiles["EBR_R6C21:EBR1"].add_unknown(0, 12); +    cc.tiles["EBR_R6C2:EBR1"].add_unknown(0, 12); +    cc.tiles["EBR_R6C5:EBR1"].add_unknown(0, 12); +    cc.tiles["EBR_R6C8:EBR1"].add_unknown(0, 12); + +    cc.tiles["PT4:CFG0"].add_unknown(5, 30); +    cc.tiles["PT4:CFG0"].add_unknown(5, 32); +    cc.tiles["PT4:CFG0"].add_unknown(5, 36); + +    cc.tiles["PT7:CFG3"].add_unknown(5, 18); +} +} // namespace BaseConfigs + +// Convert an absolute wire name to a relative Trellis one +static std::string get_trellis_wirename(Context *ctx, Location loc, WireId wire) +{ +    std::string basename = ctx->tile_info(wire)->wire_data[wire.index].name.get(); +    std::string prefix2 = basename.substr(0, 2); +    std::string prefix7 = basename.substr(0, 7); +    int max_col = ctx->chip_info->width - 1; + +    // Handle MachXO2's wonderful naming quirks for wires in left/right tiles, whose +    // relative coords push them outside the bounds of the chip. +    auto is_pio_wire = [](std::string name) { +        return (name.find("DI") != std::string::npos || name.find("JDI") != std::string::npos || +                name.find("PADD") != std::string::npos || name.find("INDD") != std::string::npos || +                name.find("IOLDO") != std::string::npos || name.find("IOLTO") != std::string::npos || +                name.find("JCE") != std::string::npos || name.find("JCLK") != std::string::npos || +                name.find("JLSR") != std::string::npos || name.find("JONEG") != std::string::npos || +                name.find("JOPOS") != std::string::npos || name.find("JTS") != std::string::npos || +                name.find("JIN") != std::string::npos || name.find("JIP") != std::string::npos || +                // Connections to global mux +                name.find("JINCK") != std::string::npos); +    }; + +    if (prefix2 == "G_" || prefix2 == "L_" || prefix2 == "R_" || prefix2 == "U_" || prefix2 == "D_" || +        prefix7 == "BRANCH_") +        return basename; +    if (loc == wire.location) { +        // TODO: JINCK is not currently handled by this. +        if (is_pio_wire(basename)) { +            if (wire.location.x == 0) +                return "W1_" + basename; +            else if (wire.location.x == max_col) +                return "E1_" + basename; +        } +        return basename; +    } + +    std::string rel_prefix; +    if (wire.location.y < loc.y) +        rel_prefix += "N" + std::to_string(loc.y - wire.location.y); +    if (wire.location.y > loc.y) +        rel_prefix += "S" + std::to_string(wire.location.y - loc.y); +    if (wire.location.x > loc.x) +        rel_prefix += "E" + std::to_string(wire.location.x - loc.x); +    if (wire.location.x < loc.x) +        rel_prefix += "W" + std::to_string(loc.x - wire.location.x); +    return rel_prefix + "_" + basename; +} + +static void set_pip(Context *ctx, ChipConfig &cc, PipId pip) +{ +    std::string tile = ctx->get_pip_tilename(pip); +    std::string source = get_trellis_wirename(ctx, pip.location, ctx->getPipSrcWire(pip)); +    std::string sink = get_trellis_wirename(ctx, pip.location, ctx->getPipDstWire(pip)); +    cc.tiles[tile].add_arc(sink, source); +} + +static std::vector<bool> int_to_bitvector(int val, int size) +{ +    std::vector<bool> bv; +    for (int i = 0; i < size; i++) { +        bv.push_back((val & (1 << i)) != 0); +    } +    return bv; +} + +static std::vector<bool> str_to_bitvector(std::string str, int size) +{ +    std::vector<bool> bv; +    bv.resize(size, 0); +    if (str.substr(0, 2) != "0b") +        log_error("error parsing value '%s', expected 0b prefix\n", str.c_str()); +    for (int i = 0; i < int(str.size()) - 2; i++) { +        char c = str.at((str.size() - i) - 1); +        NPNR_ASSERT(c == '0' || c == '1'); +        bv.at(i) = (c == '1'); +    } +    return bv; +} + +std::string intstr_or_default(const std::unordered_map<IdString, Property> &ct, const IdString &key, +                              std::string def = "0") +{ +    auto found = ct.find(key); +    if (found == ct.end()) +        return def; +    else { +        if (found->second.is_string) +            return found->second.as_string(); +        else +            return std::to_string(found->second.as_int64()); +    } +}; + +// Get the PIC tile corresponding to a PIO bel +static std::string get_pic_tile(Context *ctx, BelId bel) +{ +    static const std::set<std::string> pio_l = {"PIC_L0", "PIC_LS0", "PIC_L0_VREF3"}; +    static const std::set<std::string> pio_r = {"PIC_R0", "PIC_RS0"}; + +    std::string pio_name = ctx->tile_info(bel)->bel_data[bel.index].name.get(); +    if (bel.location.y == 0) { +        return ctx->get_tile_by_type_and_loc(0, bel.location.x, "PIC_T0"); +    } else if (bel.location.y == ctx->chip_info->height - 1) { +        return ctx->get_tile_by_type_and_loc(bel.location.y, bel.location.x, "PIC_B0"); +    } else if (bel.location.x == 0) { +        return ctx->get_tile_by_type_and_loc(bel.location.y, 0, pio_l); +    } else if (bel.location.x == ctx->chip_info->width - 1) { +        return ctx->get_tile_by_type_and_loc(bel.location.y, bel.location.x, pio_r); +    } else { +        NPNR_ASSERT_FALSE("bad PIO location"); +    } +} + +void write_bitstream(Context *ctx, std::string text_config_file) +{ +    ChipConfig cc; + +    switch (ctx->args.type) { +    case ArchArgs::LCMXO2_1200HC: +        BaseConfigs::config_empty_lcmxo2_1200hc(cc); +        break; +    default: +        NPNR_ASSERT_FALSE("Unsupported device type"); +    } + +    cc.metadata.push_back("Part: " + ctx->get_full_chip_name()); + +    // Add all set, configurable pips to the config +    for (auto pip : ctx->getPips()) { +        if (ctx->getBoundPipNet(pip) != nullptr) { +            if (ctx->get_pip_class(pip) == 0) { // ignore fixed pips +                set_pip(ctx, cc, pip); +            } +        } +    } + +    // TODO: Bank Voltages + +    // Configure slices +    for (auto &cell : ctx->cells) { +        CellInfo *ci = cell.second.get(); +        if (ci->bel == BelId()) { +            log_warning("found unplaced cell '%s' during bitstream gen. Not writing to bitstream.\n", +                        ci->name.c_str(ctx)); +            continue; +        } +        BelId bel = ci->bel; +        if (ci->type == id_FACADE_SLICE) { +            std::string tname = ctx->get_tile_by_type_and_loc(bel.location.y, bel.location.x, "PLC"); +            std::string slice = ctx->tile_info(bel)->bel_data[bel.index].name.get(); + +            NPNR_ASSERT(slice.substr(0, 5) == "SLICE"); +            int int_index = slice[5] - 'A'; +            NPNR_ASSERT(int_index >= 0 && int_index < 4); + +            int lut0_init = int_or_default(ci->params, ctx->id("LUT0_INITVAL")); +            int lut1_init = int_or_default(ci->params, ctx->id("LUT1_INITVAL")); +            cc.tiles[tname].add_word(slice + ".K0.INIT", int_to_bitvector(lut0_init, 16)); +            cc.tiles[tname].add_word(slice + ".K1.INIT", int_to_bitvector(lut1_init, 16)); +            cc.tiles[tname].add_enum(slice + ".MODE", str_or_default(ci->params, ctx->id("MODE"), "LOGIC")); +            cc.tiles[tname].add_enum(slice + ".GSR", str_or_default(ci->params, ctx->id("GSR"), "ENABLED")); +            cc.tiles[tname].add_enum("LSR" + std::to_string(int_index) + ".SRMODE", +                                     str_or_default(ci->params, ctx->id("SRMODE"), "LSR_OVER_CE")); +            cc.tiles[tname].add_enum(slice + ".CEMUX", intstr_or_default(ci->params, ctx->id("CEMUX"), "1")); +            cc.tiles[tname].add_enum("CLK" + std::to_string(int_index) + ".CLKMUX", +                                     intstr_or_default(ci->params, ctx->id("CLKMUX"), "0")); +            cc.tiles[tname].add_enum("LSR" + std::to_string(int_index) + ".LSRMUX", +                                     str_or_default(ci->params, ctx->id("LSRMUX"), "LSR")); +            cc.tiles[tname].add_enum("LSR" + std::to_string(int_index) + ".LSRONMUX", +                                     intstr_or_default(ci->params, ctx->id("LSRONMUX"), "LSRMUX")); +            cc.tiles[tname].add_enum(slice + ".REGMODE", str_or_default(ci->params, ctx->id("REGMODE"), "FF")); +            cc.tiles[tname].add_enum(slice + ".REG0.SD", intstr_or_default(ci->params, ctx->id("REG0_SD"), "0")); +            cc.tiles[tname].add_enum(slice + ".REG1.SD", intstr_or_default(ci->params, ctx->id("REG1_SD"), "0")); +            cc.tiles[tname].add_enum(slice + ".REG0.REGSET", +                                     str_or_default(ci->params, ctx->id("REG0_REGSET"), "RESET")); +            cc.tiles[tname].add_enum(slice + ".REG1.REGSET", +                                     str_or_default(ci->params, ctx->id("REG1_REGSET"), "RESET")); +        } else if (ci->type == ctx->id("FACADE_IO")) { +            std::string pio = ctx->tile_info(bel)->bel_data[bel.index].name.get(); +            std::string iotype = str_or_default(ci->attrs, ctx->id("IO_TYPE"), "LVCMOS33"); +            std::string dir = str_or_default(ci->params, ctx->id("DIR"), "INPUT"); +            std::string pic_tile = get_pic_tile(ctx, bel); +            cc.tiles[pic_tile].add_enum(pio + ".BASE_TYPE", dir + "_" + iotype); +        } else if (ci->type == ctx->id("OSCH")) { +            std::string freq = str_or_default(ci->params, ctx->id("NOM_FREQ"), "2.08"); +            cc.tiles[ctx->get_tile_by_type("CFG1")].add_enum("OSCH.MODE", "OSCH"); +            cc.tiles[ctx->get_tile_by_type("CFG1")].add_enum("OSCH.NOM_FREQ", freq); +        } +    } + +    // Configure chip +    if (!text_config_file.empty()) { +        std::ofstream out_config(text_config_file); +        out_config << cc; +    } +} + +NEXTPNR_NAMESPACE_END diff --git a/machxo2/bitstream.h b/machxo2/bitstream.h new file mode 100644 index 00000000..e54e134a --- /dev/null +++ b/machxo2/bitstream.h @@ -0,0 +1,32 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018  David Shah <david@symbioticeda.com> + *  Copyright (C) 2021  William D. Jones <wjones@wdj-consulting.com> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef BITSTREAM_H +#define BITSTREAM_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +void write_bitstream(Context *ctx, std::string text_config_file = ""); + +NEXTPNR_NAMESPACE_END + +#endif // BITSTREAM_H diff --git a/machxo2/cells.cc b/machxo2/cells.cc new file mode 100644 index 00000000..03ba0a41 --- /dev/null +++ b/machxo2/cells.cc @@ -0,0 +1,180 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2019  David Shah <david@symbioticeda.com> + *  Copyright (C) 2021  William D. Jones <wjones@wdj-consulting.com> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "cells.h" +#include "design_utils.h" +#include "log.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +void add_port(const Context *ctx, CellInfo *cell, std::string name, PortType dir) +{ +    IdString id = ctx->id(name); +    NPNR_ASSERT(cell->ports.count(id) == 0); +    cell->ports[id] = PortInfo{id, nullptr, dir}; +} + +void add_port(const Context *ctx, CellInfo *cell, IdString id, PortType dir) +{ +    NPNR_ASSERT(cell->ports.count(id) == 0); +    cell->ports[id] = PortInfo{id, nullptr, dir}; +} + +std::unique_ptr<CellInfo> create_machxo2_cell(Context *ctx, IdString type, std::string name) +{ +    static int auto_idx = 0; +    std::unique_ptr<CellInfo> new_cell = std::unique_ptr<CellInfo>(new CellInfo()); +    if (name.empty()) { +        new_cell->name = ctx->id("$nextpnr_" + type.str(ctx) + "_" + std::to_string(auto_idx++)); +    } else { +        new_cell->name = ctx->id(name); +    } +    new_cell->type = type; + +    if (type == id_FACADE_SLICE) { +        new_cell->params[id_MODE] = std::string("LOGIC"); +        new_cell->params[id_GSR] = std::string("ENABLED"); +        new_cell->params[id_SRMODE] = std::string("LSR_OVER_CE"); +        new_cell->params[id_CEMUX] = std::string("1"); +        new_cell->params[id_CLKMUX] = std::string("0"); +        new_cell->params[id_LSRMUX] = std::string("LSR"); +        new_cell->params[id_LSRONMUX] = std::string("LSRMUX"); +        new_cell->params[id_LUT0_INITVAL] = Property(0xFFFF, 16); +        new_cell->params[id_LUT1_INITVAL] = Property(0xFFFF, 16); +        new_cell->params[id_REGMODE] = std::string("FF"); +        new_cell->params[id_REG0_SD] = std::string("1"); +        new_cell->params[id_REG1_SD] = std::string("1"); +        new_cell->params[id_REG0_REGSET] = std::string("SET"); +        new_cell->params[id_REG1_REGSET] = std::string("SET"); +        new_cell->params[id_CCU2_INJECT1_0] = std::string("YES"); +        new_cell->params[id_CCU2_INJECT1_1] = std::string("YES"); +        new_cell->params[id_WREMUX] = std::string("INV"); + +        add_port(ctx, new_cell.get(), id_A0, PORT_IN); +        add_port(ctx, new_cell.get(), id_B0, PORT_IN); +        add_port(ctx, new_cell.get(), id_C0, PORT_IN); +        add_port(ctx, new_cell.get(), id_D0, PORT_IN); + +        add_port(ctx, new_cell.get(), id_A1, PORT_IN); +        add_port(ctx, new_cell.get(), id_B1, PORT_IN); +        add_port(ctx, new_cell.get(), id_C1, PORT_IN); +        add_port(ctx, new_cell.get(), id_D1, PORT_IN); + +        add_port(ctx, new_cell.get(), id_M0, PORT_IN); +        add_port(ctx, new_cell.get(), id_M1, PORT_IN); + +        add_port(ctx, new_cell.get(), id_FCI, PORT_IN); +        add_port(ctx, new_cell.get(), id_FXA, PORT_IN); +        add_port(ctx, new_cell.get(), id_FXB, PORT_IN); + +        add_port(ctx, new_cell.get(), id_CLK, PORT_IN); +        add_port(ctx, new_cell.get(), id_LSR, PORT_IN); +        add_port(ctx, new_cell.get(), id_CE, PORT_IN); + +        add_port(ctx, new_cell.get(), id_DI0, PORT_IN); +        add_port(ctx, new_cell.get(), id_DI1, PORT_IN); + +        add_port(ctx, new_cell.get(), id_WD0, PORT_IN); +        add_port(ctx, new_cell.get(), id_WD1, PORT_IN); +        add_port(ctx, new_cell.get(), id_WAD0, PORT_IN); +        add_port(ctx, new_cell.get(), id_WAD1, PORT_IN); +        add_port(ctx, new_cell.get(), id_WAD2, PORT_IN); +        add_port(ctx, new_cell.get(), id_WAD3, PORT_IN); +        add_port(ctx, new_cell.get(), id_WRE, PORT_IN); +        add_port(ctx, new_cell.get(), id_WCK, PORT_IN); + +        add_port(ctx, new_cell.get(), id_F0, PORT_OUT); +        add_port(ctx, new_cell.get(), id_Q0, PORT_OUT); +        add_port(ctx, new_cell.get(), id_F1, PORT_OUT); +        add_port(ctx, new_cell.get(), id_Q1, PORT_OUT); + +        add_port(ctx, new_cell.get(), id_FCO, PORT_OUT); +        add_port(ctx, new_cell.get(), id_OFX0, PORT_OUT); +        add_port(ctx, new_cell.get(), id_OFX1, PORT_OUT); + +        add_port(ctx, new_cell.get(), id_WDO0, PORT_OUT); +        add_port(ctx, new_cell.get(), id_WDO1, PORT_OUT); +        add_port(ctx, new_cell.get(), id_WDO2, PORT_OUT); +        add_port(ctx, new_cell.get(), id_WDO3, PORT_OUT); +        add_port(ctx, new_cell.get(), id_WADO0, PORT_OUT); +        add_port(ctx, new_cell.get(), id_WADO1, PORT_OUT); +        add_port(ctx, new_cell.get(), id_WADO2, PORT_OUT); +        add_port(ctx, new_cell.get(), id_WADO3, PORT_OUT); +    } else if (type == id_FACADE_IO) { +        new_cell->params[id_DIR] = std::string("INPUT"); +        new_cell->attrs[ctx->id("IO_TYPE")] = std::string("LVCMOS33"); + +        add_port(ctx, new_cell.get(), "PAD", PORT_INOUT); +        add_port(ctx, new_cell.get(), "I", PORT_IN); +        add_port(ctx, new_cell.get(), "EN", PORT_IN); +        add_port(ctx, new_cell.get(), "O", PORT_OUT); +    } else if (type == id_LUT4) { +        new_cell->params[id_INIT] = Property(0, 16); + +        add_port(ctx, new_cell.get(), id_A, PORT_IN); +        add_port(ctx, new_cell.get(), id_B, PORT_IN); +        add_port(ctx, new_cell.get(), id_C, PORT_IN); +        add_port(ctx, new_cell.get(), id_D, PORT_IN); +        add_port(ctx, new_cell.get(), id_Z, PORT_OUT); +    } else { +        log_error("unable to create MachXO2 cell of type %s", type.c_str(ctx)); +    } + +    return new_cell; +} + +void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff) +{ +    lc->params[ctx->id("LUT0_INITVAL")] = lut->params[ctx->id("INIT")]; + +    for (std::string i : {"A", "B", "C", "D"}) { +        IdString lut_port = ctx->id(i); +        IdString lc_port = ctx->id(i + "0"); +        replace_port(lut, lut_port, lc, lc_port); +    } + +    replace_port(lut, ctx->id("Z"), lc, ctx->id("F0")); +} + +void dff_to_lc(Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut) +{ +    // FIXME: This will have to change once we support FFs with reset value of 1. +    lc->params[ctx->id("REG0_REGSET")] = std::string("RESET"); + +    replace_port(dff, ctx->id("CLK"), lc, ctx->id("CLK")); +    replace_port(dff, ctx->id("LSR"), lc, ctx->id("LSR")); +    replace_port(dff, ctx->id("Q"), lc, ctx->id("Q0")); + +    // If a register's DI port is fed by a constant, options for placing are +    // limited. Use the LUT to get around this. +    if (pass_thru_lut) { +        lc->params[ctx->id("LUT0_INITVAL")] = Property(0xAAAA, 16); +        ; +        replace_port(dff, ctx->id("DI"), lc, ctx->id("A0")); +        connect_ports(ctx, lc, ctx->id("F0"), lc, ctx->id("DI0")); +    } else { +        replace_port(dff, ctx->id("DI"), lc, ctx->id("DI0")); +    } +} + +void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *iob, std::unordered_set<IdString> &todelete_cells) {} + +NEXTPNR_NAMESPACE_END diff --git a/machxo2/cells.h b/machxo2/cells.h new file mode 100644 index 00000000..a6de219e --- /dev/null +++ b/machxo2/cells.h @@ -0,0 +1,56 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2019  David Shah <david@symbioticeda.com> + *  Copyright (C) 2021  William D. Jones <wjones@wdj-consulting.com> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "nextpnr.h" + +#ifndef MACHXO2_CELLS_H +#define MACHXO2_CELLS_H + +NEXTPNR_NAMESPACE_BEGIN + +// Create a MachXO2 arch cell and return it +// Name will be automatically assigned if not specified +std::unique_ptr<CellInfo> create_machxo2_cell(Context *ctx, IdString type, std::string name = ""); + +// Return true if a cell is a LUT +inline bool is_lut(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("LUT4"); } + +// Return true if a cell is a flipflop +inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("FACADE_FF"); } + +inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("FACADE_SLICE"); } + +// Convert a LUT primitive to (part of) an GENERIC_SLICE, swapping ports +// as needed. Set no_dff if a DFF is not being used, so that the output +// can be reconnected +void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff = true); + +// Convert a DFF primitive to (part of) an GENERIC_SLICE, setting parameters +// and reconnecting signals as necessary. If pass_thru_lut is True, the LUT will +// be configured as pass through and D connected to I0, otherwise D will be +// ignored +void dff_to_lc(Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut = false); + +// Convert a nextpnr IO buffer to a GENERIC_IOB +void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *sbio, std::unordered_set<IdString> &todelete_cells); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/machxo2/config.cc b/machxo2/config.cc new file mode 100644 index 00000000..2e17ce24 --- /dev/null +++ b/machxo2/config.cc @@ -0,0 +1,357 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018  David Shah <david@symbioticeda.com> + *  Copyright (C) 2021  William D. Jones <wjones@wdj-consulting.com> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "config.h" +#include <boost/range/adaptor/reversed.hpp> +#include <iomanip> +#include "log.h" +NEXTPNR_NAMESPACE_BEGIN + +#define fmt(x) (static_cast<const std::ostringstream &>(std::ostringstream() << x).str()) + +inline std::string to_string(const std::vector<bool> &bv) +{ +    std::ostringstream os; +    for (auto bit : boost::adaptors::reverse(bv)) +        os << (bit ? '1' : '0'); +    return os.str(); +} + +inline std::istream &operator>>(std::istream &in, std::vector<bool> &bv) +{ +    bv.clear(); +    std::string s; +    in >> s; +    for (auto c : boost::adaptors::reverse(s)) { +        assert((c == '0') || (c == '1')); +        bv.push_back((c == '1')); +    } +    return in; +} + +struct ConfigBit +{ +    int frame; +    int bit; +    bool inv; +}; + +static ConfigBit cbit_from_str(const std::string &s) +{ +    size_t idx = 0; +    ConfigBit b; +    if (s[idx] == '!') { +        b.inv = true; +        ++idx; +    } else { +        b.inv = false; +    } +    NPNR_ASSERT(s[idx] == 'F'); +    ++idx; +    size_t b_pos = s.find('B'); +    NPNR_ASSERT(b_pos != std::string::npos); +    b.frame = stoi(s.substr(idx, b_pos - idx)); +    b.bit = stoi(s.substr(b_pos + 1)); +    return b; +} + +inline std::string to_string(ConfigBit b) +{ +    std::ostringstream ss; +    if (b.inv) +        ss << "!"; +    ss << "F" << b.frame; +    ss << "B" << b.bit; +    return ss.str(); +} + +// Skip whitespace, optionally including newlines +inline void skip_blank(std::istream &in, bool nl = false) +{ +    int c = in.peek(); +    while (in && (((c == ' ') || (c == '\t')) || (nl && ((c == '\n') || (c == '\r'))))) { +        in.get(); +        c = in.peek(); +    } +} +// Return true if end of line (or file) +inline bool skip_check_eol(std::istream &in) +{ +    skip_blank(in, false); +    if (!in) +        return false; +    int c = in.peek(); +    // Comments count as end of line +    if (c == '#') { +        in.get(); +        c = in.peek(); +        while (in && c != EOF && c != '\n') { +            in.get(); +            c = in.peek(); +        } +        return true; +    } +    return (c == EOF || c == '\n'); +} + +// Skip past blank lines and comments +inline void skip(std::istream &in) +{ +    skip_blank(in, true); +    while (in && (in.peek() == '#')) { +        // Skip comment line +        skip_check_eol(in); +        skip_blank(in, true); +    } +} + +// Return true if at the end of a record (or file) +inline bool skip_check_eor(std::istream &in) +{ +    skip(in); +    int c = in.peek(); +    return (c == EOF || c == '.'); +} + +// Return true if at the end of file +inline bool skip_check_eof(std::istream &in) +{ +    skip(in); +    int c = in.peek(); +    return (c == EOF); +} + +std::ostream &operator<<(std::ostream &out, const ConfigArc &arc) +{ +    out << "arc: " << arc.sink << " " << arc.source << std::endl; +    return out; +} + +std::istream &operator>>(std::istream &in, ConfigArc &arc) +{ +    in >> arc.sink; +    in >> arc.source; +    return in; +} + +std::ostream &operator<<(std::ostream &out, const ConfigWord &cw) +{ +    out << "word: " << cw.name << " " << to_string(cw.value) << std::endl; +    return out; +} + +std::istream &operator>>(std::istream &in, ConfigWord &cw) +{ +    in >> cw.name; +    in >> cw.value; +    return in; +} + +std::ostream &operator<<(std::ostream &out, const ConfigEnum &cw) +{ +    out << "enum: " << cw.name << " " << cw.value << std::endl; +    return out; +} + +std::istream &operator>>(std::istream &in, ConfigEnum &ce) +{ +    in >> ce.name; +    in >> ce.value; +    return in; +} + +std::ostream &operator<<(std::ostream &out, const ConfigUnknown &cu) +{ +    out << "unknown: " << to_string(ConfigBit{cu.frame, cu.bit, false}) << std::endl; +    return out; +} + +std::istream &operator>>(std::istream &in, ConfigUnknown &cu) +{ +    std::string s; +    in >> s; +    ConfigBit c = cbit_from_str(s); +    cu.frame = c.frame; +    cu.bit = c.bit; +    assert(!c.inv); +    return in; +} + +std::ostream &operator<<(std::ostream &out, const TileConfig &tc) +{ +    for (const auto &arc : tc.carcs) +        out << arc; +    for (const auto &cword : tc.cwords) +        out << cword; +    for (const auto &cenum : tc.cenums) +        out << cenum; +    for (const auto &cunk : tc.cunknowns) +        out << cunk; +    return out; +} + +std::istream &operator>>(std::istream &in, TileConfig &tc) +{ +    tc.carcs.clear(); +    tc.cwords.clear(); +    tc.cenums.clear(); +    while (!skip_check_eor(in)) { +        std::string type; +        in >> type; +        if (type == "arc:") { +            ConfigArc a; +            in >> a; +            tc.carcs.push_back(a); +        } else if (type == "word:") { +            ConfigWord w; +            in >> w; +            tc.cwords.push_back(w); +        } else if (type == "enum:") { +            ConfigEnum e; +            in >> e; +            tc.cenums.push_back(e); +        } else if (type == "unknown:") { +            ConfigUnknown u; +            in >> u; +            tc.cunknowns.push_back(u); +        } else { +            NPNR_ASSERT_FALSE_STR("unexpected token " + type + " while reading config text"); +        } +    } +    return in; +} + +void TileConfig::add_arc(const std::string &sink, const std::string &source) { carcs.push_back({sink, source}); } + +void TileConfig::add_word(const std::string &name, const std::vector<bool> &value) { cwords.push_back({name, value}); } + +void TileConfig::add_enum(const std::string &name, const std::string &value) { cenums.push_back({name, value}); } + +void TileConfig::add_unknown(int frame, int bit) { cunknowns.push_back({frame, bit}); } + +std::string TileConfig::to_string() const +{ +    std::stringstream ss; +    ss << *this; +    return ss.str(); +} + +TileConfig TileConfig::from_string(const std::string &str) +{ +    std::stringstream ss(str); +    TileConfig tc; +    ss >> tc; +    return tc; +} + +bool TileConfig::empty() const { return carcs.empty() && cwords.empty() && cenums.empty() && cunknowns.empty(); } + +std::ostream &operator<<(std::ostream &out, const ChipConfig &cc) +{ +    out << ".device " << cc.chip_name << std::endl << std::endl; +    for (const auto &meta : cc.metadata) +        out << ".comment " << meta << std::endl; +    for (const auto &sc : cc.sysconfig) +        out << ".sysconfig " << sc.first << " " << sc.second << std::endl; +    out << std::endl; +    for (const auto &tile : cc.tiles) { +        if (!tile.second.empty()) { +            out << ".tile " << tile.first << std::endl; +            out << tile.second; +            out << std::endl; +        } +    } +    for (const auto &bram : cc.bram_data) { +        out << ".bram_init " << bram.first << std::endl; +        std::ios_base::fmtflags f(out.flags()); +        for (size_t i = 0; i < bram.second.size(); i++) { +            out << std::setw(3) << std::setfill('0') << std::hex << bram.second.at(i); +            if (i % 8 == 7) +                out << std::endl; +            else +                out << " "; +        } +        out.flags(f); +        out << std::endl; +    } +    for (const auto &tg : cc.tilegroups) { +        out << ".tile_group"; +        for (const auto &tile : tg.tiles) { +            out << " " << tile; +        } +        out << std::endl; +        out << tg.config; +        out << std::endl; +    } +    return out; +} + +std::istream &operator>>(std::istream &in, ChipConfig &cc) +{ +    while (!skip_check_eof(in)) { +        std::string verb; +        in >> verb; +        if (verb == ".device") { +            in >> cc.chip_name; +        } else if (verb == ".comment") { +            std::string line; +            getline(in, line); +            cc.metadata.push_back(line); +        } else if (verb == ".sysconfig") { +            std::string key, value; +            in >> key >> value; +            cc.sysconfig[key] = value; +        } else if (verb == ".tile") { +            std::string tilename; +            in >> tilename; +            TileConfig tc; +            in >> tc; +            cc.tiles[tilename] = tc; +        } else if (verb == ".tile_group") { +            TileGroup tg; +            std::string line; +            getline(in, line); +            std::stringstream ss2(line); + +            std::string tile; +            while (ss2) { +                ss2 >> tile; +                tg.tiles.push_back(tile); +            } +            in >> tg.config; +            cc.tilegroups.push_back(tg); +        } else if (verb == ".bram_init") { +            uint16_t bram; +            in >> bram; +            std::ios_base::fmtflags f(in.flags()); +            while (!skip_check_eor(in)) { +                uint16_t value; +                in >> std::hex >> value; +                cc.bram_data[bram].push_back(value); +            } +            in.flags(f); +        } else { +            log_error("unrecognised config entry %s\n", verb.c_str()); +        } +    } +    return in; +} + +NEXTPNR_NAMESPACE_END diff --git a/machxo2/config.h b/machxo2/config.h new file mode 100644 index 00000000..9e09d721 --- /dev/null +++ b/machxo2/config.h @@ -0,0 +1,128 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018  David Shah <david@symbioticeda.com> + *  Copyright (C) 2021  William D. Jones <wjones@wdj-consulting.com> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef MACHXO2_CONFIG_H +#define MACHXO2_CONFIG_H + +#include <map> +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +// This represents configuration at "FASM" level, in terms of routing arcs and non-routing configuration settings - +// either words or enums. + +// A connection in a tile +struct ConfigArc +{ +    std::string sink; +    std::string source; +    inline bool operator==(const ConfigArc &other) const { return other.source == source && other.sink == sink; } +}; + +std::ostream &operator<<(std::ostream &out, const ConfigArc &arc); + +std::istream &operator>>(std::istream &in, ConfigArc &arc); + +// A configuration setting in a tile that takes one or more bits (such as LUT init) +struct ConfigWord +{ +    std::string name; +    std::vector<bool> value; +    inline bool operator==(const ConfigWord &other) const { return other.name == name && other.value == value; } +}; + +std::ostream &operator<<(std::ostream &out, const ConfigWord &cw); + +std::istream &operator>>(std::istream &in, ConfigWord &cw); + +// A configuration setting in a tile that takes an enumeration value (such as IO type) +struct ConfigEnum +{ +    std::string name; +    std::string value; +    inline bool operator==(const ConfigEnum &other) const { return other.name == name && other.value == value; } +}; + +std::ostream &operator<<(std::ostream &out, const ConfigEnum &ce); + +std::istream &operator>>(std::istream &in, ConfigEnum &ce); + +// An unknown bit, specified by position only +struct ConfigUnknown +{ +    int frame, bit; +    inline bool operator==(const ConfigUnknown &other) const { return other.frame == frame && other.bit == bit; } +}; + +std::ostream &operator<<(std::ostream &out, const ConfigUnknown &tc); + +std::istream &operator>>(std::istream &in, ConfigUnknown &ce); + +struct TileConfig +{ +    std::vector<ConfigArc> carcs; +    std::vector<ConfigWord> cwords; +    std::vector<ConfigEnum> cenums; +    std::vector<ConfigUnknown> cunknowns; +    int total_known_bits = 0; + +    void add_arc(const std::string &sink, const std::string &source); +    void add_word(const std::string &name, const std::vector<bool> &value); +    void add_enum(const std::string &name, const std::string &value); +    void add_unknown(int frame, int bit); + +    std::string to_string() const; +    static TileConfig from_string(const std::string &str); + +    bool empty() const; +}; + +std::ostream &operator<<(std::ostream &out, const TileConfig &tc); + +std::istream &operator>>(std::istream &in, TileConfig &ce); + +// A group of tiles to configure at once for a particular feature that is split across tiles +// TileGroups are currently for non-routing configuration only +struct TileGroup +{ +    std::vector<std::string> tiles; +    TileConfig config; +}; + +// This represents the configuration of a chip at a high level +class ChipConfig +{ +  public: +    std::string chip_name; +    std::vector<std::string> metadata; +    std::map<std::string, TileConfig> tiles; +    std::vector<TileGroup> tilegroups; +    std::map<std::string, std::string> sysconfig; +    std::map<uint16_t, std::vector<uint16_t>> bram_data; +}; + +std::ostream &operator<<(std::ostream &out, const ChipConfig &cc); + +std::istream &operator>>(std::istream &in, ChipConfig &cc); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/machxo2/constids.inc b/machxo2/constids.inc new file mode 100644 index 00000000..b2ff51ae --- /dev/null +++ b/machxo2/constids.inc @@ -0,0 +1,118 @@ +X(FACADE_SLICE) +X(A0) +X(B0) +X(C0) +X(D0) +X(A1) +X(B1) +X(C1) +X(D1) +X(M0) +X(M1) +X(FCI) +X(FXA) +X(FXB) +X(CLK) +X(LSR) +X(CE) +X(DI0) +X(DI1) +X(WD0) +X(WD1) +X(WAD0) +X(WAD1) +X(WAD2) +X(WAD3) +X(WRE) +X(WCK) +X(F0) +X(Q0) +X(F1) +X(Q1) +X(FCO) +X(OFX0) +X(OFX1) +X(WDO0) +X(WDO1) +X(WDO2) +X(WDO3) +X(WADO0) +X(WADO1) +X(WADO2) +X(WADO3) + +X(MODE) +X(GSR) +X(SRMODE) +X(CEMUX) +X(CLKMUX) +X(LSRMUX) +X(LSRONMUX) +X(LUT0_INITVAL) +X(LUT1_INITVAL) +X(REGMODE) +X(REG0_SD) +X(REG1_SD) +X(REG0_REGSET) +X(REG1_REGSET) +X(CCU2_INJECT1_0) +X(CCU2_INJECT1_1) +X(WREMUX) + + +X(FACADE_FF) +X(DI) +X(Q) + +X(REGSET) + + +X(FACADE_IO) +X(PAD) +X(I) +X(EN) +X(O) + +X(DIR) + + +X(LUT4) +X(A) +X(B) +X(C) +X(D) +X(Z) + +X(INIT) + + +X(PFUMX) +X(ALUT) +X(BLUT) + + +X(L6MUX21) +X(SD) + + +X(T) +X(IOLDO) +X(IOLTO) + + +X(OSCH) +X(STDBY) +X(OSC) +X(SEDSTDBY) + + +X(DCCA) +X(CLKI) +X(CLKO) + + +X(DCMA) +X(CLK0) +X(CLK1) +X(SEL) +X(DCMOUT) diff --git a/machxo2/examples/.gitignore b/machxo2/examples/.gitignore new file mode 100644 index 00000000..8a87cc8d --- /dev/null +++ b/machxo2/examples/.gitignore @@ -0,0 +1,11 @@ +*_simtest* +*.vcd +*.png +*.log +*.smt2 +pack*.v +place*.v +pnr*.v +abc.history +*.txt +*.bit diff --git a/machxo2/examples/README.md b/machxo2/examples/README.md new file mode 100644 index 00000000..3542da70 --- /dev/null +++ b/machxo2/examples/README.md @@ -0,0 +1,110 @@ +# MachXO2 Architecture Example +This directory contains a simple example of running `nextpnr-machxo2`: + +* `simple.sh` produces nextpnr output in the files `{pack,place,pnr}*.json`, +  as well as pre-pnr and post-pnr diagrams in `{pack,place,pnr}*.{dot, png}`. +* `simtest.sh` extends `simple.sh` by generating `{pack,place,pnr}*.v` from +  `{pack,place,pnr}*.json`. The script calls the [`iverilog`](http://iverilog.icarus.com) +  compiler and `vvp` runtime to compare the behavior of `{pack,place,pnr}*.v` +  and the original Verilog input (using a testbench `*_tb.v`). This is known as +  post-place-and-route simulation. +* `mitertest.sh` is similar to `simtest.sh`, but more comprehensive. This +  script creates a [miter circuit](https://www21.in.tum.de/~lammich/2015_SS_Seminar_SAT/resources/Equivalence_Checking_11_30_08.pdf) +  to compare the output port values of `{pack,place,pnr}*.v` against the +  original Verilog code _when both modules are fed the same values on their input +  ports._ + +  All possible inputs and resulting outputs can be tested in reasonable time by +  using `yosys`' built-in SAT solver or [`z3`](https://github.com/Z3Prover/z3), +  an external SMT solver. +* `demo.sh` creates bitstreams for [TinyFPGA Ax](https://tinyfpga.com/a-series-guide.html) +  and writes the resulting bitstream to MachXO2's internal flash using +  [`tinyproga`](https://github.com/tinyfpga/TinyFPGA-A-Programmer). + +As `nextpnr-machxo2` is developed the contents `simple.sh`, `simtest.sh`, +`mitertest.sh`, and `demo.sh` are subject to change. + +## How To Run +Each script requires a prefix that matches one of the self-contained Verilog +examples in this directory. For instance, to create a bitstream from +`tinyfpga.v`, use `demo.sh tinyfpga` (the `*` glob used throughout this file +is filled with the the prefix). + +Each of `simple.sh`, `simtest.sh`, and `mitertest.sh` runs yosys and nextpnr +to validate a Verilog design in various ways. They require an additional `mode` +argument- `pack`, `place`, or `pnr`- which stops `nextpnr-machxo2` after the +specified phase and writes out a JSON file of the results in +`{pack,place,pnr}*.json`; `pnr` runs all of the Pack, Place, and Route phases. + +`mitertest.sh` requires an third option- `sat` or `smt`- to choose between +verifying the miter with either yosys' built-in SAT solver, or an external +SMT solver. + +Each script will exit if it finds an input Verilog example it knows it can't +handle. To keep file count lower, all yosys scripts are written inline inside +the `sh` scripts using the `-p` option. + +### Clean +To clean output files from _all_ scripts, run: + +``` +rm -rf *.dot *.json *.png *.vcd *.smt2 *.log *.txt *.bit {pack,place,pnr}*.v *_simtest* +``` + +## Known Issues +In principle, `mitertest.sh` should work in `sat` or `smt` mode with all +example Verilog files which don't use the internal oscillator (OSCH) or other +hard IP. However, as of this writing, only `blinky.v` passes correctly for a +few reasons: + +  1. The sim models for MachXO2 primitives used by the `gate` module contain +     `initial` values _by design_, as it matches chip behavior. Without any of +     the following in the `gold` module (like `blinky_ext.v` currently): + +     * An external reset signal +     * Internal power-on reset signal (e.g. `reg int_rst = 1'd1;`) +     * `initial` values to manually set registers + +     the `gold` and `gate` modules will inherently not match. + +     Examples using an internal power-on reset (e.g. `uart.v`) also have issues +     that I haven't debugged yet in both `sat` and `smt` mode. +  2. To keep the `gold`/`gate` generation simpler, examples are currently +     assumed to _not_ instantiate MachXO2 simulation primitives directly +    (`FACADE_IO`, `FACADE_FF`, etc). +  3. `synth_machxo2` runs `deminout` on `inouts` when generating the `gate` +     module. This is not handled yet when generating the `gold` module. + +## Verilog Examples +* `blinky.v`/`blinky_tb.v`- A blinky example meant for simulation. +* `tinyfpga.v`- Blink the LED on TinyFPA Ax. +* `rgbcount.v`- Blink an RGB LED using TinyFPGA Ax, more closely-based on +  [the TinyFPGA Ax guide](https://tinyfpga.com/a-series-guide.html). +* `blinky_ext.v`- Blink the LED on TinyFPA Ax using an external pin (pin 6). +* `uart.v`- UART loopback demo at 19200 baud. Requires the following pins: + +  * Pin 1- RX LED +  * Pin 2- TX (will echo RX) +  * Pin 3- RX +  * Pin 4- TX LED +  * Pin 5- Load LED +  * Pin 6- 12 MHz clock input +  * Pin 7- Take LED +  * Pin 8- Empty LED + +## Environment Variables For Scripts +* `YOSYS`- Set to the location of the `yosys` binary to test. Defaults to the +  `yosys` on the path. You may want to set this to a `yosys` binary in your +  source tree if doing development. +* `NEXTPNR`- Set to the location of the `nextpnr-machxo2` binary to test. +  Defaults to the `nextpnr-machxo2` binary at the root of the `nextpnr` source +  tree. This should be set, for instance, if doing an out-of-tree build of +  `nextpnr-machxo2`. +* `CELLS_SIM`- Set to the location of `machxo2/cells_sim.v` simulation models. +  Defaults to whatever `yosys-config` associated with the above `YOSYS` binary +  returns. You may want to set this to `/path/to/yosys/src/share/machxo2/cells_sim.v` +  if doing development; `yosys-config` cannot find these "before-installation" +  simulation models. +* `TRELLIS_DB`- Set to the location of the Project Trellis database to use. +  Defaults to nothing, which means `ecppack` will use whatever database is on +  its path. diff --git a/machxo2/examples/blinky.v b/machxo2/examples/blinky.v new file mode 100644 index 00000000..57bad543 --- /dev/null +++ b/machxo2/examples/blinky.v @@ -0,0 +1,17 @@ +module top(input clk, rst, output [7:0] leds); + +// TODO: Test miter circuit without reset value. SAT and SMT diverge without +// reset value (SAT succeeds, SMT fails). I haven't figured out the correct +// init set of options to make SAT fail. +// "sat -verify -prove-asserts -set-init-def -seq 1 miter" causes assertion +// failure in yosys. +reg [7:0] ctr = 8'h00; +always @(posedge clk) +	if (rst) +		ctr <= 8'h00; +	else +		ctr <= ctr + 1'b1; + +assign leds = ctr; + +endmodule diff --git a/machxo2/examples/blinky_ext.v b/machxo2/examples/blinky_ext.v new file mode 100644 index 00000000..a8bdd588 --- /dev/null +++ b/machxo2/examples/blinky_ext.v @@ -0,0 +1,19 @@ +// Modified from: +// https://github.com/tinyfpga/TinyFPGA-A-Series/tree/master/template_a2 + +module top ( +  (* LOC="13" *) +  output pin1, +  (* LOC="21" *) +  input clk +); + +  reg [23:0] led_timer; + +  always @(posedge clk) begin +    led_timer <= led_timer + 1; +  end + +  // left side of board +  assign pin1 = led_timer[23]; +endmodule diff --git a/machxo2/examples/blinky_tb.v b/machxo2/examples/blinky_tb.v new file mode 100644 index 00000000..f9925e6f --- /dev/null +++ b/machxo2/examples/blinky_tb.v @@ -0,0 +1,38 @@ +`timescale 1ns / 1ps +module blinky_tb; + +reg clk = 1'b0, rst = 1'b0; +reg [7:0] ctr_gold = 8'h00; +wire [7:0] ctr_gate; +top dut_i(.clk(clk), .rst(rst), .leds(ctr_gate)); + +task oneclk; +	begin +		clk = 1'b1; +		#10; +		clk = 1'b0; +		#10; +	end +endtask + +initial begin +	$dumpfile("blinky_simtest.vcd"); +	$dumpvars(0, blinky_tb); +	#100; +	rst = 1'b1; +	repeat (5) oneclk; +	#5 +	rst = 1'b0; +	#5 +	repeat (500) begin +		if (ctr_gold !== ctr_gate) begin +			$display("mismatch gold=%b gate=%b", ctr_gold, ctr_gate); +			$stop; +		end +		oneclk; +		ctr_gold = ctr_gold + 1'b1; +	end +	$finish; +end + +endmodule diff --git a/machxo2/examples/demo.sh b/machxo2/examples/demo.sh new file mode 100644 index 00000000..00cb0cd0 --- /dev/null +++ b/machxo2/examples/demo.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +if [ $# -lt 1 ]; then +    echo "Usage: $0 prefix" +    exit -1 +fi + +if ! grep -q "(\*.*LOC.*\*)" $1.v; then +    echo "$1.v does not have LOC constraints for tinyfpga_a." +    exit -2 +fi + +if [ ! -z ${TRELLIS_DB+x} ]; then +    DB_ARG="--db $TRELLIS_DB" +fi + +set -ex + +${YOSYS:-yosys} -p "synth_machxo2 -json $1.json" $1.v +${NEXTPNR:-../../nextpnr-machxo2} --1200 --package QFN32 --no-iobs --json $1.json --textcfg $1.txt +ecppack --compress $DB_ARG $1.txt $1.bit +tinyproga -b $1.bit diff --git a/machxo2/examples/mitertest.sh b/machxo2/examples/mitertest.sh new file mode 100644 index 00000000..cfae28b7 --- /dev/null +++ b/machxo2/examples/mitertest.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +if [ $# -lt 3 ]; then +    echo "Usage: $0 prefix nextpnr_mode solve_mode" +    exit -1 +fi + +if grep -q "OSCH" $1.v; then +    echo "$1.v uses blackbox primitive OSCH and cannot be simulated." +    exit -2 +fi + +case $2 in +    "pack") +        NEXTPNR_MODE="--pack-only" +        ;; +    "place") +        NEXTPNR_MODE="--no-route" +        ;; +    "pnr") +        NEXTPNR_MODE="" +        ;; +    *) +        echo "nextpnr_mode string must be \"pack\", \"place\", or \"pnr\"" +        exit -3 +        ;; +esac + +case $3 in +    "sat") +        SAT=1 +        ;; +    "smt") +        SMT=1 +        ;; +    *) +        echo "solve_mode string must be \"sat\", or \"smt\"" +        exit -4 +        ;; +esac + +do_sat() { +    ${YOSYS:-yosys} -l ${2}${1}_miter_sat.log -p "read_verilog ${1}.v +                        rename top gold +                        read_verilog ${2}${1}.v +                        rename top gate +                        read_verilog +/machxo2/cells_sim.v + +                        miter -equiv -make_assert -flatten gold gate ${2}${1}_miter +                        hierarchy -top ${2}${1}_miter +                        sat -verify -prove-asserts -tempinduct ${2}${1}_miter" +} + +do_smt() { +    ${YOSYS:-yosys} -l ${2}${1}_miter_smt.log -p "read_verilog ${1}.v +                        rename top gold +                        read_verilog ${2}${1}.v +                        rename top gate +                        read_verilog +/machxo2/cells_sim.v + +                        miter -equiv -make_assert gold gate ${2}${1}_miter +                        hierarchy -top ${2}${1}_miter; proc; +                        opt_clean +                        write_verilog ${2}${1}_miter.v +                        write_smt2 ${2}${1}_miter.smt2" + +    yosys-smtbmc -s z3 --dump-vcd ${2}${1}_miter_bmc.vcd ${2}${1}_miter.smt2 +    yosys-smtbmc -s z3 -i --dump-vcd ${2}${1}_miter_tmp.vcd ${2}${1}_miter.smt2 +} + +set -ex + +${YOSYS:-yosys} -p "read_verilog ${1}.v +                    synth_machxo2 -noiopad -json ${1}.json" +${NEXTPNR:-../../nextpnr-machxo2} $NEXTPNR_MODE --1200 --package QFN32 --no-iobs --json ${1}.json --write ${2}${1}.json +${YOSYS:-yosys} -p "read_verilog -lib +/machxo2/cells_sim.v +                    read_json ${2}${1}.json +                    clean -purge +                    write_verilog -noattr -norename ${2}${1}.v" + +if [ $3 = "sat" ]; then +    do_sat $1 $2 +elif [ $3 = "smt" ]; then +    do_smt $1 $2 +fi diff --git a/machxo2/examples/rgbcount.v b/machxo2/examples/rgbcount.v new file mode 100644 index 00000000..bf5c7518 --- /dev/null +++ b/machxo2/examples/rgbcount.v @@ -0,0 +1,33 @@ +// Modified from: +// https://github.com/tinyfpga/TinyFPGA-A-Series/tree/master/template_a2 +// https://tinyfpga.com/a-series-guide.html used as a basis. + +module top ( +  (* LOC="21" *) +  inout pin6, +  (* LOC="26" *) +  inout pin9_jtgnb, +  (* LOC="27" *) +  inout pin10_sda, +); +  wire clk; + +  OSCH #( +    .NOM_FREQ("2.08") +  ) internal_oscillator_inst ( +    .STDBY(1'b0), +    .OSC(clk) +  ); + +  reg [23:0] led_timer; + +  always @(posedge clk) begin +    led_timer <= led_timer + 1; +  end + +  // left side of board +  assign pin9_jtgnb = led_timer[23]; +  assign pin10_sda = led_timer[22]; +  assign pin6 = led_timer[21]; + +endmodule diff --git a/machxo2/examples/simple.sh b/machxo2/examples/simple.sh new file mode 100644 index 00000000..1da60933 --- /dev/null +++ b/machxo2/examples/simple.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +if [ $# -lt 2 ]; then +    echo "Usage: $0 prefix mode" +    exit -1 +fi + +case $2 in +    "pack") +        NEXTPNR_MODE="--pack-only" +        ;; +    "place") +        NEXTPNR_MODE="--no-route" +        ;; +    "pnr") +        NEXTPNR_MODE="" +        ;; +    *) +        echo "Mode string must be \"pack\", \"place\", or \"pnr\"" +        exit -2 +        ;; +esac + +set -ex + +${YOSYS:-yosys} -p "read_verilog ${1}.v +                    synth_machxo2 -json ${1}.json +                    show -format png -prefix ${1}" +${NEXTPNR:-../../nextpnr-machxo2} $NEXTPNR_MODE --1200 --package QFN32 --no-iobs --json ${1}.json --write ${2}${1}.json +${YOSYS:-yosys} -p "read_verilog -lib +/machxo2/cells_sim.v +                    read_json ${2}${1}.json +                    clean -purge +                    show -format png -prefix ${2}${1} +                    write_verilog -noattr -norename ${2}${1}.v" diff --git a/machxo2/examples/simtest.sh b/machxo2/examples/simtest.sh new file mode 100644 index 00000000..2c7f6f30 --- /dev/null +++ b/machxo2/examples/simtest.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +if [ $# -lt 2 ]; then +    echo "Usage: $0 prefix mode" +    exit -1 +fi + +case $2 in +    "pack") +        NEXTPNR_MODE="--pack-only" +        ;; +    "place") +        NEXTPNR_MODE="--no-route" +        ;; +    "pnr") +        NEXTPNR_MODE="" +        ;; +    *) +        echo "Mode string must be \"pack\", \"place\", or \"pnr\"" +        exit -2 +        ;; +esac + +if [ ! -f ${1}_tb.v ]; then +    echo "No testbench file (${1}_tb.v) found for ${1}.v" +    exit -3 +fi + +set -ex + +${YOSYS:-yosys} -p "read_verilog ${1}.v +                    synth_machxo2 -json ${1}.json" +${NEXTPNR:-../../nextpnr-machxo2} $NEXTPNR_MODE --1200 --package QFN32 --no-iobs --json ${1}.json --write ${2}${1}.json +${YOSYS:-yosys} -p "read_verilog -lib +/machxo2/cells_sim.v +                    read_json ${2}${1}.json +                    clean -purge +                    write_verilog -noattr -norename ${2}${1}.v" +iverilog -o ${1}_simtest ${CELLS_SIM:-`${YOSYS:yosys}-config --datdir/machxo2/cells_sim.v`} ${1}_tb.v ${2}${1}.v +vvp -N ./${1}_simtest diff --git a/machxo2/examples/tinyfpga.v b/machxo2/examples/tinyfpga.v new file mode 100644 index 00000000..bd26d8eb --- /dev/null +++ b/machxo2/examples/tinyfpga.v @@ -0,0 +1,28 @@ +// Modified from: +// https://github.com/tinyfpga/TinyFPGA-A-Series/tree/master/template_a2 +// https://tinyfpga.com/a-series-guide.html used as a basis. + +module top ( +  (* LOC="13" *) +  inout pin1 +); + + +  wire clk; + +  OSCH #( +    .NOM_FREQ("16.63") +  ) internal_oscillator_inst ( +    .STDBY(1'b0), +    .OSC(clk) +  ); + +  reg [23:0] led_timer; + +  always @(posedge clk) begin +    led_timer <= led_timer + 1; +  end + +  // left side of board +  assign pin1 = led_timer[23]; +endmodule diff --git a/machxo2/examples/uart.v b/machxo2/examples/uart.v new file mode 100644 index 00000000..f1d95bd8 --- /dev/null +++ b/machxo2/examples/uart.v @@ -0,0 +1,209 @@ +/* Example UART derived from: https://github.com/cr1901/migen_uart. +   Requires 12MHz clock and runs at 19,200 baud. */ + +/* Machine-generated using Migen */ + +module top( +	(* LOC = "14" *) +	output tx, +	(* LOC = "16" *) +	input rx, +	(* LOC = "13" *) +	output rx_led, +	(* LOC = "17" *) +	output tx_led, +	(* LOC = "20" *) +	output load_led, +	(* LOC = "23" *) +	output take_led, +	(* LOC = "25" *) +	output empty_led, +	(* LOC = "21" *) +	input clk +); + +wire [7:0] out_data; +wire [7:0] in_data; +reg wr = 1'd0; +reg rd = 1'd0; +wire tx_empty; +wire rx_empty; +wire tx_ov; +wire rx_ov; +wire sout_load; +wire [7:0] sout_out_data; +wire sout_shift; +reg sout_empty = 1'd1; +reg sout_overrun = 1'd0; +reg [3:0] sout_count = 4'd0; +reg [9:0] sout_reg = 10'd0; +reg sout_tx; +wire sin_rx; +wire sin_shift; +wire sin_take; +reg [7:0] sin_in_data = 8'd0; +wire sin_edge; +reg sin_empty = 1'd1; +reg sin_busy = 1'd0; +reg sin_overrun = 1'd0; +reg sin_sync_rx = 1'd0; +reg [8:0] sin_reg = 9'd0; +reg sin_rx_prev = 1'd0; +reg [3:0] sin_count = 4'd0; +wire out_active; +wire in_active; +reg shift_out_strobe = 1'd0; +reg shift_in_strobe = 1'd0; +reg [9:0] in_counter = 10'd0; +reg [9:0] out_counter = 10'd0; +wire sys_clk; +wire sys_rst; +wire por_clk; +reg int_rst = 1'd1; + +// synthesis translate_off +reg dummy_s; +initial dummy_s <= 1'd0; +// synthesis translate_on + +assign tx_led = (~tx); +assign rx_led = (~rx); +assign load_led = sout_load; +assign take_led = sin_take; +assign empty_led = sin_empty; +assign out_data = in_data; +assign in_data = sin_in_data; +assign sout_out_data = out_data; +assign sin_take = rd; +assign sout_load = wr; +assign tx = sout_tx; +assign sin_rx = rx; +assign tx_empty = sout_empty; +assign rx_empty = sin_empty; +assign tx_ov = sout_overrun; +assign rx_ov = sin_overrun; +assign sout_shift = shift_out_strobe; +assign sin_shift = shift_in_strobe; +assign out_active = (~sout_empty); +assign in_active = sin_busy; + +// synthesis translate_off +reg dummy_d; +// synthesis translate_on +always @(*) begin +	sout_tx <= 1'd0; +	if (sout_empty) begin +		sout_tx <= 1'd1; +	end else begin +		sout_tx <= sout_reg[0]; +	end +// synthesis translate_off +	dummy_d <= dummy_s; +// synthesis translate_on +end +assign sin_edge = ((sin_rx_prev == 1'd1) & (sin_sync_rx == 1'd0)); +assign sys_clk = clk; +assign por_clk = clk; +assign sys_rst = int_rst; + +always @(posedge por_clk) begin +	int_rst <= 1'd0; +end + +always @(posedge sys_clk) begin +	wr <= 1'd0; +	rd <= 1'd0; +	if ((~sin_empty)) begin +		wr <= 1'd1; +		rd <= 1'd1; +	end +	if (sout_load) begin +		if (sout_empty) begin +			sout_reg[0] <= 1'd0; +			sout_reg[8:1] <= sout_out_data; +			sout_reg[9] <= 1'd1; +			sout_empty <= 1'd0; +			sout_overrun <= 1'd0; +			sout_count <= 1'd0; +		end else begin +			sout_overrun <= 1'd1; +		end +	end +	if (((~sout_empty) & sout_shift)) begin +		sout_reg[8:0] <= sout_reg[9:1]; +		sout_reg[9] <= 1'd0; +		if ((sout_count == 4'd9)) begin +			sout_empty <= 1'd1; +			sout_count <= 1'd0; +		end else begin +			sout_count <= (sout_count + 1'd1); +		end +	end +	sin_sync_rx <= sin_rx; +	sin_rx_prev <= sin_sync_rx; +	if (sin_take) begin +		sin_empty <= 1'd1; +		sin_overrun <= 1'd0; +	end +	if (((~sin_busy) & sin_edge)) begin +		sin_busy <= 1'd1; +	end +	if ((sin_shift & sin_busy)) begin +		sin_reg[8] <= sin_sync_rx; +		sin_reg[7:0] <= sin_reg[8:1]; +		if ((sin_count == 4'd9)) begin +			sin_in_data <= sin_reg[8:1]; +			sin_count <= 1'd0; +			sin_busy <= 1'd0; +			if ((~sin_empty)) begin +				sin_overrun <= 1'd1; +			end else begin +				sin_empty <= 1'd0; +			end +		end else begin +			sin_count <= (sin_count + 1'd1); +		end +	end +	out_counter <= 1'd0; +	in_counter <= 1'd0; +	if (in_active) begin +		shift_in_strobe <= 1'd0; +		in_counter <= (in_counter + 1'd1); +		if ((in_counter == 9'd311)) begin +			shift_in_strobe <= 1'd1; +		end +		if ((in_counter == 10'd623)) begin +			in_counter <= 1'd0; +		end +	end +	if (out_active) begin +		shift_out_strobe <= 1'd0; +		out_counter <= (out_counter + 1'd1); +		if ((out_counter == 10'd623)) begin +			out_counter <= 1'd0; +			shift_out_strobe <= 1'd1; +		end +	end +	if (sys_rst) begin +		wr <= 1'd0; +		rd <= 1'd0; +		sout_empty <= 1'd1; +		sout_overrun <= 1'd0; +		sout_count <= 4'd0; +		sout_reg <= 10'd0; +		sin_in_data <= 8'd0; +		sin_empty <= 1'd1; +		sin_busy <= 1'd0; +		sin_overrun <= 1'd0; +		sin_sync_rx <= 1'd0; +		sin_reg <= 9'd0; +		sin_rx_prev <= 1'd0; +		sin_count <= 4'd0; +		shift_out_strobe <= 1'd0; +		shift_in_strobe <= 1'd0; +		in_counter <= 10'd0; +		out_counter <= 10'd0; +	end +end + +endmodule diff --git a/machxo2/facade_import.py b/machxo2/facade_import.py new file mode 100644 index 00000000..5bb2d78b --- /dev/null +++ b/machxo2/facade_import.py @@ -0,0 +1,368 @@ +#!/usr/bin/env python3 +import argparse +import json +import sys +from os import path + +tiletype_names = dict() + +parser = argparse.ArgumentParser(description="import MachXO2 routing and bels from Project Trellis") +parser.add_argument("device", type=str, help="target device") +parser.add_argument("-p", "--constids", type=str, help="path to constids.inc") +parser.add_argument("-g", "--gfxh", type=str, help="path to gfx.h (unused)") +parser.add_argument("-L", "--libdir", type=str, action="append", help="extra Python library path") +args = parser.parse_args() + +sys.path += args.libdir +import pytrellis +import database + +# Get the index for a tiletype +def get_tiletype_index(name): +    if name in tiletype_names: +        return tiletype_names[name] +    idx = len(tiletype_names) +    tiletype_names[name] = idx +    return idx + + +constids = dict() + + +class BinaryBlobAssembler: +    def l(self, name, ltype = None, export = False): +        if ltype is None: +            print("label %s" % (name,)) +        else: +            print("label %s %s" % (name, ltype)) + +    def r(self, name, comment): +        if comment is None: +            print("ref %s" % (name,)) +        else: +            print("ref %s %s" % (name, comment)) + +    def s(self, s, comment): +        assert "|" not in s +        print("str |%s| %s" % (s, comment)) + +    def u8(self, v, comment): +        if comment is None: +            print("u8 %d" % (v,)) +        else: +            print("u8 %d %s" % (v, comment)) + +    def u16(self, v, comment): +        if comment is None: +            print("u16 %d" % (v,)) +        else: +            print("u16 %d %s" % (v, comment)) + +    def u32(self, v, comment): +        if comment is None: +            print("u32 %d" % (v,)) +        else: +            print("u32 %d %s" % (v, comment)) + +    def pre(self, s): +        print("pre %s" % s) + +    def post(self, s): +        print("post %s" % s) + +    def push(self, name): +        print("push %s" % name) + +    def pop(self): +        print("pop") + +def get_bel_index(rg, loc, name): +    tile = rg.tiles[loc] +    idx = 0 +    for bel in tile.bels: +        if rg.to_str(bel.name) == name: +            return idx +        idx += 1 +    # FIXME: I/O pins can be missing in various rows. Is there a nice way to +    # assert on each device size? +    return None + + +packages = {} +pindata = [] + +def process_pio_db(rg, device): +    piofile = path.join(database.get_db_root(), "MachXO2", dev_names[device], "iodb.json") +    with open(piofile, 'r') as f: +        piodb = json.load(f) +        for pkgname, pkgdata in sorted(piodb["packages"].items()): +            pins = [] +            for name, pinloc in sorted(pkgdata.items()): +                x = pinloc["col"] +                y = pinloc["row"] +                if x == 0 or x == max_col: +                    # FIXME: Oversight in read_pinout.py. We use 0-based +                    # columns for 0 and max row, but we otherwise extract +                    # the names from the CSV, and... +                    loc = pytrellis.Location(x, y) +                else: +                    # Lattice uses 1-based columns! +                    loc = pytrellis.Location(x - 1, y) +                pio = "PIO" + pinloc["pio"] +                bel_idx = get_bel_index(rg, loc, pio) +                if bel_idx is not None: +                    pins.append((name, loc, bel_idx)) +            packages[pkgname] = pins +        for metaitem in piodb["pio_metadata"]: +            x = metaitem["col"] +            y = metaitem["row"] +            if x == 0 or x == max_col: +                loc = pytrellis.Location(x, y) +            else: +                loc = pytrellis.Location(x - 1, y) +            pio = "PIO" + metaitem["pio"] +            bank = metaitem["bank"] +            if "function" in metaitem: +                pinfunc = metaitem["function"] +            else: +                pinfunc = None +            dqs = -1 +            if "dqs" in metaitem: +                pass +                # tdqs = metaitem["dqs"] +                # if tdqs[0] == "L": +                #     dqs = 0 +                # elif tdqs[0] == "R": +                #     dqs = 2048 +                # suffix_size = 0 +                # while tdqs[-(suffix_size+1)].isdigit(): +                #     suffix_size += 1 +                # dqs |= int(tdqs[-suffix_size:]) +            bel_idx = get_bel_index(rg, loc, pio) +            if bel_idx is not None: +                pindata.append((loc, bel_idx, bank, pinfunc, dqs)) + +def write_database(dev_name, chip, rg, endianness): +    def write_loc(loc, sym_name): +        bba.u16(loc.x, "%s.x" % sym_name) +        bba.u16(loc.y, "%s.y" % sym_name) + +    # Use Lattice naming conventions, so convert to 1-based col indexing. +    def get_wire_name(loc, idx): +        tile = rg.tiles[loc] +        return "R{}C{}_{}".format(loc.y, loc.x + 1, rg.to_str(tile.wires[idx].name)) + +    # Before doing anything, ensure sorted routing graph iteration matches +    # y, x +    loc_iter = list(sorted(rg.tiles, key=lambda l : (l.y, l.x))) + +    i = 1 # Drop (-2, -2) location. +    for y in range(0, max_row+1): +        for x in range(0, max_col+1): +            l = loc_iter[i] +            assert((y, x) == (l.y, l.x)) +            i = i + 1 + +    bba = BinaryBlobAssembler() +    bba.pre('#include "nextpnr.h"') +    bba.pre('#include "embed.h"') +    bba.pre('NEXTPNR_NAMESPACE_BEGIN') +    bba.post('EmbeddedFile chipdb_file_%s("machxo2/chipdb-%s.bin", chipdb_blob_%s);' % (dev_name, dev_name, dev_name)) +    bba.post('NEXTPNR_NAMESPACE_END') +    bba.push("chipdb_blob_%s" % args.device) +    bba.r("chip_info", "chip_info") + +    # Nominally should be in order, but support situations where python +    # decides to iterate over rg.tiles out-of-order. +    for l in loc_iter: +        t = rg.tiles[l] + +        # Do not include special globals location for now. +        if (l.x, l.y) == (-2, -2): +            continue + +        if len(t.arcs) > 0: +            bba.l("loc%d_%d_pips" % (l.y, l.x), "PipInfoPOD") +            for arc in t.arcs: +                write_loc(arc.srcWire.rel, "src") +                write_loc(arc.sinkWire.rel, "dst") +                bba.u32(arc.srcWire.id, "src_idx {}".format(get_wire_name(arc.srcWire.rel, arc.srcWire.id))) +                bba.u32(arc.sinkWire.id, "dst_idx {}".format(get_wire_name(arc.sinkWire.rel, arc.sinkWire.id))) +                src_name = get_wire_name(arc.srcWire.rel, arc.srcWire.id) +                snk_name = get_wire_name(arc.sinkWire.rel, arc.sinkWire.id) +                # TODO: ECP5 timing-model-specific. Reuse for MachXO2? +                # bba.u32(get_pip_class(src_name, snk_name), "timing_class") +                bba.u32(0, "timing_class") +                bba.u16(get_tiletype_index(rg.to_str(arc.tiletype)), "tile_type") +                cls = arc.cls +                bba.u8(arc.cls, "pip_type") +                bba.u8(0, "padding") + +        if len(t.wires) > 0: +            for wire_idx in range(len(t.wires)): +                wire = t.wires[wire_idx] +                if len(wire.arcsDownhill) > 0: +                    bba.l("loc%d_%d_wire%d_downpips" % (l.y, l.x, wire_idx), "PipLocatorPOD") +                    for dp in wire.arcsDownhill: +                        write_loc(dp.rel, "rel_loc") +                        bba.u32(dp.id, "index") +                if len(wire.arcsUphill) > 0: +                    bba.l("loc%d_%d_wire%d_uppips" % (l.y, l.x, wire_idx), "PipLocatorPOD") +                    for up in wire.arcsUphill: +                        write_loc(up.rel, "rel_loc") +                        bba.u32(up.id, "index") +                if len(wire.belPins) > 0: +                    bba.l("loc%d_%d_wire%d_belpins" % (l.y, l.x, wire_idx), "BelPortPOD") +                    for bp in wire.belPins: +                        write_loc(bp.bel.rel, "rel_bel_loc") +                        bba.u32(bp.bel.id, "bel_index") +                        bba.u32(constids[rg.to_str(bp.pin)], "port") + +            bba.l("loc%d_%d_wires" % (l.y, l.x), "WireInfoPOD") +            for wire_idx in range(len(t.wires)): +                wire = t.wires[wire_idx] +                bba.s(rg.to_str(wire.name), "name") +                # TODO: Padding until GUI support is added. +                # bba.u32(constids[wire_type(ddrg.to_str(wire.name))], "type") +                # if ("TILE_WIRE_" + ddrg.to_str(wire.name)) in gfx_wire_ids: +                #     bba.u32(gfx_wire_ids["TILE_WIRE_" + ddrg.to_str(wire.name)], "tile_wire") +                # else: +                bba.u32(0, "tile_wire") +                bba.u32(len(wire.arcsUphill), "num_uphill") +                bba.u32(len(wire.arcsDownhill), "num_downhill") +                bba.r("loc%d_%d_wire%d_uppips" % (l.y, l.x, wire_idx) if len(wire.arcsUphill) > 0 else None, "pips_uphill") +                bba.r("loc%d_%d_wire%d_downpips" % (l.y, l.x, wire_idx) if len(wire.arcsDownhill) > 0 else None, "pips_downhill") +                bba.u32(len(wire.belPins), "num_bel_pins") +                bba.r("loc%d_%d_wire%d_belpins" % (l.y, l.x, wire_idx) if len(wire.belPins) > 0 else None, "bel_pins") + +        if len(t.bels) > 0: +            for bel_idx in range(len(t.bels)): +                bel = t.bels[bel_idx] +                bba.l("loc%d_%d_bel%d_wires" % (l.y, l.x, bel_idx), "BelWirePOD") +                for pin in bel.wires: +                    write_loc(pin.wire.rel, "rel_wire_loc") +                    bba.u32(pin.wire.id, "wire_index") +                    bba.u32(constids[rg.to_str(pin.pin)], "port") +                    bba.u32(int(pin.dir), "dir") +            bba.l("loc%d_%d_bels" % (l.y, l.x), "BelInfoPOD") +            for bel_idx in range(len(t.bels)): +                bel = t.bels[bel_idx] +                bba.s(rg.to_str(bel.name), "name") +                bba.u32(constids[rg.to_str(bel.type)], "type") +                bba.u32(bel.z, "z") +                bba.u32(len(bel.wires), "num_bel_wires") +                bba.r("loc%d_%d_bel%d_wires" % (l.y, l.x, bel_idx), "bel_wires") + +    bba.l("tiles", "TileTypePOD") +    for l in loc_iter: +        t = rg.tiles[l] + +        if (l.y, l.x) == (-2, -2): +            continue + +        bba.u32(len(t.bels), "num_bels") +        bba.u32(len(t.wires), "num_wires") +        bba.u32(len(t.arcs), "num_pips") +        bba.r("loc%d_%d_bels" % (l.y, l.x) if len(t.bels) > 0 else None, "bel_data") +        bba.r("loc%d_%d_wires" % (l.y, l.x) if len(t.wires) > 0 else None, "wire_data") +        bba.r("loc%d_%d_pips" % (l.y, l.x) if len(t.arcs) > 0 else None, "pips_data") + +    for y in range(0, max_row+1): +        for x in range(0, max_col+1): +            bba.l("tile_info_%d_%d" % (x, y), "TileNamePOD") +            for tile in chip.get_tiles_by_position(y, x): +                bba.s(tile.info.name, "name") +                bba.u16(get_tiletype_index(tile.info.type), "type_idx") +                bba.u16(0, "padding") + +    bba.l("tiles_info", "TileInfoPOD") +    for y in range(0, max_row+1): +        for x in range(0, max_col+1): +            bba.u32(len(chip.get_tiles_by_position(y, x)), "num_tiles") +            bba.r("tile_info_%d_%d" % (x, y), "tile_names") + +    for package, pkgdata in sorted(packages.items()): +        bba.l("package_data_%s" % package, "PackagePinPOD") +        for pin in pkgdata: +            name, loc, bel_idx = pin +            bba.s(name, "name") +            write_loc(loc, "abs_loc") +            bba.u32(bel_idx, "bel_index") + +    bba.l("package_data", "PackageInfoPOD") +    for package, pkgdata in sorted(packages.items()): +        bba.s(package, "name") +        bba.u32(len(pkgdata), "num_pins") +        bba.r("package_data_%s" % package, "pin_data") + +    bba.l("pio_info", "PIOInfoPOD") +    for pin in pindata: +        loc, bel_idx, bank, func, dqs = pin +        write_loc(loc, "abs_loc") +        bba.u32(bel_idx, "bel_index") +        if func is not None and func != "WRITEN": +            bba.s(func, "function_name") +        else: +            bba.r(None, "function_name") +        # TODO: io_grouping? And DQS. +        bba.u16(bank, "bank") +        bba.u16(dqs, "dqsgroup") + +    bba.l("tiletype_names", "RelPtr<char>") +    for tt, idx in sorted(tiletype_names.items(), key=lambda x: x[1]): +        bba.s(tt, "name") + + +    bba.l("chip_info") +    bba.u32(max_col + 1, "width") +    bba.u32(max_row + 1, "height") +    bba.u32((max_col + 1) * (max_row + 1), "num_tiles") +    bba.u32(len(packages), "num_packages") +    bba.u32(len(pindata), "num_pios") +    bba.u32(const_id_count, "const_id_count") + +    bba.r("tiles", "tiles") +    bba.r("tiletype_names", "tiletype_names") +    bba.r("package_data", "package_info") +    bba.r("pio_info", "pio_info") +    bba.r("tiles_info", "tile_info") + +    bba.pop() + + +dev_names = {"1200": "LCMXO2-1200HC"} + +def main(): +    global max_row, max_col, const_id_count + +    pytrellis.load_database(database.get_db_root()) +    args = parser.parse_args() + +    const_id_count = 1 # count ID_NONE +    with open(args.constids) as f: +        for line in f: +            line = line.replace("(", " ") +            line = line.replace(")", " ") +            line = line.split() +            if len(line) == 0: +                continue +            assert len(line) == 2 +            assert line[0] == "X" +            idx = len(constids) + 1 +            constids[line[1]] = idx +            const_id_count += 1 + +    constids["SLICE"] = constids["FACADE_SLICE"] +    constids["PIO"] = constids["FACADE_IO"] + +    chip = pytrellis.Chip(dev_names[args.device]) +    rg = pytrellis.make_optimized_chipdb(chip) +    max_row = chip.get_max_row() +    max_col = chip.get_max_col() +    process_pio_db(rg, args.device) +    bba = write_database(args.device, chip, rg, "le") + + + +if __name__ == "__main__": +    main() diff --git a/machxo2/family.cmake b/machxo2/family.cmake new file mode 100644 index 00000000..76c5df93 --- /dev/null +++ b/machxo2/family.cmake @@ -0,0 +1,53 @@ +add_subdirectory(${family}) +message(STATUS "Using MachXO2 chipdb: ${MACHXO2_CHIPDB}") + +set(chipdb_sources) +set(chipdb_binaries) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${family}/chipdb) +foreach(device ${MACHXO2_DEVICES}) +    set(chipdb_bba ${MACHXO2_CHIPDB}/chipdb-${device}.bba) +    set(chipdb_bin ${family}/chipdb/chipdb-${device}.bin) +    set(chipdb_cc  ${family}/chipdb/chipdb-${device}.cc) +    if(BBASM_MODE STREQUAL "binary") +        add_custom_command( +            OUTPUT ${chipdb_bin} +            COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${chipdb_bba} ${chipdb_bin} +            DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba}) +        list(APPEND chipdb_binaries ${chipdb_bin}) +    elseif(BBASM_MODE STREQUAL "embed") +        add_custom_command( +            OUTPUT ${chipdb_cc} ${chipdb_bin} +            COMMAND bbasm ${BBASM_ENDIAN_FLAG} --e ${chipdb_bba} ${chipdb_cc} ${chipdb_bin} +            DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba}) +        list(APPEND chipdb_sources ${chipdb_cc}) +        list(APPEND chipdb_binaries ${chipdb_bin}) +    elseif(BBASM_MODE STREQUAL "string") +        add_custom_command( +            OUTPUT ${chipdb_cc} +            COMMAND bbasm ${BBASM_ENDIAN_FLAG} --c ${chipdb_bba} ${chipdb_cc} +            DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba}) +        list(APPEND chipdb_sources ${chipdb_cc}) +    endif() +endforeach() +if(WIN32) +    set(chipdb_rc ${CMAKE_CURRENT_BINARY_DIR}/${family}/resource/chipdb.rc) +    list(APPEND chipdb_sources ${chipdb_rc}) + +    file(WRITE ${chipdb_rc}) +    foreach(device ${MACHXO2_DEVICES}) +        file(APPEND ${chipdb_rc} +             "${family}/chipdb-${device}.bin RCDATA \"${CMAKE_CURRENT_BINARY_DIR}/${family}/chipdb/chipdb-${device}.bin\"") +    endforeach() +endif() + +add_custom_target(chipdb-${family}-bins DEPENDS ${chipdb_sources} ${chipdb_binaries}) + +add_library(chipdb-${family} OBJECT ${MACHXO2_CHIPDB} ${chipdb_sources}) +add_dependencies(chipdb-${family} chipdb-${family}-bins) +target_compile_options(chipdb-${family} PRIVATE -g0 -O0 -w) +target_compile_definitions(chipdb-${family} PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family}) +target_include_directories(chipdb-${family} PRIVATE ${family}) + +foreach(family_target ${family_targets}) +    target_sources(${family_target} PRIVATE $<TARGET_OBJECTS:chipdb-${family}>) +endforeach() diff --git a/machxo2/main.cc b/machxo2/main.cc new file mode 100644 index 00000000..961fe9ae --- /dev/null +++ b/machxo2/main.cc @@ -0,0 +1,122 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018  Claire Xen <claire@symbioticeda.com> + *  Copyright (C) 2021  William D. Jones <wjones@wdj-consulting.com> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifdef MAIN_EXECUTABLE + +#include <fstream> +#include "bitstream.h" +#include "command.h" +#include "design_utils.h" +#include "log.h" +#include "timing.h" + +USING_NEXTPNR_NAMESPACE + +class MachXO2CommandHandler : public CommandHandler +{ +  public: +    MachXO2CommandHandler(int argc, char **argv); +    virtual ~MachXO2CommandHandler(){}; +    std::unique_ptr<Context> createContext(std::unordered_map<std::string, Property> &values) override; +    void setupArchContext(Context *ctx) override{}; +    void customBitstream(Context *ctx) override; + +  protected: +    po::options_description getArchOptions() override; +}; + +MachXO2CommandHandler::MachXO2CommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {} + +po::options_description MachXO2CommandHandler::getArchOptions() +{ +    po::options_description specific("Architecture specific options"); +    if (Arch::is_available(ArchArgs::LCMXO2_256HC)) +        specific.add_options()("256", "set device type to LCMXO2-256HC"); +    if (Arch::is_available(ArchArgs::LCMXO2_640HC)) +        specific.add_options()("640", "set device type to LCMXO2-640HC"); +    if (Arch::is_available(ArchArgs::LCMXO2_1200HC)) +        specific.add_options()("1200", "set device type to LCMXO2-1200HC"); +    if (Arch::is_available(ArchArgs::LCMXO2_2000HC)) +        specific.add_options()("2000", "set device type to LCMXO2-2000HC"); +    if (Arch::is_available(ArchArgs::LCMXO2_4000HC)) +        specific.add_options()("4000", "set device type to LCMXO2-4000HC"); +    if (Arch::is_available(ArchArgs::LCMXO2_7000HC)) +        specific.add_options()("7000", "set device type to LCMXO2-7000HC"); + +    specific.add_options()("package", po::value<std::string>(), "select device package"); +    specific.add_options()("speed", po::value<int>(), "select device speedgrade (1 to 6 inclusive)"); + +    specific.add_options()("override-basecfg", po::value<std::string>(), +                           "base chip configuration in Trellis text format"); +    specific.add_options()("textcfg", po::value<std::string>(), "textual configuration in Trellis format to write"); + +    // specific.add_options()("lpf", po::value<std::vector<std::string>>(), "LPF pin constraint file(s)"); + +    specific.add_options()("no-iobs", "disable automatic IO buffer insertion (unimplemented- always enabled)"); +    return specific; +} + +void MachXO2CommandHandler::customBitstream(Context *ctx) +{ +    std::string textcfg; +    if (vm.count("textcfg")) +        textcfg = vm["textcfg"].as<std::string>(); + +    write_bitstream(ctx, textcfg); +} + +std::unique_ptr<Context> MachXO2CommandHandler::createContext(std::unordered_map<std::string, Property> &values) +{ +    ArchArgs chipArgs; +    chipArgs.type = ArchArgs::NONE; +    if (vm.count("256")) +        chipArgs.type = ArchArgs::LCMXO2_256HC; +    if (vm.count("640")) +        chipArgs.type = ArchArgs::LCMXO2_640HC; +    if (vm.count("1200")) +        chipArgs.type = ArchArgs::LCMXO2_1200HC; +    if (vm.count("2000")) +        chipArgs.type = ArchArgs::LCMXO2_2000HC; +    if (vm.count("4000")) +        chipArgs.type = ArchArgs::LCMXO2_4000HC; +    if (vm.count("7000")) +        chipArgs.type = ArchArgs::LCMXO2_7000HC; +    if (vm.count("package")) +        chipArgs.package = vm["package"].as<std::string>(); + +    if (values.find("arch.name") != values.end()) { +        std::string arch_name = values["arch.name"].as_string(); +        if (arch_name != "machxo2") +            log_error("Unsuported architecture '%s'.\n", arch_name.c_str()); +    } + +    auto ctx = std::unique_ptr<Context>(new Context(chipArgs)); +    if (vm.count("no-iobs")) +        ctx->settings[ctx->id("disable_iobs")] = Property::State::S1; +    return ctx; +} + +int main(int argc, char *argv[]) +{ +    MachXO2CommandHandler handler(argc, argv); +    return handler.exec(); +} + +#endif diff --git a/machxo2/pack.cc b/machxo2/pack.cc new file mode 100644 index 00000000..5a6cd97b --- /dev/null +++ b/machxo2/pack.cc @@ -0,0 +1,296 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018-19  David Shah <david@symbioticeda.com> + *  Copyright (C) 2021  William D. Jones <wjones@wdj-consulting.com> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <algorithm> +#include <iterator> +#include <unordered_set> +#include "cells.h" +#include "design_utils.h" +#include "log.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +// Pack LUTs and LUT-FF pairs +static void pack_lut_lutffs(Context *ctx) +{ +    log_info("Packing LUT-FFs..\n"); + +    std::unordered_set<IdString> packed_cells; +    std::vector<std::unique_ptr<CellInfo>> new_cells; +    for (auto cell : sorted(ctx->cells)) { +        CellInfo *ci = cell.second; +        if (ctx->verbose) +            log_info("cell '%s' is of type '%s'\n", ci->name.c_str(ctx), ci->type.c_str(ctx)); +        if (is_lut(ctx, ci)) { +            std::unique_ptr<CellInfo> packed = create_machxo2_cell(ctx, id_FACADE_SLICE, ci->name.str(ctx) + "_LC"); +            std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(packed->attrs, packed->attrs.begin())); + +            packed_cells.insert(ci->name); +            if (ctx->verbose) +                log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx)); +            // See if we can pack into a DFF. Both LUT4 and FF outputs are +            // available for a given slice, so we can pack a FF even if the +            // LUT4 drives more than one FF. +            NetInfo *o = ci->ports.at(id_Z).net; +            CellInfo *dff = net_only_drives(ctx, o, is_ff, id_DI, false); +            auto lut_bel = ci->attrs.find(ctx->id("BEL")); +            bool packed_dff = false; + +            if (dff) { +                if (ctx->verbose) +                    log_info("found attached dff %s\n", dff->name.c_str(ctx)); +                auto dff_bel = dff->attrs.find(ctx->id("BEL")); +                if (lut_bel != ci->attrs.end() && dff_bel != dff->attrs.end() && lut_bel->second != dff_bel->second) { +                    // Locations don't match, can't pack +                } else { +                    lut_to_lc(ctx, ci, packed.get(), false); +                    dff_to_lc(ctx, dff, packed.get(), false); +                    if (dff_bel != dff->attrs.end()) +                        packed->attrs[ctx->id("BEL")] = dff_bel->second; +                    packed_cells.insert(dff->name); +                    if (ctx->verbose) +                        log_info("packed cell %s into %s\n", dff->name.c_str(ctx), packed->name.c_str(ctx)); +                    packed_dff = true; +                } +            } +            if (!packed_dff) { +                lut_to_lc(ctx, ci, packed.get(), true); +            } +            new_cells.push_back(std::move(packed)); +        } +    } + +    for (auto pcell : packed_cells) { +        ctx->cells.erase(pcell); +    } +    for (auto &ncell : new_cells) { +        ctx->cells[ncell->name] = std::move(ncell); +    } +} + +static void pack_remaining_ffs(Context *ctx) +{ +    log_info("Packing remaining FFs..\n"); + +    std::unordered_set<IdString> packed_cells; +    std::vector<std::unique_ptr<CellInfo>> new_cells; + +    for (auto cell : sorted(ctx->cells)) { +        CellInfo *ci = cell.second; + +        if (is_ff(ctx, ci)) { +            if (ctx->verbose) +                log_info("cell '%s' of type '%s remains unpacked'\n", ci->name.c_str(ctx), ci->type.c_str(ctx)); + +            std::unique_ptr<CellInfo> packed = create_machxo2_cell(ctx, id_FACADE_SLICE, ci->name.str(ctx) + "_LC"); +            std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(packed->attrs, packed->attrs.begin())); + +            auto dff_bel = ci->attrs.find(ctx->id("BEL")); +            dff_to_lc(ctx, ci, packed.get(), false); +            if (dff_bel != ci->attrs.end()) +                packed->attrs[ctx->id("BEL")] = dff_bel->second; +            packed_cells.insert(ci->name); +            if (ctx->verbose) +                log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx)); + +            new_cells.push_back(std::move(packed)); +        } +    } + +    for (auto pcell : packed_cells) { +        ctx->cells.erase(pcell); +    } +    for (auto &ncell : new_cells) { +        ctx->cells[ncell->name] = std::move(ncell); +    } +} + +// Merge a net into a constant net +static void set_net_constant(Context *ctx, NetInfo *orig, NetInfo *constnet, bool constval) +{ +    (void)constval; + +    std::unordered_set<IdString> packed_cells; +    std::vector<std::unique_ptr<CellInfo>> new_cells; + +    orig->driver.cell = nullptr; +    for (auto user : orig->users) { +        if (user.cell != nullptr) { +            CellInfo *uc = user.cell; +            if (ctx->verbose) +                log_info("%s user %s\n", orig->name.c_str(ctx), uc->name.c_str(ctx)); + +            if (uc->type == id_FACADE_FF && user.port == id_DI) { +                log_info("FACADE_FF %s is driven by a constant\n", uc->name.c_str(ctx)); + +                std::unique_ptr<CellInfo> lc = create_machxo2_cell(ctx, id_FACADE_SLICE, uc->name.str(ctx) + "_CONST"); +                std::copy(uc->attrs.begin(), uc->attrs.end(), std::inserter(lc->attrs, lc->attrs.begin())); + +                dff_to_lc(ctx, uc, lc.get(), true); +                packed_cells.insert(uc->name); + +                lc->ports[id_A0].net = constnet; +                user.cell = lc.get(); +                user.port = id_A0; + +                new_cells.push_back(std::move(lc)); +            } else { +                uc->ports[user.port].net = constnet; +            } + +            constnet->users.push_back(user); +        } +    } +    orig->users.clear(); + +    for (auto pcell : packed_cells) { +        ctx->cells.erase(pcell); +    } +    for (auto &ncell : new_cells) { +        ctx->cells[ncell->name] = std::move(ncell); +    } +} + +// Pack constants (based on simple implementation in generic). +// VCC/GND cells provided by nextpnr automatically. +static void pack_constants(Context *ctx) +{ +    log_info("Packing constants..\n"); + +    std::unique_ptr<CellInfo> const_cell = create_machxo2_cell(ctx, id_FACADE_SLICE, "$PACKER_CONST"); +    const_cell->params[id_LUT0_INITVAL] = Property(0, 16); +    const_cell->params[id_LUT1_INITVAL] = Property(0xFFFF, 16); + +    std::unique_ptr<NetInfo> gnd_net = std::unique_ptr<NetInfo>(new NetInfo); +    gnd_net->name = ctx->id("$PACKER_GND_NET"); +    gnd_net->driver.cell = const_cell.get(); +    gnd_net->driver.port = id_F0; +    const_cell->ports.at(id_F0).net = gnd_net.get(); + +    std::unique_ptr<NetInfo> vcc_net = std::unique_ptr<NetInfo>(new NetInfo); +    vcc_net->name = ctx->id("$PACKER_VCC_NET"); +    vcc_net->driver.cell = const_cell.get(); +    vcc_net->driver.port = id_F1; +    const_cell->ports.at(id_F1).net = vcc_net.get(); + +    std::vector<IdString> dead_nets; + +    for (auto net : sorted(ctx->nets)) { +        NetInfo *ni = net.second; +        if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("GND")) { +            IdString drv_cell = ni->driver.cell->name; +            set_net_constant(ctx, ni, gnd_net.get(), false); +            dead_nets.push_back(net.first); +            ctx->cells.erase(drv_cell); +        } else if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("VCC")) { +            IdString drv_cell = ni->driver.cell->name; +            set_net_constant(ctx, ni, vcc_net.get(), true); +            dead_nets.push_back(net.first); +            ctx->cells.erase(drv_cell); +        } +    } + +    ctx->cells[const_cell->name] = std::move(const_cell); +    ctx->nets[gnd_net->name] = std::move(gnd_net); +    ctx->nets[vcc_net->name] = std::move(vcc_net); + +    for (auto dn : dead_nets) { +        ctx->nets.erase(dn); +    } +} + +static bool is_nextpnr_iob(Context *ctx, CellInfo *cell) +{ +    return cell->type == ctx->id("$nextpnr_ibuf") || cell->type == ctx->id("$nextpnr_obuf") || +           cell->type == ctx->id("$nextpnr_iobuf"); +} + +static bool is_facade_iob(const Context *ctx, const CellInfo *cell) { return cell->type == id_FACADE_IO; } + +// Pack IO buffers- Right now, all this does is remove $nextpnr_[io]buf cells. +// User is expected to manually instantiate FACADE_IO with BEL/IO_TYPE +// attributes. +static void pack_io(Context *ctx) +{ +    std::unordered_set<IdString> packed_cells; + +    log_info("Packing IOs..\n"); + +    for (auto cell : sorted(ctx->cells)) { +        CellInfo *ci = cell.second; +        if (is_nextpnr_iob(ctx, ci)) { +            for (auto &p : ci->ports) +                disconnect_port(ctx, ci, p.first); +            packed_cells.insert(ci->name); +        } else if (is_facade_iob(ctx, ci)) { +            // If FACADE_IO has LOC attribute, convert the LOC (pin) to a BEL +            // attribute and place FACADE_IO at resulting BEL location. A BEL +            // attribute already on a FACADE_IO is an error. Attributes on +            // the pin attached to the PAD of FACADE_IO are ignored by this +            // packing phase. +            auto loc_attr_cell = ci->attrs.find(ctx->id("LOC")); +            auto bel_attr_cell = ci->attrs.find(ctx->id("BEL")); + +            if (loc_attr_cell != ci->attrs.end()) { +                if (bel_attr_cell != ci->attrs.end()) { +                    log_error("IO buffer %s has both a BEL attribute and LOC attribute.\n", ci->name.c_str(ctx)); +                } + +                log_info("found LOC attribute on IO buffer %s.\n", ci->name.c_str(ctx)); +                std::string pin = loc_attr_cell->second.as_string(); + +                BelId pinBel = ctx->getPackagePinBel(pin); +                if (pinBel == BelId()) { +                    log_error("IO buffer '%s' constrained to pin '%s', which does not exist for package '%s'.\n", +                              ci->name.c_str(ctx), pin.c_str(), ctx->args.package.c_str()); +                } else { +                    log_info("pin '%s' constrained to Bel '%s'.\n", ci->name.c_str(ctx), ctx->nameOfBel(pinBel)); +                } +                ci->attrs[ctx->id("BEL")] = ctx->getBelName(pinBel).str(ctx); +            } +        } +    } + +    for (auto pcell : packed_cells) { +        ctx->cells.erase(pcell); +    } +} + +// Main pack function +bool Arch::pack() +{ +    Context *ctx = getCtx(); +    try { +        log_break(); +        pack_constants(ctx); +        pack_io(ctx); +        pack_lut_lutffs(ctx); +        pack_remaining_ffs(ctx); +        ctx->settings[ctx->id("pack")] = 1; +        ctx->assignArchInfo(); +        log_info("Checksum: 0x%08x\n", ctx->checksum()); +        return true; +    } catch (log_execution_error_exception) { +        return false; +    } +} + +NEXTPNR_NAMESPACE_END  | 
