himbaechel: Adding a xilinx uarch for xc7 with prjxray

Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
gatecat 2023-10-16 14:06:41 +02:00 committed by myrtle
parent a32ad13a86
commit 5bfe0dd1b1
33 changed files with 8868 additions and 6 deletions

3
.gitmodules vendored
View File

@ -4,3 +4,6 @@
[submodule "fpga-interchange-schema"] [submodule "fpga-interchange-schema"]
path = 3rdparty/fpga-interchange-schema path = 3rdparty/fpga-interchange-schema
url = https://github.com/SymbiFlow/fpga-interchange-schema.git 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

View File

@ -318,7 +318,7 @@ const std::string Arch::defaultPlacer = "heap";
const std::vector<std::string> Arch::availablePlacers = {"sa", "heap"}; const std::vector<std::string> Arch::availablePlacers = {"sa", "heap"};
const std::string Arch::defaultRouter = "router1"; const std::string Arch::defaultRouter = "router2";
const std::vector<std::string> Arch::availableRouters = {"router1", "router2"}; const std::vector<std::string> Arch::availableRouters = {"router1", "router2"};
void Arch::set_fast_pip_delays(bool fast_mode) void Arch::set_fast_pip_delays(bool fast_mode)

View File

@ -546,6 +546,15 @@ struct Arch : BaseArch<ArchRanges>
// Scale delay (fF * mOhm -> ps) // Scale delay (fF * mOhm -> ps)
delay_t total_delay = (input_res * input_cap) / uint64_t(1e6); delay_t total_delay = (input_res * input_cap) / uint64_t(1e6);
total_delay += pip_tmg->int_delay.slow_max; 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); return DelayQuad(total_delay);
} else { } else {
// Pip with no specified delay. Return a notional value so the router still has something to work with. // Pip with no specified delay. Return a notional value so the router still has something to work with.

View File

@ -1,4 +1,4 @@
set(HIMBAECHEL_UARCHES "example;gowin") set(HIMBAECHEL_UARCHES "example;gowin;xilinx")
foreach(uarch ${HIMBAECHEL_UARCHES}) foreach(uarch ${HIMBAECHEL_UARCHES})
add_subdirectory(${family}/uarch/${uarch}) add_subdirectory(${family}/uarch/${uarch})
aux_source_directory(${family}/uarch/${uarch} HM_UARCH_FILES) aux_source_directory(${family}/uarch/${uarch} HM_UARCH_FILES)

View File

@ -301,6 +301,7 @@ class NodeShape(BBAStruct):
def key(self): def key(self):
m = hashlib.md5() m = hashlib.md5()
m.update(struct.pack("h"*len(self.wires), *self.wires)) m.update(struct.pack("h"*len(self.wires), *self.wires))
m.update(struct.pack("i", self.timing_index))
return m.digest() return m.digest()
def serialise_lists(self, context: str, bba: BBAWriter): def serialise_lists(self, context: str, bba: BBAWriter):
@ -644,7 +645,11 @@ class TimingPool(BBAStruct):
if idx >= len(sg.pip_classes): if idx >= len(sg.pip_classes):
sg.pip_classes += [None for i in range(1 + 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" 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, def set_bel_pin_class(self, grade: str, name: str, delay: TimingValue,
in_cap: Optional[TimingValue]=None, out_res: Optional[TimingValue]=None): in_cap: Optional[TimingValue]=None, out_res: Optional[TimingValue]=None):
@ -658,7 +663,7 @@ class TimingPool(BBAStruct):
if idx >= len(sg.node_classes): if idx >= len(sg.node_classes):
sg.node_classes += [None for i in range(1 + 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" 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): def add_cell_variant(self, speed_grade: str, name: str):
cell = CellTiming(self.strs, name) cell = CellTiming(self.strs, name)
@ -700,13 +705,13 @@ class Chip:
def set_speed_grades(self, speed_grades: list): def set_speed_grades(self, speed_grades: list):
self.timing.set_speed_grades(speed_grades) self.timing.set_speed_grades(speed_grades)
return self.timing 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) # 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 # all the tile wires must exist, and the tile types must be set, first
x0 = wires[0].x x0 = wires[0].x
y0 = wires[0].y y0 = wires[0].y
# compute node shape # compute node shape
shape = NodeShape() shape = NodeShape(timing_index=self.timing.node_class_idx(timing_class))
for w in wires: for w in wires:
if isinstance(w.wire, int): if isinstance(w.wire, int):
wire_index = w.wire wire_index = w.wire

View File

@ -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)

View File

@ -0,0 +1,197 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2023 Myrtle Shah <gatecat@ds0.me>
*
* 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<NetInfo *> &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

View File

@ -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)

View File

@ -0,0 +1,6 @@
*.bin
*.json
*.log
*.frames
*.fasm
abc.history

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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 <device> <design.fasm> <design.bit>"
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"

View File

@ -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<int32_t> variants;
});
NPNR_PACKED_STRUCT(struct XlnxTileInstExtraDataPOD {
int32_t name_prefix;
int16_t tile_x, tile_y;
RelSlice<SiteInstPOD> 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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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())

View File

@ -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:])

View File

@ -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()

@ -0,0 +1 @@
Subproject commit 57de9216639b0670949664cfdc61b2679064eb7b

View File

@ -0,0 +1,750 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2023 Myrtle Shah <gatecat@ds0.me>
*
* 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 <algorithm>
#include <boost/optional.hpp>
#include <iterator>
#include <queue>
#include <unordered_set>
#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<IdString, XFormRule> &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<IdString> 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<IdString> xform_params;
for (auto &param : 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 &param : rule.set_params)
ci->params[param.first] = param.second;
}
void XilinxPacker::generic_xform(const dict<IdString, XFormRule> &rules, bool print_summary)
{
std::map<std::string, int> cell_count;
std::map<std::string, int> 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<PortRef> &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<PortRef> &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<IdString, XFormRule> 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<IdString, XFormRule> 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<CellInfo *> &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<CellInfo *> 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<IdString, XFormRule> 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<IdString, XFormRule> 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<IdString> dead_nets;
std::vector<std::tuple<CellInfo *, IdString, bool>> 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<NetInfo> 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<IdString, XFormRule> 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<std::pair<IdString, std::vector<std::string>>> 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<IdString, XFormRule> 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

View File

@ -0,0 +1,218 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2019-23 Myrtle Shah <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <algorithm>
#include <boost/optional.hpp>
#include <iterator>
#include <queue>
#include <unordered_set>
#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<IdString, IdString> port_xform;
dict<IdString, std::vector<IdString>> port_multixform;
dict<IdString, IdString> param_xform;
std::vector<std::pair<IdString, std::string>> set_attrs;
std::vector<std::pair<IdString, Property>> set_params;
};
// Distributed RAM control set
struct DRAMControlSet
{
std::vector<NetInfo *> 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<CellInfo *> muxcys;
std::vector<CellInfo *> xorcys;
};
pool<IdString> packed_cells;
// General helper functions
void flush_cells();
void xform_cell(const dict<IdString, XFormRule> &rules, CellInfo *ci);
void generic_xform(const dict<IdString, XFormRule> &rules, bool print_summary = false);
CellInfo *feed_through_lut(NetInfo *net, const std::vector<PortRef> &feed_users);
CellInfo *feed_through_muxf(NetInfo *net, IdString type, const std::vector<PortRef> &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<CellInfo *> &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<NetInfo *> &data,
const std::vector<NetInfo *> &select, NetInfo *out, int zoffset);
void pack_srls();
void split_carry4s();
// DistRAM
dict<IdString, XFormRule> dram_rules, dram32_6_rules, dram32_5_rules;
CellInfo *create_dram_lut(const std::string &name, CellInfo *base, const DRAMControlSet &ctrlset,
std::vector<NetInfo *> address, NetInfo *di, NetInfo *dout, int z);
CellInfo *create_dram32_lut(const std::string &name, CellInfo *base, const DRAMControlSet &ctrlset,
std::vector<NetInfo *> address, NetInfo *di, NetInfo *dout, bool o5, int z);
void pack_dram();
// Constant pins
dict<IdString, dict<IdString, bool>> tied_pins;
dict<IdString, pool<IdString>> invertible_pins;
void pack_constants();
// IO
dict<IdString, pool<IdString>> 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<CellInfo *, PortRef> 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<NetInfo *> &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<NetInfo *, 2> &i, NetInfo *o);
void decompose_iob(CellInfo *xil_iob, bool is_hr, const std::string &iostandard);
void pack_io();
// IOLOGIC
dict<IdString, XFormRule> 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

View File

@ -0,0 +1,405 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2019-2023 Myrtle Shah <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <algorithm>
#include <boost/optional.hpp>
#include <iterator>
#include <queue>
#include <unordered_set>
#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<CellInfo *> 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<CellInfo *> 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<IdString> processed_muxcys;
std::vector<CarryGroup> 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<IdString> lut_types{id_LUT1, id_LUT2, id_LUT3, id_LUT4, id_LUT5};
pool<IdString> folded_nets;
for (auto &grp : groups) {
std::vector<CellInfo *> 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<IdString> 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<IdString, XFormRule> 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<IdString, XFormRule> 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

View File

@ -0,0 +1,350 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2019-2023 Myrtle Shah <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <algorithm>
#include <boost/optional.hpp>
#include <iterator>
#include <queue>
#include <unordered_set>
#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<WireId> visited;
std::queue<WireId> 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<IdString, IdString> 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<IdString, XFormRule> 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<IdString, XFormRule> 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<WireId> visit;
dict<WireId, PipId> 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<WireId> 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<WireId> 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

View File

@ -0,0 +1,477 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2023 Myrtle Shah <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <algorithm>
#include <boost/optional.hpp>
#include <iterator>
#include <queue>
#include <unordered_set>
#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<NetInfo *> 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<NetInfo *> 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<NetInfo *> &data,
const std::vector<NetInfo *> &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<std::vector<NetInfo *>> 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<DRAMControlSet, std::vector<CellInfo *>> dram_groups;
dict<IdString, DRAMType> 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<NetInfo *> address(cs.wa.begin(), cs.wa.begin() + std::min<size_t>(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<NetInfo *> address(cs.wa.begin(),
cs.wa.begin() + std::min<size_t>(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<NetInfo *> 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<NetInfo *> address(cs.wa.begin(), cs.wa.begin() + std::min<size_t>(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<NetInfo *> address(cs.wa.begin(),
cs.wa.begin() + std::min<size_t>(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<NetInfo *> 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<NetInfo *> 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<NetInfo *> addressw_64(cs.wa.begin(), cs.wa.begin() + std::min<size_t>(cs.wa.size(), 6));
// Upper bits of address - feed decode muxes
std::vector<NetInfo *> addressw_high(cs.wa.begin() + std::min<size_t>(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<NetInfo *> 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<NetInfo *> 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

View File

@ -0,0 +1,497 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2019-23 Myrtle Shah <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <queue>
#include <unordered_set>
#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<NetInfo *, 2> &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<CellInfo *, PortRef> 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<CellInfo *, PortRef> result;
std::unique_ptr<CellInfo> pad_cell = std::make_unique<CellInfo>(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<CellInfo *> 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<std::pair<CellInfo *, PortRef>> pad_and_buf;
for (auto ci : npnr_io) {
pad_and_buf.push_back(insert_pad_and_buf(ci));
}
flush_cells();
pool<BelId> 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<IdString, XFormRule> 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<IdString, XFormRule> 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<IdString, XFormRule> 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

View File

@ -0,0 +1,480 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2023 Myrtle Shah <gatecat@ds0.me>
*
* 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 <set>
#include "nextpnr.h"
#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc"
#include "himbaechel_constids.h"
NEXTPNR_NAMESPACE_BEGIN
void get_invertible_pins(Context *ctx, dict<IdString, pool<IdString>> &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<IdString, dict<IdString, bool>> &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<std::pair<IdString, std::vector<std::string>>> &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<std::string> 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<std::string>{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<IdString, pool<IdString>> &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

View File

@ -0,0 +1,34 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2023 Myrtle Shah <gatecat@ds0.me>
*
* 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<IdString, pool<IdString>> &invertible_pins);
void get_tied_pins(Context *ctx, dict<IdString, dict<IdString, bool>> &tied_pins);
void get_bram36_ul_pins(Context *ctx, std::vector<std::pair<IdString, std::vector<std::string>>> &ul_pins);
void get_top_level_pins(Context *ctx, dict<IdString, pool<IdString>> &toplevel_pins);
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,200 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2019-2023 gatecat <gatecat@ds0.me>
*
* 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 <boost/algorithm/string.hpp>
#include <fstream>
#include <regex>
#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<std::string> 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<CellInfo *> 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<NetInfo *> 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<std::string> arguments = split_to_args(line, true);
if (arguments.empty())
continue;
std::string &cmd = arguments.front();
if (cmd == "set_property") {
std::vector<std::pair<std::string, std::string>> 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<std::string> 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<CellInfo *> 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<NetInfo *> dest = get_nets(arguments.at(cursor));
for (auto n : dest) {
n->clkconstr = std::unique_ptr<ClockConstraint>(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

View File

@ -0,0 +1,557 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2023 gatecat <gatecat@ds0.me>
*
* 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 <boost/algorithm/string.hpp>
#include <queue>
#include <regex>
#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<const XlnxBelExtraDataPOD *>(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<const XlnxBelExtraDataPOD *>(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<LogicTileStatus>();
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<const XlnxPipExtraDataPOD *>(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 &lts = 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 &lts = 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 &lts = 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 &lts = 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<const XlnxTileInstExtraDataPOD *>(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<WireId> visit;
dict<WireId, WireId> 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<WireId> visit;
dict<WireId, WireId> 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<HimbaechelAPI> create(const std::string &device, const dict<std::string, std::string> &args)
{
return std::make_unique<XilinxImpl>();
}
} xilinxArch;
} // namespace
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,180 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2023 gatecat <gatecat@ds0.me>
*
* 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<LogicTileStatus> lts;
std::unique_ptr<BRAMTileStatus> bts;
std::vector<int> 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 &lts) 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<XilinxCellTags> cell_tags;
const XilinxCellTags *get_tags(const CellInfo *cell) const
{
return cell ? &cell_tags.at(cell->flat_index) : nullptr;
}
std::vector<TileStatus> tile_status;
// Improved delay predictions where sites are located far from their associated interconnect
dict<WireId, Loc> 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

View File

@ -0,0 +1,640 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2019-2023 gatecat <gatecat@ds0.me>
*
* 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 <boost/algorithm/string.hpp>
#include <regex>
#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 &lts) 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 &lt = *(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<IdString, std::vector<int>> 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<IdString> 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<int, std::vector<int>> 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<const XlnxPipExtraDataPOD *>(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 &lt = *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<IdString, std::vector<IdString>> 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<IdString, NetInfo *> orig_nets;
dict<IdString, std::string> 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