Compare commits

...

53 Commits

Author SHA1 Message Date
Miodrag Milanovic
2830340870 Use extra tile information from chip database 2025-01-23 13:02:32 +01:00
Miodrag Milanovic
75d684d032 Use pass trough signals to validate architecture data 2025-01-22 16:02:55 +01:00
Miodrag Milanovic
e2b3e7e86f Display few more primitives 2025-01-21 11:47:31 +01:00
Miodrag Milanovic
dbcc9b734f Add USR_RSTN support 2025-01-20 08:41:36 +01:00
Miodrag Milanovic
5c142fc257 Add some timings 2025-01-17 11:12:17 +01:00
Miodrag Milanovic
6ee0098cf9 Add basic PLL support 2025-01-17 09:53:27 +01:00
Miodrag Milanovic
b3658c47e0 cleanup 2025-01-16 11:36:18 +01:00
Miodrag Milanovic
fb9471aced init DFF only when needed 2025-01-16 10:28:41 +01:00
Miodrag Milanovic
1adda174de DFF input can be constant 2025-01-15 17:02:51 +01:00
Miodrag Milanovic
a2e5ded5a6 cleanup 2025-01-15 15:10:10 +01:00
Miodrag Milanovic
86e947e9d2 Allow inversion for muxes 2025-01-15 13:01:54 +01:00
Miodrag Milanovic
9e2208adbe Handle muxes with constant inputs 2025-01-15 12:39:47 +01:00
Miodrag Milanovic
3b1b549aa3 Add forgotten virtual port renames 2025-01-14 17:01:10 +01:00
Miodrag Milanovic
1eac0528ab Few more DFF features 2025-01-14 16:45:48 +01:00
Miodrag Milanovic
3e6e0273dd Simplify inversion of special signals 2025-01-14 15:17:18 +01:00
Miodrag Milanovic
ea6cbf9804 Create reproducible chip database 2025-01-14 15:16:19 +01:00
Miodrag Milanovic
cb1f01f3a6 Some CC_DFF improvements 2025-01-14 14:21:50 +01:00
Lofty
7d84aefb7f debug print if route found with wrong polarity 2025-01-13 16:20:06 +00:00
Miodrag Milanovic
fdb2dd4c42 BUFG support 2025-01-13 13:16:58 +01:00
Miodrag Milanovic
2a41124378 Fix script 2025-01-10 09:16:23 +01:00
Miodrag Milanovic
e6450a179b Added MX2 and MX4 support 2025-01-09 14:27:21 +01:00
Miodrag Milanovic
94071078dc Add clock inversion pip 2025-01-08 15:02:56 +01:00
Lofty
005bffab48 Constrain routes to have correct inversion state 2025-01-08 13:46:02 +00:00
Miodrag Milanovic
1fc8809e18 Add CPE input inverters 2025-01-08 14:08:41 +01:00
Miodrag Milanovic
91a88dda77 Fix CC_L2T5 pack 2025-01-08 14:07:46 +01:00
Miodrag Milanovic
0987d5a2b9 Start work on BUFG support 2025-01-06 14:15:05 +01:00
Miodrag Milanovic
ed6f6a4d98 Use pin connection aliases 2025-01-03 15:06:52 +01:00
Miodrag Milanovic
5fb63c6a0c Update due to API changes 2024-12-27 14:34:12 +01:00
Miodrag Milanovic
6cc8c2ee54 Use device wrapper class 2024-12-27 10:04:18 +01:00
Miodrag Milanovic
f92728b826 Cleanup 2024-12-26 09:34:24 +01:00
Miodrag Milanovic
150428fe7b Prevent pass trough issues 2024-12-25 16:39:01 +01:00
Miodrag Milanovic
5f85167f8c Fix DFF pack 2024-12-25 15:48:09 +01:00
Miodrag Milanovic
4badd8bbbf Handle MUX flags 2024-12-25 14:36:33 +01:00
Miodrag Milanovic
d41f13bde9 pack DFF fixes 2024-12-25 08:54:54 +01:00
Miodrag Milanovic
63beb45950 Naive pack CC_DFF 2024-12-24 09:29:48 +01:00
Miodrag Milanovic
d09c901f67 Naive lut tree CPE pack 2024-12-23 09:37:23 +01:00
Miodrag Milanovic
cb990e3731 Keep just used connections for now 2024-12-21 12:24:26 +01:00
Miodrag Milanovic
f027bc3cf9 Cleanup 2024-12-21 12:15:03 +01:00
Miodrag Milanovic
81a1f02dcc Add LVDS support 2024-12-20 19:53:29 +01:00
Miodrag Milanovic
ab29eab513 More parameter checks 2024-12-20 13:48:02 +01:00
Miodrag Milanovic
b020a47bb8 Cleanup 2024-12-20 12:03:53 +01:00
Miodrag Milanovic
2f537d2ccc Fixed typo 2024-12-20 11:09:12 +01:00
Miodrag Milanovic
86d93849c8 Add IN1->RAM_O2 propagation 2024-12-20 11:03:10 +01:00
Miodrag Milanovic
757ed10047 GPIO initial work 2024-12-20 09:38:14 +01:00
Miodrag Milanovic
2b038faeb1 Fix script 2024-12-18 11:46:38 +01:00
Miodrag Milanovic
de2d551035 Start adding infrastructure for reading bitstream 2024-12-18 10:44:25 +01:00
Miodrag Milanovic
7f98c33d6b Add support for reading from config 2024-12-18 10:30:14 +01:00
Miodrag Milanovic
19f5d24b79 Restructure code 2024-12-18 08:56:54 +01:00
Miodrag Milanovic
503fafd574 Propagate errors 2024-12-17 10:52:19 +01:00
Miodrag Milanovic
8436191307 Use CCF to set IO location 2024-12-16 19:21:32 +01:00
Miodrag Milanovic
0add621416 Add CCF parsing 2024-12-16 16:58:46 +01:00
Miodrag Milanovic
4d46e57d12 Initial work on forming bitstream 2024-12-16 11:19:56 +01:00
Miodrag Milanovic
6cf832e9b0 Initial code for GateMate 2024-12-10 15:48:07 +01:00
20 changed files with 2463 additions and 53 deletions

View File

@ -454,6 +454,9 @@ template <typename R> struct BaseArch : ArchAPI<R>
// Flow methods
virtual void assignArchInfo() override {};
// HACK
virtual bool isPipInverting(PipId pip) const { return false; }
// --------------------------------------------------------------
// These structures are used to provide default implementations of bel/wire/pip binding. Arches might want to
// replace them with their own, for example to use faster access structures than dict. Arches might also

View File

@ -94,9 +94,9 @@ struct Router2
// The notional location of the wire, to guarantee thread safety
int16_t x = 0, y = 0;
// Visit data
PipId pip_fwd, pip_bwd;
bool visited_fwd = false, visited_bwd = false;
float cost_fwd = 0.0, cost_bwd = 0.0;
PipId pip_fwd[2], pip_bwd[2];
bool visited_fwd[2] = {false, false}, visited_bwd[2] = {false, false};
float cost_fwd[2] = {0.0, 0.0}, cost_bwd[2] = {0.0, 0.0};
};
Context *ctx;
@ -240,11 +240,12 @@ struct Router2
struct QueuedWire
{
explicit QueuedWire(int wire = -1, WireScore score = WireScore{}, int randtag = 0)
: wire(wire), score(score), randtag(randtag) {};
explicit QueuedWire(int wire = -1, WireScore score = WireScore{}, bool inverted = false, int randtag = 0)
: wire(wire), score(score), inverted(inverted), randtag(randtag) {};
int wire;
WireScore score;
bool inverted = false;
int randtag = 0;
struct Greater
@ -527,12 +528,18 @@ struct Router2
void reset_wires(ThreadContext &t)
{
for (auto w : t.dirty_wires) {
flat_wires[w].pip_fwd = PipId();
flat_wires[w].pip_bwd = PipId();
flat_wires[w].visited_fwd = false;
flat_wires[w].visited_bwd = false;
flat_wires[w].cost_fwd = 0.0;
flat_wires[w].cost_bwd = 0.0;
flat_wires[w].pip_fwd[0] = PipId();
flat_wires[w].pip_fwd[1] = PipId();
flat_wires[w].pip_bwd[0] = PipId();
flat_wires[w].pip_bwd[1] = PipId();
flat_wires[w].visited_fwd[0] = false;
flat_wires[w].visited_fwd[1] = false;
flat_wires[w].visited_bwd[0] = false;
flat_wires[w].visited_bwd[1] = false;
flat_wires[w].cost_fwd[0] = 0.0;
flat_wires[w].cost_fwd[1] = 0.0;
flat_wires[w].cost_bwd[0] = 0.0;
flat_wires[w].cost_bwd[1] = 0.0;
}
t.dirty_wires.clear();
}
@ -563,32 +570,32 @@ struct Router2
}
// Functions for marking wires as visited, and checking if they have already been visited
void set_visited_fwd(ThreadContext &t, int wire, PipId pip, float cost)
void set_visited_fwd(ThreadContext &t, int wire, PipId pip, float cost, bool inverted)
{
auto &wd = flat_wires.at(wire);
if (!wd.visited_fwd && !wd.visited_bwd)
if (!wd.visited_fwd[0] && !wd.visited_fwd[1] && !wd.visited_bwd[0] && !wd.visited_bwd[1])
t.dirty_wires.push_back(wire);
wd.pip_fwd = pip;
wd.visited_fwd = true;
wd.cost_fwd = cost;
wd.pip_fwd[inverted] = pip;
wd.visited_fwd[inverted] = true;
wd.cost_fwd[inverted] = cost;
}
void set_visited_bwd(ThreadContext &t, int wire, PipId pip, float cost)
void set_visited_bwd(ThreadContext &t, int wire, PipId pip, float cost, bool inverted)
{
auto &wd = flat_wires.at(wire);
if (!wd.visited_fwd && !wd.visited_bwd)
if (!wd.visited_fwd[0] && !wd.visited_fwd[1] && !wd.visited_bwd[0] && !wd.visited_bwd[1])
t.dirty_wires.push_back(wire);
wd.pip_bwd = pip;
wd.visited_bwd = true;
wd.cost_bwd = cost;
wd.pip_bwd[inverted] = pip;
wd.visited_bwd[inverted] = true;
wd.cost_bwd[inverted] = cost;
}
bool was_visited_fwd(int wire, float cost)
bool was_visited_fwd(int wire, float cost, bool inverted)
{
return flat_wires.at(wire).visited_fwd && flat_wires.at(wire).cost_fwd <= cost;
return flat_wires.at(wire).visited_fwd[inverted] && flat_wires.at(wire).cost_fwd[inverted] <= cost;
}
bool was_visited_bwd(int wire, float cost)
bool was_visited_bwd(int wire, float cost, bool inverted)
{
return flat_wires.at(wire).visited_bwd && flat_wires.at(wire).cost_bwd <= cost;
return flat_wires.at(wire).visited_bwd[inverted] && flat_wires.at(wire).cost_bwd[inverted] <= cost;
}
float get_arc_crit(NetInfo *net, store_index<PortRef> i)
@ -635,12 +642,13 @@ struct Router2
// We have two modes:
// 0. starting within a small range of existing routing
// 1. expanding from all routing
int mode = 0;
int mode = 1;
if (net->users.entries() < 4 || nd.wires.empty() || (crit > 0.95))
mode = 1;
// This records the point where forwards and backwards routing met
int midpoint_wire = -1;
bool midpoint_inversion = false;
int explored = 1;
for (; mode < 2; mode++) {
@ -666,9 +674,11 @@ struct Router2
int wire_idx = wire_to_idx.at(wire);
base_score.togo_cost = get_togo_cost(net, i, wire_idx, dst_wire, false, crit_weight);
t.fwd_queue.push(QueuedWire(wire_idx, base_score));
set_visited_fwd(t, wire_idx, PipId(), 0.0);
set_visited_fwd(t, wire_idx, PipId(), 0.0, false);
};
#ifndef ARCH_HIMBAECHEL
auto &dst_data = flat_wires.at(dst_wire_idx);
// TODO: does this break gatemate because of not knowing inversion state? (yes)
// Look for nearby existing routing
for (int dy = -cfg.bb_margin_y; dy <= cfg.bb_margin_y; dy++)
for (int dx = -cfg.bb_margin_x; dx <= cfg.bb_margin_x; dx++) {
@ -680,6 +690,7 @@ struct Router2
seed_queue_fwd(wire);
}
}
#endif
if (mode == 0 && t.fwd_queue.size() < 4)
continue;
@ -688,7 +699,7 @@ struct Router2
// Seed forwards with the source wire, if less than 8 existing wires added
seed_queue_fwd(src_wire);
} else {
set_visited_fwd(t, src_wire_idx, PipId(), 0.0);
set_visited_fwd(t, src_wire_idx, PipId(), 0.0, false);
}
}
auto seed_queue_bwd = [&](WireId wire) {
@ -698,7 +709,7 @@ struct Router2
int wire_idx = wire_to_idx.at(wire);
base_score.togo_cost = get_togo_cost(net, i, wire_idx, src_wire, true, crit_weight);
t.bwd_queue.push(QueuedWire(wire_idx, base_score));
set_visited_bwd(t, wire_idx, PipId(), 0.0);
set_visited_bwd(t, wire_idx, PipId(), 0.0, false);
};
// Seed backwards with the dest wire
@ -717,11 +728,15 @@ struct Router2
auto curr = t.fwd_queue.top();
t.fwd_queue.pop();
++explored;
if (was_visited_bwd(curr.wire, std::numeric_limits<float>::max())) {
if (was_visited_bwd(curr.wire, std::numeric_limits<float>::max(), curr.inverted)) {
// Meet in the middle; done
midpoint_wire = curr.wire;
midpoint_inversion = curr.inverted;
break;
}
if (was_visited_bwd(curr.wire, std::numeric_limits<float>::max(), !curr.inverted)) {
ROUTE_LOG_DBG("fwd: met bwd with wrong polarity\n");
}
auto &curr_data = flat_wires.at(curr.wire);
for (PipId dh : ctx->getPipsDownhill(curr_data.w)) {
// Skip pips outside of box in bounding-box mode
@ -736,7 +751,7 @@ struct Router2
next_score.cost = curr.score.cost + score_wire_for_arc(net, i, phys_pin, next, dh, crit_weight);
next_score.togo_cost =
cfg.estimate_weight * get_togo_cost(net, i, next_idx, dst_wire, false, crit_weight);
if (was_visited_fwd(next_idx, next_score.delay)) {
if (was_visited_fwd(next_idx, next_score.delay, /*inverted=*/curr.inverted ^ ctx->isPipInverting(dh))) {
// Don't expand the same node twice.
continue;
}
@ -752,8 +767,8 @@ struct Router2
continue;
if (!thread_test_wire(t, nwd))
continue; // thread safety issue
set_visited_fwd(t, next_idx, dh, next_score.delay);
t.fwd_queue.push(QueuedWire(next_idx, next_score, t.rng.rng()));
set_visited_fwd(t, next_idx, dh, next_score.delay, curr.inverted ^ ctx->isPipInverting(dh));
t.fwd_queue.push(QueuedWire(next_idx, next_score, curr.inverted ^ ctx->isPipInverting(dh), t.rng.rng()));
}
}
if (!t.bwd_queue.empty()) {
@ -762,12 +777,16 @@ struct Router2
t.bwd_queue.pop();
++explored;
auto &curr_data = flat_wires.at(curr.wire);
if (was_visited_fwd(curr.wire, std::numeric_limits<float>::max()) ||
(const_mode && ctx->getWireConstantValue(curr_data.w) == net->constant_value)) {
if (was_visited_fwd(curr.wire, std::numeric_limits<float>::max(), curr.inverted) ||
(const_mode && ctx->getWireConstantValue(curr_data.w) == net->constant_value && !curr.inverted)) {
// Meet in the middle; done
midpoint_wire = curr.wire;
midpoint_inversion = curr.inverted;
break;
}
if (was_visited_fwd(curr.wire, std::numeric_limits<float>::max(), !curr.inverted)) {
ROUTE_LOG_DBG("bwd: met fwd with wrong polarity\n");
}
// Don't allow the same wire to be bound to the same net with a different driving pip
PipId bound_pip;
auto fnd_wire = nd.wires.find(curr_data.w);
@ -790,7 +809,7 @@ struct Router2
? 0
: cfg.estimate_weight * get_togo_cost(net, i, next_idx, src_wire,
true, crit_weight);
if (was_visited_bwd(next_idx, next_score.delay)) {
if (was_visited_bwd(next_idx, next_score.delay, /*inverted=*/curr.inverted ^ ctx->isPipInverting(uh))) {
// Don't expand the same node twice.
continue;
}
@ -802,8 +821,8 @@ struct Router2
continue;
if (!thread_test_wire(t, nwd))
continue; // thread safety issue
set_visited_bwd(t, next_idx, uh, next_score.delay);
t.bwd_queue.push(QueuedWire(next_idx, next_score, t.rng.rng()));
set_visited_bwd(t, next_idx, uh, next_score.delay, curr.inverted ^ ctx->isPipInverting(uh));
t.bwd_queue.push(QueuedWire(next_idx, next_score, curr.inverted ^ ctx->isPipInverting(uh), t.rng.rng()));
}
}
}
@ -812,27 +831,29 @@ struct Router2
}
ArcRouteResult result = ARC_SUCCESS;
if (midpoint_wire != -1) {
ROUTE_LOG_DBG(" Routed (explored %d wires): ", explored);
ROUTE_LOG_DBG(" Routed (explored %d wires):\n", explored);
if (const_mode) {
bind_pip_internal(nd, i, midpoint_wire, PipId());
} else {
int cursor_bwd = midpoint_wire;
while (was_visited_fwd(cursor_bwd, std::numeric_limits<float>::max())) {
PipId pip = flat_wires.at(cursor_bwd).pip_fwd;
int inversion_bwd = midpoint_inversion;
while (was_visited_fwd(cursor_bwd, std::numeric_limits<float>::max(), inversion_bwd)) {
PipId pip = flat_wires.at(cursor_bwd).pip_fwd[inversion_bwd];
if (pip == PipId() && cursor_bwd != src_wire_idx)
break;
bind_pip_internal(nd, i, cursor_bwd, pip);
if (ctx->debug && !is_mt) {
auto &wd = flat_wires.at(cursor_bwd);
ROUTE_LOG_DBG(" fwd wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w),
wd.curr_cong - 1, wd.hist_cong_cost, nd.wires.at(wd.w).second);
ROUTE_LOG_DBG(" fwd wire: %s (curr %d hist %f share %d inverted %d)\n", ctx->nameOfWire(wd.w),
wd.curr_cong - 1, wd.hist_cong_cost, nd.wires.at(wd.w).second, inversion_bwd);
}
if (pip == PipId()) {
break;
}
ROUTE_LOG_DBG(" fwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x,
ctx->getPipLocation(pip).y);
ROUTE_LOG_DBG(" fwd pip: %s (%d, %d) (inverting %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x,
ctx->getPipLocation(pip).y, ctx->isPipInverting(pip));
cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(pip));
inversion_bwd ^= ctx->isPipInverting(pip);
}
while (cursor_bwd != src_wire_idx) {
@ -857,19 +878,21 @@ struct Router2
}
int cursor_fwd = midpoint_wire;
while (was_visited_bwd(cursor_fwd, std::numeric_limits<float>::max())) {
PipId pip = flat_wires.at(cursor_fwd).pip_bwd;
int inversion_fwd = midpoint_inversion;
while (was_visited_bwd(cursor_fwd, std::numeric_limits<float>::max(), inversion_fwd)) {
PipId pip = flat_wires.at(cursor_fwd).pip_bwd[inversion_fwd];
if (pip == PipId()) {
break;
}
ROUTE_LOG_DBG(" bwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x,
ctx->getPipLocation(pip).y);
ROUTE_LOG_DBG(" bwd pip: %s (%d, %d) (inverting %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x,
ctx->getPipLocation(pip).y, ctx->isPipInverting(pip));
cursor_fwd = wire_to_idx.at(ctx->getPipDstWire(pip));
inversion_fwd ^= ctx->isPipInverting(pip);
bind_pip_internal(nd, i, cursor_fwd, pip);
if (ctx->debug && !is_mt) {
auto &wd = flat_wires.at(cursor_fwd);
ROUTE_LOG_DBG(" bwd wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w),
wd.curr_cong - 1, wd.hist_cong_cost, nd.wires.at(wd.w).second);
ROUTE_LOG_DBG(" bwd wire: %s (curr %d hist %f share %d inverted %d)\n", ctx->nameOfWire(wd.w),
wd.curr_cong - 1, wd.hist_cong_cost, nd.wires.at(wd.w).second, inversion_fwd);
}
}
NPNR_ASSERT(cursor_fwd == dst_wire_idx);

View File

@ -662,6 +662,9 @@ struct Arch : BaseArch<ArchRanges>
uarch->notifyPipChange(pip, nullptr);
BaseArch::unbindPip(pip);
}
bool isPipInverting(PipId pip) const override {
return uarch->isPipInverting(pip);
}
// -------------------------------------------------

View File

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

View File

@ -130,6 +130,9 @@ struct HimbaechelAPI
// For custom placer configuration
virtual void configurePlacerHeap(PlacerHeapCfg &cfg) {};
// HACK
virtual bool isPipInverting(PipId pip) const { return false; }
virtual ~HimbaechelAPI() {};
};

View File

@ -0,0 +1,38 @@
message(STATUS "Configuring Himbaechel-GateMate uarch")
cmake_minimum_required(VERSION 3.5)
project(himbaechel-gatemate-chipdb NONE)
set(HIMBAECHEL_PEPPERCORN_PATH "" CACHE STRING
"Path to a Project Peppercorn database scripts")
set(ALL_HIMBAECHE_GATEMATE_DEVICES CCGM1A1 CCGM1A2 CCGM1A4 CCGM1A9 CCGM1A16 CCGM1A25)
set(HIMBAECHEL_GATEMATE_DEVICES "" CACHE STRING
"Include support for these GateMate devices (available: ${ALL_HIMBAECHE_GATEMATE_DEVICES})")
message(STATUS "Enabled Himbaechel-GateMate devices: ${HIMBAECHEL_GATEMATE_DEVICES}")
set(chipdb_binaries)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/share/himbaechel/gatemate)
foreach(device ${HIMBAECHEL_GATEMATE_DEVICES})
set(device_bba ${CMAKE_BINARY_DIR}/share/himbaechel/gatemate/chipdb-${device}.bba)
set(device_bin ${CMAKE_BINARY_DIR}/share/himbaechel/gatemate/chipdb-${device}.bin)
if("${HIMBAECHEL_PEPPERCORN_PATH}" STREQUAL "")
message(SEND_ERROR "HIMBAECHEL_PEPPERCORN_PATH must be set to a Project Peppercorn checkout")
endif()
add_custom_command(
OUTPUT ${device_bin}
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/gen/arch_gen.py --device ${device} --bba ${device_bba} --lib ${HIMBAECHEL_PEPPERCORN_PATH}/gatemate
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/arch_gen.py
${CMAKE_CURRENT_SOURCE_DIR}/constids.inc
${CMAKE_CURRENT_SOURCE_DIR}/gfxids.inc
VERBATIM)
list(APPEND chipdb_binaries ${device_bin})
endforeach()
add_custom_target(chipdb-himbaechel-gatemate ALL DEPENDS ${chipdb_binaries})
install(DIRECTORY ${CMAKE_BINARY_DIR}/share/himbaechel/gatemate/ DESTINATION share/nextpnr/himbaechel/gatemate
PATTERN "*.bba" EXCLUDE)

View File

@ -0,0 +1,197 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <fstream>
#include <boost/algorithm/string.hpp>
#include "config.h"
#include "gatemate.h"
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#include "himbaechel_constids.h"
NEXTPNR_NAMESPACE_BEGIN
namespace {
struct BitstreamBackend
{
Context *ctx;
GateMateImpl *uarch;
const std::string &device;
std::ostream &out;
BitstreamBackend(Context *ctx, GateMateImpl *uarch, const std::string &device, std::ostream &out)
: ctx(ctx), uarch(uarch), device(device), out(out) {};
std::vector<bool> int_to_bitvector(int val, int size)
{
std::vector<bool> bv;
for (int i = 0; i < size; i++) {
bv.push_back((val & (1 << i)) != 0);
}
return bv;
}
std::vector<bool> str_to_bitvector(std::string str, int size)
{
std::vector<bool> bv;
bv.resize(size, 0);
for (int i = 0; i < int(str.size()); i++) {
char c = str.at((str.size() - i) - 1);
NPNR_ASSERT(c == '0' || c == '1');
bv.at(i) = (c == '1');
}
return bv;
}
CfgLoc getConfigLoc(int tile)
{
auto ti = *uarch->tile_extra_data(tile);
CfgLoc loc;
loc.die = ti.die;
loc.x = ti.bit_x;
loc.y = ti.bit_y;
return loc;
}
void write_bitstream()
{
ChipConfig cc;
cc.chip_name = device;
cc.configs[0].add_word("GPIO.BANK_E1", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_E2", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_N1", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_N2", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_S1", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_S2", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_W1", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_W2", int_to_bitvector(1, 1));
for (auto &cell : ctx->cells) {
CfgLoc loc = getConfigLoc(cell.second.get()->bel.tile);
auto &params = cell.second.get()->params;
switch (cell.second->type.index) {
case id_CC_IBUF.index:
case id_CC_TOBUF.index:
case id_CC_OBUF.index:
case id_CC_IOBUF.index:
case id_CC_LVDS_IBUF.index:
case id_CC_LVDS_TOBUF.index:
case id_CC_LVDS_OBUF.index:
case id_CC_LVDS_IOBUF.index:
for (auto &p : params) {
cc.tiles[loc].add_word(stringf("GPIO.%s", p.first.c_str(ctx)), p.second.as_bits());
}
break;
case id_CPE.index: {
int id = uarch->tile_extra_data(cell.second.get()->bel.tile)->prim_id;
for (auto &p : params) {
cc.tiles[loc].add_word(stringf("CPE%d.%s", id, p.first.c_str(ctx)), p.second.as_bits());
}
} break;
case id_BUFG.index:
{
Loc l = ctx->getBelLocation(cell.second->bel);
cc.configs[0].add_word(stringf("GLBOUT.GLB%d_EN",l.z), int_to_bitvector(1,1));
}
break;
case id_PLL.index:
{
Loc l = ctx->getBelLocation(cell.second->bel);
cc.configs[0].add_word(stringf("PLL%d.CLK_OUT_EN",l.z-4), int_to_bitvector(1,1));
cc.configs[0].add_word(stringf("PLL%d.PLL_EN",l.z-4), int_to_bitvector(1,1));
cc.configs[0].add_word(stringf("PLL%d.PLL_RST",l.z-4), int_to_bitvector(1,1));
for (auto &p : params) {
cc.configs[0].add_word(stringf("PLL%d.%s", l.z-4, p.first.c_str(ctx)), p.second.as_bits());
}
}
break;
case id_USR_RSTN.index:
break;
default:
log_error("Unhandled cell %s of type %s\n", cell.second.get()->name.c_str(ctx),
cell.second->type.c_str(ctx));
}
}
for (auto &net : ctx->nets) {
NetInfo *ni = net.second.get();
if (ni->wires.empty())
continue;
std::set<std::string> nets;
for (auto &w : ni->wires) {
if (w.second.pip != PipId()) {
PipId pip = w.second.pip;
const auto extra_data = *reinterpret_cast<const GateMatePipExtraDataPOD *>(
chip_pip_info(ctx->chip_info, pip).extra_data.get());
if (extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.flags & MUX_VISIBLE)) {
IdString name = IdString(extra_data.name);
CfgLoc loc = getConfigLoc(pip.tile);
std::string word = name.c_str(ctx);
if (extra_data.flags & MUX_CONFIG) {
cc.configs[loc.die].add_word(word, int_to_bitvector(extra_data.value, extra_data.bits));
} else {
int id = uarch->tile_extra_data(pip.tile)->prim_id;
if (boost::starts_with(word, "IM."))
boost::replace_all(word, "IM.", stringf("IM%d.", id));
else if (boost::starts_with(word, "OM."))
boost::replace_all(word, "OM.", stringf("OM%d.", id));
else if (boost::starts_with(word, "CPE."))
boost::replace_all(word, "CPE.", stringf("CPE%d.", id));
else if (boost::starts_with(word, "IOES."))
boost::replace_all(word, "IOES.", stringf("IOES%d.", id));
else if (boost::starts_with(word, "LES."))
boost::replace_all(word, "LES.", stringf("LES%d.", id));
else if (boost::starts_with(word, "BES."))
boost::replace_all(word, "BES.", stringf("BES%d.", id));
else if (boost::starts_with(word, "RES."))
boost::replace_all(word, "RES.", stringf("RES%d.", id));
else if (boost::starts_with(word, "TES."))
boost::replace_all(word, "TES.", stringf("TES%d.", id));
cc.tiles[loc].add_word(word, int_to_bitvector(extra_data.value, extra_data.bits));
}
}
}
}
}
out << cc;
}
};
} // namespace
void GateMateImpl::write_bitstream(const std::string &device, const std::string &filename)
{
std::ofstream out(filename);
if (!out)
log_error("failed to open file %s for writing (%s)\n", filename.c_str(), strerror(errno));
BitstreamBackend be(ctx, this, device, out);
be.write_bitstream();
}
bool GateMateImpl::read_bitstream(const std::string &device, const std::string &filename)
{
std::ifstream in(filename);
if (!in)
log_error("failed to open file %s for reading (%s)\n", filename.c_str(), strerror(errno));
return false;
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,211 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* 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 <boost/lexical_cast.hpp>
#include <fstream>
#include <regex>
#include "extra_data.h"
#include "himbaechel_api.h"
#include "log.h"
#include "nextpnr.h"
#include "util.h"
#include "gatemate.h"
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#include "himbaechel_constids.h"
NEXTPNR_NAMESPACE_BEGIN
struct GateMateCCFReader
{
Context *ctx;
std::istream &in;
int lineno;
dict<IdString, Property> defaults;
GateMateCCFReader(Context *ctx, std::istream &in) : ctx(ctx), in(in) {};
std::string strip_quotes(const std::string &str)
{
if (str.at(0) == '"') {
if (str.back() != '"') {
log_error("expected '\"' at end of string '%s' (on line %d).\n", str.c_str(), lineno);
}
return str.substr(1, str.size() - 2);
} else {
return str;
}
};
void parse_params(std::vector<std::string> &params, bool is_default, dict<IdString, Property> *props)
{
for (auto param : params) {
std::vector<std::string> expr;
boost::split(expr, param, boost::is_any_of("="));
if (expr.size() != 2)
log_error("each parameter must be in form NAME=VALUE (on line %d)\n", lineno);
std::string name = expr.at(0);
boost::algorithm::trim(name);
boost::algorithm::to_upper(name);
std::string value = strip_quotes(expr.at(1));
boost::algorithm::trim(value);
boost::algorithm::to_upper(value);
if (name == "LOC") {
if (is_default)
log_error("Value '%s' can not be defined for default GPIO in line %d.\n", name.c_str(), lineno);
if (ctx->get_package_pin_bel(ctx->id(value)) == BelId())
log_error("Unknown location '%s' used in line %d.\n", value.c_str(), lineno);
props->emplace(id_LOC, Property(value));
} else if (name == "SCHMITT_TRIGGER" || name == "PULLUP" || name == "PULLDOWN" || name == "KEEPER" ||
name == "FF_IBF" || name == "FF_OBF" || name == "LVDS_BOOST" || name == "LVDS_RTERM") {
if (value == "TRUE") {
props->emplace(ctx->id(name.c_str()), Property(Property::State::S1));
} else if (value == "FALSE") {
props->emplace(ctx->id(name.c_str()), Property(Property::State::S0));
} else
log_error("Uknown value '%s' for parameter '%s' in line %d, must be TRUE or FALSE.\n",
value.c_str(), name.c_str(), lineno);
} else if (name == "SLEW") {
if (value == "FAST" || value == "SLOW") {
props->emplace(ctx->id(name.c_str()), Property(value));
} else
log_error("Uknown value '%s' for parameter '%s' in line %d, must be SLOW or FAST.\n", value.c_str(),
name.c_str(), lineno);
} else if (name == "DRIVE") {
try {
int drive = boost::lexical_cast<int>(value.c_str());
if (drive == 3 || drive == 6 || drive == 9 || drive == 12) {
props->emplace(ctx->id(name.c_str()), Property(drive, 2));
} else
log_error("Parameter '%s' must have value 3,6,9 or 12 in line %d.\n", name.c_str(), lineno);
} catch (boost::bad_lexical_cast const &) {
log_error("Parameter '%s' must be number in line %d.\n", name.c_str(), lineno);
}
} else if (name == "DELAY_IBF" || name == "DELAY_OBF") {
try {
int delay = boost::lexical_cast<int>(value.c_str());
if (delay >= 0 && delay <= 15) {
props->emplace(ctx->id(name.c_str()), Property(delay, 4));
} else
log_error("Parameter '%s' must have value from 0 to 15 in line %d.\n", name.c_str(), lineno);
} catch (boost::bad_lexical_cast const &) {
log_error("Parameter '%s' must be number in line %d.\n", name.c_str(), lineno);
}
} else {
log_error("Uknown parameter name '%s' in line %d.\n", name.c_str(), lineno);
}
}
}
void run()
{
log_info("Parsing CCF file..\n");
std::string line;
std::string linebuf;
defaults.clear();
auto isempty = [](const std::string &str) {
return std::all_of(str.begin(), str.end(), [](char c) { return isblank(c) || c == '\r' || c == '\n'; });
};
lineno = 0;
while (std::getline(in, line)) {
++lineno;
// Both // and # are considered start of comment
size_t com_start = line.find("//");
if (com_start != std::string::npos)
line = line.substr(0, com_start);
com_start = line.find('#');
if (com_start != std::string::npos)
line = line.substr(0, com_start);
if (isempty(line))
continue;
linebuf += line;
size_t pos = linebuf.find(';');
// Need to concatenate lines until there is closing ; sign
while (pos != std::string::npos) {
std::string content = linebuf.substr(0, pos);
std::vector<std::string> params;
boost::split(params, content, boost::is_any_of("|"));
std::string command = params.at(0);
std::stringstream ss(command);
std::vector<std::string> words;
std::string tmp;
while (ss >> tmp)
words.push_back(tmp);
std::string type = words.at(0);
boost::algorithm::to_lower(type);
if (type == "default_gpio") {
if (words.size() != 1)
log_error("line with default_GPIO should not contain only parameters (in line %d).\n", lineno);
params.erase(params.begin());
parse_params(params, true, &defaults);
} else if (type == "net" || type == "pin_in" || type == "pin_out" || type == "pin_inout") {
if (words.size() < 3 || words.size() > 5)
log_error("pin definition line not properly formed (in line %d).\n", lineno);
std::string pin_name = strip_quotes(words.at(1));
// put back other words and use them as parameters
std::stringstream ss;
for (size_t i = 2; i < words.size(); i++)
ss << words.at(i);
params[0] = ss.str();
IdString cellname = ctx->id(pin_name);
if (ctx->cells.count(cellname)) {
CellInfo *cell = ctx->cells.at(cellname).get();
for (auto p : defaults)
cell->params[p.first] = p.second;
parse_params(params, false, &cell->params);
} else
log_warning("Pad with name '%s' not found in netlist.\n", pin_name.c_str());
} else {
log_error("unknown type '%s' in line %d.\n", type.c_str(), lineno);
}
linebuf = linebuf.substr(pos + 1);
pos = linebuf.find(';');
}
}
if (!isempty(linebuf))
log_error("unexpected end of CCF file\n");
}
};
void GateMateImpl::parse_ccf(const std::string &filename)
{
std::ifstream in(filename);
if (!in)
log_error("failed to open CCF file '%s'\n", filename.c_str());
GateMateCCFReader reader(ctx, in);
reader.run();
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,246 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "config.h"
#include <boost/range/adaptor/reversed.hpp>
#include <iomanip>
#include <set>
#include "log.h"
NEXTPNR_NAMESPACE_BEGIN
#define fmt(x) (static_cast<const std::ostringstream &>(std::ostringstream() << x).str())
inline std::string to_string(const std::vector<bool> &bv)
{
std::ostringstream os;
for (auto bit : boost::adaptors::reverse(bv))
os << (bit ? '1' : '0');
return os.str();
}
inline std::istream &operator>>(std::istream &in, std::vector<bool> &bv)
{
bv.clear();
std::string s;
in >> s;
for (auto c : boost::adaptors::reverse(s)) {
assert((c == '0') || (c == '1'));
bv.push_back((c == '1'));
}
return in;
}
// Skip whitespace, optionally including newlines
inline void skip_blank(std::istream &in, bool nl = false)
{
int c = in.peek();
while (in && (((c == ' ') || (c == '\t')) || (nl && ((c == '\n') || (c == '\r'))))) {
in.get();
c = in.peek();
}
}
// Return true if end of line (or file)
inline bool skip_check_eol(std::istream &in)
{
skip_blank(in, false);
if (!in)
return false;
int c = in.peek();
// Comments count as end of line
if (c == '#') {
in.get();
c = in.peek();
while (in && c != EOF && c != '\n') {
in.get();
c = in.peek();
}
return true;
}
return (c == EOF || c == '\n');
}
// Skip past blank lines and comments
inline void skip(std::istream &in)
{
skip_blank(in, true);
while (in && (in.peek() == '#')) {
// Skip comment line
skip_check_eol(in);
skip_blank(in, true);
}
}
// Return true if at the end of a record (or file)
inline bool skip_check_eor(std::istream &in)
{
skip(in);
int c = in.peek();
return (c == EOF || c == '.');
}
// Return true if at the end of file
inline bool skip_check_eof(std::istream &in)
{
skip(in);
int c = in.peek();
return (c == EOF);
}
std::ostream &operator<<(std::ostream &out, const ConfigWord &cw)
{
out << cw.name << " " << to_string(cw.value) << std::endl;
return out;
}
std::istream &operator>>(std::istream &in, ConfigWord &cw)
{
in >> cw.name;
in >> cw.value;
return in;
}
std::ostream &operator<<(std::ostream &out, const TileConfig &tc)
{
for (const auto &cword : tc.cwords)
out << cword;
return out;
}
std::istream &operator>>(std::istream &in, TileConfig &tc)
{
tc.cwords.clear();
while (!skip_check_eor(in)) {
ConfigWord w;
in >> w;
tc.cwords.push_back(w);
}
return in;
}
void TileConfig::add_word(const std::string &name, const std::vector<bool> &value) { cwords.push_back({name, value}); }
std::string TileConfig::to_string() const
{
std::stringstream ss;
ss << *this;
return ss.str();
}
TileConfig TileConfig::from_string(const std::string &str)
{
std::stringstream ss(str);
TileConfig tc;
ss >> tc;
return tc;
}
bool TileConfig::empty() const { return cwords.empty(); }
std::ostream &operator<<(std::ostream &out, const ChipConfig &cc)
{
out << ".device " << cc.chip_name << std::endl << std::endl;
for (const auto &config : cc.configs) {
if (!config.second.empty()) {
out << ".config " << config.first << " " << std::endl;
out << config.second;
out << std::endl;
}
}
for (const auto &tile : cc.tiles) {
if (!tile.second.empty()) {
out << ".tile " << tile.first.die << " " << tile.first.x << " " << tile.first.y << std::endl;
out << tile.second;
out << std::endl;
}
}
for (const auto &bram : cc.brams) {
if (!bram.second.empty()) {
out << ".bram " << bram.first.die << " " << bram.first.x << " " << bram.first.y << std::endl;
out << bram.second;
out << std::endl;
}
}
for (const auto &bram : cc.bram_data) {
if (!bram.second.empty()) {
out << ".bram_init " << bram.first.die << " " << bram.first.x << " " << bram.first.y << std::endl;
std::ios_base::fmtflags f(out.flags());
for (size_t i = 0; i < bram.second.size(); i++) {
out << std::setw(2) << std::setfill('0') << std::hex << (int)bram.second.at(i);
if (i % 32 == 31)
out << std::endl;
else
out << " ";
}
out.flags(f);
out << std::endl;
}
}
return out;
}
std::istream &operator>>(std::istream &in, ChipConfig &cc)
{
while (!skip_check_eof(in)) {
std::string verb;
in >> verb;
if (verb == ".device") {
in >> cc.chip_name;
} else if (verb == ".config") {
int die;
in >> die;
TileConfig tc;
in >> tc;
cc.configs.emplace(die, tc);
} else if (verb == ".tile") {
CfgLoc loc;
in >> loc.die;
in >> loc.x;
in >> loc.y;
TileConfig tc;
in >> tc;
cc.tiles.emplace(loc, tc);
} else if (verb == ".bram") {
CfgLoc loc;
in >> loc.die;
in >> loc.x;
in >> loc.y;
TileConfig tc;
in >> tc;
cc.brams.emplace(loc, tc);
} else if (verb == ".bram_init") {
CfgLoc loc;
in >> loc.die;
in >> loc.x;
in >> loc.y;
std::ios_base::fmtflags f(in.flags());
while (!skip_check_eor(in)) {
uint16_t value;
in >> std::hex >> value;
cc.bram_data[loc].push_back(value);
}
in.flags(f);
} else {
log_error("unrecognised config entry %s\n", verb.c_str());
}
}
return in;
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,89 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* 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 GATEMATE_CONFIG_H
#define GATEMATE_CONFIG_H
#include <map>
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
struct ConfigWord
{
std::string name;
std::vector<bool> value;
inline bool operator==(const ConfigWord &other) const { return other.name == name && other.value == value; }
};
std::ostream &operator<<(std::ostream &out, const ConfigWord &cw);
std::istream &operator>>(std::istream &in, ConfigWord &cw);
struct TileConfig
{
std::vector<ConfigWord> cwords;
void add_word(const std::string &name, const std::vector<bool> &value);
std::string to_string() const;
static TileConfig from_string(const std::string &str);
bool empty() const;
};
std::ostream &operator<<(std::ostream &out, const TileConfig &tc);
std::istream &operator>>(std::istream &in, TileConfig &ce);
struct CfgLoc
{
int die;
int x;
int y;
inline bool operator==(const CfgLoc &other) const { return other.die == die && other.x == x && other.y == y; }
inline bool operator!=(const CfgLoc &other) const { return other.die != die || x != other.x || y == other.y; }
inline bool operator<(const CfgLoc &other) const
{
return die < other.die ||
((die == other.die && y < other.y) || (die == other.die && y == other.y && x < other.x));
}
};
class ChipConfig
{
public:
std::string chip_name;
std::string chip_package;
std::map<CfgLoc, TileConfig> tiles;
std::map<CfgLoc, TileConfig> brams;
std::map<int, TileConfig> configs;
// Block RAM initialisation
std::map<CfgLoc, std::vector<uint8_t>> bram_data;
};
std::ostream &operator<<(std::ostream &out, const ChipConfig &cc);
std::istream &operator>>(std::istream &in, ChipConfig &cc);
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,167 @@
X(CPE)
X(CPE_BIG)
X(CPE_SML)
X(RAM_I1)
X(RAM_I2)
X(IN1)
X(IN2)
X(IN3)
X(IN4)
X(IN5)
X(IN6)
X(IN7)
X(IN8)
X(CLK)
X(EN)
X(SR)
X(CINX)
X(PINX)
X(CINY1)
X(PINY1)
X(CINY2)
X(PINY2)
X(OUT1)
X(OUT2)
X(RAM_O1)
X(RAM_O2)
X(COUTX)
X(POUTX)
X(COUTY1)
X(POUTY1)
X(COUTY2)
X(POUTY2)
X(GPIO)
X(CC_IBUF)
X(CC_OBUF)
X(CC_TOBUF)
X(CC_IOBUF)
X(CC_LVDS_IBUF)
X(CC_LVDS_OBUF)
X(CC_LVDS_TOBUF)
X(CC_LVDS_IOBUF)
X(CC_IDDR)
X(CC_ODDR)
X(I)
X(O)
X(IO)
X(DI)
X(DO)
X(OE)
X(Y)
X(A)
X(T)
X(I_P)
X(I_N)
X(O_P)
X(O_N)
X(IO_P)
X(IO_N)
X(OUT3)
X(OUT4)
X(LOC)
X(PIN_NAME)
X(PIN_NAME_P)
X(PIN_NAME_N)
X(V_IO)
X(PULLUP)
X(PULLDOWN)
X(KEEPER)
X(SCHMITT_TRIGGER)
X(DELAY_IBF)
X(FF_IBF)
X(DRIVE)
X(SLEW)
X(DELAY_OBF)
X(FF_OBF)
X(LVDS_RTERM)
X(LVDS_BOOST)
X(OE_ENABLE)
X(OUT_SIGNAL)
X(INPUT_ENABLE)
X(OUT23_14_SEL)
X(LVDS_EN)
X(LVDS_IE)
X(CC_L2T4)
X(CC_L2T5)
X(CC_LUT1)
X(CC_LUT2)
X(I0)
X(I1)
X(I2)
X(I3)
X(I4)
X(INIT)
X(INIT_L00)
X(INIT_L01)
X(INIT_L02)
X(INIT_L03)
X(INIT_L10)
X(INIT_L11)
X(INIT_L20)
X(INIT_L30)
X(O1)
X(O2)
X(CC_DFF)
X(CPE_DFF)
X(Q)
X(D)
X(R)
X(S)
X(FF_INIT)
X(2D_IN)
X(SR_VAL)
X(SR_INV)
X(EN_INV)
X(CLK_INV)
X(EN_SR)
X(CC_BUFG)
X(BUFG)
X(CC_MX2)
X(CC_MX4)
X(FUNCTION)
X(D0)
X(D1)
X(D2)
X(D3)
X(S0)
X(S1)
X(CC_PLL)
X(CC_PLL_ADV)
X(PLL)
X(CLK_REF)
X(USR_CLK_REF)
X(USR_SEL_A_B)
X(CLK_FEEDBACK)
X(USR_LOCKED_STDY_RST)
X(CLK0)
X(CLK90)
X(CLK180)
X(CLK270)
X(CLK_REF_OUT)
X(USR_PLL_LOCKED_STDY)
X(USR_PLL_LOCKED)
X(REF_CLK)
X(OUT_CLK)
X(PERF_MD)
X(LOCK_REQ)
X(CLK270_DOUB)
X(CLK180_DOUB)
X(LOW_JITTER)
X(CI_FILTER_CONST)
X(CP_FILTER_CONST)
X(PLL_CFG_A)
X(PLL_CFG_B)
X(CC_USR_RSTN)
X(USR_RSTN)

View File

@ -0,0 +1,58 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* 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 GATEMATE_EXTRA_DATA_H
#define GATEMATE_EXTRA_DATA_H
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
NPNR_PACKED_STRUCT(struct GateMateTileExtraDataPOD {
uint8_t die;
uint8_t bit_x;
uint8_t bit_y;
uint8_t prim_id;
});
NPNR_PACKED_STRUCT(struct GateMatePipExtraDataPOD {
int32_t name;
uint8_t bits;
uint8_t value;
uint8_t flags;
uint8_t type;
});
enum MuxFlags
{
MUX_INVERT = 1,
MUX_VISIBLE = 2,
MUX_CONFIG = 4,
MUX_CPE_INV = 8,
};
enum PipExtra
{
PIP_EXTRA_MUX = 1,
PIP_EXTRA_CPE = 2,
};
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,288 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* 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 "gatemate.h"
#include "design_utils.h"
#define GEN_INIT_CONSTIDS
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#include "himbaechel_constids.h"
NEXTPNR_NAMESPACE_BEGIN
GateMateImpl::~GateMateImpl() {};
void GateMateImpl::init_database(Arch *arch)
{
const ArchArgs &args = arch->args;
init_uarch_constids(arch);
arch->load_chipdb(stringf("gatemate/chipdb-%s.bin", args.device.c_str()));
arch->set_package("FBGA324");
arch->set_speed_grade("DEFAULT");
}
void GateMateImpl::init(Context *ctx) { HimbaechelAPI::init(ctx); }
delay_t GateMateImpl::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);
return 100 * (std::abs(dx - sx) / 4 + std::abs(dy - sy) / 4 + 2);
}
bool GateMateImpl::isBelLocationValid(BelId bel, bool explain_invalid) const
{
CellInfo *cell = ctx->getBoundBelCell(bel);
if (cell == nullptr) {
return true;
}
if (ctx->getBelType(bel) == id_CPE) {
Loc loc = ctx->getBelLocation(bel);
int x = loc.x - 2;
int y = loc.y - 2;
if (x < 2 || x > 167)
return false;
if (y < 2 || y > 127)
return false;
if (x == 1 && y == 66)
return false;
return true;
}
return true;
}
void updateLUT(Context *ctx, CellInfo *cell, IdString port, IdString init)
{
if (cell->params.count(init) == 0) return;
unsigned init_val = int_or_default(cell->params, init);
WireId pin_wire = ctx->getBelPinWire(cell->bel, port);
for (PipId pip : ctx->getPipsUphill(pin_wire)) {
if (!ctx->getBoundPipNet(pip))
continue;
const auto extra_data = *reinterpret_cast<const GateMatePipExtraDataPOD *>(
chip_pip_info(ctx->chip_info, pip).extra_data.get());
if (!extra_data.name)
continue;
if (extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.flags & MUX_CPE_INV)) {
if (port.in(id_IN1,id_IN3,id_IN5,id_IN7))
init_val = (init_val & 0b1010) >> 1 | (init_val & 0b0101) << 1;
else
init_val = (init_val & 0b0011) << 2 | (init_val & 0b1100) >> 2;
cell->params[init] = Property(init_val, 4);
}
}
}
void updateINV(Context *ctx, CellInfo *cell, IdString port)
{
if (cell->params.count(port) == 0) return;
unsigned init_val = int_or_default(cell->params, port);
WireId pin_wire = ctx->getBelPinWire(cell->bel, port);
for (PipId pip : ctx->getPipsUphill(pin_wire)) {
if (!ctx->getBoundPipNet(pip))
continue;
const auto extra_data = *reinterpret_cast<const GateMatePipExtraDataPOD *>(
chip_pip_info(ctx->chip_info, pip).extra_data.get());
if (!extra_data.name)
continue;
if (extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.flags & MUX_CPE_INV)) {
cell->params[port] = Property(3 - init_val, 2);
}
}
}
void updateSR_INV(Context *ctx, CellInfo *cell, IdString port, IdString param)
{
if (cell->params.count(param) == 0) return;
unsigned init_val = int_or_default(cell->params, param);
WireId pin_wire = ctx->getBelPinWire(cell->bel, port);
for (PipId pip : ctx->getPipsUphill(pin_wire)) {
if (!ctx->getBoundPipNet(pip))
continue;
const auto extra_data = *reinterpret_cast<const GateMatePipExtraDataPOD *>(
chip_pip_info(ctx->chip_info, pip).extra_data.get());
if (!extra_data.name)
continue;
if (extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.flags & MUX_CPE_INV)) {
cell->params[param] = Property(3 - init_val, 2);
}
}
}
void updateMUX_INV(Context *ctx, CellInfo *cell, IdString port, IdString param, int bit)
{
unsigned init_val = int_or_default(cell->params, param);
WireId pin_wire = ctx->getBelPinWire(cell->bel, port);
for (PipId pip : ctx->getPipsUphill(pin_wire)) {
if (!ctx->getBoundPipNet(pip))
continue;
const auto extra_data = *reinterpret_cast<const GateMatePipExtraDataPOD *>(
chip_pip_info(ctx->chip_info, pip).extra_data.get());
if (!extra_data.name)
continue;
if (extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.flags & MUX_CPE_INV)) {
int old = (init_val >> bit) & 1;
int val = (init_val & (~(1 << bit) & 0xf)) | ((!old) << bit);
cell->params[param] = Property(val, 4);
}
}
}
void GateMateImpl::postRoute()
{
ctx->assignArchInfo();
log_break();
log_info("Resources spent on routing:\n");
for (auto &net : ctx->nets) {
NetInfo *ni = net.second.get();
for (auto &w : ni->wires) {
if (w.second.pip != PipId()) {
const auto extra_data = *reinterpret_cast<const GateMatePipExtraDataPOD *>(
chip_pip_info(ctx->chip_info, w.second.pip).extra_data.get());
if (!extra_data.name)
continue;
if (extra_data.type == PipExtra::PIP_EXTRA_CPE) {
IdStringList id = ctx->getPipName(w.second.pip);
BelId bel = ctx->getBelByName(IdStringList::concat(id[0], id_CPE));
if (!ctx->getBoundBelCell(bel)) {
CellInfo *cell = ctx->createCell(ctx->id(ctx->nameOfBel(bel)), id_CPE);
ctx->bindBel(bel, cell, PlaceStrength::STRENGTH_FIXED);
if (IdString(extra_data.name) == id_RAM_O2) {
// Propagate IN1 to O2 and RAM_O2
cell->params[id_INIT_L00] = Property(0b1010, 4);
cell->params[id_INIT_L10] = Property(0b1010, 4);
cell->params[id_O2] = Property(0b11, 2);
cell->params[id_RAM_O2] = Property(1, 1);
} else if (IdString(extra_data.name) == id_RAM_O1) {
// Propagate IN1 to O1 and RAM_O1
cell->params[id_INIT_L00] = Property(0b1010, 4);
cell->params[id_INIT_L10] = Property(0b1010, 4);
cell->params[id_INIT_L20] = Property(0b1010, 4);
cell->params[id_O1] = Property(0b11, 2);
cell->params[id_RAM_O1] = Property(1, 1);
} else if (IdString(extra_data.name) == id_RAM_I1) {
cell->params[id_RAM_I1] = Property(1, 1);
} else if (IdString(extra_data.name) == id_RAM_I2) {
cell->params[id_RAM_I2] = Property(1, 1);
} else {
log_error("Issue adding pass trough signal for %s.\n",IdString(extra_data.name).c_str(ctx));
}
} else
log_error("Issue adding pass trough signal.\n");
}
}
}
}
for (auto &cell : ctx->cells) {
if (cell.second->type == id_CPE) {
// if LUT part used
uint8_t func = int_or_default(cell.second->params, id_FUNCTION, 0);
if (func != 4) {
updateLUT(ctx, cell.second.get(), id_IN1, id_INIT_L00);
updateLUT(ctx, cell.second.get(), id_IN2, id_INIT_L00);
updateLUT(ctx, cell.second.get(), id_IN3, id_INIT_L01);
updateLUT(ctx, cell.second.get(), id_IN4, id_INIT_L01);
} else {
updateMUX_INV(ctx, cell.second.get(), id_IN1, id_INIT_L11, 0);
updateMUX_INV(ctx, cell.second.get(), id_IN2, id_INIT_L11, 1);
updateMUX_INV(ctx, cell.second.get(), id_IN3, id_INIT_L11, 2);
updateMUX_INV(ctx, cell.second.get(), id_IN4, id_INIT_L11, 3);
}
updateLUT(ctx, cell.second.get(), id_IN5, id_INIT_L02);
updateLUT(ctx, cell.second.get(), id_IN6, id_INIT_L02);
updateLUT(ctx, cell.second.get(), id_IN7, id_INIT_L03);
updateLUT(ctx, cell.second.get(), id_IN8, id_INIT_L03);
updateINV(ctx, cell.second.get(), id_CLK);
updateINV(ctx, cell.second.get(), id_EN);
bool set = int_or_default(cell.second->params, id_EN_SR, 0) == 1;
if (set)
updateSR_INV(ctx, cell.second.get(), id_SR, id_S);
else
updateSR_INV(ctx, cell.second.get(), id_SR, id_R);
}
}
print_utilisation(ctx);
const ArchArgs &args = ctx->args;
if (args.options.count("out")) {
write_bitstream(args.device, args.options.at("out"));
}
}
void GateMateImpl::postPlace()
{
ctx->assignArchInfo();
}
void GateMateImpl::setupArchContext()
{
const ArchArgs &args = ctx->args;
if (args.options.count("read")) {
if (!read_bitstream(args.device, args.options.at("read")))
log_error("Loading bitstream failed.\n");
}
}
// Bel bucket functions
IdString GateMateImpl::getBelBucketForCellType(IdString cell_type) const
{
if (cell_type.in(id_CC_IBUF, id_CC_OBUF, id_CC_TOBUF, id_CC_IOBUF, id_CC_LVDS_IBUF, id_CC_LVDS_TOBUF,
id_CC_LVDS_OBUF, id_CC_LVDS_IOBUF))
return id_GPIO;
else
return cell_type;
}
bool GateMateImpl::isValidBelForCellType(IdString cell_type, BelId bel) const
{
IdString bel_type = ctx->getBelType(bel);
if (bel_type == id_GPIO)
return cell_type.in(id_CC_IBUF, id_CC_OBUF, id_CC_TOBUF, id_CC_IOBUF, id_CC_LVDS_IBUF, id_CC_LVDS_TOBUF,
id_CC_LVDS_OBUF, id_CC_LVDS_IOBUF);
else
return (bel_type == cell_type);
}
const GateMateTileExtraDataPOD *GateMateImpl::tile_extra_data(int tile) const
{
return reinterpret_cast<const GateMateTileExtraDataPOD *>(ctx->chip_info->tile_insts[tile].extra_data.get());
}
struct GateMateArch : HimbaechelArch
{
GateMateArch() : HimbaechelArch("gatemate") {};
bool match_device(const std::string &device) override
{
return device.size() > 6 && device.substr(0, 6) == "CCGM1A";
}
std::unique_ptr<HimbaechelAPI> create(const std::string &device, const dict<std::string, std::string> &args)
{
return std::make_unique<GateMateImpl>();
}
} gateMateArch;
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,72 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* 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_GATEMATE_H
#define HIMBAECHEL_GATEMATE_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 GateMateImpl : HimbaechelAPI
{
~GateMateImpl();
void init_database(Arch *arch) override;
void init(Context *ctx) override;
void setupArchContext() override;
void pack() override;
void postPlace() override;
void postRoute() override;
bool isBelLocationValid(BelId bel, bool explain_invalid = false) const override;
delay_t estimateDelay(WireId src, WireId dst) const override;
void drawBel(std::vector<GraphicElement> &g, GraphicElement::style_t style, IdString bel_type, Loc loc) override;
void write_bitstream(const std::string &device, const std::string &filename);
bool read_bitstream(const std::string &device, const std::string &filename);
bool checkPipAvail(PipId pip) const override { return blocked_pips.count(pip) == 0; }
bool checkPipAvailForNet(PipId pip, const NetInfo *net) const override { return checkPipAvail(pip); };
void parse_ccf(const std::string &filename);
IdString getBelBucketForCellType(IdString cell_type) const;
bool isValidBelForCellType(IdString cell_type, BelId bel) const;
bool isPipInverting(PipId pip) const override {
const auto &extra_data = *reinterpret_cast<const GateMatePipExtraDataPOD*>(chip_pip_info(ctx->chip_info, pip).extra_data.get());
return extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.flags & MUX_INVERT);
}
const GateMateTileExtraDataPOD *tile_extra_data(int tile) const;
pool<PipId> blocked_pips;
};
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,260 @@
#
# nextpnr -- Next Generation Place and Route
#
# Copyright (C) 2024 The Project Peppercorn Authors.
#
# 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.
#
import os
from os import path
import sys
import argparse
sys.path.append(path.join(path.dirname(__file__), "../../.."))
from himbaechel_dbgen.chip import *
PIP_EXTRA_MUX = 1
PIP_EXTRA_CPE = 2
MUX_INVERT = 1
MUX_VISIBLE = 2
MUX_CONFIG = 4
MUX_CPE_INV = 8
parser = argparse.ArgumentParser()
parser.add_argument("--lib", help="Project Peppercorn python database script path", type=str, required=True)
parser.add_argument("--device", help="name of device to export", type=str, required=True)
parser.add_argument("--bba", help="bba file to write", type=str, required=True)
args = parser.parse_args()
sys.path.append(os.path.expanduser(args.lib))
sys.path += args.lib
import chip
import die
@dataclass
class TileExtraData(BBAStruct):
die : int = 0
bit_x: int = 0
bit_y: int = 0
prim_id : int = 0
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u8(self.die)
bba.u8(self.bit_x)
bba.u8(self.bit_y)
bba.u8(self.prim_id)
@dataclass
class PipExtraData(BBAStruct):
pip_type: int
name: IdString
bits: int = 0
value: int = 0
invert: int = 0
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.name.index)
bba.u8(self.bits)
bba.u8(self.value)
bba.u8(self.invert)
bba.u8(self.pip_type)
def set_timings(ch):
speed = "DEFAULT"
tmg = ch.set_speed_grades([speed])
lut = ch.timing.add_cell_variant(speed, "CPE")
lut.add_comb_arc("IN1", "OUT1", TimingValue(455, 459))
lut.add_comb_arc("IN2", "OUT1", TimingValue(450, 457))
lut.add_comb_arc("IN3", "OUT1", TimingValue(427, 430))
lut.add_comb_arc("IN4", "OUT1", TimingValue(423, 424))
lut.add_comb_arc("IN5", "OUT1", TimingValue(416, 418))
lut.add_comb_arc("IN6", "OUT1", TimingValue(413, 422))
lut.add_comb_arc("IN7", "OUT1", TimingValue(372, 374))
lut.add_comb_arc("IN8", "OUT1", TimingValue(275, 385))
lut.add_comb_arc("IN1", "OUT2", TimingValue(479, 484))
lut.add_comb_arc("IN2", "OUT2", TimingValue(471, 488))
lut.add_comb_arc("IN3", "OUT2", TimingValue(446, 449))
lut.add_comb_arc("IN4", "OUT2", TimingValue(443, 453))
dff = ch.timing.add_cell_variant(speed, "CPE_DFF")
dff.add_setup_hold("CLK", "IN1", ClockEdge.RISING, TimingValue(60), TimingValue(50))
dff.add_clock_out("CLK", "OUT1", ClockEdge.RISING, TimingValue(60))
dff.add_clock_out("CLK", "OUT2", ClockEdge.RISING, TimingValue(60))
def main():
# Range needs to be +1, but we are adding +2 more to coordinates, since
# they are starting from -2 instead of zero required for nextpnr
dev = chip.get_device(args.device)
ch = Chip("gatemate", args.device, dev.max_col() + 3, dev.max_row() + 3)
# Init constant ids
ch.strs.read_constids(path.join(path.dirname(__file__), "..", "constids.inc"))
ch.read_gfxids(path.join(path.dirname(__file__), "..", "gfxids.inc"))
for type_name in die.get_tile_type_list():
tt = ch.create_tile_type(type_name)
for group in sorted(die.get_groups_for_type(type_name)):
tt.create_group(group.name, group.type)
for wire in sorted(die.get_endpoints_for_type(type_name)):
tt.create_wire(wire.name, wire.type)
for prim in sorted(die.get_primitives_for_type(type_name)):
bel = tt.create_bel(prim.name, prim.type, prim.z)
for pin in sorted(die.get_primitive_pins(prim.type)):
tt.add_bel_pin(bel, pin.name, die.get_pin_connection_name(prim,pin), pin.dir)
for mux in sorted(die.get_mux_connections_for_type(type_name)):
pp = tt.create_pip(mux.src, mux.dst)
mux_flags = MUX_INVERT if mux.invert else 0
mux_flags |= MUX_VISIBLE if mux.visible else 0
mux_flags |= MUX_CONFIG if mux.config else 0
pp.extra_data = PipExtraData(PIP_EXTRA_MUX, ch.strs.id(mux.name), mux.bits, mux.value, mux_flags)
if "CPE" in type_name:
pp = tt.create_pip("CPE.IN1", "CPE.RAM_O2")
pp.extra_data = PipExtraData(PIP_EXTRA_CPE,ch.strs.id("RAM_O2"))
pp = tt.create_pip("CPE.IN1", "CPE.RAM_O1")
pp.extra_data = PipExtraData(PIP_EXTRA_CPE,ch.strs.id("RAM_O1"))
pp = tt.create_pip("CPE.RAM_I1", "CPE.OUT1")
pp.extra_data = PipExtraData(PIP_EXTRA_CPE,ch.strs.id("RAM_I1"))
pp = tt.create_pip("CPE.RAM_I2", "CPE.OUT2")
pp.extra_data = PipExtraData(PIP_EXTRA_CPE,ch.strs.id("RAM_I2"))
#pp = tt.create_pip("CPE.CINX", "CPE.COUTX")
#pp.extra_data = PipExtraData(PIP_EXTRA_CPE,ch.strs.id("COUTX"))
#pp = tt.create_pip("CPE.PINX", "CPE.POUTX")
#pp.extra_data = PipExtraData(PIP_EXTRA_CPE,ch.strs.id("POUTX"))
#pp = tt.create_pip("CPE.CINY1", "CPE.COUTY1")
#pp.extra_data = PipExtraData(PIP_EXTRA_CPE,ch.strs.id("COUTY1"))
#pp = tt.create_pip("CPE.PINY1", "CPE.POUTY1")
#pp.extra_data = PipExtraData(PIP_EXTRA_CPE,ch.strs.id("POUTY1"))
#pp = tt.create_pip("CPE.CINY2", "CPE.COUTY2")
#pp.extra_data = PipExtraData(PIP_EXTRA_CPE,ch.strs.id("COUTY2"))
#pp = tt.create_pip("CPE.PINY2", "CPE.POUTY2")
#pp.extra_data = PipExtraData(PIP_EXTRA_CPE,ch.strs.id("POUTY2"))
for i in range(1,9):
tt.create_wire(f"CPE.V_IN{i}", "CPE_VIRTUAL_WIRE")
pp = tt.create_pip(f"CPE.V_IN{i}", f"CPE.IN{i}")
pp = tt.create_pip(f"CPE.V_IN{i}", f"CPE.IN{i}")
pp.extra_data = PipExtraData(PIP_EXTRA_MUX, ch.strs.id(f"CPE.IN{i}_INV"), 1, i, MUX_CPE_INV | MUX_INVERT)
tt.create_wire("CPE.V_CLK", "CPE_VIRTUAL_WIRE")
pp = tt.create_pip("CPE.V_CLK", "CPE.CLK")
pp = tt.create_pip("CPE.V_CLK", "CPE.CLK")
pp.extra_data = PipExtraData(PIP_EXTRA_MUX, ch.strs.id("CPE.CLK_INV"), 1, 1, MUX_CPE_INV| MUX_INVERT)
tt.create_wire("CPE.V_EN", "CPE_VIRTUAL_WIRE")
pp = tt.create_pip("CPE.V_EN", "CPE.EN")
pp = tt.create_pip("CPE.V_EN", "CPE.EN")
pp.extra_data = PipExtraData(PIP_EXTRA_MUX, ch.strs.id("CPE.EN_INV"), 1, 1, MUX_CPE_INV| MUX_INVERT)
tt.create_wire("CPE.V_SR", "CPE_VIRTUAL_WIRE")
pp = tt.create_pip("CPE.V_SR", "CPE.SR")
pp = tt.create_pip("CPE.V_SR", "CPE.SR")
pp.extra_data = PipExtraData(PIP_EXTRA_MUX, ch.strs.id("CPE.SR_INV"), 1, 1, MUX_CPE_INV| MUX_INVERT)
if "GPIO" in type_name:
tt.create_wire("GPIO.OUT_D1", "WIRE_INTERNAL")
tt.create_wire("GPIO.OUT_D2", "WIRE_INTERNAL")
#tt.create_wire("GPIO.OUT_Q1", "WIRE_INTERNAL")
#tt.create_wire("GPIO.OUT_Q2", "WIRE_INTERNAL")
#tt.create_wire("GPIO.OUT_CLK","WIRE_INTERNAL")
#tt.create_wire("GPIO.CLK_INT","WIRE_INTERNAL")
pp = tt.create_pip("GPIO.OUT1", "GPIO.OUT_D1")
pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.OUT1_4"), 1, 0, MUX_VISIBLE)
pp = tt.create_pip("GPIO.OUT4", "GPIO.OUT_D1")
pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.OUT1_4"), 1, 1, MUX_VISIBLE)
pp = tt.create_pip("GPIO.OUT2", "GPIO.OUT_D2")
pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.OUT2_3"), 1, 0, MUX_VISIBLE)
pp = tt.create_pip("GPIO.OUT3", "GPIO.OUT_D2")
pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.OUT2_3"), 1, 1, MUX_VISIBLE)
pp = tt.create_pip("GPIO.OUT_D1","GPIO.DO")
pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.OUT23_14_SEL"), 1, 0, MUX_VISIBLE)
pp = tt.create_pip("GPIO.OUT_D2","GPIO.DO")
pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.OUT23_14_SEL"), 1, 1, MUX_VISIBLE)
pp = tt.create_pip("GPIO.OUT2","GPIO.OE")
pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.OE_SIGNAL"), 2, 1, MUX_VISIBLE)
pp = tt.create_pip("GPIO.OUT3","GPIO.OE")
pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.OE_SIGNAL"), 2, 2, MUX_VISIBLE)
pp = tt.create_pip("GPIO.OUT4","GPIO.OE")
pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.OE_SIGNAL"), 2, 3, MUX_VISIBLE)
#pp = tt.create_pip("GPIO.OUT4", "GPIO.CLK_INT")
#pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.CLK_1_4"), 1, 0, MUX_VISIBLE)
#pp = tt.create_pip("GPIO.OUT1", "GPIO.CLK_INT")
#pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.CLK_1_4"), 1, 1, MUX_VISIBLE)
#pp = tt.create_pip("GPIO.CLK_INT", "GPIO.OUT_CLK")
#pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.SEL_OUT_CLOCK"), 1, 1, MUX_VISIBLE)
#pp = tt.create_pip("GPIO.CLOCK1", "GPIO.OUT_CLK")
#pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.OUT_CLOCK"), 2, 0, MUX_VISIBLE)
#pp = tt.create_pip("GPIO.CLOCK2", "GPIO.OUT_CLK")
#pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.OUT_CLOCK"), 2, 1, MUX_VISIBLE)
#pp = tt.create_pip("GPIO.CLOCK3", "GPIO.OUT_CLK")
#pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.OUT_CLOCK"), 2, 2, MUX_VISIBLE)
#pp = tt.create_pip("GPIO.CLOCK4", "GPIO.OUT_CLK")
#pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.OUT_CLOCK"), 2, 3, MUX_VISIBLE)
#tt.create_wire("GPIO.IN_D1", "WIRE_INTERNAL")
#tt.create_wire("GPIO.IN_D2", "WIRE_INTERNAL")
#tt.create_wire("GPIO.IN_Q1", "WIRE_INTERNAL")
#tt.create_wire("GPIO.IN_Q2", "WIRE_INTERNAL")
#tt.create_wire("GPIO.IN_CLK","WIRE_INTERNAL")
#pp = tt.create_pip("GPIO.CLK_INT", "GPIO.IN_CLK")
#pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.SEL_IN_CLOCK"), 1, 1, MUX_VISIBLE)
#pp = tt.create_pip("GPIO.CLOCK1", "GPIO.IN_CLK")
#pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.IN_CLOCK"), 2, 0, MUX_VISIBLE)
#pp = tt.create_pip("GPIO.CLOCK2", "GPIO.IN_CLK")
#pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.IN_CLOCK"), 2, 1, MUX_VISIBLE)
#pp = tt.create_pip("GPIO.CLOCK3", "GPIO.IN_CLK")
#pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.IN_CLOCK"), 2, 2, MUX_VISIBLE)
#pp = tt.create_pip("GPIO.CLOCK4", "GPIO.IN_CLK")
#pp.extra_data = PipExtraData(PIP_EXTRA_MUX,ch.strs.id("GPIO.IN_CLOCK"), 2, 3, MUX_VISIBLE)
tt.create_pip("GPIO.DI", "GPIO.IN1")
tt.create_pip("GPIO.DI", "GPIO.IN2")
# Setup tile grid
for x in range(dev.max_col() + 3):
for y in range(dev.max_row() + 3):
ti = ch.set_tile_type(x, y, dev.get_tile_type(x - 2,y - 2))
tileinfo = dev.get_tile_info(x - 2,y - 2)
ti.extra_data = TileExtraData(tileinfo.die, tileinfo.bit_x, tileinfo.bit_y, tileinfo.prim_index)
# Create nodes between tiles
for _,nodes in dev.get_connections():
node = []
for conn in sorted(nodes):
conn.name = conn.name.replace("CPE.IN", "CPE.V_IN")
conn.name = conn.name.replace("CPE.CLK", "CPE.V_CLK")
conn.name = conn.name.replace("CPE.EN", "CPE.V_EN")
conn.name = conn.name.replace("CPE.SR", "CPE.V_SR")
node.append(NodeWire(conn.x + 2, conn.y + 2, conn.name))
ch.add_node(node)
set_timings(ch)
for package in dev.get_packages():
pkg = ch.create_package(package)
for pad in sorted(dev.get_package_pads(package)):
pkg.create_pad(pad.name, f"X{pad.x+2}Y{pad.y+2}", pad.bel, pad.function, pad.bank)
ch.write_bba(args.bba)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,75 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* 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 "gatemate.h"
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#define HIMBAECHEL_GFXIDS "uarch/gatemate/gfxids.inc"
#define HIMBAECHEL_UARCH gatemate
#include "himbaechel_constids.h"
#include "himbaechel_gfxids.h"
NEXTPNR_NAMESPACE_BEGIN
void GateMateImpl::drawBel(std::vector<GraphicElement> &g, GraphicElement::style_t style, IdString bel_type, Loc loc)
{
GraphicElement el;
el.type = GraphicElement::TYPE_BOX;
el.style = style;
switch (bel_type.index) {
case id_CPE.index:
el.x1 = loc.x + 0.70;
el.x2 = el.x1 + 0.20;
el.y1 = loc.y + 0.55;
el.y2 = el.y1 + 0.40;
g.push_back(el);
break;
case id_GPIO.index:
el.x1 = loc.x + 0.20;
el.x2 = el.x1 + 0.60;
el.y1 = loc.y + 0.20;
el.y2 = el.y1 + 0.60;
g.push_back(el);
break;
case id_BUFG.index:
el.x1 = loc.x + 0.15 + loc.z * 0.20;
el.x2 = el.x1 + 0.15;
el.y1 = loc.y + 0.10;
el.y2 = el.y1 + 0.30;
g.push_back(el);
break;
case id_PLL.index:
el.x1 = loc.x + 0.15 + (loc.z - 4) * 0.20;
el.x2 = el.x1 + 0.15;
el.y1 = loc.y + 0.60;
el.y2 = el.y1 + 0.30;
g.push_back(el);
break;
case id_USR_RSTN.index:
el.x1 = loc.x + 0.20;
el.x2 = el.x1 + 0.20;
el.y1 = loc.y + 0.20;
el.y2 = el.y1 + 0.20;
g.push_back(el);
break;
}
}
NEXTPNR_NAMESPACE_END

View File

View File

@ -0,0 +1,629 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* 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 "pack.h"
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#include "himbaechel_constids.h"
NEXTPNR_NAMESPACE_BEGIN
void GateMatePacker::disconnect_if_gnd(CellInfo *cell, IdString input)
{
NetInfo *net = cell->getPort(input);
if (!net)
return;
if (net->name.in(ctx->id("$PACKER_GND"))) {
cell->disconnectPort(input);
}
}
void GateMatePacker::pack_io()
{
// Trim nextpnr IOBs - assume IO buffer insertion has been done in synthesis
for (auto &port : ctx->ports) {
if (!ctx->cells.count(port.first))
log_error("Port '%s' doesn't seem to have a corresponding top level IO\n", ctx->nameOf(port.first));
CellInfo *ci = ctx->cells.at(port.first).get();
PortRef top_port;
top_port.cell = nullptr;
bool is_npnr_iob = false;
if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
// Might have an input buffer connected to it
is_npnr_iob = true;
NetInfo *o = ci->getPort(id_O);
if (o == nullptr)
;
else if (o->users.entries() > 1)
log_error("Top level pin '%s' has multiple input buffers\n", ctx->nameOf(port.first));
else if (o->users.entries() == 1)
top_port = *o->users.begin();
}
if (ci->type == ctx->id("$nextpnr_obuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
// Might have an output buffer connected to it
is_npnr_iob = true;
NetInfo *i = ci->getPort(id_I);
if (i != nullptr && i->driver.cell != nullptr) {
if (top_port.cell != nullptr)
log_error("Top level pin '%s' has multiple input/output buffers\n", ctx->nameOf(port.first));
top_port = i->driver;
}
// Edge case of a bidirectional buffer driving an output pin
if (i->users.entries() > 2) {
log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first));
} else if (i->users.entries() == 2) {
if (top_port.cell != nullptr)
log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first));
for (auto &usr : i->users) {
if (usr.cell->type == ctx->id("$nextpnr_obuf") || usr.cell->type == ctx->id("$nextpnr_iobuf"))
continue;
top_port = usr;
break;
}
}
}
if (!is_npnr_iob)
log_error("Port '%s' doesn't seem to have a corresponding top level IO (internal cell type mismatch)\n",
ctx->nameOf(port.first));
if (top_port.cell == nullptr) {
log_info("Trimming port '%s' as it is unused.\n", ctx->nameOf(port.first));
} else {
// Copy attributes to real IO buffer
for (auto &attrs : ci->attrs)
top_port.cell->attrs[attrs.first] = attrs.second;
for (auto &params : ci->params) {
IdString key = params.first;
if (top_port.cell->type.in(id_CC_LVDS_IBUF, id_CC_LVDS_OBUF, id_CC_LVDS_TOBUF, id_CC_LVDS_IOBUF)) {
if (top_port.port.in(id_I_P, id_O_P, id_IO_P))
key = id_PIN_NAME_P;
if (top_port.port.in(id_I_N, id_O_N, id_IO_N))
key = id_PIN_NAME_N;
}
if (top_port.cell->params.count(key)) {
if (top_port.cell->params[key] != params.second) {
std::string val = params.second.is_string ? params.second.as_string()
: std::to_string(params.second.as_int64());
log_warning("Overriding parameter '%s' with value '%s' for cell '%s'.\n", key.c_str(ctx),
val.c_str(), ctx->nameOf(top_port.cell));
}
}
top_port.cell->params[key] = params.second;
}
// Make sure that top level net is set correctly
port.second.net = top_port.cell->ports.at(top_port.port).net;
}
// Now remove the nextpnr-inserted buffer
ci->disconnectPort(id_I);
ci->disconnectPort(id_O);
ctx->cells.erase(port.first);
}
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_IBUF, id_CC_OBUF, id_CC_TOBUF, id_CC_IOBUF, id_CC_LVDS_IBUF, id_CC_LVDS_OBUF,
id_CC_LVDS_TOBUF, id_CC_LVDS_IOBUF))
continue;
bool is_lvds = ci.type.in(id_CC_LVDS_IBUF, id_CC_LVDS_OBUF, id_CC_LVDS_TOBUF, id_CC_LVDS_IOBUF);
std::string loc = str_or_default(ci.params, is_lvds ? id_PIN_NAME_P : id_PIN_NAME, "UNPLACED");
if (ci.params.count(id_LOC)) {
std::string new_loc = str_or_default(ci.params, id_LOC, "UNPLACED");
if (loc != "UNPLACED" && loc != new_loc)
log_warning("Overriding location of cell '%s' from '%s' with '%s'\n", ctx->nameOf(&ci), loc.c_str(),
new_loc.c_str());
loc = new_loc;
}
if (loc == "UNPLACED")
log_warning("IO signal name '%s' is not defined in CCF file and will be auto-placed.\n", ctx->nameOf(&ci));
disconnect_if_gnd(&ci, id_T);
if (ci.type == id_CC_TOBUF && !ci.getPort(id_T))
ci.type = id_CC_OBUF;
if (ci.type == id_CC_LVDS_TOBUF && !ci.getPort(id_T))
ci.type = id_CC_LVDS_OBUF;
std::vector<IdString> keys;
for (auto &p : ci.params) {
if (p.first.in(id_PIN_NAME, id_PIN_NAME_P, id_PIN_NAME_N)) {
if (ctx->get_package_pin_bel(ctx->id(p.second.as_string())) == BelId())
log_error("Unknown %s '%s' for cell '%s'.\n", p.first.c_str(ctx), p.second.as_string().c_str(),
ci.name.c_str(ctx));
keys.push_back(p.first);
continue;
}
if (p.first.in(id_V_IO, id_LOC)) {
keys.push_back(p.first);
continue;
}
if (ci.type.in(id_CC_IBUF, id_CC_IOBUF) &&
p.first.in(id_PULLUP, id_PULLDOWN, id_KEEPER, id_SCHMITT_TRIGGER, id_DELAY_IBF, id_FF_IBF))
continue;
if (ci.type.in(id_CC_TOBUF) && p.first.in(id_PULLUP, id_PULLDOWN, id_KEEPER))
continue;
if (ci.type.in(id_CC_OBUF, id_CC_TOBUF, id_CC_IOBUF) &&
p.first.in(id_DRIVE, id_SLEW, id_DELAY_OBF, id_FF_OBF))
continue;
if (ci.type.in(id_CC_LVDS_IBUF, id_CC_LVDS_IOBUF) && p.first.in(id_LVDS_RTERM, id_DELAY_IBF, id_FF_IBF))
continue;
if (ci.type.in(id_CC_LVDS_OBUF, id_CC_LVDS_TOBUF, id_CC_LVDS_IOBUF) &&
p.first.in(id_LVDS_BOOST, id_DELAY_OBF, id_FF_OBF))
continue;
log_warning("Removing unsupported parameter '%s' for type '%s'.\n", p.first.c_str(ctx), ci.type.c_str(ctx));
keys.push_back(p.first);
}
if (ci.params.count(id_SLEW)) {
std::string val = str_or_default(ci.params, id_SLEW, "UNDEFINED");
if (val == "UNDEFINED")
keys.push_back(id_SLEW);
else if (val == "FAST")
ci.params[id_SLEW] = Property(Property::State::S1);
else if (val == "SLOW")
ci.params[id_SLEW] = Property(Property::State::S0);
else
log_error("Unknown value '%s' for SLEW parameter of '%s' cell.\n", val.c_str(), ci.name.c_str(ctx));
}
if (is_lvds) {
std::string p_pin = str_or_default(ci.params, id_PIN_NAME_P, "UNPLACED");
std::string n_pin = str_or_default(ci.params, id_PIN_NAME_N, "UNPLACED");
if (p_pin == "UNPLACED" || n_pin == "UNPLACED")
log_error("Both LVDS pins must be set to a valid locations.\n");
if (p_pin.substr(0, 6) != n_pin.substr(0, 6) || p_pin[7] != n_pin[7])
log_error("Both LVDS pads '%s' and '%s' do not match.\n", p_pin.c_str(), n_pin.c_str());
if (p_pin[6] != 'A')
log_error("Both LVDS positive pad must be from type A.\n");
if (n_pin[6] != 'B')
log_error("Both LVDS negative pad must be from type B.\n");
}
for (auto key : keys)
ci.params.erase(key);
if ((ci.params.count(id_KEEPER) + ci.params.count(id_PULLUP) + ci.params.count(id_PULLDOWN)) > 1)
log_error("PULLUP, PULLDOWN and KEEPER are mutually exclusive parameters.\n");
if (is_lvds)
ci.params[id_LVDS_EN] = Property(Property::State::S1);
// DELAY_IBF and DELAY_OBF must be set depending of type
// Also we need to enable input/output
if (ci.type.in(id_CC_IBUF, id_CC_IOBUF, id_CC_LVDS_IBUF, id_CC_LVDS_IOBUF)) {
ci.params[id_DELAY_IBF] = Property(1 << int_or_default(ci.params, id_DELAY_IBF, 0), 16);
if (is_lvds)
ci.params[id_LVDS_IE] = Property(Property::State::S1);
else
ci.params[id_INPUT_ENABLE] = Property(Property::State::S1);
}
if (ci.type.in(id_CC_OBUF, id_CC_TOBUF, id_CC_IOBUF, id_CC_LVDS_OBUF, id_CC_LVDS_TOBUF, id_CC_LVDS_IOBUF)) {
ci.params[id_DELAY_OBF] = Property(1 << int_or_default(ci.params, id_DELAY_OBF, 0), 16);
ci.params[id_OE_ENABLE] = Property(Property::State::S1);
}
if (ci.params.count(id_DRIVE)) {
int val = int_or_default(ci.params, id_DRIVE, 0);
if (val != 3 && val != 6 && val != 9 && val != 12)
log_error("Unsupported value '%d' for DRIVE parameter of '%s' cell.\n", val, ci.name.c_str(ctx));
ci.params[id_DRIVE] = Property((val - 3) / 3, 2);
}
for (auto &p : ci.params) {
if (p.first.in(id_PULLUP, id_PULLDOWN, id_KEEPER, id_SCHMITT_TRIGGER, id_FF_OBF, id_FF_IBF, id_LVDS_RTERM,
id_LVDS_BOOST)) {
int val = int_or_default(ci.params, p.first, 0);
if (val != 0 && val != 1)
log_error("Unsupported value '%d' for %s parameter of '%s' cell.\n", val, p.first.c_str(ctx),
ci.name.c_str(ctx));
ci.params[p.first] = Property(val, 1);
}
}
// Disconnect PADs
ci.disconnectPort(id_IO);
ci.disconnectPort(id_I);
ci.disconnectPort(id_O);
ci.disconnectPort(id_IO_P);
ci.disconnectPort(id_IO_N);
ci.disconnectPort(id_I_P);
ci.disconnectPort(id_I_N);
ci.disconnectPort(id_O_P);
ci.disconnectPort(id_O_N);
// Remap ports to GPIO bel
ci.renamePort(id_A, id_DO);
ci.renamePort(id_Y, id_DI);
ci.renamePort(id_T, id_OE);
NetInfo *do_net = ci.getPort(id_DO);
if (do_net) {
if (do_net->name.in(ctx->id("$PACKER_GND"), ctx->id("$PACKER_VCC"))) {
ci.params[id_OUT23_14_SEL] =
Property(do_net->name == ctx->id("$PACKER_VCC") ? Property::State::S1 : Property::State::S0);
ci.disconnectPort(id_DO);
} else {
ci.params[id_OUT_SIGNAL] = Property(Property::State::S1);
}
}
if (!loc.empty()) {
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", ci.name.c_str(ctx),
loc.c_str());
log_info(" Constraining '%s' to pad '%s'\n", ci.name.c_str(ctx), loc.c_str());
if (!ctx->checkBelAvail(bel)) {
log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci),
ctx->nameOfBel(bel), ctx->nameOf(ctx->getBoundBelCell(bel)));
}
ctx->bindBel(bel, &ci, PlaceStrength::STRENGTH_FIXED);
}
}
}
void GateMatePacker::pack_cpe()
{
log_info("Packing CPEs..\n");
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_L2T4, id_CC_L2T5, id_CC_LUT2, id_CC_LUT1))
continue;
if (ci.type == id_CC_L2T5) {
ci.renamePort(id_I0, id_IN5);
ci.renamePort(id_I1, id_IN6);
ci.renamePort(id_I2, id_IN7);
ci.renamePort(id_I3, id_IN8);
ci.renamePort(id_I4, id_IN1);
ci.renamePort(id_O, id_OUT1);
ci.params[id_INIT_L00] = Property(0b1010, 4);
ci.params[id_INIT_L10] = Property(0b1010, 4);
ci.params[id_O1] = Property(0b11, 2);
} else {
ci.renamePort(id_I0, id_IN1);
ci.renamePort(id_I1, id_IN2);
ci.renamePort(id_I2, id_IN3);
ci.renamePort(id_I3, id_IN4);
ci.renamePort(id_O, id_OUT1);
ci.params[id_O1] = Property(0b11, 2);
ci.params[id_INIT_L20] = Property(0b1010, 4);
if (ci.type.in(id_CC_LUT1, id_CC_LUT2)) {
uint8_t val = int_or_default(ci.params, id_INIT, 0);
if (ci.type == id_CC_LUT1)
val = val << 2 | val;
ci.params[id_INIT_L00] = Property(val, 4);
ci.unsetParam(id_INIT);
ci.params[id_INIT_L10] = Property(0b1010, 4);
}
}
ci.type = id_CPE;
}
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_MX2, id_CC_MX4))
continue;
ci.renamePort(id_D0, id_IN1);
ci.renamePort(id_D1, id_IN2);
ci.renamePort(id_S0, id_IN6);
ci.renamePort(id_Y, id_OUT1);
// Only for CC_MX4
ci.renamePort(id_D2, id_IN3);
ci.renamePort(id_D3, id_IN4);
ci.renamePort(id_S1, id_IN8);
uint8_t select = 0;
uint8_t invert = 0;
for(int i=0;i<4;i++) {
NetInfo *net = ci.getPort(ctx->idf("IN%d",i+1));
if (net) {
if (net->name.in(ctx->id("$PACKER_GND"),ctx->id("$PACKER_VCC"))) {
if (net->name == ctx->id("$PACKER_VCC"))
invert |= 1<< i;
ci.disconnectPort(ctx->idf("IN%d",i+1));
} else {
select |= 1 << i;
}
}
}
ci.params[id_FUNCTION] = Property(0b100, 3);
ci.params[id_INIT_L02] = Property(0b1100, 4); // IN6
if (ci.type == id_CC_MX4)
ci.params[id_INIT_L03] = Property(0b1100, 4); // IN8
ci.params[id_INIT_L10] = Property(select, 4); // Selection bits
ci.params[id_INIT_L11] = Property(invert, 4); // Inversion bits
ci.params[id_INIT_L20] = Property(0b1100, 4); // Always D1
ci.params[id_O1] = Property(0b11, 2);
ci.type = id_CPE;
}
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_DFF))
continue;
ci.renamePort(id_Q, id_OUT2);
ci.params[id_O2] = Property(0b00, 2);
ci.params[id_2D_IN] = Property(1, 1);
NetInfo *d_net = ci.getPort(id_D);
if (d_net->name == ctx->id("$PACKER_GND")) {
ci.params[id_INIT_L00] = Property(0b0000, 4);
ci.disconnectPort(id_D);
} else if (d_net->name == ctx->id("$PACKER_VCC")) {
ci.params[id_INIT_L00] = Property(0b1111, 4);
ci.disconnectPort(id_D);
} else {
ci.params[id_INIT_L00] = Property(0b1010, 4);
}
ci.params[id_INIT_L10] = Property(0b1010, 4);
ci.renamePort(id_D, id_IN1);
NetInfo *en_net = ci.getPort(id_EN);
bool invert = int_or_default(ci.params, id_EN_INV, 0) == 1;
if (en_net) {
if (en_net->name == ctx->id("$PACKER_GND")) {
ci.params[id_EN] = Property(invert ? 0b11 : 0b00, 2);
ci.disconnectPort(id_EN);
} else if (en_net->name == ctx->id("$PACKER_VCC")) {
ci.params[id_EN] = Property(invert ? 0b00 : 0b11, 2);
ci.disconnectPort(id_EN);
} else {
ci.params[id_EN] = Property(invert ? 0b01 : 0b10, 2);
}
} else {
ci.params[id_EN] = Property(invert ? 0b11 : 0b00, 2);
}
ci.unsetParam(id_EN_INV);
NetInfo *clk_net = ci.getPort(id_CLK);
invert = int_or_default(ci.params, id_CLK_INV, 0) == 1;
if (clk_net) {
if (clk_net->name == ctx->id("$PACKER_GND")) {
ci.params[id_CLK] = Property(invert ? 0b11 : 0b00, 2);
ci.disconnectPort(id_CLK);
} else if (clk_net->name == ctx->id("$PACKER_VCC")) {
ci.params[id_CLK] = Property(invert ? 0b00 : 0b11, 2);
ci.disconnectPort(id_CLK);
} else {
ci.params[id_CLK] = Property(invert ? 0b01 : 0b10, 2);
}
} else {
ci.params[id_CLK] = Property(invert ? 0b11 : 0b00, 2);
}
ci.unsetParam(id_CLK_INV);
NetInfo *sr_net = ci.getPort(id_SR);
invert = int_or_default(ci.params, id_SR_INV, 0) == 1;
int sr_val = int_or_default(ci.params, id_SR_VAL, 0) == 1;
if (sr_net) {
if (sr_net->name == ctx->id("$PACKER_GND")) {
if (invert)
log_error("Invalid DFF configuration\n.");
ci.params[id_R] = Property(0b11, 2);
ci.params[id_S] = Property(0b11, 2);
ci.disconnectPort(id_SR);
} else if (sr_net->name == ctx->id("$PACKER_VCC")) {
if (!invert)
log_error("Invalid DFF configuration\n.");
ci.params[id_R] = Property(0b11, 2);
ci.params[id_S] = Property(0b11, 2);
ci.disconnectPort(id_SR);
} else {
if (sr_val) {
ci.params[id_R] = Property(0b11, 2);
ci.params[id_S] = Property(invert ? 0b01 : 0b10, 2);
ci.params[id_EN_SR] = Property(0b1, 1);
} else {
ci.params[id_R] = Property(invert ? 0b01 : 0b10, 2);
ci.params[id_S] = Property(0b11, 2);
}
}
} else {
if (invert)
log_error("Invalid DFF configuration\n.");
ci.params[id_R] = Property(0b11, 2);
ci.params[id_S] = Property(0b11, 2);
}
ci.unsetParam(id_SR_VAL);
ci.unsetParam(id_SR_INV);
if (ci.params.count(id_INIT) && ci.params[id_INIT].is_fully_def()) {
bool init = int_or_default(ci.params, id_INIT, 0) == 1;
if (init)
ci.params[id_FF_INIT] = Property(0b11, 2);
else
ci.params[id_FF_INIT] = Property(0b10, 2);
ci.unsetParam(id_INIT);
} else {
ci.unsetParam(id_INIT);
}
ci.timing_index = ctx->get_cell_timing_idx(id_CPE_DFF);
ci.type = id_CPE;
}
}
void GateMatePacker::pack_bufg()
{
log_info("Packing BUFGs..\n");
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_BUFG))
continue;
ci.type = id_BUFG;
}
}
template <typename KeyType> double double_or_default(const dict<KeyType, Property> &ct, const KeyType &key, double def = 0)
{
auto found = ct.find(key);
if (found == ct.end())
return def;
else {
if (found->second.is_string) {
try {
return std::stod(found->second.as_string());
} catch (std::invalid_argument &e) {
log_error("Expecting numeric value but got '%s'.\n", found->second.as_string().c_str());
}
} else
return double(found->second.as_int64());
}
};
void GateMatePacker::pack_pll()
{
log_info("Packing PLLss..\n");
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_PLL))
continue;
disconnect_if_gnd(&ci, id_CLK_REF);
disconnect_if_gnd(&ci, id_USR_CLK_REF);
disconnect_if_gnd(&ci, id_CLK_FEEDBACK);
disconnect_if_gnd(&ci, id_USR_LOCKED_STDY_RST);
ci.params[id_LOCK_REQ] = Property(int_or_default(ci.params, id_LOCK_REQ, 1),1);
ci.params[id_CLK180_DOUB] = Property(int_or_default(ci.params, id_CLK180_DOUB, 0),1);
ci.params[id_CLK270_DOUB] = Property(int_or_default(ci.params, id_CLK270_DOUB, 0),1);
std::string mode = str_or_default(ci.params, id_PERF_MD, "SPEED");
boost::algorithm::to_upper(mode);
int perf_md;
if (mode == "LOWPOWER") perf_md = 1;
else if (mode == "ECONOMY") perf_md = 2;
else if (mode == "SPEED") perf_md = 3;
else log_error("Unknown PERF_MD parameter value '%s' for cell %s.\n", mode.c_str(), ci.name.c_str(ctx));
double ref_clk = double_or_default(ci.params, id_REF_CLK, 0.0);
if (ref_clk <= 0 || ref_clk > 125)
log_error("REF_CLK parameter is out of range (0,125.00].\n");
double max_freq = 0.0;
switch(perf_md) {
case 1: max_freq = 250.00; break;
case 2: max_freq = 312.50; break;
case 3: max_freq = 416.75; break;
}
double out_clk = double_or_default(ci.params, id_OUT_CLK, 0.0);
if (out_clk <= 0 || out_clk > max_freq)
log_error("OUT_CLK parameter is out of range (0,%.2lf].\n", max_freq);
// For 10 MHz - > 25 MHz
ci.params[ctx->id("CFG_A.AO_SW")] = Property(0b01000,5);
ci.params[ctx->id("CFG_A.CI_FILTER_CONST")] = Property(0b00010,5);
ci.params[ctx->id("CFG_A.COARSE_TUNE")] = Property(0b100,3);
ci.params[ctx->id("CFG_A.CP_FILTER_CONST")] = Property(0b00100,5);
ci.params[ctx->id("CFG_A.EN_COARSE_TUNE")] = Property(0b1,1);
ci.params[ctx->id("CFG_A.FAST_LOCK")] = Property(0b1,1);
ci.params[ctx->id("CFG_A.FILTER_SHIFT")] = Property(0b10,2);
ci.params[ctx->id("CFG_A.FINE_TUNE")] = Property(0b00011001000,11);
ci.params[ctx->id("CFG_A.K")] = Property(0b000000000001,12);
ci.params[ctx->id("CFG_A.M1")] = Property(0b000010,6);
ci.params[ctx->id("CFG_A.M2")] = Property(0b0000001001,10);
ci.params[ctx->id("CFG_A.N1")] = Property(0b001001,6);
ci.params[ctx->id("CFG_A.N2")] = Property(0b0000001010,10);
ci.params[ctx->id("CFG_A.PDIV0_MUX")] = Property(0b1,1);
ci.params[ctx->id("CFG_A.PDIV1_SEL")] = Property(0b1,1);
ci.params[ctx->id("CFG_A.SAR_LIMIT")] = Property(0b010,3);
// Remove all not propagated parameters
ci.unsetParam(id_PERF_MD);
ci.unsetParam(id_REF_CLK);
ci.unsetParam(id_OUT_CLK);
ci.unsetParam(id_LOW_JITTER);
ci.unsetParam(id_CI_FILTER_CONST);
ci.unsetParam(id_CP_FILTER_CONST);
//ci.unsetParam(id_PLL_CFG_A);
//ci.unsetParam(id_PLL_CFG_B);
ci.type = id_PLL;
}
}
void GateMatePacker::pack_misc()
{
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_USR_RSTN))
continue;
ci.type = id_USR_RSTN;
}
}
void GateMatePacker::pack_constants()
{
log_info("Packing constants..\n");
// Replace constants with LUTs
const dict<IdString, Property> vcc_params = {{id_INIT_L20, Property(0b1111, 4)}, {id_O1, Property(0b11, 2)}};
const dict<IdString, Property> gnd_params = {{id_INIT_L20, Property(0b0000, 4)}, {id_O1, Property(0b11, 2)}};
h.replace_constants(CellTypePort(id_CPE, id_OUT1), CellTypePort(id_CPE, id_OUT1), vcc_params, gnd_params);
}
void GateMatePacker::remove_constants()
{
log_info("Removing constants..\n");
auto fnd_cell = ctx->cells.find(ctx->id("$PACKER_VCC_DRV"));
if (fnd_cell != ctx->cells.end()) {
auto fnd_net = ctx->nets.find(ctx->id("$PACKER_VCC"));
if (fnd_net != ctx->nets.end() && fnd_net->second->users.entries() == 0) {
BelId bel = (*fnd_cell).second.get()->bel;
if (bel != BelId())
ctx->unbindBel(bel);
ctx->cells.erase(fnd_cell);
ctx->nets.erase(fnd_net);
log_info(" Removed unused VCC cell\n");
}
}
fnd_cell = ctx->cells.find(ctx->id("$PACKER_GND_DRV"));
if (fnd_cell != ctx->cells.end()) {
auto fnd_net = ctx->nets.find(ctx->id("$PACKER_GND"));
if (fnd_net != ctx->nets.end() && fnd_net->second->users.entries() == 0) {
BelId bel = (*fnd_cell).second.get()->bel;
if (bel != BelId())
ctx->unbindBel(bel);
ctx->cells.erase(fnd_cell);
ctx->nets.erase(fnd_net);
log_info(" Removed unused GND cell\n");
}
}
}
void GateMateImpl::pack()
{
const ArchArgs &args = ctx->args;
if (args.options.count("ccf")) {
parse_ccf(args.options.at("ccf"));
}
GateMatePacker packer(ctx, this);
packer.pack_constants();
packer.pack_io();
packer.pack_pll();
packer.pack_bufg();
packer.pack_misc();
packer.pack_cpe();
packer.remove_constants();
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,48 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* 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 GATEMATE_PACK_H
#define GATEMATE_PACK_H
#include "gatemate.h"
NEXTPNR_NAMESPACE_BEGIN
struct GateMatePacker
{
GateMatePacker(Context *ctx, GateMateImpl *uarch) : ctx(ctx), uarch(uarch) { h.init(ctx); };
void pack_io();
void pack_cpe();
void pack_bufg();
void pack_pll();
void pack_misc();
void pack_constants();
void disconnect_if_gnd(CellInfo *cell, IdString input);
void remove_constants();
Context *ctx;
GateMateImpl *uarch;
HimbaechelHelpers h;
};
NEXTPNR_NAMESPACE_END
#endif

2
tests

@ -1 +1 @@
Subproject commit d2d5b69dd34b1a9a1e4e9466599ed18e9254fee9
Subproject commit 00c55a9eb9ea2e062b51fe0d64741412b185d95d