Skip to content

Commit

Permalink
gowin: Himbaechel. Extend clock router
Browse files Browse the repository at this point in the history
Now the clock router can place a buffer into the specified network,
which divides the network into two parts: from the source to the buffer,
routing occurs through any available PIPs, and after the buffer to the
sink, only through a dedicated global clock network.

This is made specifically for the Tangnano20k where the external
oscillator is soldered to a regular non-clock pin. But it can be used
for other purposes, you just need to remember that the recipient must be
a CLK input or output pin.

The port/network to set the buffer to is specified in the .CST file:

CLOCK_LOC "name" BUFG;

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
  • Loading branch information
yrabbit committed Sep 4, 2023
1 parent 79c6840 commit fa1577f
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 37 deletions.
4 changes: 4 additions & 0 deletions himbaechel/uarch/gowin/constids.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1097,3 +1097,7 @@ X(HCLK_OUT1)
X(HCLK_OUT2)
X(HCLK_OUT3)

// BUFG, clock buffers stuff
X(BUFG)
X(CLOCK)

12 changes: 10 additions & 2 deletions himbaechel/uarch/gowin/cst.cc
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,16 @@ struct GowinCstReader
log_info("Can't use the long wires. The %s network will use normal routing.\n", net.c_str(ctx));
//}
} else {
log_info("BUFG isn't supported\n");
continue;
auto ni = ctx->nets.find(net);
if (ni == ctx->nets.end()) {
log_info("Net %s not found\n", net.c_str(ctx));
continue;
}
if (ctx->debug) {
log_info("Mark net '%s' as CLOCK\n", net.c_str(ctx));
}
// XXX YES for now. May be put the number here
ni->second->attrs[id_CLOCK] = Property(std::string("YES"));
}
} break;
case ioloc: { // IO_LOC name pin
Expand Down
129 changes: 100 additions & 29 deletions himbaechel/uarch/gowin/globals.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,29 +67,14 @@ struct GowinGlobalRouter

// 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)
bool backwards_bfs_route(NetInfo *net, WireId src, WireId dst, int iter_limit, bool strict, Tfilt pip_filter)
{
// log_info("%s:%s->%s\n", net->name.c_str(ctx), ctx->nameOfWire(src), ctx->nameOfWire(dst));
// 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;
Expand All @@ -106,24 +91,29 @@ struct GowinGlobalRouter
// 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)
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)
if (!ctx->checkWireAvail(prev) && ctx->getBoundWireNet(prev) != net) {
continue;
}
// Skip already visited wires
if (backtrace.count(prev))
if (backtrace.count(prev)) {
continue;
}
// Apply our custom pip filter
if (!pip_filter(pip))
if (!pip_filter(pip)) {
continue;
}
// Add to the queue
visit.push(prev);
backtrace[prev] = pip;
// Check if we are done yet
if (prev == src)
if (prev == src) {
goto done;
}
}
if (false) {
done:
Expand All @@ -137,18 +127,22 @@ struct GowinGlobalRouter
// Create a list of pips on the routed path
while (true) {
PipId pip = backtrace.at(cursor);
if (pip == PipId())
if (pip == PipId()) {
break;
}
pips.push_back(pip);
cursor = ctx->getPipDstWire(pip);
// log_info(">> %s:%s\n", ctx->getPipName(pip).str(ctx).c_str(), ctx->nameOfWire(cursor));
}
// 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)
// log_info("%s:%s\n", ctx->getPipName(pip).str(ctx).c_str(), ctx->nameOfWire(dst));
if (ctx->getBoundWireNet(dst) == net) {
break;
}
ctx->bindPip(pip, net, STRENGTH_LOCKED);
}
return true;
Expand All @@ -159,29 +153,90 @@ struct GowinGlobalRouter
} else {
log_warning("Failed to route net '%s' from %s to %s using dedicated routing.\n", ctx->nameOf(net),
ctx->nameOfWire(src), ctx->nameOfWire(dst));
ctx->unbindWire(src);
return false;
}
}
}

void route_clk_net(NetInfo *net)
bool route_direct_net(NetInfo *net)
{
// Lookup source and destination wires
WireId src = ctx->getNetinfoSourceWire(net);
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 (ctx->getBoundWireNet(src) != net) {
ctx->bindWire(src, net, STRENGTH_LOCKED);
}

bool routed = false;
for (auto usr : net->users.enumerate()) {
routed = backwards_bfs_route(net, usr.index, 1000000, false, [&](PipId pip) {
WireId dst = ctx->getNetinfoSinkWire(net, net->users.at(usr.index), 0);
if (dst == WireId()) {
log_error("Net '%s' has an invalid sink port %s.%s\n", ctx->nameOf(net),
ctx->nameOf(net->users.at(usr.index).cell), ctx->nameOf(net->users.at(usr.index).port));
}
routed = backwards_bfs_route(net, src, dst, 1000000, false, [&](PipId pip) {
return (is_relaxed_sink(usr.value) || global_pip_filter(pip));
});
if (!routed) {
break;
}
}
if (!routed) {
ctx->unbindWire(src);
}
return routed;
}

void route_buffered_net(NetInfo *net)
{
// a) route net after buf using the buf input as source
CellInfo *buf_ci = net->driver.cell;
WireId src = ctx->getBelPinWire(buf_ci->bel, id_I);

NetInfo *net_before_buf = buf_ci->getPort(id_I);
NPNR_ASSERT(net_before_buf != nullptr);

if (routed) {
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));
}
ctx->bindWire(src, net, STRENGTH_LOCKED);

for (auto usr : net->users.enumerate()) {
WireId dst = ctx->getNetinfoSinkWire(net, net->users.at(usr.index), 0);
if (dst == WireId()) {
log_error("Net '%s' has an invalid sink port %s.%s\n", ctx->nameOf(net),
ctx->nameOf(net->users.at(usr.index).cell), ctx->nameOf(net->users.at(usr.index).port));
}
// log_info(" usr wire: %s\n", ctx->nameOfWire(dst));
backwards_bfs_route(net, src, dst, 1000000, true,
[&](PipId pip) { return (is_relaxed_sink(usr.value) || global_pip_filter(pip)); });
}

// b) route net before buf from whatever to the buf input
WireId dst = src;
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);
ctx->unbindWire(dst);
backwards_bfs_route(net, src, dst, 1000000, false, [&](PipId pip) { return true; });
// remove net
buf_ci->movePortTo(id_O, true_src_ci, net_before_buf->driver.port);
net_before_buf->driver.cell = nullptr;
}

void route_clk_net(NetInfo *net)
{
if (route_direct_net(net)) {
log_info(" routed net '%s' using global resources\n", ctx->nameOf(net));
}
}

bool driver_is_buf(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_BUFG, id_O); }

bool driver_is_clksrc(const PortRef &driver)
{
// dedicated pins
Expand Down Expand Up @@ -219,11 +274,27 @@ struct GowinGlobalRouter
void run(void)
{
log_info("Routing globals...\n");
// buffered nets first
for (auto &net : ctx->nets) {
NetInfo *ni = net.second.get();
CellInfo *drv = ni->driver.cell;
if (drv == nullptr) {
continue;
}
if (driver_is_buf(ni->driver)) {
if (ctx->verbose) {
log_info("route buffered net '%s'\n", ctx->nameOf(ni));
}
route_buffered_net(ni);
continue;
}
}
for (auto &net : ctx->nets) {
NetInfo *ni = net.second.get();
CellInfo *drv = ni->driver.cell;
if (drv == nullptr)
if (drv == nullptr) {
continue;
}
if (driver_is_clksrc(ni->driver)) {
route_clk_net(ni);
continue;
Expand Down
2 changes: 2 additions & 0 deletions himbaechel/uarch/gowin/gowin.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ enum
IDES16_Z = 72,
OSER16_Z = 73,

BUFG_Z = 74, // : 81 reserve just in case

OSC_Z = 274,
PLL_Z = 275,
GSR_Z = 276,
Expand Down
14 changes: 14 additions & 0 deletions himbaechel/uarch/gowin/gowin_arch_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
IDES16_Z = 72
OSER16_Z = 73

BUFG_Z = 74 # : 81 reserve just in case

OSC_Z = 274
PLL_Z = 275
GSR_Z = 276
Expand Down Expand Up @@ -291,6 +293,18 @@ def create_extra_funcs(tt: TileType, db: chipdb, x: int, y: int):
tt.add_bel_pin(bel, port, wire, PinType.OUTPUT)
else:
tt.add_bel_pin(bel, port, wire, PinType.INPUT)
if func == 'buf':
for buf_type, wires in desc.items():
for i, wire in enumerate(wires):
if not tt.has_wire(wire):
tt.create_wire(wire, "TILE_CLK")
wire_out = f'{buf_type}{i}_O'
tt.create_wire(wire_out, "TILE_CLK")
# XXX make Z from buf_type
bel = tt.create_bel(f'{buf_type}{i}', buf_type, z = BUFG_Z + i)
bel.flags = BEL_FLAG_GLOBAL
tt.add_bel_pin(bel, "I", wire, PinType.INPUT)
tt.add_bel_pin(bel, "O", wire_out, PinType.OUTPUT)

def create_tiletype(create_func, chip: Chip, db: chipdb, x: int, y: int, ttyp: int):
has_extra_func = (y, x) in db.extra_func
Expand Down
62 changes: 56 additions & 6 deletions himbaechel/uarch/gowin/pack.cc
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ struct GowinPacker
auto &ci = *cell.second;
if (!ci.type.in(ctx->id("$nextpnr_ibuf"), ctx->id("$nextpnr_obuf"), ctx->id("$nextpnr_iobuf")))
continue;
NetInfo *i = ci.getPort(ctx->id("I"));
NetInfo *i = ci.getPort(id_I);
if (i && i->driver.cell) {
if (!top_ports.count(CellTypePort(i->driver)))
log_error("Top-level port '%s' driven by illegal port %s.%s\n", ctx->nameOf(&ci),
Expand All @@ -119,7 +119,7 @@ struct GowinPacker
i->driver.cell->attrs[attr.first] = attr.second;
}
}
NetInfo *o = ci.getPort(ctx->id("O"));
NetInfo *o = ci.getPort(id_O);
if (o) {
for (auto &usr : o->users) {
if (!top_ports.count(CellTypePort(usr)))
Expand All @@ -128,9 +128,21 @@ struct GowinPacker
for (const auto &attr : ci.attrs) {
usr.cell->attrs[attr.first] = attr.second;
}
// network/port attributes that can be set in the
// restriction file and that need to be transferred to real
// networks before nextpnr buffers are removed.
NetInfo *dst_net = usr.cell->getPort(id_O);
if (dst_net != nullptr) {
for (const auto &attr : o->attrs) {
if (!attr.first.in(id_CLOCK)) {
continue;
}
dst_net->attrs[attr.first] = attr.second;
}
}
}
}
NetInfo *io = ci.getPort(ctx->id("IO"));
NetInfo *io = ci.getPort(id_IO);
if (io && io->driver.cell) {
if (!top_ports.count(CellTypePort(io->driver)))
log_error("Top-level port '%s' driven by illegal port %s.%s\n", ctx->nameOf(&ci),
Expand All @@ -139,9 +151,9 @@ struct GowinPacker
io->driver.cell->attrs[attr.first] = attr.second;
}
}
ci.disconnectPort(ctx->id("I"));
ci.disconnectPort(ctx->id("O"));
ci.disconnectPort(ctx->id("IO"));
ci.disconnectPort(id_I);
ci.disconnectPort(id_O);
ci.disconnectPort(id_IO);
to_remove.push_back(ci.name);
}
for (IdString cell_name : to_remove)
Expand Down Expand Up @@ -1317,6 +1329,41 @@ struct GowinPacker
}
}

// =========================================
// Create entry points to the clock system
// =========================================
void pack_buffered_nets()
{
log_info("Pack buffered nets..\n");

for (auto &net : ctx->nets) {
auto &ni = *net.second;
if (ni.driver.cell == nullptr || ni.attrs.count(id_CLOCK) == 0 || ni.users.empty()) {
continue;
}

// make new BUF cell single user for the net driver
IdString buf_name = ctx->idf("%s_BUFG", net.first.c_str(ctx));
ctx->createCell(buf_name, id_BUFG);
CellInfo *buf_ci = ctx->cells.at(buf_name).get();
buf_ci->addInput(id_I);
auto s_net = std::make_unique<NetInfo>(ctx->idf("$PACKER_BUF_%s", net.first.c_str(ctx)));
NetInfo *buf_ni = s_net.get();
ctx->nets[s_net->name] = std::move(s_net);

if (ctx->verbose) {
log_info("Create buf '%s' with IN net '%s'\n", buf_name.c_str(ctx), buf_ni->name.c_str(ctx));
}
// move driver
CellInfo *driver_cell = ni.driver.cell;
IdString driver_port = ni.driver.port;

driver_cell->movePortTo(driver_port, buf_ci, id_O);
buf_ci->connectPort(id_I, buf_ni);
driver_cell->connectPort(driver_port, buf_ni);
}
}

void run(void)
{
handle_constants();
Expand Down Expand Up @@ -1348,6 +1395,9 @@ struct GowinPacker
ctx->check();

pack_ram16sdp4();
ctx->check();

pack_buffered_nets();

ctx->fixupHierarchy();
ctx->check();
Expand Down

0 comments on commit fa1577f

Please sign in to comment.