Gowin. Add a router for segments. (#1456)

Gowin chips have an interesting mechanism - wires that run vertically
through several rows (at least 10) in each column of the chip. In each
row a particular wire has branches to the left and right, covering on
average 4 neighboring cells in the row. For lack of a better term, I
further call such a wire a segment.

So a segment can provide a direct connection in a local rectangle. There
are no special restrictions on the sinks, so segment networks can be
used for ClockEnable, LocalSetReset, as well as for LUT and DFF inputs.

The sources are not so simple - the sources can be the upper or lower
end of the segment, which in theory can lead to unfortunate consequences
if the signal is applied from both ends.

The matter is complicated by the fact that there are default
connections, i.e. in the absence of any set fuse the segment input is
still connected to something (VCC for example) and to disable the unused
end of the segment you need to set a special combination of fuses.

Taking into account which end of which segment is used is one of the
tasks of this router. In addition, segment ends can physically coincide
with PLL, DSP and BSRAM inputs, which can also lead to unexpected
effects. Some of these things are tracked when generating the base, some
in this router, some when packing in gowin_pack.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
This commit is contained in:
YRabbit 2025-03-18 21:02:49 +10:00 committed by GitHub
parent d8988e1682
commit 864c1e471d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 722 additions and 12 deletions

View File

@ -1346,3 +1346,8 @@ X(TREG_LSR_NET)
X(NOIOBFF)
X(IOBFF)
// segments
X(LW_TAP)
X(LW_TAP_0)
X(LW_BRANCH)
X(SEG_WIRES_TO_ISOLATE)

View File

@ -42,6 +42,8 @@ struct GowinGlobalRouter
bool global_pip_available(PipId pip) const { return gwu.is_global_pip(pip) || ctx->checkPipAvail(pip); };
bool segment_wire_filter(PipId pip) const { return !gwu.is_segment_pip(pip); }
// allow io->global, global->global and global->tile clock
bool global_pip_filter(PipId pip, WireId src_wire) const
{
@ -359,7 +361,11 @@ struct GowinGlobalRouter
}
RouteResult route_result = route_direct_net(
net, [&](PipId pip, WireId src_wire) { return global_DQCE_pip_filter(pip, src); }, src);
net,
[&](PipId pip, WireId src_wire) {
return global_DQCE_pip_filter(pip, src) && segment_wire_filter(pip);
},
src);
if (route_result == NOT_ROUTED) {
log_error("Can't route the %s network.\n", ctx->nameOf(net));
}
@ -436,8 +442,10 @@ struct GowinGlobalRouter
src = ctx->getBelPinWire(driver.cell->bel, driver.port);
}
RouteResult route_result =
route_direct_net(net, [&](PipId pip, WireId src_wire) { return global_DCS_pip_filter(pip, src); }, src);
RouteResult route_result = route_direct_net(
net,
[&](PipId pip, WireId src_wire) { return global_DCS_pip_filter(pip, src) && segment_wire_filter(pip); },
src);
if (route_result == NOT_ROUTED) {
log_error("Can't route the %s network.\n", ctx->nameOf(net));
}
@ -526,10 +534,13 @@ struct GowinGlobalRouter
std::vector<PipId> path;
RouteResult route_result;
if (driver_is_mipi(driver)) {
route_result = route_direct_net(net, [&](PipId pip, WireId src_wire) { return true; }, src, &path);
route_result = route_direct_net(
net, [&](PipId pip, WireId src_wire) { return segment_wire_filter(pip); }, src, &path);
} else {
route_result = route_direct_net(
net, [&](PipId pip, WireId src_wire) { return global_pip_filter(pip, src); }, src, &path);
net,
[&](PipId pip, WireId src_wire) { return global_pip_filter(pip, src) && segment_wire_filter(pip); },
src, &path);
}
if (route_result == NOT_ROUTED) {
@ -599,7 +610,11 @@ struct GowinGlobalRouter
NPNR_ASSERT(net_before_buf != nullptr);
RouteResult route_result = route_direct_net(
net, [&](PipId pip, WireId src_wire) { return global_pip_filter(pip, src_wire); }, src);
net,
[&](PipId pip, WireId src_wire) {
return global_pip_filter(pip, src_wire) && segment_wire_filter(pip);
},
src);
if (route_result == NOT_ROUTED || route_result == ROUTED_PARTIALLY) {
log_error("Can't route the %s net. It might be worth removing the BUFG buffer flag.\n", ctx->nameOf(net));
}
@ -609,7 +624,8 @@ struct GowinGlobalRouter
CellInfo *true_src_ci = net_before_buf->driver.cell;
src = ctx->getBelPinWire(true_src_ci->bel, net_before_buf->driver.port);
ctx->bindWire(src, net, STRENGTH_LOCKED);
backwards_bfs_route(net, src, dst, 1000000, false, [&](PipId pip, WireId src_wire) { return true; });
backwards_bfs_route(net, src, dst, 1000000, false,
[&](PipId pip, WireId src_wire) { return segment_wire_filter(pip); });
// remove net
buf_ci->movePortTo(id_O, true_src_ci, net_before_buf->driver.port);
net_before_buf->driver.cell = nullptr;
@ -619,19 +635,505 @@ struct GowinGlobalRouter
void route_clk_net(NetInfo *net)
{
RouteResult route_result =
route_direct_net(net, [&](PipId pip, WireId src_wire) { return global_pip_filter(pip, src_wire); });
RouteResult route_result = route_direct_net(net, [&](PipId pip, WireId src_wire) {
return global_pip_filter(pip, src_wire) && segment_wire_filter(pip);
});
if (route_result != NOT_ROUTED) {
log_info(" '%s' net was routed using global resources %s.\n", ctx->nameOf(net),
log_info(" '%s' net was routed using global resources %s.\n", ctx->nameOf(net),
route_result == ROUTED_ALL ? "only" : "partially");
}
}
// segmented wires
enum SegmentRouteResult
{
SEG_NOT_ROUTED = 0,
SEG_ROUTED_TO_ANOTHER_SEGMENT,
SEG_ROUTED
};
// Step 0: route LBx1 -> sinks
SegmentRouteResult route_segmented_step0(NetInfo *ni, Loc dst_loc, WireId dst_wire, int s_idx, int s_x,
std::vector<PipId> &bound_pips)
{
bool routed = false;
WireId lbo_wire = ctx->getWireByName(
IdStringList::concat(ctx->idf("X%dY%d", s_x, dst_loc.y), ctx->idf("LBO%d", s_idx / 4)));
if (ctx->debug) {
log_info(" step 0: %s -> %s\n", ctx->nameOfWire(lbo_wire), ctx->nameOfWire(dst_wire));
}
routed = backwards_bfs_route(
ni, lbo_wire, dst_wire, 1000000, false, [&](PipId pip, WireId src) { return true; }, &bound_pips);
return routed ? SEG_ROUTED : SEG_ROUTED_TO_ANOTHER_SEGMENT;
}
// Step 1: segment wire -> LBOx
SegmentRouteResult route_segmented_step1(NetInfo *ni, Loc dst_loc, int s_idx, int s_x,
std::vector<PipId> &bound_pips)
{
IdString tile = ctx->idf("X%dY%d", s_x, dst_loc.y);
IdString lbo_wire_name = ctx->idf("LBO%d", s_idx > 3 ? 1 : 0);
IdStringList pip_dst_name = IdStringList::concat(tile, lbo_wire_name);
// if we used other wire
IdStringList last_pip_src_name = ctx->getWireName(ctx->getPipSrcWire(bound_pips.back()));
if (last_pip_src_name != pip_dst_name) {
if (ctx->debug) {
log_info(" step 1: Already joined the network in another segment at %s. Skip.\n",
last_pip_src_name.str(ctx).c_str());
}
return SEG_ROUTED_TO_ANOTHER_SEGMENT;
}
IdString lt_wire_name = ctx->idf("LT0%d", s_idx > 3 ? 4 : 1);
PipId pip = ctx->getPipByName(IdStringList::concat(pip_dst_name, lt_wire_name));
if (ctx->debug) {
log_info(" step 1: %s -> %s\n", lt_wire_name.c_str(ctx), pip_dst_name.str(ctx).c_str());
}
NPNR_ASSERT(pip != PipId());
NetInfo *pip_net = ctx->getBoundPipNet(pip);
if (pip_net == nullptr) {
ctx->bindPip(pip, ni, STRENGTH_LOCKED);
bound_pips.push_back(pip);
} else {
if (pip_net != ni) {
return SEG_NOT_ROUTED;
}
}
return SEG_ROUTED;
}
// Step 2: gate wire -> segment wire
SegmentRouteResult route_segmented_step2(NetInfo *ni, WireId segment_wire, WireId gate_wire,
std::vector<PipId> &bound_pips)
{
IdStringList pip_name = IdStringList::concat(ctx->getWireName(segment_wire), ctx->getWireName(gate_wire)[1]);
PipId pip = ctx->getPipByName(pip_name);
if (ctx->debug) {
log_info(" step 2: %s\n", ctx->nameOfPip(pip));
}
NPNR_ASSERT(pip != PipId());
NetInfo *pip_net = ctx->getBoundPipNet(pip);
if (pip_net == nullptr) {
ctx->bindPip(pip, ni, STRENGTH_LOCKED);
bound_pips.push_back(pip);
} else {
if (pip_net != ni) {
return SEG_NOT_ROUTED;
}
}
return SEG_ROUTED;
}
// Step 3: route src -> gate wires
SegmentRouteResult route_segmented_step3(NetInfo *ni, pool<WireId> gate_wires, std::vector<PipId> &bound_pips,
pool<WireId> &bound_wires)
{
bool routed = false;
WireId src_wire = ctx->getNetinfoSourceWire(ni);
if (ctx->debug) {
log_info(" step 3: %s -> \n", ctx->nameOfWire(src_wire));
}
for (WireId gatewire : gate_wires) {
if (ctx->debug) {
log_info(" %s\n", ctx->nameOfWire(gatewire));
}
routed = backwards_bfs_route(
ni, src_wire, gatewire, 1000000, false, [&](PipId pip, WireId src) { return true; }, &bound_pips);
if (routed) {
// bind src
if (ctx->checkWireAvail(src_wire)) {
ctx->bindWire(src_wire, ni, STRENGTH_LOCKED);
bound_wires.insert(src_wire);
}
} else {
break;
}
}
return routed ? SEG_ROUTED : SEG_NOT_ROUTED;
}
void route_segmented(std::vector<IdString> &nets)
{
if (ctx->verbose) {
log_info("routing segmented...\n");
}
struct selected_net
{
int sink_cnt;
std::vector<int> segs; // segments
dict<int, WireId> gate_wires; // from logic to segment
dict<int, WireId> tb_wires; // top or bottom segment wire
dict<int, WireId> wire_to_isolate; // this wire should be disconnected to avoid conflict
};
dict<IdString, selected_net> selected_nets;
NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();
auto get_port_loc = [&](PortRef &cell_wire) -> Loc {
BelId bel = cell_wire.cell->bel;
NPNR_ASSERT(bel != BelId());
return ctx->getBelLocation(bel);
};
for (IdString net_name : nets) {
NetInfo *ni = ctx->nets.at(net_name).get();
// We restrict the considered networks from above because networks
// with a large number of sinks have all chances to cross quadrant
// boundaries and for such large global networks it is better to
// use free clock wires.
int sinks_num = ni->users.entries();
if (ni->driver.cell == nullptr || sinks_num < 8 || sinks_num > 50 || ni == vcc_net || ni == vss_net) {
continue;
}
// We cut off very compact networks because regular wires will
// suffice for them, and using segmented ones takes up a whole
// column in the bank at once.
Loc src_loc = get_port_loc(ni->driver);
if (ctx->debug) {
log_info(" net:%s, src:(%d, %d) %s\n", ctx->nameOf(ni), src_loc.y, src_loc.x,
ni->driver.port.c_str(ctx));
}
int far_sink_cnt = 0;
for (auto sink : ni->users) {
Loc sink_loc = get_port_loc(sink);
if (ctx->debug) {
log_info(" sink:(%d, %d) %s\n", sink_loc.y, sink_loc.x, sink.port.c_str(ctx));
}
if (std::abs(sink_loc.x - src_loc.x) > 4 || std::abs(sink_loc.y - src_loc.y) > 4) {
++far_sink_cnt;
}
}
if (far_sink_cnt > 10) {
if (ctx->debug) {
log_info(" far sinks:%d, net is selected for processing.\n", far_sink_cnt);
}
selected_nets[net_name].sink_cnt = far_sink_cnt;
}
}
// Now that we have selected candidate grids, let's put them into a
// structure convenient for working with each grid cell of the chip
// individually.
// Each segment "serves" a rectangular area, the width and height of
// which depends on the position of the tap from the horizontal
// "spine" wire.
// The areas of neighboring taps overlap, but not completely, so we'll
// have to handle the sinks of the nets cell by cell.
// Another reason why we have to work with each cell individually,
// instead of using the total number of sinks of a particular network
// in the whole rectangular area, is that it makes sense to connect the
// sinks that are in the immediate neighborhood of the network source
// with ordinary wires.
struct grid_net
{
IdString net;
int sink_cnt; // It is not currently used, but it may be useful if
// the network search algorithm is based on the
// number of sinks in the segment's service region.
};
// The largest Gowin chip to date (GW5A-138) contains 138000 LUTs,
// which is a rough estimate without taking into account the placement
// of a few LUTs in the cell gives 400 columns and 400 rows. We use
// the combination of row number << 16 and column number as a key.
auto xy_to_key = [&](int x, int y) -> uint32_t { return (y << 16) | x; };
std::unordered_multimap<uint32_t, grid_net> grid;
int min_x = ctx->getGridDimX();
int max_x = -1;
int min_y = ctx->getGridDimY();
int max_y = -1;
for (auto &net : selected_nets) {
IdString net_name = net.first;
NetInfo *ni = ctx->nets.at(net_name).get();
Loc src_loc = get_port_loc(ni->driver);
for (auto sink : ni->users) {
Loc sink_loc = get_port_loc(sink);
min_x = std::min(min_x, sink_loc.x);
max_x = std::max(max_x, sink_loc.x);
min_y = std::min(min_y, sink_loc.y);
max_y = std::max(max_y, sink_loc.y);
if (std::abs(sink_loc.x - src_loc.x) > 4 || std::abs(sink_loc.y - src_loc.y) > 4) {
uint32_t key = xy_to_key(sink_loc.x, sink_loc.y);
bool found = false;
if (grid.count(key)) {
auto net_range = grid.equal_range(key);
for (auto it = net_range.first; it != net_range.second; ++it) {
if (it->second.net == net_name) {
found = true;
++(it->second.sink_cnt);
}
}
}
if (!found) {
grid_net new_cell;
new_cell.net = net_name;
new_cell.sink_cnt = 1;
grid.insert(std::make_pair(key, new_cell));
}
}
}
}
if (ctx->debug) {
log_info("Net grid. (%d, %d) <=> (%d, %d)\n", min_y, min_x, max_y, max_x);
for (auto it = grid.begin(); it != grid.end(); ++it) {
log_info(" (%d, %d): %s %d\n", it->first >> 16, it->first & 0xffff, it->second.net.c_str(ctx),
it->second.sink_cnt);
}
}
// Net -> s_idx (0 <= s_idx < 8 -indices of vertical segments)
dict<IdString, int> net_to_s_idx;
// We search all segmental columns, ignoring those that do not fall
// into the grid of networks
for (int s_i = 0; s_i < gwu.get_segments_count(); ++s_i) {
int s_x, s_idx, s_min_x, s_min_y, s_max_x, s_max_y;
gwu.get_segment_region(s_i, s_idx, s_x, s_min_x, s_min_y, s_max_x, s_max_y);
// skip empty (in sense of net sinks) segments
if (s_max_x < min_x || s_min_x > max_x || s_max_y < min_y || s_min_y > max_y) {
continue;
}
if (ctx->debug) {
log_info("segment:%d/%d, x:%d, (%d, %d) <=> (%d, %d)\n", s_i, s_idx, s_x, s_min_y, s_min_x, s_max_y,
s_max_x);
}
// Selecting networks whose sinks fall in the served region.
// Networks with an already assigned segment index are prioritized
// over the rest, among which the network with the maximum number
// of sinks is selected.
bool found_net_with_index = false;
IdString net;
int sink_cnt = 0;
for (int y = s_min_y; y <= s_max_y && (!found_net_with_index); ++y) {
for (int x = s_min_x; x <= s_max_x && (!found_net_with_index); ++x) {
auto net_range = grid.equal_range(xy_to_key(x, y));
for (auto it = net_range.first; it != net_range.second; ++it) {
if (net_to_s_idx.count(it->second.net)) {
if (net_to_s_idx.at(it->second.net) == s_idx) {
// far network already use our segment index - reuse it
found_net_with_index = true;
net = it->second.net;
sink_cnt = selected_nets.at(it->second.net).sink_cnt;
break;
}
continue;
}
// new net, calculate maximum sinks
if (selected_nets.at(it->second.net).sink_cnt > sink_cnt) {
sink_cnt = selected_nets.at(it->second.net).sink_cnt;
net = it->second.net;
}
}
}
}
// no suitable nets, segment is unused, skip
if (sink_cnt == 0) {
continue;
}
if (!found_net_with_index) {
// new net
if (ctx->debug) {
log_info(" new net: %s, index:%d\n", net.c_str(ctx), s_idx);
}
net_to_s_idx[net] = s_idx;
} else {
// old net
if (ctx->debug) {
log_info(" old net: %s, index:%d\n", net.c_str(ctx), s_idx);
}
}
selected_nets.at(net).segs.push_back(s_i);
}
// Sort in descending order of the number of segments used.
std::multimap<int, IdString> sorted_nets;
for (auto const &net_seg : net_to_s_idx) {
sorted_nets.insert(std::make_pair(-selected_nets.at(net_seg.first).segs.size(), net_seg.first));
}
// Now that we have all the segments for the networks we need to
// decide which end of the segment (upper or lower) to use
// depending on the distance to the network source.
// This is critical because the signal in a segment can propagate
// from bottom to top or top to bottom and you need to know exactly
// which end to isolate.
for (auto const &net_seg : sorted_nets) {
IdString net = net_seg.second;
NetInfo *ni = ctx->nets.at(net).get();
Loc src_loc = get_port_loc(ni->driver);
if (ctx->debug) {
log_info("net:%s, src:(%d, %d)\n", ctx->nameOf(ni), src_loc.y, src_loc.x);
}
std::string wires_to_isolate;
for (int s_i : selected_nets.at(net).segs) {
// distances to net source
Loc top_loc, bottom_loc;
gwu.get_segment_wires_loc(s_i, top_loc, bottom_loc);
int top_to_src = std::abs(src_loc.x - top_loc.x) + std::abs(src_loc.y - top_loc.y);
int bottom_to_src = std::abs(src_loc.x - bottom_loc.x) + std::abs(src_loc.y - bottom_loc.y);
if (ctx->debug) {
log_info(" segment:%d, top:(%d, %d), bottom:(%d, %d) dists:%d %d\n", s_i, top_loc.y, top_loc.x,
bottom_loc.y, bottom_loc.x, top_to_src, bottom_to_src);
}
// By selecting the top or bottom end we also select a pair of
// gate wires to use.
WireId tb_wire, gate_wire, top_seg_wire, bottom_seg_wire, wire_to_isolate;
gwu.get_segment_wires(s_i, top_seg_wire, bottom_seg_wire);
tb_wire = top_seg_wire;
if (top_to_src <= bottom_to_src) {
WireId gate_wire1;
gwu.get_segment_top_gate_wires(s_i, gate_wire, gate_wire1);
if (gate_wire == WireId()) {
gate_wire == gate_wire1;
}
if (gate_wire == WireId()) {
// This segment has no top gate wires, so we use one of the bottom ones.
gwu.get_segment_bottom_gate_wires(s_i, gate_wire, gate_wire1);
if (gate_wire == WireId()) {
gate_wire == gate_wire1;
}
tb_wire = bottom_seg_wire;
wire_to_isolate = top_seg_wire;
NPNR_ASSERT(gate_wire != WireId()); // Completely isolated segment. The chip base is damaged.
}
} else {
WireId gate_wire1;
tb_wire = bottom_seg_wire;
wire_to_isolate = top_seg_wire;
gwu.get_segment_bottom_gate_wires(s_i, gate_wire, gate_wire1);
if (gate_wire == WireId()) {
gate_wire == gate_wire1;
}
if (gate_wire == WireId()) {
// This segment has no top gate wires, so we use one of the bottom ones.
gwu.get_segment_top_gate_wires(s_i, gate_wire, gate_wire1);
if (gate_wire == WireId()) {
gate_wire == gate_wire1;
}
tb_wire = top_seg_wire;
wire_to_isolate = WireId();
NPNR_ASSERT(gate_wire != WireId()); // Completely isolated segment. The chip base is damaged.
}
}
selected_nets.at(net).tb_wires[s_i] = tb_wire;
selected_nets.at(net).gate_wires[s_i] = gate_wire;
// store used wires for gowin_pack
if (wire_to_isolate != WireId()) {
wires_to_isolate += ctx->getWireName(wire_to_isolate).str(ctx);
wires_to_isolate += ";";
}
if (ctx->debug) {
log_info(" wire:%s, gate wire:%s\n", ctx->nameOfWire(tb_wire), ctx->nameOfWire(gate_wire));
}
}
// Laying out a route for the network.
std::vector<PipId> bound_pips;
pool<WireId> bound_wires;
SegmentRouteResult routed = SEG_NOT_ROUTED;
pool<WireId> gate_wires;
if (ctx->debug) {
log_info(" Route\n");
}
for (auto usr : ni->users) {
BelId dst_bel = usr.cell->bel;
NPNR_ASSERT(dst_bel != BelId());
Loc dst_loc(ctx->getBelLocation(dst_bel));
WireId dst_wire = ctx->getNetinfoSinkWire(ni, usr, 0);
// find segment covers dest
int s_idx = -1;
int s_x, s_min_x, s_min_y, s_max_x, s_max_y;
WireId tb_wire, gate_wire;
for (int s_i : selected_nets.at(net).segs) {
int idx;
gwu.get_segment_region(s_i, idx, s_x, s_min_x, s_min_y, s_max_x, s_max_y);
if (dst_loc.x >= s_min_x && dst_loc.x <= s_max_x && dst_loc.y >= s_min_y && dst_loc.y <= s_max_y) {
s_idx = idx;
tb_wire = selected_nets.at(net).tb_wires.at(s_i);
gate_wire = selected_nets.at(net).gate_wires.at(s_i);
break;
}
}
if (ctx->debug) {
log_info(" segment index:%d, dst:%s\n", s_idx, ctx->nameOf(usr.cell));
}
// There may not be a suitable segment if the sink is close to
// the source. In that case consider these sinks along with
// gate wires.
if (s_idx == -1) {
gate_wires.insert(dst_wire);
} else {
// Step 0: LBx1 -> dest
routed = route_segmented_step0(ni, dst_loc, dst_wire, s_idx, s_x, bound_pips);
if (routed == SEG_NOT_ROUTED) {
break;
}
// Step 1: segment wire -> LBOx
routed = route_segmented_step1(ni, dst_loc, s_idx, s_x, bound_pips);
if (routed == SEG_NOT_ROUTED) {
break;
}
if (routed == SEG_ROUTED_TO_ANOTHER_SEGMENT) {
continue;
}
// Step 2: gate wire -> segment wire
routed = route_segmented_step2(ni, tb_wire, gate_wire, bound_pips);
if (routed == SEG_NOT_ROUTED) {
break;
}
// mark gate for step 3
gate_wires.insert(gate_wire);
}
}
// Step 3: src -> gate wire
routed = route_segmented_step3(ni, gate_wires, bound_pips, bound_wires);
if (routed == SEG_NOT_ROUTED) {
if (ctx->verbose || ctx->debug) {
log_warning("Can't route net %s using segments.\n", ctx->nameOf(ni));
}
// unbind pips and wires
for (PipId pip : bound_pips) {
ctx->unbindPip(pip);
}
for (WireId wire : bound_wires) {
ctx->unbindWire(wire);
}
} else {
// make list of wires for isolation
if (!wires_to_isolate.empty()) {
ni->attrs[id_SEG_WIRES_TO_ISOLATE] = wires_to_isolate;
}
if (ctx->verbose) {
log_info("Net %s is routed using segments.\n", ctx->nameOf(ni));
}
if (ctx->debug) {
log_info(" routed\n");
for (PipId pip : bound_pips) {
log_info(" %s\n", ctx->nameOfPip(pip));
}
for (WireId wire : bound_wires) {
log_info(" %s\n", ctx->nameOfWire(wire));
}
}
}
}
}
// Route all
void run(void)
{
log_info("Routing globals...\n");
std::vector<IdString> dhcen_nets, dqce_nets, dcs_nets, buf_nets, clk_nets;
std::vector<IdString> dhcen_nets, dqce_nets, dcs_nets, buf_nets, clk_nets, seg_nets;
// Determining the priority of network routing
for (auto &net : ctx->nets) {
@ -657,6 +1159,8 @@ struct GowinGlobalRouter
} else {
if (driver_is_dhcen(ni->driver)) {
dhcen_nets.push_back(net.first);
} else {
seg_nets.push_back(net.first);
}
}
}
@ -722,6 +1226,11 @@ struct GowinGlobalRouter
}
route_clk_net(ni);
}
// segmented nets
if (gwu.get_segments_count() != 0) {
route_segmented(seg_nets);
}
}
};

View File

@ -214,7 +214,8 @@ void GowinImpl::init(Context *ctx)
// We do not allow the use of global wires that bypass a special router.
bool GowinImpl::checkPipAvail(PipId pip) const
{
return (ctx->getWireConstantValue(ctx->getPipSrcWire(pip)) != IdString()) || (!gwu.is_global_pip(pip));
return (ctx->getWireConstantValue(ctx->getPipSrcWire(pip)) != IdString()) ||
(!(gwu.is_global_pip(pip) || gwu.is_segment_pip(pip)));
}
void GowinImpl::pack()

View File

@ -149,6 +149,21 @@ NPNR_PACKED_STRUCT(struct Wire_bel_POD {
int32_t side;
});
NPNR_PACKED_STRUCT(struct Segment_POD {
int16_t x;
int16_t seg_idx;
int16_t min_x;
int16_t min_y;
int16_t max_x;
int16_t max_y;
int16_t top_row;
int16_t bottom_row;
uint32_t top_wire;
uint32_t bottom_wire;
RelSlice<uint32_t> top_gate_wire;
RelSlice<uint32_t> bottom_gate_wire;
});
NPNR_PACKED_STRUCT(struct Constraint_POD {
int32_t net;
int32_t row;
@ -166,6 +181,7 @@ NPNR_PACKED_STRUCT(struct Extra_chip_data_POD {
RelSlice<Spine_bel_POD> dqce_bels;
RelSlice<Spine_bel_POD> dcs_bels;
RelSlice<Wire_bel_POD> dhcen_bels;
RelSlice<Segment_POD> segments;
// chip flags
static constexpr int32_t HAS_SP32 = 1;
static constexpr int32_t NEED_SP_FIX = 2;

View File

@ -202,6 +202,44 @@ class WireBel(BBAStruct):
bba.u32(self.bel_z)
bba.u32(self.hclk_side.index)
# segment column description
@dataclass
class Segment(BBAStruct):
x: int
seg_idx: int
min_x: int
min_y: int
max_x: int
max_y: int
top_row: int
bottom_row: int
top_wire: IdString
bottom_wire: IdString
top_gate_wire: list[IdString] = field(default_factory = list)
bottom_gate_wire: list[IdString] = field(default_factory = list)
def serialise_lists(self, context: str, bba: BBAWriter):
bba.label(f"{context}_top_gate_wire")
for i, wire in enumerate(self.top_gate_wire):
bba.u32(wire.index)
bba.label(f"{context}_bottom_gate_wire")
for i, wire in enumerate(self.bottom_gate_wire):
bba.u32(wire.index)
def serialise(self, context: str, bba: BBAWriter):
bba.u16(self.x)
bba.u16(self.seg_idx)
bba.u16(self.min_x)
bba.u16(self.min_y)
bba.u16(self.max_x)
bba.u16(self.max_y)
bba.u16(self.top_row)
bba.u16(self.bottom_row)
bba.u32(self.top_wire.index)
bba.u32(self.bottom_wire.index)
bba.slice(f"{context}_top_gate_wire", len(self.top_gate_wire))
bba.slice(f"{context}_bottom_gate_wire", len(self.bottom_gate_wire))
@dataclass
class ChipExtraData(BBAStruct):
strs: StringPool
@ -211,6 +249,7 @@ class ChipExtraData(BBAStruct):
dqce_bels: list[SpineBel] = field(default_factory = list)
dcs_bels: list[SpineBel] = field(default_factory = list)
dhcen_bels: list[WireBel] = field(default_factory = list)
segments: list[Segment] = field(default_factory = list)
def create_bottom_io(self):
self.bottom_io = BottomIO()
@ -230,8 +269,25 @@ class ChipExtraData(BBAStruct):
def add_dcs_bel(self, spine: str, x: int, y: int, z: int):
self.dcs_bels.append(SpineBel(self.strs.id(spine), x, y, z))
def add_segment(self, x: int, seg_idx: int, min_x: int, min_y: int, max_x: int, max_y: int,
top_row: int, bottom_row: int, top_wire: str, bottom_wire: str, top_gate_wire: list, bottom_gate_wire: list):
new_seg = Segment(x, seg_idx, min_x, min_y, max_x, max_y, top_row, bottom_row,
self.strs.id(top_wire), self.strs.id(bottom_wire),
[self.strs.id(top_gate_wire[0])], [self.strs.id(bottom_gate_wire[0])])
if top_gate_wire[1]:
new_seg.top_gate_wire.append(self.strs.id(top_gate_wire[1]))
else:
new_seg.top_gate_wire.append(self.strs.id(''))
if bottom_gate_wire[1]:
new_seg.bottom_gate_wire.append(self.strs.id(bottom_gate_wire[1]))
else:
new_seg.bottom_gate_wire.append(self.strs.id(''))
self.segments.append(new_seg)
def serialise_lists(self, context: str, bba: BBAWriter):
self.bottom_io.serialise_lists(f"{context}_bottom_io", bba)
for i, t in enumerate(self.segments):
t.serialise_lists(f"{context}_segment{i}", bba)
bba.label(f"{context}_diff_io_types")
for i, diff_io_type in enumerate(self.diff_io_types):
bba.u32(diff_io_type.index)
@ -244,6 +300,9 @@ class ChipExtraData(BBAStruct):
bba.label(f"{context}_dhcen_bels")
for i, t in enumerate(self.dhcen_bels):
t.serialise(f"{context}_dhcen_bel{i}", bba)
bba.label(f"{context}_segments")
for i, t in enumerate(self.segments):
t.serialise(f"{context}_segment{i}", bba)
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.flags)
@ -252,6 +311,7 @@ class ChipExtraData(BBAStruct):
bba.slice(f"{context}_dqce_bels", len(self.dqce_bels))
bba.slice(f"{context}_dcs_bels", len(self.dcs_bels))
bba.slice(f"{context}_dhcen_bels", len(self.dhcen_bels))
bba.slice(f"{context}_segments", len(self.segments))
@dataclass
class PackageExtraData(BBAStruct):
@ -402,6 +462,8 @@ def create_switch_matrix(tt: TileType, db: chipdb, x: int, y: int):
def get_wire_type(name):
if name in {'XD0', 'XD1', 'XD2', 'XD3', 'XD4', 'XD5',}:
return "X0"
if name in {'LT00', 'LT10', 'LT20', 'LT30', 'LT02', 'LT13'}:
return "LW_TAP"
return ""
for dst, srcs in db.grid[y][x].pips.items():
@ -1284,6 +1346,23 @@ def create_extra_data(chip: Chip, db: chipdb, chip_flags: int):
# create spine->dcs bel map
for spine, bel in dcs_bels.items():
chip.extra_data.add_dcs_bel(spine, bel[0], bel[1], bel[2])
# create segments
if hasattr(db, "segments"):
for y_x_idx, seg in db.segments.items():
_, x, idx = y_x_idx
chip.extra_data.add_segment(x, idx, seg['min_x'], seg['min_y'], seg['max_x'], seg['max_y'],
seg['top_row'], seg['bottom_row'], seg['top_wire'], seg['bottom_wire'],
seg['top_gate_wire'], seg['bottom_gate_wire'])
# add segment nodes
lt_node = [NodeWire(x, seg['top_row'], seg['top_wire'])]
lt_node.append(NodeWire(x, seg['bottom_row'], seg['bottom_wire']))
for row in range(seg['min_y'], seg['max_y'] + 1):
lt_node.append(NodeWire(x, row, f'LT0{1 + (idx // 4) * 3}'))
node = [NodeWire(x, row, f'LBO{idx // 4}')]
for col in range(seg['min_x'], seg['max_x'] + 1):
node.append(NodeWire(col, row, f'LB{idx}1'))
chip.add_node(node)
chip.add_node(lt_node)
def create_timing_info(chip: Chip, db: chipdb.Device):
def group_to_timingvalue(group):

View File

@ -12,6 +12,90 @@
NEXTPNR_NAMESPACE_BEGIN
// Segments
int GowinUtils::get_segments_count(void) const
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
return extra->segments.ssize();
}
void GowinUtils::get_segment_region(int s_i, int &seg_idx, int &x, int &min_x, int &min_y, int &max_x, int &max_y) const
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
seg_idx = extra->segments[s_i].seg_idx;
x = extra->segments[s_i].x;
min_x = extra->segments[s_i].min_x;
min_y = extra->segments[s_i].min_y;
max_x = extra->segments[s_i].max_x;
max_y = extra->segments[s_i].max_y;
}
void GowinUtils::get_segment_wires_loc(int s_i, Loc &top, Loc &bottom) const
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
top.x = bottom.x = extra->segments[s_i].x;
top.y = extra->segments[s_i].top_row;
bottom.y = extra->segments[s_i].bottom_row;
}
void GowinUtils::get_segment_wires(int s_i, WireId &top, WireId &bottom) const
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
Loc top_loc, bottom_loc;
get_segment_wires_loc(s_i, top_loc, bottom_loc);
IdString tile = ctx->idf("X%dY%d", top_loc.x, top_loc.y);
IdStringList name = IdStringList::concat(tile, IdString(extra->segments[s_i].top_wire));
top = ctx->getWireByName(name);
tile = ctx->idf("X%dY%d", bottom_loc.x, bottom_loc.y);
name = IdStringList::concat(tile, IdString(extra->segments[s_i].bottom_wire));
bottom = ctx->getWireByName(name);
}
void GowinUtils::get_segment_top_gate_wires(int s_i, WireId &wire0, WireId &wire1) const
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
Loc top_loc, bottom_loc;
get_segment_wires_loc(s_i, top_loc, bottom_loc);
IdString tile = ctx->idf("X%dY%d", top_loc.x, top_loc.y);
IdStringList name;
wire0 = WireId();
IdString wire_name = IdString(extra->segments[s_i].top_gate_wire[0]);
if (wire_name != IdString()) {
name = IdStringList::concat(tile, wire_name);
wire0 = ctx->getWireByName(name);
}
wire1 = WireId();
wire_name = IdString(extra->segments[s_i].top_gate_wire[1]);
if (wire_name != IdString()) {
name = IdStringList::concat(tile, wire_name);
wire1 = ctx->getWireByName(name);
}
}
void GowinUtils::get_segment_bottom_gate_wires(int s_i, WireId &wire0, WireId &wire1) const
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
Loc top_loc, bottom_loc;
get_segment_wires_loc(s_i, top_loc, bottom_loc);
IdString tile = ctx->idf("X%dY%d", bottom_loc.x, bottom_loc.y);
IdStringList name;
wire0 = WireId();
IdString wire_name = IdString(extra->segments[s_i].bottom_gate_wire[0]);
if (wire_name != IdString()) {
name = IdStringList::concat(tile, wire_name);
wire0 = ctx->getWireByName(name);
}
wire1 = WireId();
wire_name = IdString(extra->segments[s_i].bottom_gate_wire[1]);
if (wire_name != IdString()) {
name = IdStringList::concat(tile, wire_name);
wire1 = ctx->getWireByName(name);
}
}
// tile extra data
IdString GowinUtils::get_tile_class(int x, int y)
{

View File

@ -24,6 +24,12 @@ struct GowinUtils
IdString get_tile_class(int x, int y);
Loc get_tile_io16_offs(int x, int y);
bool get_i3c_capable(int x, int y);
inline Loc get_wire_loc(WireId wire) const
{
Loc loc;
tile_xy(ctx->chip_info, wire.tile, loc.x, loc.y);
return loc;
}
// pin functions: GCLKT_4, SSPI_CS, READY etc
IdStringList get_pin_funcs(BelId io_bel);
@ -39,6 +45,14 @@ struct GowinUtils
BelId get_dcs_bel(IdString spine_name);
BelId get_dhcen_bel(WireId hclkin_wire, IdString &side);
// Segments
int get_segments_count(void) const;
void get_segment_region(int s_i, int &seg_idx, int &x, int &min_x, int &min_y, int &max_x, int &max_y) const;
void get_segment_wires_loc(int s_i, Loc &top, Loc &bottom) const;
void get_segment_wires(int s_i, WireId &top, WireId &bottom) const;
void get_segment_top_gate_wires(int s_i, WireId &wire0, WireId &wire1) const;
void get_segment_bottom_gate_wires(int s_i, WireId &wire0, WireId &wire1) const;
// ports
inline bool port_used(CellInfo *cell, IdString port_name)
{
@ -110,6 +124,8 @@ struct GowinUtils
return is_global_wire(ctx->getPipSrcWire(pip)) || is_global_wire(ctx->getPipDstWire(pip));
}
inline bool is_segment_pip(PipId pip) const { return ctx->getWireType(ctx->getPipDstWire(pip)) == id_LW_TAP; }
// construct name
IdString create_aux_name(IdString main_name, int idx = 0, const char *str_suffix = "_aux$");