diff --git a/himbaechel/uarch/gowin/constids.inc b/himbaechel/uarch/gowin/constids.inc index a33dc5791a..a748540713 100644 --- a/himbaechel/uarch/gowin/constids.inc +++ b/himbaechel/uarch/gowin/constids.inc @@ -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) diff --git a/himbaechel/uarch/gowin/globals.cc b/himbaechel/uarch/gowin/globals.cc index f71af38c0d..66211b85ad 100644 --- a/himbaechel/uarch/gowin/globals.cc +++ b/himbaechel/uarch/gowin/globals.cc @@ -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 { @@ -358,7 +360,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)); } @@ -435,8 +441,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)); } @@ -525,10 +533,13 @@ struct GowinGlobalRouter std::vector 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) { @@ -598,7 +609,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)); } @@ -608,7 +623,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; @@ -618,19 +634,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 &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 &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 &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 gate_wires, std::vector &bound_pips, + pool &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 &nets) + { + if (ctx->verbose) { + log_info("routing segmented...\n"); + } + + struct selected_net + { + int sink_cnt; + std::vector segs; // segments + dict gate_wires; // from logic to segment + dict tb_wires; // top or bottom segment wire + dict wire_to_isolate; // this wire should be disconnected to avoid conflict + }; + + dict 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 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 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 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 bound_pips; + pool bound_wires; + SegmentRouteResult routed = SEG_NOT_ROUTED; + pool 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 dhcen_nets, dqce_nets, dcs_nets, buf_nets, clk_nets; + std::vector dhcen_nets, dqce_nets, dcs_nets, buf_nets, clk_nets, seg_nets; // Determining the priority of network routing for (auto &net : ctx->nets) { @@ -656,6 +1158,8 @@ struct GowinGlobalRouter } else { if (driver_is_dhcen(ni->driver)) { dhcen_nets.push_back(net.first); + } else { + seg_nets.push_back(net.first); } } } @@ -721,6 +1225,11 @@ struct GowinGlobalRouter } route_clk_net(ni); } + + // segmented nets + if (gwu.get_segments_count() != 0) { + route_segmented(seg_nets); + } } }; diff --git a/himbaechel/uarch/gowin/gowin.cc b/himbaechel/uarch/gowin/gowin.cc index aa70669e6a..369794ed62 100644 --- a/himbaechel/uarch/gowin/gowin.cc +++ b/himbaechel/uarch/gowin/gowin.cc @@ -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() diff --git a/himbaechel/uarch/gowin/gowin.h b/himbaechel/uarch/gowin/gowin.h index 0c7a24d9c4..fbae596ac9 100644 --- a/himbaechel/uarch/gowin/gowin.h +++ b/himbaechel/uarch/gowin/gowin.h @@ -145,6 +145,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 top_gate_wire; + RelSlice bottom_gate_wire; +}); + NPNR_PACKED_STRUCT(struct Constraint_POD { int32_t net; int32_t row; @@ -162,6 +177,7 @@ NPNR_PACKED_STRUCT(struct Extra_chip_data_POD { RelSlice dqce_bels; RelSlice dcs_bels; RelSlice dhcen_bels; + RelSlice segments; // chip flags static constexpr int32_t HAS_SP32 = 1; static constexpr int32_t NEED_SP_FIX = 2; diff --git a/himbaechel/uarch/gowin/gowin_arch_gen.py b/himbaechel/uarch/gowin/gowin_arch_gen.py index 7c2169ca26..9b65d2388c 100644 --- a/himbaechel/uarch/gowin/gowin_arch_gen.py +++ b/himbaechel/uarch/gowin/gowin_arch_gen.py @@ -200,6 +200,44 @@ def serialise(self, context: str, bba: BBAWriter): 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 @@ -209,6 +247,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() @@ -228,8 +267,25 @@ def add_dqce_bel(self, spine: str, x: int, y: int, z: int): 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) @@ -242,6 +298,9 @@ def serialise_lists(self, context: str, bba: BBAWriter): 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) @@ -250,6 +309,7 @@ def serialise(self, context: str, bba: BBAWriter): 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): @@ -400,6 +460,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(): @@ -1282,6 +1344,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): diff --git a/himbaechel/uarch/gowin/gowin_utils.cc b/himbaechel/uarch/gowin/gowin_utils.cc index f1d1c7a855..8631f889a4 100644 --- a/himbaechel/uarch/gowin/gowin_utils.cc +++ b/himbaechel/uarch/gowin/gowin_utils.cc @@ -12,6 +12,90 @@ NEXTPNR_NAMESPACE_BEGIN +// Segments +int GowinUtils::get_segments_count(void) const +{ + const Extra_chip_data_POD *extra = reinterpret_cast(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(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(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(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(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(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) { diff --git a/himbaechel/uarch/gowin/gowin_utils.h b/himbaechel/uarch/gowin/gowin_utils.h index 40894319c7..98a46732ad 100644 --- a/himbaechel/uarch/gowin/gowin_utils.h +++ b/himbaechel/uarch/gowin/gowin_utils.h @@ -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) { @@ -108,6 +122,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$");