himbaechel: Adding a xilinx uarch for xc7 with prjxray
Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
parent
a32ad13a86
commit
5bfe0dd1b1
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
37
himbaechel/uarch/xilinx/CMakeLists.txt
Normal file
37
himbaechel/uarch/xilinx/CMakeLists.txt
Normal 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)
|
197
himbaechel/uarch/xilinx/cells.cc
Normal file
197
himbaechel/uarch/xilinx/cells.cc
Normal 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
|
740
himbaechel/uarch/xilinx/constids.inc
Normal file
740
himbaechel/uarch/xilinx/constids.inc
Normal 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)
|
6
himbaechel/uarch/xilinx/examples/.gitignore
vendored
Normal file
6
himbaechel/uarch/xilinx/examples/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
*.bin
|
||||||
|
*.json
|
||||||
|
*.log
|
||||||
|
*.frames
|
||||||
|
*.fasm
|
||||||
|
abc.history
|
52
himbaechel/uarch/xilinx/examples/arty-a35/arty.xdc
Normal file
52
himbaechel/uarch/xilinx/examples/arty-a35/arty.xdc
Normal 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]
|
5
himbaechel/uarch/xilinx/examples/arty-a35/blinky.sh
Executable file
5
himbaechel/uarch/xilinx/examples/arty-a35/blinky.sh
Executable 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
|
21
himbaechel/uarch/xilinx/examples/arty-a35/blinky.v
Normal file
21
himbaechel/uarch/xilinx/examples/arty-a35/blinky.v
Normal 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
|
34
himbaechel/uarch/xilinx/examples/bitgen_xray.sh
Executable file
34
himbaechel/uarch/xilinx/examples/bitgen_xray.sh
Executable 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"
|
107
himbaechel/uarch/xilinx/extra_data.h
Normal file
107
himbaechel/uarch/xilinx/extra_data.h
Normal 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
|
1664
himbaechel/uarch/xilinx/fasm.cc
Normal file
1664
himbaechel/uarch/xilinx/fasm.cc
Normal file
File diff suppressed because it is too large
Load Diff
106
himbaechel/uarch/xilinx/gen/filters.py
Normal file
106
himbaechel/uarch/xilinx/gen/filters.py
Normal 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
|
134
himbaechel/uarch/xilinx/gen/parse_sdf.py
Normal file
134
himbaechel/uarch/xilinx/gen/parse_sdf.py
Normal 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
|
38
himbaechel/uarch/xilinx/gen/tileconn.py
Normal file
38
himbaechel/uarch/xilinx/gen/tileconn.py
Normal 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())
|
544
himbaechel/uarch/xilinx/gen/xilinx_device.py
Normal file
544
himbaechel/uarch/xilinx/gen/xilinx_device.py
Normal 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:])
|
371
himbaechel/uarch/xilinx/gen/xilinx_gen.py
Normal file
371
himbaechel/uarch/xilinx/gen/xilinx_gen.py
Normal 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()
|
1
himbaechel/uarch/xilinx/meta
Submodule
1
himbaechel/uarch/xilinx/meta
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 57de9216639b0670949664cfdc61b2679064eb7b
|
750
himbaechel/uarch/xilinx/pack.cc
Normal file
750
himbaechel/uarch/xilinx/pack.cc
Normal 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 ¶m : ci->params)
|
||||||
|
if (rule.param_xform.count(param.first))
|
||||||
|
xform_params.push_back(param.first);
|
||||||
|
for (auto param : xform_params)
|
||||||
|
ci->params[rule.param_xform.at(param)] = ci->params[param];
|
||||||
|
|
||||||
|
for (auto &attr : rule.set_attrs)
|
||||||
|
ci->attrs[attr.first] = attr.second;
|
||||||
|
|
||||||
|
for (auto ¶m : rule.set_params)
|
||||||
|
ci->params[param.first] = param.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XilinxPacker::generic_xform(const dict<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
|
218
himbaechel/uarch/xilinx/pack.h
Normal file
218
himbaechel/uarch/xilinx/pack.h
Normal 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
|
405
himbaechel/uarch/xilinx/pack_carry.cc
Normal file
405
himbaechel/uarch/xilinx/pack_carry.cc
Normal 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
|
350
himbaechel/uarch/xilinx/pack_clocking.cc
Normal file
350
himbaechel/uarch/xilinx/pack_clocking.cc
Normal 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
|
477
himbaechel/uarch/xilinx/pack_dram.cc
Normal file
477
himbaechel/uarch/xilinx/pack_dram.cc
Normal 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
|
497
himbaechel/uarch/xilinx/pack_io.cc
Normal file
497
himbaechel/uarch/xilinx/pack_io.cc
Normal 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
|
480
himbaechel/uarch/xilinx/pins.cc
Normal file
480
himbaechel/uarch/xilinx/pins.cc
Normal 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
|
34
himbaechel/uarch/xilinx/pins.h
Normal file
34
himbaechel/uarch/xilinx/pins.h
Normal 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
|
200
himbaechel/uarch/xilinx/xdc.cc
Normal file
200
himbaechel/uarch/xilinx/xdc.cc
Normal 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
|
557
himbaechel/uarch/xilinx/xilinx.cc
Normal file
557
himbaechel/uarch/xilinx/xilinx.cc
Normal 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 <s = tile_status[dst.tile].lts;
|
||||||
|
if (lts && (lts->cells[BEL_5LUT] != nullptr || lts->cells[BEL_6LUT] != nullptr))
|
||||||
|
return true; // Ground driver only available if lowest 5LUT and 6LUT not used
|
||||||
|
}
|
||||||
|
} else if (pip_type == PIP_CONST_DRIVER) {
|
||||||
|
WireId dst = ctx->getPipDstWire(pip);
|
||||||
|
const auto <s = tile_status[dst.tile].lts;
|
||||||
|
if (lts && (lts->cells[BEL_5LUT] != nullptr || lts->cells[BEL_6LUT] != nullptr))
|
||||||
|
return true; // Ground driver only available if lowest 5LUT and 6LUT not used
|
||||||
|
} else if (pip_type == PIP_SITE_INTERNAL) {
|
||||||
|
if (extra_data.bel_name == ID_TRIBUF)
|
||||||
|
return true;
|
||||||
|
auto site = BelSiteKey::unpack(extra_data.site_key);
|
||||||
|
// Check site variant of PIP matches configured site variant of tile
|
||||||
|
if (site.site >= 0 && site.site < int(tile_status[pip.tile].site_variant.size())) {
|
||||||
|
if (site.site_variant > 0 && site.site_variant != tile_status[pip.tile].site_variant.at(site.site))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (pip_type == PIP_LUT_PERMUTATION) {
|
||||||
|
const auto <s = tile_status[pip.tile].lts;
|
||||||
|
if (!lts)
|
||||||
|
return false;
|
||||||
|
int eight = (extra_data.pip_config >> 8) & 0xF;
|
||||||
|
|
||||||
|
if (((extra_data.pip_config >> 4) & 0xF) == (extra_data.pip_config & 0xF))
|
||||||
|
return false; // from==to, always valid
|
||||||
|
|
||||||
|
auto lut6 = get_tags(lts->cells[(eight << 4) | BEL_6LUT]);
|
||||||
|
if (lut6 && (lut6->lut.is_memory || lut6->lut.is_srl))
|
||||||
|
return true;
|
||||||
|
auto lut5 = get_tags(lts->cells[(eight << 4) | BEL_5LUT]);
|
||||||
|
if (lut5 && (lut5->lut.is_memory || lut5->lut.is_srl))
|
||||||
|
return true;
|
||||||
|
} else if (pip_type == PIP_LUT_ROUTETHRU) {
|
||||||
|
int eight = (extra_data.pip_config >> 8) & 0xF;
|
||||||
|
int dest = (extra_data.pip_config & 0x1);
|
||||||
|
if (eight == 0)
|
||||||
|
return true; // FIXME: conflict with ground
|
||||||
|
if (dest & 0x1)
|
||||||
|
return true; // FIXME: routethru to MUX
|
||||||
|
const auto <s = tile_status[pip.tile].lts;
|
||||||
|
if (!lts)
|
||||||
|
return false;
|
||||||
|
const CellInfo *lut6 = lts->cells[(eight << 4) | BEL_6LUT];
|
||||||
|
if (lut6)
|
||||||
|
return true;
|
||||||
|
const CellInfo *lut5 = lts->cells[(eight << 4) | BEL_5LUT];
|
||||||
|
if (lut5)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XilinxImpl::prePlace() { assign_cell_tags(); }
|
||||||
|
|
||||||
|
void XilinxImpl::postPlace()
|
||||||
|
{
|
||||||
|
fixup_placement();
|
||||||
|
ctx->assignArchInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XilinxImpl::configurePlacerHeap(PlacerHeapCfg &cfg)
|
||||||
|
{
|
||||||
|
cfg.hpwl_scale_x = 2;
|
||||||
|
cfg.hpwl_scale_y = 1;
|
||||||
|
cfg.beta = 0.5;
|
||||||
|
cfg.placeAllAtOnce = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XilinxImpl::preRoute()
|
||||||
|
{
|
||||||
|
find_source_sink_locs();
|
||||||
|
route_clocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XilinxImpl::postRoute()
|
||||||
|
{
|
||||||
|
fixup_routing();
|
||||||
|
ctx->assignArchInfo();
|
||||||
|
const ArchArgs &args = ctx->args;
|
||||||
|
if (args.options.count("fasm")) {
|
||||||
|
write_fasm(args.options.at("fasm"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IdString XilinxImpl::bel_tile_type(BelId bel) const
|
||||||
|
{
|
||||||
|
return IdString(chip_tile_info(ctx->chip_info, bel.tile).type_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XilinxImpl::is_logic_tile(BelId bel) const
|
||||||
|
{
|
||||||
|
return bel_tile_type(bel).in(id_CLEL_L, id_CLEL_R, id_CLEM, id_CLEM_R, id_CLBLL_L, id_CLBLL_R, id_CLBLM_L,
|
||||||
|
id_CLBLM_R);
|
||||||
|
}
|
||||||
|
bool XilinxImpl::is_bram_tile(BelId bel) const { return bel_tile_type(bel).in(id_BRAM, id_BRAM_L, id_BRAM_R); }
|
||||||
|
|
||||||
|
const XlnxTileInstExtraDataPOD *XilinxImpl::tile_extra_data(int tile) const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<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
|
180
himbaechel/uarch/xilinx/xilinx.h
Normal file
180
himbaechel/uarch/xilinx/xilinx.h
Normal 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 <s) const;
|
||||||
|
|
||||||
|
// Pips
|
||||||
|
bool is_pip_unavail(PipId pip) const;
|
||||||
|
bool checkPipAvail(PipId pip) const override { return !is_pip_unavail(pip); }
|
||||||
|
bool checkPipAvailForNet(PipId pip, const NetInfo *net) const override { return !is_pip_unavail(pip); }
|
||||||
|
|
||||||
|
// Flow management
|
||||||
|
void parse_xdc(const std::string &filename);
|
||||||
|
void pack() override;
|
||||||
|
void prePlace() override;
|
||||||
|
void preRoute() override;
|
||||||
|
void postPlace() override;
|
||||||
|
void postRoute() override;
|
||||||
|
void write_fasm(const std::string &filename);
|
||||||
|
|
||||||
|
void configurePlacerHeap(PlacerHeapCfg &cfg) override;
|
||||||
|
|
||||||
|
void fixup_placement();
|
||||||
|
void fixup_routing();
|
||||||
|
void route_clocks();
|
||||||
|
|
||||||
|
// Misc utility functions
|
||||||
|
const XlnxTileInstExtraDataPOD *tile_extra_data(int tile) const;
|
||||||
|
IdString bel_tile_type(BelId bel) const;
|
||||||
|
bool is_logic_tile(BelId bel) const;
|
||||||
|
bool is_bram_tile(BelId bel) const;
|
||||||
|
|
||||||
|
SiteIndex get_bel_site(BelId bel) const;
|
||||||
|
Loc rel_site_loc(SiteIndex site) const;
|
||||||
|
IdString get_site_name(SiteIndex site) const;
|
||||||
|
IdString bel_name_in_site(BelId bel) const;
|
||||||
|
IdStringList get_site_bel_name(BelId bel) const;
|
||||||
|
BelId get_site_bel(SiteIndex site, IdString bel_name) const;
|
||||||
|
|
||||||
|
int hclk_for_iob(BelId pad) const;
|
||||||
|
int hclk_for_ioi(int tile) const;
|
||||||
|
|
||||||
|
std::string tile_name(int tile) const;
|
||||||
|
|
||||||
|
std::vector<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
|
640
himbaechel/uarch/xilinx/xilinx_place.cc
Normal file
640
himbaechel/uarch/xilinx/xilinx_place.cc
Normal 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 <s) const
|
||||||
|
{
|
||||||
|
bool is_slicem = (tile_type == id_CLBLM_L) || (tile_type == id_CLBLM_R);
|
||||||
|
bool tile_is_memory = false;
|
||||||
|
if (lts.cells[(3 << 4) | BEL_6LUT] != nullptr && get_tags(lts.cells[(3 << 4) | BEL_6LUT])->lut.is_memory)
|
||||||
|
tile_is_memory = true;
|
||||||
|
bool small_memory = false;
|
||||||
|
if (lts.cells[(3 << 4) | BEL_5LUT] != nullptr && get_tags(lts.cells[(3 << 4) | BEL_5LUT])->lut.is_memory)
|
||||||
|
small_memory = true;
|
||||||
|
NetInfo *wclk = nullptr;
|
||||||
|
// Check eight-tiles (mostly LUT-related validity)
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if (lts.eights[i].dirty) {
|
||||||
|
lts.eights[i].dirty = false;
|
||||||
|
lts.eights[i].valid = false;
|
||||||
|
|
||||||
|
auto lut6 = get_tags(lts.cells[(i << 4) | BEL_6LUT]);
|
||||||
|
auto lut5 = get_tags(lts.cells[(i << 4) | BEL_5LUT]);
|
||||||
|
|
||||||
|
// Check 6LUT
|
||||||
|
if (lut6) {
|
||||||
|
if (!is_slicem && (lut6->lut.is_memory || lut6->lut.is_srl)) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
} // Memory and SRLs only valid in SLICEMs
|
||||||
|
if (lut6->lut.is_srl && (i >= 4)) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (lut6->lut.is_memory || lut6->lut.is_srl) {
|
||||||
|
if (wclk == nullptr)
|
||||||
|
wclk = lut6->lut.wclk;
|
||||||
|
else if (lut6->lut.wclk != wclk) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lut5) {
|
||||||
|
// Can't mix memory and non-memory
|
||||||
|
if (lut6->lut.is_memory != lut5->lut.is_memory || lut6->lut.is_srl != lut5->lut.is_srl) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// If all 6 inputs or 2 outputs are used, 5LUT can't also be present
|
||||||
|
if (lut6->lut.input_count == 6 || lut6->lut.output_count == 2) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// If more than 5 total inputs are used, need to check number of shared input
|
||||||
|
if ((lut6->lut.input_count + lut5->lut.input_count) > 5) {
|
||||||
|
int shared = 0, need_shared = (lut6->lut.input_count + lut5->lut.input_count - 5);
|
||||||
|
for (int j = 0; j < lut6->lut.input_count; j++) {
|
||||||
|
for (int k = 0; k < lut5->lut.input_count; k++) {
|
||||||
|
if (lut6->lut.input_sigs[j] == lut5->lut.input_sigs[k])
|
||||||
|
shared++;
|
||||||
|
if (shared >= need_shared)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shared < need_shared) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lut5 != nullptr) {
|
||||||
|
if (!is_slicem && (lut5->lut.is_memory || lut5->lut.is_srl)) {
|
||||||
|
DBG();
|
||||||
|
return false; // Memory and SRLs only valid in SLICEMs
|
||||||
|
}
|
||||||
|
if (lut5->lut.is_srl) {
|
||||||
|
if (wclk == nullptr)
|
||||||
|
wclk = lut5->lut.wclk;
|
||||||
|
else if (lut5->lut.wclk != wclk) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 5LUT can use at most 5 inputs and 1 output
|
||||||
|
if (lut5->lut.input_count > 5 || lut5->lut.output_count == 2) {
|
||||||
|
DBG();
|
||||||
|
return false; // Memory and SRLs only valid in SLICEMs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check (over)usage ofX inputs
|
||||||
|
NetInfo *x_net = nullptr;
|
||||||
|
if (lut6) {
|
||||||
|
x_net = lut6->lut.di2_net;
|
||||||
|
}
|
||||||
|
|
||||||
|
CellInfo *mux_cell = nullptr;
|
||||||
|
// Eights A, C, E, G: F7MUX uses X input
|
||||||
|
if (i == 0 || i == 2 || i == 4 || i == 6)
|
||||||
|
mux_cell = lts.cells[i << 4 | BEL_F7MUX];
|
||||||
|
// Eights B, F: F8MUX uses X input
|
||||||
|
if (i == 1 || i == 5)
|
||||||
|
mux_cell = lts.cells[(i - 1) << 4 | BEL_F8MUX];
|
||||||
|
auto mux = get_tags(mux_cell);
|
||||||
|
if (mux) {
|
||||||
|
if (x_net)
|
||||||
|
x_net = mux->mux.sel;
|
||||||
|
else if (x_net != mux->mux.sel) {
|
||||||
|
DBG();
|
||||||
|
return false; // Memory and SRLs only valid in SLICEMs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CellInfo *out_fmux_cell = nullptr;
|
||||||
|
// Subslices A, C: F7MUX connects to F7F8 out
|
||||||
|
if (i == 0 || i == 2 || i == 4 || i == 6)
|
||||||
|
out_fmux_cell = lts.cells[(i << 4) | BEL_F7MUX];
|
||||||
|
// Subslices B: F8MUX connects to F7F8 out
|
||||||
|
if (i == 1 || i == 5)
|
||||||
|
out_fmux_cell = lts.cells[(i - 1) << 4 | BEL_F8MUX];
|
||||||
|
|
||||||
|
auto carry4 = get_tags(lts.cells[((i / 4) << 6) | BEL_CARRY4]);
|
||||||
|
|
||||||
|
if (carry4 != nullptr && carry4->carry.x_sigs[i % 4] != nullptr) {
|
||||||
|
if (x_net == nullptr)
|
||||||
|
x_net = carry4->carry.x_sigs[i % 4];
|
||||||
|
else if (x_net != carry4->carry.x_sigs[i % 4]) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FF1 might use X, if it isn't driven directly
|
||||||
|
auto ff1 = get_tags(lts.cells[i << 4 | BEL_FF]);
|
||||||
|
if (ff1 != nullptr && ff1->ff.d != nullptr && ff1->ff.d->driver.cell != nullptr) {
|
||||||
|
auto &drv = ff1->ff.d->driver;
|
||||||
|
if ((drv.cell == lts.cells[(i << 4) | BEL_6LUT] && drv.port != id_MC31) ||
|
||||||
|
drv.cell == lts.cells[(i << 4) | BEL_5LUT] || drv.cell == out_fmux_cell) {
|
||||||
|
// Direct, OK
|
||||||
|
} else {
|
||||||
|
// Indirect, must use X input
|
||||||
|
if (x_net == nullptr)
|
||||||
|
x_net = ff1->ff.d;
|
||||||
|
else if (x_net != ff1->ff.d) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FF2 might use X, if it isn't driven directly
|
||||||
|
auto ff2 = get_tags(lts.cells[i << 4 | BEL_FF2]);
|
||||||
|
if (ff2 != nullptr && ff2->ff.d != nullptr && ff2->ff.d->driver.cell != nullptr) {
|
||||||
|
auto &drv = ff2->ff.d->driver;
|
||||||
|
if (drv.cell == lts.cells[(i << 4) | BEL_5LUT]) {
|
||||||
|
// Direct, OK
|
||||||
|
} else {
|
||||||
|
// Indirect, must use X input
|
||||||
|
if (x_net == nullptr)
|
||||||
|
x_net = ff2->ff.d;
|
||||||
|
else if (x_net != ff2->ff.d) {
|
||||||
|
#ifdef DEBUG_VALIDITY
|
||||||
|
log_info("%s %s %s %s %s\n", nameOf(lut6), nameOf(ff1), nameOf(lut5), nameOf(ff2),
|
||||||
|
nameOf(drv.cell));
|
||||||
|
#endif
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// collision with top address bits
|
||||||
|
if (tile_is_memory && !small_memory) {
|
||||||
|
auto top_lut = get_tags(lts.cells[(3 << 4) | BEL_6LUT]);
|
||||||
|
if (top_lut) {
|
||||||
|
if ((i == 2) && x_net != top_lut->lut.address_msb[0]) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((i == 1) && x_net != top_lut->lut.address_msb[1]) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mux_output_used = false;
|
||||||
|
NetInfo *out5 = nullptr;
|
||||||
|
if (lut6 != nullptr && lut6->lut.output_count == 2)
|
||||||
|
out5 = lut6->lut.output_sigs[1];
|
||||||
|
else if (lut5 != nullptr && !lut5->lut.only_drives_carry)
|
||||||
|
out5 = lut5->lut.output_sigs[0];
|
||||||
|
if (out5 != nullptr && (out5->users.entries() > 1 ||
|
||||||
|
((ff1 == nullptr || out5 != ff1->ff.d) && (ff2 == nullptr || out5 != ff2->ff.d)))) {
|
||||||
|
mux_output_used = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (carry4 != nullptr && carry4->carry.out_sigs[i % 4] != nullptr) {
|
||||||
|
// FIXME: direct connections to FF
|
||||||
|
if (mux_output_used) {
|
||||||
|
DBG();
|
||||||
|
return false; // Memory and SRLs only valid in SLICEMs
|
||||||
|
}
|
||||||
|
mux_output_used = true;
|
||||||
|
}
|
||||||
|
if (out_fmux_cell != nullptr) {
|
||||||
|
auto out_fmux = get_tags(out_fmux_cell);
|
||||||
|
NetInfo *f7f8 = out_fmux->mux.out;
|
||||||
|
if (f7f8 != nullptr && (f7f8->users.entries() > 1 || ((ff1 == nullptr || f7f8 != ff1->ff.d)))) {
|
||||||
|
if (mux_output_used) {
|
||||||
|
DBG();
|
||||||
|
return false; // Memory and SRLs only valid in SLICEMs
|
||||||
|
}
|
||||||
|
mux_output_used = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ff2 != nullptr) {
|
||||||
|
if (mux_output_used) {
|
||||||
|
DBG();
|
||||||
|
return false; // Memory and SRLs only valid in SLICEMs
|
||||||
|
}
|
||||||
|
mux_output_used = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
lts.eights[i].valid = true;
|
||||||
|
} else if (!lts.eights[i].valid) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check half-tiles
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
if (lts.halfs[i].dirty) {
|
||||||
|
lts.halfs[i].valid = false;
|
||||||
|
bool found_ff[2] = {false, false};
|
||||||
|
if (i == 0 && wclk == nullptr) {
|
||||||
|
// Need to check wclk too
|
||||||
|
for (int z = 4 * i; z < 4 * (i + 1); z++) {
|
||||||
|
for (int k = 0; k < 2; k++) {
|
||||||
|
auto lut = get_tags(lts.cells[z << 4 | (BEL_6LUT + k)]);
|
||||||
|
if (!lut)
|
||||||
|
continue;
|
||||||
|
if (!lut->lut.is_memory && !lut->lut.is_srl)
|
||||||
|
continue;
|
||||||
|
if (lut->lut.wclk != nullptr) {
|
||||||
|
wclk = lut->lut.wclk;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NetInfo *clk = nullptr, *sr = nullptr, *ce = nullptr;
|
||||||
|
bool clkinv = false, srinv = false, islatch = false, ffsync = false;
|
||||||
|
for (int z = 4 * i; z < 4 * (i + 1); z++) {
|
||||||
|
for (int k = 0; k < 2; k++) {
|
||||||
|
auto ff = get_tags(lts.cells[z << 4 | (BEL_FF + k)]);
|
||||||
|
if (ff == nullptr)
|
||||||
|
continue;
|
||||||
|
if (ff->ff.is_latch && k == 1) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (found_ff[0] || found_ff[1]) {
|
||||||
|
if (ff->ff.clk != clk) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ff->ff.sr != sr) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ff->ff.ce != ce) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ff->ff.is_clkinv != clkinv) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ff->ff.is_srinv != srinv) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ff->ff.is_latch != islatch) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ff->ff.ffsync != ffsync) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clk = ff->ff.clk;
|
||||||
|
if (i == 0 && wclk != nullptr && clk != wclk) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sr = ff->ff.sr;
|
||||||
|
ce = ff->ff.ce;
|
||||||
|
clkinv = ff->ff.is_clkinv;
|
||||||
|
srinv = ff->ff.is_srinv;
|
||||||
|
islatch = ff->ff.is_latch;
|
||||||
|
ffsync = ff->ff.ffsync;
|
||||||
|
}
|
||||||
|
found_ff[k] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lts.halfs[i].valid = true;
|
||||||
|
} else if (!lts.halfs[i].valid) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XilinxImpl::isBelLocationValid(BelId bel, bool explain_invalid) const
|
||||||
|
{
|
||||||
|
if (is_logic_tile(bel)) {
|
||||||
|
if (!tile_status.at(bel.tile).lts)
|
||||||
|
return true;
|
||||||
|
return xc7_logic_tile_valid(bel_tile_type(bel), *tile_status.at(bel.tile).lts);
|
||||||
|
} else if (is_bram_tile(bel)) {
|
||||||
|
const auto &bts = tile_status.at(bel.tile).bts;
|
||||||
|
if (!bts)
|
||||||
|
return true;
|
||||||
|
auto onehot = [&](CellInfo *a, CellInfo *b, CellInfo *c) {
|
||||||
|
return (((a != nullptr) ? 1 : 0) + ((b != nullptr) ? 1 : 0) + ((c != nullptr) ? 1 : 0)) <= 1;
|
||||||
|
};
|
||||||
|
// Only one type of BRAM cell at any given location
|
||||||
|
if (!onehot(bts->cells[BEL_RAMFIFO36], bts->cells[BEL_RAM36], bts->cells[BEL_FIFO36])) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!onehot(bts->cells[BEL_RAMFIFO18_L], bts->cells[BEL_RAM18_L], bts->cells[BEL_FIFO18_L])) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 18-bit BRAMs cannot be used whilst 36-bit is used
|
||||||
|
if (bts->cells[BEL_RAMFIFO36] || bts->cells[BEL_RAM36] || bts->cells[BEL_FIFO36]) {
|
||||||
|
for (int i = 4; i < 12; i++)
|
||||||
|
if (bts->cells[i]) {
|
||||||
|
DBG();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XilinxImpl::fixup_placement()
|
||||||
|
{
|
||||||
|
log_info("Running post-placement legalisation...\n");
|
||||||
|
for (auto &ts : tile_status) {
|
||||||
|
if (!ts.lts)
|
||||||
|
continue;
|
||||||
|
auto < = *(ts.lts);
|
||||||
|
for (int z = 0; z < 8; z++) {
|
||||||
|
// Fixup LUT connectivity - applies whenever a LUT5 is used
|
||||||
|
CellInfo *lut5 = lt.cells[z << 4 | BEL_5LUT];
|
||||||
|
if (!lut5)
|
||||||
|
continue;
|
||||||
|
auto l5_tags = get_tags(lut5);
|
||||||
|
dict<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 < = *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
|
Loading…
Reference in New Issue
Block a user