From 2930d8062703c811d507a32badd7961e63f2587a Mon Sep 17 00:00:00 2001 From: YRabbit Date: Wed, 12 Jul 2023 11:25:28 +1000 Subject: [PATCH] gowin: Himbaechel. Add a clock router. Shamelessly adapted gatecat's router. Very early version, not yet puzzled with recognizing clock sources and controlling the type of wires involved. Signed-off-by: YRabbit --- himbaechel/uarch/gowin/constids.inc | 16 ++ himbaechel/uarch/gowin/globals.cc | 186 +++++++++++++++++++++++ himbaechel/uarch/gowin/globals.h | 12 ++ himbaechel/uarch/gowin/gowin.cc | 55 ++++--- himbaechel/uarch/gowin/gowin_arch_gen.py | 38 ++++- 5 files changed, 282 insertions(+), 25 deletions(-) create mode 100644 himbaechel/uarch/gowin/globals.cc create mode 100644 himbaechel/uarch/gowin/globals.h diff --git a/himbaechel/uarch/gowin/constids.inc b/himbaechel/uarch/gowin/constids.inc index 1dd3052c..5d9a1ed3 100644 --- a/himbaechel/uarch/gowin/constids.inc +++ b/himbaechel/uarch/gowin/constids.inc @@ -1060,3 +1060,19 @@ X(router) X(GOWIN_GND) X(GOWIN_VCC) +// wire types +X(GLOBAL_CLK) +X(TILE_CLK) +X(TILE_LSR) +X(TILE_CE) +X(IO_I) +X(IO_O) +X(LUT_INPUT) +X(LUT_OUT) +X(FF_INPUT) +X(FF_OUT) +X(MUX_OUT) +X(MUX_SEL) +X(ALU_CIN) +X(ALU_COUT) + diff --git a/himbaechel/uarch/gowin/globals.cc b/himbaechel/uarch/gowin/globals.cc new file mode 100644 index 00000000..52dd58ba --- /dev/null +++ b/himbaechel/uarch/gowin/globals.cc @@ -0,0 +1,186 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +#include + +#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc" +#include "himbaechel_constids.h" +#include "himbaechel_helpers.h" + +#include "globals.h" +#include "gowin.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct GowinGlobalRouter +{ + Context *ctx; + + GowinGlobalRouter(Context *ctx) : ctx(ctx){}; + + // allow io->global, global->global and global->tile clock + bool global_pip_filter(PipId pip) const + { + /* + IdString src_type = ctx->getWireType(ctx->getPipSrcWire(pip)); + IdString dst_type = ctx->getWireType(ctx->getPipDstWire(pip)); + bool src_valid = src_type.in(id_GLOBAL_CLK, id_IO_O); + bool dst_valid = dst_type.in(id_GLOBAL_CLK, id_TILE_CLK); + return src_valid && dst_valid; + */ + return true; + } + + bool is_relaxed_sink(const PortRef &sink) const { return false; } + + // Dedicated backwards BFS routing for global networks + template + bool backwards_bfs_route(NetInfo *net, store_index user_idx, int iter_limit, bool strict, Tfilt pip_filter) + { + // Queue of wires to visit + std::queue visit; + // Wire -> upstream pip + dict backtrace; + + // Lookup source and destination wires + WireId src = ctx->getNetinfoSourceWire(net); + WireId dst = ctx->getNetinfoSinkWire(net, net->users.at(user_idx), 0); + + if (src == WireId()) + log_error("Net '%s' has an invalid source port %s.%s\n", ctx->nameOf(net), ctx->nameOf(net->driver.cell), + ctx->nameOf(net->driver.port)); + + if (dst == WireId()) + log_error("Net '%s' has an invalid sink port %s.%s\n", ctx->nameOf(net), + ctx->nameOf(net->users.at(user_idx).cell), ctx->nameOf(net->users.at(user_idx).port)); + + if (ctx->getBoundWireNet(src) != net) + ctx->bindWire(src, net, STRENGTH_LOCKED); + + if (src == dst) { + // Nothing more to do + return true; + } + + visit.push(dst); + backtrace[dst] = PipId(); + + int iter = 0; + + while (!visit.empty() && (iter++ < iter_limit)) { + WireId cursor = visit.front(); + visit.pop(); + // Search uphill pips + for (PipId pip : ctx->getPipsUphill(cursor)) { + // Skip pip if unavailable, and not because it's already used for this net + if (!ctx->checkPipAvail(pip) && ctx->getBoundPipNet(pip) != net) + continue; + WireId prev = ctx->getPipSrcWire(pip); + // Ditto for the upstream wire + if (!ctx->checkWireAvail(prev) && ctx->getBoundWireNet(prev) != net) + continue; + // Skip already visited wires + if (backtrace.count(prev)) + continue; + // Apply our custom pip filter + if (!pip_filter(pip)) + continue; + // Add to the queue + visit.push(prev); + backtrace[prev] = pip; + // Check if we are done yet + if (prev == src) + goto done; + } + if (false) { + done: + break; + } + } + + if (backtrace.count(src)) { + WireId cursor = src; + std::vector pips; + // Create a list of pips on the routed path + while (true) { + PipId pip = backtrace.at(cursor); + if (pip == PipId()) + break; + pips.push_back(pip); + cursor = ctx->getPipDstWire(pip); + } + // Reverse that list + std::reverse(pips.begin(), pips.end()); + // Bind pips until we hit already-bound routing + for (PipId pip : pips) { + WireId dst = ctx->getPipDstWire(pip); + if (ctx->getBoundWireNet(dst) == net) + break; + ctx->bindPip(pip, net, STRENGTH_LOCKED); + } + return true; + } else { + if (strict) + log_error("Failed to route net '%s' from %s to %s using dedicated routing.\n", ctx->nameOf(net), + ctx->nameOfWire(src), ctx->nameOfWire(dst)); + return false; + } + } + + void route_clk_net(NetInfo *net) + { + for (auto usr : net->users.enumerate()) + backwards_bfs_route(net, usr.index, 1000000, true, + [&](PipId pip) { return (is_relaxed_sink(usr.value) || global_pip_filter(pip)); }); + log_info(" routed net '%s' using global resources\n", ctx->nameOf(net)); + } + + bool driver_is_clksrc(const PortRef &driver) + { + // XXX dedicated pins + return driver.port == id_O; + } + + void run(void) + { + log_info("Routing globals...\n"); + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + CellInfo *drv = ni->driver.cell; + if (drv == nullptr) + continue; + if (driver_is_clksrc(ni->driver)) { + route_clk_net(ni); + continue; + } + } + } +}; + +void gowin_route_globals(Context *ctx) +{ + GowinGlobalRouter router(ctx); + router.run(); +} + +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/gowin/globals.h b/himbaechel/uarch/gowin/globals.h new file mode 100644 index 00000000..77e82769 --- /dev/null +++ b/himbaechel/uarch/gowin/globals.h @@ -0,0 +1,12 @@ +#ifndef GOWIN_GLOBALS_H +#define GOWIN_GLOBALS_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +void gowin_route_globals(Context *ctx); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/himbaechel/uarch/gowin/gowin.cc b/himbaechel/uarch/gowin/gowin.cc index 14a5cc0d..082c0980 100644 --- a/himbaechel/uarch/gowin/gowin.cc +++ b/himbaechel/uarch/gowin/gowin.cc @@ -7,6 +7,7 @@ #define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc" #include "himbaechel_constids.h" +#include "globals.h" #include "gowin.h" #include "pack.h" @@ -20,9 +21,10 @@ struct GowinImpl : HimbaechelAPI void init_constids(Arch *arch) override { init_uarch_constids(arch); } void init(Context *ctx) override; + void pack() override; void prePlace() override; void postPlace() override; - void pack() override; + void preRoute() override; bool isBelLocationValid(BelId bel, bool explain_invalid) const override; @@ -34,6 +36,9 @@ struct GowinImpl : HimbaechelAPI private: HimbaechelHelpers h; + IdString chip; + IdString partno; + // Validity checking struct GowinCellInfo { @@ -61,17 +66,44 @@ void GowinImpl::init(Context *ctx) { h.init(ctx); HimbaechelAPI::init(ctx); + + const ArchArgs &args = ctx->getArchArgs(); // These fields go in the header of the output JSON file and can help // gowin_pack support different architectures ctx->settings[ctx->id("packer.arch")] = std::string("himbaechel/gowin"); - // XXX it would be nice to write chip/base name in the header as well, - // but maybe that will come up when there is clarity with - // Arch::archArgsToId + ctx->settings[ctx->id("packer.chipdb")] = args.chipdb; + + if (!args.options.count("partno")) { + log_error("Partnumber (like --vopt partno=GW1NR-LV9QN88PC6/I5) must be specified.\n"); + } + ctx->settings[ctx->id("packer.partno")] = args.options.at("partno"); + + // GW1N-9C.xxx -> GW1N-9C + std::string chipdb = args.chipdb; + auto dot_pos = chipdb.find("."); + if (dot_pos != std::string::npos) { + chipdb.resize(dot_pos); + } + chip = ctx->id(chipdb); + partno = ctx->id(args.options.at("partno")); } -void GowinImpl::prePlace() { assign_cell_info(); } - void GowinImpl::pack() { gowin_pack(ctx); } +void GowinImpl::prePlace() { assign_cell_info(); } +void GowinImpl::postPlace() +{ + if (ctx->debug) { + log_info("================== Final Placement ===================\n"); + for (auto &cell : ctx->cells) { + auto ci = cell.second.get(); + IdStringList bel = ctx->getBelName(ci->bel); + log_info("%s -> %s\n", ctx->nameOf(ci), bel.str(ctx).c_str()); + } + log_info("======================================================\n"); + } +} + +void GowinImpl::preRoute() { gowin_route_globals(ctx); } bool GowinImpl::isBelLocationValid(BelId bel, bool explain_invalid) const { @@ -253,17 +285,6 @@ bool GowinImpl::slice_valid(int x, int y, int z) const return true; } -void GowinImpl::postPlace() -{ - if (ctx->debug) { - log_info("================== Final Placement ===================\n"); - for (auto &cell : ctx->cells) { - auto ci = cell.second.get(); - IdStringList bel = ctx->getBelName(ci->bel); - log_info("%s -> %s\n", ctx->nameOf(ci), bel.str(ctx).c_str()); - } - } -} } // namespace NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/gowin/gowin_arch_gen.py b/himbaechel/uarch/gowin/gowin_arch_gen.py index 86e6f216..2c631908 100644 --- a/himbaechel/uarch/gowin/gowin_arch_gen.py +++ b/himbaechel/uarch/gowin/gowin_arch_gen.py @@ -70,7 +70,8 @@ def create_nodes(chip: Chip, db: chipdb): for y in range(Y): for x in range(X): nodes = [] - extra_tile_data = chip.tile_type_at(x, y).extra_data + tt = chip.tile_type_at(x, y) + extra_tile_data = tt.extra_data # SN and EW for i in [1, 2]: nodes.append([NodeWire(x, y, f'SN{i}0'), @@ -118,22 +119,43 @@ def create_nodes(chip: Chip, db: chipdb): global_nodes.setdefault('GND', []).append(NodeWire(x, y, 'VSS')) global_nodes.setdefault('VCC', []).append(NodeWire(x, y, 'VCC')) - for node in global_nodes.values(): + # add nodes from the apicula db + for node_name, node in db.nodes.items(): + for y, x, wire in node: + new_node = NodeWire(x, y, wire) + gl_nodes = global_nodes.setdefault(node_name, []) + if new_node not in gl_nodes: + gl_nodes.append(NodeWire(x, y, wire)) + + for name, node in global_nodes.items(): chip.add_node(node) + # About X and Y as parameters - in some cases, the type of manufacturer's tile # is not different, but some wires are not physically present, that is, routing # depends on the location of otherwise identical tiles. There are many options # for taking this into account, but for now we make a distinction here, by # coordinates. def create_switch_matrix(tt: TileType, db: chipdb, x: int, y: int): - pips = db.grid[y][x].pips - for dst, srcs in pips.items(): + def get_wire_type(name): + if name.startswith('GB') or name.startswith('GT'): + return "GLOBAL_CLK" + return "" + + for dst, srcs in db.grid[y][x].pips.items(): if not tt.has_wire(dst): - tt.create_wire(dst) + tt.create_wire(dst, get_wire_type(dst)) for src in srcs.keys(): if not tt.has_wire(src): - tt.create_wire(src) + tt.create_wire(src, get_wire_type(dst)) + tt.create_pip(src, dst) + # clock wires + for dst, srcs in db.grid[y][x].clock_pips.items(): + if not tt.has_wire(dst): + tt.create_wire(dst, "GLOBAL_CLK") + for src in srcs.keys(): + if not tt.has_wire(src): + tt.create_wire(src, "GLOBAL_CLK") tt.create_pip(src, dst) def create_null_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): @@ -179,7 +201,7 @@ def create_io_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int): # wires portmap = db.grid[y][x].bels[name].portmap tt.create_wire(portmap['I'], "IO_I") - tt.create_wire(portmap['O'], "IO_I") + tt.create_wire(portmap['O'], "IO_O") # bels io = tt.create_bel(name, "IOB", z = i) tt.add_bel_pin(io, "I", portmap['I'], PinType.INPUT) @@ -330,7 +352,7 @@ def main(): # these differences (in case it turns out later that there is a slightly # different routing or something like that). logic_tiletypes = {12, 13, 14, 15, 16} - io_tiletypes = {53, 55, 58, 59, 64, 65} + io_tiletypes = {53, 55, 58, 59, 64, 65, 66} ssram_tiletypes = {17, 18, 19} # Setup tile grid for x in range(X):