diff --git a/common/kernel/arch_api.h b/common/kernel/arch_api.h index c7cb7f87..6be6ce31 100644 --- a/common/kernel/arch_api.h +++ b/common/kernel/arch_api.h @@ -150,6 +150,10 @@ template struct ArchAPI : BaseCtx std::vector> &placement) const = 0; // Routing methods virtual void expandBoundingBox(BoundingBox &bb) const = 0; + // Resource methods + virtual GroupId getResourceKeyForPip(PipId pip) const = 0; + virtual int getResourceValueForPip(PipId pip) const = 0; + virtual bool isGroupResource(GroupId group) const = 0; // Flow methods virtual bool pack() = 0; diff --git a/common/kernel/base_arch.h b/common/kernel/base_arch.h index db02dc42..e0b9e014 100644 --- a/common/kernel/base_arch.h +++ b/common/kernel/base_arch.h @@ -452,6 +452,11 @@ template struct BaseArch : ArchAPI bb.y1 = std::min(bb.y1 + 1, this->getGridDimY()); } + // Resource methods + virtual GroupId getResourceKeyForPip(PipId /*pip*/) const override { return GroupId(); } + virtual int getResourceValueForPip(PipId /*pip*/) const override { return 0; } + virtual bool isGroupResource(GroupId /*group*/) const override { return false; } + // Flow methods virtual void assignArchInfo() override {}; diff --git a/common/route/router2.cc b/common/route/router2.cc index 304b3b08..0851a412 100644 --- a/common/route/router2.cc +++ b/common/route/router2.cc @@ -39,6 +39,7 @@ #include "log.h" #include "nextpnr.h" +#include "nextpnr_assertions.h" #include "router1.h" #include "scope_lock.h" #include "timing.h" @@ -49,6 +50,12 @@ NEXTPNR_NAMESPACE_BEGIN namespace { struct Router2 { + // std::pair is a bit too confusing, so: + struct NetResourceData + { + int value = 0; + int count = 0; + }; struct PerArcData { @@ -64,6 +71,7 @@ struct Router2 WireId src_wire; dict> wires; std::vector> arcs; + dict resources; BoundingBox bb; // Coordinates of the center of the net, used for the weight-to-average int cx, cy, hpwl; @@ -99,6 +107,14 @@ struct Router2 float cost_fwd = 0.0, cost_bwd = 0.0; }; + struct PerResourceData + { + GroupId key; + // Historical congestion cost + dict value_count; + float hist_cong_cost = 1.0; + }; + Context *ctx; Router2Cfg cfg; @@ -237,6 +253,36 @@ struct Router2 } } + dict resource_to_idx; + dict wire_to_resource; + std::vector flat_resources; + + PerResourceData &resource_data(GroupId r) { return flat_resources[resource_to_idx.at(r)]; } + + void setup_resources() + { + for (auto resource_key : ctx->getGroups()) { + if (!ctx->isGroupResource(resource_key)) + continue; + + auto entry = resource_to_idx.find(resource_key); + + if (entry == resource_to_idx.end()) { + auto data = PerResourceData{}; + data.key = resource_key; + resource_to_idx.insert({resource_key, flat_resources.size()}); + flat_resources.push_back(data); + + entry = resource_to_idx.find(resource_key); + } + for (auto pip : ctx->getGroupPips(resource_key)) { + auto dest_wire = ctx->getPipDstWire(pip); + if (wire_to_resource.count(dest_wire) == 0) + wire_to_resource.insert({dest_wire, entry->second}); + } + } + } + struct QueuedWire { @@ -316,8 +362,8 @@ struct Router2 void bind_pip_internal(PerNetData &net, store_index user, int wire, PipId pip) { auto &wd = flat_wires.at(wire); - auto found = net.wires.find(wd.w); - if (found == net.wires.end()) { + auto wire_found = net.wires.find(wd.w); + if (wire_found == net.wires.end()) { // Not yet used for any arcs of this net, add to list net.wires.emplace(wd.w, std::make_pair(pip, 1)); // Increase bound count of wire by 1 @@ -325,22 +371,72 @@ struct Router2 } else { // Already used for at least one other arc of this net // Don't allow two uphill PIPs for the same net and wire - NPNR_ASSERT(found->second.first == pip); + NPNR_ASSERT(wire_found->second.first == pip); // Increase the count of bound arcs - ++found->second.second; + ++wire_found->second.second; + } + + if (pip == PipId()) + return; + + auto resource_key = ctx->getResourceKeyForPip(pip); + if (resource_key == GroupId()) + return; + + auto &rd = resource_data(resource_key); + auto resource_value = ctx->getResourceValueForPip(pip); + + auto resource_found = net.resources.find(resource_key); + + if (resource_found == net.resources.end()) { + net.resources.emplace(resource_key, NetResourceData{resource_value, 1}); + } else { + // net resource value must agree with arc resource value + NPNR_ASSERT(resource_found->second.value == resource_value); + + ++resource_found->second.count; + } + + auto resource_value_count = rd.value_count.find(resource_value); + if (resource_value_count == rd.value_count.end()) { + rd.value_count.insert({resource_value, 1}); + } else { + ++resource_value_count->second; } } void unbind_pip_internal(PerNetData &net, store_index user, WireId wire) { auto &wd = wire_data(wire); - auto &b = net.wires.at(wd.w); - --b.second; - if (b.second == 0) { + auto &wire_found = net.wires.at(wd.w); + auto pip = wire_found.first; + + --wire_found.second; + if (wire_found.second == 0) { // No remaining arcs of this net bound to this wire --wd.curr_cong; net.wires.erase(wd.w); } + + if (pip == PipId()) + return; + + auto resource_key = ctx->getResourceKeyForPip(pip); + if (resource_key == GroupId()) + return; + + auto &rd = resource_data(resource_key); + auto resource_value = ctx->getResourceValueForPip(pip); + auto resource_found = net.resources.at(resource_key); + + --resource_found.count; + --rd.value_count.at(resource_value); + if (resource_found.count == 0) { + net.resources.erase(resource_key); + } + if (rd.value_count.at(resource_value) == 0) { + rd.value_count.erase(resource_value); + } } void ripup_arc(NetInfo *net, store_index user, size_t phys_pin) @@ -372,6 +468,8 @@ struct Router2 int overuse = wd.curr_cong; float hist_cost = 1.0f + crit_weight * (wd.hist_cong_cost - 1.0f); float bias_cost = 0; + float resource_hist_cost = 0.0f; + float resource_present_cost = 0.0f; int source_uses = 0; if (nd.wires.count(wire)) { overuse -= 1; @@ -383,7 +481,16 @@ struct Router2 bias_cost = cfg.bias_cost_factor * (base_cost / int(net->users.entries())) * ((std::abs(pl.x - nd.cx) + std::abs(pl.y - nd.cy)) / float(nd.hpwl)); } - return base_cost * hist_cost * present_cost / (1 + (source_uses * crit_weight)) + bias_cost; + + auto resource_key = wire_to_resource.find(wire); + if (resource_key != wire_to_resource.end()) { + auto &rd = flat_resources.at(resource_key->second); + resource_hist_cost = 1.0f + crit_weight * (rd.hist_cong_cost - 1.0f); + resource_present_cost = 1.0f + rd.value_count.size() * curr_cong_weight * crit_weight; + } + + return base_cost * hist_cost * present_cost / (1 + (source_uses * crit_weight)) + bias_cost + + base_cost * resource_hist_cost * resource_present_cost / (1 + crit_weight); } float get_togo_cost(NetInfo *net, store_index user, int wire, WireId src_sink, bool bwd, float crit_weight) @@ -412,6 +519,12 @@ struct Router2 auto &uh = nd.wires.at(cursor).first; if (uh == PipId()) break; + auto resource_key = ctx->getResourceKeyForPip(uh); + if (resource_key != GroupId()) { + auto &rd = resource_data(resource_key); + if (rd.value_count.size() > 1) + break; + } cursor = ctx->getPipSrcWire(uh); } return (cursor == src_wire); @@ -817,6 +930,14 @@ struct Router2 auto fnd_wire = nd.wires.find(next); if (fnd_wire != nd.wires.end() && fnd_wire->second.first != dh) continue; + // Don't allow the same resource to be bound to the same net with a different value + auto resource_key = ctx->getResourceKeyForPip(dh); + if (resource_key != GroupId()) { + auto fnd_resource = nd.resources.find(resource_key); + if (fnd_resource != nd.resources.end() && + fnd_resource->second.value != ctx->getResourceValueForPip(dh)) + continue; + } if (!thread_test_wire(t, nwd)) continue; // thread safety issue set_visited_fwd(t, next_idx, dh, next_score.delay); @@ -882,6 +1003,14 @@ struct Router2 // Reserved for another net if (nwd.reserved_net != -1 && nwd.reserved_net != net->udata) continue; + // Don't allow the same resource to be bound to the same net with a different value + auto resource_key = ctx->getResourceKeyForPip(uh); + if (resource_key != GroupId()) { + auto fnd_resource = nd.resources.find(resource_key); + if (fnd_resource != nd.resources.end() && + fnd_resource->second.value != ctx->getResourceValueForPip(uh)) + continue; + } if (!thread_test_wire(t, nwd)) continue; // thread safety issue set_visited_bwd(t, next_idx, uh, next_score.delay); @@ -912,8 +1041,15 @@ struct Router2 if (pip == PipId()) { break; } - ROUTE_LOG_DBG(" fwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x, - ctx->getPipLocation(pip).y); + auto resource_key = ctx->getResourceKeyForPip(pip); + if (resource_key != GroupId()) { + ROUTE_LOG_DBG(" fwd pip: %s (%d, %d) %s = %d\n", ctx->nameOfPip(pip), + ctx->getPipLocation(pip).x, ctx->getPipLocation(pip).y, + ctx->nameOfGroup(resource_key), ctx->getResourceValueForPip(pip)); + } else { + ROUTE_LOG_DBG(" fwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), + ctx->getPipLocation(pip).x, ctx->getPipLocation(pip).y); + } cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(pip)); } @@ -944,8 +1080,16 @@ struct Router2 if (pip == PipId()) { break; } - ROUTE_LOG_DBG(" bwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x, - ctx->getPipLocation(pip).y); + auto resource_key = ctx->getResourceKeyForPip(pip); + if (resource_key != GroupId()) { + ROUTE_LOG_DBG(" bwd pip: %s (%d, %d) %s = %d\n", ctx->nameOfPip(pip), + ctx->getPipLocation(pip).x, ctx->getPipLocation(pip).y, + ctx->nameOfGroup(resource_key), ctx->getResourceValueForPip(pip)); + } else { + ROUTE_LOG_DBG(" bwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x, + ctx->getPipLocation(pip).y); + } + cursor_fwd = wire_to_idx.at(ctx->getPipDstWire(pip)); bind_pip_internal(nd, i, cursor_fwd, pip); if (ctx->debug && !is_mt) { @@ -1060,35 +1204,58 @@ struct Router2 int total_wire_use = 0; int overused_wires = 0; - int total_overuse = 0; + int total_wire_overuse = 0; + int total_resource_use = 0; + int overused_resources = 0; + int total_resource_overuse = 0; std::vector route_queue; std::set failed_nets; void update_congestion() { - total_overuse = 0; + total_wire_overuse = 0; overused_wires = 0; total_wire_use = 0; + total_resource_overuse = 0; + overused_resources = 0; + total_resource_use = 0; failed_nets.clear(); - pool already_updated; + pool already_updated_wires; + pool already_updated_resources; for (size_t i = 0; i < nets.size(); i++) { auto &nd = nets.at(i); for (const auto &w : nd.wires) { ++total_wire_use; auto &wd = wire_data(w.first); if (wd.curr_cong > 1) { - if (already_updated.count(w.first)) { - ++total_overuse; + if (already_updated_wires.count(w.first)) { + ++total_wire_overuse; } else { if (curr_cong_weight > 0) wd.hist_cong_cost = std::min(1e9, wd.hist_cong_cost + (wd.curr_cong - 1) * hist_cong_weight); - already_updated.insert(w.first); + already_updated_wires.insert(w.first); ++overused_wires; } failed_nets.insert(i); } } + for (const auto &r : nd.resources) { + ++total_resource_use; + auto &rd = resource_data(r.first); + if (rd.value_count.size() > 1) { + if (already_updated_resources.count(r.first)) { + ++total_resource_overuse; + } else { + if (curr_cong_weight > 0) + rd.hist_cong_cost = + std::min(1e9, rd.hist_cong_cost + (rd.value_count.size() - 1) * hist_cong_weight); + already_updated_resources.insert(r.first); + ++overused_resources; + } + failed_nets.insert(i); + } + } } for (int n : failed_nets) { auto &net_data = nets.at(n); @@ -1502,6 +1669,7 @@ struct Router2 log_info("Running router2...\n"); log_info("Setting up routing resources...\n"); auto rstart = std::chrono::high_resolution_clock::now(); + setup_resources(); setup_nets(); setup_wires(); find_all_reserved_wires(); @@ -1587,19 +1755,24 @@ struct Router2 } } } - if (overused_wires == 0 && tmgfail == 0) { + if (overused_wires == 0 && overused_resources == 0 && tmgfail == 0) { // Try and actually bind nextpnr Arch API wires bind_and_check_all(); } for (auto cn : failed_nets) route_queue.push_back(cn); + std::string resource_str = total_resource_use == 0 + ? "" + : stringf("resources=%d overused=%d overuse=%d ", total_resource_use, + overused_resources, total_resource_overuse); if (timing_driven_ripup) - log_info(" iter=%d wires=%d overused=%d overuse=%d tmgfail=%d archfail=%s\n", iter, total_wire_use, - overused_wires, total_overuse, tmgfail, + log_info(" iter=%d wires=%d overused=%d overuse=%d %stmgfail=%d " + "archfail=%s\n", + iter, total_wire_use, overused_wires, total_wire_overuse, resource_str.c_str(), tmgfail, (overused_wires > 0 || tmgfail > 0) ? "NA" : std::to_string(arch_fail).c_str()); else - log_info(" iter=%d wires=%d overused=%d overuse=%d archfail=%s\n", iter, total_wire_use, - overused_wires, total_overuse, + log_info(" iter=%d wires=%d overused=%d overuse=%d %sarchfail=%s\n", iter, total_wire_use, + overused_wires, total_wire_overuse, resource_str.c_str(), (overused_wires > 0 || tmgfail > 0) ? "NA" : std::to_string(arch_fail).c_str()); ++iter; if (curr_cong_weight < 1e9) diff --git a/docs/archapi.md b/docs/archapi.md index 9166db11..f457a9ab 100644 --- a/docs/archapi.md +++ b/docs/archapi.md @@ -780,3 +780,26 @@ Router Methods As part of `router2` implementation, during congestion update, every third time a net fails to route, this method is executed to expand the bounding box to increase the search space. Default implementation expands by one tile in each direction. + +Resource Methods +--------------- + +Some architectures may have shared resources across pips, e.g. a single bitstream bit controls multiple multiplexers. To produce legal output, these resource APIs can be used to ensure resource "keys" have "values" that agree across pips. Note that a pip has zero or one resource keys, so if a shared resource requires multiple keys, it must have multiple pips in series. + +### GroupId getResourceKeyForPip(PipId pip) const + +Returns the resource key for given pip, or `GroupId()` if a pip has no resource key. + +*BaseArch default: returns `GroupId()`* + +### int getResourceValueForPip(PipId pip) const + +Returns the resource value for given pip, or `0` if a pip has no resource key. + +*BaseArch default: returns `0`* + +### bool isGroupResource(GroupId group) const + +Returns `true` if `group` represents a resource. + +*BaseArch default: returns `false`* diff --git a/himbaechel/arch.h b/himbaechel/arch.h index fc0cfca1..0f48063a 100644 --- a/himbaechel/arch.h +++ b/himbaechel/arch.h @@ -788,6 +788,12 @@ struct Arch : BaseArch // Routing methods void expandBoundingBox(BoundingBox &bb) const override { uarch->expandBoundingBox(bb); }; + // ------------------------------------------------ + // Resource methods + GroupId getResourceKeyForPip(PipId pip) const override { return uarch->getResourceKeyForPip(pip); }; + int getResourceValueForPip(PipId pip) const override { return uarch->getResourceValueForPip(pip); }; + bool isGroupResource(GroupId group) const override { return uarch->isGroupResource(group); }; + // ------------------------------------------------ bool pack() override; diff --git a/himbaechel/himbaechel_api.h b/himbaechel/himbaechel_api.h index 673261ce..dae43273 100644 --- a/himbaechel/himbaechel_api.h +++ b/himbaechel/himbaechel_api.h @@ -130,6 +130,12 @@ struct HimbaechelAPI // Routing methods virtual void expandBoundingBox(BoundingBox &bb) const; + + // Resource methods + virtual GroupId getResourceKeyForPip(PipId pip) const { return GroupId(); }; + virtual int getResourceValueForPip(PipId pip) const { return 0; } + virtual bool isGroupResource(GroupId /*group*/) const { return false; } + // --- Flow hooks --- virtual void pack() {}; // replaces the pack function // Called before and after main placement and routing diff --git a/himbaechel/uarch/gatemate/bitstream.cc b/himbaechel/uarch/gatemate/bitstream.cc index 813a69ae..61e7a1dc 100644 --- a/himbaechel/uarch/gatemate/bitstream.cc +++ b/himbaechel/uarch/gatemate/bitstream.cc @@ -65,10 +65,12 @@ struct BitstreamBackend WireId cursor = dst_wire; bool invert = false; - if (net_info->driver.cell && net_info->driver.cell->type == id_CPE_BRIDGE && - net_info->driver.port == id_MUXOUT) { - int val = int_or_default(net_info->driver.cell->params, id_C_SN, 0) + 1; - invert ^= need_inversion(net_info->driver.cell, ctx->idf("IN%d", val)); + if (net_info->driver.cell && uarch->pass_backtrace.count(net_info->driver.cell->name)) { + auto &bt = uarch->pass_backtrace[net_info->driver.cell->name]; + if (bt.count(net_info->driver.port)) { + IdString src_port = bt[net_info->driver.port]; + invert ^= need_inversion(net_info->driver.cell, src_port); + } } while (cursor != WireId() && cursor != src_wire) { auto it = net_info->wires.find(cursor); diff --git a/himbaechel/uarch/gatemate/constids.inc b/himbaechel/uarch/gatemate/constids.inc index 87b694b1..ea74e427 100644 --- a/himbaechel/uarch/gatemate/constids.inc +++ b/himbaechel/uarch/gatemate/constids.inc @@ -925,6 +925,10 @@ X(CINX) X(CPE_FF_U) // CPE_FF_U pins X(DIN) +X(CLK_INT) +X(EN_INT) +X(CINY2) +X(PINY2) //X(CLK) //X(EN) //X(SR) @@ -958,8 +962,8 @@ X(COMBIN) X(PINX) X(CINY1) //X(PINY1) -X(CINY2) -X(PINY2) +//X(CINY2) +//X(PINY2) X(COUTX) X(POUTX) X(COUTY1) @@ -971,6 +975,10 @@ X(POUTY2) X(CPE_FF_L) // CPE_FF_L pins //X(DIN) +//X(CLK_INT) +//X(EN_INT) +//X(CINY2) +//X(PINY2) //X(CLK) //X(EN) //X(SR) @@ -3220,3 +3228,5 @@ X(timing_pll_clock_core0_i_clk_core180_o) X(timing_pll_clock_core0_i_clk_core270_o) X(timing_pll_clock_core0_i_clk_core90_o) X(timing_pll_locked_steady_reset_i_pll_locked_steady_o) + +X(RESOURCE) diff --git a/himbaechel/uarch/gatemate/delay.cc b/himbaechel/uarch/gatemate/delay.cc index 805e099f..ed8b97fe 100644 --- a/himbaechel/uarch/gatemate/delay.cc +++ b/himbaechel/uarch/gatemate/delay.cc @@ -243,7 +243,7 @@ TimingPortClass GateMateImpl::getPortTimingClass(const CellInfo *cell, IdString return TMG_COMB_OUTPUT; return TMG_COMB_INPUT; } else if (cell->type.in(id_CPE_FF, id_CPE_FF_L, id_CPE_FF_U, id_CPE_LATCH)) { - if (port == id_CLK) + if (port.in(id_CLK_INT)) return TMG_CLOCK_INPUT; clockInfoCount = 1; if (port == id_DOUT) @@ -364,7 +364,7 @@ TimingClockingInfo GateMateImpl::getPortClockingInfo(const CellInfo *cell, IdStr if (cell->type.in(id_CPE_FF, id_CPE_FF_L, id_CPE_FF_U, id_CPE_LATCH)) { bool inverted = int_or_default(cell->params, id_C_CPE_CLK, 0) == 0b01; info.edge = inverted ? FALLING_EDGE : RISING_EDGE; - info.clock_port = id_CLK; + info.clock_port = id_CLK_INT; if (port.in(id_DIN, id_EN, id_SR)) get_setuphold_from_tmg_db(id_timing_del_Setup_D_L, id_timing_del_Hold_D_L, info.setup, info.hold); if (port.in(id_DOUT)) { diff --git a/himbaechel/uarch/gatemate/extra_data.h b/himbaechel/uarch/gatemate/extra_data.h index 96e8cc5b..dc527f29 100644 --- a/himbaechel/uarch/gatemate/extra_data.h +++ b/himbaechel/uarch/gatemate/extra_data.h @@ -43,6 +43,9 @@ NPNR_PACKED_STRUCT(struct GateMatePipExtraDataPOD { uint8_t plane; uint8_t dummy1; uint16_t dummy2; + uint32_t block; + uint32_t resource; + uint32_t group_index; }); NPNR_PACKED_STRUCT(struct GateMateBelPinConstraintPOD { @@ -124,6 +127,26 @@ enum ClusterPlacement PLACE_DB_CONSTR = 32, }; +enum PipMask +{ + C_SELX = 1 << 0, + C_SELY1 = 1 << 1, + C_SELY2 = 1 << 2, + C_SEL_C = 1 << 3, + C_SEL_P = 1 << 4, + C_Y12 = 1 << 5, + C_CX_I = 1 << 6, + C_CY1_I = 1 << 7, + C_CY2_I = 1 << 8, + C_PX_I = 1 << 9, + C_PY1_I = 1 << 10, + C_PY2_I = 1 << 11, + + IS_MULT = 1 << 29, + IS_ADDF = 1 << 30, + IS_COMP = 1 << 31, +}; + struct PllCfgRecord { double weight; diff --git a/himbaechel/uarch/gatemate/gatemate.cc b/himbaechel/uarch/gatemate/gatemate.cc index bee44ad9..ee30e7a4 100644 --- a/himbaechel/uarch/gatemate/gatemate.cc +++ b/himbaechel/uarch/gatemate/gatemate.cc @@ -21,6 +21,7 @@ #include "gatemate.h" #include "log.h" +#include "nextpnr_assertions.h" #include "placer_heap.h" #define GEN_INIT_CONSTIDS @@ -43,6 +44,9 @@ po::options_description GateMateImpl::getUArchOptions() specific.add_options()("strategy", po::value(), "multi-die clock placement strategy (mirror, full or clk1)"); specific.add_options()("force_die", po::value(), "force specific die (example 1A,1B...)"); + specific.add_options()("clk-cp", "use CP lines for CLK and EN"); + specific.add_options()("no-cpe-cp", "do not use CP lines pass through CPE"); + specific.add_options()("no-bridges", "do not use CPE in bridge mode"); return specific; } @@ -112,6 +116,9 @@ void GateMateImpl::init_database(Arch *arch) : fpga_mode == 3 ? "SPEED" : ""); arch->set_speed_grade(speed_grade); + use_cp_for_clk = args.options.count("clk-cp") == 1; + use_cp_for_cpe = args.options.count("no-cpe-cp") == 0; + use_bridges = args.options.count("no-bridges") == 0; } void GateMateImpl::init(Context *ctx) @@ -312,6 +319,16 @@ void GateMateImpl::postPlace() repack(); ctx->assignArchInfo(); used_cpes.resize(ctx->getGridDimX() * ctx->getGridDimY()); + pip_data.resize(ctx->getGridDimX() * ctx->getGridDimY()); + pip_mask.resize(ctx->getGridDimX() * ctx->getGridDimY()); + + auto set_param_mask_data = [&](CellInfo *cell, IdString param, uint32_t pip_mask, uint32_t &mask, uint32_t &data) { + if (cell->params.count(param)) { + mask |= pip_mask; + if (int_or_default(cell->params, param, 0)) + data |= pip_mask; + } + }; for (auto &cell : ctx->cells) { // We need to skip CPE_MULT since using CP outputs is mandatory // even if output is actually not connected @@ -321,11 +338,64 @@ void GateMateImpl::postPlace() marked_used = true; if (marked_used) used_cpes[cell.second.get()->bel.tile] = true; + + uint32_t mask = pip_mask[cell.second.get()->bel.tile]; + uint32_t data = pip_data[cell.second.get()->bel.tile]; + if (cell.second.get()->type == id_CPE_MULT) { + mask |= PipMask::IS_MULT; + data |= PipMask::IS_MULT; + } + if (cell.second.get()->type.in(id_CPE_ADDF, id_CPE_ADDF2)) { + data |= PipMask::IS_ADDF; + mask |= PipMask::IS_ADDF; + } + if (cell.second.get()->type == id_CPE_COMP) { + data |= PipMask::IS_COMP; + mask |= PipMask::IS_COMP; + } + set_param_mask_data(cell.second.get(), id_C_SELX, PipMask::C_SELX, mask, data); + set_param_mask_data(cell.second.get(), id_C_SELY1, PipMask::C_SELY1, mask, data); + set_param_mask_data(cell.second.get(), id_C_SELY2, PipMask::C_SELY2, mask, data); + set_param_mask_data(cell.second.get(), id_C_SEL_C, PipMask::C_SEL_C, mask, data); + set_param_mask_data(cell.second.get(), id_C_SEL_P, PipMask::C_SEL_P, mask, data); + set_param_mask_data(cell.second.get(), id_C_Y12, PipMask::C_Y12, mask, data); + set_param_mask_data(cell.second.get(), id_C_CX_I, PipMask::C_CX_I, mask, data); + set_param_mask_data(cell.second.get(), id_C_CY1_I, PipMask::C_CY1_I, mask, data); + set_param_mask_data(cell.second.get(), id_C_CY2_I, PipMask::C_CY2_I, mask, data); + set_param_mask_data(cell.second.get(), id_C_PX_I, PipMask::C_PX_I, mask, data); + set_param_mask_data(cell.second.get(), id_C_PY1_I, PipMask::C_PY1_I, mask, data); + set_param_mask_data(cell.second.get(), id_C_PY2_I, PipMask::C_PY2_I, mask, data); + pip_mask[cell.second.get()->bel.tile] = mask; + pip_data[cell.second.get()->bel.tile] = data; } } bool GateMateImpl::checkPipAvail(PipId pip) const { const auto &extra_data = *pip_extra_data(pip); + if (!use_cp_for_clk && extra_data.type == PipExtra::PIP_EXTRA_MUX) { + if (extra_data.value == 1 && IdString(extra_data.name).in(id_C_CLKSEL, id_C_ENSEL)) + return false; + } + if (!use_cp_for_cpe && extra_data.type == PipExtra::PIP_EXTRA_MUX && extra_data.resource != 0 && + extra_data.resource <= PipMask::C_PY2_I) { + return false; + } + if (!use_bridges && extra_data.type == PipExtra::PIP_EXTRA_MUX && + IdString(extra_data.name) == ctx->id("CPE.C_SN")) { + return false; + } + if (extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.block != 0)) { + if (pip_mask[pip.tile] & extra_data.block) { + return false; + } + } + if (extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.resource != 0)) { + if (pip_mask[pip.tile] & extra_data.resource) { + if ((pip_data[pip.tile] & extra_data.resource) != (extra_data.value ? extra_data.resource : 0)) { + return false; + } + } + } if (extra_data.type != PipExtra::PIP_EXTRA_MUX || (extra_data.flags & MUX_ROUTING) == 0) return true; if (used_cpes[pip.tile]) @@ -338,6 +408,21 @@ void GateMateImpl::preRoute() route_mult(); route_clock(); ctx->assignArchInfo(); + + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (ni->wires.empty()) + continue; + for (auto &w : ni->wires) { + if (w.second.pip != PipId()) { + const auto &extra_data = *pip_extra_data(w.second.pip); + if (extra_data.type == PipExtra::PIP_EXTRA_MUX && extra_data.resource != 0) { + pip_mask[w.second.pip.tile] |= extra_data.resource; + pip_data[w.second.pip.tile] |= extra_data.value ? extra_data.resource : 0; + } + } + } + } } void GateMateImpl::reassign_bridges(NetInfo *ni, const dict &net_wires, WireId wire, @@ -380,11 +465,18 @@ void GateMateImpl::reassign_bridges(NetInfo *ni, const dict &net NetInfo *new_net = ctx->createNet(ctx->idf("%s$muxout", name.c_str(ctx))); IdString in_port = ctx->idf("IN%d", extra_data.value + 1); - cell->addInput(in_port); - cell->connectPort(in_port, ni); + auto add_port = [&](const IdString id, PortType dir) { + cell->ports[id].name = id; + cell->ports[id].type = dir; + cell->cell_bel_pins[id] = std::vector{id}; + }; - cell->addOutput(id_MUXOUT); + add_port(in_port, PORT_IN); + add_port(id_MUXOUT, PORT_OUT); + + cell->connectPort(in_port, ni); cell->connectPort(id_MUXOUT, new_net); + pass_backtrace[cell->name][id_MUXOUT] = in_port; num++; @@ -392,11 +484,122 @@ void GateMateImpl::reassign_bridges(NetInfo *ni, const dict &net } } +void GateMateImpl::reassign_cplines(NetInfo *ni, const dict &net_wires, WireId wire, + dict &wire_to_net, int &num, IdString in_port) +{ + wire_to_net.insert({wire, ni->name}); + + for (auto pip : ctx->getPipsDownhill(wire)) { + auto dst = ctx->getPipDstWire(pip); + + // Ignore wires not part of the net + auto it = net_wires.find(dst); + if (it == net_wires.end()) + continue; + // Ignore pips if the wire is driven by another pip. + if (pip != it->second.pip) + continue; + // Ignore wires already visited. + if (wire_to_net.count(dst)) + continue; + + const auto &extra_data = *pip_extra_data(pip); + // If not a CP line pip, just recurse. + if (extra_data.type != PipExtra::PIP_EXTRA_MUX || extra_data.resource == 0) { + reassign_cplines(ni, net_wires, dst, wire_to_net, num, in_port); + continue; + } + + // We have a bridge that needs to be translated to a bel. + IdStringList id = ctx->getPipName(pip); + Loc loc = ctx->getPipLocation(pip); + BelId bel = ctx->getBelByLocation({loc.x, loc.y, CPE_CPLINES_Z}); + CellInfo *cell = ctx->getBoundBelCell(bel); + if (!cell) { + IdString name = ctx->idf("cplines$%s", id[0].c_str(ctx)); + cell = ctx->createCell(name, id_CPE_CPLINES); + auto add_port = [&](const IdString id, PortType dir) { + cell->ports[id].name = id; + cell->ports[id].type = dir; + cell->cell_bel_pins[id] = std::vector{id}; + }; + + add_port(id_OUT1, PORT_IN); + add_port(id_OUT2, PORT_IN); + add_port(id_COMPOUT, PORT_IN); + + add_port(id_CINX, PORT_IN); + add_port(id_PINX, PORT_IN); + add_port(id_CINY1, PORT_IN); + add_port(id_PINY1, PORT_IN); + add_port(id_CINY2, PORT_IN); + add_port(id_PINY2, PORT_IN); + + add_port(id_COUTX, PORT_OUT); + add_port(id_POUTX, PORT_OUT); + add_port(id_COUTY1, PORT_OUT); + add_port(id_POUTY1, PORT_OUT); + add_port(id_COUTY2, PORT_OUT); + add_port(id_POUTY2, PORT_OUT); + + ctx->bindBel(bel, cell, PlaceStrength::STRENGTH_FIXED); + } + + cell->setParam(ctx->getGroupName(ctx->getResourceKeyForPip(pip))[1], + Property(extra_data.value, extra_data.bits)); + + // We have to discover the ports needed by this config. + auto input_port_map = dict{ + {ctx->id("CPE.CINX"), id_CINX}, + {ctx->id("CPE.CINY1"), id_CINY1}, + {ctx->id("CPE.CINY2"), id_CINY2}, + {ctx->id("CPE.PINX"), id_PINX}, + {ctx->id("CPE.PINY1"), id_PINY1}, + {ctx->id("CPE.PINY2"), id_PINY2}, + {ctx->id("CPE.OUT1_IN_int"), id_OUT1}, + {ctx->id("CPE.OUT2_IN_int"), id_OUT2}, + {ctx->id("CPE.COMPOUT_IN_int"), id_COMPOUT}, + }; + + auto input_port_name = input_port_map.find(ctx->getWireName(ctx->getPipSrcWire(pip))[1]); + if (input_port_name != input_port_map.end()) { + if (cell->getPort(input_port_name->second) == nullptr) { + cell->connectPort(input_port_name->second, ni); + in_port = input_port_name->second; + } else + NPNR_ASSERT(cell->getPort(input_port_name->second) == ni); + } + + auto output_port_map = + dict{{ctx->id("CPE.COUTX"), id_COUTX}, {ctx->id("CPE.COUTY1"), id_COUTY1}, + {ctx->id("CPE.COUTY2"), id_COUTY2}, {ctx->id("CPE.POUTX"), id_POUTX}, + {ctx->id("CPE.POUTY1"), id_POUTY1}, {ctx->id("CPE.POUTY2"), id_POUTY2}}; + + auto output_port_name = output_port_map.find(ctx->getWireName(ctx->getPipDstWire(pip))[1]); + if (output_port_name != output_port_map.end()) { + NetInfo *new_net = + ctx->createNet(ctx->idf("%s$%s", cell->name.c_str(ctx), output_port_name->second.c_str(ctx))); + + cell->addOutput(output_port_name->second); + cell->connectPort(output_port_name->second, new_net); + pass_backtrace[cell->name][output_port_name->second] = in_port; + + num++; + + reassign_cplines(new_net, net_wires, dst, wire_to_net, num, in_port); + } else { + // this is an internal resource pip; recurse anyway. + reassign_cplines(ni, net_wires, dst, wire_to_net, num, in_port); + } + } +} + void GateMateImpl::postRoute() { int num = 0; pool nets_with_bridges; + pool nets_with_cplines; for (auto &net : ctx->nets) { NetInfo *ni = net.second.get(); @@ -451,19 +654,76 @@ void GateMateImpl::postRoute() } } + num = 0; + + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + for (auto &w : ni->wires) { + if (w.second.pip != PipId()) { + const auto &extra_data = *pip_extra_data(w.second.pip); + if (extra_data.type == PipExtra::PIP_EXTRA_MUX && extra_data.resource != 0) { + nets_with_cplines.insert(ni->name); + } + } + } + } + + for (auto net_name : nets_with_cplines) { + auto *ni = ctx->nets.at(net_name).get(); + auto net_wires = ni->wires; // copy wires to preserve across unbind/rebind. + auto wire_to_net = dict{}; + auto wire_to_port = dict>{}; + + for (auto &usr : ni->users) + for (auto sink_wire : ctx->getNetinfoSinkWires(ni, usr)) { + auto result = wire_to_port.find(sink_wire); + if (result == wire_to_port.end()) + wire_to_port.insert({sink_wire, std::vector{usr}}); + else + result->second.push_back(usr); + } + + // traverse the routing tree to assign bridge nets to wires. + reassign_cplines(ni, net_wires, ctx->getNetinfoSourceWire(ni), wire_to_net, num, IdString()); + + for (auto &pair : net_wires) + ctx->unbindWire(pair.first); + + for (auto &pair : net_wires) { + auto wire = pair.first; + auto pip = pair.second.pip; + auto strength = pair.second.strength; + auto *net = ctx->nets.at(wire_to_net.at(wire)).get(); + if (pip == PipId()) + ctx->bindWire(wire, net, strength); + else + ctx->bindPip(pip, net, strength); + + if (wire_to_port.count(wire)) { + for (auto sink : wire_to_port.at(wire)) { + NPNR_ASSERT(sink.cell != nullptr && sink.port != IdString()); + sink.cell->disconnectPort(sink.port); + sink.cell->connectPort(sink.port, net); + } + } + } + } + dict cfg; dict port_mapping; auto add_input = [&](IdString orig_port, IdString port, bool merged) -> bool { static dict convert_port = { - {ctx->id("CPE.IN1"), id_IN1}, {ctx->id("CPE.IN2"), id_IN2}, {ctx->id("CPE.IN3"), id_IN3}, - {ctx->id("CPE.IN4"), id_IN4}, {ctx->id("CPE.IN5"), id_IN1}, {ctx->id("CPE.IN6"), id_IN2}, - {ctx->id("CPE.IN7"), id_IN3}, {ctx->id("CPE.IN8"), id_IN4}, {ctx->id("CPE.PINY1"), id_PINY1}, - {ctx->id("CPE.CINX"), id_CINX}, {ctx->id("CPE.PINX"), id_PINX}}; + {ctx->id("CPE.IN1"), id_IN1}, {ctx->id("CPE.IN2"), id_IN2}, {ctx->id("CPE.IN3"), id_IN3}, + {ctx->id("CPE.IN4"), id_IN4}, {ctx->id("CPE.IN5"), id_IN1}, {ctx->id("CPE.IN6"), id_IN2}, + {ctx->id("CPE.IN7"), id_IN3}, {ctx->id("CPE.IN8"), id_IN4}, {ctx->id("CPE.PINY1"), id_PINY1}, + {ctx->id("CPE.PINY2"), id_PINY2}, {ctx->id("CPE.CINY2"), id_CINY2}, {ctx->id("CPE.CLK"), id_CLK}, + {ctx->id("CPE.EN"), id_EN}, {ctx->id("CPE.CINX"), id_CINX}, {ctx->id("CPE.PINX"), id_PINX}}; static dict convert_port_merged = { - {ctx->id("CPE.IN1"), id_IN1}, {ctx->id("CPE.IN2"), id_IN2}, {ctx->id("CPE.IN3"), id_IN3}, - {ctx->id("CPE.IN4"), id_IN4}, {ctx->id("CPE.IN5"), id_IN5}, {ctx->id("CPE.IN6"), id_IN6}, - {ctx->id("CPE.IN7"), id_IN7}, {ctx->id("CPE.IN8"), id_IN8}, {ctx->id("CPE.PINY1"), id_PINY1}, - {ctx->id("CPE.CINX"), id_CINX}, {ctx->id("CPE.PINX"), id_PINX}}; + {ctx->id("CPE.IN1"), id_IN1}, {ctx->id("CPE.IN2"), id_IN2}, {ctx->id("CPE.IN3"), id_IN3}, + {ctx->id("CPE.IN4"), id_IN4}, {ctx->id("CPE.IN5"), id_IN5}, {ctx->id("CPE.IN6"), id_IN6}, + {ctx->id("CPE.IN7"), id_IN7}, {ctx->id("CPE.IN8"), id_IN8}, {ctx->id("CPE.PINY1"), id_PINY1}, + {ctx->id("CPE.PINY2"), id_PINY2}, {ctx->id("CPE.CINY2"), id_CINY2}, {ctx->id("CPE.CLK"), id_CLK}, + {ctx->id("CPE.EN"), id_EN}, {ctx->id("CPE.CINX"), id_CINX}, {ctx->id("CPE.PINX"), id_PINX}}; if (convert_port.count(port)) { port_mapping.emplace(orig_port, merged ? convert_port_merged[port] : convert_port[port]); return true; @@ -645,6 +905,24 @@ void GateMateImpl::postRoute() if (cfg.count(id_C_I4) && cfg.at(id_C_I4) == 1) cell.second->params[id_C_I4] = Property(1, 1); } + if (cell.second->type.in(id_CPE_FF, id_CPE_FF_L, id_CPE_FF_U, id_CPE_LATCH)) { + cfg.clear(); + port_mapping.clear(); + check_input(cell.second.get(), id_CLK_INT, false); + check_input(cell.second.get(), id_EN_INT, false); + if (cfg.count(id_C_CLKSEL) && cfg.at(id_C_CLKSEL) == 1) { + uint8_t val = int_or_default(cell.second->params, id_C_CPE_CLK, 0) & 1; + cell.second->params[id_C_CPE_CLK] = Property(val ? 3 : 0, 2); + cell.second->params[id_C_CLKSEL] = Property(1, 1); + } + if (cfg.count(id_C_ENSEL) && cfg.at(id_C_ENSEL) == 1) { + uint8_t val = int_or_default(cell.second->params, id_C_CPE_EN, 0) & 1; + cell.second->params[id_C_CPE_EN] = Property(val ? 3 : 0, 2); + cell.second->params[id_C_ENSEL] = Property(1, 1); + } + cell.second->renamePort(id_CLK_INT, port_mapping[id_CLK_INT]); + cell.second->renamePort(id_EN_INT, port_mapping[id_EN_INT]); + } } ctx->assignArchInfo(); @@ -681,6 +959,25 @@ void GateMateImpl::expandBoundingBox(BoundingBox &bb) const bb.y1 = std::min((bb.y1 & 0xfffe) + 5, ctx->getGridDimY()); } +GroupId GateMateImpl::getResourceKeyForPip(PipId pip) const +{ + const auto &extra_data = *pip_extra_data(pip); + if (extra_data.type != PipExtra::PIP_EXTRA_MUX || extra_data.group_index == 0) + return GroupId(); + + return GroupId(pip.tile, extra_data.group_index); +} + +int GateMateImpl::getResourceValueForPip(PipId pip) const +{ + const auto &extra_data = *pip_extra_data(pip); + if (extra_data.type != PipExtra::PIP_EXTRA_MUX || extra_data.resource == 0) + return 0; + return extra_data.value; +} + +bool GateMateImpl::isGroupResource(GroupId group) const { return ctx->getGroupType(group) == id_RESOURCE; } + void GateMateImpl::configurePlacerHeap(PlacerHeapCfg &cfg) { cfg.chainRipup = true; @@ -722,8 +1019,8 @@ void GateMateImpl::assign_cell_info() CellInfo *ci = cell.second.get(); auto &fc = fast_cell_info.at(ci->flat_index); if (getBelBucketForCellType(ci->type) == id_CPE_FF) { - fc.ff_en = ci->getPort(id_EN); - fc.ff_clk = ci->getPort(id_CLK); + fc.ff_en = ci->getPort(id_EN_INT); + fc.ff_clk = ci->getPort(id_CLK_INT); fc.ff_sr = ci->getPort(id_SR); fc.config = get_dff_config(ci); fc.used = true; diff --git a/himbaechel/uarch/gatemate/gatemate.h b/himbaechel/uarch/gatemate/gatemate.h index 7298325a..e6b65050 100644 --- a/himbaechel/uarch/gatemate/gatemate.h +++ b/himbaechel/uarch/gatemate/gatemate.h @@ -79,6 +79,10 @@ struct GateMateImpl : HimbaechelAPI bool isPipInverting(PipId pip) const override; + GroupId getResourceKeyForPip(PipId pip) const override; + int getResourceValueForPip(PipId pip) const override; + bool isGroupResource(GroupId group) const override; + const GateMateTileExtraDataPOD *tile_extra_data(int tile) const; const GateMateBelExtraDataPOD *bel_extra_data(BelId bel) const; const GateMatePipExtraDataPOD *pip_extra_data(PipId pip) const; @@ -95,8 +99,9 @@ struct GateMateImpl : HimbaechelAPI pool multiplier_a_passthru_lowers; pool multiplier_a_passthru_uppers; pool multiplier_zero_drivers; - std::vector multipliers; std::vector used_cpes; + std::vector pip_data; + std::vector pip_mask; int fpga_mode; int timing_mode; std::map global_signals; @@ -109,6 +114,7 @@ struct GateMateImpl : HimbaechelAPI MultiDieStrategy strategy; dict index_to_die; dict die_to_index; + dict> pass_backtrace; private: bool getChildPlacement(const BaseClusterInfo *cluster, Loc root_loc, @@ -123,6 +129,8 @@ struct GateMateImpl : HimbaechelAPI void route_mult(); void reassign_bridges(NetInfo *net, const dict &net_wires, WireId wire, dict &wire_to_net, int &num); + void reassign_cplines(NetInfo *net, const dict &net_wires, WireId wire, + dict &wire_to_net, int &num, IdString in_port); void repack(); bool get_delay_from_tmg_db(IdString id, DelayQuad &delay) const; @@ -142,6 +150,9 @@ struct GateMateImpl : HimbaechelAPI std::map timing; dict ram_signal_clk; IdString forced_die; + bool use_cp_for_clk; + bool use_cp_for_cpe; + bool use_bridges; }; NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/gatemate/gen/arch_gen.py b/himbaechel/uarch/gatemate/gen/arch_gen.py index 1e5b6a32..cfa866fe 100644 --- a/himbaechel/uarch/gatemate/gen/arch_gen.py +++ b/himbaechel/uarch/gatemate/gen/arch_gen.py @@ -73,6 +73,9 @@ class PipExtraData(BBAStruct): value: int = 0 invert: int = 0 plane: int = 0 + block: int = 0 + resource: int = 0 + group_index: int = 0 def serialise_lists(self, context: str, bba: BBAWriter): pass @@ -85,6 +88,9 @@ class PipExtraData(BBAStruct): bba.u8(self.plane) bba.u8(0) bba.u16(0) + bba.u32(self.block) + bba.u32(self.resource) + bba.u32(self.group_index) @dataclass class BelPinConstraint(BBAStruct): @@ -224,7 +230,22 @@ def set_timings(ch): assert k in timing, f"pip class {k} not found in timing data" tmg.set_pip_class(grade=speed, name=k, delay=convert_timing(timing[k])) -EXPECTED_VERSION = 1.11 +EXPECTED_VERSION = 1.12 + +RESOURCE_NAMES = { + 1 << 0: "C_SELX", + 1 << 1: "C_SELY1", + 1 << 2: "C_SELY2", + 1 << 3: "C_SEL_C", + 1 << 4: "C_SEL_P", + 1 << 5: "C_Y12", + 1 << 6: "C_CX_I", + 1 << 7: "C_CY1_I", + 1 << 8: "C_CY2_I", + 1 << 9: "C_PX_I", + 1 << 10: "C_PY1_I", + 1 << 11: "C_PY2_I", +} def main(): # Range needs to be +1, but we are adding +2 more to coordinates, since @@ -278,6 +299,9 @@ def main(): tt = ch.create_tile_type(type_name) for group in sorted(die.get_groups_for_type(type_name)): tt.create_group(group.name, group.type) + if ("CPE" in type_name): + for name in RESOURCE_NAMES.values(): + tt.create_group(name, "RESOURCE") for wire in sorted(die.get_endpoints_for_type(type_name)): tt.create_wire(wire.name, wire.type) if type_name in new_wires: @@ -310,7 +334,12 @@ def main(): plane = int(mux.name[10:12]) if mux.name == "CPE.C_SN": mux_flags |= MUX_ROUTING - pp.extra_data = PipExtraData(PIP_EXTRA_MUX, ch.strs.id(mux.name), mux.bits, mux.value, mux_flags, plane) + group_index = 0 + if mux.resource > 0: + group = RESOURCE_NAMES.get(mux.resource, "UNKNOWN") + group_index = tt._group2idx[tt.strs.id(group)] + tt.add_pip_to_group(pp, group) + pp.extra_data = PipExtraData(PIP_EXTRA_MUX, ch.strs.id(mux.name), mux.bits, mux.value, mux_flags, plane, mux.block, mux.resource, group_index) if type_name in new_wires: for wire in sorted(new_wires[type_name]): delay = wire_delay[wire] diff --git a/himbaechel/uarch/gatemate/gfx.cc b/himbaechel/uarch/gatemate/gfx.cc index db9b1e84..c187283d 100644 --- a/himbaechel/uarch/gatemate/gfx.cc +++ b/himbaechel/uarch/gatemate/gfx.cc @@ -41,6 +41,13 @@ void GateMateImpl::drawBel(std::vector &g, GraphicElement::style el.y2 = el.y1 + 0.50; g.push_back(el); break; + case id_CPE_CPLINES.index: + el.x1 = loc.x + 0.72; + el.x2 = el.x1 + 0.06; + el.y1 = loc.y + 0.25; + el.y2 = el.y1 + 0.50; + g.push_back(el); + break; case id_CPE_LT_L.index: el.x1 = loc.x + 0.20; el.x2 = el.x1 + 0.20; diff --git a/himbaechel/uarch/gatemate/pack.cc b/himbaechel/uarch/gatemate/pack.cc index cac93631..9aeea68c 100644 --- a/himbaechel/uarch/gatemate/pack.cc +++ b/himbaechel/uarch/gatemate/pack.cc @@ -381,8 +381,6 @@ void GateMatePacker::repack_cpe() if (l.z == CPE_LT_L_Z) { if (!cell.second->params.count(id_INIT_L20)) cell.second->params[id_INIT_L20] = Property(LUT_D1, 4); - if (cell.second->getPort(id_D0_10)) { - } } cell.second->params[id_L2T4_UPPER] = Property((l.z == CPE_LT_U_Z) ? 1 : 0, 1); } else if (cell.second->type.in(id_CPE_LT_L)) { diff --git a/himbaechel/uarch/gatemate/pack_cpe.cc b/himbaechel/uarch/gatemate/pack_cpe.cc index 76fb37ae..cbb6223c 100644 --- a/himbaechel/uarch/gatemate/pack_cpe.cc +++ b/himbaechel/uarch/gatemate/pack_cpe.cc @@ -33,9 +33,9 @@ bool GateMatePacker::are_ffs_compatible(CellInfo *dff, CellInfo *other) { if (!other) return true; - if (dff->getPort(id_CLK) != other->getPort(id_CLK)) + if (dff->getPort(id_CLK_INT) != other->getPort(id_CLK)) return false; - if (dff->getPort(id_EN) != other->getPort(id_EN)) + if (dff->getPort(id_EN_INT) != other->getPort(id_EN)) return false; if (dff->getPort(id_SR) != other->getPort(id_SR)) return false; @@ -192,6 +192,8 @@ void GateMatePacker::pack_cpe() ci.constr_children.push_back(dff); dff->renamePort(id_D, id_DIN); dff->renamePort(id_Q, id_DOUT); + dff->renamePort(id_CLK, id_CLK_INT); + dff->renamePort(id_EN, id_EN_INT); dff->type = (dff->type == id_CC_DLT) ? id_CPE_LATCH : id_CPE_FF; }; @@ -387,6 +389,8 @@ void GateMatePacker::pack_cpe() ci.cluster = ci.name; ci.constr_children.push_back(lt); ci.renamePort(id_Q, id_DOUT); + ci.renamePort(id_CLK, id_CLK_INT); + ci.renamePort(id_EN, id_EN_INT); NetInfo *d_net = ci.getPort(id_D); if (d_net == net_PACKER_GND) { lt->params[id_INIT_L10] = Property(LUT_ZERO, 4); @@ -537,6 +541,8 @@ void GateMatePacker::pack_addf() cell->constr_children.push_back(dff); dff->renamePort(id_D, id_DIN); dff->renamePort(id_Q, id_DOUT); + dff->renamePort(id_CLK, id_CLK_INT); + dff->renamePort(id_EN, id_EN_INT); dff->type = (dff->type == id_CC_DLT) ? id_CPE_LATCH : id_CPE_FF; return dff; } @@ -567,6 +573,7 @@ void GateMatePacker::pack_addf() CellInfo *ci_cplines = create_cell_ptr(id_CPE_CPLINES, ctx->idf("%s$ci_cplines", root->name.c_str(ctx))); ci_cplines->params[id_C_SELY1] = Property(1, 1); + ci_cplines->params[id_C_SEL_C] = Property(0, 1); ci_cplines->params[id_C_CY1_I] = Property(1, 1); root->constr_children.push_back(ci_cplines); ci_cplines->cluster = root->name; @@ -763,6 +770,8 @@ std::pair GateMatePacker::move_ram_io(CellInfo *cell, Id /* if (ci.type.in(id_CC_DFF, id_CC_DLT)) { cpe_half = create_cell_ptr(id_CPE_L2T4, ctx->idf("%s$%s_cpe", cell->name.c_str(ctx), oPort.c_str(ctx))); ci.renamePort(id_Q, id_DOUT); + ci.renamePort(id_CLK, id_CLK_INT); + ci.renamePort(id_EN, id_EN_INT); NetInfo *d_net = ci.getPort(id_D); if (d_net == net_PACKER_GND) { cpe_half->params[id_INIT_L10] = Property(LUT_ZERO, 4); diff --git a/himbaechel/uarch/gatemate/pack_io.cc b/himbaechel/uarch/gatemate/pack_io.cc index 5c913295..79547551 100644 --- a/himbaechel/uarch/gatemate/pack_io.cc +++ b/himbaechel/uarch/gatemate/pack_io.cc @@ -540,11 +540,11 @@ void GateMatePacker::pack_io_sel() if (do_net->driver.cell && do_net->driver.cell->type == id_CC_LUT1 && do_net->users.entries() == 1) { NetInfo *net = do_net->driver.cell->getPort(id_I0); if (net->driver.cell && net->driver.cell->type == id_CC_ODDR && net->users.entries() == 1) { - do_net = net; - packed_cells.insert(net->driver.cell->name); + packed_cells.insert(do_net->driver.cell->name); // Inverting both input is equal to inverter at output is_inverted[0] = true; is_inverted[1] = true; + do_net = net; } } if (do_net->driver.cell && do_net->driver.cell->type == id_CC_ODDR && do_net->users.entries() == 1) { diff --git a/himbaechel/uarch/gatemate/pack_mult.cc b/himbaechel/uarch/gatemate/pack_mult.cc index 48783089..85726008 100644 --- a/himbaechel/uarch/gatemate/pack_mult.cc +++ b/himbaechel/uarch/gatemate/pack_mult.cc @@ -936,9 +936,44 @@ void GateMatePacker::pack_mult() if (p_net && p_net->users.entries() == 1) { auto *p_net_sink = (*p_net->users.begin()).cell; NPNR_ASSERT(p_net_sink != nullptr); - if (p_net_sink->type == id_CC_DFF && !are_ffs_compatible(p_zero_sink, p_net_sink)) { - log_info(" Inconsistent control set; not packing output register.\n"); - return false; + if (p_net_sink->type == id_CC_DFF) { + bool incompatible = false; + if (p_zero_sink->getPort(id_CLK) != p_net_sink->getPort(id_CLK)) { + const char *p_zero_clk = "(none)"; + const char *p_net_clk = "(none)"; + if (p_zero_sink->getPort(id_CLK) != nullptr) + p_zero_clk = p_zero_sink->getPort(id_CLK)->name.c_str(ctx); + if (p_net_sink->getPort(id_CLK) != nullptr) + p_net_clk = p_net_sink->getPort(id_CLK)->name.c_str(ctx); + log_info(" registers have inconsistent clocks: %s vs %s\n", p_zero_clk, p_net_clk); + incompatible = true; + } + if (p_zero_sink->getPort(id_EN) != p_net_sink->getPort(id_EN)) { + const char *p_zero_en = "(none)"; + const char *p_net_en = "(none)"; + if (p_zero_sink->getPort(id_EN) != nullptr) + p_zero_en = p_zero_sink->getPort(id_EN)->name.c_str(ctx); + if (p_net_sink->getPort(id_EN) != nullptr) + p_net_en = p_net_sink->getPort(id_EN)->name.c_str(ctx); + log_info(" registers have inconsistent enables: %s vs %s\n", p_zero_en, p_net_en); + incompatible = true; + } + if (p_zero_sink->getPort(id_SR) != p_net_sink->getPort(id_SR)) { + const char *p_zero_sr = "(none)"; + const char *p_net_sr = "(none)"; + if (p_zero_sink->getPort(id_SR) != nullptr) + p_zero_sr = p_zero_sink->getPort(id_SR)->name.c_str(ctx); + if (p_net_sink->getPort(id_SR) != nullptr) + p_net_sr = p_net_sink->getPort(id_EN)->name.c_str(ctx); + log_info(" registers have inconsistent resets: %s vs %s\n", p_zero_sr, p_net_sr); + incompatible = true; + } + if (uarch->get_dff_config(p_zero_sink) != uarch->get_dff_config(p_net_sink)) + log_info(" registers have different configurations\n"); + if (incompatible) { + log_info(" ...not packing output register\n"); + return false; + } } } } diff --git a/himbaechel/uarch/gatemate/route_clock.cc b/himbaechel/uarch/gatemate/route_clock.cc index fd680cf5..101e0fa7 100644 --- a/himbaechel/uarch/gatemate/route_clock.cc +++ b/himbaechel/uarch/gatemate/route_clock.cc @@ -52,11 +52,11 @@ void GateMateImpl::route_clock() auto reserved_wires = dict{}; auto feeds_clk_port = [&](PortRef &port) { - return (ctx->getBelBucketForCellType(port.cell->type) == id_CPE_FF) && port.port.in(id_CLK); + return (ctx->getBelBucketForCellType(port.cell->type) == id_CPE_FF) && port.port.in(id_CLK_INT); }; auto feeds_ddr_port = [&](NetInfo *net, PortRef &port) { - return this->ddr_nets.find(net->name) != this->ddr_nets.end() && port.port == id_IN1; + return this->ddr_nets.find(net->name) != this->ddr_nets.end() && port.port == id_D0_10; }; auto pip_plane = [&](PipId pip) { @@ -151,6 +151,12 @@ void GateMateImpl::route_clock() } for (auto dh : ctx->getPipsDownhill(curr.wire)) { + const auto &extra_data = *pip_extra_data(dh); + // Allow only CINY2->COUTY2 pass through for clock router + if (extra_data.type == PipExtra::PIP_EXTRA_MUX && extra_data.resource != 0) { + if (!(extra_data.resource == PipMask::C_CY2_I && extra_data.value == 0)) + continue; + } if (!ctx->checkPipAvailForNet(dh, clk_net)) continue; WireId dst = ctx->getPipDstWire(dh);