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 <rabbit@yrabbit.cyou>
This commit is contained in:
parent
c4b3268e90
commit
2930d80627
@ -1060,3 +1060,19 @@ X(router)
|
|||||||
X(GOWIN_GND)
|
X(GOWIN_GND)
|
||||||
X(GOWIN_VCC)
|
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)
|
||||||
|
|
||||||
|
186
himbaechel/uarch/gowin/globals.cc
Normal file
186
himbaechel/uarch/gowin/globals.cc
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* nextpnr -- Next Generation Place and Route
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021 gatecat <gatecat@ds0.me>
|
||||||
|
*
|
||||||
|
* 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 <queue>
|
||||||
|
|
||||||
|
#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 <typename Tfilt>
|
||||||
|
bool backwards_bfs_route(NetInfo *net, store_index<PortRef> user_idx, int iter_limit, bool strict, Tfilt pip_filter)
|
||||||
|
{
|
||||||
|
// Queue of wires to visit
|
||||||
|
std::queue<WireId> visit;
|
||||||
|
// Wire -> upstream pip
|
||||||
|
dict<WireId, PipId> 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<PipId> 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
|
12
himbaechel/uarch/gowin/globals.h
Normal file
12
himbaechel/uarch/gowin/globals.h
Normal file
@ -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
|
@ -7,6 +7,7 @@
|
|||||||
#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc"
|
#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc"
|
||||||
#include "himbaechel_constids.h"
|
#include "himbaechel_constids.h"
|
||||||
|
|
||||||
|
#include "globals.h"
|
||||||
#include "gowin.h"
|
#include "gowin.h"
|
||||||
#include "pack.h"
|
#include "pack.h"
|
||||||
|
|
||||||
@ -20,9 +21,10 @@ struct GowinImpl : HimbaechelAPI
|
|||||||
void init_constids(Arch *arch) override { init_uarch_constids(arch); }
|
void init_constids(Arch *arch) override { init_uarch_constids(arch); }
|
||||||
void init(Context *ctx) override;
|
void init(Context *ctx) override;
|
||||||
|
|
||||||
|
void pack() override;
|
||||||
void prePlace() override;
|
void prePlace() override;
|
||||||
void postPlace() override;
|
void postPlace() override;
|
||||||
void pack() override;
|
void preRoute() override;
|
||||||
|
|
||||||
bool isBelLocationValid(BelId bel, bool explain_invalid) const override;
|
bool isBelLocationValid(BelId bel, bool explain_invalid) const override;
|
||||||
|
|
||||||
@ -34,6 +36,9 @@ struct GowinImpl : HimbaechelAPI
|
|||||||
private:
|
private:
|
||||||
HimbaechelHelpers h;
|
HimbaechelHelpers h;
|
||||||
|
|
||||||
|
IdString chip;
|
||||||
|
IdString partno;
|
||||||
|
|
||||||
// Validity checking
|
// Validity checking
|
||||||
struct GowinCellInfo
|
struct GowinCellInfo
|
||||||
{
|
{
|
||||||
@ -61,17 +66,44 @@ void GowinImpl::init(Context *ctx)
|
|||||||
{
|
{
|
||||||
h.init(ctx);
|
h.init(ctx);
|
||||||
HimbaechelAPI::init(ctx);
|
HimbaechelAPI::init(ctx);
|
||||||
|
|
||||||
|
const ArchArgs &args = ctx->getArchArgs();
|
||||||
// These fields go in the header of the output JSON file and can help
|
// These fields go in the header of the output JSON file and can help
|
||||||
// gowin_pack support different architectures
|
// gowin_pack support different architectures
|
||||||
ctx->settings[ctx->id("packer.arch")] = std::string("himbaechel/gowin");
|
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,
|
ctx->settings[ctx->id("packer.chipdb")] = args.chipdb;
|
||||||
// but maybe that will come up when there is clarity with
|
|
||||||
// Arch::archArgsToId
|
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::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
|
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;
|
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
|
} // namespace
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -70,7 +70,8 @@ def create_nodes(chip: Chip, db: chipdb):
|
|||||||
for y in range(Y):
|
for y in range(Y):
|
||||||
for x in range(X):
|
for x in range(X):
|
||||||
nodes = []
|
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
|
# SN and EW
|
||||||
for i in [1, 2]:
|
for i in [1, 2]:
|
||||||
nodes.append([NodeWire(x, y, f'SN{i}0'),
|
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('GND', []).append(NodeWire(x, y, 'VSS'))
|
||||||
global_nodes.setdefault('VCC', []).append(NodeWire(x, y, 'VCC'))
|
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)
|
chip.add_node(node)
|
||||||
|
|
||||||
|
|
||||||
# About X and Y as parameters - in some cases, the type of manufacturer's tile
|
# 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
|
# 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
|
# 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
|
# for taking this into account, but for now we make a distinction here, by
|
||||||
# coordinates.
|
# coordinates.
|
||||||
def create_switch_matrix(tt: TileType, db: chipdb, x: int, y: int):
|
def create_switch_matrix(tt: TileType, db: chipdb, x: int, y: int):
|
||||||
pips = db.grid[y][x].pips
|
def get_wire_type(name):
|
||||||
for dst, srcs in pips.items():
|
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):
|
if not tt.has_wire(dst):
|
||||||
tt.create_wire(dst)
|
tt.create_wire(dst, get_wire_type(dst))
|
||||||
for src in srcs.keys():
|
for src in srcs.keys():
|
||||||
if not tt.has_wire(src):
|
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)
|
tt.create_pip(src, dst)
|
||||||
|
|
||||||
def create_null_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int):
|
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
|
# wires
|
||||||
portmap = db.grid[y][x].bels[name].portmap
|
portmap = db.grid[y][x].bels[name].portmap
|
||||||
tt.create_wire(portmap['I'], "IO_I")
|
tt.create_wire(portmap['I'], "IO_I")
|
||||||
tt.create_wire(portmap['O'], "IO_I")
|
tt.create_wire(portmap['O'], "IO_O")
|
||||||
# bels
|
# bels
|
||||||
io = tt.create_bel(name, "IOB", z = i)
|
io = tt.create_bel(name, "IOB", z = i)
|
||||||
tt.add_bel_pin(io, "I", portmap['I'], PinType.INPUT)
|
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
|
# these differences (in case it turns out later that there is a slightly
|
||||||
# different routing or something like that).
|
# different routing or something like that).
|
||||||
logic_tiletypes = {12, 13, 14, 15, 16}
|
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}
|
ssram_tiletypes = {17, 18, 19}
|
||||||
# Setup tile grid
|
# Setup tile grid
|
||||||
for x in range(X):
|
for x in range(X):
|
||||||
|
Loading…
Reference in New Issue
Block a user