mirror of https://github.com/YosysHQ/nextpnr.git
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:
parent
d8988e1682
commit
864c1e471d
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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$");
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue