From 5564211981936134d50f0b1a06629d6e5aaa209e Mon Sep 17 00:00:00 2001 From: gatecat Date: Mon, 16 Oct 2023 14:06:41 +0200 Subject: [PATCH] himbaechel: Adding a xilinx uarch for xc7 with prjxray Signed-off-by: gatecat --- .gitmodules | 3 + himbaechel/arch.cc | 2 +- himbaechel/arch.h | 9 + himbaechel/family.cmake | 2 +- himbaechel/himbaechel_dbgen/chip.py | 13 +- himbaechel/uarch/xilinx/CMakeLists.txt | 37 + himbaechel/uarch/xilinx/cells.cc | 197 ++ himbaechel/uarch/xilinx/constids.inc | 740 ++++++++ himbaechel/uarch/xilinx/examples/.gitignore | 6 + .../uarch/xilinx/examples/arty-a35/arty.xdc | 52 + .../uarch/xilinx/examples/arty-a35/blinky.sh | 5 + .../uarch/xilinx/examples/arty-a35/blinky.v | 21 + .../uarch/xilinx/examples/bitgen_xray.sh | 34 + himbaechel/uarch/xilinx/extra_data.h | 107 ++ himbaechel/uarch/xilinx/fasm.cc | 1664 +++++++++++++++++ himbaechel/uarch/xilinx/gen/filters.py | 106 ++ himbaechel/uarch/xilinx/gen/parse_sdf.py | 134 ++ himbaechel/uarch/xilinx/gen/tileconn.py | 38 + himbaechel/uarch/xilinx/gen/xilinx_device.py | 544 ++++++ himbaechel/uarch/xilinx/gen/xilinx_gen.py | 371 ++++ himbaechel/uarch/xilinx/meta | 1 + himbaechel/uarch/xilinx/pack.cc | 750 ++++++++ himbaechel/uarch/xilinx/pack.h | 218 +++ himbaechel/uarch/xilinx/pack_carry.cc | 405 ++++ himbaechel/uarch/xilinx/pack_clocking.cc | 350 ++++ himbaechel/uarch/xilinx/pack_dram.cc | 477 +++++ himbaechel/uarch/xilinx/pack_io.cc | 497 +++++ himbaechel/uarch/xilinx/pins.cc | 480 +++++ himbaechel/uarch/xilinx/pins.h | 34 + himbaechel/uarch/xilinx/xdc.cc | 200 ++ himbaechel/uarch/xilinx/xilinx.cc | 557 ++++++ himbaechel/uarch/xilinx/xilinx.h | 180 ++ himbaechel/uarch/xilinx/xilinx_place.cc | 640 +++++++ 33 files changed, 8868 insertions(+), 6 deletions(-) create mode 100644 himbaechel/uarch/xilinx/CMakeLists.txt create mode 100644 himbaechel/uarch/xilinx/cells.cc create mode 100644 himbaechel/uarch/xilinx/constids.inc create mode 100644 himbaechel/uarch/xilinx/examples/.gitignore create mode 100644 himbaechel/uarch/xilinx/examples/arty-a35/arty.xdc create mode 100755 himbaechel/uarch/xilinx/examples/arty-a35/blinky.sh create mode 100644 himbaechel/uarch/xilinx/examples/arty-a35/blinky.v create mode 100755 himbaechel/uarch/xilinx/examples/bitgen_xray.sh create mode 100644 himbaechel/uarch/xilinx/extra_data.h create mode 100644 himbaechel/uarch/xilinx/fasm.cc create mode 100644 himbaechel/uarch/xilinx/gen/filters.py create mode 100644 himbaechel/uarch/xilinx/gen/parse_sdf.py create mode 100644 himbaechel/uarch/xilinx/gen/tileconn.py create mode 100644 himbaechel/uarch/xilinx/gen/xilinx_device.py create mode 100644 himbaechel/uarch/xilinx/gen/xilinx_gen.py create mode 160000 himbaechel/uarch/xilinx/meta create mode 100644 himbaechel/uarch/xilinx/pack.cc create mode 100644 himbaechel/uarch/xilinx/pack.h create mode 100644 himbaechel/uarch/xilinx/pack_carry.cc create mode 100644 himbaechel/uarch/xilinx/pack_clocking.cc create mode 100644 himbaechel/uarch/xilinx/pack_dram.cc create mode 100644 himbaechel/uarch/xilinx/pack_io.cc create mode 100644 himbaechel/uarch/xilinx/pins.cc create mode 100644 himbaechel/uarch/xilinx/pins.h create mode 100644 himbaechel/uarch/xilinx/xdc.cc create mode 100644 himbaechel/uarch/xilinx/xilinx.cc create mode 100644 himbaechel/uarch/xilinx/xilinx.h create mode 100644 himbaechel/uarch/xilinx/xilinx_place.cc diff --git a/.gitmodules b/.gitmodules index a22fbc419e..fde861f7ff 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "fpga-interchange-schema"] path = 3rdparty/fpga-interchange-schema url = https://github.com/SymbiFlow/fpga-interchange-schema.git +[submodule "himbaechel/uarch/xilinx/meta"] + path = himbaechel/uarch/xilinx/meta + url = https://github.com/gatecat/nextpnr-xilinx-meta diff --git a/himbaechel/arch.cc b/himbaechel/arch.cc index 1270934e02..44b79b620a 100644 --- a/himbaechel/arch.cc +++ b/himbaechel/arch.cc @@ -318,7 +318,7 @@ const std::string Arch::defaultPlacer = "heap"; const std::vector Arch::availablePlacers = {"sa", "heap"}; -const std::string Arch::defaultRouter = "router1"; +const std::string Arch::defaultRouter = "router2"; const std::vector Arch::availableRouters = {"router1", "router2"}; void Arch::set_fast_pip_delays(bool fast_mode) diff --git a/himbaechel/arch.h b/himbaechel/arch.h index 8485908677..5590ef9929 100644 --- a/himbaechel/arch.h +++ b/himbaechel/arch.h @@ -546,6 +546,15 @@ struct Arch : BaseArch // Scale delay (fF * mOhm -> ps) delay_t total_delay = (input_res * input_cap) / uint64_t(1e6); total_delay += pip_tmg->int_delay.slow_max; + + WireId dst = getPipDstWire(pip); + auto dst_tmg = get_node_timing(dst); + if (dst_tmg != nullptr) { + total_delay += + ((pip_tmg->out_res.slow_max + uint64_t(dst_tmg->res.slow_max) / 2) * dst_tmg->cap.slow_max) / + uint64_t(1e6); + } + return DelayQuad(total_delay); } else { // Pip with no specified delay. Return a notional value so the router still has something to work with. diff --git a/himbaechel/family.cmake b/himbaechel/family.cmake index cf4477e351..88a52b339a 100644 --- a/himbaechel/family.cmake +++ b/himbaechel/family.cmake @@ -1,4 +1,4 @@ -set(HIMBAECHEL_UARCHES "example;gowin") +set(HIMBAECHEL_UARCHES "example;gowin;xilinx") foreach(uarch ${HIMBAECHEL_UARCHES}) add_subdirectory(${family}/uarch/${uarch}) aux_source_directory(${family}/uarch/${uarch} HM_UARCH_FILES) diff --git a/himbaechel/himbaechel_dbgen/chip.py b/himbaechel/himbaechel_dbgen/chip.py index e3708107a7..26741a3162 100644 --- a/himbaechel/himbaechel_dbgen/chip.py +++ b/himbaechel/himbaechel_dbgen/chip.py @@ -301,6 +301,7 @@ class NodeShape(BBAStruct): def key(self): m = hashlib.md5() m.update(struct.pack("h"*len(self.wires), *self.wires)) + m.update(struct.pack("i", self.timing_index)) return m.digest() def serialise_lists(self, context: str, bba: BBAWriter): @@ -644,7 +645,11 @@ def set_pip_class(self, grade: str, name: str, delay: TimingValue, if idx >= len(sg.pip_classes): sg.pip_classes += [None for i in range(1 + idx - len(sg.pip_classes))] assert sg.pip_classes[idx] is None, f"attempting to set pip class {name} in speed grade {grade} twice" - sg.pip_classes[idx] = PipTiming(int_delay=delay, in_cap=in_cap, out_res=out_res, flags=(1 if is_buffered else 0)) + sg.pip_classes[idx] = PipTiming(int_delay=delay, + in_cap=in_cap or TimingValue(), + out_res=out_res or TimingValue(), + flags=(1 if is_buffered else 0) + ) def set_bel_pin_class(self, grade: str, name: str, delay: TimingValue, in_cap: Optional[TimingValue]=None, out_res: Optional[TimingValue]=None): @@ -658,7 +663,7 @@ def set_node_class(self, grade: str, name: str, delay: TimingValue, if idx >= len(sg.node_classes): sg.node_classes += [None for i in range(1 + idx - len(sg.node_classes))] assert sg.node_classes[idx] is None, f"attempting to set node class {name} in speed grade {grade} twice" - sg.node_classes[idx] = NodeTiming(delay=delay, res=res, cap=cap) + sg.node_classes[idx] = NodeTiming(delay=delay, res=res or TimingValue(), cap=cap or TimingValue()) def add_cell_variant(self, speed_grade: str, name: str): cell = CellTiming(self.strs, name) @@ -700,13 +705,13 @@ def tile_type_at(self, x: int, y: int): def set_speed_grades(self, speed_grades: list): self.timing.set_speed_grades(speed_grades) return self.timing - def add_node(self, wires: list[NodeWire]): + def add_node(self, wires: list[NodeWire], timing_class=""): # add a node - joining between multiple tile wires into a single connection (from nextpnr's point of view) # all the tile wires must exist, and the tile types must be set, first x0 = wires[0].x y0 = wires[0].y # compute node shape - shape = NodeShape() + shape = NodeShape(timing_index=self.timing.node_class_idx(timing_class)) for w in wires: if isinstance(w.wire, int): wire_index = w.wire diff --git a/himbaechel/uarch/xilinx/CMakeLists.txt b/himbaechel/uarch/xilinx/CMakeLists.txt new file mode 100644 index 0000000000..62d68f0513 --- /dev/null +++ b/himbaechel/uarch/xilinx/CMakeLists.txt @@ -0,0 +1,37 @@ +message(STATUS "Configuring Xilinx uarch") +cmake_minimum_required(VERSION 3.5) +project(himbaechel-xilinx-chipdb NONE) + +set(HIMBAECHEL_XILINX_DEVICES "" CACHE STRING + "Include support for these Xilinx devices via himbaechel") +set(HIMBAECHEL_PRJXRAY_DB "" CACHE STRING + "Path to a project x-ray database") +message(STATUS "Enabled Himbaechel-Xilinx devices: ${HIMBAECHEL_XILINX_DEVICES}") + + +set(chipdb_binaries) +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/share/himbaechel/xilinx) +foreach(device ${HIMBAECHEL_XILINX_DEVICES}) + if("${HIMBAECHEL_PRJXRAY_DB}" STREQUAL "") + message(SEND_ERROR "HIMBAECHEL_PRJXRAY_DB must be set to a prjxray database checkout") + endif() + + set(device_bba ${CMAKE_BINARY_DIR}/share/himbaechel/xilinx/chipdb-${device}.bba) + set(device_bin ${CMAKE_BINARY_DIR}/share/himbaechel/xilinx/chipdb-${device}.bin) + add_custom_command( + OUTPUT ${device_bin} + COMMAND pypy3 ${CMAKE_CURRENT_SOURCE_DIR}/gen/xilinx_gen.py --xray ${HIMBAECHEL_PRJXRAY_DB}/artix7 --device ${device} --bba ${device_bba} + COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${device_bba} ${device_bin}.new + # atomically update + COMMAND ${CMAKE_COMMAND} -E rename ${device_bin}.new ${device_bin} + DEPENDS + bbasm + ${CMAKE_CURRENT_SOURCE_DIR}/gen/xilinx_gen.py + ${CMAKE_CURRENT_SOURCE_DIR}/constids.inc + VERBATIM) + list(APPEND chipdb_binaries ${device_bin}) +endforeach() + +add_custom_target(chipdb-himbaechel-xilinx ALL DEPENDS ${chipdb_binaries}) +install(DIRECTORY ${CMAKE_BINARY_DIR}/share/himbaechel/xilinx/ DESTINATION share/nextpnr/himbaechel/xilinx + PATTERN "*.bba" EXCLUDE) diff --git a/himbaechel/uarch/xilinx/cells.cc b/himbaechel/uarch/xilinx/cells.cc new file mode 100644 index 0000000000..218d1f4877 --- /dev/null +++ b/himbaechel/uarch/xilinx/cells.cc @@ -0,0 +1,197 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2023 Myrtle Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "nextpnr.h" +#include "pack.h" +#include "pins.h" + +#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" +#include "himbaechel_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +CellInfo *XilinxPacker::create_cell(IdString type, IdString name) +{ + CellInfo *cell = ctx->createCell(name, type); + + auto add_port = [&](const std::string &name, PortType dir) { + IdString id = ctx->id(name); + cell->ports[id].name = id; + cell->ports[id].type = dir; + }; + if (type == id_SLICE_LUTX) { + for (int i = 1; i <= 6; i++) + add_port("A" + std::to_string(i), PORT_IN); + for (int i = 1; i <= 9; i++) + add_port("WA" + std::to_string(i), PORT_IN); + add_port("DI1", PORT_IN); + add_port("DI2", PORT_IN); + add_port("CLK", PORT_IN); + add_port("WE", PORT_IN); + add_port("SIN", PORT_IN); + add_port("O5", PORT_OUT); + add_port("O6", PORT_OUT); + add_port("MC31", PORT_OUT); + } else if (type == id_SLICE_FFX) { + add_port("D", PORT_IN); + add_port("SR", PORT_IN); + add_port("CE", PORT_IN); + add_port("CLK", PORT_IN); + add_port("Q", PORT_OUT); + } else if (type == id_RAMD64E) { + for (int i = 0; i < 6; i++) + add_port("RADR" + std::to_string(i), PORT_IN); + for (int i = 0; i < 8; i++) + add_port("WADR" + std::to_string(i), PORT_IN); + add_port("CLK", PORT_IN); + add_port("I", PORT_IN); + add_port("WE", PORT_IN); + add_port("O", PORT_OUT); + } else if (type == id_RAMD32) { + for (int i = 0; i < 5; i++) + add_port("RADR" + std::to_string(i), PORT_IN); + for (int i = 0; i < 5; i++) + add_port("WADR" + std::to_string(i), PORT_IN); + add_port("CLK", PORT_IN); + add_port("I", PORT_IN); + add_port("WE", PORT_IN); + add_port("O", PORT_OUT); + } else if (type.in(id_MUXF7, id_MUXF8, id_MUXF9)) { + add_port("I0", PORT_IN); + add_port("I1", PORT_IN); + add_port("S", PORT_IN); + add_port("O", PORT_OUT); + } else if (type == id_CARRY8) { + add_port("CI", PORT_IN); + add_port("CI_TOP", PORT_IN); + + for (int i = 0; i < 8; i++) { + add_port("DI[" + std::to_string(i) + "]", PORT_IN); + add_port("S[" + std::to_string(i) + "]", PORT_IN); + add_port("CO[" + std::to_string(i) + "]", PORT_OUT); + add_port("O[" + std::to_string(i) + "]", PORT_OUT); + } + } else if (type == id_MUXCY) { + add_port("CI", PORT_IN); + add_port("DI", PORT_IN); + add_port("S", PORT_IN); + add_port("O", PORT_OUT); + } else if (type == id_XORCY) { + add_port("CI", PORT_IN); + add_port("LI", PORT_IN); + add_port("O", PORT_OUT); + } else if (type == id_PAD) { + add_port("PAD", PORT_INOUT); + } else if (type == id_INBUF) { + add_port("VREF", PORT_IN); + add_port("PAD", PORT_IN); + add_port("OSC_EN", PORT_IN); + for (int i = 0; i < 4; i++) + add_port("OSC[" + std::to_string(i) + "]", PORT_IN); + add_port("O", PORT_OUT); + } else if (type == id_IBUFCTRL) { + add_port("I", PORT_IN); + add_port("IBUFDISABLE", PORT_IN); + add_port("T", PORT_IN); + add_port("O", PORT_OUT); + } else if (type.in(id_OBUF, id_IBUF)) { + add_port("I", PORT_IN); + add_port("O", PORT_OUT); + } else if (type == id_OBUFT) { + add_port("I", PORT_IN); + add_port("T", PORT_IN); + add_port("O", PORT_OUT); + } else if (type == id_IOBUF) { + add_port("I", PORT_IN); + add_port("T", PORT_IN); + add_port("O", PORT_OUT); + add_port("IO", PORT_INOUT); + } else if (type == id_OBUFT_DCIEN) { + add_port("I", PORT_IN); + add_port("T", PORT_IN); + add_port("DCITERMDISABLE", PORT_IN); + add_port("O", PORT_OUT); + } else if (type == id_DIFFINBUF) { + add_port("DIFF_IN_P", PORT_IN); + add_port("DIFF_IN_N", PORT_IN); + add_port("OSC_EN[0]", PORT_IN); + add_port("OSC_EN[1]", PORT_IN); + for (int i = 0; i < 4; i++) + add_port("OSC[" + std::to_string(i) + "]", PORT_IN); + add_port("VREF", PORT_IN); + add_port("O", PORT_OUT); + add_port("O_B", PORT_OUT); + } else if (type == id_HPIO_VREF) { + for (int i = 0; i < 7; i++) + add_port("FABRIC_VREF_TUNE[" + std::to_string(i) + "]", PORT_IN); + add_port("VREF", PORT_OUT); + } else if (type == id_INV) { + add_port("I", PORT_IN); + add_port("O", PORT_OUT); + } else if (type == id_IDELAYCTRL) { + add_port("REFCLK", PORT_IN); + add_port("RST", PORT_IN); + add_port("RDY", PORT_OUT); + } else if (type == id_IBUF) { + add_port("I", PORT_IN); + add_port("O", PORT_OUT); + } else if (type == id_IBUF_INTERMDISABLE) { + add_port("I", PORT_IN); + add_port("IBUFDISABLE", PORT_IN); + add_port("INTERMDISABLE", PORT_IN); + add_port("O", PORT_OUT); + } else if (type == id_IBUFDS) { + add_port("I", PORT_IN); + add_port("IB", PORT_IN); + add_port("O", PORT_OUT); + } else if (type == id_IBUFDS_INTERMDISABLE_INT) { + add_port("I", PORT_IN); + add_port("IB", PORT_IN); + add_port("IBUFDISABLE", PORT_IN); + add_port("INTERMDISABLE", PORT_IN); + add_port("O", PORT_OUT); + } else if (type == id_CARRY4) { + add_port("CI", PORT_IN); + add_port("CYINIT", PORT_IN); + for (int i = 0; i < 4; i++) { + add_port("DI[" + std::to_string(i) + "]", PORT_IN); + add_port("S[" + std::to_string(i) + "]", PORT_IN); + add_port("CO[" + std::to_string(i) + "]", PORT_OUT); + add_port("O[" + std::to_string(i) + "]", PORT_OUT); + } + } + return cell; +} + +CellInfo *XilinxPacker::create_lut(const std::string &name, const std::vector &inputs, NetInfo *output, + const Property &init) +{ + CellInfo *cell = ctx->createCell(ctx->id(name), ctx->idf("LUT%d", int(inputs.size()))); + for (size_t i = 0; i < inputs.size(); i++) { + IdString ip = ctx->idf("I%d", int(i)); + cell->addInput(ip); + cell->connectPort(ip, inputs.at(i)); + } + cell->addOutput(id_O); + cell->connectPort(id_O, output); + cell->params[id_INIT] = init; + return cell; +} + +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/xilinx/constids.inc b/himbaechel/uarch/xilinx/constids.inc new file mode 100644 index 0000000000..d4db03b2da --- /dev/null +++ b/himbaechel/uarch/xilinx/constids.inc @@ -0,0 +1,740 @@ +X(SLICE_LUTX) +X(SLICE_FFX) +X(F7MUX) +X(F8MUX) +X(F9MUX) +X(CARRY4) +X(CARRY8) +X(CLEL_L) +X(CLEL_R) +X(CLEM) +X(CLEM_R) +X(CLBLL_L) +X(CLBLL_R) +X(CLBLM_L) +X(CLBLM_R) + +X(A1) +X(A2) +X(A3) +X(A4) +X(A5) +X(A6) +X(CLK) +X(DI1) +X(DI2) +X(SIN) +X(WA1) +X(WA2) +X(WA3) +X(WA4) +X(WA5) +X(WA6) +X(WA7) +X(WA8) +X(WA9) +X(WE) +X(MC31) +X(O5) +X(O6) + +X(CE) +X(CK) +X(D) +X(SR) +X(Q) + +X(AX) +X(BX) +X(CIN) +X(CX) +X(DI0) +X(DI3) +X(DI4) +X(DI5) +X(DI6) +X(DI7) +X(DX) +X(EX) +X(FX) +X(HX) +X(S0) +X(S1) +X(S2) +X(S3) +X(S4) +X(S5) +X(S6) +X(S7) +X(CO0) +X(CO1) +X(CO2) +X(CO3) +X(CO4) +X(CO5) +X(CO6) +X(CO7) +X(O0) +X(O1) +X(O2) +X(O3) +X(O4) +X(O7) + +X(OUT) + +X(PSEUDO_GND) +X(PSEUDO_VCC) +X(HARD0) +X(Y) + +X(BRAM) +X(RAMBFIFO36) +X(RAMBFIFO18) + +X(RAMBFIFO18E2_RAMBFIFO18E2) +X(RAMB18E2_RAMB18E2) +X(FIFO18E2_FIFO18E2) + +X(RAMBFIFO36E2_RAMBFIFO36E2) +X(RAMB36E2_RAMB36E2) +X(FIFO36E2_FIFO36E2) + +X(BRAM_L) +X(BRAM_R) + +X(RAMB18E1_RAMB18E1) +X(FIFO18E1_FIFO18E1) + +X(RAMBFIFO36E1_RAMBFIFO36E1) +X(RAMB36E1_RAMB36E1) +X(FIFO36E1_FIFO36E1) + +X(URAM288) +X(BEL_URAM288) +X(URAM_URAM_FT) +X(URAM_URAM_DELAY_FT) + +X(DSP) +X(DSP48E2) +X(DSP_PREADD) +X(DSP_PREADD_DATA) +X(DSP_A_B_DATA) +X(DSP_MULTIPLIER) +X(DSP_M_DATA) +X(DSP_ALU) +X(DSP_OUTPUT) +X(DSP_C_DATA) + +X(DSP_L) +X(DSP_R) +X(DSP48E1) +X(DSP48E1_DSP48E1) + +X(HPIO_L) +X(XIPHY_BYTE_L) + +X(BITSLICE_COMPONENT_RX_TX) +X(OUT_FF) +X(ODELAYE3) +X(OSERDESE3) +X(IDELAYE3) +X(ISERDESE3) +X(IN_FF) + +X(LIOB33) +X(RIOB33) + +X(IOB33M) +X(IOB33S) +X(IOB33) +X(IPAD) + +X(PAD) +X(IOB33M_OUTBUF) +X(IOB33S_OUTBUF) +X(IOB33_OUTBUF) +X(IOB33M_INBUF_EN) +X(IOB33S_INBUF_EN) +X(IOB33_INBUF_EN) +X(IOB33M_TERM_OVERRIDE) +X(IOB33S_TERM_OVERRIDE) +X(IOB33_TERM_OVERRIDE) +X(PULL_OR_KEEP1) + +X(IDELAYE2) +X(IDELAYE2_IDELAYE2) +X(OLOGICE3) +X(OLOGICE3_TFF) +X(OLOGICE3_OUTFF) +X(OLOGICE3_MISR) +X(OSERDESE2) +X(OSERDESE2_OSERDESE2) + +X(ILOGICE3) +X(ILOGICE3_IFF) +X(ILOGICE3_ZHOLD_DELAY) +X(ISERDESE2) +X(ISERDESE2_ISERDESE2) + +X(BUFR_BUFR) +X(BUFIO_BUFIO) +X(IDELAYCTRL_IDELAYCTRL) + +X(PLL_SELECT_BEL) + +X(PLL) +X(PLL_PLL_TOP) +X(MMCM) +X(MMCM_MMCM_TOP) +X(BUFGCE) +X(BUFGCE_DIV) +X(BUFCE_BUFG_PS) +X(CMT_L) + +X(INTENT_DEFAULT) +X(NODE_OUTPUT) +X(NODE_DEDICATED) +X(NODE_GLOBAL_VDISTR) +X(NODE_GLOBAL_HROUTE) +X(NODE_GLOBAL_HDISTR) +X(NODE_PINFEED) +X(NODE_PINBOUNCE) +X(NODE_LOCAL) +X(NODE_HLONG) +X(NODE_SINGLE) +X(NODE_DOUBLE) +X(NODE_HQUAD) +X(NODE_VLONG) +X(NODE_VQUAD) +X(NODE_OPTDELAY) +X(NODE_GLOBAL_VROUTE) +X(NODE_GLOBAL_LEAF) +X(NODE_GLOBAL_BUFG) + +X(NODE_LAGUNA_DATA) +X(NODE_CLE_OUTPUT) +X(NODE_INT_INTERFACE) +X(NODE_LAGUNA_OUTPUT) + +X(BENTQUAD) +X(BOUNCEACROSS) +X(BOUNCEIN) +X(BUFGROUT) +X(BUFINP2OUT) +X(CLKPIN) +X(DOUBLE) +X(GENERIC) +X(GLOBAL) +X(HLONG) +X(HQUAD) +X(HVCCGNDOUT) +X(INPUT) +X(IOBIN2OUT) +X(IOBINPUT) +X(IOBOUTPUT) +X(LUTINPUT) +X(OPTDELAY) +X(OUTBOUND) +X(OUTPUT) +X(PADINPUT) +X(PADOUTPUT) +X(PINBOUNCE) +X(PINFEED) +X(PINFEEDR) +X(REFCLK) +X(SINGLE) +X(SLOWSINGLE) +X(SVLONG) +X(VLONG) +X(VLONG12) +X(VQUAD) + +X(INTENT_SITE_WIRE) +X(INTENT_SITE_GND) + +X(IOB_IBUFCTRL) +X(IOB_INBUF) +X(IOB_OUTBUF) +X(IOB_PAD) +X(TRIBUF) + +X(BUFGCTRL) +X(BUFGCE_DIV_BUFGCE_DIV) +X(BUFCE_BUFCE) + +X(PS7) +X(PS7_PS7) + +X(0) +X(1) +X(A) +X(A0) +X(ADDRARDADDRL15) +X(ADDRATIEHIGH0) +X(ADDRATIEHIGH1) +X(ADDRBTIEHIGH0) +X(ADDRBTIEHIGH1) +X(ADDRBWRADDRL15) +X(AREG) +X(BEL) +X(BITSLICE_CONTROL_BEL) +X(BREG) +X(BUFG) +X(BUFG_BUFG) +X(BUFG_PS) +X(BUFHCE) +X(BUFMRCE) +X(C) +X(CARRYIN) +X(CARRY_TYPE) +X(CB) +X(CDDCREQ) +X(CE0) +X(CE1) +X(CEA1) +X(CEA2) +X(CEAD) +X(CEALUMODE) +X(CEB1) +X(CEB2) +X(CEC) +X(CECARRYIN) +X(CECTRL) +X(CED) +X(CEINMODE) +X(CEM) +X(CEP) +X(CFGLUT5) +X(CI) +X(CINVCTRL) +X(CINVCTRL_SEL) +X(CKB) +X(CK_C) +X(CLKARDCLK) +X(CLKB) +X(CLKBWRCLK) +X(CLKDIV) +X(CLKDIVP) +X(CLKFBIN) +X(CLKIN) +X(CLKIN1) +X(CLKIN2) +X(CLKINSEL) +X(CLKRSVD0) +X(CLKRSVD1) +X(CLK_B) +X(CLK_EXT) +X(CLR) +X(COMPENSATION) +X(CONVSTCLK) +X(CPLLLOCKDETCLK) +X(CYINIT) +X(D1) +X(D2) +X(D3) +X(D4) +X(D5) +X(D6) +X(D7) +X(D8) +X(DATAOUT) +X(DATA_RATE) +X(DATA_RATE_OQ) +X(DATA_RATE_TQ) +X(DATA_WIDTH) +X(DCITERMDISABLE) +X(DCLK) +X(DDLY) +X(DDR_CLK_EDGE) +X(DELAY_SRC) +X(DEN) +X(DI) +X(DIADI0) +X(DIADI1) +X(DIBDI0) +X(DIBDI1) +X(DIFFINBUF) +X(DIFFI_IN) +X(DIFF_IN_N) +X(DIFF_IN_P) +X(DIPADIP0) +X(DIPADIP1) +X(DIPBDIP0) +X(DIPBDIP1) +X(DLY_TEST_IN) +X(DMONITORCLK) +X(DOA_REG) +X(DOB_REG) +X(DPO) +X(DRIVE) +X(DRPCLK) +X(DWE) +X(E) +X(ECCPIPECE) +X(ECCPIPECEL) +X(ENARDEN) +X(ENBWREN) +X(EN_A) +X(EN_B) +X(EN_VTC) +X(FDCE) +X(FDCE_1) +X(FDPE) +X(FDPE_1) +X(FDRE) +X(FDRE_1) +X(FDSE) +X(FDSE_1) +X(FIFO18E1) +X(FIFO18E2) +X(FIFO36E1) +X(FIFO36E2) +X(G) +X(GND) +X(GTGREFCLK) +X(GTGREFCLK0) +X(GTGREFCLK1) +X(GTHE2_CHANNEL) +X(GTHE2_COMMON) +X(GTPE2_CHANNEL) +X(GTPE2_COMMON) +X(GTXE2_CHANNEL) +X(GTXE2_COMMON) +X(HARD_SYNC) +X(HCLK_IOI) +X(HCLK_IOI3) +X(HIGH_PERFORMANCE_MODE) +X(HPIO_OUTINV) +X(HPIO_RIGHT) +X(HPIO_VREF) +X(I) +X(I0) +X(I1) +X(I2) +X(I3) +X(I4) +X(I5) +X(IB) +X(IBUF) +X(IBUFCTRL) +X(IBUFDISABLE) +X(IBUFDS) +X(IBUFDSE3) +X(IBUFDS_DIFF_OUT) +X(IBUFDS_DIFF_OUT_IBUFDISABLE) +X(IBUFDS_DIFF_OUT_INTERMDISABLE) +X(IBUFDS_GTE3) +X(IBUFDS_GTE4) +X(IBUFDS_INTERMDISABLE) +X(IBUFDS_INTERMDISABLE_INT) +X(IBUFE3) +X(IBUF_ANALOG) +X(IBUF_IBUFDISABLE) +X(IBUF_INTERMDISABLE) +X(IDATAIN) +X(IDDR) +X(IDDRE1) +X(IDDR_2CLK) +X(IDELAYCTRL) +X(IDELAY_TYPE) +X(IDELAY_VALUE) +X(IFD_CE) +X(IGNORE0) +X(IGNORE1) +X(IN) +X(INBUF) +X(INIT) +X(INIT_OQ) +X(INIT_OUT) +X(INIT_Q1) +X(INIT_Q2) +X(INIT_TQ) +X(INJECTDBITERR) +X(INJECTSBITERR) +X(INTENT) +X(INTERFACE_TYPE) +X(INTERMDISABLE) +X(INV) +X(INVERTER) +X(IN_TERM) +X(IO) +X(IOB) +X(IOB18M_OUTBUF_DCIEN) +X(IOB18_INBUF_DCIEN) +X(IOB18_OUTBUF_DCIEN) +X(IOBDELAY) +X(IOBUF) +X(IOBUFDS) +X(IOBUFDSE3) +X(IOBUFDS_DCIEN) +X(IOBUFDS_DIFF_OUT) +X(IOBUFDS_DIFF_OUT_DCIEN) +X(IOBUFDS_DIFF_OUT_INTERMDISABLE) +X(IOBUFE3) +X(IOBUF_DCIEN) +X(IOBUF_INTERMDISABLE) +X(IOB_DIFFINBUF) +X(IOB_DIFFI_IN0) +X(IOB_O0) +X(IOB_O_IN1) +X(IOB_O_OUT0) +X(IOB_PADOUT1) +X(IOB_T0) +X(IOB_T_IN1) +X(IOB_T_OUT0) +X(IOL_IDDR) +X(IOL_OPTFF) +X(IOSTANDARD) +X(IS_CARRYIN_INVERTED) +X(IS_CE0_INVERTED) +X(IS_CE1_INVERTED) +X(IS_CLKINSEL_INVERTED) +X(IS_CLK_INVERTED) +X(IS_CLR_INVERTED) +X(IS_C_INVERTED) +X(IS_DATAIN_INVERTED) +X(IS_D_INVERTED) +X(IS_IDATAIN_INVERTED) +X(IS_IGNORE0_INVERTED) +X(IS_IGNORE1_INVERTED) +X(IS_OCLK_INVERTED) +X(IS_ODATAIN_INVERTED) +X(IS_PRE_INVERTED) +X(IS_PWRDWN_INVERTED) +X(IS_RST_INVERTED) +X(IS_R_INVERTED) +X(IS_S0_INVERTED) +X(IS_S1_INVERTED) +X(IS_SR_INVERTED) +X(IS_S_INVERTED) +X(IS_WCLK_INVERTED) +X(LDCE) +X(LDPE) +X(LDPIPEEN) +X(LI) +X(LOAD) +X(LOC) +X(LUT1) +X(LUT2) +X(LUT3) +X(LUT4) +X(LUT5) +X(LUT6) +X(LUT6_2) +X(LUT_OR_MEM5LRAM) +X(LUT_OR_MEM6LRAM) +X(MMCME2_ADV) +X(MMCME2_ADV_MMCME2_ADV) +X(MMCME2_BASE) +X(MMCME3_ADV) +X(MMCME3_BASE) +X(MMCME4_ADV) +X(MMCME4_BASIC) +X(MUXCY) +X(MUXF7) +X(MUXF8) +X(MUXF9) +X(MUX_TREE_ROOT) +X(NUM_CE) +X(O) +X(OB) +X(OBUF) +X(OBUFDS) +X(OBUFDS_GTE3) +X(OBUFDS_GTE3_ADV) +X(OBUFDS_GTE4) +X(OBUFDS_GTE4_ADV) +X(OBUFT) +X(OBUFTDS) +X(OBUFT_DCIEN) +X(OCE) +X(OCLK) +X(OCLKB) +X(ODATAIN) +X(ODDR) +X(ODDRE1) +X(ODDR_MODE) +X(ODELAYE2) +X(ODELAYE2_ODELAYE2) +X(ODELAY_TYPE) +X(ODELAY_VALUE) +X(OFB) +X(OFD_CE) +X(OLOGICE2_TFF) +X(OLOGICE2_OUTFF) +X(OQ) +X(OR2L) +X(OSC_EN) +X(OSERDES_T_BYPASS) +X(O_B) +X(PACKAGE_PIN) +X(PHASER_IN) +X(PHASER_IN_PHY) +X(PHASER_OUT) +X(PHASER_OUT_PHY) +X(PHASER_REF) +X(PIP) +X(PIPE_SEL) +X(PLL0LOCKDETCLK) +X(PLL1LOCKDETCLK) +X(PLLE2_ADV) +X(PLLE2_ADV_PLLE2_ADV) +X(PLLE2_BASE) +X(PLLE3_ADV) +X(PLLE3_BASE) +X(PLLE4_ADV) +X(PLLE4_BASIC) +X(PRE) +X(PS8) +X(PSCLK) +X(PSEN) +X(PSEUDO_GND_WIRE_GLBL) +X(PSEUDO_GND_WIRE_ROW) +X(PSEUDO_VCC_WIRE_GLBL) +X(PSEUDO_VCC_WIRE_ROW) +X(PSINCDEC) +X(PSS_ALTO_CORE) +X(PULLTYPE) +X(PWRDWN) +X(Q0) +X(Q1) +X(Q2) +X(QPLLLOCKDETCLK) +X(R) +X(RADR0) +X(RADR1) +X(RADR2) +X(RADR3) +X(RADR4) +X(RADR5) +X(RAM128X1D) +X(RAM128X1S) +X(RAM256X1D) +X(RAM256X1S) +X(RAM32M) +X(RAM32M16) +X(RAM32X1D) +X(RAM32X1S) +X(RAM32X2S) +X(RAM512X1D) +X(RAM512X1S) +X(RAM64M) +X(RAM64M8) +X(RAM64X1D) +X(RAM64X1S) +X(RAM64X8SW) +X(RAMB18E1) +X(RAMB18E2) +X(RAMB36E1) +X(RAMB36E2) +X(RAMD32) +X(RAMD64E) +X(RDB_WR_A) +X(RDB_WR_B) +X(RDCLK) +X(RDEN) +X(RDY) +X(REGCEAREGCE) +X(REGCEB) +X(REGRST) +X(RIU_NIBBLE_SEL) +X(RST) +X(RSTA) +X(RSTALLCARRYIN) +X(RSTALUMODE) +X(RSTB) +X(RSTC) +X(RSTCTRL) +X(RSTD) +X(RSTINMODE) +X(RSTM) +X(RSTP) +X(RSTRAMARSTRAM) +X(RSTRAMB) +X(RSTREG) +X(RSTREGARSTREG) +X(RSTREGB) +X(RST_A) +X(RST_B) +X(RST_DLY) +X(RST_DLY_EXT) +X(RXTX_BITSLICE) +X(RXUSRCLK) +X(RXUSRCLK2) +X(RX_BITSLICE) +X(RX_CLK) +X(RX_RST) +X(RX_RST_DLY) +X(S) +X(SELMUX2_1) +X(SERDES_MODE) +X(SIGVALIDCLK) +X(SLEEP) +X(SLEW) +X(SPO) +X(SRI) +X(SRL16E) +X(SRLC32E) +X(SRTYPE) +X(SRVAL_OQ) +X(SRVAL_TQ) +X(SYSMONE1) +X(SYSMONE4) +X(T) +X(T1) +X(T2) +X(T3) +X(T4) +X(TBYTE_IN0) +X(TBYTE_IN1) +X(TBYTE_IN2) +X(TBYTE_IN3) +X(TCE) +X(TQ) +X(TRI) +X(TXPHDLYTSTCLK) +X(TXUSRCLK) +X(TXUSRCLK2) +X(TX_BITSLICE) +X(TX_BITSLICE_TRI) +X(TX_BIT_CTRL_OUT0) +X(TX_CLK) +X(TX_RST) +X(TX_RST_DLY) +X(T_OUT) +X(URAM288_BASE) +X(USE_DPORT) +X(VCC) +X(VREF) +X(VTC_RDY) +X(WCLK) +X(WEA0) +X(WEA1) +X(WEA2) +X(WEA3) +X(WRCLK) +X(WREN) +X(WRITE_WIDTH_A) +X(WRITE_WIDTH_B) +X(XADC) +X(XORCY) +X(X_FFSYNC) +X(X_FF_AS_LATCH) +X(X_IOB_SITE_TYPE) +X(X_IO_BEL) +X(X_IO_DIR) +X(X_LUT_AS_DRAM) +X(X_LUT_AS_SRL) +X(X_ORIG_MACRO_PRIM) +X(X_ORIG_PORT_DIADI1) +X(X_ORIG_PORT_DIBDI1) +X(X_ORIG_PORT_O5) +X(X_ORIG_PORT_O6) +X(X_ORIG_PORT_SR) +X(X_ORIG_TYPE) +X(placer) +X(route) +X(router) +X(step) +X(xilinx) diff --git a/himbaechel/uarch/xilinx/examples/.gitignore b/himbaechel/uarch/xilinx/examples/.gitignore new file mode 100644 index 0000000000..f0927398d7 --- /dev/null +++ b/himbaechel/uarch/xilinx/examples/.gitignore @@ -0,0 +1,6 @@ +*.bin +*.json +*.log +*.frames +*.fasm +abc.history diff --git a/himbaechel/uarch/xilinx/examples/arty-a35/arty.xdc b/himbaechel/uarch/xilinx/examples/arty-a35/arty.xdc new file mode 100644 index 0000000000..cdfbb57003 --- /dev/null +++ b/himbaechel/uarch/xilinx/examples/arty-a35/arty.xdc @@ -0,0 +1,52 @@ +# R +set_property LOC G6 [get_ports led[0]] +set_property LOC G3 [get_ports led[1]] +set_property LOC J3 [get_ports led[2]] +set_property LOC K1 [get_ports led[3]] +# G +set_property LOC F6 [get_ports led[4]] +set_property LOC J4 [get_ports led[5]] +set_property LOC J2 [get_ports led[6]] +set_property LOC H6 [get_ports led[7]] +# B +set_property LOC E1 [get_ports led[8]] +set_property LOC G4 [get_ports led[9]] +set_property LOC H4 [get_ports led[10]] +set_property LOC K2 [get_ports led[11]] + +# second row +# set_property LOC H5 [get_ports led[12]] +# set_property LOC J5 [get_ports led[13]] +# set_property LOC T9 [get_ports led[14]] +# set_property LOC T10 [get_ports led[15]] + +set_property IOSTANDARD LVCMOS33 [get_ports led[0]] +set_property IOSTANDARD LVCMOS33 [get_ports led[1]] +set_property IOSTANDARD LVCMOS33 [get_ports led[2]] +set_property IOSTANDARD LVCMOS33 [get_ports led[3]] +set_property IOSTANDARD LVCMOS33 [get_ports led[4]] +set_property IOSTANDARD LVCMOS33 [get_ports led[5]] +set_property IOSTANDARD LVCMOS33 [get_ports led[6]] +set_property IOSTANDARD LVCMOS33 [get_ports led[7]] +set_property IOSTANDARD LVCMOS33 [get_ports led[8]] +set_property IOSTANDARD LVCMOS33 [get_ports led[9]] +set_property IOSTANDARD LVCMOS33 [get_ports led[10]] +set_property IOSTANDARD LVCMOS33 [get_ports led[11]] +set_property IOSTANDARD LVCMOS33 [get_ports led[12]] +set_property IOSTANDARD LVCMOS33 [get_ports led[13]] +set_property IOSTANDARD LVCMOS33 [get_ports led[14]] +set_property IOSTANDARD LVCMOS33 [get_ports led[15]] + + +set_property LOC A8 [get_ports sw[0]] +set_property LOC C11 [get_ports sw[1]] +set_property LOC C10 [get_ports sw[2]] +set_property LOC A10 [get_ports sw[3]] + +set_property IOSTANDARD LVCMOS33 [get_ports sw[0]] +set_property IOSTANDARD LVCMOS33 [get_ports sw[1]] +set_property IOSTANDARD LVCMOS33 [get_ports sw[2]] +set_property IOSTANDARD LVCMOS33 [get_ports sw[3]] + +set_property LOC E3 [get_ports clk] +set_property IOSTANDARD LVCMOS33 [get_ports clk] diff --git a/himbaechel/uarch/xilinx/examples/arty-a35/blinky.sh b/himbaechel/uarch/xilinx/examples/arty-a35/blinky.sh new file mode 100755 index 0000000000..96f61b7a77 --- /dev/null +++ b/himbaechel/uarch/xilinx/examples/arty-a35/blinky.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -ex +yosys -p "synth_xilinx -flatten -abc9 -nobram -arch xc7 -top top; write_json blinky.json" blinky.v +nextpnr-himbaechel --device xc7a35tcsg324-1 -o xdc=arty.xdc --json blinky.json -o fasm=blinky.fasm +../bitgen_xray.sh xc7a35tcsg324-1 blinky.fasm blinky.bit diff --git a/himbaechel/uarch/xilinx/examples/arty-a35/blinky.v b/himbaechel/uarch/xilinx/examples/arty-a35/blinky.v new file mode 100644 index 0000000000..a5b50c53ff --- /dev/null +++ b/himbaechel/uarch/xilinx/examples/arty-a35/blinky.v @@ -0,0 +1,21 @@ +module top (input clk, input [3:0] sw, output [11:0] led); + + // assign led = {8'b0, ~(&sw), ^sw, &sw, |sw}; + + reg clkdiv; + reg [22:0] ctr; + + always @(posedge clk) {clkdiv, ctr} <= ctr + 1'b1; + + reg [5:0] led_r = 4'b0000; + + always @(posedge clk) begin + if (clkdiv) + led_r <= led_r + 1'b1; + end + + wire [11:0] led_s = led_r[3:0] << (4 * led_r[5:4]); + + assign led = (&(led_r[5:4]) ? {3{led_r[3:0]}} : led_s) ^ {3{sw}}; + +endmodule diff --git a/himbaechel/uarch/xilinx/examples/bitgen_xray.sh b/himbaechel/uarch/xilinx/examples/bitgen_xray.sh new file mode 100755 index 0000000000..60fe0bb5ef --- /dev/null +++ b/himbaechel/uarch/xilinx/examples/bitgen_xray.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -e + +if [ -z "$PRJXRAY" ]; then + echo "\$PRJXRAY must be set to a path to the prjxray repo" + exit 2 +fi +if [ -z "$PRJXRAY_DB" ]; then + echo "\$PRJXRAY_DB must be set to a path to the prjxray-db repo" + exit 2 +fi + +if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then + echo "Usage: bitgen_xray.sh " + exit 2 +fi + +DEVICE="$1" + +FRAMES="$3.frames" + +if [[ "$DEVICE" =~ xc7a.* ]]; then + FAMILY=artix7 +elif [[ "$DEVICE" =~ xc7z.* ]]; then + FAMILY=zynq7 +elif [[ "$DEVICE" =~ xc7k.* ]]; then + FAMILY=kintex7 +else + echo "Unknown device $DEVICE" + exit 2 +fi + +python3 ${PRJXRAY}/utils/fasm2frames.py --part "${DEVICE}" --db-root "${PRJXRAY_DB}/${FAMILY}" "$2" "$FRAMES" +${PRJXRAY}/build/tools/xc7frames2bit --part_file "${PRJXRAY_DB}/${FAMILY}/${DEVICE}/part.yaml" --part_name ${DEVICE} --frm_file "$FRAMES" --output_file "$3" diff --git a/himbaechel/uarch/xilinx/extra_data.h b/himbaechel/uarch/xilinx/extra_data.h new file mode 100644 index 0000000000..8b41bf23e6 --- /dev/null +++ b/himbaechel/uarch/xilinx/extra_data.h @@ -0,0 +1,107 @@ +#ifndef XILINX_EXTRA_DATA_H +#define XILINX_EXTRA_DATA_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +NPNR_PACKED_STRUCT(struct XlnxBelExtraDataPOD { int32_t name_in_site; }); + +struct BelSiteKey +{ + int16_t site; + int16_t site_variant; + inline static BelSiteKey unpack(uint32_t s) + { + BelSiteKey result; + result.site_variant = (s & 0xFF); + result.site = (s >> 8) & 0xFFFF; + return result; + }; +}; + +enum XlnxPipType +{ + PIP_TILE_ROUTING = 0, + PIP_SITE_ENTRY = 1, + PIP_SITE_EXIT = 2, + PIP_SITE_INTERNAL = 3, + PIP_LUT_PERMUTATION = 4, + PIP_LUT_ROUTETHRU = 5, + PIP_CONST_DRIVER = 6, +}; + +NPNR_PACKED_STRUCT(struct XlnxPipExtraDataPOD { + int32_t site_key; + int32_t bel_name; + int32_t pip_config; +}); + +enum class PipClass +{ + PIP_TILE_ROUTING = 0, + PIP_SITE_ENTRY = 1, + PIP_SITE_EXIT = 2, + PIP_SITE_INTERNAL = 3, + PIP_LUT_PERMUTATION = 4, + PIP_LUT_ROUTETHRU = 5, + PIP_CONST_DRIVER = 6, +}; + +NPNR_PACKED_STRUCT(struct SiteInstPOD { + int32_t name_prefix; + int16_t site_x, site_y; // absolute site coords + int16_t rel_x, rel_y; // coords in tiles + int16_t int_x, int_y; // associated interconnect coords + RelSlice variants; +}); + +NPNR_PACKED_STRUCT(struct XlnxTileInstExtraDataPOD { + int32_t name_prefix; + int16_t tile_x, tile_y; + RelSlice sites; +}); + +// LSnibble of Z: function +// MSnibble of Z: index in CLB (A-H) +enum LogicBelTypeZ +{ + BEL_6LUT = 0x0, + BEL_5LUT = 0x1, + BEL_FF = 0x2, + BEL_FF2 = 0x3, + BEL_FFMUX1 = 0x4, + BEL_FFMUX2 = 0x5, + BEL_OUTMUX = 0x6, + BEL_F7MUX = 0x7, + BEL_F8MUX = 0x8, + BEL_F9MUX = 0x9, + BEL_CARRY8 = 0xA, + BEL_CLKINV = 0xB, + BEL_RSTINV = 0xC, + BEL_HARD0 = 0xD, + BEL_CARRY4 = 0xF +}; + +enum BRAMBelTypeZ +{ + BEL_RAMFIFO36 = 0, + BEL_RAM36 = 1, + BEL_FIFO36 = 2, + + BEL_RAM18_U = 5, + + BEL_RAMFIFO18_L = 8, + BEL_RAM18_L = 9, + BEL_FIFO18_L = 10, +}; + +enum DSP48E1BelTypeZ +{ + BEL_LOWER_DSP = 6, + BEL_UPPER_DSP = 25, +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/himbaechel/uarch/xilinx/fasm.cc b/himbaechel/uarch/xilinx/fasm.cc new file mode 100644 index 0000000000..2c9dced7a0 --- /dev/null +++ b/himbaechel/uarch/xilinx/fasm.cc @@ -0,0 +1,1664 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019-2023 gatecat + * Copyright (C) 2023 Hans Baier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include + +#include +#include + +#include "extra_data.h" +#include "himbaechel_api.h" +#include "log.h" +#include "nextpnr.h" +#include "pins.h" +#include "util.h" + +#include "xilinx.h" + +#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" +#include "himbaechel_constids.h" + +NEXTPNR_NAMESPACE_BEGIN +namespace { +struct FasmBackend +{ + Context *ctx; + XilinxImpl *uarch; + std::ostream &out; + std::vector fasm_ctx; + dict> pips_by_tile; + + dict> invertible_pins; + + FasmBackend(Context *ctx, XilinxImpl *uarch, std::ostream &out) : ctx(ctx), uarch(uarch), out(out){}; + + void push(const std::string &x) { fasm_ctx.push_back(x); } + + void pop() { fasm_ctx.pop_back(); } + + void pop(int N) + { + for (int i = 0; i < N; i++) + fasm_ctx.pop_back(); + } + bool last_was_blank = true; + void blank() + { + if (!last_was_blank) + out << std::endl; + last_was_blank = true; + } + + void write_prefix() + { + for (auto &x : fasm_ctx) + out << x << "."; + last_was_blank = false; + } + + void write_bit(const std::string &name, bool value = true) + { + if (value) { + write_prefix(); + out << name << std::endl; + } + } + + void write_vector(const std::string &name, const std::vector &value, bool invert = false) + { + write_prefix(); + out << name << " = " << int(value.size()) << "'b"; + for (auto bit : boost::adaptors::reverse(value)) + out << ((bit ^ invert) ? '1' : '0'); + out << std::endl; + } + + void write_int_vector(const std::string &name, uint64_t value, int width, bool invert = false) + { + std::vector bits(width, false); + for (int i = 0; i < width; i++) + bits[i] = (value & (1ULL << i)) != 0; + write_vector(name, bits, invert); + } + + struct PseudoPipKey + { + IdString tileType; + IdString dest; + IdString source; + + bool operator==(const PseudoPipKey &b) const + { + return std::tie(this->tileType, this->dest, this->source) == std::tie(b.tileType, b.dest, b.source); + } + + unsigned int hash() const { return mkhash(mkhash(tileType.hash(), source.hash()), dest.hash()); } + }; + + dict> pp_config; + void get_pseudo_pip_data() + { + /* + * Create the mapping from pseudo pip tile type, dest wire, and source wire, to + * the config bits set when that pseudo pip is used + */ + for (std::string s : {"L", "R"}) + for (std::string s2 : {"", "_TBYTESRC", "_TBYTETERM", "_SING"}) + for (std::string i : + (s2 == "_SING") ? std::vector{"", "0", "1"} : std::vector{"0", "1"}) { + pp_config[{ctx->id(s + "IOI3" + s2), ctx->id(s + "IOI_OLOGIC" + i + "_OQ"), + ctx->id("IOI_OLOGIC" + i + "_D1")}] = {"OLOGIC_Y" + i + ".OMUX.D1", + "OLOGIC_Y" + i + ".OQUSED", + "OLOGIC_Y" + i + ".OSERDES.DATA_RATE_TQ.BUF"}; + pp_config[{ctx->id(s + "IOI3" + s2), ctx->id("IOI_ILOGIC" + i + "_O"), + ctx->id(s + "IOI_ILOGIC" + i + "_D")}] = {"IDELAY_Y" + i + ".IDELAY_TYPE_FIXED", + "ILOGIC_Y" + i + ".ZINV_D"}; + pp_config[{ctx->id(s + "IOI3" + s2), ctx->id("IOI_ILOGIC" + i + "_O"), + ctx->id(s + "IOI_ILOGIC" + i + "_DDLY")}] = {"ILOGIC_Y" + i + ".IDELMUXE3.P0", + "ILOGIC_Y" + i + ".ZINV_D"}; + pp_config[{ctx->id(s + "IOI3" + s2), ctx->id(s + "IOI_OLOGIC" + i + "_TQ"), + ctx->id("IOI_OLOGIC" + i + "_T1")}] = {"OLOGIC_Y" + i + ".ZINV_T1"}; + if (i == "0") { + pp_config[{ctx->id(s + "IOB33" + s2), id_IOB_O_IN1, id_IOB_O_OUT0}] = {}; + pp_config[{ctx->id(s + "IOB33" + s2), id_IOB_O_OUT0, id_IOB_O0}] = {}; + pp_config[{ctx->id(s + "IOB33" + s2), id_IOB_T_IN1, id_IOB_T_OUT0}] = {}; + pp_config[{ctx->id(s + "IOB33" + s2), id_IOB_T_OUT0, id_IOB_T0}] = {}; + pp_config[{ctx->id(s + "IOB33" + s2), id_IOB_DIFFI_IN0, id_IOB_PADOUT1}] = {}; + } + } + + for (std::string s2 : {"", "_TBYTESRC", "_TBYTETERM", "_SING"}) + for (std::string i : (s2 == "_SING") ? std::vector{"0"} : std::vector{"0", "1"}) { + pp_config[{ctx->id("RIOI" + s2), ctx->id("RIOI_OLOGIC" + i + "_OQ"), + ctx->id("IOI_OLOGIC" + i + "_D1")}] = {"OLOGIC_Y" + i + ".OMUX.D1", + "OLOGIC_Y" + i + ".OQUSED", + "OLOGIC_Y" + i + ".OSERDES.DATA_RATE_TQ.BUF"}; + pp_config[{ctx->id("RIOI" + s2), ctx->id("RIOI_OLOGIC" + i + "_OFB"), + ctx->id("RIOI_OLOGIC" + i + "_OQ")}] = {}; + pp_config[{ctx->id("RIOI" + s2), ctx->id("RIOI_O" + i), ctx->id("RIOI_ODELAY" + i + "_DATAOUT")}] = {}; + pp_config[{ctx->id("RIOI" + s2), ctx->id("RIOI_OLOGIC" + i + "_OFB"), + ctx->id("IOI_OLOGIC" + i + "_D1")}] = {"OLOGIC_Y" + i + ".OMUX.D1", + "OLOGIC_Y" + i + ".OSERDES.DATA_RATE_TQ.BUF"}; + pp_config[{ctx->id("RIOI" + s2), ctx->id("IOI_ILOGIC" + i + "_O"), ctx->id("RIOI_ILOGIC" + i + "_D")}] = + {"ILOGIC_Y" + i + ".ZINV_D"}; + pp_config[{ctx->id("RIOI" + s2), ctx->id("IOI_ILOGIC" + i + "_O"), + ctx->id("RIOI_ILOGIC" + i + "_DDLY")}] = {"ILOGIC_Y" + i + ".IDELMUXE3.P0", + "ILOGIC_Y" + i + ".ZINV_D"}; + pp_config[{ctx->id("RIOI" + s2), ctx->id("RIOI_OLOGIC" + i + "_TQ"), + ctx->id("IOI_OLOGIC" + i + "_T1")}] = {"OLOGIC_Y" + i + ".ZINV_T1"}; + pp_config[{ctx->id("RIOI" + s2), ctx->id("RIOI_OLOGIC" + i + "_OFB"), + ctx->id("RIOI_ODELAY" + i + "_ODATAIN")}] = {"OLOGIC_Y" + i + ".ZINV_ODATAIN"}; + if (i == "0") { + pp_config[{ctx->id("RIOB18" + s2), id_IOB_O_IN1, id_IOB_O_OUT0}] = {}; + pp_config[{ctx->id("RIOB18" + s2), id_IOB_O_OUT0, id_IOB_O0}] = {}; + pp_config[{ctx->id("RIOB18" + s2), id_IOB_T_IN1, id_IOB_T_OUT0}] = {}; + pp_config[{ctx->id("RIOB18" + s2), id_IOB_T_OUT0, id_IOB_T0}] = {}; + pp_config[{ctx->id("RIOB18" + s2), id_IOB_DIFFI_IN0, id_IOB_PADOUT1}] = {}; + } + } + + for (std::string s1 : {"TOP", "BOT"}) { + for (std::string s2 : {"L", "R"}) { + for (int i = 0; i < 12; i++) { + std::string ii = std::to_string(i); + std::string hck = s2 + ii; + std::string buf = std::string((s2 == "R") ? "X1Y" : "X0Y") + ii; + pp_config[{ctx->id("CLK_HROW_" + s1 + "_R"), ctx->id("CLK_HROW_CK_HCLK_OUT_" + hck), + ctx->id("CLK_HROW_CK_MUX_OUT_" + hck)}] = {"BUFHCE.BUFHCE_" + buf + ".IN_USE", + "BUFHCE.BUFHCE_" + buf + ".ZINV_CE"}; + } + } + + for (int i = 0; i < 16; i++) { + std::string ii = std::to_string(i); + pp_config[{ctx->id("CLK_BUFG_" + s1 + "_R"), ctx->id("CLK_BUFG_BUFGCTRL" + ii + "_O"), + ctx->id("CLK_BUFG_BUFGCTRL" + ii + "_I0")}] = { + "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".IN_USE", "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".IS_IGNORE1_INVERTED", + "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".ZINV_CE0", "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".ZINV_S0"}; + pp_config[{ctx->id("CLK_BUFG_" + s1 + "_R"), ctx->id("CLK_BUFG_BUFGCTRL" + ii + "_O"), + ctx->id("CLK_BUFG_BUFGCTRL" + ii + "_I1")}] = { + "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".IN_USE", "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".IS_IGNORE0_INVERTED", + "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".ZINV_CE1", "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".ZINV_S1"}; + } + } + + int rclk_y_to_i[4] = {2, 3, 0, 1}; + for (int y = 0; y < 4; y++) { + std::string yy = std::to_string(y); + std::string ii = std::to_string(rclk_y_to_i[y]); + pp_config[{id_HCLK_IOI3, ctx->id("HCLK_IOI_RCLK_OUT" + ii), ctx->id("HCLK_IOI_RCLK_BEFORE_DIV" + ii)}] = { + "BUFR_Y" + yy + ".IN_USE", "BUFR_Y" + yy + ".BUFR_DIVIDE.BYPASS"}; + pp_config[{id_HCLK_IOI, ctx->id("HCLK_IOI_RCLK_OUT" + ii), ctx->id("HCLK_IOI_RCLK_BEFORE_DIV" + ii)}] = { + "BUFR_Y" + yy + ".IN_USE", "BUFR_Y" + yy + ".BUFR_DIVIDE.BYPASS"}; + } + + // FIXME: shouldn't these be in the X-RAY ppips database? + for (char c : {'L', 'R'}) { + for (int i = 0; i < 24; i++) { + pp_config[{ctx->idf("INT_INTERFACE_%c", c), ctx->idf("INT_INTERFACE_LOGIC_OUTS_%c%d", c, i), + ctx->idf("INT_INTERFACE_LOGIC_OUTS_%c_B%d", c, i)}]; + } + } + } + + void write_pip(PipId pip, NetInfo *net) + { + + pips_by_tile[pip.tile].push_back(pip); + + auto dst_intent = ctx->getWireType(ctx->getPipDstWire(pip)); + if (dst_intent == id_PSEUDO_GND || dst_intent == id_PSEUDO_VCC) + return; + + auto &pd = chip_pip_info(ctx->chip_info, pip); + const auto &extra_data = *reinterpret_cast(pd.extra_data.get()); + unsigned pip_type = pd.flags; + + if (pip_type != PIP_TILE_ROUTING && pip_type != PIP_SITE_INTERNAL) + return; + + IdString src = IdString(chip_tile_info(ctx->chip_info, pip.tile).wires[pd.src_wire].name); + IdString dst = IdString(chip_tile_info(ctx->chip_info, pip.tile).wires[pd.dst_wire].name); + + // handle certain site internal pips: + // this is necessary, because in tristate outputs, the + // ZINV_T1 bit needs to be set, because in the OLOGIC tiles the + // tristate control signals are inverted if this bit is not set + // this only applies to router1, because router2 does not generate + // site internal pips here. + if (pip_type == PIP_SITE_INTERNAL) { + if (src.str(ctx) == "T1" && dst.str(ctx) == "T1INV_OUT") { + auto srcwire_uphill_iter = ctx->getPipsUphill(ctx->getPipSrcWire(pip)); + auto uphill = srcwire_uphill_iter.begin(); + if (uphill != srcwire_uphill_iter.end()) { + // source wire should be like: LIOI3_X0Y73/IOI_OLOGIC1_T1 + auto loc = ctx->getWireName(ctx->getPipSrcWire(*uphill)).str(ctx); + boost::replace_all(loc, "/", "."); + boost::erase_all(loc, "_T1"); + boost::replace_all(loc, "IOI_OLOGIC", "OLOGIC_Y"); + // the replacements transformed it into : LIOI3_X0Y73.OLOGIC_Y1 + out << loc << "." + << "ZINV_T1" << std::endl; + } + } + return; + } + + // handle tile routing pips + IdString tile_type = IdString(chip_tile_info(ctx->chip_info, pip.tile).type_name); + PseudoPipKey ppk{tile_type, dst, src}; + + if (pp_config.count(ppk)) { + auto &pp = pp_config.at(ppk); + std::string tile_name = uarch->tile_name(pip.tile); + for (auto c : pp) { + if (boost::starts_with(tile_name, "RIOI3_SING") || boost::starts_with(tile_name, "LIOI3_SING") || + boost::starts_with(tile_name, "RIOI_SING")) { + // Need to flip for top HCLK + bool is_top_sing = pip.tile < uarch->hclk_for_ioi(pip.tile); + if (is_top_sing) { + auto y0pos = c.find("Y0"); + if (y0pos != std::string::npos) + c.replace(y0pos, 2, "Y1"); + } + } + out << tile_name << "." << c << std::endl; + } + if (!pp.empty()) + last_was_blank = false; + } else { + if (extra_data.pip_config == 1) + log_warning("Unprocessed route-thru %s.%s.%s\n!", tile_type.c_str(ctx), src.c_str(ctx), dst.c_str(ctx)); + + std::string tile_name = uarch->tile_name(pip.tile); + std::string dst_name = dst.str(ctx); + std::string src_name = src.str(ctx); + + if (boost::starts_with(tile_name, "DSP_L") || boost::starts_with(tile_name, "DSP_R")) { + // FIXME: PPIPs missing for DSPs + return; + } + std::string orig_dst_name = dst_name; + if (boost::starts_with(tile_name, "RIOI3_SING") || boost::starts_with(tile_name, "LIOI3_SING") || + boost::starts_with(tile_name, "RIOI_SING")) { + // FIXME: PPIPs missing for SING IOI3s + if ((boost::contains(src_name, "IMUX") || boost::contains(src_name, "CTRL0")) && + !boost::contains(dst_name, "CLK")) + return; + auto spos = src_name.find("_SING_"); + if (spos != std::string::npos) + src_name.erase(spos, 5); + // Need to flip for top HCLK + // TODO + + bool is_top_sing = pip.tile < uarch->hclk_for_ioi(pip.tile); + if (is_top_sing) { + auto us0pos = dst_name.find("_0"); + if (us0pos != std::string::npos) + dst_name.replace(us0pos, 2, "_1"); + auto ol0pos = dst_name.find("OLOGIC0"); + if (ol0pos != std::string::npos) { + dst_name.replace(ol0pos, 7, "OLOGIC1"); + us0pos = src_name.find("_0"); + if (us0pos != std::string::npos) + src_name.replace(us0pos, 2, "_1"); + } + } + + NPNR_ASSERT_FALSE("unimplemented!"); + } + if (boost::contains(tile_name, "IOI")) { + if (boost::contains(dst_name, "OCLKB") && boost::contains(src_name, "IOI_OCLKM_")) + return; // missing, not sure if really a ppip? + } + + out << tile_name << "."; + out << dst_name << "."; + out << src_name << std::endl; + + if (boost::contains(tile_name, "IOI") && boost::starts_with(dst_name, "IOI_OCLK_")) { +#if 0 + dst_name.insert(dst_name.find("OCLK") + 4, 1, 'M'); + orig_dst_name.insert(dst_name.find("OCLK") + 4, 1, 'M'); + + WireId w = ctx->getWireByNameStr(tile_name + "/" + orig_dst_name); + NPNR_ASSERT(w != WireId()); + if (ctx->getBoundWireNet(w) == nullptr) { + out << tile_name << "."; + out << dst_name << "."; + out << src_name << std::endl; + } +#endif + NPNR_ASSERT_FALSE("unimplemented!"); + } + + last_was_blank = false; + } + }; + + // Get the set of input signals for a LUT-type cell + std::vector get_inputs(CellInfo *cell) + { + IdString type = ctx->id(str_or_default(cell->attrs, id_X_ORIG_TYPE, "")); + if (type == id_LUT1) + return {id_I0}; + else if (type == id_LUT2) + return {id_I0, id_I1}; + else if (type == id_LUT3) + return {id_I0, id_I1, id_I2}; + else if (type == id_LUT4) + return {id_I0, id_I1, id_I2, id_I3}; + else if (type == id_LUT5) + return {id_I0, id_I1, id_I2, id_I3, id_I4}; + else if (type == id_LUT6) + return {id_I0, id_I1, id_I2, id_I3, id_I4, id_I5}; + else if (type == id_RAMD64E) + return {id_RADR0, id_RADR1, id_RADR2, id_RADR3, id_RADR4, id_RADR5}; + else if (type == id_SRL16E) + return {id_A0, id_A1, id_A2, id_A3}; + else if (type == id_SRLC32E) + return {ctx->id("A[0]"), ctx->id("A[1]"), ctx->id("A[2]"), ctx->id("A[3]"), ctx->id("A[4]")}; + else if (type == id_RAMD32) + return {id_RADR0, id_RADR1, id_RADR2, id_RADR3, id_RADR4}; + else + NPNR_ASSERT_FALSE("unsupported LUT-type cell"); + } + + // Process LUT initialisation + std::vector get_lut_init(CellInfo *lut6, CellInfo *lut5) + { + std::vector bits(64, false); + + std::vector phys_inputs; + for (int i = 1; i <= 6; i++) + phys_inputs.push_back(ctx->id("A" + std::to_string(i))); + + for (int i = 0; i < 2; i++) { + CellInfo *lut = (i == 1) ? lut5 : lut6; + if (lut == nullptr) + continue; + auto lut_inputs = get_inputs(lut); + dict> phys_to_log; + dict log_to_bit; + for (int j = 0; j < int(lut_inputs.size()); j++) + log_to_bit[lut_inputs[j].str(ctx)] = j; + for (int j = 0; j < 6; j++) { + // Get the LUT physical to logical mapping + phys_to_log[j]; + if (!lut->attrs.count(ctx->idf("X_ORIG_PORT_%s", phys_inputs[j].c_str(ctx)))) + continue; + std::string orig = lut->attrs.at(ctx->idf("X_ORIG_PORT_%s", phys_inputs[j].c_str(ctx))).as_string(); + boost::split(phys_to_log[j], orig, boost::is_any_of(" ")); + } + int lbound = 0, ubound = 64; + // Fracturable LUTs + if (lut5 && lut6) { + lbound = (i == 1) ? 0 : 32; + ubound = (i == 1) ? 32 : 64; + } + Property init = get_or_default(lut->params, id_INIT, Property()).extract(0, 64); + for (int j = lbound; j < ubound; j++) { + int log_index = 0; + for (int k = 0; k < 6; k++) { + if ((j & (1 << k)) == 0) + continue; + for (auto &p2l : phys_to_log[k]) + log_index |= (1 << log_to_bit[p2l]); + } + bits[j] = (init.str.at(log_index) == Property::S1); + } + } + return bits; + }; + + // Return the name for a half-logic-tile + std::string get_half_name(int half, bool is_m) + { + if (is_m) + return half ? "SLICEL_X1" : "SLICEM_X0"; + else + return half ? "SLICEL_X1" : "SLICEL_X0"; + } + + std::string get_bel_name(BelId bel) { return uarch->bel_name_in_site(bel).str(ctx); } + + void write_routing_bel(WireId dst_wire) + { + for (auto pip : ctx->getPipsUphill(dst_wire)) { + if (ctx->getBoundPipNet(pip) != nullptr) { + auto &pd = chip_pip_info(ctx->chip_info, pip); + const auto &extra_data = *reinterpret_cast(pd.extra_data.get()); + std::string belname = IdString(extra_data.bel_name).str(ctx); + std::string pinname = IdString(extra_data.pip_config).str(ctx); + bool skip_pinname = false; + // Ignore modes with no associated bit (X-ray omission??) + if (belname == "WEMUX" && pinname == "WE") + continue; + + if (belname.substr(1) == "DI1MUX") { + belname = "DI1MUX"; + } + + if (belname.substr(1) == "CY0") { + if (pinname.substr(1) == "5") + skip_pinname = true; + else + continue; + } + + write_prefix(); + out << belname; + if (!skip_pinname) + out << "." << pinname; + out << std::endl; + } + } + } + + // Process flipflops in a half-tile + void write_ffs_config(int tile, int half) + { + bool found_ff = false; + bool negedge_ff = false; + bool is_latch = false; + bool is_sync = false; + bool is_clkinv = false; + bool is_srused = false; + bool is_ceused = false; + +#define SET_CHECK(dst, src) \ + do { \ + if (found_ff) \ + NPNR_ASSERT(dst == (src)); \ + else \ + dst = (src); \ + } while (0) + + std::string tname = uarch->tile_name(tile); + + const auto <s = uarch->tile_status.at(tile).lts; + if (!lts) + return; + + push(tname); + push(get_half_name(half, boost::contains(tname, "CLBLM"))); + + for (int i = 0; i < 4; i++) { + CellInfo *ff1 = lts->cells[(half << 6) | (i << 4) | BEL_FF]; + CellInfo *ff2 = lts->cells[(half << 6) | (i << 4) | BEL_FF2]; + for (int j = 0; j < 2; j++) { + CellInfo *ff = (j == 1) ? ff2 : ff1; + if (ff == nullptr) + continue; + push(get_bel_name(ff->bel)); + bool zrst = false, zinit = false; + zinit = (int_or_default(ff->params, id_INIT, 0) != 1); + IdString srsig; + std::string type = str_or_default(ff->attrs, id_X_ORIG_TYPE, ""); + if (type == "FDRE") { + zrst = true; + SET_CHECK(negedge_ff, false); + SET_CHECK(is_latch, false); + SET_CHECK(is_sync, true); + } else if (type == "FDRE_1") { + zrst = true; + SET_CHECK(negedge_ff, true); + SET_CHECK(is_latch, false); + SET_CHECK(is_sync, true); + } else if (type == "FDSE") { + zrst = false; + SET_CHECK(negedge_ff, false); + SET_CHECK(is_latch, false); + SET_CHECK(is_sync, true); + } else if (type == "FDSE_1") { + zrst = false; + SET_CHECK(negedge_ff, true); + SET_CHECK(is_latch, false); + SET_CHECK(is_sync, true); + } else if (type == "FDCE") { + zrst = true; + SET_CHECK(negedge_ff, false); + SET_CHECK(is_latch, false); + SET_CHECK(is_sync, false); + } else if (type == "FDCE_1") { + zrst = true; + SET_CHECK(negedge_ff, true); + SET_CHECK(is_latch, false); + SET_CHECK(is_sync, false); + } else if (type == "FDPE") { + zrst = false; + SET_CHECK(negedge_ff, false); + SET_CHECK(is_latch, false); + SET_CHECK(is_sync, false); + } else if (type == "FDPE_1") { + zrst = false; + SET_CHECK(negedge_ff, true); + SET_CHECK(is_latch, false); + SET_CHECK(is_sync, false); + } else { + log_error("unsupported FF type: '%s'\n", type.c_str()); + } + + write_bit("ZINI", zinit); + write_bit("ZRST", zrst); + + pop(); + if (negedge_ff) + SET_CHECK(is_clkinv, true); + else + SET_CHECK(is_clkinv, int_or_default(ff->params, id_IS_C_INVERTED) == 1); + + NetInfo *sr = ff->getPort(id_SR), *ce = ff->getPort(id_CE); + + SET_CHECK(is_srused, sr != nullptr && sr->name != ctx->id("$PACKER_GND_NET")); + SET_CHECK(is_ceused, ce != nullptr && ce->name != ctx->id("$PACKER_VCC_NET")); + + // Input mux + write_routing_bel(ctx->getBelPinWire(ff->bel, id_D)); + + found_ff = true; + } + } + write_bit("LATCH", is_latch); + write_bit("FFSYNC", is_sync); + write_bit("CLKINV", is_clkinv); + write_bit("NOCLKINV", !is_clkinv); + write_bit("SRUSEDMUX", is_srused); + write_bit("CEUSEDMUX", is_ceused); + pop(2); + } + + // Get a named wire in the same site as a bel + WireId get_site_wire(BelId site_bel, std::string name) + { + IdStringList bel_name = ctx->getBelName(site_bel); + NPNR_ASSERT(bel_name.size() == 2); + IdString tile_name = bel_name[0]; + const std::string &bel_name_str = bel_name[1].str(ctx); + size_t sep_pos = bel_name_str.find('.'); + NPNR_ASSERT(sep_pos != std::string::npos); + std::string site_name = bel_name_str.substr(0, sep_pos); + IdString wire_name = ctx->idf("%s.%s", site_name.c_str(), name.c_str()); + WireId wire = ctx->getWireByName(IdStringList::concat(tile_name, wire_name)); + NPNR_ASSERT(wire != WireId()); + return wire; + } + + // Process LUTs and associated functionality in a half + void write_luts_config(int tile, int half) + { + bool wa7_used = false, wa8_used = false; + + std::string tname = uarch->tile_name(tile); + bool is_mtile = boost::contains(tname, "CLBLM"); + bool is_slicem = is_mtile && (half == 0); + + const auto <s = uarch->tile_status.at(tile).lts; + if (!lts) + return; + + push(tname); + push(get_half_name(half, is_mtile)); + + BelId bel_in_half = + ctx->getBelByLocation(Loc(tile % ctx->chip_info->width, tile / ctx->chip_info->width, half << 6)); + + for (int i = 0; i < 4; i++) { + CellInfo *lut6 = lts->cells[(half << 6) | (i << 4) | BEL_6LUT]; + CellInfo *lut5 = lts->cells[(half << 6) | (i << 4) | BEL_5LUT]; + // Write LUT initialisation + if (lut6 != nullptr || lut5 != nullptr) { + std::string lutname = stringf("%cLUT", "ABCD"[i]); + push(lutname); + write_vector("INIT[63:0]", get_lut_init(lut6, lut5)); + + // Write LUT mode config + bool is_small = false, is_ram = false, is_srl = false; + for (int j = 0; j < 2; j++) { + CellInfo *lut = (j == 1) ? lut5 : lut6; + if (lut == nullptr) + continue; + std::string type = str_or_default(lut->attrs, id_X_ORIG_TYPE); + if (type == "RAMD64E" || type == "RAMS64E") { + is_ram = true; + } else if (type == "RAMD32" || type == "RAMS32") { + is_ram = true; + is_small = true; + } else if (type == "SRL16E") { + is_srl = true; + is_small = true; + } else if (type == "SRLC32E") { + is_srl = true; + } + wa7_used |= (lut->getPort(id_WA7) != nullptr); + wa8_used |= (lut->getPort(id_WA8) != nullptr); + } + if (is_slicem && i != 3) { + write_routing_bel(get_site_wire(bel_in_half, stringf("%cDI1MUX_OUT", "ABCD"[i]))); + } + write_bit("SMALL", is_small); + write_bit("RAM", is_ram); + write_bit("SRL", is_srl); + pop(); + } + write_routing_bel(get_site_wire(bel_in_half, stringf("%cMUX", "ABCD"[i]))); + } + write_bit("WA7USED", wa7_used); + write_bit("WA8USED", wa8_used); + if (is_slicem) + write_routing_bel(get_site_wire(bel_in_half, "WEMUX_OUT")); + + pop(2); + } + + void write_carry_config(int tile, int half) + { + std::string tname = uarch->tile_name(tile); + bool is_mtile = boost::contains(tname, "CLBLM"); + + const auto <s = uarch->tile_status.at(tile).lts; + if (!lts) + return; + + CellInfo *carry = lts->cells[half << 6 | BEL_CARRY4]; + if (carry == nullptr) + return; + + push(tname); + push(get_half_name(half, is_mtile)); + + write_routing_bel(get_site_wire(carry->bel, "PRECYINIT_OUT")); + if (carry->getPort(id_CIN) != nullptr) + write_bit("PRECYINIT.CIN"); + push("CARRY4"); + for (char c : {'A', 'B', 'C', 'D'}) + write_routing_bel(get_site_wire(carry->bel, stringf("%cCY0_OUT", c))); + pop(3); + } + + void write_logic() + { + std::set used_logic_tiles; + for (auto &cell : ctx->cells) { + if (uarch->is_logic_tile(cell.second->bel)) + used_logic_tiles.insert(cell.second->bel.tile); + } + for (int tile : used_logic_tiles) { + write_luts_config(tile, 0); + write_luts_config(tile, 1); + write_ffs_config(tile, 0); + write_ffs_config(tile, 1); + write_carry_config(tile, 0); + write_carry_config(tile, 1); + blank(); + } + } + + void write_routing() + { + get_pseudo_pip_data(); + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + out << stringf("# routing for net %s", ni->name.c_str(ctx)) << std::endl; + for (auto &w : ni->wires) { + if (w.second.pip != PipId()) + write_pip(w.second.pip, ni); + } + blank(); + } + } + + struct BankIoConfig + { + bool stepdown = false; + bool vref = false; + bool tmds_33 = false; + bool lvds_25 = false; + bool only_diff = false; + }; + + dict ioconfig_by_hclk; + + void write_io_config(CellInfo *pad) + { + NetInfo *pad_net = pad->getPort(id_PAD); + NPNR_ASSERT(pad_net != nullptr); + std::string iostandard = str_or_default(pad->attrs, id_IOSTANDARD, "LVCMOS33"); + std::string pulltype = str_or_default(pad->attrs, id_PULLTYPE, "NONE"); + std::string slew = str_or_default(pad->attrs, id_SLEW, "SLOW"); + + Loc ioLoc = uarch->rel_site_loc(uarch->get_bel_site(pad->bel)); + bool is_output = false, is_input = false; + if (pad_net->driver.cell != nullptr) + is_output = true; + for (auto &usr : pad_net->users) + if (boost::contains(usr.cell->type.str(ctx), "INBUF")) + is_input = true; + std::string tile = uarch->tile_name(pad->bel.tile); + push(tile); + + bool is_riob18 = boost::starts_with(tile, "RIOB18_"); + bool is_sing = boost::contains(tile, "_SING_"); + bool is_top_sing = pad->bel.tile < uarch->hclk_for_iob(pad->bel); + bool is_stepdown = false; + bool is_lvcmos = boost::starts_with(iostandard, "LVCMOS"); + bool is_low_volt_lvcmos = iostandard == "LVCMOS12" || iostandard == "LVCMOS15" || iostandard == "LVCMOS18"; + + auto yLoc = is_sing ? (is_top_sing ? 1 : 0) : (1 - ioLoc.y); + push("IOB_Y" + std::to_string(yLoc)); + + bool has_diff_prefix = boost::starts_with(iostandard, "DIFF_"); + bool is_tmds33 = iostandard == "TMDS_33"; + bool is_lvds25 = iostandard == "LVDS_25"; + bool is_lvds = boost::starts_with(iostandard, "LVDS"); + bool only_diff = is_tmds33 || is_lvds; + bool is_diff = only_diff || has_diff_prefix; + if (has_diff_prefix) + iostandard.erase(0, 5); + bool is_sstl = iostandard == "SSTL12" || iostandard == "SSTL135" || iostandard == "SSTL15"; + + int hclk = uarch->hclk_for_iob(pad->bel); + + if (only_diff) + ioconfig_by_hclk[hclk].only_diff = true; + if (is_tmds33) + ioconfig_by_hclk[hclk].tmds_33 = true; + if (is_lvds25) + ioconfig_by_hclk[hclk].lvds_25 = true; + + if (is_output) { + // DRIVE + int default_drive = (is_riob18 && iostandard == "LVCMOS12") ? 8 : 12; + int drive = int_or_default(pad->attrs, id_DRIVE, default_drive); + + if ((iostandard == "LVCMOS33" || iostandard == "LVTTL") && is_riob18) + log_error("high performance banks (RIOB18) do not support IO standard %s\n", iostandard.c_str()); + + if (iostandard == "SSTL135") + write_bit("SSTL135.DRIVE.I_FIXED"); + else if (is_riob18) { + if ((iostandard == "LVCMOS18" || iostandard == "LVCMOS15")) + write_bit("LVCMOS15_LVCMOS18.DRIVE.I12_I16_I2_I4_I6_I8"); + else if (iostandard == "LVCMOS12") + write_bit("LVCMOS12.DRIVE.I2_I4_I6_I8"); + else if (iostandard == "LVDS") + write_bit("LVDS.DRIVE.I_FIXED"); + else if (is_sstl) { + write_bit(iostandard + ".DRIVE.I_FIXED"); + } + } else { // IOB33 + if (iostandard == "TMDS_33" && yLoc == 0) { + write_bit("TMDS_33.DRIVE.I_FIXED"); + write_bit("TMDS_33.OUT"); + } else if (iostandard == "LVDS_25" && yLoc == 0) { + write_bit("LVDS_25.DRIVE.I_FIXED"); + write_bit("LVDS_25.OUT"); + } else if ((iostandard == "LVCMOS15" && drive == 16) || iostandard == "SSTL15") + write_bit("LVCMOS15_SSTL15.DRIVE.I16_I_FIXED"); + else if (iostandard == "LVCMOS18" && (drive == 12 || drive == 8)) + write_bit("LVCMOS18.DRIVE.I12_I8"); + else if ((iostandard == "LVCMOS33" && drive == 16) || (iostandard == "LVTTL" && drive == 16)) + write_bit("LVCMOS33_LVTTL.DRIVE.I12_I16"); + else if ((iostandard == "LVCMOS33" && (drive == 8 || drive == 12)) || + (iostandard == "LVTTL" && (drive == 8 || drive == 12))) + write_bit("LVCMOS33_LVTTL.DRIVE.I12_I8"); + else if ((iostandard == "LVCMOS33" && drive == 4) || (iostandard == "LVTTL" && drive == 4)) + write_bit("LVCMOS33_LVTTL.DRIVE.I4"); + else if (drive == 8 && (iostandard == "LVCMOS12" || iostandard == "LVCMOS25")) + write_bit("LVCMOS12_LVCMOS25.DRIVE.I8"); + else if (drive == 4 && + (iostandard == "LVCMOS15" || iostandard == "LVCMOS18" || iostandard == "LVCMOS25")) + write_bit("LVCMOS15_LVCMOS18_LVCMOS25.DRIVE.I4"); + else if (is_lvcmos || iostandard == "LVTTL") + write_bit(iostandard + ".DRIVE.I" + std::to_string(drive)); + } + + // SSTL output used + if (is_riob18 && is_sstl) + write_bit(iostandard + ".IN_USE"); + + // SLEW + if (is_riob18 && slew == "SLOW") { + if (iostandard == "SSTL135") + write_bit("SSTL135.SLEW.SLOW"); + else if (iostandard == "SSTL15") + write_bit("SSTL15.SLEW.SLOW"); + else + write_bit("LVCMOS12_LVCMOS15_LVCMOS18.SLEW.SLOW"); + } else if (slew == "SLOW") { + if (iostandard != "LVDS_25" && iostandard != "TMDS_33") + write_bit("LVCMOS12_LVCMOS15_LVCMOS18_LVCMOS25_LVCMOS33_LVTTL_SSTL135_SSTL15.SLEW.SLOW"); + } else if (is_riob18) + write_bit(iostandard + ".SLEW.FAST"); + else if (iostandard == "SSTL135" || iostandard == "SSTL15") + write_bit("SSTL135_SSTL15.SLEW.FAST"); + else + write_bit("LVCMOS12_LVCMOS15_LVCMOS18_LVCMOS25_LVCMOS33_LVTTL.SLEW.FAST"); + } + + if (is_input) { + if (!is_diff) { + if (iostandard == "LVCMOS33" || iostandard == "LVTTL" || iostandard == "LVCMOS25") { + if (!is_riob18) + write_bit("LVCMOS25_LVCMOS33_LVTTL.IN"); + else + log_error("high performance banks (RIOB18) do not support IO standard %s\n", + iostandard.c_str()); + } + + if (is_sstl) { + ioconfig_by_hclk[hclk].vref = true; + if (!is_riob18) + write_bit("SSTL135_SSTL15.IN"); + + if (is_riob18) { + write_bit("SSTL12_SSTL135_SSTL15.IN"); + } + + if (!is_riob18 && pad->attrs.count(id_IN_TERM)) + write_bit("IN_TERM." + pad->attrs.at(id_IN_TERM).as_string()); + } + + if (is_low_volt_lvcmos) { + write_bit("LVCMOS12_LVCMOS15_LVCMOS18.IN"); + } + } else /* is_diff */ { + if (is_riob18) { + // vivado generates these bits only for Y0 of a diff pair + if (yLoc == 0) { + write_bit("LVDS_SSTL12_SSTL135_SSTL15.IN_DIFF"); + if (iostandard == "LVDS") + write_bit("LVDS.IN_USE"); + } + } else { + if (iostandard == "TDMS_33") + write_bit("TDMS_33.IN_DIFF"); + else + write_bit("LVDS_25_SSTL135_SSTL15.IN_DIFF"); + } + + if (pad->attrs.count(id_IN_TERM)) + write_bit("IN_TERM." + pad->attrs.at(id_IN_TERM).as_string()); + } + + // IN_ONLY + if (!is_output) { + if (is_riob18) { + // vivado also sets this bit for DIFF_SSTL + if (is_diff && (yLoc == 0)) + write_bit("LVDS.IN_ONLY"); + else + write_bit("LVCMOS12_LVCMOS15_LVCMOS18_SSTL12_SSTL135_SSTL15.IN_ONLY"); + } else + write_bit("LVCMOS12_LVCMOS15_LVCMOS18_LVCMOS25_LVCMOS33_LVDS_25_LVTTL_SSTL135_SSTL15_TMDS_33.IN_" + "ONLY"); + } + } + + if (!is_riob18 && (is_low_volt_lvcmos || is_sstl)) { + if (iostandard == "SSTL12") { + log_error("SSTL12 is only available on high performance banks."); + } + write_bit("LVCMOS12_LVCMOS15_LVCMOS18_SSTL135_SSTL15.STEPDOWN"); + ioconfig_by_hclk[hclk].stepdown = true; + is_stepdown = true; + } + + if (is_riob18 && (is_input || is_output) && (boost::contains(iostandard, "SSTL") || iostandard == "LVDS")) { + if (((yLoc == 0) && (iostandard == "LVDS")) || boost::contains(iostandard, "SSTL")) { + // TODO: I get bit conflicts with this, it seems to work anyway. Test more. + // write_bit("LVDS.IN_USE"); + } + } + + if (is_input && is_output && !is_diff && yLoc == 1) { + if (is_riob18 && boost::starts_with(iostandard, "SSTL")) + write_bit("SSTL12_SSTL135_SSTL15.IN"); + } + + write_bit("PULLTYPE." + pulltype); + pop(); // IOB_YN + + BelId inv; + + auto pad_bel_site = uarch->get_bel_site(pad->bel); + + if (is_riob18) + inv = uarch->get_site_bel(pad_bel_site, ctx->id("IOB18S.O_ININV")); + else + inv = uarch->get_site_bel(pad_bel_site, ctx->id("IOB33S.O_ININV")); + + if (inv != BelId() && ctx->getBoundBelCell(inv) != nullptr) + write_bit("OUT_DIFF"); + + if (is_stepdown && !is_sing) + write_bit("IOB_Y" + std::to_string(ioLoc.y) + ".LVCMOS12_LVCMOS15_LVCMOS18_SSTL135_SSTL15.STEPDOWN"); + + pop(); // tile + } + + void write_iol_config(CellInfo *ci) + { + std::string tile = uarch->tile_name(ci->bel.tile); + push(tile); + bool is_sing = boost::contains(tile, "_SING_"); + bool is_top_sing = ci->bel.tile < uarch->hclk_for_ioi(ci->bel.tile); + + auto site_key = uarch->get_bel_site(ci->bel); + std::string site = uarch->get_site_name(site_key).str(ctx); + std::string sitetype = site.substr(0, site.find('_')); + Loc siteloc = uarch->rel_site_loc(site_key); + push(stringf("%s_Y%d", sitetype.c_str(), is_sing ? (is_top_sing ? 1 : 0) : (1 - siteloc.y))); + + if (ci->type == id_ILOGICE3_IFF) { + write_bit("IDDR.IN_USE"); + write_bit("IDDR_OR_ISERDES.IN_USE"); + write_bit("ISERDES.MODE.MASTER"); + write_bit("ISERDES.NUM_CE.N1"); + + // Switch IDELMUXE3 to include the IDELAY element, if we have an IDELAYE2 driving D + NetInfo *d = ci->getPort(id_D); + if (d == nullptr || d->driver.cell == nullptr) + log_error("%s '%s' has disconnected D input\n", ci->type.c_str(ctx), ctx->nameOf(ci)); + CellInfo *drv = d->driver.cell; + if (boost::contains(drv->type.str(ctx), "IDELAYE2")) + write_bit("IDELMUXE3.P0"); + else + write_bit("IDELMUXE3.P1"); + + // clock edge + std::string edge = str_or_default(ci->params, id_DDR_CLK_EDGE, "OPPOSITE_EDGE"); + if (edge == "SAME_EDGE") + write_bit("IFF.DDR_CLK_EDGE.SAME_EDGE"); + else if (edge == "OPPOSITE_EDGE") + write_bit("IFF.DDR_CLK_EDGE.OPPOSITE_EDGE"); + else + log_error("unsupported clock edge parameter for cell '%s' at %s: %s. Supported are: SAME_EDGE and " + "OPPOSITE_EDGE", + ci->name.c_str(ctx), site.c_str(), edge.c_str()); + + std::string srtype = str_or_default(ci->params, id_SRTYPE, "SYNC"); + if (srtype == "SYNC") + write_bit("IFF.SRTYPE.SYNC"); + else + write_bit("IFF.SRTYPE.ASYNC"); + + write_bit("IFF.ZINV_C", !bool_or_default(ci->params, id_IS_CLK_INVERTED, false)); + write_bit("ZINV_D", !bool_or_default(ci->params, id_IS_D_INVERTED, false)); + + auto init = int_or_default(ci->params, id_INIT_Q1, 0); + if (init == 0) + write_bit("IFF.ZINIT_Q1"); + init = int_or_default(ci->params, id_INIT_Q2, 0); + if (init == 0) + write_bit("IFF.ZINIT_Q2"); + + auto sr_name = str_or_default(ci->attrs, id_X_ORIG_PORT_SR, "R"); + if (sr_name == "R") { + write_bit("IFF.ZSRVAL_Q1"); + write_bit("IFF.ZSRVAL_Q2"); + } + } else if (ci->type.in(id_OLOGICE2_OUTFF, id_OLOGICE3_OUTFF)) { + std::string edge = str_or_default(ci->params, id_DDR_CLK_EDGE, "OPPOSITE_EDGE"); + if (edge == "SAME_EDGE") + write_bit("ODDR.DDR_CLK_EDGE.SAME_EDGE"); + + write_bit("ODDR_TDDR.IN_USE"); + write_bit("OQUSED"); + write_bit("OSERDES.DATA_RATE_OQ.DDR"); + write_bit("OSERDES.DATA_RATE_TQ.BUF"); + + std::string srtype = str_or_default(ci->params, id_SRTYPE, "SYNC"); + if (srtype == "SYNC") + write_bit("OSERDES.SRTYPE.SYNC"); + + for (std::string d : {"D1", "D2"}) + write_bit("IS_" + d + "_INVERTED", + bool_or_default(ci->params, ctx->id("IS_" + d + "_INVERTED"), false)); + + auto init = int_or_default(ci->params, id_INIT, 1); + if (init == 0) + write_bit("ZINIT_OQ"); + + write_bit("ODDR.SRUSED", ci->getPort(id_SR) != nullptr); + auto sr_name = str_or_default(ci->attrs, id_X_ORIG_PORT_SR, "R"); + if (sr_name == "R") + write_bit("ZSRVAL_OQ"); + + auto clk_inv = bool_or_default(ci->params, id_IS_CLK_INVERTED); + if (!clk_inv) + write_bit("ZINV_CLK"); + } else if (ci->type == id_OSERDESE2_OSERDESE2) { + write_bit("ODDR.DDR_CLK_EDGE.SAME_EDGE"); + write_bit("ODDR.SRUSED"); + write_bit("ODDR_TDDR.IN_USE"); + write_bit("OQUSED", ci->getPort(id_OQ) != nullptr); + write_bit("ZINV_CLK", !bool_or_default(ci->params, id_IS_CLK_INVERTED, false)); + for (std::string t : {"T1", "T2", "T3", "T4"}) + write_bit("ZINV_" + t, (ci->getPort(ctx->id(t)) != nullptr || t == "T1") && + !bool_or_default(ci->params, ctx->id("IS_" + t + "_INVERTED"), false)); + for (std::string d : {"D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8"}) + write_bit("IS_" + d + "_INVERTED", + bool_or_default(ci->params, ctx->id("IS_" + d + "_INVERTED"), false)); + write_bit("ZINIT_OQ", !bool_or_default(ci->params, id_INIT_OQ, false)); + write_bit("ZINIT_TQ", !bool_or_default(ci->params, id_INIT_TQ, false)); + write_bit("ZSRVAL_OQ", !bool_or_default(ci->params, id_SRVAL_OQ, false)); + write_bit("ZSRVAL_TQ", !bool_or_default(ci->params, id_SRVAL_TQ, false)); + + push("OSERDES"); + write_bit("IN_USE"); + std::string type = str_or_default(ci->params, id_DATA_RATE_OQ, "BUF"); + write_bit(std::string("DATA_RATE_OQ.") + ((ci->getPort(id_OQ) != nullptr) ? type : "BUF")); + write_bit(std::string("DATA_RATE_TQ.") + + ((ci->getPort(id_TQ) != nullptr) ? str_or_default(ci->params, id_DATA_RATE_TQ, "BUF") : "BUF")); + int width = int_or_default(ci->params, id_DATA_WIDTH, 8); +#if 0 + write_bit("DATA_WIDTH.W" + std::to_string(width)); + if (type == "DDR" && (width == 6 || width == 8)) { + write_bit("DATA_WIDTH.DDR.W6_8"); + write_bit("DATA_WIDTH.SDR.W2_4_5_6"); + } else if (type == "SDR" && (width == 2 || width == 4 || width == 5 || width == 6)) { + write_bit("DATA_WIDTH.SDR.W2_4_5_6"); + } +#else + if (type == "DDR") + write_bit("DATA_WIDTH.DDR.W" + std::to_string(width)); + else if (type == "SDR") + write_bit("DATA_WIDTH.SDR.W" + std::to_string(width)); + else + write_bit("DATA_WIDTH.W" + std::to_string(width)); +#endif + write_bit("SRTYPE.SYNC"); + write_bit("TSRTYPE.SYNC"); + pop(); + } else if (ci->type == id_ISERDESE2_ISERDESE2) { + std::string data_rate = str_or_default(ci->params, id_DATA_RATE); + write_bit("IDDR_OR_ISERDES.IN_USE"); + if (data_rate == "DDR") + write_bit("IDDR.IN_USE"); + write_bit("IFF.DDR_CLK_EDGE.OPPOSITE_EDGE"); + write_bit("IFF.SRTYPE.SYNC"); + for (int i = 1; i <= 4; i++) { + write_bit("IFF.ZINIT_Q" + std::to_string(i), + !bool_or_default(ci->params, ctx->idf("INIT_Q%d", i), false)); + write_bit("IFF.ZSRVAL_Q" + std::to_string(i), + !bool_or_default(ci->params, ctx->idf("SRVAL_Q%d", i), false)); + } + write_bit("IFF.ZINV_C", !bool_or_default(ci->params, id_IS_CLK_INVERTED, false)); + write_bit("IFF.ZINV_OCLK", !bool_or_default(ci->params, id_IS_OCLK_INVERTED, false)); + + std::string iobdelay = str_or_default(ci->params, id_IOBDELAY, "NONE"); + write_bit("IFFDELMUXE3.P0", (iobdelay == "IFD")); + write_bit("ZINV_D", !bool_or_default(ci->params, id_IS_D_INVERTED, false) && (iobdelay != "IFD")); + + push("ISERDES"); + write_bit("IN_USE"); + int width = int_or_default(ci->params, id_DATA_WIDTH, 8); + std::string mode = str_or_default(ci->params, id_INTERFACE_TYPE, "NETWORKING"); + std::string rate = str_or_default(ci->params, id_DATA_RATE, "DDR"); + write_bit(mode + "." + rate + ".W" + std::to_string(width)); + write_bit("MODE." + str_or_default(ci->params, id_SERDES_MODE, "MASTER")); + write_bit("NUM_CE.N" + std::to_string(int_or_default(ci->params, id_NUM_CE, 1))); + pop(); + } else if (ci->type == id_IDELAYE2_IDELAYE2) { + write_bit("IN_USE"); + write_bit("CINVCTRL_SEL", str_or_default(ci->params, id_CINVCTRL_SEL, "FALSE") == "TRUE"); + write_bit("PIPE_SEL", str_or_default(ci->params, id_PIPE_SEL, "FALSE") == "TRUE"); + write_bit("HIGH_PERFORMANCE_MODE", str_or_default(ci->params, id_HIGH_PERFORMANCE_MODE, "FALSE") == "TRUE"); + write_bit("DELAY_SRC_" + str_or_default(ci->params, id_DELAY_SRC, "IDATAIN")); + write_bit("IDELAY_TYPE_" + str_or_default(ci->params, id_IDELAY_TYPE, "FIXED")); + write_int_vector("IDELAY_VALUE[4:0]", int_or_default(ci->params, id_IDELAY_VALUE, 0), 5, false); + write_int_vector("ZIDELAY_VALUE[4:0]", int_or_default(ci->params, id_IDELAY_VALUE, 0), 5, true); + write_bit("IS_DATAIN_INVERTED", bool_or_default(ci->params, id_IS_DATAIN_INVERTED, false)); + write_bit("IS_IDATAIN_INVERTED", bool_or_default(ci->params, id_IS_IDATAIN_INVERTED, false)); + } else if (ci->type == id_ODELAYE2_ODELAYE2) { + write_bit("IN_USE"); + write_bit("CINVCTRL_SEL", str_or_default(ci->params, id_CINVCTRL_SEL, "FALSE") == "TRUE"); + write_bit("HIGH_PERFORMANCE_MODE", str_or_default(ci->params, id_HIGH_PERFORMANCE_MODE, "FALSE") == "TRUE"); + auto type = str_or_default(ci->params, id_ODELAY_TYPE, "FIXED"); + if (type != "FIXED") + write_bit("ODELAY_TYPE_" + type); + write_int_vector("ODELAY_VALUE[4:0]", int_or_default(ci->params, id_ODELAY_VALUE, 0), 5, false); + write_int_vector("ZODELAY_VALUE[4:0]", int_or_default(ci->params, id_ODELAY_VALUE, 0), 5, true); + write_bit("ZINV_ODATAIN", !bool_or_default(ci->params, id_IS_ODATAIN_INVERTED, false)); + } else { + NPNR_ASSERT_FALSE("unsupported IOLOGIC"); + } + pop(2); + } + + void write_io() + { + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id_PAD) { + write_io_config(ci); + blank(); + } else if (ci->type.in(id_ILOGICE3_IFF, id_OLOGICE2_OUTFF, id_OLOGICE3_OUTFF, id_OSERDESE2_OSERDESE2, + id_ISERDESE2_ISERDESE2, id_IDELAYE2_IDELAYE2, id_ODELAYE2_ODELAYE2)) { + write_iol_config(ci); + blank(); + } + } + for (auto &hclk : ioconfig_by_hclk) { + push(uarch->tile_name(hclk.first)); + write_bit("STEPDOWN", hclk.second.stepdown); + write_bit("VREF.V_675_MV", hclk.second.vref); + write_bit("ONLY_DIFF_IN_USE", hclk.second.only_diff); + write_bit("TMDS_33_IN_USE", hclk.second.tmds_33); + write_bit("LVDS_25_IN_USE", hclk.second.lvds_25); + pop(); + } + } + + std::vector used_wires_starting_with(int tile, const std::string &prefix, bool is_source) + { + std::vector wires; + if (!pips_by_tile.count(tile)) + return wires; + for (auto pip : pips_by_tile[tile]) { + auto &pd = chip_pip_info(ctx->chip_info, pip); + int wire_index = is_source ? pd.src_wire : pd.dst_wire; + std::string wire = IdString(chip_wire_info(ctx->chip_info, WireId(pip.tile, wire_index)).name).str(ctx); + if (boost::starts_with(wire, prefix)) + wires.push_back(wire); + } + return wires; + } + + void write_clocking() + { + std::string name, type; + + std::set all_gclk; + dict> hclk_by_row; + + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id_BUFGCTRL) { + push(uarch->tile_name(ci->bel.tile)); + auto xy = uarch->rel_site_loc(uarch->get_bel_site(ci->bel)); + push(stringf("BUFGCTRL.BUFGCTRL_X%dY%d", xy.x, xy.y)); + write_bit("IN_USE"); + write_bit("INIT_OUT", bool_or_default(ci->params, id_INIT_OUT)); + write_bit("IS_IGNORE0_INVERTED", bool_or_default(ci->params, id_IS_IGNORE0_INVERTED)); + write_bit("IS_IGNORE1_INVERTED", bool_or_default(ci->params, id_IS_IGNORE1_INVERTED)); + write_bit("ZINV_CE0", !bool_or_default(ci->params, id_IS_CE0_INVERTED)); + write_bit("ZINV_CE1", !bool_or_default(ci->params, id_IS_CE1_INVERTED)); + write_bit("ZINV_S0", !bool_or_default(ci->params, id_IS_S0_INVERTED)); + write_bit("ZINV_S1", !bool_or_default(ci->params, id_IS_S1_INVERTED)); + pop(2); + } else if (ci->type == id_PLLE2_ADV_PLLE2_ADV) { + write_pll(ci); + } + blank(); + } + + for (int tile = 0; tile < ctx->chip_info->tile_insts.ssize(); tile++) { + std::string name = uarch->tile_name(tile); + std::string type = ctx->get_tile_type(tile).str(ctx); + push(name); + if (type == "HCLK_L" || type == "HCLK_R" || type == "HCLK_L_BOT_UTURN" || type == "HCLK_R_BOT_UTURN") { + auto used_sources = used_wires_starting_with(tile, "HCLK_CK_", true); + push("ENABLE_BUFFER"); + for (auto s : used_sources) { + if (boost::contains(s, "BUFHCLK")) { + write_bit(s); + hclk_by_row[tile / ctx->chip_info->width].insert(s.substr(s.find("BUFHCLK"))); + } + } + pop(); + } else if (boost::starts_with(type, "CLK_HROW")) { + auto used_gclk = used_wires_starting_with(tile, "CLK_HROW_R_CK_GCLK", true); + auto used_ck_in = used_wires_starting_with(tile, "CLK_HROW_CK_IN", true); + for (auto s : used_gclk) { + write_bit(s + "_ACTIVE"); + all_gclk.insert(s.substr(s.find("GCLK"))); + } + for (auto s : used_ck_in) { + if (boost::contains(s, "HROW_CK_INT")) + continue; + write_bit(s + "_ACTIVE"); + } + } else if (boost::starts_with(type, "HCLK_CMT")) { + auto used_ccio = used_wires_starting_with(tile, "HCLK_CMT_CCIO", true); + for (auto s : used_ccio) { + write_bit(s + "_ACTIVE"); + write_bit(s + "_USED"); + } + auto used_hclk = used_wires_starting_with(tile, "HCLK_CMT_CK_", true); + for (auto s : used_hclk) { + if (boost::contains(s, "BUFHCLK")) { + write_bit(s + "_USED"); + hclk_by_row[tile / ctx->chip_info->width].insert(s.substr(s.find("BUFHCLK"))); + } + } + } + pop(); + blank(); + } + + for (int tile = 0; tile < ctx->chip_info->tile_insts.ssize(); tile++) { + std::string name = uarch->tile_name(tile); + std::string type = ctx->get_tile_type(tile).str(ctx); + push(name); + if (type == "CLK_BUFG_REBUF") { + for (auto &gclk : all_gclk) { + write_bit(gclk + "_ENABLE_ABOVE"); + write_bit(gclk + "_ENABLE_BELOW"); + } + } else if (boost::starts_with(type, "HCLK_CMT")) { + for (auto &hclk : hclk_by_row[tile / ctx->chip_info->width]) { + write_bit("HCLK_CMT_CK_" + hclk + "_USED"); + } + } + pop(); + blank(); + } + } + + void write_bram_width(CellInfo *ci, const std::string &name, bool is_36, bool is_y1) + { + int width = int_or_default(ci->params, ctx->id(name), 0); + if (width == 0) + return; + int actual_width = width; + if (is_36) { + if (width == 1) + actual_width = 1; + else + actual_width = width / 2; + } + if (((is_36 && width == 72) || (is_y1 && actual_width == 36)) && name == "READ_WIDTH_A") { + write_bit(name + "_18"); + } + if (actual_width == 36) { + write_bit("SDP_" + name.substr(0, name.length() - 2) + "_36"); + if (name.find("WRITE") == 0) { + write_bit(name.substr(0, name.size() - 1) + "A_18"); + write_bit(name.substr(0, name.size() - 1) + "B_18"); + } else if (name.find("READ") == 0) { + write_bit(name.substr(0, name.size() - 1) + "B_18"); + } + } else { + write_bit(name + "_" + std::to_string(actual_width)); + } + } + + void write_bram_init(int half, CellInfo *ci, bool is_36) + { + for (std::string mode : {"", "P"}) { + for (int i = 0; i < (mode == "P" ? 8 : 64); i++) { + bool has_init = false; + std::vector init_data(256, false); + if (is_36) { + for (int j = 0; j < 2; j++) { + IdString param = ctx->idf("INIT%s_%02X", mode.c_str(), i * 2 + j); + if (ci->params.count(param)) { + auto &init0 = ci->params.at(param); + has_init = true; + for (int k = half; k < 256; k += 2) { + if (k >= int(init0.str.size())) + break; + init_data[j * 128 + (k / 2)] = init0.str[k] == Property::S1; + } + } + } + } else { + IdString param = ctx->idf("INIT%s_%02X", mode.c_str(), i); + if (ci->params.count(param)) { + auto &init = ci->params.at(param); + has_init = true; + for (int k = 0; k < 256; k++) { + if (k >= int(init.str.size())) + break; + init_data[k] = init.str[k] == Property::S1; + } + } + } + if (has_init) + write_vector(stringf("INIT%s_%02X[255:0]", mode.c_str(), i), init_data); + } + } + } + + void write_bram_half(int tile, int half, CellInfo *ci) + { + push(uarch->tile_name(tile)); + push("RAMB18_Y" + std::to_string(half)); + if (ci != nullptr) { + bool is_36 = ci->type == id_RAMB36E1_RAMB36E1; + write_bit("IN_USE"); + write_bram_width(ci, "READ_WIDTH_A", is_36, half == 1); + write_bram_width(ci, "READ_WIDTH_B", is_36, half == 1); + write_bram_width(ci, "WRITE_WIDTH_A", is_36, half == 1); + write_bram_width(ci, "WRITE_WIDTH_B", is_36, half == 1); + write_bit("DOA_REG", bool_or_default(ci->params, id_DOA_REG, false)); + write_bit("DOB_REG", bool_or_default(ci->params, id_DOB_REG, false)); + for (auto &invpin : invertible_pins[ctx->id(ci->attrs[id_X_ORIG_TYPE].as_string())]) + write_bit("ZINV_" + invpin.str(ctx), + !bool_or_default(ci->params, ctx->id("IS_" + invpin.str(ctx) + "_INVERTED"), false)); + for (auto wrmode : {"WRITE_MODE_A", "WRITE_MODE_B"}) { + std::string mode = str_or_default(ci->params, ctx->id(wrmode), "WRITE_FIRST"); + if (mode != "WRITE_FIRST") + write_bit(std::string(wrmode) + "_" + mode); + } + write_vector("ZINIT_A[17:0]", std::vector(18, true)); + write_vector("ZINIT_B[17:0]", std::vector(18, true)); + write_vector("ZSRVAL_A[17:0]", std::vector(18, true)); + write_vector("ZSRVAL_B[17:0]", std::vector(18, true)); + + write_bram_init(half, ci, is_36); + } + pop(); + if (half == 0) { + auto used_rdaddrcasc = used_wires_starting_with(tile, "BRAM_CASCOUT_ADDRARDADDR", false); + auto used_wraddrcasc = used_wires_starting_with(tile, "BRAM_CASCOUT_ADDRBWRADDR", false); + write_bit("CASCOUT_ARD_ACTIVE", !used_rdaddrcasc.empty()); + write_bit("CASCOUT_BWR_ACTIVE", !used_wraddrcasc.empty()); + } + pop(); + } + + void write_bram() + { + for (int tile = 0; tile < ctx->chip_info->tile_insts.ssize(); tile++) { + IdString type = ctx->get_tile_type(tile); + if (type.in(id_BRAM_L, id_BRAM_R)) { + CellInfo *l = nullptr, *u = nullptr; + const auto &bts = uarch->tile_status[tile].bts; + if (bts) { + if (bts->cells[BEL_RAM36] != nullptr) { + l = bts->cells[BEL_RAM36]; + u = bts->cells[BEL_RAM36]; + } else { + l = bts->cells[BEL_RAM18_L]; + u = bts->cells[BEL_RAM18_U]; + } + } + write_bram_half(tile, 0, l); + write_bram_half(tile, 1, u); + blank(); + } + } + } + + double float_or_default(CellInfo *ci, const std::string &name, double def) + { + IdString p = ctx->id(name); + if (!ci->params.count(p)) + return def; + auto &prop = ci->params.at(p); + if (prop.is_string) + return std::stod(prop.as_string()); + else + return prop.as_int64(); + } + + void write_pll_clkout(const std::string &name, CellInfo *ci) + { + // FIXME: variable duty cycle + int high = 1, low = 1, phasemux = 0, delaytime = 0, frac = 0; + bool no_count = false, edge = false; + double divide = float_or_default(ci, name + ((name == "CLKFBOUT") ? "_MULT" : "_DIVIDE"), 1); + double phase = float_or_default(ci, name + "_PHASE", 1); + if (divide <= 1) { + no_count = true; + } else { + high = floor(divide / 2); + low = int(floor(divide) - high); + if (high != low) + edge = true; + if (name == "CLKOUT1" || name == "CLKFBOUT") + frac = floor(divide * 8) - floor(divide) * 8; + int phase_eights = floor((phase / 360) * divide * 8); + phasemux = phase_eights % 8; + delaytime = phase_eights / 8; + } + bool used = false; + if (name == "DIVCLK" || name == "CLKFBOUT") { + used = true; + } else { + used = ci->getPort(ctx->id(name)) != nullptr; + } + if (name == "DIVCLK") { + write_int_vector("DIVCLK_DIVCLK_HIGH_TIME[5:0]", high, 6); + write_int_vector("DIVCLK_DIVCLK_LOW_TIME[5:0]", low, 6); + write_bit("DIVCLK_DIVCLK_EDGE[0]", edge); + write_bit("DIVCLK_DIVCLK_NO_COUNT[0]", no_count); + } else if (used) { + write_bit(name + "_CLKOUT1_OUTPUT_ENABLE[0]"); + write_int_vector(name + "_CLKOUT1_HIGH_TIME[5:0]", high, 6); + write_int_vector(name + "_CLKOUT1_LOW_TIME[5:0]", low, 6); + write_int_vector(name + "_CLKOUT1_PHASE_MUX[2:0]", phasemux, 3); + write_bit(name + "_CLKOUT2_EDGE[0]", edge); + write_bit(name + "_CLKOUT2_NO_COUNT[0]", no_count); + write_int_vector(name + "_CLKOUT2_DELAY_TIME[5:0]", delaytime, 6); + if (frac != 0) { + write_bit(name + "_CLKOUT2_FRAC_EN[0]", edge); + write_int_vector(name + "_CLKOUT2_FRAC[2:0]", frac, 3); + } + } + } + + void write_pll(CellInfo *ci) + { + push(uarch->tile_name(ci->bel.tile)); + push("PLLE2_ADV"); + write_bit("IN_USE"); + // FIXME: should be INV not ZINV (XRay error?) + write_bit("ZINV_PWRDWN", bool_or_default(ci->params, id_IS_PWRDWN_INVERTED, false)); + write_bit("ZINV_RST", bool_or_default(ci->params, id_IS_RST_INVERTED, false)); + write_bit("INV_CLKINSEL", bool_or_default(ci->params, id_IS_CLKINSEL_INVERTED, false)); + write_pll_clkout("DIVCLK", ci); + write_pll_clkout("CLKFBOUT", ci); + write_pll_clkout("CLKOUT0", ci); + write_pll_clkout("CLKOUT1", ci); + write_pll_clkout("CLKOUT2", ci); + write_pll_clkout("CLKOUT3", ci); + write_pll_clkout("CLKOUT4", ci); + write_pll_clkout("CLKOUT5", ci); + + std::string comp = str_or_default(ci->params, id_COMPENSATION, "INTERNAL"); + push("COMPENSATION"); + if (comp == "INTERNAL") { + // write_bit("INTERNAL"); + write_bit("Z_ZHOLD_OR_CLKIN_BUF"); + } else { + NPNR_ASSERT_FALSE("unsupported compensation type"); + } + pop(); + + // FIXME: should these be calculated somehow? + write_int_vector("FILTREG1_RESERVED[11:0]", 0x8, 12); + write_int_vector("LKTABLE[39:0]", 0xB5BE8FA401ULL, 40); + write_bit("LOCKREG3_RESERVED[0]"); + write_int_vector("TABLE[9:0]", 0x3B4, 10); + pop(2); + } + + void write_dsp_cell(CellInfo *ci) + { + auto tile_name = uarch->tile_name(ci->bel.tile); + auto tile_side = tile_name.at(4); + push(tile_name); + push("DSP48"); + auto xy = uarch->rel_site_loc(uarch->get_bel_site(ci->bel)); + auto dsp = stringf("DSP_%d", xy.y); + push(dsp); + + auto write_bus_zinv = [&](std::string name, int width) { + for (int i = 0; i < width; i++) { + std::string b = stringf("[%d]", i); + bool inv = (int_or_default(ci->params, ctx->id("IS_" + name + "_INVERTED"), 0) >> i) & 0x1; + inv |= bool_or_default(ci->params, ctx->id("IS_" + name + b + "_INVERTED"), false); + write_bit("ZIS_" + name + "_INVERTED" + b, !inv); + } + }; + + // value 1 is equivalent to 2, according to UG479 + // but in real life, Vivado sets AREG_0 is 0, + // no bit is 1, and AREG_2 is 2 + auto areg = int_or_default(ci->params, ctx->id("AREG"), 1); + if (areg == 0 or areg == 2) + write_bit("AREG_" + std::to_string(areg)); + + auto ainput = str_or_default(ci->params, ctx->id("A_INPUT"), "DIRECT"); + if (ainput == "CASCADE") + write_bit("A_INPUT[0]"); + + // value 1 is equivalent to 2, according to UG479 + // but in real life, Vivado sets AREG_0 is 0, + // no bit is 1, and AREG_2 is 2 + auto breg = int_or_default(ci->params, ctx->id("BREG"), 1); + if (breg == 0 or breg == 2) + write_bit("BREG_" + std::to_string(breg)); + + auto binput = str_or_default(ci->params, ctx->id("B_INPUT"), "DIRECT"); + if (binput == "CASCADE") + write_bit("B_INPUT[0]"); + + auto use_dport = str_or_default(ci->params, ctx->id("USE_DPORT"), "FALSE"); + if (use_dport == "TRUE") + write_bit("USE_DPORT[0]"); + + auto use_simd = str_or_default(ci->params, ctx->id("USE_SIMD"), "ONE48"); + if (use_simd == "TWO24") + write_bit("USE_SIMD_FOUR12_TWO24"); + if (use_simd == "FOUR12") + write_bit("USE_SIMD_FOUR12"); + + // PATTERN + auto pattern_str = str_or_default(ci->params, ctx->id("PATTERN"), ""); + if (!boost::empty(pattern_str)) { + const size_t pattern_size = 48; + std::vector pattern_vector(pattern_size, true); + size_t i = 0; + for (auto it = pattern_str.crbegin(); it != pattern_str.crend() && i < pattern_size; ++i, ++it) { + pattern_vector[i] = *it == '1'; + } + write_vector("PATTERN[47:0]", pattern_vector); + } + + auto autoreset_patdet = str_or_default(ci->params, ctx->id("AUTORESET_PATDET"), "NO_RESET"); + if (autoreset_patdet == "RESET_MATCH") + write_bit("AUTORESET_PATDET_RESET"); + if (autoreset_patdet == "RESET_NOT_MATCH") + write_bit("AUTORESET_PATDET_RESET_NOT_MATCH"); + + // MASK + auto mask_str = str_or_default(ci->params, ctx->id("MASK"), "001111111111111111111111111111111111111111111111"); + // Yosys gives us 48 bit, but prjxray only recognizes 46 bits + // The most significant two bits seem to be zero, so let us just truncate them + const size_t mask_size = 46; + std::vector mask_vector(mask_size, true); + size_t i = 0; + for (auto it = mask_str.crbegin(); it != mask_str.crend() && i < mask_size; ++i, ++it) { + mask_vector[i] = *it == '1'; + } + write_vector("MASK[45:0]", mask_vector); + + auto sel_mask = str_or_default(ci->params, ctx->id("SEL_MASK"), "MASK"); + if (sel_mask == "C") + write_bit("SEL_MASK_C"); + if (sel_mask == "ROUNDING_MODE1") + write_bit("SEL_MASK_ROUNDING_MODE1"); + if (sel_mask == "ROUNDING_MODE2") + write_bit("SEL_MASK_ROUNDING_MODE2"); + + write_bit("ZADREG[0]", !bool_or_default(ci->params, ctx->id("ADREG"), true)); + write_bit("ZALUMODEREG[0]", !bool_or_default(ci->params, ctx->id("ALUMODEREG"))); + write_bit("ZAREG_2_ACASCREG_1", !bool_or_default(ci->params, ctx->id("ACASCREG"))); + write_bit("ZBREG_2_BCASCREG_1", !bool_or_default(ci->params, ctx->id("BCASCREG"))); + write_bit("ZCARRYINREG[0]", !bool_or_default(ci->params, ctx->id("CARRYINREG"))); + write_bit("ZCARRYINSELREG[0]", !bool_or_default(ci->params, ctx->id("CARRYINSELREG"))); + write_bit("ZCREG[0]", !bool_or_default(ci->params, ctx->id("CREG"), true)); + write_bit("ZDREG[0]", !bool_or_default(ci->params, ctx->id("DREG"), true)); + write_bit("ZINMODEREG[0]", !bool_or_default(ci->params, ctx->id("INMODEREG"))); + write_bus_zinv("ALUMODE", 4); + write_bus_zinv("INMODE", 5); + write_bus_zinv("OPMODE", 7); + write_bit("ZMREG[0]", !bool_or_default(ci->params, ctx->id("MREG"))); + write_bit("ZOPMODEREG[0]", !bool_or_default(ci->params, ctx->id("OPMODEREG"))); + write_bit("ZPREG[0]", !bool_or_default(ci->params, ctx->id("PREG"))); + write_bit("USE_DPORT[0]", str_or_default(ci->params, ctx->id("USE_DPORT"), "FALSE") == "TRUE"); + write_bit("ZIS_CLK_INVERTED", !bool_or_default(ci->params, ctx->id("IS_CLK_INVERTED"))); + write_bit("ZIS_CARRYIN_INVERTED", !bool_or_default(ci->params, ctx->id("IS_CARRYIN_INVERTED"))); + pop(2); + + auto write_const_pins = [&](std::string const_net_name) { + std::vector pins; + const auto attr_name = "DSP_" + const_net_name + "_PINS"; + const auto attr_value = str_or_default(ci->attrs, ctx->id(attr_name), ""); + boost::split(pins, attr_value, boost::is_any_of(" ")); + for (auto pin : pins) { + if (boost::empty(pin)) + continue; + auto pin_basename = pin; + boost::erase_all(pin_basename, "0123456789"); + auto inv = bool_or_default(ci->params, ctx->id("IS_" + pin_basename + "_INVERTED"), 0); + auto net_name = inv ? (const_net_name == "GND" ? "VCC" : "GND") : const_net_name; + write_bit(stringf("%s_%s.DSP_%s_%c", dsp.c_str(), pin.c_str(), net_name.c_str(), tile_side)); + } + }; + + write_const_pins("GND"); + write_const_pins("VCC"); + + pop(); + } + + void write_ip() + { + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id_DSP48E1_DSP48E1) { + write_dsp_cell(ci); + blank(); + } + } + } + + void write_fasm() + { + get_invertible_pins(ctx, invertible_pins); + write_logic(); + write_io(); + write_routing(); + write_bram(); + write_clocking(); + write_ip(); + } +}; + +} // namespace + +void XilinxImpl::write_fasm(const std::string &filename) +{ + std::ofstream out(filename); + if (!out) + log_error("failed to open file %s for writing (%s)\n", filename.c_str(), strerror(errno)); + + FasmBackend be(this->ctx, this, out); + be.write_fasm(); +} + +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/xilinx/gen/filters.py b/himbaechel/uarch/xilinx/gen/filters.py new file mode 100644 index 0000000000..ba74546d17 --- /dev/null +++ b/himbaechel/uarch/xilinx/gen/filters.py @@ -0,0 +1,106 @@ +# Mapping rules from prjxray to nextpnr + +def get_bel_z_override(bel, default_z): + s = bel.site + t = s.tile + bt = bel.bel_type() + bn = bel.name() + if t.tile_type() == "BRAM_L" or t.tile_type() == "BRAM_R": + is_top18 = (s.primary.site_type() == "RAMB18E1") + if bt == "RAMBFIFO36E1_RAMBFIFO36E1": + return 0 + elif bt == "RAMB36E1_RAMB36E1": + return 1 + elif bt == "FIFO36E1_FIFO36E1": + return 2 + elif bt == "RAMB18E1_RAMB18E1": + return (5 if is_top18 else 9) + elif bt == "FIFO18E1_FIFO18E1": + return 10 + if s.site_type() == "SLICEL" or s.site_type() == "SLICEM": + is_upper_site = (s.rel_xy()[0] == 1) + subslices = "ABCD" + postfixes = ["6LUT", "5LUT", "FF", "5FF"] + for i, pf in enumerate(postfixes): + if len(bn) == len(pf) + 1 and bn[1:] == pf: + return (64 if is_upper_site else 0) | (subslices.index(bn[0]) << 4) | i + if bn == "F7AMUX": + return (0x47 if is_upper_site else 0x07) + elif bn == "F7BMUX": + return (0x67 if is_upper_site else 0x27) + elif bn == "F8MUX": + return (0x48 if is_upper_site else 0x08) + elif bn == "CARRY4": + return (0x4F if is_upper_site else 0x0F) + # Other bels (e.g. extra xc7 routing bels) can be ignored for nextpnr porpoises + return -1 + return default_z + +def get_bel_type_override(bt): + if bt.endswith("6LUT") or bt == "LUT_OR_MEM6" or bt == "LUT6": + return "SLICE_LUTX" + elif bt.endswith("5LUT") or bt == "LUT_OR_MEM5" or bt == "LUT5": + return "SLICE_LUTX" + elif len(bt) == 4 and bt.endswith("FF2"): + return "SLICE_FFX" + elif len(bt) == 3 and bt.endswith("FF"): + return "SLICE_FFX" + elif bt == "FF_INIT" or bt == "REG_INIT": + return "SLICE_FFX" + + iolParts = ["COMBUF_", "IDDR_", "IPFF_", "OPFF_", "OPTFF_", "TFF_"] + for p in iolParts: + if bt.startswith(p): + return "IOL_" + p.replace("_", "") + if bt.endswith("_VREF"): + return "IOB_VREF" + elif bt.endswith("_DIFFINBUF"): + return "IOB_DIFFINBUF" + elif bt.startswith("PSS_ALTO_CORE_PAD_"): + return "PSS_PAD" + elif bt.startswith("LAGUNA_RX_REG") or bt.startswith("LAGUNA_TX_REG"): + return "LAGUNA_REGX" + elif bt.startswith("BSCAN"): + return "BSCAN" + elif bt == "BUFGCTRL_BUFGCTRL": + return "BUFGCTRL" + elif bt == "RAMB18E2_U_RAMB18E2" or bt == "RAMB18E2_L_RAMB18E2": + return "RAMB18E2_RAMB18E2" + else: + return bt + +def include_pip(tile_type, p): + is_xc7_logic = (tile_type in ("CLBLL_L", "CLBLL_R", "CLBLM_L", "CLBLM_R")) + if p.is_route_thru() and p.src_wire().name().endswith("_CE_INT"): + return False + if p.is_route_thru() and is_xc7_logic: + return False + if p.is_route_thru() and "TFB" in p.dst_wire().name(): + return False + if p.src_wire().name().startswith("CLK_BUFG_R_FBG_OUT"): + return False + if "CLK_HROW_CK_INT" in p.src_wire().name(): + return False + if tile_type.startswith("HCLK_CMT") and "FREQ_REF" in p.dst_wire().name(): + return False + if tile_type.startswith("CMT_TOP_L_LOWER"): + return False + if tile_type.startswith("CLK_HROW_TOP"): + if "CK_BUFG_CASCO" in p.dst_wire().name() and "CK_BUFG_CASCIN" in p.src_wire().name(): + return False + if tile_type.startswith("HCLK_IOI"): + if "RCLK_BEFORE_DIV" in p.dst_wire().name() and "IMUX" in p.src_wire().name(): + return False + if "IOI" in tile_type: + if "CLKB" in p.dst_wire().name() and "IMUX22" in p.src_wire().name(): + return False + if "OCLKB" in p.dst_wire().name() and "IOI_OCLK_" in p.src_wire().name(): + return False + if "OCLKM" in p.dst_wire().name() and "IMUX31" in p.src_wire().name(): + return False + if "CMT_TOP_R" in tile_type: + if "PLLOUT_CLK_FREQ_BB_REBUFOUT" in p.dst_wire().name(): + return False + if "MMCM_CLK_FREQ_BB" in p.dst_wire().name(): + return False + return True \ No newline at end of file diff --git a/himbaechel/uarch/xilinx/gen/parse_sdf.py b/himbaechel/uarch/xilinx/gen/parse_sdf.py new file mode 100644 index 0000000000..2eea49ecda --- /dev/null +++ b/himbaechel/uarch/xilinx/gen/parse_sdf.py @@ -0,0 +1,134 @@ +""" +Utilities for SDF file parsing to determine cell timings +""" +import sys + + +class SDFData: + def __init__(self): + self.cells = {} + +class Delay: + def __init__(self, minv, typv, maxv): + self.minv = minv + self.typv = typv + self.maxv = maxv + + +class IOPath: + def __init__(self, from_pin, to_pin, rising, falling): + self.from_pin = from_pin + self.to_pin = to_pin + self.rising = rising + self.falling = falling + + +class SetupHoldCheck: + def __init__(self, pin, clock, setup, hold): + self.pin = pin + self.clock = clock + self.setup = setup + self.hold = hold + + +class WidthCheck: + def __init__(self, clock, width): + self.clock = clock + self.width = width + + +class Interconnect: + def __init__(self, from_net, to_net, rising, falling): + self.from_net = from_net + self.to_net = to_net + self.rising = rising + self.falling = falling + + +class CellData: + def __init__(self, celltype, inst): + self.type = celltype + self.inst = inst + self.entries = [] + self.interconnect = {} + + +def parse_sexpr(stream): + content = [] + buffer = "" + instr = False + while True: + c = stream.read(1) + assert c != "", "unexpected end of file" + if instr: + if c == '"': + instr = False + else: + buffer += c + else: + if c == '(': + content.append(parse_sexpr(stream)) + elif c == ')': + if buffer != "": + content.append(buffer) + return content + elif c.isspace(): + if buffer != "": + content.append(buffer) + buffer = "" + elif c == '"': + instr = True + else: + buffer += c + + +def parse_sexpr_file(filename): + with open(filename, 'r') as f: + c = f.read(1) + while c != '(': + assert c == ' ' or c == '\n' or c == '\t' + c = f.read(1) + return parse_sexpr(f) + + +def parse_delay(delay): + sp = [float(x) if x != '' else None for x in delay.split(":")] + assert len(sp) == 3 + return Delay(sp[0], sp[1], sp[2]) + + +def parse_sdf_file(filename): + sdata = parse_sexpr_file(filename) + assert sdata[0] == "DELAYFILE" + sdf = SDFData() + for entry in sdata[1:]: + if entry[0] != "CELL": + continue + assert entry[1][0] == "CELLTYPE" + celltype = entry[1][1] + assert entry[2][0] == "INSTANCE" + if len(entry[2]) > 1: + inst = entry[2][1] + else: + inst = "top" + cell = CellData(celltype, inst) + for subentry in entry[3:]: + if subentry[0] == "DELAY": + assert subentry[1][0] == "ABSOLUTE" + for delay in subentry[1][1:]: + if delay[0] == "IOPATH": + cell.entries.append( + IOPath(delay[1], delay[2], parse_delay(delay[3][0]), parse_delay(delay[4][0]))) + elif delay[0] == "INTERCONNECT": + cell.interconnect[(delay[1], delay[2])] = Interconnect(delay[1], delay[2], + parse_delay(delay[3][0]), + parse_delay(delay[4][0])) + elif subentry[0] == "TIMINGCHECK": + for check in subentry[1:]: + if check[0] == "SETUPHOLD": + cell.entries.append( + SetupHoldCheck(check[1], check[2], parse_delay(check[3][0]), parse_delay(check[4][0]))) + elif check[0] == "WIDTH": + cell.entries.append(WidthCheck(check[1], parse_delay(check[2][0]))) + sdf.cells[(celltype, inst)] = cell + return sdf diff --git a/himbaechel/uarch/xilinx/gen/tileconn.py b/himbaechel/uarch/xilinx/gen/tileconn.py new file mode 100644 index 0000000000..2b1c7d147c --- /dev/null +++ b/himbaechel/uarch/xilinx/gen/tileconn.py @@ -0,0 +1,38 @@ +import json + +def apply_tileconn(f, d): + def merge_nodes(a, b): + for bwire in b.wires: + bwire.tile.wire_to_node[bwire.index] = a + a.wires.append(bwire) + b.wires = [] + + tj = json.load(f) + # Restructure to tiletype -> coord offset -> type -> wire_pairs + ttn = {} + for entry in tj: + tile0, tile1 = entry["tile_types"] + dx, dy = entry["grid_deltas"] + if tile0 not in ttn: + ttn[tile0] = {} + if (dx, dy) not in ttn[tile0]: + ttn[tile0][dx, dy] = {} + ttn[tile0][dx, dy][tile1] = entry["wire_pairs"] + for tile in d.tiles: + tt = tile.tile_type() + if tt not in ttn: + continue + # Search the neighborhood around a tile + for dxy, nd in sorted(ttn[tt].items()): + nx = tile.x + dxy[0] + ny = tile.y + dxy[1] + if (nx, ny) not in d.tiles_by_xy: + continue + ntile = d.tiles_by_xy[nx, ny] + ntt = ntile.tile_type() + if ntt not in nd: + continue + # Found a pair with connections + wc = nd[ntt] + for wirea, wireb in wc: + merge_nodes(tile.wire(wirea).node(), ntile.wire(wireb).node()) diff --git a/himbaechel/uarch/xilinx/gen/xilinx_device.py b/himbaechel/uarch/xilinx/gen/xilinx_device.py new file mode 100644 index 0000000000..fc61ba89a4 --- /dev/null +++ b/himbaechel/uarch/xilinx/gen/xilinx_device.py @@ -0,0 +1,544 @@ +import json +import os +import re +from enum import Enum +from tileconn import apply_tileconn +from parse_sdf import parse_sdf_file +from dataclasses import dataclass +from typing import Optional +# Represents Xilinx device data from PrjXray etc + +@dataclass +class WireData: + index: int + name: str + intent: str = "" + tied_value: Optional[int] = None + resistance: float = 0 + capacitance: float = 0 + +@dataclass +class PIPData: + index: int + from_wire: int + to_wire: int + is_bidi: bool = False + is_route_thru: bool = False + is_buffered: bool = False + min_delay: float = 0 + max_delay: float = 0 + resistance: float = 0 + capacitance: float = 0 + +@dataclass +class SiteWireData: + name: str + is_pin: bool = False + +@dataclass +class SiteBELPinData: + name: str + pindir: str + site_wire_idx: int + +@dataclass +class SiteBELData: + name: str + bel_type: str + bel_class: str + pins: list[SiteBELPinData] + +@dataclass +class SitePIPData: + bel_idx: int + bel_input: str + from_wire_idx: int + to_wire_idx: int + +@dataclass +class SitePinData: + name: str + pindir: str + site_wire_idx: int + prim_pin_name: str + +class SiteData: + def __init__(self, site_type): + self.site_type = site_type + self.wires = [] + self.bels = [] + self.pips = [] + self.pins = [] + self.variants = {} + +class TileSitePinData: + def __init__(self, wire_idx): + self.wire_idx = wire_idx + self.min_delay = 0 + self.max_delay = 0 + self.resistance = 0 + self.capacitance = 0 + +class TileData: + def __init__(self, tile_type): + self.tile_type = tile_type + self.wires = [] + self.wires_by_name = {} + self.pips = [] + self.sitepin_data = {} # (type, relxy, pin) -> TileSitePinData + self.cell_timing = None + +class PIP: + def __init__(self, tile, index): + self.tile = tile + self.index = index + self.data = tile.get_pip_data(index) + def src_wire(self): + return Wire(self.tile, self.data.from_wire) + def dst_wire(self): + return Wire(self.tile, self.data.to_wire) + def is_route_thru(self): + return self.data.is_route_thru + def is_bidi(self): + return self.data.is_bidi + def is_buffered(self): + return self.data.is_buffered + def min_delay(self): + return self.data.min_delay + def max_delay(self): + return self.data.max_delay + def resistance(self): + return self.data.resistance + def capacitance(self): + return self.data.capacitance + +class Wire: + def __init__(self, tile, index): + self.tile = tile + self.index = index + self.data = tile.get_wire_data(index) + def name(self): + return self.data.name + def intent(self): + return self.data.intent + def node(self): + if self.index not in self.tile.wire_to_node: + self.tile.wire_to_node[self.index] = Node(self.tile, [self]) + return self.tile.wire_to_node[self.index] + def is_gnd(self): + return "GND_WIRE" in self.name() + def is_vcc(self): + return "VCC_WIRE" in self.name() + def resistance(self): + return self.data.resistance + def capacitance(self): + return self.data.capacitance + +class SiteWire: + def __init__(self, site, index): + self.site = site + self.index = index + self.data = self.site.get_wire_data(index) + def name(self): + return self.data.name + +class SiteBELPin: + def __init__(self, bel, name): + self.bel = bel + self.name = name + self.data = self.bel.data.pins[name] + def name(self): + return self.name + def dir(self): + return self.data.pindir + def site_wire(self): + return SiteWire(self.bel.site, self.data.site_wire_idx) + +class SiteBEL: + def __init__(self, site, index): + self.site = site + self.index = index + self.data = site.get_bel_data(index) + def name(self): + return self.data.name + def bel_type(self): + return self.data.bel_type + def bel_class(self): + return self.data.bel_class + def pins(self): + return (SiteBELPin(self, n) for n in self.data.pins.keys()) + +class SitePIP: + def __init__(self, site, index): + self.site = site + self.index = index + self.data = site.get_pip_data(index) + def bel(self): + return SiteBEL(self.site, self.data.bel_idx) + def bel_input(self): + return self.data.bel_input + def src_wire(self): + return SiteWire(self.site, self.data.from_wire_idx) + def dst_wire(self): + return SiteWire(self.site, self.data.to_wire_idx) + +class SitePin: + def __init__(self, site, index): + self.site = site + self.index = index + self.data = site.data.pins[index] + def name(self): + return self.data.name + def dir(self): + return self.data.pindir + def site_wire(self): + return SiteWire(self.site, self.data.site_wire_idx) + def tile_wire(self): + return self.site.tile.site_pin_wire(self.site.primary.site_type(), self.site.rel_xy(), self.data.prim_pin_name) + def min_delay(self): + return self.site.tile.site_pin_timing(self.site.primary.site_type(), self.site.rel_xy(), self.data.prim_pin_name).min_delay + def max_delay(self): + return self.site.tile.site_pin_timing(self.site.primary.site_type(), self.site.rel_xy(), self.data.prim_pin_name).max_delay + def resistance(self): + return self.site.tile.site_pin_timing(self.site.primary.site_type(), self.site.rel_xy(), self.data.prim_pin_name).resistance + def capacitance(self): + return self.site.tile.site_pin_timing(self.site.primary.site_type(), self.site.rel_xy(), self.data.prim_pin_name).capacitance + +class Site: + def __init__(self, tile, name, index, grid_xy, data, primary=None): + self.tile = tile + self.name = name + self.index = index + self.prefix = name[0:name.rfind('_')] + self.grid_xy = grid_xy + self.data = data + self.primary = primary if primary is not None else self + self._rel_xy = None # filled later + self._variants = None #filled later + def get_bel_data(self, index): + return self.data.bels[index] + def get_wire_data(self, index): + return self.data.wires[index] + def get_pip_data(self, index): + return self.data.pips[index] + def site_type(self): + return self.data.site_type + def rel_xy(self): + if self._rel_xy is None: + base_x = 999999 + base_y = 999999 + for site in self.tile.sites(): + if site.prefix != self.prefix: + continue + base_x = min(base_x, site.grid_xy[0]) + base_y = min(base_y, site.grid_xy[1]) + self._rel_xy = (self.grid_xy[0] - base_x, self.grid_xy[1] - base_y) + return self._rel_xy + def bels(self): + return (SiteBEL(self, i) for i in range(len(self.data.bels))) + def wires(self): + return (SiteWire(self, i) for i in range(len(self.data.wires))) + def pips(self): + return (SitePIP(self, i) for i in range(len(self.data.pips))) + def pins(self): + return (SitePin(self, i) for i in range(len(self.data.pins))) + def pin(self, p): + for i in range(len(self.data.pins)): + if self.data.pins[i].name == p: + return SitePin(self, i) + return None + def available_variants(self): + # Make sure primary type is first + if self._variants is None: + self._variants = [] + self._variants.append(self.site_type()) + for var in sorted(self.data.variants.keys()): + if var != self.site_type(): + self._variants.append(var) + return self._variants + def variant(self, vtype): + vsite = Site(self.tile, self.name, self.index, self.grid_xy, self.data.variants[vtype], self) + return vsite + def rel_name(self): + x, y = self.rel_xy() + return f"{self.prefix}_X{x}Y{y}" + +class Tile: + def __init__(self, x, y, name, data, interconn_xy, site_insts): + self.x = x + self.y = y + self.name = name + self.data = data + self.interconn_xy = interconn_xy + self.site_insts = site_insts + self.wire_to_node = {} + self.node_autoidx = 0 + self.used_wires = None + def get_pip_data(self, i): + return self.data.pips[i] + def get_wire_data(self, i): + return self.data.wires[i] + def tile_type(self): + return self.data.tile_type + def wires(self): + return (Wire(self, i) for i in range(len(self.data.wires))) + def wire(self, name): + return Wire(self, self.data.wires_by_name[name].index) + def pips(self): + return (PIP(self, i) for i in range(len(self.data.pips))) + def sites(self): + return self.site_insts + def site_pin_wire(self, sitetype, rel_xy, pin): + wire_idx = self.data.sitepin_data[(sitetype, rel_xy, pin)].wire_idx + return Wire(self, wire_idx) if wire_idx is not None else None + def site_pin_timing(self, sitetype, rel_xy, pin): + return self.data.sitepin_data[(sitetype, rel_xy, pin)] + def cell_timing(self): + return self.data.cell_timing + def used_wire_indices(self): + if self.used_wires is None: + self.used_wires = set() + for pip in self.pips(): + self.used_wires.add(pip.src_wire().index) + self.used_wires.add(pip.dst_wire().index) + for site in self.sites(): + for v in site.available_variants(): + variant = site.variant(v) + for pin in variant.pins(): + if pin.tile_wire() is not None: + self.used_wires.add(pin.tile_wire().index) + return self.used_wires + def split_name(self): + prefix, xy = self.name.rsplit("_", 1) + xy_m = re.match(r"X(\d+)Y(\d+)", xy) + return prefix, int(xy_m.group(1)), int(xy_m.group(2)) + +class Node: + def __init__(self, tile, wires=[]): + self.tile = tile + self.index = tile.node_autoidx + tile.node_autoidx += 1 + self.wires = wires + def unique_index(self): + return (self.tile.y << 48) | (self.tile.x << 32) | self.index + def is_vcc(self): + for wire in self.wires: + if wire.is_vcc(): + return True + return False + def is_gnd(self): + for wire in self.wires: + if wire.is_gnd(): + return True + return False + +class Package: + def __init__(self, name): + self.name = name + self.pin_map = {} + +class Device: + def __init__(self, name): + self.name = name + self.tiles = [] + self.tiles_by_name = {} + self.tiles_by_xy = {} + self.sites_by_name = {} + self.width = 0 + self.height = 0 + self.packages = {} + def tile(self, name): + return self.tiles_by_name[name] + def site(self, name): + return self.sites_by_name[name] + +def import_device(fabricname, prjxray_root, metadata_root): + site_type_cache = {} + tile_type_cache = {} + tile_json_cache = {} + def parse_xy(xy): + xpos = xy.rfind("X") + ypos = xy.rfind("Y") + return int(xy[xpos+1:ypos]), int(xy[ypos+1:]) + + def get_site_type_data(sitetype): + if sitetype not in site_type_cache: + sd = SiteData(sitetype) + sp = metadata_root + "/site_type_" + sitetype + ".json" + if os.path.exists(sp): + with open(sp, "r") as jf: + sj = json.load(jf) + for vtype, vdata in sorted(sj.items()): # Consider all site variants + if vtype == sitetype: + vd = sd # primary variant + else: + vd = SiteData(vtype) + site_wire_by_name = {} + def wire_index(name): + if name not in site_wire_by_name: + idx = len(vd.wires) + vd.wires.append(SiteWireData(name=name)) + site_wire_by_name[name] = idx + return site_wire_by_name[name] + # Import bels + bel_idx_by_name = {} + for bel, beldata in sorted(vdata["bels"].items()): + belpins = {} + for pin, pindata in sorted(beldata["pins"].items()): + belpins[pin] = SiteBELPinData(name=pin, pindir=pindata["dir"], site_wire_idx=wire_index(pindata["wire"])) + bd = SiteBELData(name=bel, bel_type=beldata["type"], bel_class=beldata["class"], pins=belpins) + bel_idx_by_name[bel] = len(vd.bels) + vd.bels.append(bd) + # Import pips + for pipdata in vdata["pips"]: + bel_idx = bel_idx_by_name[pipdata["bel"]] + bel_data = vd.bels[bel_idx] + vd.pips.append(SitePIPData(bel_idx=bel_idx_by_name[pipdata["bel"]], bel_input=pipdata["from_pin"], + from_wire_idx=bel_data.pins[pipdata["from_pin"]].site_wire_idx, + to_wire_idx=bel_data.pins[pipdata["to_pin"]].site_wire_idx)) + # Import pins + for pin, pindata in sorted(vdata["pins"].items()): + vd.pins.append(SitePinData(name=pin, pindir=pindata["dir"], site_wire_idx=wire_index(pindata["wire"]), + prim_pin_name=pindata["primary"])) + sd.variants[vtype] = vd + else: + sd.variants[sitetype] = sd + site_type_cache[sitetype] = sd + return site_type_cache[sitetype] + + def read_tile_type_json(tiletype): + if tiletype not in tile_json_cache: + if not os.path.exists(prjxray_root + "/tile_type_" + tiletype + ".json"): + tile_json_cache[tiletype] = dict(wires={}, pips={}, sites=[]) + else: + with open(prjxray_root + "/tile_type_" + tiletype + ".json", "r") as jf: + tile_json_cache[tiletype] = json.load(jf) + return tile_json_cache[tiletype] + + def get_tile_type_data(tiletype): + if tiletype not in tile_type_cache: + td = TileData(tiletype) + # Import wires and pips + tj = read_tile_type_json(tiletype) + for wire, wire_data in sorted(tj["wires"].items()): + wire_id = len(td.wires) + wd = WireData(index=wire_id, name=wire, tied_value=None) # FIXME: tied_value + wd.intent = get_wire_intent(tiletype, wire) + if wire_data is not None: + if "res" in wire_data: + wd.resistance = float(wire_data["res"]) + if "cap" in wire_data: + wd.capacitance = float(wire_data["cap"]) + td.wires.append(wd) + td.wires_by_name[wire] = wd + for pip, pipdata in sorted(tj["pips"].items()): + # FIXME: pip/wire delays + pip_id = len(td.pips) + pd = PIPData(index=pip_id, + from_wire=td.wires_by_name[pipdata["src_wire"]].index, to_wire=td.wires_by_name[pipdata["dst_wire"]].index, + is_bidi=(not bool(int(pipdata["is_directional"]))), is_route_thru=bool(int(pipdata["is_pseudo"]))) + if "is_pass_transistor" in pipdata: + pd.is_buffered = (not bool(int(pipdata["is_pass_transistor"]))) + if "src_to_dst" in pipdata: + s2d = pipdata["src_to_dst"] + if "delay" in s2d and s2d["delay"] is not None: + pd.min_delay = min(float(s2d["delay"][0]), float(s2d["delay"][1])) + pd.max_delay = max(float(s2d["delay"][2]), float(s2d["delay"][3])) + if "res" in s2d and s2d["res"] is not None: + pd.resistance = float(s2d["res"]) + if "in_cap" in s2d and s2d["in_cap"] is not None: + pd.capacitance = float(s2d["in_cap"]) + td.pips.append(pd) + for sitedata in tj["sites"]: + rel_xy = parse_xy(sitedata["name"]) + sitetype = sitedata["type"] + for sitepin, pindata in sorted(sitedata["site_pins"].items()): + if pindata is None: + tspd = TileSitePinData(None) + else: + pinwire = td.wires_by_name[pindata["wire"]].index + tspd = TileSitePinData(pinwire) + if "delay" in pindata: + tspd.min_delay = min(float(pindata["delay"][0]), float(pindata["delay"][1])) + tspd.max_delay = max(float(pindata["delay"][2]), float(pindata["delay"][3])) + if "res" in pindata: + tspd.resistance = float(pindata["res"]) + if "cap" in pindata: + tspd.capacitance = float(pindata["cap"]) + td.sitepin_data[(sitetype, rel_xy, sitepin)] = tspd + if os.path.exists(prjxray_root + "/timings/" + tiletype + ".sdf"): + td.cell_timing = parse_sdf_file(prjxray_root + "/timings/" + tiletype + ".sdf") + + tile_type_cache[tiletype] = td + + return tile_type_cache[tiletype] + + def get_wire_intent(tiletype, wirename): + if tiletype not in ij["tiles"]: + return "GENERIC" + if wirename not in ij["tiles"][tiletype]: + return "GENERIC" + return ij["intents"][str(ij["tiles"][tiletype][wirename])] + + d = Device(fabricname) + + # Load intent JSON + with open(metadata_root + "/wire_intents.json", "r") as ijf: + ij = json.load(ijf) + with open(prjxray_root + "/" + fabricname + "/tilegrid.json") as gf: + tgj = json.load(gf) + for tile, tiledata in sorted(tgj.items()): + x = int(tiledata["grid_x"]) + y = int(tiledata["grid_y"]) + d.width = max(d.width, x + 1) + d.height = max(d.height, y + 1) + tiletype = tiledata["type"] + t = Tile(x, y, tile, get_tile_type_data(tiletype), (-1, -1), []) + for idx, (site, sitetype) in enumerate(sorted(tiledata["sites"].items())): + si = Site(t, site, idx, parse_xy(site), get_site_type_data(sitetype)) + t.site_insts.append(si) + d.sites_by_name[site] = si + d.tiles_by_name[tile] = t + d.tiles_by_xy[x, y] = t + d.tiles.append(t) + + # Resolve interconnect tile coordinates + for t in d.tiles: + for delta in range(0, 30): + if t.interconn_xy != (-1, -1): + break # found, done + for direction in (-1, +1): + nxy = (t.x + direction * delta, t.y) + if nxy not in d.tiles_by_xy: + continue + if d.tiles_by_xy[nxy].tile_type not in ("INT", "INT_L", "INT_R"): + continue + t.interconn_xy = nxy + break + # Read package pins + for entry in os.scandir(prjxray_root): + if not entry.is_dir() or not entry.name.startswith(fabricname): + continue + device_postfix = entry.name[len(fabricname):] + if len(device_postfix) == 0: + continue + package_name = device_postfix.split("-")[0] + if package_name in d.packages: + continue # already seen in a different speed grade + with open(prjxray_root + "/" + entry.name + "/package_pins.csv") as ppf: + pkg = Package(name=package_name) + for line in ppf: + sl = line.strip().split(",") + if len(sl) < 3: + continue + if sl[2] == "site": + continue # header + pkg.pin_map[sl[0]] = sl[2] + d.packages[package_name] = pkg + with open(prjxray_root + "/" + fabricname + "/tileconn.json", "r") as tcf: + apply_tileconn(tcf, d) + return d + +if __name__ == '__main__': + import sys + import_device(*sys.argv[1:]) diff --git a/himbaechel/uarch/xilinx/gen/xilinx_gen.py b/himbaechel/uarch/xilinx/gen/xilinx_gen.py new file mode 100644 index 0000000000..8eec7f56cd --- /dev/null +++ b/himbaechel/uarch/xilinx/gen/xilinx_gen.py @@ -0,0 +1,371 @@ +from os import path +import sys +import argparse +import xilinx_device +import filters +import struct +sys.path.append(path.join(path.dirname(__file__), "../../..")) +from himbaechel_dbgen.chip import * + +def lookup_port_type(t): + if t == "INPUT": return PinType.INPUT + elif t == "OUTPUT": return PinType.OUTPUT + elif t == "BIDIR": return PinType.INOUT + else: assert False + +def gen_bel_name(site, bel_name): + prim_st = site.primary.site_type() + if prim_st in ("IOB33M", "IOB33S", "IOB33", "IOB18M", "IOB18S", "IOB18"): + return f"{site.site_type()}.{bel_name}" + else: + return f"{bel_name}" + +@dataclass +class PipClass(Enum): + TILE_ROUTING = 0 + SITE_ENTRANCE = 1 + SITE_EXIT = 2 + SITE_INTERNAL = 3 + LUT_PERMUTATION = 4 + LUT_ROUTETHRU = 5 + CONST_DRIVER = 6 + +@dataclass +class PipExtraData(BBAStruct): + site_key: int = -1 + bel_name: IdString = field(default_factory=IdString) + pip_config: int = 0 + + def serialise_lists(self, context: str, bba: BBAWriter): + pass + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.site_key) + bba.u32(self.bel_name.index) + bba.u32(self.pip_config) + +@dataclass +class BelExtraData(BBAStruct): + name_in_site: IdString + + def serialise_lists(self, context: str, bba: BBAWriter): + pass + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.name_in_site.index) + +@dataclass +class SiteInst(BBAStruct): + name_prefix: IdString + site_x: int + site_y: int + rel_x: int + rel_y: int + int_x: int + int_y: int + variants: list[IdString] = field(default_factory=list) + + def serialise_lists(self, context: str, bba: BBAWriter): + bba.label(f"{context}_variants") + for i, variant in enumerate(self.variants): + bba.u32(variant.index) + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.name_prefix.index) + bba.u16(self.site_x) + bba.u16(self.site_y) + bba.u16(self.rel_x) + bba.u16(self.rel_y) + bba.u16(self.int_x) + bba.u16(self.int_y) + bba.slice(f"{context}_variants", len(self.variants)) + +@dataclass +class TileExtraData(BBAStruct): + name_prefix: IdString + tile_x: int + tile_y: int + sites: list[SiteInst] = field(default_factory=list) + + def serialise_lists(self, context: str, bba: BBAWriter): + for i, site in enumerate(self.sites): + site.serialise_lists(f"{context}_si{i}", bba) + bba.label(f"{context}_sites") + for i, site in enumerate(self.sites): + site.serialise(f"{context}_si{i}", bba) + def serialise(self, context: str, bba: BBAWriter): + bba.u32(self.name_prefix.index) + bba.u16(self.tile_x) + bba.u16(self.tile_y) + bba.slice(f"{context}_sites", len(self.sites)) + +def timing_pip_classs(pip: xilinx_device.PIP): + return f"{'buf' if pip.is_buffered() else 'sw'}_d{pip.max_delay()}_r{pip.resistance()}_c{pip.capacitance()}" + +seen_pip_timings = set() +seen_node_timings = set() + +def import_tiletype(ch: Chip, tile: xilinx_device.Tile): + tile_type = tile.tile_type() + if tile.x == 0 and tile.y == 0: + assert tile_type == "NULL" + tile_type = "NULL_CORNER" + tt = ch.create_tile_type(tile_type) + # import tile wires + for wire in tile.wires(): + nw = tt.create_wire(wire.name(), wire.intent()) + nw.flags = -1 # not a site wire + if wire.is_gnd(): + nw.const_value = ch.strs.id("GND") + if wire.is_vcc(): + nw.const_value = ch.strs.id("VCC") + if tile_type == "NULL_CORNER": + # pseudo ground bels + tt.create_wire(f"GND", "GND", const_value="GND") + tt.create_wire(f"VCC", "VCC", const_value="VCC") + + gnd = tt.create_bel(f"PSEUDO_GND", f"PSEUDO_GND", z=0) + gnd.site = -1 + gnd.extra_data = BelExtraData(name_in_site=ch.strs.id("PSEUDO_GND")) + tt.add_bel_pin(gnd, "Y", "GND", PinType.OUTPUT) + + vcc = tt.create_bel(f"PSEUDO_VCC", f"PSEUDO_VCC", z=1) + vcc.site = -1 + vcc.extra_data = BelExtraData(name_in_site=ch.strs.id("PSEUDO_VCC")) + tt.add_bel_pin(vcc, "Y", "VCC", PinType.OUTPUT) + def add_pip(src_wire, dst_wire, pip_class=PipClass.TILE_ROUTING, timing="", + site_key=-1, bel_name="", pip_config=0): + np = tt.create_pip(src_wire, dst_wire, timing) + np.flags = pip_class.value + np.extra_data = PipExtraData(site_key=site_key, bel_name=ch.strs.id(bel_name), + pip_config=pip_config) + + def lookup_site_wire(sw): + canon_name = f"{sw.site.rel_name()}.{sw.name()}" + if not tt.has_wire(canon_name): + nw = tt.create_wire(name=canon_name, + type="INTENT_SITE_GND" if sw.name() == "GND_WIRE" else "INTENT_SITE_WIRE") + nw.flags = sw.site.primary.index + return canon_name + + def add_site_io_pip(pin): + pn = pin.name() + s = pin.site + site_key = (s.index << 8) + if pin.tile_wire() is None: + return None + # TODO: timing class + if pin.dir() in ("OUTPUT", "BIDIR"): + if s.primary.site_type() == "IPAD" and pn == "O": + return None + # if s.rel_xy()[0] == 0 and s.primary.site_type() == "SLICEL" and pin.site_wire().name() == "A": + # # Add ground pip + # # self.add_pseudo_pip(self.row_gnd_wire_index, self.sitewire_to_tilewire(pin.site_wire()), + # # pip_type=NextpnrPipType.CONST_DRIVER) + # pass + add_pip(lookup_site_wire(pin.site_wire()), pin.tile_wire().name(), + pip_class=PipClass.SITE_EXIT, timing="SITE_NULL") + else: + if s.site_type() in ("SLICEL", "SLICEM"): + # Add permuation pseudo-pips for LUT inputs + swn = pin.site_wire().name() + if len(swn) == 2 and swn[0] in "ABCDEFGH" and swn[1] in "123456": + i = int(swn[1]) + for j in range(1, 7): + if (i == 6) != (j == 6): + continue # don't allow permutation of input 6 + pip_config = ("ABCDEFGH".index(swn[0]) << 8) | ((j - 1) << 4) | (i - 1) + if s.rel_xy()[0] == 1: + pip_config |= (4 << 8) + add_pip(s.pin(f"{swn[0]}{j}").tile_wire().name(), lookup_site_wire(pin.site_wire()), + pip_class=PipClass.LUT_PERMUTATION, + pip_config=pip_config, + site_key=(s.index << 8), + timing="SITE_NULL" + ) + return + add_pip(pin.tile_wire().name(), lookup_site_wire(pin.site_wire()), + pip_class=PipClass.SITE_ENTRANCE, timing="SITE_NULL") + + # TODO: ground/vcc + tile_wire_count = len(tt.wires) + for site in tile.sites(): + seen_pins = set() + for variant_idx, variant in enumerate(site.available_variants()): + if variant in ("FIFO36E1", ): #unsupported atm + continue + sv = site.variant(variant) + variant_key = (site.index << 8) | (variant_idx & 0xFF) + # Import site bels + for bel in sv.bels(): + z = filters.get_bel_z_override(bel, len(tt.bels)) + # Overriden z of -1 means we skip this bel + if z == -1: + continue + bel_name = gen_bel_name(sv, bel.name()) + nb = tt.create_bel(name=f"{site.rel_name()}.{bel_name}", type=filters.get_bel_type_override(bel.bel_type()), z=z) + nb.site = variant_key + nb.extra_data = BelExtraData(name_in_site=ch.strs.id(bel_name)) + if bel.bel_class() == "RBEL": nb.flags |= 2 + # TODO: extra data (site etc) + for pin in bel.pins(): + tt.add_bel_pin(nb, pin.name, lookup_site_wire(pin.site_wire()), lookup_port_type(pin.dir())) + # Import site pins + for pin in sv.pins(): + pin_key = (pin.name(), pin.site_wire().name()) + if pin_key not in seen_pins: + add_site_io_pip(pin) + seen_pins.add(pin_key) + # Import site pips + for site_pip in sv.pips(): + if "LUT" in site_pip.bel().bel_type(): + continue # ignore site LUT route-throughs + bel_name = site_pip.bel().name() + bel_pin = site_pip.bel_input() + if (bel_name == "ADI1MUX" and bel_pin == "BDI1") or \ + (bel_name == "BDI1MUX" and bel_pin == "DI") or \ + (bel_name == "CDI1MUX" and bel_pin == "DI") or \ + (bel_name.startswith("TFBUSED")) or \ + (bel_name == "OMUX"): + continue + add_pip(lookup_site_wire(site_pip.src_wire()), + lookup_site_wire(site_pip.dst_wire()), + pip_class=PipClass.SITE_INTERNAL, + site_key=variant_key, + bel_name=bel_name, + pip_config=ch.strs.id(bel_pin).index, + timing="SITE_NULL" + ) + # Import tile pips + for pip in tile.pips(): + if not filters.include_pip(tile.tile_type(), pip): + continue + tcls = timing_pip_classs(pip) + if tcls not in seen_pip_timings: + ch.timing.set_pip_class(f"DEFAULT", tcls, + delay=TimingValue(int(pip.min_delay()*1000), int(pip.max_delay()*1000)), # ps + in_cap=TimingValue(int(pip.capacitance()*1000)), # fF + out_res=TimingValue(int(pip.resistance())), # mohm + is_buffered=pip.is_buffered()) + seen_pip_timings.add(tcls) + add_pip(pip.src_wire().name(), pip.dst_wire().name(), pip_class=PipClass.TILE_ROUTING, timing=tcls, + pip_config=1 if pip.is_route_thru() else 0) + # TODO: extra data, route-through flag + if pip.is_bidi(): + add_pip(pip.dst_wire().name(), pip.src_wire().name(), pip_class=PipClass.TILE_ROUTING, timing=tcls, + pip_config=1 if pip.is_route_thru() else 0) +def main(): + xlbase = path.join(path.dirname(path.realpath(__file__)), "..") + + parser = argparse.ArgumentParser() + parser.add_argument("--xray", help="Project X-Ray device database path for current family (e.g. ../prjxray-db/artix7)", type=str, required=True) + parser.add_argument("--metadata", help="nextpnr-xilinx site metadata root", type=str, default=path.join(xlbase, "meta", "artix7")) + parser.add_argument("--device", help="name of device to export", type=str, required=True) + parser.add_argument("--constids", help="name of nextpnr constids file to read", type=str, default=path.join(xlbase, "constids.inc")) + parser.add_argument("--bba", help="bba file to write", type=str, required=True) + args = parser.parse_args() + + # Init database paths + metadata_root = args.metadata + xraydb_root = args.xray + if "xc7z" in args.device: + metadata_root = metadata_root.replace("artix7", "zynq7") + xraydb_root = xraydb_root.replace("artix7", "zynq7") + if "xc7k" in args.device: + metadata_root = metadata_root.replace("artix7", "kintex7") + xraydb_root = xraydb_root.replace("artix7", "kintex7") + if "xc7s" in args.device: + metadata_root = metadata_root.replace("artix7", "spartan7") + xraydb_root = xraydb_root.replace("artix7", "spartan7") + # Load prjxray device data + d = xilinx_device.import_device(args.device, xraydb_root, metadata_root) + # Init constant ids + ch = Chip("xilinx", args.device, d.width, d.height) + ch.strs.read_constids(path.join(path.dirname(__file__), args.constids)) + ch.set_speed_grades(["DEFAULT", ]) # TODO: figure out how speed grades are supposed to work in prjxray + # Import tile types + for tile in d.tiles: + tile_type = tile.tile_type() + if tile.x == 0 and tile.y == 0: + assert tile_type == "NULL" + # location for pseudo gnd/vcc bels + tile_type = "NULL_CORNER" + if tile_type not in ch.tile_type_idx: + import_tiletype(ch, tile) + ti = ch.set_tile_type(tile.x, tile.y, tile_type) + prefix, tx, ty = tile.split_name() + ti.extra_data = TileExtraData(ch.strs.id(prefix), tx, ty) + for site in tile.sites(): + ti.extra_data.sites.append(SiteInst( + name_prefix=ch.strs.id(site.prefix), + site_x=site.grid_xy[0], site_y=site.grid_xy[1], + rel_x=site.rel_xy()[0], rel_y=site.rel_xy()[1], + int_x=tile.interconn_xy[0], int_y=tile.interconn_xy[1], + variants=[ch.strs.id(v) for v in site.available_variants()] + )) + # Import nodes + seen_nodes = set() + tt_used_wires = {} + print("Processing nodes...") + for row in range(d.height): + # TODO: GND and VCC row connectivity... + for col in range(d.width): + t = d.tiles_by_xy[col, row] + for w in t.wires(): + n = w.node() + uid = n.unique_index() + if uid in seen_nodes: + continue + if len(n.wires) > 1: + node_wires = [] + node_cap = 0 + node_res = 0 + # Add interconnect tiles first for better delay estimates in nextpnr + for j in range(2): + for w in n.wires: + if (w.tile.tile_type() in ("INT", "INT_L", "INT_R")) != (j == 0): + continue + if w.tile.tile_type() not in tt_used_wires: + tt_used_wires[w.tile.tile_type()] = w.tile.used_wire_indices() + node_cap += int(w.capacitance() * 1000) + node_res += int(w.resistance()) + # prune wires from nodes that just pass through a tile without connecting to + # bels/pips, because nextpnr doesn't care about these + if w.index not in tt_used_wires[w.tile.tile_type()]: + continue + node_wires.append(NodeWire(w.tile.x, w.tile.y, w.index)) + if len(node_wires) > 1: + timing_class = f"node_c{node_cap}_c{node_res}" + ch.add_node(node_wires, timing_class=timing_class) + if timing_class not in seen_node_timings: + ch.timing.set_node_class("DEFAULT", timing_class, delay=TimingValue(0), cap=TimingValue(node_cap), res=TimingValue(node_res)) + seen_node_timings.add(timing_class) + del node_wires + seen_nodes.add(uid) + # Stub timing class + ch.timing.set_pip_class("DEFAULT", "SITE_NULL", delay=TimingValue(20)) + # Stub bel timings + lut = ch.timing.add_cell_variant("DEFAULT", "SLICE_LUTX") + for i in range(1, 6+1): + lut.add_comb_arc(f"A{i}", "O6", TimingValue(100, 125)) + if i <= 5: lut.add_comb_arc(f"A{i}", "O5", TimingValue(120, 150)) + for i in range(1, 8+1): + lut.add_setup_hold("CLK", f"WA{i}", ClockEdge.RISING, TimingValue(100, 500), TimingValue(100, 200)) + lut.add_setup_hold("CLK", "WE", ClockEdge.RISING, TimingValue(100, 600), TimingValue(100, 100)) + lut.add_setup_hold("CLK", "DI1", ClockEdge.RISING, TimingValue(100, 100), TimingValue(100, 100)) + ff = ch.timing.add_cell_variant("DEFAULT", "SLICE_FFX") + ff.add_setup_hold("CK", "CE", ClockEdge.RISING, TimingValue(100, 100), TimingValue(0, 0)) + ff.add_setup_hold("CK", "SR", ClockEdge.RISING, TimingValue(100, 100), TimingValue(0, 0)) + ff.add_setup_hold("CK", "D", ClockEdge.RISING, TimingValue(100, 100), TimingValue(200, 200)) + ff.add_clock_out("CK", "Q", ClockEdge.RISING, TimingValue(300, 350)) + # Import package pins + for package_name, package in sorted(d.packages.items(), key=lambda x:x[0]): + pkg = ch.create_package(package_name) + for pin, site in sorted(package.pin_map.items(), key=lambda x:x[0]): + site_data = d.sites_by_name[site] + bel_name = gen_bel_name(site_data, "PAD") + tile_type = ch.tile_type_at(site_data.tile.x, site_data.tile.y) + pkg.create_pad(pin, f"X{site_data.tile.x}Y{site_data.tile.y}", + f"{site_data.rel_name()}.{bel_name}", + "", 0) # TODO: bank + ch.write_bba(args.bba) + +if __name__ == '__main__': + main() diff --git a/himbaechel/uarch/xilinx/meta b/himbaechel/uarch/xilinx/meta new file mode 160000 index 0000000000..57de921663 --- /dev/null +++ b/himbaechel/uarch/xilinx/meta @@ -0,0 +1 @@ +Subproject commit 57de9216639b0670949664cfdc61b2679064eb7b diff --git a/himbaechel/uarch/xilinx/pack.cc b/himbaechel/uarch/xilinx/pack.cc new file mode 100644 index 0000000000..a40ccbd387 --- /dev/null +++ b/himbaechel/uarch/xilinx/pack.cc @@ -0,0 +1,750 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2023 Myrtle Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "pack.h" +#include +#include +#include +#include +#include +#include "chain_utils.h" +#include "design_utils.h" +#include "extra_data.h" +#include "log.h" +#include "nextpnr.h" +#include "pins.h" + +#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" +#include "himbaechel_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +// Process the contents of packed_cells +void XilinxPacker::flush_cells() +{ + for (auto pcell : packed_cells) { + for (auto &port : ctx->cells[pcell]->ports) { + ctx->cells[pcell]->disconnectPort(port.first); + } + ctx->cells.erase(pcell); + } + packed_cells.clear(); +} + +void XilinxPacker::xform_cell(const dict &rules, CellInfo *ci) +{ + auto &rule = rules.at(ci->type); + ci->attrs[id_X_ORIG_TYPE] = ci->type.str(ctx); + ci->type = rule.new_type; + std::vector orig_port_names; + for (auto &port : ci->ports) + orig_port_names.push_back(port.first); + + for (auto pname : orig_port_names) { + if (rule.port_multixform.count(pname)) { + auto old_port = ci->ports.at(pname); + ci->disconnectPort(pname); + ci->ports.erase(pname); + for (auto new_name : rule.port_multixform.at(pname)) { + ci->ports[new_name].name = new_name; + ci->ports[new_name].type = old_port.type; + ci->connectPort(new_name, old_port.net); + ci->attrs[ctx->id("X_ORIG_PORT_" + new_name.str(ctx))] = pname.str(ctx); + } + } else { + IdString new_name; + if (rule.port_xform.count(pname)) { + new_name = rule.port_xform.at(pname); + } else { + std::string stripped_name; + for (auto c : pname.str(ctx)) + if (c != '[' && c != ']') + stripped_name += c; + new_name = ctx->id(stripped_name); + } + if (new_name != pname) { + ci->renamePort(pname, new_name); + } + ci->attrs[ctx->id("X_ORIG_PORT_" + new_name.str(ctx))] = pname.str(ctx); + } + } + + std::vector xform_params; + for (auto ¶m : ci->params) + if (rule.param_xform.count(param.first)) + xform_params.push_back(param.first); + for (auto param : xform_params) + ci->params[rule.param_xform.at(param)] = ci->params[param]; + + for (auto &attr : rule.set_attrs) + ci->attrs[attr.first] = attr.second; + + for (auto ¶m : rule.set_params) + ci->params[param.first] = param.second; +} + +void XilinxPacker::generic_xform(const dict &rules, bool print_summary) +{ + std::map cell_count; + std::map new_types; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (rules.count(ci->type)) { + cell_count[ci->type.str(ctx)]++; + xform_cell(rules, ci); + new_types[ci->type.str(ctx)]++; + } + } + if (print_summary) { + for (auto &nt : new_types) { + log_info(" Created %d %s cells from:\n", nt.second, nt.first.c_str()); + for (auto &cc : cell_count) { + if (rules.at(ctx->id(cc.first)).new_type != ctx->id(nt.first)) + continue; + log_info(" %6dx %s\n", cc.second, cc.first.c_str()); + } + } + } +} + +CellInfo *XilinxPacker::feed_through_lut(NetInfo *net, const std::vector &feed_users) +{ + NetInfo *feedthru_net = ctx->createNet(ctx->idf("%s$legal%d", net->name.c_str(ctx), ++autoidx)); + + CellInfo *lut = create_lut(stringf("%s$LUT%d", net->name.c_str(ctx), ++autoidx), {net}, feedthru_net, Property(2)); + + for (auto &usr : feed_users) { + usr.cell->disconnectPort(usr.port); + usr.cell->connectPort(usr.port, feedthru_net); + } + + return lut; +} + +CellInfo *XilinxPacker::feed_through_muxf(NetInfo *net, IdString type, const std::vector &feed_users) +{ + NetInfo *feedthru_net = ctx->createNet(ctx->idf("%s$legal$%d", net->name.c_str(ctx), ++autoidx)); + CellInfo *mux = create_cell(type, ctx->idf("%s$MUX$%d", net->name.c_str(ctx), ++autoidx)); + mux->connectPort(id_I0, net); + mux->connectPort(id_O, feedthru_net); + mux->connectPort(id_S, ctx->nets[ctx->id("$PACKER_GND_NET")].get()); + + for (auto &usr : feed_users) { + usr.cell->disconnectPort(usr.port); + usr.cell->connectPort(usr.port, feedthru_net); + } + + return mux; +} + +IdString XilinxPacker::int_name(IdString base, const std::string &postfix, bool is_hierarchy) +{ + return ctx->id(base.str(ctx) + (is_hierarchy ? "$subcell$" : "$intcell$") + postfix); +} + +NetInfo *XilinxPacker::create_internal_net(IdString base, const std::string &postfix, bool is_hierarchy) +{ + IdString name = ctx->id(base.str(ctx) + (is_hierarchy ? "$subnet$" : "$intnet$") + postfix); + return ctx->createNet(name); +} + +void XilinxPacker::pack_luts() +{ + log_info("Packing LUTs..\n"); + + dict lut_rules; + for (int k = 1; k <= 6; k++) { + IdString lut = ctx->id("LUT" + std::to_string(k)); + lut_rules[lut].new_type = id_SLICE_LUTX; + for (int i = 0; i < k; i++) + lut_rules[lut].port_xform[ctx->id("I" + std::to_string(i))] = ctx->id("A" + std::to_string(i + 1)); + lut_rules[lut].port_xform[id_O] = id_O6; + } + lut_rules[id_LUT6_2] = XFormRule(lut_rules[id_LUT6]); + generic_xform(lut_rules, true); +} + +void XilinxPacker::pack_ffs() +{ + log_info("Packing flipflops..\n"); + + dict ff_rules; + ff_rules[id_FDCE].new_type = id_SLICE_FFX; + ff_rules[id_FDCE].port_xform[id_C] = id_CK; + ff_rules[id_FDCE].port_xform[id_CLR] = id_SR; + // ff_rules[id_FDCE].param_xform[id_IS_CLR_INVERTED] = id_IS_SR_INVERTED; + + ff_rules[id_FDPE].new_type = id_SLICE_FFX; + ff_rules[id_FDPE].port_xform[id_C] = id_CK; + ff_rules[id_FDPE].port_xform[id_PRE] = id_SR; + // ff_rules[id_FDPE].param_xform[id_IS_PRE_INVERTED] = id_IS_SR_INVERTED; + + ff_rules[id_FDRE].new_type = id_SLICE_FFX; + ff_rules[id_FDRE].port_xform[id_C] = id_CK; + ff_rules[id_FDRE].port_xform[id_R] = id_SR; + ff_rules[id_FDRE].set_attrs.emplace_back(id_X_FFSYNC, "1"); + // ff_rules[id_FDRE].param_xform[id_IS_R_INVERTED] = id_IS_SR_INVERTED; + + ff_rules[id_FDSE].new_type = id_SLICE_FFX; + ff_rules[id_FDSE].port_xform[id_C] = id_CK; + ff_rules[id_FDSE].port_xform[id_S] = id_SR; + ff_rules[id_FDSE].set_attrs.emplace_back(id_X_FFSYNC, "1"); + // ff_rules[id_FDSE].param_xform[id_IS_S_INVERTED] = id_IS_SR_INVERTED; + + ff_rules[id_FDCE_1] = XFormRule(ff_rules[id_FDCE]); + ff_rules[id_FDCE_1].set_params.emplace_back(id_IS_C_INVERTED, 1); + + ff_rules[id_FDPE_1] = XFormRule(ff_rules[id_FDPE]); + ff_rules[id_FDPE_1].set_params.emplace_back(id_IS_C_INVERTED, 1); + + ff_rules[id_FDRE_1] = XFormRule(ff_rules[id_FDRE]); + ff_rules[id_FDRE_1].set_params.emplace_back(id_IS_C_INVERTED, 1); + + ff_rules[id_FDSE_1] = XFormRule(ff_rules[id_FDSE]); + ff_rules[id_FDSE_1].set_params.emplace_back(id_IS_C_INVERTED, 1); + + generic_xform(ff_rules, true); +} + +void XilinxPacker::pack_lutffs() +{ + int pairs = 0; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->cluster != ClusterId() || !ci->constr_children.empty()) + continue; + if (ci->type != id_SLICE_FFX) + continue; + NetInfo *d = ci->getPort(id_D); + if (d->driver.cell == nullptr || d->driver.cell->type != id_SLICE_LUTX || d->driver.port != id_O6) + continue; + CellInfo *lut = d->driver.cell; + if (lut->cluster != ClusterId() || !lut->constr_children.empty()) + continue; + lut->constr_children.push_back(ci); + lut->cluster = lut->name; + ci->cluster = lut->name; + ci->constr_x = 0; + ci->constr_y = 0; + ci->constr_z = (BEL_FF - BEL_6LUT); + ++pairs; + } + log_info("Constrained %d LUTFF pairs.\n", pairs); +} + +bool XilinxPacker::is_constrained(const CellInfo *cell) { return cell->cluster != ClusterId(); } + +void XilinxPacker::legalise_muxf_tree(CellInfo *curr, std::vector &mux_roots) +{ + if (curr->type.str(ctx).substr(0, 3) == "LUT") + return; + for (IdString p : {id_I0, id_I1}) { + NetInfo *pn = curr->getPort(p); + if (pn == nullptr || pn->driver.cell == nullptr) + continue; + if (curr->type == id_MUXF7) { + if (pn->driver.cell->type.str(ctx).substr(0, 3) != "LUT" || is_constrained(pn->driver.cell)) { + PortRef pr; + pr.cell = curr; + pr.port = p; + feed_through_lut(pn, {pr}); + continue; + } + } else { + IdString next_type; + if (curr->type == id_MUXF9) + next_type = id_MUXF8; + else if (curr->type == id_MUXF8) + next_type = id_MUXF7; + else + NPNR_ASSERT_FALSE("bad mux type"); + if (pn->driver.cell->type != next_type || is_constrained(pn->driver.cell) || + bool_or_default(pn->driver.cell->attrs, id_MUX_TREE_ROOT)) { + PortRef pr; + pr.cell = curr; + pr.port = p; + feed_through_muxf(pn, next_type, {pr}); + continue; + } + } + legalise_muxf_tree(pn->driver.cell, mux_roots); + } +} + +void XilinxPacker::constrain_muxf_tree(CellInfo *curr, CellInfo *base, int zoffset) +{ + + if (curr->type == id_SLICE_LUTX && (curr->constr_abs_z || curr->cluster != ClusterId())) + return; + + int base_z = 0; + if (base->type == id_MUXF7) + base_z = BEL_F7MUX; + else if (base->type == id_MUXF8) + base_z = BEL_F8MUX; + else if (base->type == id_MUXF9) + base_z = BEL_F9MUX; + else if (base->constr_abs_z) + base_z = base->constr_z; + else + NPNR_ASSERT_FALSE("unexpected mux base type"); + int curr_z = zoffset * 16; + int input_spacing = 0; + if (curr->type == id_MUXF7) { + curr_z += BEL_F7MUX; + input_spacing = 1; + } else if (curr->type == id_MUXF8) { + curr_z += BEL_F8MUX; + input_spacing = 2; + } else if (curr->type == id_MUXF9) { + curr_z += BEL_F9MUX; + input_spacing = 4; + } else + curr_z += BEL_6LUT; + if (curr != base) { + curr->constr_x = 0; + curr->constr_y = 0; + curr->constr_z = curr_z - base_z; + curr->constr_abs_z = false; + curr->cluster = base->name; + base->constr_children.push_back(curr); + } + if (curr->type.in(id_MUXF7, id_MUXF8, id_MUXF9)) { + NetInfo *i0 = curr->getPort(id_I0), *i1 = curr->getPort(id_I1); + if (i0 != nullptr && i0->driver.cell != nullptr) + constrain_muxf_tree(i0->driver.cell, base, zoffset + input_spacing); + if (i1 != nullptr && i1->driver.cell != nullptr) + constrain_muxf_tree(i1->driver.cell, base, zoffset); + } +} + +void XilinxPacker::pack_muxfs() +{ + log_info("Packing MUX[789]s..\n"); + std::vector mux_roots; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + ci->attrs.erase(id_MUX_TREE_ROOT); + if (ci->type == id_MUXF9) { + log_error("MUXF9 is not supported on xc7!\n"); + } else if (ci->type == id_MUXF8) { + NetInfo *o = ci->getPort(id_O); + if (o == nullptr || o->users.entries() != 1 || (*o->users.begin()).cell->type != id_MUXF9 || + is_constrained((*o->users.begin()).cell) || (*o->users.begin()).port == id_S) + mux_roots.push_back(ci); + } else if (ci->type == id_MUXF7) { + NetInfo *o = ci->getPort(id_O); + if (o == nullptr || o->users.entries() != 1 || (*o->users.begin()).cell->type != id_MUXF8 || + is_constrained((*o->users.begin()).cell) || (*o->users.begin()).port == id_S) + mux_roots.push_back(ci); + } + } + for (auto root : mux_roots) + root->attrs[id_MUX_TREE_ROOT] = 1; + for (auto root : mux_roots) + legalise_muxf_tree(root, mux_roots); + for (auto root : mux_roots) { + root->cluster = root->name; + constrain_muxf_tree(root, root, 0); + } +} + +void XilinxPacker::finalise_muxfs() +{ + dict muxf_rules; + muxf_rules[id_MUXF9].new_type = id_F9MUX; + muxf_rules[id_MUXF9].port_xform[id_I0] = id_0; + muxf_rules[id_MUXF9].port_xform[id_I1] = id_1; + muxf_rules[id_MUXF9].port_xform[id_S] = id_S0; + muxf_rules[id_MUXF9].port_xform[id_O] = id_OUT; + muxf_rules[id_MUXF8].new_type = id_SELMUX2_1; + muxf_rules[id_MUXF8].port_xform = muxf_rules[id_MUXF9].port_xform; + muxf_rules[id_MUXF7].new_type = id_SELMUX2_1; + muxf_rules[id_MUXF7].port_xform = muxf_rules[id_MUXF9].port_xform; + generic_xform(muxf_rules, true); +} + +void XilinxPacker::pack_srls() +{ + dict srl_rules; + srl_rules[id_SRL16E].new_type = id_SLICE_LUTX; + srl_rules[id_SRL16E].port_xform[id_CLK] = id_CLK; + srl_rules[id_SRL16E].port_xform[id_CE] = id_WE; + srl_rules[id_SRL16E].port_xform[id_D] = id_DI2; + srl_rules[id_SRL16E].port_xform[id_Q] = id_O6; + srl_rules[id_SRL16E].set_attrs.emplace_back(id_X_LUT_AS_SRL, "1"); + + srl_rules[id_SRLC32E].new_type = id_SLICE_LUTX; + srl_rules[id_SRLC32E].port_xform[id_CLK] = id_CLK; + srl_rules[id_SRLC32E].port_xform[id_CE] = id_WE; + srl_rules[id_SRLC32E].port_xform[id_D] = id_DI1; + srl_rules[id_SRLC32E].port_xform[id_Q] = id_O6; + srl_rules[id_SRLC32E].set_attrs.emplace_back(id_X_LUT_AS_SRL, "1"); + // FIXME: Q31 support + generic_xform(srl_rules, true); + // Fixup SRL inputs + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type != id_SLICE_LUTX) + continue; + std::string orig_type = str_or_default(ci->attrs, id_X_ORIG_TYPE); + if (orig_type == "SRL16E") { + for (int i = 3; i >= 0; i--) { + ci->renamePort(ctx->id("A" + std::to_string(i)), ctx->id("A" + std::to_string(i + 2))); + } + for (auto tp : {id_A1, id_A6}) { + ci->ports[tp].name = tp; + ci->ports[tp].type = PORT_IN; + ci->connectPort(tp, ctx->nets[ctx->id("$PACKER_VCC_NET")].get()); + } + } else if (orig_type == "SRLC32E") { + for (int i = 4; i >= 0; i--) { + ci->renamePort(ctx->id("A" + std::to_string(i)), ctx->id("A" + std::to_string(i + 2))); + } + for (auto tp : {id_A1}) { + ci->ports[tp].name = tp; + ci->ports[tp].type = PORT_IN; + ci->connectPort(tp, ctx->nets[ctx->id("$PACKER_VCC_NET")].get()); + } + } + } +} + +void XilinxPacker::pack_constants() +{ + log_info("Packing constants..\n"); + if (tied_pins.empty()) + get_tied_pins(ctx, tied_pins); + if (invertible_pins.empty()) + get_invertible_pins(ctx, invertible_pins); + if (!ctx->cells.count(ctx->id("$PACKER_GND_DRV"))) { + CellInfo *gnd_cell = ctx->createCell(ctx->id("$PACKER_GND_DRV"), id_PSEUDO_GND); + gnd_cell->addOutput(id_Y); + NetInfo *gnd_net = ctx->createNet(ctx->id("$PACKER_GND_NET")); + gnd_net->constant_value = id_GND; + gnd_cell->connectPort(id_Y, gnd_net); + + CellInfo *vcc_cell = ctx->createCell(ctx->id("$PACKER_VCC_DRV"), id_PSEUDO_VCC); + vcc_cell->addOutput(id_Y); + NetInfo *vcc_net = ctx->createNet(ctx->id("$PACKER_VCC_NET")); + vcc_net->constant_value = id_VCC; + vcc_cell->connectPort(id_Y, vcc_net); + } + NetInfo *gnd = ctx->nets[ctx->id("$PACKER_GND_NET")].get(), *vcc = ctx->nets[ctx->id("$PACKER_VCC_NET")].get(); + + std::vector dead_nets; + + std::vector> const_ports; + + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (!tied_pins.count(ci->type)) + continue; + auto &tp = tied_pins.at(ci->type); + for (auto port : tp) { + if (cell.second->ports.count(port.first) && cell.second->ports.at(port.first).net != nullptr && + cell.second->ports.at(port.first).net->driver.cell != nullptr) + continue; + const_ports.emplace_back(ci, port.first, port.second); + } + } + + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (ni->driver.cell != nullptr && ni->driver.cell->type == id_GND) { + IdString drv_cell = ni->driver.cell->name; + for (auto &usr : ni->users) { + const_ports.emplace_back(usr.cell, usr.port, false); + usr.cell->ports.at(usr.port).net = nullptr; + } + dead_nets.push_back(net.first); + ctx->cells.erase(drv_cell); + } else if (ni->driver.cell != nullptr && ni->driver.cell->type == id_VCC) { + IdString drv_cell = ni->driver.cell->name; + for (auto &usr : ni->users) { + const_ports.emplace_back(usr.cell, usr.port, true); + usr.cell->ports.at(usr.port).net = nullptr; + } + dead_nets.push_back(net.first); + ctx->cells.erase(drv_cell); + } + } + + for (auto port : const_ports) { + CellInfo *ci; + IdString pname; + bool cval; + std::tie(ci, pname, cval) = port; + + if (!ci->ports.count(pname)) { + ci->addInput(pname); + } + if (ci->ports.at(pname).net != nullptr) { + // Case where a port with a default tie value is previously connected to an undriven net + NPNR_ASSERT(ci->ports.at(pname).net->driver.cell == nullptr); + ci->disconnectPort(pname); + } + + if (!cval && invertible_pins.count(ci->type) && invertible_pins.at(ci->type).count(pname)) { + // Invertible pins connected to zero are optimised to a connection to Vcc (which is easier to route) + // and an inversion + ci->params[ctx->idf("IS_%s_INVERTED", pname.c_str(ctx))] = Property(1); + cval = true; + } + + ci->connectPort(pname, cval ? vcc : gnd); + } + + for (auto dn : dead_nets) { + ctx->nets.erase(dn); + } +} + +void XilinxPacker::rename_net(IdString old, IdString newname) +{ + std::unique_ptr ni; + std::swap(ni, ctx->nets[old]); + ctx->nets.erase(old); + ni->name = newname; + ctx->nets[newname] = std::move(ni); +} + +void XilinxPacker::tie_port(CellInfo *ci, const std::string &port, bool value, bool inv) +{ + IdString p = ctx->id(port); + if (!ci->ports.count(p)) { + ci->addInput(p); + } + if (value || inv) + ci->connectPort(p, ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get()); + else + ci->connectPort(p, ctx->nets.at(ctx->id("$PACKER_GND_NET")).get()); + if (!value && inv) + ci->params[ctx->idf("IS_%s_INVERTED", port.c_str())] = Property(1); +} + +void XC7Packer::pack_bram() +{ + log_info("Packing BRAM..\n"); + + // Rules for normal TDP BRAM + dict bram_rules; + bram_rules[id_RAMB18E1].new_type = id_RAMB18E1_RAMB18E1; + bram_rules[id_RAMB18E1].port_multixform[ctx->id("WEA[0]")] = {id_WEA0, id_WEA1}; + bram_rules[id_RAMB18E1].port_multixform[ctx->id("WEA[1]")] = {id_WEA2, id_WEA3}; + bram_rules[id_RAMB36E1].new_type = id_RAMB36E1_RAMB36E1; + + // Some ports have upper/lower bel pins in 36-bit mode + std::vector>> ul_pins; + get_bram36_ul_pins(ctx, ul_pins); + for (auto &ul : ul_pins) { + for (auto &bp : ul.second) + bram_rules[id_RAMB36E1].port_multixform[ul.first].push_back(ctx->id(bp)); + } + bram_rules[id_RAMB36E1].port_multixform[ctx->id("ADDRARDADDR[15]")].push_back(id_ADDRARDADDRL15); + bram_rules[id_RAMB36E1].port_multixform[ctx->id("ADDRBWRADDR[15]")].push_back(id_ADDRBWRADDRL15); + + // Special rules for SDP rules, relating to WE connectivity + dict sdp_bram_rules = bram_rules; + for (int i = 0; i < 4; i++) { + // Connects to two WEBWE bel pins + sdp_bram_rules[id_RAMB18E1].port_multixform[ctx->idf("WEBWE[%d]", i)].push_back(ctx->idf("WEBWE%d", i * 2)); + sdp_bram_rules[id_RAMB18E1].port_multixform[ctx->idf("WEBWE[%d]", i)].push_back(ctx->idf("WEBWE%d", i * 2 + 1)); + // Not used in SDP mode + sdp_bram_rules[id_RAMB18E1].port_multixform[ctx->idf("WEA[%d]", i)] = {}; + } + + for (int i = 0; i < 8; i++) { + sdp_bram_rules[id_RAMB36E1].port_multixform[ctx->idf("WEBWE[%d]", i)].clear(); + // Connects to two WEBWE bel pins + sdp_bram_rules[id_RAMB36E1].port_multixform[ctx->idf("WEBWE[%d]", i)].push_back(ctx->idf("WEBWEL%d", i)); + sdp_bram_rules[id_RAMB36E1].port_multixform[ctx->idf("WEBWE[%d]", i)].push_back(ctx->idf("WEBWEU%d", i)); + // Not used in SDP mode + sdp_bram_rules[id_RAMB36E1].port_multixform[ctx->idf("WEA[%d]", i)] = {}; + } + + // 72-bit BRAMs: drop upper bits of WEB in TDP mode + for (int i = 4; i < 8; i++) + bram_rules[id_RAMB36E1].port_multixform[ctx->idf("WEBWE[%d]", i)] = {}; + + // Process SDP BRAM first + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if ((ci->type == id_RAMB18E1 && int_or_default(ci->params, ctx->id("WRITE_WIDTH_B"), 0) == 36) || + (ci->type == id_RAMB36E1 && int_or_default(ci->params, ctx->id("WRITE_WIDTH_B"), 0) == 72)) + xform_cell(sdp_bram_rules, ci); + } + + // Rewrite byte enables according to data width + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type.in(id_RAMB18E1, id_RAMB36E1)) { + for (char port : {'A', 'B'}) { + int write_width = int_or_default(ci->params, ctx->idf("WRITE_WIDTH_%c", port), 18); + int we_width; + if (ci->type == id_RAMB36E1) + we_width = 4; + else + we_width = (port == 'B') ? 4 : 2; + if (write_width >= (9 * we_width)) + continue; + int used_we_width = std::max(write_width / 9, 1); + for (int i = used_we_width; i < we_width; i++) { + NetInfo *low_we = ci->getPort(ctx->id(std::string(port == 'B' ? "WEBWE[" : "WEA[") + + std::to_string(i % used_we_width) + "]")); + IdString curr_we = ctx->id(std::string(port == 'B' ? "WEBWE[" : "WEA[") + std::to_string(i) + "]"); + if (!ci->ports.count(curr_we)) { + ci->ports[curr_we].type = PORT_IN; + ci->ports[curr_we].name = curr_we; + } + ci->disconnectPort(curr_we); + ci->connectPort(curr_we, low_we); + } + } + } + } + + generic_xform(bram_rules, false); + + // These pins have no logical mapping, so must be tied after transformation + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id_RAMB18E1_RAMB18E1) { + int wwa = int_or_default(ci->params, id_WRITE_WIDTH_A, 0); + for (int i = ((wwa == 0) ? 0 : 2); i < 4; i++) { + IdString port = ctx->idf("WEA%d", i); + if (!ci->ports.count(port)) { + ci->ports[port].name = port; + ci->ports[port].type = PORT_IN; + ci->connectPort(port, ctx->nets[ctx->id("$PACKER_GND_NET")].get()); + } + } + int wwb = int_or_default(ci->params, id_WRITE_WIDTH_B, 0); + if (wwb != 36) { + for (int i = 4; i < 8; i++) { + IdString port = ctx->id("WEBWE" + std::to_string(i)); + if (!ci->ports.count(port)) { + ci->ports[port].name = port; + ci->ports[port].type = PORT_IN; + ci->connectPort(port, ctx->nets[ctx->id("$PACKER_GND_NET")].get()); + } + } + } + for (auto p : {id_ADDRATIEHIGH0, id_ADDRATIEHIGH1, id_ADDRBTIEHIGH0, id_ADDRBTIEHIGH1}) { + if (!ci->ports.count(p)) { + ci->ports[p].name = p; + ci->ports[p].type = PORT_IN; + } else { + ci->disconnectPort(p); + } + ci->connectPort(p, ctx->nets[ctx->id("$PACKER_VCC_NET")].get()); + } + } else if (ci->type == id_RAMB36E1_RAMB36E1) { + for (auto p : {id_ADDRARDADDRL15, id_ADDRBWRADDRL15}) { + if (!ci->ports.count(p)) { + ci->ports[p].name = p; + ci->ports[p].type = PORT_IN; + } else { + ci->disconnectPort(p); + } + ci->connectPort(p, ctx->nets[ctx->id("$PACKER_VCC_NET")].get()); + } + if (int_or_default(ci->params, id_WRITE_WIDTH_A, 0) == 1) { + ci->disconnectPort(id_DIADI1); + ci->connectPort(id_DIADI1, ci->getPort(id_DIADI0)); + ci->attrs[id_X_ORIG_PORT_DIADI1] = std::string("DIADI[0]"); + ci->disconnectPort(id_DIPADIP0); + ci->disconnectPort(id_DIPADIP1); + } + if (int_or_default(ci->params, id_WRITE_WIDTH_B, 0) == 1) { + ci->disconnectPort(id_DIBDI1); + ci->connectPort(id_DIBDI1, ci->getPort(id_DIBDI0)); + ci->attrs[id_X_ORIG_PORT_DIBDI1] = std::string("DIBDI[0]"); + ci->disconnectPort(id_DIPBDIP0); + ci->disconnectPort(id_DIPBDIP1); + } + if (int_or_default(ci->params, id_WRITE_WIDTH_B, 0) != 72) { + for (std::string s : {"L", "U"}) { + for (int i = 4; i < 8; i++) { + IdString port = ctx->idf("WEBWE%s%d", s.c_str(), i); + if (!ci->ports.count(port)) { + ci->ports[port].name = port; + ci->ports[port].type = PORT_IN; + ci->connectPort(port, ctx->nets[ctx->id("$PACKER_GND_NET")].get()); + } + } + } + } else { + // Tie WEA low + for (std::string s : {"L", "U"}) { + for (int i = 0; i < 4; i++) { + IdString port = ctx->idf("WEA%s%d", s.c_str(), i); + if (!ci->ports.count(port)) { + ci->ports[port].name = port; + ci->ports[port].type = PORT_IN; + ci->connectPort(port, ctx->nets[ctx->id("$PACKER_GND_NET")].get()); + } + } + } + } + } + } +} + +void XilinxPacker::pack_inverters() +{ + // FIXME: fold where possible + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id_INV) { + ci->params[id_INIT] = Property(1, 2); + ci->renamePort(id_I, id_I0); + ci->type = id_LUT1; + } + } +} + +void XilinxImpl::pack() +{ + const ArchArgs &args = ctx->args; + if (args.options.count("xdc")) { + parse_xdc(args.options.at("xdc")); + } + + XC7Packer packer(ctx, this); + packer.pack_constants(); + packer.pack_inverters(); + packer.pack_io(); + packer.prepare_clocking(); + packer.pack_constants(); + // packer.pack_iologic(); + // packer.pack_idelayctrl(); + packer.pack_clocking(); + packer.pack_muxfs(); + packer.pack_carries(); + packer.pack_srls(); + packer.pack_luts(); + packer.pack_dram(); + packer.pack_bram(); + // packer.pack_dsps(); + packer.pack_ffs(); + packer.finalise_muxfs(); + packer.pack_lutffs(); +} +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/xilinx/pack.h b/himbaechel/uarch/xilinx/pack.h new file mode 100644 index 0000000000..130949581e --- /dev/null +++ b/himbaechel/uarch/xilinx/pack.h @@ -0,0 +1,218 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019-23 Myrtle Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include "chain_utils.h" +#include "design_utils.h" +#include "log.h" +#include "nextpnr.h" +#include "pins.h" +#include "xilinx.h" + +#ifndef HB_XILINX_PACK_H +#define HB_XILINX_PACK_H + +NEXTPNR_NAMESPACE_BEGIN + +struct XilinxPacker +{ + Context *ctx; + XilinxImpl *uarch; + + XilinxPacker(Context *ctx, XilinxImpl *uarch) : ctx(ctx), uarch(uarch){}; + + // Generic cell transformation + // Given cell name map and port map + // If port name is not found in port map; it will be copied as-is but stripping [] + struct XFormRule + { + IdString new_type; + dict port_xform; + dict> port_multixform; + dict param_xform; + std::vector> set_attrs; + std::vector> set_params; + }; + + // Distributed RAM control set + struct DRAMControlSet + { + std::vector wa; + NetInfo *wclk, *we; + bool wclk_inv; + IdString memtype; + + bool operator==(const DRAMControlSet &other) const + { + return wa == other.wa && wclk == other.wclk && we == other.we && wclk_inv == other.wclk_inv && + memtype == other.memtype; + } + bool operator!=(const DRAMControlSet &other) const + { + return wa != other.wa || wclk != other.wclk || we != other.we || wclk_inv != other.wclk_inv || + memtype != other.memtype; + } + unsigned int hash() const + { + unsigned seed = 0; + seed = mkhash(seed, wa.size()); + for (auto abit : wa) + seed = mkhash(seed, (abit == nullptr ? IdString() : abit->name).hash()); + seed = mkhash(seed, (wclk == nullptr ? IdString() : wclk->name).hash()); + seed = mkhash(seed, (we == nullptr ? IdString() : we->name).hash()); + seed = mkhash(seed, wclk_inv); + seed = mkhash(seed, memtype.hash()); + return seed; + } + }; + + struct DRAMType + { + int abits; + int dbits; + int rports; + }; + + struct CarryGroup + { + std::vector muxcys; + std::vector xorcys; + }; + + pool packed_cells; + + // General helper functions + void flush_cells(); + + void xform_cell(const dict &rules, CellInfo *ci); + void generic_xform(const dict &rules, bool print_summary = false); + + CellInfo *feed_through_lut(NetInfo *net, const std::vector &feed_users); + CellInfo *feed_through_muxf(NetInfo *net, IdString type, const std::vector &feed_users); + + IdString int_name(IdString base, const std::string &postfix, bool is_hierarchy = true); + NetInfo *create_internal_net(IdString base, const std::string &postfix, bool is_hierarchy = true); + void rename_net(IdString old, IdString newname); + + void tie_port(CellInfo *ci, const std::string &port, bool value, bool inv = false); + + // LUTs & FFs + void pack_inverters(); + void pack_luts(); + void pack_ffs(); + void pack_lutffs(); + + bool is_constrained(const CellInfo *cell); + void pack_muxfs(); + void finalise_muxfs(); + void legalise_muxf_tree(CellInfo *curr, std::vector &mux_roots); + void constrain_muxf_tree(CellInfo *curr, CellInfo *base, int zoffset); + void create_muxf_tree(CellInfo *base, const std::string &name_base, const std::vector &data, + const std::vector &select, NetInfo *out, int zoffset); + + void pack_srls(); + + void split_carry4s(); + + // DistRAM + dict dram_rules, dram32_6_rules, dram32_5_rules; + CellInfo *create_dram_lut(const std::string &name, CellInfo *base, const DRAMControlSet &ctrlset, + std::vector address, NetInfo *di, NetInfo *dout, int z); + CellInfo *create_dram32_lut(const std::string &name, CellInfo *base, const DRAMControlSet &ctrlset, + std::vector address, NetInfo *di, NetInfo *dout, bool o5, int z); + void pack_dram(); + + // Constant pins + dict> tied_pins; + dict> invertible_pins; + void pack_constants(); + + // IO + dict> toplevel_ports; + NetInfo *invert_net(NetInfo *toinv); + CellInfo *insert_obuf(IdString name, IdString type, NetInfo *i, NetInfo *o, NetInfo *tri = nullptr); + CellInfo *insert_outinv(IdString name, NetInfo *i, NetInfo *o); + std::pair insert_pad_and_buf(CellInfo *npnr_io); + CellInfo *create_iobuf(CellInfo *npnr_io, IdString &top_port); + + // Clocking + BelId find_bel_with_short_route(WireId source, IdString beltype, IdString belpin); + void try_preplace(CellInfo *cell, IdString port); + void preplace_unique(CellInfo *cell); + + // Cell creating + CellInfo *create_cell(IdString type, IdString name); + CellInfo *create_lut(const std::string &name, const std::vector &inputs, NetInfo *output, + const Property &init); + + int autoidx = 0; +}; + +struct XC7Packer : public XilinxPacker +{ + XC7Packer(Context *ctx, XilinxImpl *uarch) : XilinxPacker(ctx, uarch){}; + + // Carries + bool has_illegal_fanout(NetInfo *carry); + void pack_carries(); + + // IO + CellInfo *insert_ibuf(IdString name, IdString type, NetInfo *i, NetInfo *o); + CellInfo *insert_diffibuf(IdString name, IdString type, const std::array &i, NetInfo *o); + + void decompose_iob(CellInfo *xil_iob, bool is_hr, const std::string &iostandard); + void pack_io(); + + // IOLOGIC + dict hp_iol_rules, hd_iol_rules, ioctrl_rules; + void fold_inverter(CellInfo *cell, std::string port); + std::string get_ologic_site(const std::string &io_bel); + std::string get_ilogic_site(const std::string &io_bel); + std::string get_ioctrl_site(const std::string &io_bel); + std::string get_odelay_site(const std::string &io_bel); + std::string get_idelay_site(const std::string &io_bel); + // Call before packing constants + void prepare_iologic(); + + void pack_iologic(); + void pack_idelayctrl(); + + // Clocking + void prepare_clocking(); + void pack_plls(); + void pack_gbs(); + void pack_clocking(); + + // BRAM + void pack_bram(); + + // DSP + void pack_dsps(); + + private: + void walk_dsp(CellInfo *root, CellInfo *ci, int constr_z); + void check_valid_pad(CellInfo *ci, std::string type); +}; + +NEXTPNR_NAMESPACE_END +#endif diff --git a/himbaechel/uarch/xilinx/pack_carry.cc b/himbaechel/uarch/xilinx/pack_carry.cc new file mode 100644 index 0000000000..5382f3de41 --- /dev/null +++ b/himbaechel/uarch/xilinx/pack_carry.cc @@ -0,0 +1,405 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019-2023 Myrtle Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include "chain_utils.h" +#include "design_utils.h" +#include "extra_data.h" +#include "log.h" +#include "nextpnr.h" +#include "pack.h" +#include "pins.h" + +#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" +#include "himbaechel_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +bool XC7Packer::has_illegal_fanout(NetInfo *carry) +{ + // FIXME: sometimes we can feed out of the chain + if (carry->users.entries() > 2) + return true; + CellInfo *muxcy = nullptr, *xorcy = nullptr; + for (auto &usr : carry->users) { + if (usr.cell->type == id_MUXCY) { + if (muxcy != nullptr) + return true; + else if (usr.port != id_CI) + return true; + else + muxcy = usr.cell; + } else if (usr.cell->type == id_XORCY) { + if (xorcy != nullptr) + return true; + else if (usr.port != id_CI) + return true; + else + xorcy = usr.cell; + } else { + return true; + } + } + if (muxcy && xorcy) { + NetInfo *muxcy_s = muxcy->getPort(id_S); + NetInfo *xorcy_li = xorcy->getPort(id_LI); + if (muxcy_s != xorcy_li) + return true; + } + return false; +} + +void XilinxPacker::split_carry4s() +{ + std::vector carry4s; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type != id_CARRY4) + continue; + carry4s.push_back(ci); + } + for (CellInfo *ci : carry4s) { + NetInfo *cin = ci->getPort(id_CI); + if (cin == nullptr || cin->name == ctx->id("$PACKER_GND_NET")) { + cin = ci->getPort(id_CYINIT); + } + ci->disconnectPort(id_CI); + ci->disconnectPort(id_CYINIT); + + for (int i = 0; i < 4; i++) { + CellInfo *xorcy = create_cell(id_XORCY, ctx->idf("%s$split$xorcy%d", ci->name.c_str(ctx), i)); + CellInfo *muxcy = create_cell(id_MUXCY, ctx->idf("%s$split$muxcy%d", ci->name.c_str(ctx), i)); + muxcy->connectPort(id_CI, cin); + xorcy->connectPort(id_CI, cin); + ci->movePortTo(ctx->idf("DI[%d]", i), muxcy, id_DI); + muxcy->connectPort(id_S, ci->getPort(ctx->id("S[" + std::to_string(i) + "]"))); + ci->movePortTo(ctx->idf("S[%d]", i), xorcy, id_LI); + ci->movePortTo(ctx->idf("O[%d]", i), xorcy, id_O); + NetInfo *co = ci->getPort(ctx->idf("CO[%d]", i)); + ci->disconnectPort(ctx->idf("CO[%d]", i)); + if (!co) + co = create_internal_net(ci->name, stringf("$split$co%d", i), false); + muxcy->connectPort(id_O, co); + cin = co; + } + packed_cells.insert(ci->name); + } + flush_cells(); +} + +void XC7Packer::pack_carries() +{ + log_info("Packing carries..\n"); + split_carry4s(); + std::vector root_muxcys; + // Find MUXCYs + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type != id_MUXCY) + continue; + NetInfo *ci_net = ci->getPort(id_CI); + if (!ci_net || !ci_net->driver.cell || ci_net->driver.cell->type != id_MUXCY || has_illegal_fanout(ci_net)) { + root_muxcys.push_back(ci); + } + } + + // Create chains from root MUXCYs + pool processed_muxcys; + std::vector groups; + int muxcy_count = 0, xorcy_count = 0; + for (auto root : root_muxcys) { + CarryGroup group; + + CellInfo *muxcy = root; + NetInfo *mux_ci = nullptr; + while (true) { + + group.muxcys.push_back(muxcy); + ++muxcy_count; + mux_ci = muxcy->getPort(id_CI); + NetInfo *mux_s = muxcy->getPort(id_S); + group.xorcys.push_back(nullptr); + if (mux_s != nullptr) { + for (auto &user : mux_s->users) { + if (user.cell->type == id_XORCY && user.port == id_LI) { + CellInfo *xorcy = user.cell; + NetInfo *xor_ci = xorcy->getPort(id_CI); + if (xor_ci == mux_ci) { + group.xorcys.back() = xorcy; + ++xorcy_count; + break; + } + } + } + } + + mux_ci = muxcy->getPort(id_O); + if (mux_ci == nullptr) + break; + if (has_illegal_fanout(mux_ci)) + break; + muxcy = nullptr; + for (auto &user : mux_ci->users) { + if (user.cell->type == id_MUXCY) { + muxcy = user.cell; + break; + } + } + if (muxcy == nullptr) + break; + } + if (mux_ci != nullptr) { + if (mux_ci->users.entries() == 1 && (*mux_ci->users.begin()).cell->type == id_XORCY && + (*mux_ci->users.begin()).port == id_CI) { + // Trailing XORCY at end, can pack into chain. + CellInfo *xorcy = (*mux_ci->users.begin()).cell; + CellInfo *dummy_muxcy = create_cell(id_MUXCY, ctx->idf("%s$legal_muxcy$", xorcy->name.c_str(ctx))); + dummy_muxcy->connectPort(id_CI, mux_ci); + dummy_muxcy->connectPort(id_S, xorcy->getPort(id_LI)); + group.muxcys.push_back(dummy_muxcy); + group.xorcys.push_back(xorcy); + } else if (mux_ci->users.entries() > 0) { + // Users other than a MUXCY + // Feed out with a zero-driving LUT and a XORCY + // (creating a zero-driver using Vcc and an inverter for now...) + CellInfo *zero_lut = create_lut(mux_ci->name.str(ctx) + "$feed$zero", + {ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get()}, nullptr, Property(1)); + CellInfo *feed_xorcy = create_cell(id_XORCY, ctx->id(mux_ci->name.str(ctx) + "$feed$xor")); + CellInfo *dummy_muxcy = create_cell(id_MUXCY, ctx->id(mux_ci->name.str(ctx) + "$feed$muxcy")); + + CellInfo *last_muxcy = mux_ci->driver.cell; + + last_muxcy->disconnectPort(id_O); + + zero_lut->connectPorts(id_O, feed_xorcy, id_LI); + zero_lut->connectPorts(id_O, dummy_muxcy, id_S); + last_muxcy->connectPorts(id_O, feed_xorcy, id_CI); + last_muxcy->connectPorts(id_O, dummy_muxcy, id_CI); + + feed_xorcy->connectPort(id_O, mux_ci); + + group.muxcys.push_back(dummy_muxcy); + group.xorcys.push_back(feed_xorcy); + } + } + + groups.push_back(group); + } + flush_cells(); + + log_info(" Grouped %d MUXCYs and %d XORCYs into %d chains.\n", muxcy_count, xorcy_count, int(root_muxcys.size())); + + // N.B. LUT6 is not a valid type here, as CARRY requires dual outputs + pool lut_types{id_LUT1, id_LUT2, id_LUT3, id_LUT4, id_LUT5}; + + pool folded_nets; + + for (auto &grp : groups) { + std::vector carry4s; + for (int i = 0; i < int(grp.muxcys.size()); i++) { + int z = i % 4; + CellInfo *muxcy = grp.muxcys.at(i), *xorcy = grp.xorcys.at(i); + if (z == 0) + carry4s.push_back(create_cell(id_CARRY4, ctx->idf("%s$PACKED_CARRY4$", muxcy->name.c_str(ctx)))); + CellInfo *c4 = carry4s.back(); + CellInfo *root = carry4s.front(); + if (i == 0) { + // Constrain initial CARRY4, forcing it to the CARRY4 of a logic tile + c4->cluster = c4->name; + c4->constr_abs_z = true; + c4->constr_z = BEL_CARRY4; + } else if (z == 0) { + // Constrain relative to the root carry4 + c4->cluster = root->name; + root->constr_children.push_back(c4); + c4->constr_x = 0; + // Looks no CARRY4 on the tile of which grid_y is a multiple of 26. Skip them + c4->constr_y = -(i / 4 + i / (4 * 25)); + c4->constr_abs_z = true; + c4->constr_z = BEL_CARRY4; + } + // Fold CI->CO connections into the CARRY4, except for those external ones every 8 units + if (z == 0 && i == 0) { + muxcy->movePortTo(id_CI, c4, id_CYINIT); + } else if (z == 0 && i > 0) { + muxcy->movePortTo(id_CI, c4, id_CI); + } else { + NetInfo *muxcy_ci = muxcy->getPort(id_CI); + if (muxcy_ci) + folded_nets.insert(muxcy_ci->name); + muxcy->disconnectPort(id_CI); + } + if (z == 3) { + muxcy->movePortTo(id_O, c4, ctx->id("CO[3]")); + } else { + NetInfo *muxcy_o = muxcy->getPort(id_O); + if (muxcy_o) + folded_nets.insert(muxcy_o->name); + muxcy->disconnectPort(id_O); + } + // Replace connections into the MUXCY with external CARRY4 ports + muxcy->movePortTo(id_S, c4, ctx->idf("S[%d]", z)); + muxcy->movePortTo(id_DI, c4, ctx->idf("DI[%d]", z)); + packed_cells.insert(muxcy->name); + // Fold MUXCY->XORCY into the CARRY4, if there is a XORCY + if (xorcy) { + // Replace XORCY output with external CARRY4 output + xorcy->movePortTo(id_O, c4, ctx->idf("O[%d]", z)); + // Disconnect internal XORCY connectivity + xorcy->disconnectPort(id_LI); + xorcy->disconnectPort(id_DI); + packed_cells.insert(xorcy->name); + } + // Check legality of LUTs driving CARRY4, making them legal if they aren't already + NetInfo *c4_s = c4->getPort(ctx->idf("S[%d]", z)); + NetInfo *c4_di = c4->getPort(ctx->idf("DI[%d]", z)); + // Keep track of the total LUT input count; cannot exceed five or the LUTs cannot be packed together + pool unique_lut_inputs; + int s_inputs = 0, d_inputs = 0; + // Check that S and DI are validy and unqiuely driven by LUTs + // FIXME: in multiple fanout cases, cell duplication will probably be cheaper + // than feed-throughs + CellInfo *s_lut = nullptr, *di_lut = nullptr; + if (c4_s) { + if (c4_s->users.entries() == 1 && c4_s->driver.cell != nullptr && + lut_types.count(c4_s->driver.cell->type)) { + s_lut = c4_s->driver.cell; + for (int j = 0; j < 5; j++) { + NetInfo *ix = s_lut->getPort(ctx->idf("I%d", j)); + if (ix) { + unique_lut_inputs.insert(ix->name); + s_inputs++; + } + } + } + } + if (c4_di) { + if (c4_di->users.entries() == 1 && c4_di->driver.cell != nullptr && + lut_types.count(c4_di->driver.cell->type)) { + di_lut = c4_di->driver.cell; + for (int j = 0; j < 5; j++) { + NetInfo *ix = di_lut->getPort(ctx->idf("I%d", j)); + if (ix) { + unique_lut_inputs.insert(ix->name); + d_inputs++; + } + } + } + } + int lut_inp_count = int(unique_lut_inputs.size()); + if (!s_lut) + ++lut_inp_count; // for feedthrough + if (!di_lut) + ++lut_inp_count; // for feedthrough + if (lut_inp_count > 5) { + // Must use feedthrough for at least one LUT + di_lut = nullptr; + if (s_inputs > 4) + s_lut = nullptr; + } + // If LUTs are nullptr, that means we need a feedthrough lut + if (!s_lut && c4_s) { + PortRef pr; + pr.cell = c4; + pr.port = ctx->idf("S[%d]", z); + auto s_feed = feed_through_lut(c4_s, {pr}); + s_lut = s_feed; + } + if (!di_lut && c4_di) { + PortRef pr; + pr.cell = c4; + pr.port = ctx->idf("DI[%d]", z); + auto di_feed = feed_through_lut(c4_di, {pr}); + di_lut = di_feed; + } + // Constrain LUTs relative to root CARRY4 + if (s_lut) { + root->constr_children.push_back(s_lut); + s_lut->cluster = root->name; + s_lut->constr_x = 0; + s_lut->constr_y = -(i / 4 + i / (4 * 25)); + s_lut->constr_abs_z = true; + s_lut->constr_z = (z << 4 | BEL_6LUT); + } + if (di_lut) { + root->constr_children.push_back(di_lut); + di_lut->cluster = root->name; + di_lut->constr_x = 0; + di_lut->constr_y = -(i / 4 + i / (4 * 25)); + di_lut->constr_abs_z = true; + di_lut->constr_z = (z << 4 | BEL_5LUT); + } + } + } + flush_cells(); + + for (auto net : folded_nets) + ctx->nets.erase(net); + + // XORCYs and MUXCYs not part of any chain (and therefore not packed into a CARRY4) must now be blasted + // to boring soft logic (LUT2 or LUT3 - these will become SLICE_LUTXs later in the flow.) + int remaining_muxcy = 0, remaining_xorcy = 0; + for (auto &cell : ctx->cells) { + if (cell.second->type == id_MUXCY) + ++remaining_muxcy; + else if (cell.second->type == id_XORCY) + ++remaining_xorcy; + } + dict softlogic_rules; + softlogic_rules[id_MUXCY].new_type = id_LUT3; + softlogic_rules[id_MUXCY].port_xform[id_DI] = id_I0; + softlogic_rules[id_MUXCY].port_xform[id_CI] = id_I1; + softlogic_rules[id_MUXCY].port_xform[id_S] = id_I2; + // DI 1010 1010 + // CI 1100 1100 + // S 1111 0000 + // O 1100 1010 + softlogic_rules[id_MUXCY].set_params.emplace_back(id_INIT, Property(0xCA)); + + softlogic_rules[id_XORCY].new_type = id_LUT2; + softlogic_rules[id_XORCY].port_xform[id_CI] = id_I0; + softlogic_rules[id_XORCY].port_xform[id_LI] = id_I1; + // CI 1100 + // LI 1010 + // O 0110 + softlogic_rules[id_XORCY].set_params.emplace_back(id_INIT, Property(0x6)); + + generic_xform(softlogic_rules, false); + log_info(" Blasted %d non-chain MUXCYs and %d non-chain XORCYs to soft logic\n", remaining_muxcy, + remaining_xorcy); + + // Finally, use generic_xform to remove the [] from bus ports; and set up the logical-physical mapping for + // RapidWright + dict c4_rules; + c4_rules[id_CARRY4].new_type = id_CARRY4; + c4_rules[id_CARRY4].port_xform[id_CI] = id_CIN; + + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type != id_CARRY4) + continue; + xform_cell(c4_rules, ci); + } +} + +NEXTPNR_NAMESPACE_END \ No newline at end of file diff --git a/himbaechel/uarch/xilinx/pack_clocking.cc b/himbaechel/uarch/xilinx/pack_clocking.cc new file mode 100644 index 0000000000..eb743f22eb --- /dev/null +++ b/himbaechel/uarch/xilinx/pack_clocking.cc @@ -0,0 +1,350 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019-2023 Myrtle Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include "chain_utils.h" +#include "design_utils.h" +#include "extra_data.h" +#include "log.h" +#include "nextpnr.h" +#include "pack.h" +#include "pins.h" + +#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" +#include "himbaechel_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +BelId XilinxPacker::find_bel_with_short_route(WireId source, IdString beltype, IdString belpin) +{ + if (source == WireId()) + return BelId(); + const size_t max_visit = 50000; // effort/runtime tradeoff + pool visited; + std::queue visit; + visit.push(source); + while (!visit.empty() && visited.size() < max_visit) { + WireId cursor = visit.front(); + visit.pop(); + for (auto bp : ctx->getWireBelPins(cursor)) + if (bp.pin == belpin && ctx->getBelType(bp.bel) == beltype && ctx->checkBelAvail(bp.bel)) + return bp.bel; + for (auto pip : ctx->getPipsDownhill(cursor)) { + WireId dst = ctx->getPipDstWire(pip); + if (visited.count(dst)) + continue; + visit.push(dst); + visited.insert(dst); + } + } + return BelId(); +} + +void XilinxPacker::try_preplace(CellInfo *cell, IdString port) +{ + if (cell->attrs.count(id_BEL) || cell->bel != BelId()) + return; + NetInfo *n = cell->getPort(port); + if (n == nullptr || n->driver.cell == nullptr) + return; + CellInfo *drv = n->driver.cell; + BelId drv_bel = drv->bel; + if (drv_bel == BelId()) + return; + WireId drv_wire = ctx->getBelPinWire(drv_bel, n->driver.port); + if (drv_wire == WireId()) + return; + BelId tgt = find_bel_with_short_route(drv_wire, cell->type, port); + if (tgt != BelId()) { + ctx->bindBel(tgt, cell, STRENGTH_LOCKED); + log_info(" Constrained %s '%s' to bel '%s' based on dedicated routing\n", cell->type.c_str(ctx), + ctx->nameOf(cell), ctx->nameOfBel(tgt)); + } +} + +void XilinxPacker::preplace_unique(CellInfo *cell) +{ + if (cell->attrs.count(id_BEL) || cell->bel != BelId()) + return; + for (auto bel : ctx->getBels()) { + if (ctx->checkBelAvail(bel) && ctx->getBelType(bel) == cell->type) { + ctx->bindBel(bel, cell, STRENGTH_LOCKED); + return; + } + } +} + +void XC7Packer::prepare_clocking() +{ + log_info("Preparing clocking...\n"); + dict upgrade; + upgrade[id_MMCME2_BASE] = id_MMCME2_ADV; + upgrade[id_PLLE2_BASE] = id_PLLE2_ADV; + + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (upgrade.count(ci->type)) { + IdString new_type = upgrade.at(ci->type); + ci->type = new_type; + } else if (ci->type == id_BUFG) { + ci->type = id_BUFGCTRL; + ci->renamePort(id_I, id_I0); + tie_port(ci, "CE0", true, true); + tie_port(ci, "S0", true, true); + tie_port(ci, "S1", false, true); + tie_port(ci, "IGNORE0", true, true); + } else if (ci->type == id_BUFGCE) { + ci->type = id_BUFGCTRL; + ci->renamePort(id_I, id_I0); + ci->renamePort(id_CE, id_CE0); + tie_port(ci, "S0", true, true); + tie_port(ci, "S1", false, true); + tie_port(ci, "IGNORE0", true, true); + } + } +} + +void XC7Packer::pack_plls() +{ + log_info("Packing PLLs...\n"); + + auto set_default = [](CellInfo *ci, IdString param, const Property &value) { + if (!ci->params.count(param)) + ci->params[param] = value; + }; + + dict pll_rules; + pll_rules[id_MMCME2_ADV].new_type = id_MMCME2_ADV_MMCME2_ADV; + pll_rules[id_PLLE2_ADV].new_type = id_PLLE2_ADV_PLLE2_ADV; + generic_xform(pll_rules); + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + // Preplace PLLs to make use of dedicated/short routing paths + if (ci->type.in(id_MMCM_MMCM_TOP, id_PLL_PLL_TOP)) + try_preplace(ci, id_CLKIN1); + if (ci->type == id_MMCM_MMCM_TOP) { + // Fixup parameters + for (int i = 1; i <= 2; i++) + set_default(ci, ctx->idf("CLKIN%d_PERIOD", i), Property("0.0")); + for (int i = 0; i <= 6; i++) { + set_default(ci, ctx->idf("CLKOUT%d_CASCADE", i), Property("FALSE")); + set_default(ci, ctx->idf("CLKOUT%d_DIVIDE", i), Property(1)); + set_default(ci, ctx->idf("CLKOUT%d_DUTY_CYCLE", i), Property("0.5")); + set_default(ci, ctx->idf("CLKOUT%d_PHASE", i), Property(0)); + set_default(ci, ctx->idf("CLKOUT%d_USE_FINE_PS", i), Property("FALSE")); + } + set_default(ci, id_COMPENSATION, Property("INTERNAL")); + + // Fixup routing + if (str_or_default(ci->params, id_COMPENSATION, "INTERNAL") == "INTERNAL") { + ci->disconnectPort(id_CLKFBIN); + ci->connectPort(id_CLKFBIN, ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get()); + } + } + } +} + +void XC7Packer::pack_gbs() +{ + log_info("Packing global buffers...\n"); + dict gb_rules; + gb_rules[id_BUFGCTRL].new_type = id_BUFGCTRL; + gb_rules[id_BUFGCTRL].new_type = id_BUFGCTRL; + + generic_xform(gb_rules); + + // Make sure prerequisites are set up first + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id_PS7_PS7) + preplace_unique(ci); + if (ci->type.in(id_PSEUDO_GND, id_PSEUDO_VCC)) + preplace_unique(ci); + } + + // Preplace global buffers to make use of dedicated/short routing + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id_BUFGCTRL) + try_preplace(ci, id_I0); + if (ci->type == id_BUFG_BUFG) + try_preplace(ci, id_I); + } +} + +void XC7Packer::pack_clocking() +{ + pack_plls(); + pack_gbs(); +} + +void XilinxImpl::route_clocks() +{ + log_info("Routing global clocks...\n"); + // Special pass for faster routing of global clock psuedo-net + for (auto &net : ctx->nets) { + NetInfo *clk_net = net.second.get(); + if (!clk_net->driver.cell) + continue; + + // check if we have a global clock net, skip otherwise + bool is_global = false; + if ((clk_net->driver.cell->type.in(id_BUFGCTRL, id_BUFCE_BUFG_PS, id_BUFCE_BUFCE, id_BUFGCE_DIV_BUFGCE_DIV)) && + clk_net->driver.port == id_O) + is_global = true; + else if (clk_net->driver.cell->type == id_PLLE2_ADV_PLLE2_ADV && clk_net->users.entries() == 1 && + ((*clk_net->users.begin()).cell->type.in(id_BUFGCTRL, id_BUFCE_BUFCE, id_BUFGCE_DIV_BUFGCE_DIV))) + is_global = true; + else if (clk_net->users.entries() == 1 && (*clk_net->users.begin()).cell->type == id_PLLE2_ADV_PLLE2_ADV && + (*clk_net->users.begin()).port == id_CLKIN1) + is_global = true; + if (!is_global) + continue; + + log_info(" routing clock '%s'\n", clk_net->name.c_str(ctx)); + ctx->bindWire(ctx->getNetinfoSourceWire(clk_net), clk_net, STRENGTH_LOCKED); + + for (auto &usr : clk_net->users) { + std::queue visit; + dict backtrace; + WireId dest = WireId(); + + auto sink_wire = ctx->getNetinfoSinkWire(clk_net, usr, 0); + if (ctx->debug) { + auto sink_wire_name = "(uninitialized)"; + if (sink_wire != WireId()) + sink_wire_name = ctx->nameOfWire(sink_wire); + log_info(" routing arc to %s.%s (wire %s):\n", usr.cell->name.c_str(ctx), usr.port.c_str(ctx), + sink_wire_name); + } + + visit.push(sink_wire); + while (!visit.empty()) { + WireId curr = visit.front(); + visit.pop(); + if (ctx->getBoundWireNet(curr) == clk_net) { + dest = curr; + break; + } + for (auto uh : ctx->getPipsUphill(curr)) { + if (!ctx->checkPipAvail(uh)) + continue; + WireId src = ctx->getPipSrcWire(uh); + if (backtrace.count(src)) + continue; + IdString intent = ctx->getWireType(src); + if (intent.in(id_NODE_DOUBLE, id_NODE_HLONG, id_NODE_HQUAD, id_NODE_VLONG, id_NODE_VQUAD, + id_NODE_SINGLE, id_NODE_CLE_OUTPUT, id_NODE_OPTDELAY, id_BENTQUAD, id_DOUBLE, + id_HLONG, id_HQUAD, id_OPTDELAY, id_SINGLE, id_VLONG, id_VLONG12, id_VQUAD, + id_PINBOUNCE)) + continue; + if (!ctx->checkWireAvail(src) && ctx->getBoundWireNet(src) != clk_net) + continue; + backtrace[src] = uh; + visit.push(src); + } + } + if (dest == WireId()) { + log_info(" failed to find a route using dedicated resources.\n"); + if (clk_net->users.entries() == 1 && (*clk_net->users.begin()).cell->type == id_PLLE2_ADV_PLLE2_ADV && + (*clk_net->users.begin()).port == id_CLKIN1) { + // Due to some missing pips, currently special case more lenient solution + std::queue empty; + std::swap(visit, empty); + backtrace.clear(); + visit.push(sink_wire); + while (!visit.empty()) { + WireId curr = visit.front(); + visit.pop(); + if (ctx->getBoundWireNet(curr) == clk_net) { + dest = curr; + break; + } + for (auto uh : ctx->getPipsUphill(curr)) { + if (!ctx->checkPipAvail(uh)) + continue; + WireId src = ctx->getPipSrcWire(uh); + if (backtrace.count(src)) + continue; + if (!ctx->checkWireAvail(src) && ctx->getBoundWireNet(src) != clk_net) + continue; + backtrace[src] = uh; + visit.push(src); + } + } + if (dest == WireId()) + continue; + } else { + continue; + } + } + while (backtrace.count(dest)) { + auto uh = backtrace[dest]; + dest = ctx->getPipDstWire(uh); + if (ctx->getBoundWireNet(dest) == clk_net) { + NPNR_ASSERT(clk_net->wires.at(dest).pip == uh); + break; + } + if (ctx->debug) + log_info(" bind pip %s --> %s\n", ctx->nameOfPip(uh), ctx->nameOfWire(dest)); + ctx->bindPip(uh, clk_net, STRENGTH_LOCKED); + } + } + } +#if 0 + for (auto& net : nets) { + NetInfo *ni = net.second.get(); + for (auto &usr : ni->users) { + if (usr.cell->type != id_BUFGCTRL || usr.port != id_I0) + continue; + WireId dst = getCtx()->getNetinfoSinkWire(ni, usr, 0); + std::queue visit; + visit.push(dst); + int i = 0; + while(!visit.empty() && i < 5000) { + WireId curr = visit.front(); + visit.pop(); + log(" %s\n", nameOfWire(curr)); + for (auto pip : getPipsUphill(curr)) { + auto &pd = locInfo(pip).pip_data[pip.index]; + log_info(" p %s sr %s (t %d s %d sv %d)\n", nameOfPip(pip), nameOfWire(getPipSrcWire(pip)), pd.flags, pd.site, pd.site_variant); + if (!checkPipAvail(pip)) { + log(" p unavail\n"); + continue; + } + WireId src = getPipSrcWire(pip); + if (!checkWireAvail(src)) { + log(" w unavail (%s)\n", nameOf(getBoundWireNet(src))); + continue; + } + log_info(" p %s s %s\n", nameOfPip(pip), nameOfWire(src)); + visit.push(src); + } + ++i; + } + } + } +#endif +} + +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/xilinx/pack_dram.cc b/himbaechel/uarch/xilinx/pack_dram.cc new file mode 100644 index 0000000000..00f9d40869 --- /dev/null +++ b/himbaechel/uarch/xilinx/pack_dram.cc @@ -0,0 +1,477 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2023 Myrtle Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include "chain_utils.h" +#include "design_utils.h" +#include "extra_data.h" +#include "log.h" +#include "nextpnr.h" +#include "pack.h" +#include "pins.h" + +#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" +#include "himbaechel_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +CellInfo *XilinxPacker::create_dram_lut(const std::string &name, CellInfo *base, const DRAMControlSet &ctrlset, + std::vector address, NetInfo *di, NetInfo *dout, int z) +{ + CellInfo *dram_lut = create_cell(id_RAMD64E, ctx->id(name)); + for (int i = 0; i < int(address.size()); i++) + dram_lut->connectPort(ctx->idf("RADR%d", i), address[i]); + dram_lut->connectPort(id_I, di); + dram_lut->connectPort(id_O, dout); + dram_lut->connectPort(id_CLK, ctrlset.wclk); + dram_lut->connectPort(id_WE, ctrlset.we); + for (int i = 0; i < int(ctrlset.wa.size()); i++) + dram_lut->connectPort(ctx->idf("WADR%d", i), ctrlset.wa[i]); + dram_lut->params[id_IS_WCLK_INVERTED] = ctrlset.wclk_inv ? 1 : 0; + + xform_cell(dram_rules, dram_lut); + + dram_lut->constr_abs_z = true; + dram_lut->constr_z = (z << 4) | BEL_6LUT; + if (base != nullptr) { + dram_lut->cluster = base->name; + dram_lut->constr_x = 0; + dram_lut->constr_y = 0; + base->constr_children.push_back(dram_lut); + } + if (base == nullptr) { + dram_lut->cluster = dram_lut->name; + } + return dram_lut; +} + +CellInfo *XilinxPacker::create_dram32_lut(const std::string &name, CellInfo *base, const DRAMControlSet &ctrlset, + std::vector address, NetInfo *di, NetInfo *dout, bool o5, int z) +{ + CellInfo *dram_lut = create_cell(id_RAMD32, ctx->id(name)); + for (int i = 0; i < int(address.size()); i++) + dram_lut->connectPort(ctx->idf("RADR%d", i), address[i]); + dram_lut->connectPort(id_I, di); + dram_lut->connectPort(id_O, dout); + dram_lut->connectPort(id_CLK, ctrlset.wclk); + dram_lut->connectPort(id_WE, ctrlset.we); + for (int i = 0; i < int(ctrlset.wa.size()); i++) + dram_lut->connectPort(ctx->idf("WADR%d", i), ctrlset.wa[i]); + dram_lut->params[id_IS_WCLK_INVERTED] = ctrlset.wclk_inv ? 1 : 0; + + xform_cell(o5 ? dram32_5_rules : dram32_6_rules, dram_lut); + + dram_lut->constr_abs_z = true; + dram_lut->constr_z = (z << 4) | (o5 ? BEL_5LUT : BEL_6LUT); + if (base != nullptr) { + dram_lut->cluster = base->name; + dram_lut->constr_x = 0; + dram_lut->constr_y = 0; + base->constr_children.push_back(dram_lut); + } + if (base == nullptr) { + dram_lut->cluster = dram_lut->name; + } + return dram_lut; +} + +void XilinxPacker::create_muxf_tree(CellInfo *base, const std::string &name_base, const std::vector &data, + const std::vector &select, NetInfo *out, int zoffset) +{ + int levels = 0; + if (data.size() <= 2) + levels = 1; + else if (data.size() <= 4) + levels = 2; + else if (data.size() <= 8) + levels = 3; + else + NPNR_ASSERT_FALSE("muxf tree too large"); + NPNR_ASSERT(int(select.size()) == levels); + std::vector> int_data; + CellInfo *mux_root = nullptr; + int_data.push_back(data); + for (int i = 0; i < levels; i++) { + IdString mux_type; + if (i == 0) + mux_type = id_MUXF7; + else if (i == 1) + mux_type = id_MUXF8; + else if (i == 2) + mux_type = id_MUXF9; + else + NPNR_ASSERT_FALSE("unknown muxf type"); + int_data.emplace_back(); + auto &last = int_data.at(int_data.size() - 2); + for (int j = 0; j < int(last.size()) / 2; j++) { + NetInfo *output = + (i == (levels - 1)) + ? out + : create_internal_net(base->name, stringf("%s_muxq_%d_%d", name_base.c_str(), i, j), false); + int_data.back().push_back(output); + auto mux = create_cell(mux_type, + int_name(base->name, stringf("%s_muxf_%d_%d", name_base.c_str(), i, j), false)); + mux->connectPort(id_I0, last.at(j * 2)); + mux->connectPort(id_I1, last.at(j * 2 + 1)); + mux->connectPort(id_S, select.at(i)); + mux->connectPort(id_O, output); + if (i == (levels - 1)) + mux_root = mux; + } + } + constrain_muxf_tree(mux_root, base, zoffset); +} + +void XilinxPacker::pack_dram() +{ + + log_info("Packing DRAM..\n"); + + dict> dram_groups; + dict dram_types; + + dram_types[id_RAM32X1S] = {5, 1, 0}; + dram_types[id_RAM32X1D] = {5, 1, 1}; + dram_types[id_RAM64X1S] = {6, 1, 0}; + dram_types[id_RAM64X1D] = {6, 1, 1}; + dram_types[id_RAM128X1S] = {7, 1, 0}; + dram_types[id_RAM128X1D] = {7, 1, 1}; + dram_types[id_RAM256X1S] = {8, 1, 0}; + dram_types[id_RAM256X1D] = {8, 1, 1}; + dram_types[id_RAM512X1S] = {9, 1, 0}; + dram_types[id_RAM512X1D] = {9, 1, 1}; + + // Transform from RAMD64E UNISIM to SLICE_LUTX bel + dram_rules[id_RAMD64E].new_type = id_SLICE_LUTX; + dram_rules[id_RAMD64E].param_xform[id_IS_CLK_INVERTED] = id_IS_WCLK_INVERTED; + dram_rules[id_RAMD64E].set_attrs.emplace_back(id_X_LUT_AS_DRAM, "1"); + for (int i = 0; i < 6; i++) + dram_rules[id_RAMD64E].port_xform[ctx->idf("RADR%d", i)] = ctx->idf("A%d", i + 1); + for (int i = 0; i < 8; i++) + dram_rules[id_RAMD64E].port_xform[ctx->idf("WADR%d", i)] = ctx->idf("WA%d", i + 1); + dram_rules[id_RAMD64E].port_xform[id_I] = id_DI1; + dram_rules[id_RAMD64E].port_xform[id_O] = id_O6; + + // Rules for upper and lower RAMD32E + dram32_6_rules[id_RAMD32].new_type = id_SLICE_LUTX; + dram32_6_rules[id_RAMD32].param_xform[id_IS_CLK_INVERTED] = id_IS_WCLK_INVERTED; + dram32_6_rules[id_RAMD32].set_attrs.emplace_back(id_X_LUT_AS_DRAM, "1"); + for (int i = 0; i < 5; i++) + dram32_6_rules[id_RAMD32].port_xform[ctx->idf("RADR%d", i)] = ctx->idf("A%d", i + 1); + for (int i = 0; i < 5; i++) + dram32_6_rules[id_RAMD32].port_xform[ctx->idf("WADR%d", i)] = ctx->idf("WA%d", i + 1); + dram32_6_rules[id_RAMD32].port_xform[id_I] = id_DI2; + dram32_6_rules[id_RAMD32].port_xform[id_O] = id_O6; + + dram32_5_rules = dram32_6_rules; + dram32_5_rules[id_RAMD32].port_xform[id_I] = id_DI1; + dram32_5_rules[id_RAMD32].port_xform[id_O] = id_O5; + + // Optimise DRAM with tied-low inputs, to more efficiently routeable tied-high inputs + int inverted_ports = 0; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + auto dt_iter = dram_types.find(ci->type); + if (dt_iter == dram_types.end()) + continue; + auto &dt = dt_iter->second; + for (int i = 0; i < std::min(dt.abits, 6); i++) { + IdString aport = ctx->idf(dt.abits <= 6 ? "A%d" : "A[%d]", i); + if (!ci->ports.count(aport)) + continue; + NetInfo *anet = ci->getPort(aport); + if (anet == nullptr || anet->name != ctx->id("$PACKER_GND_NET")) + continue; + IdString raport; + if (dt.rports >= 1) { + NPNR_ASSERT(dt.rports == 1); // FIXME + raport = ctx->idf(dt.abits <= 6 ? "DPRA%d" : "DPRA[%d]", i); + NetInfo *ranet = ci->getPort(raport); + if (ranet == nullptr || ranet->name != ctx->id("$PACKER_GND_NET")) + continue; + } + ci->disconnectPort(aport); + if (raport != IdString()) + ci->disconnectPort(raport); + ci->connectPort(aport, ctx->nets[ctx->id("$PACKER_VCC_NET")].get()); + if (raport != IdString()) + ci->connectPort(raport, ctx->nets[ctx->id("$PACKER_VCC_NET")].get()); + ++inverted_ports; + if (ci->params.count(id_INIT)) { + Property &init = ci->params[id_INIT]; + for (int j = 0; j < int(init.str.size()); j++) { + if (j & (1 << i)) + init.str[j] = init.str[j & ~(1 << i)]; + } + init.update_intval(); + } + } + } + log_info(" Transformed %d tied-low DRAM address inputs to be tied-high\n", inverted_ports); + + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + auto dt_iter = dram_types.find(ci->type); + if (dt_iter == dram_types.end()) + continue; + auto &dt = dt_iter->second; + DRAMControlSet dcs; + for (int i = 0; i < dt.abits; i++) + dcs.wa.push_back(ci->getPort(ctx->idf(dt.abits <= 6 ? "A%d" : "A[%d]", i))); + dcs.wclk = ci->getPort(id_WCLK); + dcs.we = ci->getPort(id_WE); + dcs.wclk_inv = bool_or_default(ci->params, id_IS_WCLK_INVERTED); + dcs.memtype = ci->type; + dram_groups[dcs].push_back(ci); + } + + int height = /*ctx->xc7 ? 4 : 8*/ 4; + // Grouped DRAM + for (auto &group : dram_groups) { + auto &cs = group.first; + if (cs.memtype == id_RAM64X1D) { + int z = height - 1; + CellInfo *base = nullptr; + for (auto cell : group.second) { + NPNR_ASSERT(cell->type == id_RAM64X1D); // FIXME + + int z_size = 0; + if (cell->getPort(id_SPO) != nullptr) + z_size++; + if (cell->getPort(id_DPO) != nullptr) + z_size++; + + if (z == (height - 1) || (z - z_size + 1) < 0) { + z = (height - 1); + // Topmost cell is the write address input + std::vector address(cs.wa.begin(), cs.wa.begin() + std::min(cs.wa.size(), 6)); + base = create_dram_lut(cell->name.str(ctx) + "/ADDR", nullptr, cs, address, nullptr, nullptr, z); + z--; + } + + NetInfo *dpo = cell->getPort(id_DPO); + NetInfo *spo = cell->getPort(id_SPO); + cell->disconnectPort(id_DPO); + cell->disconnectPort(id_SPO); + + NetInfo *di = cell->getPort(id_D); + if (spo != nullptr) { + if (z == (height - 2)) { + // Can fold DPO into address buffer + base->connectPort(id_O6, spo); + base->connectPort(id_DI1, di); + if (cell->params.count(id_INIT)) + base->params[id_INIT] = cell->params[id_INIT]; + } else { + std::vector address(cs.wa.begin(), + cs.wa.begin() + std::min(cs.wa.size(), 6)); + CellInfo *dpr = create_dram_lut(cell->name.str(ctx) + "/SP", base, cs, address, di, spo, z); + if (cell->params.count(id_INIT)) + dpr->params[id_INIT] = cell->params[id_INIT]; + z--; + } + } + + if (dpo != nullptr) { + std::vector address; + for (int i = 0; i < 6; i++) + address.push_back(cell->getPort(ctx->id("DPRA" + std::to_string(i)))); + CellInfo *dpr = create_dram_lut(cell->name.str(ctx) + "/DP", base, cs, address, di, dpo, z); + if (cell->params.count(id_INIT)) + dpr->params[id_INIT] = cell->params[id_INIT]; + z--; + } + + packed_cells.insert(cell->name); + } + } else if (cs.memtype == id_RAM32X1D) { + int z = (height - 1); + CellInfo *base = nullptr; + for (auto cell : group.second) { + NPNR_ASSERT(cell->type == id_RAM32X1D); + + int z_size = 0; + if (cell->getPort(id_SPO) != nullptr) + z_size++; + if (cell->getPort(id_DPO) != nullptr) + z_size++; + + if (z == (height - 1) || (z - z_size + 1) < 0) { + z = (height - 1); + // Topmost cell is the write address input + std::vector address(cs.wa.begin(), cs.wa.begin() + std::min(cs.wa.size(), 5)); + address.push_back(ctx->nets[ctx->id("$PACKER_GND_NET")].get()); + base = create_dram_lut(cell->name.str(ctx) + "/ADDR", nullptr, cs, address, nullptr, nullptr, z); + z--; + } + + NetInfo *dpo = cell->getPort(id_DPO); + NetInfo *spo = cell->getPort(id_SPO); + cell->disconnectPort(id_DPO); + cell->disconnectPort(id_SPO); + + NetInfo *di = cell->getPort(id_D); + if (spo != nullptr) { + if (z == (height - 2)) { + // Can fold DPO into address buffer + base->connectPort(id_O6, spo); + base->connectPort(id_DI1, di); + if (cell->params.count(id_INIT)) + base->params[id_INIT] = cell->params[id_INIT]; + } else { + std::vector address(cs.wa.begin(), + cs.wa.begin() + std::min(cs.wa.size(), 5)); + address.push_back(ctx->nets[ctx->id("$PACKER_GND_NET")].get()); + CellInfo *dpr = create_dram_lut(cell->name.str(ctx) + "/SP", base, cs, address, di, spo, z); + if (cell->params.count(id_INIT)) + dpr->params[id_INIT] = cell->params[id_INIT]; + z--; + } + } + + if (dpo != nullptr) { + std::vector address; + for (int i = 0; i < 5; i++) + address.push_back(cell->getPort(ctx->idf("DPRA%d", i))); + address.push_back(ctx->nets[ctx->id("$PACKER_GND_NET")].get()); + CellInfo *dpr = create_dram_lut(cell->name.str(ctx) + "/DP", base, cs, address, di, dpo, z); + if (cell->params.count(id_INIT)) + dpr->params[id_INIT] = cell->params[id_INIT]; + z--; + } + + packed_cells.insert(cell->name); + } + } else if (cs.memtype.in(id_RAM128X1D, id_RAM256X1D)) { + // Split these cells into write and read ports and associated mux tree + bool m256 = cs.memtype == id_RAM256X1D; + for (CellInfo *ci : group.second) { + auto init = get_or_default(ci->params, id_INIT, Property(0, m256 ? 256 : 128)); + std::vector spo_pre, dpo_pre; + int z = (height - 1); + + NetInfo *dpo = ci->getPort(id_DPO); + NetInfo *spo = ci->getPort(id_SPO); + ci->disconnectPort(id_DPO); + ci->disconnectPort(id_SPO); + + // Low 6 bits of address - connect directly to RAM cells + std::vector addressw_64(cs.wa.begin(), cs.wa.begin() + std::min(cs.wa.size(), 6)); + // Upper bits of address - feed decode muxes + std::vector addressw_high(cs.wa.begin() + std::min(cs.wa.size(), 6), cs.wa.end()); + CellInfo *base = nullptr; + // Combined write address/SPO read cells + for (int i = 0; i < (m256 ? 4 : 2); i++) { + NetInfo *spo_i = create_internal_net(ci->name, stringf("SPO_%d", i), false); + CellInfo *spr = create_dram_lut(ci->name.str(ctx) + "/ADDR" + std::to_string(i), base, cs, + addressw_64, ci->getPort(id_D), spo_i, z); + if (base == nullptr) + base = spr; + spo_pre.push_back(spo_i); + spr->params[id_INIT] = init.extract(i * 64, 64); + z--; + } + // Decode mux tree using MUXF[78] + create_muxf_tree(base, "SPO", spo_pre, addressw_high, spo, m256 ? 4 : 2); + + std::vector addressr_64, addressr_high; + for (int i = 0; i < (m256 ? 8 : 7); i++) { + (i >= 6 ? addressr_high : addressr_64) + .push_back(ci->getPort(ctx->id("DPRA[" + std::to_string(i) + "]"))); + } + // Read-only port cells + for (int i = 0; i < (m256 ? 4 : 2); i++) { + NetInfo *dpo_i = create_internal_net(ci->name, stringf("DPO_%d", i), false); + CellInfo *dpr = create_dram_lut(ci->name.str(ctx) + "/DPR" + std::to_string(i), base, cs, + addressr_64, ci->getPort(id_D), dpo_i, z); + dpo_pre.push_back(dpo_i); + dpr->params[id_INIT] = init.extract(i * 64, 64); + z--; + } + // Decode mux tree using MUXF[78] + create_muxf_tree(base, "DPO", dpo_pre, addressr_high, dpo, 0); + + packed_cells.insert(ci->name); + } + } + } + // Whole-SLICE DRAM + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type.in(id_RAM64M, id_RAM32M)) { + bool is_64 = (cell.second->type == id_RAM64M); + int abits = is_64 ? 6 : 5; + int dbits = is_64 ? 1 : 2; + DRAMControlSet dcs; + for (int i = 0; i < abits; i++) + dcs.wa.push_back(ci->getPort(ctx->idf("ADDRD[%d]", i))); + dcs.wclk = ci->getPort(id_WCLK); + dcs.we = ci->getPort(id_WE); + dcs.wclk_inv = bool_or_default(ci->params, id_IS_WCLK_INVERTED); + CellInfo *base = nullptr; + int zoffset = /*ctx->xc7 ? 0 : 4*/ 0; + for (int i = 0; i < 4; i++) { + std::vector address; + for (int j = 0; j < abits; j++) { + address.push_back(ci->getPort(ctx->idf("ADDR%c[%d]", 'A' + i, j))); + } + if (is_64) { + NetInfo *di = ci->getPort(ctx->idf("DI%c", 'A' + i)); + NetInfo *dout = ci->getPort(ctx->idf("DO%c", 'A' + i)); + ci->disconnectPort(ctx->idf("DI%c", 'A' + i)); + ci->disconnectPort(ctx->idf("DO%c", 'A' + i)); + CellInfo *dram = create_dram_lut(stringf("%s/DPR%d", ctx->nameOf(ci), i), base, dcs, address, di, + dout, zoffset + i); + if (base == nullptr) + base = dram; + if (ci->params.count(ctx->idf("INIT%c", 'A' + i))) + dram->params[id_INIT] = ci->params[ctx->idf("INIT%c", 'A' + i)]; + } else { + for (int j = 0; j < dbits; j++) { + NetInfo *di = ci->getPort(ctx->idf("DI%c[%d]", 'A' + i, j)); + NetInfo *dout = ci->getPort(ctx->idf("DO%c[%d]", 'A' + i, j)); + ci->disconnectPort(ctx->idf("DI%c[%d]", 'A' + i, j)); + ci->disconnectPort(ctx->idf("DO%c[%d]", 'A' + i, j)); + CellInfo *dram = create_dram32_lut(stringf("%s/DPR%d_%d", ctx->nameOf(ci), i, j), base, dcs, + address, di, dout, (j == 0), zoffset + i); + if (base == nullptr) + base = dram; + if (ci->params.count(ctx->idf("INIT%c", 'A' + i))) { + auto orig_init = ci->params.at(ctx->idf("INIT%c", 'A' + i)).extract(0, 64).as_bits(); + std::string init; + for (int k = 0; k < 32; k++) { + init.push_back(orig_init.at(k * 2 + j)); + } + dram->params[id_INIT] = Property::from_string(init); + } + } + } + } + packed_cells.insert(ci->name); + } + } + + flush_cells(); +} + +NEXTPNR_NAMESPACE_END \ No newline at end of file diff --git a/himbaechel/uarch/xilinx/pack_io.cc b/himbaechel/uarch/xilinx/pack_io.cc new file mode 100644 index 0000000000..5bebbb9ccd --- /dev/null +++ b/himbaechel/uarch/xilinx/pack_io.cc @@ -0,0 +1,497 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019-23 Myrtle Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include +#include +#include +#include "design_utils.h" +#include "extra_data.h" +#include "log.h" +#include "nextpnr.h" +#include "pack.h" +#include "pins.h" + +#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" +#include "himbaechel_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +CellInfo *XilinxPacker::insert_obuf(IdString name, IdString type, NetInfo *i, NetInfo *o, NetInfo *tri) +{ + auto obuf = create_cell(type, name); + obuf->connectPort(id_I, i); + obuf->connectPort(id_T, tri); + obuf->connectPort(id_O, o); + return obuf; +} + +CellInfo *XilinxPacker::insert_outinv(IdString name, NetInfo *i, NetInfo *o) +{ + auto inv = create_cell(id_INV, name); + inv->connectPort(id_I, i); + inv->connectPort(id_O, o); + return inv; +} + +CellInfo *XC7Packer::insert_ibuf(IdString name, IdString type, NetInfo *i, NetInfo *o) +{ + auto inbuf = create_cell(type, name); + inbuf->connectPort(id_I, i); + inbuf->connectPort(id_O, o); + return inbuf; +} + +CellInfo *XC7Packer::insert_diffibuf(IdString name, IdString type, const std::array &i, NetInfo *o) +{ + auto inbuf = create_cell(type, name); + inbuf->connectPort(id_I, i[0]); + inbuf->connectPort(id_IB, i[1]); + inbuf->connectPort(id_O, o); + return inbuf; +} + +NetInfo *XilinxPacker::invert_net(NetInfo *toinv) +{ + if (toinv == nullptr) + return nullptr; + // If net is driven by an inverter, don't double-invert, which could cause problems with timing + // and IOLOGIC packing + if (toinv->driver.cell != nullptr && toinv->driver.cell->type == id_LUT1 && + int_or_default(toinv->driver.cell->params, id_INIT, 0) == 1) { + NetInfo *preinv = toinv->driver.cell->getPort(id_I0); + // If only one user, also sweep the inversion LUT to avoid packing issues + if (toinv->users.entries() == 1) { + packed_cells.insert(toinv->driver.cell->name); + toinv->driver.cell->disconnectPort(id_I0); + toinv->driver.cell->disconnectPort(id_O); + } + return preinv; + } else { + NetInfo *inv = ctx->createNet(ctx->idf("%s$inverted%d", toinv->name.c_str(ctx), autoidx++)); + create_lut(inv->name.str(ctx) + "$lut", {toinv}, inv, Property(1)); + return inv; + } +} + +std::pair XilinxPacker::insert_pad_and_buf(CellInfo *npnr_io) +{ + // Given a nextpnr IO buffer, create a PAD instance and insert an IO buffer if one isn't already present + std::pair result; + std::unique_ptr pad_cell = std::make_unique(ctx, npnr_io->name, id_PAD); + pad_cell->addInout(id_PAD); + // Copy IO attributes to pad + for (auto &attr : npnr_io->attrs) + pad_cell->attrs[attr.first] = attr.second; + NetInfo *ionet = nullptr; + PortRef iobuf; + iobuf.cell = nullptr; + if (npnr_io->type == ctx->id("$nextpnr_obuf") || npnr_io->type == ctx->id("$nextpnr_iobuf")) { + ionet = npnr_io->getPort(id_I); + if (ionet != nullptr && ionet->driver.cell != nullptr) + if (toplevel_ports.count(ionet->driver.cell->type) && + toplevel_ports.at(ionet->driver.cell->type).count(ionet->driver.port)) { + if (ionet->users.entries() > 1) + log_error("IO buffer '%s' is connected to more than a single top level IO pin.\n", + ionet->driver.cell->name.c_str(ctx)); + iobuf = ionet->driver; + } + pad_cell->attrs[id_X_IO_DIR] = std::string(npnr_io->type == ctx->id("$nextpnr_obuf") ? "OUT" : "INOUT"); + } + if (npnr_io->type == ctx->id("$nextpnr_ibuf") || npnr_io->type == ctx->id("$nextpnr_iobuf")) { + ionet = npnr_io->getPort(id_O); + if (ionet != nullptr) + for (auto &usr : ionet->users) + if (toplevel_ports.count(usr.cell->type) && toplevel_ports.at(usr.cell->type).count(usr.port)) { + if (ionet->users.entries() > 1) + log_error("IO buffer '%s' is connected to more than a single top level IO pin.\n", + usr.cell->name.c_str(ctx)); + iobuf = usr; + } + pad_cell->attrs[id_X_IO_DIR] = std::string(npnr_io->type == ctx->id("$nextpnr_ibuf") ? "IN" : "INOUT"); + } + + if (!iobuf.cell) { + // No IO buffer, need to create one + log_error( + " IO port '%s' is missing an IO buffer, do you need to remove -noiopad from your Yosys arguments?\n", + npnr_io->name.c_str(ctx)); + } else { + log_info(" IO port '%s' driven by %s '%s'\n", npnr_io->name.c_str(ctx), iobuf.cell->type.c_str(ctx), + iobuf.cell->name.c_str(ctx)); + } + + NPNR_ASSERT(ionet != nullptr); + + for (auto &port : npnr_io->ports) + npnr_io->disconnectPort(port.first); + + pad_cell->connectPort(id_PAD, ionet); + if (iobuf.cell->ports.at(iobuf.port).net != ionet) { + iobuf.cell->disconnectPort(iobuf.port); + iobuf.cell->connectPort(iobuf.port, ionet); + } + + result.first = pad_cell.get(); + result.second = iobuf; + // delete the npnr io and then add the pad, to avoid a name conflict + for (auto &port : npnr_io->ports) { + npnr_io->disconnectPort(port.first); + } + ctx->cells.erase(npnr_io->name); + + ctx->cells[result.first->name] = std::move(pad_cell); + + return result; +} + +void XC7Packer::decompose_iob(CellInfo *xil_iob, bool is_hr, const std::string &iostandard) +{ + bool is_se_ibuf = xil_iob->type.in(id_IBUF, id_IBUF_IBUFDISABLE, id_IBUF_INTERMDISABLE); + bool is_se_iobuf = xil_iob->type.in(id_IOBUF, id_IOBUF_DCIEN, id_IOBUF_INTERMDISABLE); + bool is_se_obuf = xil_iob->type.in(id_OBUF, id_OBUFT); + + auto pad_site = [&](NetInfo *n) { + for (auto user : n->users) + if (user.cell->type == id_PAD) + return uarch->get_bel_site(ctx->getBelByNameStr(user.cell->attrs[id_BEL].as_string())); + NPNR_ASSERT_FALSE(("can't find PAD for net " + n->name.str(ctx)).c_str()); + }; + + if (is_se_ibuf || is_se_iobuf) { + log_info("Generating input buffer for '%s'\n", xil_iob->name.c_str(ctx)); + NetInfo *pad_net = xil_iob->getPort(is_se_iobuf ? id_IO : id_I); + NPNR_ASSERT(pad_net != nullptr); + auto site = pad_site(pad_net); + if (!is_se_iobuf) + xil_iob->disconnectPort(id_I); + + NetInfo *top_out = xil_iob->getPort(id_O); + xil_iob->disconnectPort(id_O); + + IdString ibuf_type = id_IBUF; + if (xil_iob->type.in(id_IBUF_IBUFDISABLE, id_IOBUF_DCIEN)) + ibuf_type = id_IBUF_IBUFDISABLE; + if (xil_iob->type.in(id_IBUF_INTERMDISABLE, id_IOBUF_INTERMDISABLE)) + ibuf_type = id_IBUF_INTERMDISABLE; + + CellInfo *inbuf = insert_ibuf(int_name(xil_iob->name, "IBUF", is_se_iobuf), ibuf_type, pad_net, top_out); + std::string tile = ctx->get_tile_type(site.tile).str(ctx); + if (boost::starts_with(tile, "RIOB18")) + ctx->bindBel(uarch->get_site_bel(site, ctx->id("IOB18.INBUF_DCIEN")), inbuf, STRENGTH_LOCKED); + else + ctx->bindBel(uarch->get_site_bel(site, ctx->id("IOB33.INBUF_EN")), inbuf, STRENGTH_LOCKED); + xil_iob->movePortTo(id_IBUFDISABLE, inbuf, id_IBUFDISABLE); + xil_iob->movePortTo(id_INTERMDISABLE, inbuf, id_INTERMDISABLE); + } + + if (is_se_obuf || is_se_iobuf) { + log_info("Generating output buffer for '%s'\n", xil_iob->name.c_str(ctx)); + NetInfo *pad_net = xil_iob->getPort(is_se_iobuf ? id_IO : id_O); + NPNR_ASSERT(pad_net != nullptr); + auto site = pad_site(pad_net); + xil_iob->disconnectPort(is_se_iobuf ? id_IO : id_O); + bool has_dci = xil_iob->type == id_IOBUF_DCIEN; + CellInfo *obuf = insert_obuf( + int_name(xil_iob->name, (is_se_iobuf || xil_iob->type == id_OBUFT) ? "OBUFT" : "OBUF", !is_se_obuf), + is_se_iobuf ? (has_dci ? id_OBUFT_DCIEN : id_OBUFT) : xil_iob->type, xil_iob->getPort(id_I), pad_net, + xil_iob->getPort(id_T)); + std::string tile = ctx->get_tile_type(site.tile).str(ctx); + if (boost::starts_with(tile, "RIOB18")) + ctx->bindBel(uarch->get_site_bel(site, ctx->id("IOB18.OUTBUF_DCIEN")), obuf, STRENGTH_LOCKED); + else + ctx->bindBel(uarch->get_site_bel(site, ctx->id("IOB33.OUTBUF")), obuf, STRENGTH_LOCKED); + xil_iob->movePortTo(id_DCITERMDISABLE, obuf, id_DCITERMDISABLE); + } + + bool is_diff_ibuf = xil_iob->type.in(id_IBUFDS, id_IBUFDS_INTERMDISABLE, id_IBUFDS); + bool is_diff_iobuf = xil_iob->type.in(id_IOBUFDS, id_IOBUFDS_DCIEN); + bool is_diff_out_iobuf = + xil_iob->type.in(id_IOBUFDS_DIFF_OUT, id_IOBUFDS_DIFF_OUT_DCIEN, id_IOBUFDS_DIFF_OUT_INTERMDISABLE); + bool is_diff_obuf = xil_iob->type.in(id_OBUFDS, id_OBUFTDS); + + if (is_diff_ibuf || is_diff_iobuf) { + NetInfo *pad_p_net = xil_iob->getPort((is_diff_iobuf || is_diff_out_iobuf) ? id_IO : id_I); + NPNR_ASSERT(pad_p_net != nullptr); + auto site_p = pad_site(pad_p_net); + NetInfo *pad_n_net = xil_iob->getPort((is_diff_iobuf || is_diff_out_iobuf) ? id_IOB : id_IB); + NPNR_ASSERT(pad_n_net != nullptr); + std::string tile_p = ctx->get_tile_type(site_p.tile).str(ctx); + bool is_riob18 = boost::starts_with(tile_p, "RIOB18"); + + if (!is_diff_iobuf && !is_diff_out_iobuf) { + xil_iob->disconnectPort(id_I); + xil_iob->disconnectPort(id_IB); + } + + NetInfo *top_out = xil_iob->getPort(id_O); + xil_iob->disconnectPort(id_O); + + IdString ibuf_type = id_IBUFDS; + CellInfo *inbuf = insert_diffibuf(int_name(xil_iob->name, "IBUF", is_se_iobuf), ibuf_type, + {pad_p_net, pad_n_net}, top_out); + if (is_riob18) { + ctx->bindBel(uarch->get_site_bel(site_p, ctx->id("IOB18M.INBUF_DCIEN")), inbuf, STRENGTH_LOCKED); + inbuf->attrs[id_X_IOB_SITE_TYPE] = std::string("IOB18M"); + } else { + ctx->bindBel(uarch->get_site_bel(site_p, ctx->id("IOB33M.INBUF_EN")), inbuf, STRENGTH_LOCKED); + inbuf->attrs[id_X_IOB_SITE_TYPE] = std::string("IOB33M"); + } + } + + if (is_diff_obuf || is_diff_out_iobuf || is_diff_iobuf) { + // FIXME: true diff outputs + NetInfo *pad_p_net = xil_iob->getPort((is_diff_iobuf || is_diff_out_iobuf) ? id_IO : id_O); + NPNR_ASSERT(pad_p_net != nullptr); + auto site_p = pad_site(pad_p_net); + NetInfo *pad_n_net = xil_iob->getPort((is_diff_iobuf || is_diff_out_iobuf) ? id_IOB : id_OB); + NPNR_ASSERT(pad_n_net != nullptr); + auto site_n = pad_site(pad_n_net); + std::string tile_p = ctx->get_tile_type(site_p.tile).str(ctx); + bool is_riob18 = boost::starts_with(tile_p, "RIOB18"); + + xil_iob->disconnectPort((is_diff_iobuf || is_diff_out_iobuf) ? id_IO : id_O); + xil_iob->disconnectPort((is_diff_iobuf || is_diff_out_iobuf) ? id_IOB : id_OB); + + NetInfo *inv_i = create_internal_net(xil_iob->name, is_diff_obuf ? "I_B" : "OBUFTDS$subnet$I_B"); + CellInfo *inv = insert_outinv(int_name(xil_iob->name, is_diff_obuf ? "INV" : "OBUFTDS$subcell$INV"), + xil_iob->getPort(id_I), inv_i); + if (is_riob18) { + ctx->bindBel(uarch->get_site_bel(site_n, ctx->id("IOB18S.O_ININV")), inv, STRENGTH_LOCKED); + inv->attrs[id_X_IOB_SITE_TYPE] = std::string("IOB18S"); + } else { + ctx->bindBel(uarch->get_site_bel(site_n, ctx->id("IOB33S.O_ININV")), inv, STRENGTH_LOCKED); + inv->attrs[id_X_IOB_SITE_TYPE] = std::string("IOB33S"); + } + + bool has_dci = xil_iob->type.in(id_IOBUFDS_DCIEN, id_IOBUFDSE3); + + CellInfo *obuf_p = insert_obuf(int_name(xil_iob->name, is_diff_obuf ? "P" : "OBUFTDS$subcell$P"), + (is_diff_iobuf || is_diff_out_iobuf || (xil_iob->type == id_OBUFTDS)) + ? (has_dci ? id_OBUFT_DCIEN : id_OBUFT) + : id_OBUF, + xil_iob->getPort(id_I), pad_p_net, xil_iob->getPort(id_T)); + + if (is_riob18) { + ctx->bindBel(uarch->get_site_bel(site_p, ctx->id("IOB18M.OUTBUF_DCIEN")), obuf_p, STRENGTH_LOCKED); + obuf_p->attrs[id_X_IOB_SITE_TYPE] = std::string("IOB18M"); + } else { + ctx->bindBel(uarch->get_site_bel(site_p, ctx->id("IOB33M.OUTBUF")), obuf_p, STRENGTH_LOCKED); + obuf_p->attrs[id_X_IOB_SITE_TYPE] = std::string("IOB33M"); + } + obuf_p->connectPort(id_DCITERMDISABLE, xil_iob->getPort(id_DCITERMDISABLE)); + + CellInfo *obuf_n = insert_obuf(int_name(xil_iob->name, is_diff_obuf ? "N" : "OBUFTDS$subcell$N"), + (is_diff_iobuf || is_diff_out_iobuf || (xil_iob->type == id_OBUFTDS)) + ? (has_dci ? id_OBUFT_DCIEN : id_OBUFT) + : id_OBUF, + inv_i, pad_n_net, xil_iob->getPort(id_T)); + + if (is_riob18) { + ctx->bindBel(uarch->get_site_bel(site_n, ctx->id("IOB18S.OUTBUF_DCIEN")), obuf_n, STRENGTH_LOCKED); + obuf_n->attrs[id_X_IOB_SITE_TYPE] = std::string("IOB18S"); + } else { + ctx->bindBel(uarch->get_site_bel(site_n, ctx->id("IOB33S.OUTBUF")), obuf_n, STRENGTH_LOCKED); + obuf_n->attrs[id_X_IOB_SITE_TYPE] = std::string("IOB33S"); + } + obuf_n->connectPort(id_DCITERMDISABLE, xil_iob->getPort(id_DCITERMDISABLE)); + + xil_iob->disconnectPort(id_DCITERMDISABLE); + } +} + +void XC7Packer::pack_io() +{ + log_info("Inserting IO buffers..\n"); + + get_top_level_pins(ctx, toplevel_ports); + // Insert PAD cells on top level IO, and IO buffers where one doesn't exist already + std::vector npnr_io; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf") || + ci->type == ctx->id("$nextpnr_obuf")) + npnr_io.push_back(ci); + } + std::vector> pad_and_buf; + for (auto ci : npnr_io) { + pad_and_buf.push_back(insert_pad_and_buf(ci)); + } + flush_cells(); + pool used_io_bels; + int unconstr_io_count = 0; + for (auto &iob : pad_and_buf) { + CellInfo *pad = iob.first; + // Process location constraints + if (pad->attrs.count(id_PACKAGE_PIN)) { + pad->attrs[id_LOC] = pad->attrs.at(id_PACKAGE_PIN); + } + if (pad->attrs.count(id_LOC)) { + std::string loc = pad->attrs.at(id_LOC).to_string(); + BelId bel = ctx->get_package_pin_bel(ctx->id(loc)); + if (bel == BelId()) + log_error("Unable to constrain IO '%s', device does not have a pin named '%s'\n", pad->name.c_str(ctx), + loc.c_str()); + log_info(" Constraining '%s' to pad '%s'\n", pad->name.c_str(ctx), ctx->nameOfBel(bel)); + pad->attrs[id_BEL] = std::string(ctx->nameOfBel(bel)); + } + if (pad->attrs.count(id_BEL)) { + used_io_bels.insert(ctx->getBelByNameStr(pad->attrs.at(id_BEL).as_string())); + } else { + ++unconstr_io_count; + } + } + // Constrain unconstrained IO + for (auto &iob : pad_and_buf) { + CellInfo *pad = iob.first; + if (!pad->attrs.count(id_BEL)) { + log_error("FIXME: unconstrained IO not supported (pad %s)\n", ctx->nameOf(pad)); + ; + } + } + // Decompose macro IO primitives to smaller primitives that map logically to the actual IO Bels + for (auto &iob : pad_and_buf) { + if (packed_cells.count(iob.second.cell->name)) + continue; + decompose_iob(iob.second.cell, true, str_or_default(iob.first->attrs, id_IOSTANDARD, "")); + packed_cells.insert(iob.second.cell->name); + } + flush_cells(); + + dict hriobuf_rules, hpiobuf_rules; + hriobuf_rules[id_OBUF].new_type = id_IOB33_OUTBUF; + hriobuf_rules[id_OBUF].port_xform[id_I] = id_IN; + hriobuf_rules[id_OBUF].port_xform[id_O] = id_OUT; + hriobuf_rules[id_OBUF].port_xform[id_T] = id_TRI; + hriobuf_rules[id_OBUFT] = XFormRule(hriobuf_rules[id_OBUF]); + + hriobuf_rules[id_IBUF].new_type = id_IOB33_INBUF_EN; + hriobuf_rules[id_IBUF].port_xform[id_I] = id_PAD; + hriobuf_rules[id_IBUF].port_xform[id_O] = id_OUT; + hriobuf_rules[id_IBUF_INTERMDISABLE] = XFormRule(hriobuf_rules[id_IBUF]); + hriobuf_rules[id_IBUF_IBUFDISABLE] = XFormRule(hriobuf_rules[id_IBUF]); + hriobuf_rules[id_IBUFDS_INTERMDISABLE_INT] = XFormRule(hriobuf_rules[id_IBUF]); + hriobuf_rules[id_IBUFDS_INTERMDISABLE_INT].port_xform[id_IB] = id_DIFFI_IN; + hriobuf_rules[id_IBUFDS] = XFormRule(hriobuf_rules[id_IBUF]); + hriobuf_rules[id_IBUFDS].port_xform[id_IB] = id_DIFFI_IN; + + hpiobuf_rules[id_OBUF].new_type = id_IOB18_OUTBUF_DCIEN; + hpiobuf_rules[id_OBUF].port_xform[id_I] = id_IN; + hpiobuf_rules[id_OBUF].port_xform[id_O] = id_OUT; + hpiobuf_rules[id_OBUF].port_xform[id_T] = id_TRI; + hpiobuf_rules[id_OBUFT] = XFormRule(hpiobuf_rules[id_OBUF]); + + hpiobuf_rules[id_IBUF].new_type = id_IOB18_INBUF_DCIEN; + hpiobuf_rules[id_IBUF].port_xform[id_I] = id_PAD; + hpiobuf_rules[id_IBUF].port_xform[id_O] = id_OUT; + hpiobuf_rules[id_IBUF_INTERMDISABLE] = XFormRule(hpiobuf_rules[id_IBUF]); + hpiobuf_rules[id_IBUF_IBUFDISABLE] = XFormRule(hpiobuf_rules[id_IBUF]); + hriobuf_rules[id_IBUFDS_INTERMDISABLE_INT] = XFormRule(hriobuf_rules[id_IBUF]); + hpiobuf_rules[id_IBUFDS_INTERMDISABLE_INT].port_xform[id_IB] = id_DIFFI_IN; + hpiobuf_rules[id_IBUFDS] = XFormRule(hpiobuf_rules[id_IBUF]); + hpiobuf_rules[id_IBUFDS].port_xform[id_IB] = id_DIFFI_IN; + + // Special xform for OBUFx and IBUFx. + dict rules; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (!ci->attrs.count(id_BEL) && ci->bel == BelId()) + continue; + std::string belname = + ci->attrs.count(id_BEL) ? ci->attrs[id_BEL].as_string() : std::string(ctx->nameOfBel(ci->bel)); + size_t pos = belname.find("."); + if (belname.substr(pos + 1, 5) == "IOB18") + rules = hpiobuf_rules; + else if (belname.substr(pos + 1, 5) == "IOB33") + rules = hriobuf_rules; + else + log_error("Unexpected IOBUF BEL %s\n", belname.c_str()); + if (rules.count(ci->type)) { + xform_cell(rules, ci); + } + } + + dict hrio_rules; + hrio_rules[id_PAD].new_type = id_PAD; + + hrio_rules[id_INV].new_type = id_INVERTER; + hrio_rules[id_INV].port_xform[id_I] = id_IN; + hrio_rules[id_INV].port_xform[id_O] = id_OUT; + + hrio_rules[id_PS7].new_type = id_PS7_PS7; + + generic_xform(hrio_rules, true); + + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + std::string type = ci->type.str(ctx); + if (!boost::starts_with(type, "IOB33") && !boost::starts_with(type, "IOB18")) + continue; + if (!ci->attrs.count(id_X_IOB_SITE_TYPE)) + continue; + type.replace(0, 5, ci->attrs.at(id_X_IOB_SITE_TYPE).as_string()); + ci->type = ctx->id(type); + } + + // check all PAD cells for IOSTANDARD/DRIVE + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + std::string type = ci->type.str(ctx); + if (type != "PAD") + continue; + check_valid_pad(ci, type); + } +} + +void XC7Packer::check_valid_pad(CellInfo *ci, std::string type) +{ + auto iostandard_id = id_IOSTANDARD; + auto iostandard_attr = ci->attrs.find(iostandard_id); + if (iostandard_attr == ci->attrs.end()) + log_error("port %s has no IOSTANDARD property", ci->name.c_str(ctx)); + + auto iostandard = iostandard_attr->second.as_string(); + if (!boost::starts_with(iostandard, "LVTTL") && !boost::starts_with(iostandard, "LVCMOS")) + return; + + auto drive_attr = ci->attrs.find(id_DRIVE); + // no drive strength attribute: use default + if (drive_attr == ci->attrs.end()) + return; + auto drive = drive_attr->second.as_int64(); + + bool is_iob33 = boost::starts_with(type, "IOB33"); + if (is_iob33) { + if (drive == 4 || drive == 8 || drive == 12) + return; + if (iostandard != "LVCMOS12" && drive == 16) + return; + if ((iostandard == "LVCMOS18" || iostandard == "LVTTL") && drive == 24) + return; + } else { // IOB18 + if (drive == 2 || drive == 4 || drive == 6 || drive == 8) + return; + if (iostandard != "LVCMOS12" && (drive == 12 || drive == 16)) + return; + } + + log_error("unsupported DRIVE strength property %s for port %s", drive_attr->second.c_str(), ci->name.c_str(ctx)); +} + +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/xilinx/pins.cc b/himbaechel/uarch/xilinx/pins.cc new file mode 100644 index 0000000000..14c70b03c7 --- /dev/null +++ b/himbaechel/uarch/xilinx/pins.cc @@ -0,0 +1,480 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2023 Myrtle Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include + +#include "nextpnr.h" + +#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" +#include "himbaechel_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +void get_invertible_pins(Context *ctx, dict> &invertible_pins) +{ + // List of pins that have an IS_x_INVERTED attributed, so we can optimise tie-zero to tie-one for these pins + // See scripts/invertible_pins.py + + // Common and xcup + invertible_pins[id_BUFGCTRL].insert(id_CE0); + invertible_pins[id_BUFGCTRL].insert(id_CE1); + invertible_pins[id_BUFGCTRL].insert(id_S0); + invertible_pins[id_BUFGCTRL].insert(id_S1); + invertible_pins[id_BUFGCTRL].insert(id_IGNORE0); + invertible_pins[id_BUFGCTRL].insert(id_IGNORE1); + invertible_pins[id_BUFHCE].insert(id_CE); + invertible_pins[id_FDRE].insert(id_C); + invertible_pins[id_FDSE].insert(id_C); + invertible_pins[id_FDCE].insert(id_C); + invertible_pins[id_FDPE].insert(id_C); + + invertible_pins[id_SRL16E].insert(id_CLK); + invertible_pins[id_SRLC32E].insert(id_CLK); + invertible_pins[id_BUFGCE].insert(id_CE); + invertible_pins[id_BUFGCE].insert(id_I); + invertible_pins[id_BUFGCE_DIV].insert(id_CE); + invertible_pins[id_BUFGCE_DIV].insert(id_CLR); + invertible_pins[id_BUFGCE_DIV].insert(id_I); + invertible_pins[id_CFGLUT5].insert(id_CLK); + invertible_pins[id_FIFO18E2].insert(id_RDCLK); + invertible_pins[id_FIFO18E2].insert(id_RDEN); + invertible_pins[id_FIFO18E2].insert(id_RSTREG); + invertible_pins[id_FIFO18E2].insert(id_RST); + invertible_pins[id_FIFO18E2].insert(id_WRCLK); + invertible_pins[id_FIFO18E2].insert(id_WREN); + invertible_pins[id_FIFO36E2].insert(id_RDCLK); + invertible_pins[id_FIFO36E2].insert(id_RDEN); + invertible_pins[id_FIFO36E2].insert(id_RSTREG); + invertible_pins[id_FIFO36E2].insert(id_RST); + invertible_pins[id_FIFO36E2].insert(id_WRCLK); + invertible_pins[id_FIFO36E2].insert(id_WREN); + invertible_pins[id_HARD_SYNC].insert(id_CLK); + invertible_pins[id_IDDRE1].insert(id_CB); + invertible_pins[id_IDDRE1].insert(id_C); + + invertible_pins[id_LDCE].insert(id_CLR); + invertible_pins[id_LDCE].insert(id_G); + invertible_pins[id_LDPE].insert(id_G); + invertible_pins[id_LDPE].insert(id_PRE); + invertible_pins[id_ODDRE1].insert(id_C); + // invertible_pins[id_ODDRE1].insert(id_D1); + // invertible_pins[id_ODDRE1].insert(id_D2); + + invertible_pins[id_OR2L].insert(id_SRI); + + // invertible_pins[id_OSERDESE3].insert(id_RST); + invertible_pins[id_RAM128X1D].insert(id_WCLK); + invertible_pins[id_RAM128X1S].insert(id_WCLK); + invertible_pins[id_RAM256X1D].insert(id_WCLK); + invertible_pins[id_RAM256X1S].insert(id_WCLK); + invertible_pins[id_RAM32M].insert(id_WCLK); + invertible_pins[id_RAM32M16].insert(id_WCLK); + invertible_pins[id_RAM32X1D].insert(id_WCLK); + invertible_pins[id_RAM32X1S].insert(id_WCLK); + invertible_pins[id_RAM32X2S].insert(id_WCLK); + invertible_pins[id_RAM512X1S].insert(id_WCLK); + invertible_pins[id_RAM64M].insert(id_WCLK); + invertible_pins[id_RAM64M8].insert(id_WCLK); + invertible_pins[id_RAM64X1D].insert(id_WCLK); + invertible_pins[id_RAM64X1S].insert(id_WCLK); + invertible_pins[id_RAM64X8SW].insert(id_WCLK); + + invertible_pins[id_SYSMONE1].insert(id_CONVSTCLK); + invertible_pins[id_SYSMONE1].insert(id_DCLK); + + // xc7 + invertible_pins[id_RAMB18E1].insert(id_CLKARDCLK); + invertible_pins[id_RAMB18E1].insert(id_CLKBWRCLK); + invertible_pins[id_RAMB18E1].insert(id_ENARDEN); + invertible_pins[id_RAMB18E1].insert(id_ENBWREN); + invertible_pins[id_RAMB18E1].insert(id_RSTRAMARSTRAM); + invertible_pins[id_RAMB18E1].insert(id_RSTRAMB); + invertible_pins[id_RAMB18E1].insert(id_RSTREGARSTREG); + invertible_pins[id_RAMB18E1].insert(id_RSTREGB); + invertible_pins[id_RAMB36E1].insert(id_CLKARDCLK); + invertible_pins[id_RAMB36E1].insert(id_CLKBWRCLK); + invertible_pins[id_RAMB36E1].insert(id_ENARDEN); + invertible_pins[id_RAMB36E1].insert(id_ENBWREN); + invertible_pins[id_RAMB36E1].insert(id_RSTRAMARSTRAM); + invertible_pins[id_RAMB36E1].insert(id_RSTRAMB); + invertible_pins[id_RAMB36E1].insert(id_RSTREGARSTREG); + invertible_pins[id_RAMB36E1].insert(id_RSTREGB); + invertible_pins[id_BUFMRCE].insert(id_CE); + for (int i = 0; i < 4; i++) + invertible_pins[id_DSP48E1].insert(ctx->idf("ALUMODE[%d]", i)); + invertible_pins[id_DSP48E1].insert(id_CARRYIN); + for (int i = 0; i < 5; i++) + invertible_pins[id_DSP48E1].insert(ctx->idf("INMODE[%d]", i)); + for (int i = 0; i < 7; i++) + invertible_pins[id_DSP48E1].insert(ctx->idf("OPMODE[%d]", i)); + invertible_pins[id_FIFO18E1].insert(id_RDCLK); + invertible_pins[id_FIFO18E1].insert(id_RDEN); + invertible_pins[id_FIFO18E1].insert(id_RSTREG); + invertible_pins[id_FIFO18E1].insert(id_RST); + invertible_pins[id_FIFO18E1].insert(id_WRCLK); + invertible_pins[id_FIFO18E1].insert(id_WREN); + invertible_pins[id_FIFO36E1].insert(id_RDCLK); + invertible_pins[id_FIFO36E1].insert(id_RDEN); + invertible_pins[id_FIFO36E1].insert(id_RSTREG); + invertible_pins[id_FIFO36E1].insert(id_RST); + invertible_pins[id_FIFO36E1].insert(id_WRCLK); + invertible_pins[id_FIFO36E1].insert(id_WREN); + invertible_pins[id_GTHE2_CHANNEL].insert(id_CLKRSVD0); + invertible_pins[id_GTHE2_CHANNEL].insert(id_CLKRSVD1); + invertible_pins[id_GTHE2_CHANNEL].insert(id_CPLLLOCKDETCLK); + invertible_pins[id_GTHE2_CHANNEL].insert(id_DMONITORCLK); + invertible_pins[id_GTHE2_CHANNEL].insert(id_DRPCLK); + invertible_pins[id_GTHE2_CHANNEL].insert(id_GTGREFCLK); + invertible_pins[id_GTHE2_CHANNEL].insert(id_RXUSRCLK2); + invertible_pins[id_GTHE2_CHANNEL].insert(id_RXUSRCLK); + invertible_pins[id_GTHE2_CHANNEL].insert(id_SIGVALIDCLK); + invertible_pins[id_GTHE2_CHANNEL].insert(id_TXPHDLYTSTCLK); + invertible_pins[id_GTHE2_CHANNEL].insert(id_TXUSRCLK2); + invertible_pins[id_GTHE2_CHANNEL].insert(id_TXUSRCLK); + invertible_pins[id_GTHE2_COMMON].insert(id_DRPCLK); + invertible_pins[id_GTHE2_COMMON].insert(id_GTGREFCLK); + invertible_pins[id_GTHE2_COMMON].insert(id_QPLLLOCKDETCLK); + invertible_pins[id_GTPE2_CHANNEL].insert(id_CLKRSVD0); + invertible_pins[id_GTPE2_CHANNEL].insert(id_CLKRSVD1); + invertible_pins[id_GTPE2_CHANNEL].insert(id_DMONITORCLK); + invertible_pins[id_GTPE2_CHANNEL].insert(id_DRPCLK); + invertible_pins[id_GTPE2_CHANNEL].insert(id_RXUSRCLK2); + invertible_pins[id_GTPE2_CHANNEL].insert(id_RXUSRCLK); + invertible_pins[id_GTPE2_CHANNEL].insert(id_SIGVALIDCLK); + invertible_pins[id_GTPE2_CHANNEL].insert(id_TXPHDLYTSTCLK); + invertible_pins[id_GTPE2_CHANNEL].insert(id_TXUSRCLK2); + invertible_pins[id_GTPE2_CHANNEL].insert(id_TXUSRCLK); + invertible_pins[id_GTPE2_COMMON].insert(id_DRPCLK); + invertible_pins[id_GTPE2_COMMON].insert(id_GTGREFCLK0); + invertible_pins[id_GTPE2_COMMON].insert(id_GTGREFCLK1); + invertible_pins[id_GTPE2_COMMON].insert(id_PLL0LOCKDETCLK); + invertible_pins[id_GTPE2_COMMON].insert(id_PLL1LOCKDETCLK); + invertible_pins[id_GTXE2_CHANNEL].insert(id_CPLLLOCKDETCLK); + invertible_pins[id_GTXE2_CHANNEL].insert(id_DRPCLK); + invertible_pins[id_GTXE2_CHANNEL].insert(id_GTGREFCLK); + invertible_pins[id_GTXE2_CHANNEL].insert(id_RXUSRCLK2); + invertible_pins[id_GTXE2_CHANNEL].insert(id_RXUSRCLK); + invertible_pins[id_GTXE2_CHANNEL].insert(id_TXPHDLYTSTCLK); + invertible_pins[id_GTXE2_CHANNEL].insert(id_TXUSRCLK2); + invertible_pins[id_GTXE2_CHANNEL].insert(id_TXUSRCLK); + invertible_pins[id_GTXE2_COMMON].insert(id_DRPCLK); + invertible_pins[id_GTXE2_COMMON].insert(id_GTGREFCLK); + invertible_pins[id_GTXE2_COMMON].insert(id_QPLLLOCKDETCLK); + invertible_pins[id_IDDR].insert(id_C); + // invertible_pins[id_IDDR].insert(id_D); + invertible_pins[id_IDDR_2CLK].insert(id_CB); + invertible_pins[id_IDDR_2CLK].insert(id_C); + // invertible_pins[id_IDDR_2CLK].insert(id_D); + invertible_pins[id_IDELAYE2].insert(id_C); + invertible_pins[id_IDELAYE2].insert(id_IDATAIN); + invertible_pins[id_ODELAYE2].insert(id_C); + invertible_pins[id_ODELAYE2].insert(id_ODATAIN); + invertible_pins[id_ISERDESE2].insert(id_CLKB); + invertible_pins[id_ISERDESE2].insert(id_CLKDIVP); + invertible_pins[id_ISERDESE2].insert(id_CLKDIV); + invertible_pins[id_ISERDESE2].insert(id_CLK); + // invertible_pins[id_ISERDESE2].insert(id_D); + invertible_pins[id_ISERDESE2].insert(id_OCLKB); + invertible_pins[id_ISERDESE2].insert(id_OCLK); + // invertible_pins[id_LDCE].insert(id_CLR); + invertible_pins[id_LDCE].insert(id_G); + invertible_pins[id_LDPE].insert(id_G); + // invertible_pins[id_LDPE].insert(id_PRE); + invertible_pins[id_MMCME2_ADV].insert(id_CLKINSEL); + invertible_pins[id_MMCME2_ADV].insert(id_PSEN); + invertible_pins[id_MMCME2_ADV].insert(id_PSINCDEC); + invertible_pins[id_MMCME2_ADV].insert(id_PWRDWN); + invertible_pins[id_MMCME2_ADV].insert(id_RST); + invertible_pins[id_IDDR].insert(id_CK); + invertible_pins[id_ODDR].insert(id_CK); + invertible_pins[id_ODDR].insert(id_D1); + invertible_pins[id_ODDR].insert(id_D2); + invertible_pins[id_ODELAYE2].insert(id_C); + // invertible_pins[id_ODELAYE2].insert(id_ODATAIN); + invertible_pins[id_OSERDESE2].insert(id_CLKDIV); + invertible_pins[id_OSERDESE2].insert(id_CLK); + // invertible_pins[id_OSERDESE2].insert(id_D1); + // invertible_pins[id_OSERDESE2].insert(id_D2); + // invertible_pins[id_OSERDESE2].insert(id_D3); + // invertible_pins[id_OSERDESE2].insert(id_D4); + // invertible_pins[id_OSERDESE2].insert(id_D5); + // invertible_pins[id_OSERDESE2].insert(id_D6); + // invertible_pins[id_OSERDESE2].insert(id_D7); + // invertible_pins[id_OSERDESE2].insert(id_D8); + invertible_pins[id_OSERDESE2].insert(id_T1); + invertible_pins[id_OSERDESE2].insert(id_T2); + invertible_pins[id_OSERDESE2].insert(id_T3); + invertible_pins[id_OSERDESE2].insert(id_T4); + invertible_pins[id_PHASER_IN].insert(id_RST); + invertible_pins[id_PHASER_IN_PHY].insert(id_RST); + invertible_pins[id_PHASER_OUT].insert(id_RST); + invertible_pins[id_PHASER_OUT_PHY].insert(id_RST); + invertible_pins[id_PHASER_REF].insert(id_RST); + invertible_pins[id_PHASER_REF].insert(id_PWRDWN); + invertible_pins[id_PLLE2_ADV].insert(id_CLKINSEL); + invertible_pins[id_PLLE2_ADV].insert(id_PWRDWN); + invertible_pins[id_PLLE2_ADV].insert(id_RST); + invertible_pins[id_XADC].insert(id_CONVSTCLK); + invertible_pins[id_XADC].insert(id_DCLK); +} + +void get_tied_pins(Context *ctx, dict> &tied_pins) +{ + // List of pins that are tied to a fixed value when unused. + // This doesn't include the PS8, due to the large number of tied-zero pins that are implied by the + // list of Bel pins and dealt with as a special case in arch_place.cc + + for (IdString ram : {id_RAMB18E2, id_RAMB36E2}) { + // based on UG573 p37 + for (char port : {'A', 'B'}) { + tied_pins[ram][ctx->id(std::string("ADDREN") + port)] = true; + tied_pins[ram][ctx->id(std::string("CASDIMUX") + port)] = false; + tied_pins[ram][ctx->id(std::string("CASDOMUX") + port)] = false; + if (ram == id_RAMB18E2) { + tied_pins[ram][ctx->id(std::string("CASDOMUXEN_") + port)] = true; + tied_pins[ram][ctx->id(std::string("CASOREGIMUXEN_") + port)] = true; + } + + tied_pins[ram][ctx->id(std::string("CASOREGIMUX") + port)] = false; + } + + int wea_width = (ram == id_RAMB18E2 ? 2 : 4); + int web_width = 4; + + for (int i = 0; i < wea_width; i++) + tied_pins[ram][ctx->id(std::string("WEA[") + std::to_string(i) + "]")] = true; + for (int i = 0; i < web_width; i++) + tied_pins[ram][ctx->id(std::string("WEBWE[") + std::to_string(i) + "]")] = true; + + tied_pins[ram][id_CLKARDCLK] = false; + tied_pins[ram][id_CLKBWRCLK] = false; + tied_pins[ram][id_ENARDEN] = false; + tied_pins[ram][id_ENBWREN] = false; + tied_pins[ram][id_REGCEAREGCE] = true; + tied_pins[ram][id_REGCEB] = true; + + tied_pins[ram][id_RSTRAMARSTRAM] = false; + tied_pins[ram][id_RSTRAMB] = false; + tied_pins[ram][id_RSTREGARSTREG] = false; + tied_pins[ram][id_RSTREGB] = false; + tied_pins[ram][id_SLEEP] = false; + + if (ram == id_RAMB36E2) { + tied_pins[ram][id_INJECTSBITERR] = false; + tied_pins[ram][id_INJECTDBITERR] = false; + tied_pins[ram][id_ECCPIPECE] = true; + } + } + + for (IdString ram : {id_RAMB18E1, id_RAMB36E1}) { + // based on UG573 p37 + + int wea_width = (ram == id_RAMB18E1 ? 2 : 4); + int web_width = 4; + + for (int i = 0; i < wea_width; i++) + tied_pins[ram][ctx->id(std::string("WEA[") + std::to_string(i) + "]")] = true; + for (int i = 0; i < web_width; i++) + tied_pins[ram][ctx->id(std::string("WEBWE[") + std::to_string(i) + "]")] = true; + + tied_pins[ram][id_CLKARDCLK] = false; + tied_pins[ram][id_CLKBWRCLK] = false; + tied_pins[ram][id_ENARDEN] = false; + tied_pins[ram][id_ENBWREN] = false; + tied_pins[ram][id_REGCEAREGCE] = true; + tied_pins[ram][id_REGCEB] = true; + + tied_pins[ram][id_RSTRAMARSTRAM] = false; + tied_pins[ram][id_RSTRAMB] = false; + tied_pins[ram][id_RSTREGARSTREG] = false; + tied_pins[ram][id_RSTREGB] = false; + } + + // BUFGCTRL (by experiment) + for (int i = 0; i < 2; i++) { + tied_pins[id_BUFGCTRL][ctx->id("S" + std::to_string(i))] = false; + tied_pins[id_BUFGCTRL][ctx->id("IGNORE" + std::to_string(i))] = false; + tied_pins[id_BUFGCTRL][ctx->id("CE" + std::to_string(i))] = false; + } + + // IO logic primitives + tied_pins[id_IDDRE1][id_R] = false; + tied_pins[id_ODDRE1][id_SR] = false; + + tied_pins[id_OSERDESE2][id_RST] = false; + for (int i = 1; i <= 8; i++) + tied_pins[id_OSERDESE2][ctx->id("D" + std::to_string(i))] = false; + for (int i = 1; i <= 4; i++) + tied_pins[id_OSERDESE2][ctx->id("T" + std::to_string(i))] = false; + tied_pins[id_OSERDESE2][id_OCE] = true; + tied_pins[id_OSERDESE2][id_TCE] = true; + + tied_pins[id_IDELAYE2][id_REGRST] = false; + tied_pins[id_IDELAYE2][id_LDPIPEEN] = false; + tied_pins[id_IDELAYE2][id_CINVCTRL] = false; + + // IO primitives + tied_pins[id_IOBUFDSE3][id_DCITERMDISABLE] = false; + tied_pins[id_IOBUFDSE3][ctx->id("OSC_EN[0]")] = false; + tied_pins[id_IOBUFDSE3][ctx->id("OSC_EN[1]")] = false; + for (int i = 0; i < 4; i++) + tied_pins[id_IOBUFDSE3][ctx->id("OSC[" + std::to_string(i) + "]")] = false; + + // PLL + tied_pins[id_PLLE2_ADV][id_CLKFBIN] = false; + tied_pins[id_PLLE2_ADV][id_CLKIN1] = false; + tied_pins[id_PLLE2_ADV][id_CLKIN2] = false; + tied_pins[id_PLLE2_ADV][id_CLKINSEL] = true; + for (int i = 0; i < 7; i++) + tied_pins[id_PLLE2_ADV][ctx->id("DADDR[" + std::to_string(i) + "]")] = false; + tied_pins[id_PLLE2_ADV][id_DCLK] = false; + tied_pins[id_PLLE2_ADV][id_DEN] = false; + for (int i = 0; i < 16; i++) + tied_pins[id_PLLE2_ADV][ctx->id("DI[" + std::to_string(i) + "]")] = false; + tied_pins[id_PLLE2_ADV][id_DWE] = false; + tied_pins[id_PLLE2_ADV][id_PWRDWN] = false; + tied_pins[id_PLLE2_ADV][id_RST] = false; + + // Misc clock buffers + tied_pins[id_BUFGCE_DIV][id_CE] = true; + tied_pins[id_BUFGCE_DIV][id_CLR] = false; + tied_pins[id_BUFGCE][id_CE] = true; + + tied_pins[id_DSP48E1][id_CLK] = false; + tied_pins[id_DSP48E1][id_RSTA] = false; + tied_pins[id_DSP48E1][id_RSTALLCARRYIN] = false; + tied_pins[id_DSP48E1][id_RSTALUMODE] = false; + tied_pins[id_DSP48E1][id_RSTB] = false; + tied_pins[id_DSP48E1][id_RSTC] = false; + tied_pins[id_DSP48E1][id_RSTCTRL] = false; + tied_pins[id_DSP48E1][id_RSTD] = false; + tied_pins[id_DSP48E1][id_RSTINMODE] = false; + tied_pins[id_DSP48E1][id_RSTM] = false; + tied_pins[id_DSP48E1][id_RSTP] = false; + + tied_pins[id_DSP48E1][id_CARRYIN] = false; + + tied_pins[id_DSP48E1][id_CEA1] = false; + tied_pins[id_DSP48E1][id_CEA2] = false; + tied_pins[id_DSP48E1][id_CEAD] = false; + tied_pins[id_DSP48E1][id_CEALUMODE] = false; + tied_pins[id_DSP48E1][id_CEB1] = false; + tied_pins[id_DSP48E1][id_CEB2] = false; + tied_pins[id_DSP48E1][id_CEC] = false; + tied_pins[id_DSP48E1][id_CECARRYIN] = false; + tied_pins[id_DSP48E1][id_CECTRL] = false; + tied_pins[id_DSP48E1][id_CED] = false; + tied_pins[id_DSP48E1][id_CEINMODE] = false; + tied_pins[id_DSP48E1][id_CEM] = false; + tied_pins[id_DSP48E1][id_CEP] = false; + for (int i = 0; i < 30; i++) + tied_pins[id_DSP48E1][ctx->id("A[" + std::to_string(i) + "]")] = false; + for (int i = 0; i < 18; i++) + tied_pins[id_DSP48E1][ctx->id("B[" + std::to_string(i) + "]")] = false; + for (int i = 0; i < 48; i++) + tied_pins[id_DSP48E1][ctx->id("C[" + std::to_string(i) + "]")] = false; + for (int i = 0; i < 25; i++) + tied_pins[id_DSP48E1][ctx->id("D[" + std::to_string(i) + "]")] = false; + for (int i = 0; i < 4; i++) + tied_pins[id_DSP48E1][ctx->id("ALUMODE[" + std::to_string(i) + "]")] = false; + for (int i = 0; i < 3; i++) + tied_pins[id_DSP48E1][ctx->id("CARRYINSEL[" + std::to_string(i) + "]")] = false; + for (int i = 0; i < 5; i++) + tied_pins[id_DSP48E1][ctx->id("INMODE[" + std::to_string(i) + "]")] = false; + for (int i = 0; i < 7; i++) + tied_pins[id_DSP48E1][ctx->id("OPMODE[" + std::to_string(i) + "]")] = false; +} + +// Get a list of logical pins that have both L and U bel pins that need +// to be connected for a 36-bit BRAM +void get_bram36_ul_pins(Context *ctx, std::vector>> &ul_pins) +{ + BelId spec_bel; + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) == id_RAMB36E1_RAMB36E1) { + spec_bel = bel; + break; + } + } + NPNR_ASSERT(spec_bel != BelId()); + pool belpins; + for (auto &bp : ctx->getBelPins(spec_bel)) + if (ctx->getBelPinType(spec_bel, bp) == PORT_IN) + belpins.insert(bp.str(ctx)); + for (auto &bp : belpins) { + std::string bus_suffix = ""; + std::string root_name = bp; + if (std::isdigit(bp.back())) { + auto root_end = bp.find_last_not_of("0123456789"); + bus_suffix = bp.substr(root_end + 1); + root_name = bp.substr(0, root_end + 1); + } + if (root_name.back() != 'L') + continue; + std::string base_name = root_name.substr(0, root_name.length() - 1); + std::string complement = base_name + "U" + bus_suffix; + if (!belpins.count(complement)) + continue; + std::string logical_name = bus_suffix.empty() ? base_name : (base_name + "[" + bus_suffix + "]"); + ul_pins.emplace_back(ctx->id(logical_name), std::vector{bp, complement}); + } +} + +// Gets a list of pins that are to be directly connected to a top level IO pin (only) +void get_top_level_pins(Context *ctx, dict> &toplevel_pins) +{ + toplevel_pins[id_IBUF] = {id_I}; + toplevel_pins[id_IBUF_ANALOG] = {id_I}; + toplevel_pins[id_IBUF_IBUFDISABLE] = {id_I}; + toplevel_pins[id_IBUF_INTERMDISABLE] = {id_I}; + toplevel_pins[id_IBUFE3] = {id_I}; + + toplevel_pins[id_IBUFDS] = {id_I, id_IB}; + toplevel_pins[id_IBUFDS_DIFF_OUT] = {id_I, id_IB}; + toplevel_pins[id_IBUFDS_DIFF_OUT_IBUFDISABLE] = {id_I, id_IB}; + toplevel_pins[id_IBUFDS_DIFF_OUT_INTERMDISABLE] = {id_I, id_IB}; + toplevel_pins[id_IBUFDS_GTE3] = {id_I, id_IB}; + toplevel_pins[id_IBUFDS_GTE4] = {id_I, id_IB}; + toplevel_pins[id_IBUFDS_INTERMDISABLE] = {id_I, id_IB}; + toplevel_pins[id_IBUFDSE3] = {id_I, id_IB}; + + toplevel_pins[id_IOBUF] = {id_IO}; + toplevel_pins[id_IOBUF_DCIEN] = {id_IO}; + toplevel_pins[id_IOBUF_INTERMDISABLE] = {id_IO}; + toplevel_pins[id_IOBUFE3] = {id_IO}; + + toplevel_pins[id_IOBUFDS] = {id_IO, id_IOB}; + toplevel_pins[id_IOBUFDS_DCIEN] = {id_IO, id_IOB}; + toplevel_pins[id_IOBUFDS_DIFF_OUT] = {id_IO, id_IOB}; + toplevel_pins[id_IOBUFDS_DIFF_OUT_DCIEN] = {id_IO, id_IOB}; + toplevel_pins[id_IOBUFDS_DIFF_OUT_INTERMDISABLE] = {id_IO, id_IOB}; + toplevel_pins[id_IOBUFDSE3] = {id_IO, id_IOB}; + + toplevel_pins[id_OBUF] = {id_O}; + toplevel_pins[id_OBUFT] = {id_O}; + + toplevel_pins[id_OBUFDS] = {id_O, id_OB}; + toplevel_pins[id_OBUFDS_GTE3] = {id_O, id_OB}; + toplevel_pins[id_OBUFDS_GTE3_ADV] = {id_O, id_OB}; + toplevel_pins[id_OBUFDS_GTE4] = {id_O, id_OB}; + toplevel_pins[id_OBUFDS_GTE4_ADV] = {id_O, id_OB}; + toplevel_pins[id_OBUFTDS] = {id_O, id_OB}; +} + +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/xilinx/pins.h b/himbaechel/uarch/xilinx/pins.h new file mode 100644 index 0000000000..e1a7bed5d9 --- /dev/null +++ b/himbaechel/uarch/xilinx/pins.h @@ -0,0 +1,34 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2023 Myrtle Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef HB_XILINX_PINS_H +#define HB_XILINX_PINS_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +void get_invertible_pins(Context *ctx, dict> &invertible_pins); +void get_tied_pins(Context *ctx, dict> &tied_pins); +void get_bram36_ul_pins(Context *ctx, std::vector>> &ul_pins); +void get_top_level_pins(Context *ctx, dict> &toplevel_pins); + +NEXTPNR_NAMESPACE_END + +#endif \ No newline at end of file diff --git a/himbaechel/uarch/xilinx/xdc.cc b/himbaechel/uarch/xilinx/xdc.cc new file mode 100644 index 0000000000..499a623ae1 --- /dev/null +++ b/himbaechel/uarch/xilinx/xdc.cc @@ -0,0 +1,200 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019-2023 gatecat + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include +#include + +#include "extra_data.h" +#include "himbaechel_api.h" +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +#include "xilinx.h" + +#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" +#include "himbaechel_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +void XilinxImpl::parse_xdc(const std::string &filename) +{ + std::ifstream in(filename); + if (!in) + log_error("failed to open XDC file '%s'\n", filename.c_str()); + std::string line; + std::string linebuf; + int lineno = 0; + + auto isempty = [](const std::string &str) { + return std::all_of(str.begin(), str.end(), [](char c) { return std::isspace(c); }); + }; + auto strip_quotes = [](const std::string &str) { + if (str.at(0) == '"') { + NPNR_ASSERT(str.back() == '"'); + return str.substr(1, str.size() - 2); + } else if (str.at(0) == '{') { + NPNR_ASSERT(str.back() == '}'); + return str.substr(1, str.size() - 2); + } else { + return str; + } + }; + auto split_to_args = [](const std::string &str, bool group_brackets) { + std::vector split_args; + std::string buffer; + auto flush = [&]() { + if (!buffer.empty()) + split_args.push_back(buffer); + buffer.clear(); + }; + int brcount = 0; + for (char c : str) { + if ((c == '[' || c == '{') && group_brackets) { + ++brcount; + } + if ((c == ']' || c == '}') && group_brackets) { + --brcount; + buffer += c; + if (brcount == 0) + flush(); + continue; + } + if (std::isspace(c)) { + if (brcount == 0) { + flush(); + continue; + } + } + buffer += c; + } + flush(); + return split_args; + }; + + auto get_cells = [&](std::string str) { + std::vector tgt_cells; + if (str.empty() || str.front() != '[') + log_error("failed to parse target (on line %d)\n", lineno); + str = str.substr(1, str.size() - 2); + auto split = split_to_args(str, false); + if (split.size() < 1) + log_error("failed to parse target (on line %d)\n", lineno); + if (split.front() != "get_ports") + log_error("targets other than 'get_ports' are not supported (on line %d)\n", lineno); + if (split.size() < 2) + log_error("failed to parse target (on line %d)\n", lineno); + IdString cellname = ctx->id(strip_quotes(split.at(1))); + if (ctx->cells.count(cellname)) + tgt_cells.push_back(ctx->cells.at(cellname).get()); + return tgt_cells; + }; + + auto get_nets = [&](std::string str) { + std::vector tgt_nets; + if (str.empty() || str.front() != '[') + log_error("failed to parse target (on line %d)\n", lineno); + str = str.substr(1, str.size() - 2); + auto split = split_to_args(str, false); + if (split.size() < 1) + log_error("failed to parse target (on line %d)\n", lineno); + if (split.front() != "get_ports" && split.front() != "get_nets") + log_error("targets other than 'get_ports' or 'get_nets' are not supported (on line %d)\n", lineno); + if (split.size() < 2) + log_error("failed to parse target (on line %d)\n", lineno); + IdString netname = ctx->id(split.at(1)); + NetInfo *maybe_net = ctx->getNetByAlias(netname); + if (maybe_net != nullptr) + tgt_nets.push_back(maybe_net); + return tgt_nets; + }; + + while (std::getline(in, line)) { + ++lineno; + // Trim comments, from # until end of the line + size_t cstart = line.find('#'); + if (cstart != std::string::npos) + line = line.substr(0, cstart); + if (isempty(line)) + continue; + + std::vector arguments = split_to_args(line, true); + if (arguments.empty()) + continue; + std::string &cmd = arguments.front(); + if (cmd == "set_property") { + std::vector> arg_pairs; + if (arguments.size() != 4) + log_error("expected four arguments to 'set_property' (on line %d)\n", lineno); + else if (arguments.at(1) == "-dict") { + std::vector dict_args = split_to_args(strip_quotes(arguments.at(2)), false); + if ((dict_args.size() % 2) != 0) + log_error("expected an even number of argument for dictionary (on line %d)\n", lineno); + arg_pairs.reserve(dict_args.size() / 2); + for (int cursor = 0; cursor + 1 < int(dict_args.size()); cursor += 2) { + arg_pairs.emplace_back(std::move(dict_args.at(cursor)), std::move(dict_args.at(cursor + 1))); + } + } else + arg_pairs.emplace_back(std::move(arguments.at(1)), std::move(arguments.at(2))); + if (arguments.at(1) == "INTERNAL_VREF") + continue; + if (arguments.at(3).size() > 2 && arguments.at(3) == "[current_design]") { + log_warning("[current_design] isn't supported, ignoring (on line %d)\n", lineno); + continue; + } + std::vector dest = get_cells(arguments.at(3)); + for (auto c : dest) + for (const auto &pair : arg_pairs) + c->attrs[ctx->id(pair.first)] = std::string(pair.second); + } else if (cmd == "create_clock") { + double period = 0; + bool got_period = false; + int cursor = 1; + for (cursor = 1; cursor < int(arguments.size()); cursor++) { + std::string opt = arguments.at(cursor); + if (opt == "-add") + ; + else if (opt == "-name" || opt == "-waveform") + cursor++; + else if (opt == "-period") { + cursor++; + period = std::stod(arguments.at(cursor)); + got_period = true; + } else + break; + } + if (!got_period) + log_error("found create_clock without period (on line %d)", lineno); + std::vector dest = get_nets(arguments.at(cursor)); + for (auto n : dest) { + n->clkconstr = std::unique_ptr(new ClockConstraint); + n->clkconstr->period = DelayPair(ctx->getDelayFromNS(period)); + n->clkconstr->high = DelayPair(ctx->getDelayFromNS(period / 2)); + n->clkconstr->low = DelayPair(ctx->getDelayFromNS(period / 2)); + } + } else { + log_info("ignoring unsupported XDC command '%s' (on line %d)\n", cmd.c_str(), lineno); + } + } + if (!isempty(linebuf)) + log_error("unexpected end of XDC file\n"); +} + +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/xilinx/xilinx.cc b/himbaechel/uarch/xilinx/xilinx.cc new file mode 100644 index 0000000000..f5ff8b5a59 --- /dev/null +++ b/himbaechel/uarch/xilinx/xilinx.cc @@ -0,0 +1,557 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2023 gatecat + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include +#include + +#include "himbaechel_api.h" +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +#include "placer_heap.h" +#include "xilinx.h" + +#include "himbaechel_helpers.h" + +#define GEN_INIT_CONSTIDS +#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" +#include "himbaechel_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +XilinxImpl::~XilinxImpl(){}; + +void XilinxImpl::init_database(Arch *arch) +{ + const ArchArgs &args = arch->args; + init_uarch_constids(arch); + std::regex devicere = std::regex("(xc7[azkv]\\d+t)([a-z0-9]+)-(\\dL?)"); + std::smatch match; + if (!std::regex_match(args.device, match, devicere)) { + log_error("Invalid device %s\n", args.device.c_str()); + } + std::string die = match[1].str(); + if (die == "xc7a35t") + die = "xc7a50t"; + arch->load_chipdb(stringf("xilinx/chipdb-%s.bin", die.c_str())); + std::string package = match[2].str(); + arch->set_package(package); + arch->set_speed_grade("DEFAULT"); +} + +void XilinxImpl::init(Context *ctx) +{ + h.init(ctx); + HimbaechelAPI::init(ctx); + + tile_status.resize(ctx->chip_info->tile_insts.size()); + for (int i = 0; i < ctx->chip_info->tile_insts.ssize(); i++) { + auto extra_data = tile_extra_data(i); + tile_status.at(i).site_variant.resize(extra_data->sites.ssize()); + } +} + +SiteIndex XilinxImpl::get_bel_site(BelId bel) const +{ + auto &bel_data = chip_bel_info(ctx->chip_info, bel); + auto site_key = BelSiteKey::unpack(bel_data.site); + return SiteIndex(bel.tile, site_key.site); +} + +IdString XilinxImpl::get_site_name(SiteIndex site) const +{ + const auto &site_data = tile_extra_data(site.tile)->sites[site.site]; + return ctx->idf("%s_X%dY%d", IdString(site_data.name_prefix).c_str(ctx), site_data.site_x, site_data.site_y); +} + +BelId XilinxImpl::get_site_bel(SiteIndex site, IdString bel_name) const +{ + const auto &tile_data = chip_tile_info(ctx->chip_info, site.tile); + for (int32_t i = 0; i < tile_data.bels.ssize(); i++) { + const auto &bel_data = tile_data.bels[i]; + if (BelSiteKey::unpack(bel_data.site).site != site.site) + continue; + if (reinterpret_cast(bel_data.extra_data.get())->name_in_site != bel_name.index) + continue; + return BelId(site.tile, i); + } + return BelId(); +} + +IdString XilinxImpl::bel_name_in_site(BelId bel) const +{ + const auto &bel_data = chip_bel_info(ctx->chip_info, bel); + return IdString(reinterpret_cast(bel_data.extra_data.get())->name_in_site); +} + +IdStringList XilinxImpl::get_site_bel_name(BelId bel) const +{ + return IdStringList::concat(get_site_name(get_bel_site(bel)), bel_name_in_site(bel)); +} + +void XilinxImpl::notifyBelChange(BelId bel, CellInfo *cell) +{ + auto &ts = tile_status.at(bel.tile); + auto &bel_data = chip_bel_info(ctx->chip_info, bel); + auto site_key = BelSiteKey::unpack(bel_data.site); + // Update bound site variant for pip validity use later on + if (cell && cell->type != id_PAD && site_key.site >= 0 && site_key.site < int(ts.site_variant.size())) { + ts.site_variant.at(site_key.site) = site_key.site_variant; + } + if (is_logic_tile(bel)) + update_logic_bel(bel, cell); + if (is_bram_tile(bel)) + update_bram_bel(bel, cell); +} + +void XilinxImpl::update_logic_bel(BelId bel, CellInfo *cell) +{ + int z = ctx->getBelLocation(bel).z; + NPNR_ASSERT(z < 128); + auto &tts = tile_status.at(bel.tile); + if (!tts.lts) + tts.lts = std::make_unique(); + auto &ts = *(tts.lts); + auto tags = get_tags(cell), last_tags = get_tags(ts.cells[z]); + if ((z == ((3 << 4) | BEL_6LUT)) || (z == ((3 << 4) | BEL_5LUT))) { + if ((tags && tags->lut.is_memory) || (last_tags && last_tags->lut.is_memory)) { + // Special case - memory write port invalidates everything + for (int i = 0; i < 8; i++) + ts.eights[i].dirty = true; + // if (xc7) + ts.halfs[0].dirty = true; // WCLK and CLK0 shared + } + } + if ((((z & 0xF) == BEL_6LUT) || ((z & 0xF) == BEL_5LUT)) && + ((tags && tags->lut.is_srl) || (last_tags && last_tags->lut.is_srl))) { + // SRLs invalidate everything due to write clock + for (int i = 0; i < 8; i++) + ts.eights[i].dirty = true; + // if (xc7) + ts.halfs[0].dirty = true; // WCLK and CLK0 shared + } + + ts.cells[z] = cell; + + // determine which sections to mark as dirty + switch (z & 0xF) { + case BEL_FF: + case BEL_FF2: + ts.halfs[(z >> 4) / 4].dirty = true; + if ((((z >> 4) / 4) == 0) /*&& xc7*/) + ts.eights[3].dirty = true; + /* fall-through */ + case BEL_6LUT: + case BEL_5LUT: + ts.eights[z >> 4].dirty = true; + break; + case BEL_F7MUX: + ts.eights[z >> 4].dirty = true; + ts.eights[(z >> 4) + 1].dirty = true; + break; + case BEL_F8MUX: + ts.eights[(z >> 4) + 1].dirty = true; + ts.eights[(z >> 4) + 2].dirty = true; + break; + case BEL_CARRY4: + for (int i = ((z >> 4) / 4) * 4; i < (((z >> 4) / 4) + 1) * 4; i++) + ts.eights[i].dirty = true; + break; + } +} + +void XilinxImpl::update_bram_bel(BelId bel, CellInfo *cell) {} + +bool XilinxImpl::is_pip_unavail(PipId pip) const +{ + const auto &pip_data = chip_pip_info(ctx->chip_info, pip); + const auto &extra_data = *reinterpret_cast(pip_data.extra_data.get()); + unsigned pip_type = pip_data.flags; + if (pip_type == PIP_SITE_ENTRY) { + WireId dst = ctx->getPipDstWire(pip); + if (ctx->getWireType(dst) == id_INTENT_SITE_GND) { + const auto <s = tile_status[dst.tile].lts; + if (lts && (lts->cells[BEL_5LUT] != nullptr || lts->cells[BEL_6LUT] != nullptr)) + return true; // Ground driver only available if lowest 5LUT and 6LUT not used + } + } else if (pip_type == PIP_CONST_DRIVER) { + WireId dst = ctx->getPipDstWire(pip); + const auto <s = tile_status[dst.tile].lts; + if (lts && (lts->cells[BEL_5LUT] != nullptr || lts->cells[BEL_6LUT] != nullptr)) + return true; // Ground driver only available if lowest 5LUT and 6LUT not used + } else if (pip_type == PIP_SITE_INTERNAL) { + if (extra_data.bel_name == ID_TRIBUF) + return true; + auto site = BelSiteKey::unpack(extra_data.site_key); + // Check site variant of PIP matches configured site variant of tile + if (site.site >= 0 && site.site < int(tile_status[pip.tile].site_variant.size())) { + if (site.site_variant > 0 && site.site_variant != tile_status[pip.tile].site_variant.at(site.site)) + return true; + } + } else if (pip_type == PIP_LUT_PERMUTATION) { + const auto <s = tile_status[pip.tile].lts; + if (!lts) + return false; + int eight = (extra_data.pip_config >> 8) & 0xF; + + if (((extra_data.pip_config >> 4) & 0xF) == (extra_data.pip_config & 0xF)) + return false; // from==to, always valid + + auto lut6 = get_tags(lts->cells[(eight << 4) | BEL_6LUT]); + if (lut6 && (lut6->lut.is_memory || lut6->lut.is_srl)) + return true; + auto lut5 = get_tags(lts->cells[(eight << 4) | BEL_5LUT]); + if (lut5 && (lut5->lut.is_memory || lut5->lut.is_srl)) + return true; + } else if (pip_type == PIP_LUT_ROUTETHRU) { + int eight = (extra_data.pip_config >> 8) & 0xF; + int dest = (extra_data.pip_config & 0x1); + if (eight == 0) + return true; // FIXME: conflict with ground + if (dest & 0x1) + return true; // FIXME: routethru to MUX + const auto <s = tile_status[pip.tile].lts; + if (!lts) + return false; + const CellInfo *lut6 = lts->cells[(eight << 4) | BEL_6LUT]; + if (lut6) + return true; + const CellInfo *lut5 = lts->cells[(eight << 4) | BEL_5LUT]; + if (lut5) + return true; + } + + return false; +} + +void XilinxImpl::prePlace() { assign_cell_tags(); } + +void XilinxImpl::postPlace() +{ + fixup_placement(); + ctx->assignArchInfo(); +} + +void XilinxImpl::configurePlacerHeap(PlacerHeapCfg &cfg) +{ + cfg.hpwl_scale_x = 2; + cfg.hpwl_scale_y = 1; + cfg.beta = 0.5; + cfg.placeAllAtOnce = true; +} + +void XilinxImpl::preRoute() +{ + find_source_sink_locs(); + route_clocks(); +} + +void XilinxImpl::postRoute() +{ + fixup_routing(); + ctx->assignArchInfo(); + const ArchArgs &args = ctx->args; + if (args.options.count("fasm")) { + write_fasm(args.options.at("fasm")); + } +} + +IdString XilinxImpl::bel_tile_type(BelId bel) const +{ + return IdString(chip_tile_info(ctx->chip_info, bel.tile).type_name); +} + +bool XilinxImpl::is_logic_tile(BelId bel) const +{ + return bel_tile_type(bel).in(id_CLEL_L, id_CLEL_R, id_CLEM, id_CLEM_R, id_CLBLL_L, id_CLBLL_R, id_CLBLM_L, + id_CLBLM_R); +} +bool XilinxImpl::is_bram_tile(BelId bel) const { return bel_tile_type(bel).in(id_BRAM, id_BRAM_L, id_BRAM_R); } + +const XlnxTileInstExtraDataPOD *XilinxImpl::tile_extra_data(int tile) const +{ + return reinterpret_cast(ctx->chip_info->tile_insts[tile].extra_data.get()); +} + +std::string XilinxImpl::tile_name(int tile) const +{ + const auto &data = *tile_extra_data(tile); + return stringf("%s_X%dY%d", IdString(data.name_prefix).c_str(ctx), data.tile_x, data.tile_y); +} + +Loc XilinxImpl::rel_site_loc(SiteIndex site) const +{ + const auto &site_data = tile_extra_data(site.tile)->sites[site.site]; + return Loc(site_data.rel_x, site_data.rel_y, 0); +} + +int XilinxImpl::hclk_for_iob(BelId pad) const +{ + std::string tile_type = bel_tile_type(pad).str(ctx); + int ioi = pad.tile; + if (boost::starts_with(tile_type, "LIOB")) + ioi += 1; + else if (boost::starts_with(tile_type, "RIOB")) + ioi -= 1; + else + NPNR_ASSERT_FALSE("unknown IOB side"); + return hclk_for_ioi(ioi); +} + +int XilinxImpl::hclk_for_ioi(int tile) const +{ + WireId ioclk0; + auto &td = chip_tile_info(ctx->chip_info, tile); + for (int i = 0; i < td.wires.ssize(); i++) { + std::string name = IdString(td.wires[i].name).str(ctx); + if (name == "IOI_IOCLK0" || name == "IOI_SING_IOCLK0") { + ioclk0 = ctx->normalise_wire(tile, i); + break; + } + } + NPNR_ASSERT(ioclk0 != WireId()); + for (auto uh : ctx->getPipsUphill(ioclk0)) + return uh.tile; + NPNR_ASSERT_FALSE("failed to find HCLK pips"); +} + +void XilinxImpl::assign_cell_tags() +{ + cell_tags.resize(ctx->cells.size()); + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + auto &ct = cell_tags.at(ci->flat_index); + if (ci->type == id_SLICE_LUTX) { + ct.lut.input_count = 0; + for (IdString a : {id_A1, id_A2, id_A3, id_A4, id_A5, id_A6}) { + NetInfo *pn = ci->getPort(a); + if (pn != nullptr) + ct.lut.input_sigs[ct.lut.input_count++] = pn; + } + ct.lut.output_count = 0; + for (IdString o : {id_O6, id_O5}) { + NetInfo *pn = ci->getPort(o); + if (pn != nullptr) + ct.lut.output_sigs[ct.lut.output_count++] = pn; + } + for (int i = ct.lut.output_count; i < 2; i++) + ct.lut.output_sigs[i] = nullptr; + ct.lut.di1_net = ci->getPort(id_DI1); + ct.lut.di2_net = ci->getPort(id_DI2); + ct.lut.wclk = ci->getPort(id_CLK); + ct.lut.memory_group = 0; // fixme + ct.lut.is_srl = ci->attrs.count(id_X_LUT_AS_SRL); + ct.lut.is_memory = ci->attrs.count(id_X_LUT_AS_DRAM); + ct.lut.only_drives_carry = false; + if (ci->cluster != ClusterId() && ct.lut.output_count > 0 && ct.lut.output_sigs[0] != nullptr && + ct.lut.output_sigs[0]->users.entries() == 1 && + (*ct.lut.output_sigs[0]->users.begin()).cell->type == id_CARRY4) + ct.lut.only_drives_carry = true; + + const IdString addr_msb_sigs[] = {id_WA7, id_WA8, id_WA9}; + for (int i = 0; i < 3; i++) + ct.lut.address_msb[i] = ci->getPort(addr_msb_sigs[i]); + + } else if (ci->type == id_SLICE_FFX) { + ct.ff.d = ci->getPort(id_D); + ct.ff.clk = ci->getPort(id_CK); + ct.ff.ce = ci->getPort(id_CE); + ct.ff.sr = ci->getPort(id_SR); + ct.ff.is_clkinv = bool_or_default(ci->params, id_IS_CLK_INVERTED, false); + ct.ff.is_srinv = bool_or_default(ci->params, id_IS_R_INVERTED, false) || + bool_or_default(ci->params, id_IS_S_INVERTED, false) || + bool_or_default(ci->params, id_IS_CLR_INVERTED, false) || + bool_or_default(ci->params, id_IS_PRE_INVERTED, false); + ct.ff.is_latch = ci->attrs.count(id_X_FF_AS_LATCH); + ct.ff.ffsync = ci->attrs.count(id_X_FFSYNC); + } else if (ci->type.in(id_F7MUX, id_F8MUX, id_F9MUX, id_SELMUX2_1)) { + ct.mux.sel = ci->getPort(id_S0); + ct.mux.out = ci->getPort(id_OUT); + } else if (ci->type == id_CARRY4) { + for (int i = 0; i < 4; i++) { + ct.carry.out_sigs[i] = ci->getPort(ctx->idf("O%d", i)); + ct.carry.cout_sigs[i] = ci->getPort(ctx->idf("CO%d", i)); + ct.carry.x_sigs[i] = nullptr; + } + ct.carry.x_sigs[0] = ci->getPort(id_CYINIT); + } + } +} + +bool XilinxImpl::is_general_routing(WireId wire) const +{ + IdString intent = ctx->getWireType(wire); + return !intent.in(id_INTENT_DEFAULT, id_NODE_DEDICATED, id_NODE_OPTDELAY, id_NODE_OUTPUT, id_NODE_INT_INTERFACE, + id_PINFEED, id_INPUT, id_PADOUTPUT, id_PADINPUT, id_IOBINPUT, id_IOBOUTPUT, id_GENERIC, + id_IOBIN2OUT, id_INTENT_SITE_WIRE, id_INTENT_SITE_GND); +} + +void XilinxImpl::find_source_sink_locs() +{ + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + for (auto &usr : ni->users) { + BelId bel = usr.cell->bel; + if (bel == BelId() || is_logic_tile(bel)) + continue; // don't need to do this for logic bels, which are always next to their INT + WireId sink = ctx->getNetinfoSinkWire(ni, usr, 0); + if (sink == WireId() || sink_locs.count(sink)) + continue; + std::queue visit; + dict backtrace; + int iter = 0; + // as this is a best-effort optimisation to slightly improve routing, + // don't spend too long with a nice low iteration limit + const int iter_max = 500; + visit.push(sink); + while (!visit.empty() && iter < iter_max) { + ++iter; + WireId cursor = visit.front(); + visit.pop(); + if (is_general_routing(cursor)) { + Loc loc(0, 0, 0); + tile_xy(ctx->chip_info, cursor.tile, loc.x, loc.y); + sink_locs[sink] = loc; + + while (backtrace.count(cursor)) { + cursor = backtrace.at(cursor); + if (!sink_locs.count(cursor)) { + sink_locs[cursor] = loc; + } + } + + break; + } + for (auto pip : ctx->getPipsUphill(cursor)) { + WireId src = ctx->getPipSrcWire(pip); + if (!backtrace.count(src)) { + backtrace[src] = cursor; + visit.push(src); + } + } + } + } + auto &drv = ni->driver; + if (drv.cell != nullptr) { + BelId bel = drv.cell->bel; + if (bel == BelId() || is_logic_tile(bel)) + continue; // don't need to do this for logic bels, which are always next to their INT + WireId source = ctx->getNetinfoSourceWire(ni); + if (source == WireId() || source_locs.count(source)) + continue; + std::queue visit; + dict backtrace; + int iter = 0; + // as this is a best-effort optimisation to slightly improve routing, + // don't spend too long with a nice low iteration limit + const int iter_max = 500; + visit.push(source); + while (!visit.empty() && iter < iter_max) { + ++iter; + WireId cursor = visit.front(); + visit.pop(); + if (is_general_routing(cursor)) { + Loc loc(0, 0, 0); + tile_xy(ctx->chip_info, cursor.tile, loc.x, loc.y); + source_locs[source] = loc; + + while (backtrace.count(cursor)) { + cursor = backtrace.at(cursor); + if (!source_locs.count(cursor)) { + source_locs[cursor] = loc; + } + } + + break; + } + for (auto pip : ctx->getPipsDownhill(cursor)) { + WireId dst = ctx->getPipDstWire(pip); + if (!backtrace.count(dst)) { + backtrace[dst] = cursor; + visit.push(dst); + } + } + } + } + } +} + +delay_t XilinxImpl::estimateDelay(WireId src, WireId dst) const +{ + int sx, sy, dx, dy; + tile_xy(ctx->chip_info, src.tile, sx, sy); + tile_xy(ctx->chip_info, dst.tile, dx, dy); + auto fnd_src = source_locs.find(src); + if (fnd_src != source_locs.end()) { + sx = fnd_src->second.x; + sy = fnd_src->second.y; + } + auto fnd_snk = sink_locs.find(dst); + if (fnd_snk != sink_locs.end()) { + dx = fnd_snk->second.x; + dy = fnd_snk->second.y; + } + // TODO: improve sophistication here based on old nextpnr-xilinx code + return 800 + 50 * (std::abs(dy - sy) + std::abs(dx - sx)); +} + +BoundingBox XilinxImpl::getRouteBoundingBox(WireId src, WireId dst) const +{ + int x0, y0, x1, y1; + auto expand = [&](int x, int y) { + x0 = std::min(x0, x); + x1 = std::max(x1, x); + y0 = std::min(y0, y); + y1 = std::max(y1, y); + }; + + tile_xy(ctx->chip_info, src.tile, x0, y0); + x1 = x0; + y1 = y0; + + int dx, dy; + tile_xy(ctx->chip_info, dst.tile, dx, dy); + expand(dx, dy); + + auto fnd_src = source_locs.find(src); + if (fnd_src != source_locs.end()) { + expand(fnd_src->second.x, fnd_src->second.y); + } + auto fnd_snk = sink_locs.find(dst); + if (fnd_snk != sink_locs.end()) { + expand(fnd_snk->second.x, fnd_snk->second.y); + } + return {x0 - 2, y0 - 2, x1 + 2, y1 + 2}; +} + +namespace { +struct XilinxArch : HimbaechelArch +{ + XilinxArch() : HimbaechelArch("xilinx"){}; + bool match_device(const std::string &device) override { return device.size() > 3 && device.substr(0, 3) == "xc7"; } + std::unique_ptr create(const std::string &device, const dict &args) + { + return std::make_unique(); + } +} xilinxArch; +} // namespace + +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/xilinx/xilinx.h b/himbaechel/uarch/xilinx/xilinx.h new file mode 100644 index 0000000000..1a87c78a52 --- /dev/null +++ b/himbaechel/uarch/xilinx/xilinx.h @@ -0,0 +1,180 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2023 gatecat + * + * 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 HIMBAECHEL_XILINX_H +#define HIMBAECHEL_XILINX_H + +#include "extra_data.h" +#include "himbaechel_api.h" +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +#include "himbaechel_helpers.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct XilinxCellTags +{ + union + { + struct + { + bool is_memory, is_srl; + int input_count, output_count; + int memory_group; + bool only_drives_carry; + NetInfo *input_sigs[6], *output_sigs[2]; + NetInfo *address_msb[3]; + NetInfo *di1_net, *di2_net, *wclk; + } lut; + struct + { + bool is_latch, is_clkinv, is_srinv, ffsync; + bool is_paired; + NetInfo *clk, *sr, *ce, *d; + } ff; + struct + { + NetInfo *out_sigs[8], *cout_sigs[8], *x_sigs[8]; + } carry; + struct + { + NetInfo *sel, *out; + } mux; + }; +}; + +struct SiteIndex +{ + SiteIndex() : tile(-1), site(-1){}; + SiteIndex(int32_t tile, int32_t site) : tile(tile), site(site){}; + + int32_t tile; + int32_t site; + bool operator==(const SiteIndex &other) const { return tile == other.tile && site == other.site; } + bool operator!=(const SiteIndex &other) const { return tile != other.tile || site != other.site; } + unsigned hash() const { return mkhash(tile, site); } +}; + +struct XilinxImpl : HimbaechelAPI +{ + + struct LogicTileStatus + { + // z -> cell + CellInfo *cells[128]; + + // Eight-tile valid and dirty status + struct EigthTileStatus + { + mutable bool valid = true, dirty = true; + } eights[8]; + struct HalfTileStatus + { + mutable bool valid = true, dirty = true; + } halfs[8]; + }; + + struct BRAMTileStatus + { + CellInfo *cells[12] = {nullptr}; + }; + + struct TileStatus + { + std::unique_ptr lts; + std::unique_ptr bts; + std::vector site_variant; + }; + + ~XilinxImpl(); + void init_database(Arch *arch) override; + + void init(Context *ctx) override; + + // Bels + void notifyBelChange(BelId bel, CellInfo *cell) override; + void update_logic_bel(BelId bel, CellInfo *cell); + void update_bram_bel(BelId bel, CellInfo *cell); + + bool isBelLocationValid(BelId bel, bool explain_invalid = false) const override; + bool xc7_logic_tile_valid(IdString tileType, const LogicTileStatus <s) const; + + // Pips + bool is_pip_unavail(PipId pip) const; + bool checkPipAvail(PipId pip) const override { return !is_pip_unavail(pip); } + bool checkPipAvailForNet(PipId pip, const NetInfo *net) const override { return !is_pip_unavail(pip); } + + // Flow management + void parse_xdc(const std::string &filename); + void pack() override; + void prePlace() override; + void preRoute() override; + void postPlace() override; + void postRoute() override; + void write_fasm(const std::string &filename); + + void configurePlacerHeap(PlacerHeapCfg &cfg) override; + + void fixup_placement(); + void fixup_routing(); + void route_clocks(); + + // Misc utility functions + const XlnxTileInstExtraDataPOD *tile_extra_data(int tile) const; + IdString bel_tile_type(BelId bel) const; + bool is_logic_tile(BelId bel) const; + bool is_bram_tile(BelId bel) const; + + SiteIndex get_bel_site(BelId bel) const; + Loc rel_site_loc(SiteIndex site) const; + IdString get_site_name(SiteIndex site) const; + IdString bel_name_in_site(BelId bel) const; + IdStringList get_site_bel_name(BelId bel) const; + BelId get_site_bel(SiteIndex site, IdString bel_name) const; + + int hclk_for_iob(BelId pad) const; + int hclk_for_ioi(int tile) const; + + std::string tile_name(int tile) const; + + std::vector cell_tags; + const XilinxCellTags *get_tags(const CellInfo *cell) const + { + return cell ? &cell_tags.at(cell->flat_index) : nullptr; + } + + std::vector tile_status; + + // Improved delay predictions where sites are located far from their associated interconnect + dict source_locs, sink_locs; + bool is_general_routing(WireId wire) const; + void find_source_sink_locs(); + + delay_t estimateDelay(WireId src, WireId dst) const override; + BoundingBox getRouteBoundingBox(WireId src, WireId dst) const override; + + private: + HimbaechelHelpers h; + void assign_cell_tags(); +}; + +NEXTPNR_NAMESPACE_END +#endif diff --git a/himbaechel/uarch/xilinx/xilinx_place.cc b/himbaechel/uarch/xilinx/xilinx_place.cc new file mode 100644 index 0000000000..d39b94d026 --- /dev/null +++ b/himbaechel/uarch/xilinx/xilinx_place.cc @@ -0,0 +1,640 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019-2023 gatecat + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include + +#include "extra_data.h" +#include "himbaechel_api.h" +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +#include "xilinx.h" + +#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" +#include "himbaechel_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +// #define DEBUG_VALIDITY +#ifdef DEBUG_VALIDITY +#define DBG() log_info("invalid: %s %d\n", __FILE__, __LINE__) +#else +#define DBG() +#endif + +bool XilinxImpl::xc7_logic_tile_valid(IdString tile_type, const LogicTileStatus <s) const +{ + bool is_slicem = (tile_type == id_CLBLM_L) || (tile_type == id_CLBLM_R); + bool tile_is_memory = false; + if (lts.cells[(3 << 4) | BEL_6LUT] != nullptr && get_tags(lts.cells[(3 << 4) | BEL_6LUT])->lut.is_memory) + tile_is_memory = true; + bool small_memory = false; + if (lts.cells[(3 << 4) | BEL_5LUT] != nullptr && get_tags(lts.cells[(3 << 4) | BEL_5LUT])->lut.is_memory) + small_memory = true; + NetInfo *wclk = nullptr; + // Check eight-tiles (mostly LUT-related validity) + for (int i = 0; i < 8; i++) { + if (lts.eights[i].dirty) { + lts.eights[i].dirty = false; + lts.eights[i].valid = false; + + auto lut6 = get_tags(lts.cells[(i << 4) | BEL_6LUT]); + auto lut5 = get_tags(lts.cells[(i << 4) | BEL_5LUT]); + + // Check 6LUT + if (lut6) { + if (!is_slicem && (lut6->lut.is_memory || lut6->lut.is_srl)) { + DBG(); + return false; + } // Memory and SRLs only valid in SLICEMs + if (lut6->lut.is_srl && (i >= 4)) { + DBG(); + return false; + } + if (lut6->lut.is_memory || lut6->lut.is_srl) { + if (wclk == nullptr) + wclk = lut6->lut.wclk; + else if (lut6->lut.wclk != wclk) { + DBG(); + return false; + } + } + if (lut5) { + // Can't mix memory and non-memory + if (lut6->lut.is_memory != lut5->lut.is_memory || lut6->lut.is_srl != lut5->lut.is_srl) { + DBG(); + return false; + } + // If all 6 inputs or 2 outputs are used, 5LUT can't also be present + if (lut6->lut.input_count == 6 || lut6->lut.output_count == 2) { + DBG(); + return false; + } + // If more than 5 total inputs are used, need to check number of shared input + if ((lut6->lut.input_count + lut5->lut.input_count) > 5) { + int shared = 0, need_shared = (lut6->lut.input_count + lut5->lut.input_count - 5); + for (int j = 0; j < lut6->lut.input_count; j++) { + for (int k = 0; k < lut5->lut.input_count; k++) { + if (lut6->lut.input_sigs[j] == lut5->lut.input_sigs[k]) + shared++; + if (shared >= need_shared) + break; + } + } + if (shared < need_shared) { + DBG(); + return false; + } + } + } + } + if (lut5 != nullptr) { + if (!is_slicem && (lut5->lut.is_memory || lut5->lut.is_srl)) { + DBG(); + return false; // Memory and SRLs only valid in SLICEMs + } + if (lut5->lut.is_srl) { + if (wclk == nullptr) + wclk = lut5->lut.wclk; + else if (lut5->lut.wclk != wclk) { + DBG(); + return false; + } + } + // 5LUT can use at most 5 inputs and 1 output + if (lut5->lut.input_count > 5 || lut5->lut.output_count == 2) { + DBG(); + return false; // Memory and SRLs only valid in SLICEMs + } + } + + // Check (over)usage ofX inputs + NetInfo *x_net = nullptr; + if (lut6) { + x_net = lut6->lut.di2_net; + } + + CellInfo *mux_cell = nullptr; + // Eights A, C, E, G: F7MUX uses X input + if (i == 0 || i == 2 || i == 4 || i == 6) + mux_cell = lts.cells[i << 4 | BEL_F7MUX]; + // Eights B, F: F8MUX uses X input + if (i == 1 || i == 5) + mux_cell = lts.cells[(i - 1) << 4 | BEL_F8MUX]; + auto mux = get_tags(mux_cell); + if (mux) { + if (x_net) + x_net = mux->mux.sel; + else if (x_net != mux->mux.sel) { + DBG(); + return false; // Memory and SRLs only valid in SLICEMs + } + } + + CellInfo *out_fmux_cell = nullptr; + // Subslices A, C: F7MUX connects to F7F8 out + if (i == 0 || i == 2 || i == 4 || i == 6) + out_fmux_cell = lts.cells[(i << 4) | BEL_F7MUX]; + // Subslices B: F8MUX connects to F7F8 out + if (i == 1 || i == 5) + out_fmux_cell = lts.cells[(i - 1) << 4 | BEL_F8MUX]; + + auto carry4 = get_tags(lts.cells[((i / 4) << 6) | BEL_CARRY4]); + + if (carry4 != nullptr && carry4->carry.x_sigs[i % 4] != nullptr) { + if (x_net == nullptr) + x_net = carry4->carry.x_sigs[i % 4]; + else if (x_net != carry4->carry.x_sigs[i % 4]) { + DBG(); + return false; + } + } + + // FF1 might use X, if it isn't driven directly + auto ff1 = get_tags(lts.cells[i << 4 | BEL_FF]); + if (ff1 != nullptr && ff1->ff.d != nullptr && ff1->ff.d->driver.cell != nullptr) { + auto &drv = ff1->ff.d->driver; + if ((drv.cell == lts.cells[(i << 4) | BEL_6LUT] && drv.port != id_MC31) || + drv.cell == lts.cells[(i << 4) | BEL_5LUT] || drv.cell == out_fmux_cell) { + // Direct, OK + } else { + // Indirect, must use X input + if (x_net == nullptr) + x_net = ff1->ff.d; + else if (x_net != ff1->ff.d) { + DBG(); + return false; + } + } + } + + // FF2 might use X, if it isn't driven directly + auto ff2 = get_tags(lts.cells[i << 4 | BEL_FF2]); + if (ff2 != nullptr && ff2->ff.d != nullptr && ff2->ff.d->driver.cell != nullptr) { + auto &drv = ff2->ff.d->driver; + if (drv.cell == lts.cells[(i << 4) | BEL_5LUT]) { + // Direct, OK + } else { + // Indirect, must use X input + if (x_net == nullptr) + x_net = ff2->ff.d; + else if (x_net != ff2->ff.d) { +#ifdef DEBUG_VALIDITY + log_info("%s %s %s %s %s\n", nameOf(lut6), nameOf(ff1), nameOf(lut5), nameOf(ff2), + nameOf(drv.cell)); +#endif + DBG(); + return false; + } + } + } + + // collision with top address bits + if (tile_is_memory && !small_memory) { + auto top_lut = get_tags(lts.cells[(3 << 4) | BEL_6LUT]); + if (top_lut) { + if ((i == 2) && x_net != top_lut->lut.address_msb[0]) { + DBG(); + return false; + } + if ((i == 1) && x_net != top_lut->lut.address_msb[1]) { + DBG(); + return false; + } + } + } + + bool mux_output_used = false; + NetInfo *out5 = nullptr; + if (lut6 != nullptr && lut6->lut.output_count == 2) + out5 = lut6->lut.output_sigs[1]; + else if (lut5 != nullptr && !lut5->lut.only_drives_carry) + out5 = lut5->lut.output_sigs[0]; + if (out5 != nullptr && (out5->users.entries() > 1 || + ((ff1 == nullptr || out5 != ff1->ff.d) && (ff2 == nullptr || out5 != ff2->ff.d)))) { + mux_output_used = true; + } + + if (carry4 != nullptr && carry4->carry.out_sigs[i % 4] != nullptr) { + // FIXME: direct connections to FF + if (mux_output_used) { + DBG(); + return false; // Memory and SRLs only valid in SLICEMs + } + mux_output_used = true; + } + if (out_fmux_cell != nullptr) { + auto out_fmux = get_tags(out_fmux_cell); + NetInfo *f7f8 = out_fmux->mux.out; + if (f7f8 != nullptr && (f7f8->users.entries() > 1 || ((ff1 == nullptr || f7f8 != ff1->ff.d)))) { + if (mux_output_used) { + DBG(); + return false; // Memory and SRLs only valid in SLICEMs + } + mux_output_used = true; + } + } + if (ff2 != nullptr) { + if (mux_output_used) { + DBG(); + return false; // Memory and SRLs only valid in SLICEMs + } + mux_output_used = true; + } + + lts.eights[i].valid = true; + } else if (!lts.eights[i].valid) { + DBG(); + return false; + } + } + // Check half-tiles + for (int i = 0; i < 2; i++) { + if (lts.halfs[i].dirty) { + lts.halfs[i].valid = false; + bool found_ff[2] = {false, false}; + if (i == 0 && wclk == nullptr) { + // Need to check wclk too + for (int z = 4 * i; z < 4 * (i + 1); z++) { + for (int k = 0; k < 2; k++) { + auto lut = get_tags(lts.cells[z << 4 | (BEL_6LUT + k)]); + if (!lut) + continue; + if (!lut->lut.is_memory && !lut->lut.is_srl) + continue; + if (lut->lut.wclk != nullptr) { + wclk = lut->lut.wclk; + break; + } + } + } + } + NetInfo *clk = nullptr, *sr = nullptr, *ce = nullptr; + bool clkinv = false, srinv = false, islatch = false, ffsync = false; + for (int z = 4 * i; z < 4 * (i + 1); z++) { + for (int k = 0; k < 2; k++) { + auto ff = get_tags(lts.cells[z << 4 | (BEL_FF + k)]); + if (ff == nullptr) + continue; + if (ff->ff.is_latch && k == 1) { + DBG(); + return false; + } + if (found_ff[0] || found_ff[1]) { + if (ff->ff.clk != clk) { + DBG(); + return false; + } + if (ff->ff.sr != sr) { + DBG(); + return false; + } + if (ff->ff.ce != ce) { + DBG(); + return false; + } + if (ff->ff.is_clkinv != clkinv) { + DBG(); + return false; + } + if (ff->ff.is_srinv != srinv) { + DBG(); + return false; + } + if (ff->ff.is_latch != islatch) { + DBG(); + return false; + } + if (ff->ff.ffsync != ffsync) { + DBG(); + return false; + } + } else { + clk = ff->ff.clk; + if (i == 0 && wclk != nullptr && clk != wclk) { + DBG(); + return false; + } + sr = ff->ff.sr; + ce = ff->ff.ce; + clkinv = ff->ff.is_clkinv; + srinv = ff->ff.is_srinv; + islatch = ff->ff.is_latch; + ffsync = ff->ff.ffsync; + } + found_ff[k] = true; + } + } + lts.halfs[i].valid = true; + } else if (!lts.halfs[i].valid) { + DBG(); + return false; + } + } + return true; +} + +bool XilinxImpl::isBelLocationValid(BelId bel, bool explain_invalid) const +{ + if (is_logic_tile(bel)) { + if (!tile_status.at(bel.tile).lts) + return true; + return xc7_logic_tile_valid(bel_tile_type(bel), *tile_status.at(bel.tile).lts); + } else if (is_bram_tile(bel)) { + const auto &bts = tile_status.at(bel.tile).bts; + if (!bts) + return true; + auto onehot = [&](CellInfo *a, CellInfo *b, CellInfo *c) { + return (((a != nullptr) ? 1 : 0) + ((b != nullptr) ? 1 : 0) + ((c != nullptr) ? 1 : 0)) <= 1; + }; + // Only one type of BRAM cell at any given location + if (!onehot(bts->cells[BEL_RAMFIFO36], bts->cells[BEL_RAM36], bts->cells[BEL_FIFO36])) { + DBG(); + return false; + } + if (!onehot(bts->cells[BEL_RAMFIFO18_L], bts->cells[BEL_RAM18_L], bts->cells[BEL_FIFO18_L])) { + DBG(); + return false; + } + // 18-bit BRAMs cannot be used whilst 36-bit is used + if (bts->cells[BEL_RAMFIFO36] || bts->cells[BEL_RAM36] || bts->cells[BEL_FIFO36]) { + for (int i = 4; i < 12; i++) + if (bts->cells[i]) { + DBG(); + return false; + } + } + } + return true; +} + +void XilinxImpl::fixup_placement() +{ + log_info("Running post-placement legalisation...\n"); + for (auto &ts : tile_status) { + if (!ts.lts) + continue; + auto < = *(ts.lts); + for (int z = 0; z < 8; z++) { + // Fixup LUT connectivity - applies whenever a LUT5 is used + CellInfo *lut5 = lt.cells[z << 4 | BEL_5LUT]; + if (!lut5) + continue; + auto l5_tags = get_tags(lut5); + dict> lut5Inputs, lut6Inputs; + for (int i = 0; i < l5_tags->lut.input_count; i++) + if (l5_tags->lut.input_sigs[i]) + lut5Inputs[l5_tags->lut.input_sigs[i]->name].push_back(i); + CellInfo *lut6 = lt.cells[z << 4 | BEL_6LUT]; + if (lut6) { + auto l6_tags = get_tags(lut6); + for (int i = 0; i < l6_tags->lut.input_count; i++) + if (l6_tags->lut.input_sigs[i]) + lut6Inputs[l6_tags->lut.input_sigs[i]->name].push_back(i); + } + if (l5_tags->lut.is_memory || l5_tags->lut.is_srl) { + if (lut6) { + if (!lut6->ports.count(id_A6)) { + lut6->addInput(id_A6); + } + lut6->connectPort(id_A6, ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get()); + } + continue; + } + std::set uniqueInputs; + for (auto i5 : lut5Inputs) + uniqueInputs.insert(i5.first); + for (auto i6 : lut6Inputs) + uniqueInputs.insert(i6.first); + // Disconnect LUT inputs, and re-connect them to not overlap + IdString ports[6] = {id_A1, id_A2, id_A3, id_A4, id_A5, id_A6}; + for (auto p : ports) { + lut5->disconnectPort(p); + lut5->attrs.erase(ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx))); + if (lut6) { + lut6->attrs.erase(ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx))); + lut6->disconnectPort(p); + } + } + int index = 0; + for (auto i : uniqueInputs) { + if (lut5Inputs.count(i)) { + if (!lut5->ports.count(ports[index])) { + lut5->ports[ports[index]].name = ports[index]; + lut5->ports[ports[index]].type = PORT_IN; + } + lut5->connectPort(ports[index], ctx->nets.at(i).get()); + lut5->attrs[ctx->idf("X_ORIG_PORT_%s", ports[index].c_str(ctx))] = std::string(""); + bool first = true; + for (auto inp : lut5Inputs[i]) { + lut5->attrs[ctx->idf("X_ORIG_PORT_%s", ports[index].c_str(ctx))].str += + (first ? "I" : " I") + std::to_string(inp); + first = false; + } + } + if (lut6 && lut6Inputs.count(i)) { + if (!lut6->ports.count(ports[index])) { + lut6->ports[ports[index]].name = ports[index]; + lut6->ports[ports[index]].type = PORT_IN; + } + lut6->connectPort(ports[index], ctx->nets.at(i).get()); + + lut6->attrs[ctx->idf("X_ORIG_PORT_%s", ports[index].c_str(ctx))] = std::string(""); + bool first = true; + for (auto inp : lut6Inputs[i]) { + lut6->attrs[ctx->idf("X_ORIG_PORT_%s", ports[index].c_str(ctx))].str += + (first ? "I" : " I") + std::to_string(inp); + first = false; + } + } + ++index; + } + lut5->renamePort(id_O6, id_O5); + lut5->attrs.erase(id_X_ORIG_PORT_O6); + lut5->attrs[id_X_ORIG_PORT_O5] = std::string("O"); + + if (lut6) { + if (!lut6->ports.count(id_A6)) { + lut6->addInput(id_A6); + } + lut6->connectPort(id_A6, ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get()); + } + } + } + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id_PS7_PS7) { + log_info("Tieing unused PS7 inputs to constants...\n"); + for (IdString pname : ctx->getBelPins(ci->bel)) { + if (ci->ports.count(pname) && ci->ports.at(pname).net != nullptr && + ci->ports.at(pname).net->driver.cell != nullptr) + continue; + if (ctx->getBelPinType(ci->bel, pname) != PORT_IN) + continue; + std::string name = pname.str(ctx); + if (name.find("_PAD_") != std::string::npos) + continue; + if (boost::starts_with(name, "TEST") || boost::starts_with(name, "DEBUGSELECT") || + boost::starts_with(name, "MIO") || boost::starts_with(name, "DDR")) + continue; + bool constval = false; + ci->ports[pname].name = pname; + ci->ports[pname].type = PORT_IN; + if (ci->ports[pname].net != nullptr) { + ci->disconnectPort(pname); + ci->attrs.erase(ctx->idf("X_ORIG_PORT_", name.c_str())); + } + ci->connectPort(pname, + ctx->nets.at(constval ? ctx->id("$PACKER_VCC_NET") : ctx->id("$PACKER_GND_NET")).get()); + } + } + } +} + +void XilinxImpl::fixup_routing() +{ + log_info("Running post-routing legalisation...\n"); + /* + * Convert LUT permutation into correct physical connections (i.e. effectively eliminating the permutation pips), + * then specifying the permutation as a new physical-to-logical mapping using X_ORIG_PORT. This keeps RapidWright + * and Vivado happy, preserving the original logical netlist + */ + dict> used_perm_pips; // tile -> [extra_data] for LUT perm pips + + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + for (auto &wire : ni->wires) { + PipId pip = wire.second.pip; + if (pip == PipId()) + continue; + auto &pd = chip_pip_info(ctx->chip_info, pip); + if (pd.flags != PIP_LUT_PERMUTATION) + continue; + const auto &extra_data = *reinterpret_cast(pd.extra_data.get()); + used_perm_pips[pip.tile].push_back(extra_data.pip_config); + } + } + + for (size_t ti = 0; ti < tile_status.size(); ti++) { + if (!used_perm_pips.count(int(ti))) + continue; + auto &ts = tile_status.at(ti); + if (!ts.lts) + continue; + + auto < = *ts.lts; + for (int z = 0; z < 8; z++) { + CellInfo *lut5 = lt.cells[z << 4 | BEL_5LUT]; + CellInfo *lut6 = lt.cells[z << 4 | BEL_6LUT]; + if (lut5 == nullptr && lut6 == nullptr) + continue; + auto &pp = used_perm_pips.at(ti); + // from -> to + dict> new_connections; + IdString ports[6] = {id_A1, id_A2, id_A3, id_A4, id_A5, id_A6}; + for (auto pip : pp) { + if (((pip >> 8) & 0xF) != z) + continue; + new_connections[ports[(pip >> 4) & 0xF]].push_back(ports[pip & 0xF]); + } + dict orig_nets; + dict orig_ports_l6, orig_ports_l5; + for (int i = 0; i < 6; i++) { + NetInfo *l6net = lut6 ? lut6->getPort(ports[i]) : nullptr; + NetInfo *l5net = lut5 ? lut5->getPort(ports[i]) : nullptr; + orig_nets[ports[i]] = (l6net ? l6net : l5net); + if (lut6) + orig_ports_l6[ports[i]] = + str_or_default(lut6->attrs, ctx->idf("X_ORIG_PORT_%s", ports[i].c_str(ctx))); + if (lut5) + orig_ports_l5[ports[i]] = + str_or_default(lut5->attrs, ctx->idf("X_ORIG_PORT_%s", ports[i].c_str(ctx))); + } + for (auto &nc : new_connections) { + if (lut6) + lut6->disconnectPort(nc.first); + if (lut5) + lut5->disconnectPort(nc.first); + for (auto &dst : nc.second) { + if (lut6) + lut6->disconnectPort(dst); + if (lut5) + lut5->disconnectPort(dst); + } + } + for (int i = 0; i < 6; i++) { + if (lut6) + lut6->attrs.erase(ctx->idf("X_ORIG_PORT_%s", ports[i].c_str(ctx))); + if (lut5) + lut5->attrs.erase(ctx->idf("X_ORIG_PORT_%s", ports[i].c_str(ctx))); + } + for (int i = 0; i < 6; i++) { + auto p = ports[i]; + if (!new_connections.count(p) || new_connections.at(p).empty()) + continue; + if (lut6) { + if (!lut6->ports.count(p)) { + lut6->addInput(p); + } + lut6->connectPort(p, orig_nets[new_connections.at(p).front()]); + lut6->attrs[ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx))] = std::string(""); + auto &orig_attr = lut6->attrs[ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx))].str; + bool first = true; + for (auto &nc : new_connections.at(p)) { + orig_attr += orig_ports_l6[nc] + (first ? "" : " "); + first = false; + } + if (orig_attr.empty()) + lut6->attrs.erase(ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx))); + } + if (lut5) { + if (!lut5->ports.count(p)) { + lut5->addInput(p); + } + lut5->connectPort(p, orig_nets[new_connections.at(p).front()]); + lut5->attrs[ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx))] = std::string(""); + auto &orig_attr = lut5->attrs[ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx))].str; + bool first = true; + for (auto &nc : new_connections.at(p)) { + orig_attr += orig_ports_l5[nc] + (first ? "" : " "); + first = false; + } + if (orig_attr.empty()) + lut5->attrs.erase(ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx))); + } + } + } + } + + /* + * Legalise route through OSERDESE3s + */ + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id_OSERDESE3) { + if (ci->getPort(id_T_OUT) != nullptr) + continue; + ci->params[id_OSERDES_T_BYPASS] = std::string("TRUE"); + } + } +} + +NEXTPNR_NAMESPACE_END