blob: 9a4d8f4506f77903e8a1a8fca4666e08d9b263b2 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (C) 2015 Clifford Wolf <clifford@clifford.at>
#
# 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.
#
"""
The way this works is as follows;
Loop over all possible "wires" and work out;
* If the wire is an actual wire or a pin
* A unique, descriptive name
For each wire, we then work out which channel the wire should be assigned too.
We build up the channels as follows;
X Channel
Span 4 Tracks
Empty
Span 12 Tracks
Empty
Global Tracks
Y Channel
Empty
Local Tracks
Empty
Neighbour Tracks
Empty
Span 4 Tracks
Empty
Span 12 Tracks
Empty
Global Tracks
We use the Y channels for the Local + Neighbour track because we have cells
which are multiple tiles wide in the Y direction.
For each wire, we work out the "edges" (IE connection to other wires / pins).
Naming (ie track names)
http://www.clifford.at/icestorm/logic_tile.html
"Note: the Lattice tools use a different normalization scheme for this wire names."
This doc: https://docs.google.com/document/d/1kTehDgse8GA2af5HoQ9Ntr41uNL_NJ43CjA32DofK8E/edit#
I think is based on CW's naming but has some divergences
Some terminology clarification:
- A VPR "track" is a specific net that can be connected to in the global fabric
- Icestorm docs primarily talk about "wires" which generally refer to a concept
how these nets are used per tile
That is, we take the Icestorm tile wire pattern and convert it to VPR tracks
Other resources:
http://hedmen.org/icestorm-doc/icestorm.html
mithro MVP proposal: spans and locals only
Then add globals, then neighbourhood
"""
# Python libs
import logging
import operator
import sys
from collections import defaultdict
# Third party libs
import lxml.etree as ET
import icebox
import icebox_asc2hlc
from ice40_feature import Feature, IceDbEntry
# Local libs
import lib.rr_graph.channel as channel
import lib.rr_graph.graph as graph
import lib.rr_graph.points as points
from lib.rr_graph import Offset
from lib.asserts import assert_type
NP = points.NamedPosition
ic = None
class PositionIcebox(graph.Position):
def __str__(self):
return "PI(%2s,%2s)" % self
def __repr__(self):
return str(self)
class PositionVPR(graph.Position):
def __str__(self):
return "PV(%2s,%2s)" % self
def __repr__(self):
return str(self)
def pos_icebox2vpr(pos):
"""Convert icebox to VTR coordinates."""
assert_type(pos, PositionIcebox)
return PositionVPR(pos.x + 2, pos.y + 2)
def pos_vpr2icebox(pos):
"""Convert VTR to icebox coordinates."""
assert_type(pos, PositionVPR)
return PositionIcebox(pos.x - 2, pos.y - 2)
def pos_icebox2vprpin(pos):
"""Convert icebox IO position into VTR 'pin' position."""
if pos.x == 0:
return PositionVPR(1, pos.y + 2)
elif pos.y == 0:
return PositionVPR(pos.x + 2, 1)
elif pos.x == ic.max_x:
return PositionVPR(pos.x + 2 + 1, pos.y + 2)
elif pos.y == ic.max_y:
return PositionVPR(pos.x + 2, pos.y + 2 + 1)
assert False, (pos, (ic.max_x, ic.max_y))
class RunOnStr:
"""Don't run function until a str() is called."""
def __init__(self, f, *args, **kw):
self.f = f
self.args = args
self.kw = kw
self.s = None
def __str__(self):
if not self.s:
self.s = self.f(*self.args, **self.kw)
return self.s
def format_node(g, node):
"""A pretty string from an RoutingGraph node."""
if node is None:
return "None"
assert isinstance(node, ET._Element), node
if node.tag == "node":
return RunOnStr(graph.RoutingGraphPrinter.node, node, g.block_grid)
elif node.tag == "edge":
return RunOnStr(
graph.RoutingGraphPrinter.edge, g.routing, node, g.block_grid
)
def format_entry(e):
"""A pretty string from an icebox entry."""
try:
bits, sw, src, dst, *args = e
except ValueError:
return str(e)
if args:
args = " " + str(args)
else:
args = ""
return RunOnStr(
operator.mod, "[%s %s %s %s%s]", (",".join(bits), sw, src, dst, args)
)
def is_corner(ic, pos):
"""Return if a position is the corner of an chip."""
return pos in ((0, 0), (0, ic.max_y), (ic.max_x, 0), (ic.max_x, ic.max_y))
def tiles(ic):
"""Return all tiles on the chip (except the corners)."""
for x in range(ic.max_x + 1):
for y in range(ic.max_y + 1):
p = PositionIcebox(x, y)
if is_corner(ic, p):
continue
yield p
def filter_track_names(group):
"""Filter out unusable icebox track names."""
assert_type(group, list)
for p in group:
assert_type(p.pos, PositionIcebox)
names = list(p.names)
p.names.clear()
for n in names:
# FIXME: Get the sp4_r_v_ wires working
if "sp4_r_v_" in n:
continue
# FIXME: Fix the carry logic.
if "cout" in n or "carry_in" in n:
continue
if "lout" in n:
continue
p.names.append(n)
rgroup = []
for p in group:
if not p.names:
continue
rgroup.append(p)
return rgroup
def group_hlc_name(group):
"""Get the HLC "global name" from a group local names."""
assert_type(group, list)
global ic
hlcnames = defaultdict(int)
hlcnames_details = []
for ipos, localnames in group:
for name in localnames:
assert_type(ipos, PositionIcebox)
hlcname = icebox_asc2hlc.translate_netname(
*ipos, ic.max_x - 1, ic.max_y - 1, name
)
if hlcname != name:
hlcnames_details.append((ipos, name, hlcname))
hlcnames[hlcname] += 1
if not hlcnames:
return None
if len(hlcnames) > 1:
logging.warn("Multiple HLC names (%s) found group %s", hlcnames, group)
filtered_hlcnames = {k: v for k, v in hlcnames.items() if v > 1}
if not filtered_hlcnames:
return None
if len(filtered_hlcnames) != 1:
logging.warn(
"Skipping as conflicting names for %s (%s)", hlcnames,
hlcnames_details
)
return None
hlcnames = filtered_hlcnames
assert len(hlcnames) == 1, (hlcnames, hlcnames_details)
return list(hlcnames.keys())[0]
def group_seg_type(group):
"""Get the segment type from a group of local names."""
assert_type(group, list)
types = defaultdict(int)
for ipos, localnames in group:
assert_type(ipos, PositionIcebox)
for name in localnames:
# ???
if "/" in name:
types["pin"] += 1
continue
if "carry" in name:
types["pin"] += 1
continue
# DSP Pins
# FIXME: Must be a better way to do this.
if name in ("clk", "ce", "c", "a", "b", "c", "ci", "o", "co"):
types["pin"] += 1
continue
if "hold" in name:
types["pin"] += 1
continue
if "rst" in name: # irsttop, irstbot, orsttop, orstbot
types["pin"] += 1
continue
if "load" in name: # oloadtop, oloadbot
types["pin"] += 1
continue
if "addsub" in name: # oloadtop, oloadbot
types["pin"] += 1
continue
# Normal tracks...
if "local" in name:
types["local"] += 1
if "op" in name:
types["neigh"] += 1
if "global" in name:
types["global"] += 1
if "sp4" in name or "span4" in name:
types["span4"] += 1
if "sp12" in name or "span12" in name:
types["span12"] += 1
# The global drivers
if "fabout" in name:
types["local"] += 1
if "pin" in name:
types["local"] += 1
assert types, "No group types for {}".format(group)
if len(types) > 1:
logging.warn("Multiple types (%s) found for group %s", types, group)
filtered_types = {k: v for k, v in types.items() if v > 1}
assert len(filtered_types) == 1, (filtered_types, types, group)
types = filtered_types
assert len(types) == 1, "Multiple group types {} for {}".format(
types, group
)
for k in types:
return k
assert False, types
def group_chan_type(group):
"""Get the channel track type from a group of local names."""
assert_type(group, list)
for ipos, netname in group:
assert_type(ipos, PositionIcebox)
if "local" in netname:
return channel.Track.Type.Y
if "_h" in netname:
return channel.Track.Type.X
if "_v" in netname:
return channel.Track.Type.Y
# The drivers
if "fabout" in netname:
return channel.Track.Type.X
if "pin" in netname:
return channel.Track.Type.X
if "/" in netname:
return channel.Track.Type.Y
assert False, group
return None
def init(device_name, read_rr_graph):
global ic
ic = icebox.iceconfig()
{
# 't4': ic.setup_empty_t4,
'8k': ic.setup_empty_8k,
'5k': ic.setup_empty_5k,
'1k': ic.setup_empty_1k,
'384': ic.setup_empty_384,
}[device_name]()
print('Loading rr_graph')
g = graph.Graph(read_rr_graph, clear_fabric=True)
g.set_tooling(
name="icebox",
version="dev",
comment="Generated for iCE40 {} device".format(device_name)
)
return ic, g
def add_pin_aliases(g, ic):
"""Create icebox local names from the architecture pin names."""
name_rr2local = {}
# PLB - http://www.clifford.at/icestorm/logic_tile.html
name_rr2local['PLB.lutff_global/s_r[0]'] = 'lutff_global/s_r'
name_rr2local['PLB.lutff_global/clk[0]'] = 'lutff_global/clk'
name_rr2local['PLB.lutff_global/cen[0]'] = 'lutff_global/cen'
# FIXME: these two are wrong I think, but don't worry about carry for now
# name_rr2local['PLB.FCIN[0]'] = 'lutff_0/cin'
# name_rr2local['PLB.FCOUT[0]'] = 'lutff_7/cout'
# name_rr2local['PLB.lutff_0_cin[0]'] = 'lutff_0/cin'
# name_rr2local['PLB.lutff_7_cout[0]'] = 'lutff_7/cout'
for luti in range(8):
name_rr2local['PLB.lutff_{}/out[0]'.format(luti)
] = 'lutff_{}/out'.format(luti)
for lut_input in range(4):
name_rr2local['PLB.lutff_{}/in[{}]'.format(luti, lut_input)
] = 'lutff_{}/in_{}'.format(luti, lut_input)
name_rr2local['PLB.FCOUT[0]'] = 'lutff_0/cout'
# PIO - http://www.clifford.at/icestorm/io_tile.html
for blocki in range(2):
name_rr2local['PIO.[{}]LATCH[0]'.format(blocki)
] = 'io_{}/latch'.format(blocki)
name_rr2local['PIO.[{}]OUTCLK[0]'.format(blocki)
] = 'io_{}/outclk'.format(blocki)
name_rr2local['PIO.[{}]CEN[0]'.format(blocki)
] = 'io_{}/cen'.format(blocki)
name_rr2local['PIO.[{}]INCLK[0]'.format(blocki)
] = 'io_{}/inclk'.format(blocki)
name_rr2local['PIO.[{}]D_IN[0]'.format(blocki)
] = 'io_{}/D_IN_0'.format(blocki)
name_rr2local['PIO.[{}]D_IN[1]'.format(blocki)
] = 'io_{}/D_IN_1'.format(blocki)
name_rr2local['PIO.[{}]D_OUT[0]'.format(blocki)
] = 'io_{}/D_OUT_0'.format(blocki)
name_rr2local['PIO.[{}]D_OUT[1]'.format(blocki)
] = 'io_{}/D_OUT_1'.format(blocki)
name_rr2local['PIO.[{}]OUT_ENB[0]'.format(blocki)
] = 'io_{}/OUT_ENB'.format(blocki)
name_rr2local['PIO.[{}]PACKAGE_PIN[0]'.format(blocki)
] = 'io_{}/pin'.format(blocki)
# RAM - http://www.clifford.at/icestorm/ram_tile.html
for top_bottom in 'BT':
# rdata, wdata, and mask ranges are the same based on Top/Bottom
if top_bottom == 'T':
data_range = range(8, 16)
# top has Read clock and enable and address
rw = 'R'
else:
data_range = range(0, 8)
# top has Read clock and enable and address
rw = 'W'
def add_ram_pin(rw, sig, ind=None):
if ind is None:
name_rr2local['RAM.{}{}[{}]'.format(rw, sig, 0)
] = 'ram/{}{}'.format(rw, sig)
else:
name_rr2local['RAM.{}{}[{}]'.format(rw, sig, ind)
] = 'ram/{}{}_{}'.format(rw, sig, ind)
add_ram_pin(rw, 'CLK')
add_ram_pin(rw, 'CLKE')
add_ram_pin(rw, 'E')
for ind in range(11):
add_ram_pin(rw, 'ADDR', ind)
for ind in data_range:
add_ram_pin('R', 'DATA', ind)
add_ram_pin('W', 'DATA', ind)
add_ram_pin('', 'MASK', ind)
# RAM
for top_bottom in 'BT':
# rdata, wdata, and mask ranges are the same based on Top/Bottom
if top_bottom == 'T':
data_range = range(8, 16)
# top has Read clock and enbable and address
rw = 'R'
else:
data_range = range(0, 8)
# top has Read clock and enbable and address
rw = 'W'
def add_ram_pin(rw, sig, ind=None):
if ind is None:
name_rr2local['RAM.{}{}[{}]'.format(rw, sig, 0)
] = 'ram/{}{}'.format(rw, sig)
else:
name_rr2local['RAM.{}{}[{}]'.format(rw, sig, ind)
] = 'ram/{}{}_{}'.format(rw, sig, ind)
add_ram_pin(rw, 'CLK')
add_ram_pin(rw, 'CLKE')
add_ram_pin(rw, 'E')
for ind in range(11):
add_ram_pin(rw, 'ADDR', ind)
for ind in data_range:
add_ram_pin('R', 'DATA', ind)
add_ram_pin('W', 'DATA', ind)
add_ram_pin('', 'MASK', ind)
for block in g.block_grid:
for pin in block.pins:
if "RAM" in block.block_type.name:
pin_offset = ram_pin_offset(pin)
elif "DSP" in block.block_type.name:
pin_offset = dsp_pin_offset(pin)
else:
pin_offset = Offset(0, 0)
pin_pos = block.position + pin_offset
vpos = PositionVPR(*pin_pos)
ipos = pos_vpr2icebox(vpos)
node = g.routing.localnames[(pin_pos, pin.name)]
node.set_metadata("hlc_coord", "{},{}".format(*ipos))
logging.debug("On %s for %s", vpos, format_node(g, node))
hlc_name = name_rr2local.get(
pin.xmlname, group_hlc_name([NP(ipos, [pin.name])])
)
logging.debug(
" Setting local name %s on %s for %s", hlc_name, vpos,
format_node(g, node)
)
g.routing.localnames.add(vpos, hlc_name, node)
node.set_metadata("hlc_name", hlc_name)
rr_name = pin.xmlname
try:
localname = name_rr2local[rr_name]
except KeyError:
logging.warn(
"On %s - %s doesn't have a translation", ipos, rr_name
)
continue
# FIXME: only add for actual position instead for all
if localname == hlc_name:
logging.debug(
" Local name %s same as hlc_name on %s for %s", localname,
vpos, format_node(g, node)
)
else:
assert False, "{} != {}".format(localname, hlc_name)
logging.debug(
" Setting local name %s on %s for %s", localname, vpos,
format_node(g, node)
)
g.routing.localnames.add(vpos, localname, node)
# FIXME: Currently unused.
def add_global_tracks(g, ic):
"""Add the global tracks to every channel."""
GLOBAL_SPINE_ROW = ic.max_x // 2
GLOBAL_BUF = "GLOBAL_BUFFER_OUTPUT"
padin_db = ic.padin_pio_db()
iolatch_db = ic.iolatch_db()
# Create the 8 global networks
for i in range(0, 8):
glb_name = "glb_netwk_{}".format(i)
seg = g.segments[glb_name]
# Vertical global wires
for x in range(0, ic.max_x + 1):
istart = PositionIcebox(x, 0)
iend = PositionIcebox(x, ic.max_y)
track, track_node = g.create_xy_track(
pos_icebox2vpr(istart),
pos_icebox2vpr(iend),
segment=seg,
typeh=channel.Track.Type.Y,
direction=channel.Track.Direction.BI
)
track_node.set_metadata("hlc_name", glb_name)
for y in range(0, ic.max_y + 1):
ipos = PositionIcebox(x, y)
vpos = pos_icebox2vpr(ipos)
g.routing.localnames.add(vpos, glb_name, track_node)
# One horizontal wire
istart = PositionIcebox(0, GLOBAL_SPINE_ROW)
iend = PositionIcebox(ic.max_x + 1, GLOBAL_SPINE_ROW)
track, track_node = g.create_xy_track(
pos_icebox2vpr(istart),
pos_icebox2vpr(iend),
segment=seg,
typeh=channel.Track.Type.X,
direction=channel.Track.Direction.BI
)
track_node.set_metadata("hlc_name", glb_name)
for x in range(0, ic.max_x + 1):
ipos = PositionIcebox(x, GLOBAL_SPINE_ROW)
vpos = pos_icebox2vpr(ipos)
g.routing.localnames.add(vpos, glb_name + "_h", track_node)
# Connect the vertical wires to the horizontal one to make a single
# global network
for x in range(0, ic.max_x + 1):
ipos = PositionIcebox(x, GLOBAL_SPINE_ROW)
create_edge_with_names(
g,
glb_name,
glb_name + "_h",
ipos,
g.switches['short'],
)
# Create the padin_X localname aliases for the glb_network_Y
# FIXME: Why do these exist!?
for n, (gx, gy, gz) in enumerate(padin_db):
vpos = pos_icebox2vpr(PositionIcebox(gx, gy))
glb_name = "glb_netwk_{}".format(n)
glb_node = g.routing.get_by_name(glb_name, vpos)
g.routing.localnames.add(vpos, "padin_{}".format(gz), glb_node)
# Create the IO->global drivers which exist in some IO tiles.
for n, (gx, gy, gz) in enumerate(padin_db):
glb_name = "glb_netwk_{}".format(n)
ipos = PositionIcebox(gx, gy)
vpos = pos_icebox2vpr(ipos)
# Create the GLOBAL_BUFFER_OUTPUT track and short it to the
# PACKAGE_PIN output of the correct IO subtile.
track, track_node = g.create_xy_track(
vpos,
vpos,
segment=g.segments[glb_name],
typeh=channel.Track.Type.Y,
direction=channel.Track.Direction.BI
)
track_node.set_metadata("hlc_name", "io_{}/{}".format(gz, GLOBAL_BUF))
g.routing.localnames.add(vpos, GLOBAL_BUF, track_node)
create_edge_with_names(
g,
"io_{}/pin".format(gz),
GLOBAL_BUF,
ipos,
g.switches["driver"],
)
# Create the switch to enable the GLOBAL_BUFFER_OUTPUT track to
# drive the global network.
create_edge_with_names(
g,
GLOBAL_BUF,
glb_name,
ipos,
g.switches["buffer"],
)
# Work out for which tiles the fabout is directly shorted to a global
# network.
fabout_to_glb = {}
for gn, (gx, gy, gz) in enumerate(padin_db):
ipos = PositionIcebox(gx, gy)
assert ipos not in fabout_to_glb, (ipos, fabout_to_glb)
gn = None
for igx, igy, ign in ic.gbufin_db():
if ipos == (igx, igy):
gn = ign
assert gn is not None, (ipos, gz, gn)
fabout_to_glb[ipos] = (gz, gn)
# Create the nets which are "global" to an IO tile pair.
for ipos in list(tiles(ic)):
tile_type = ic.tile_type(*ipos)
if tile_type != "IO":
continue
vpos = pos_icebox2vpr(ipos)
# Create the "io_global" signals inside a tile
io_names = [
"inclk",
"outclk",
"cen",
"latch",
]
for name in io_names:
tile_glb_name = "io_global/{}".format(name)
hlc_name = group_hlc_name([NP(ipos, [tile_glb_name])])
track, track_node = g.create_xy_track(
vpos,
vpos,
segment=g.segments['tile_global'],
typeh=channel.Track.Type.Y,
direction=channel.Track.Direction.BI
)
track_node.set_metadata("hlc_name", hlc_name)
g.routing.localnames.add(vpos, tile_glb_name, track_node)
# Connect together the io_global signals inside a tile
for i in range(2):
local_name = "io_{}/{}".format(i, name)
create_edge_with_names(
g,
tile_glb_name,
local_name,
ipos,
g.switches['driver'],
)
# Create the fabout track. Every IO tile has a fabout track, but
# sometimes the track is special;
# - drives a glb_netwk_X,
# - drives the io_global/latch for the bank
hlc_name = group_hlc_name([NP(ipos, ["fabout"])])
track, track_node = g.create_xy_track(
vpos,
vpos,
segment=g.segments['fabout'],
typeh=channel.Track.Type.Y,
direction=channel.Track.Direction.BI
)
track_node.set_metadata("hlc_name", hlc_name)
g.routing.localnames.add(vpos, "fabout", track_node)
# Fabout drives a global network?
if ipos in fabout_to_glb:
gz, gn = fabout_to_glb[ipos]
create_edge_with_names(
g,
"fabout",
"glb_netwk_{}".format(gn),
ipos,
g.switches['short'],
)
# Fabout drives the io_global/latch?
if ipos in iolatch_db:
create_edge_with_names(
g,
"fabout",
"io_global/latch",
ipos,
g.switches['short'],
)
def create_edge_with_names(
g, src_name, dst_name, ipos, switch, skip=None, bidir=None, metadata={}
):
"""Create an edge at a given icebox position from two local names."""
assert_type(src_name, str)
assert_type(dst_name, str)
assert_type(ipos, PositionIcebox)
assert_type(switch, graph.Switch)
if skip is None:
def skip(fmt, *a, **k):
raise AssertionError(fmt % a)
if switch.type in (graph.SwitchType.SHORT, graph.SwitchType.PASS_GATE):
if bidir is None:
bidir = True
else:
assert bidir is True, "Switch {} must be bidir ({})".format(
switch, (ipos, src_name, dst_name, bidir)
)
elif bidir is None:
bidir = False
src_hlc_name = group_hlc_name([NP(ipos, [src_name])])
dst_hlc_name = group_hlc_name([NP(ipos, [dst_name])])
vpos = pos_icebox2vpr(ipos)
src_node = g.routing.get_by_name(src_name, vpos, None)
dst_node = g.routing.get_by_name(dst_name, vpos, None)
if src_node is None:
skip(
"src missing *%s:%s* (%s) node %s => %s:%s (%s) node %s",
vpos,
src_name,
src_hlc_name,
format_node(g, src_node),
vpos,
dst_name,
dst_hlc_name,
format_node(g, dst_node),
level=logging.WARNING,
)
return
if dst_node is None:
skip(
"dst missing %s:%s (%s) node %s => *%s:%s* (%s) node %s",
vpos,
src_name,
src_hlc_name,
format_node(g, src_node),
vpos,
dst_name,
dst_hlc_name,
format_node(g, dst_node),
)
return
logging.debug(
"On %s add %-8s edge %s - %s:%s (%s) node %s => %s:%s (%s) node %s",
ipos,
switch.name,
len(g.routing.id2element[graph.RoutingEdge]),
vpos,
src_name,
src_hlc_name,
format_node(g, src_node),
vpos,
dst_name,
dst_hlc_name,
format_node(g, dst_node),
)
metadata["hlc_coord"] = "{},{}".format(*ipos)
g.routing.create_edge_with_nodes(
src_node,
dst_node,
switch=switch,
bidir=bidir,
metadata={Offset(0, 0): metadata},
)
def add_track_with_lines(g, ic, segment, lines, connections, hlc_name_f):
"""Add tracks to the rr_graph from straight lines and connections."""
logging.debug(
"Created track %s from sections: %s", segment.name, len(lines)
)
logging.debug("Created track %s from sections: %s", segment.name, lines)
for line in lines:
istart, iend = points.straight_ends([p.pos for p in line])
logging.debug(" %s>%s (%s)", istart, iend, line)
for ipos, joins in sorted(connections.items()):
for name_a, name_b in joins:
logging.debug(" %s %s<->%s", ipos, name_a, name_b)
for line in lines:
istart, iend = points.straight_ends([p.pos for p in line])
vstart, vend = pos_icebox2vpr(istart), pos_icebox2vpr(iend)
if line.direction.value == '-':
typeh = channel.Track.Type.X
elif line.direction.value == '|':
typeh = channel.Track.Type.Y
else:
typeh = channel.Track.Type.Y
track, track_node = g.create_xy_track(
vstart,
vend,
segment=segment,
typeh=typeh,
direction=channel.Track.Direction.BI,
)
track_node.set_metadata(
"hlc_coord", "{},{}".format(*istart), offset=Offset(0, 0)
)
track_fmt = format_node(g, track_node)
logging.debug(
" Created track %s %s from %s", track_fmt, segment.name, typeh
)
for npos in line:
ipos = npos.pos
vpos = pos_icebox2vpr(ipos)
offset = Offset(
npos.pos.x - line[0].pos.x, npos.pos.y - line[0].pos.y
)
hlc_name = hlc_name_f(line, ipos)
if hlc_name is not None:
track_node.set_metadata("hlc_name", hlc_name, offset=offset)
for n in npos.names:
try:
g.routing.localnames[(vpos, n)]
drv_node = g.routing.localnames[(vpos, n)]
drv_fmt = str(format_node(g, drv_node))
logging.debug(
" Existing node %s with local name %s on %s", drv_fmt,
n, vpos
)
g.routing.localnames.add(vpos, n + "_?", track_node)
if ipos in connections:
continue
connections[ipos].append((n, n + "_?"))
except KeyError:
g.routing.localnames.add(vpos, n, track_node)
logging.debug(
" Setting local name %s on %s for %s", n, vpos,
track_fmt
)
for ipos, joins in sorted(connections.items()):
logging.info("pos:%s joins:%s", ipos, joins)
for name_a, name_b in joins:
vpos = pos_icebox2vpr(ipos)
node_a = g.routing.localnames[(vpos, name_a)]
node_b = g.routing.localnames[(vpos, name_b)]
logging.debug(
" Shorting at coords %s - %s -> %s\n\t%s\n ->\n\t%s",
ipos,
name_a,
name_b,
format_node(g, node_a),
format_node(g, node_b),
)
create_edge_with_names(
g,
name_a,
name_b,
ipos,
g.switches["short"],
)
def add_track_with_globalname(g, ic, segment, connections, lines, globalname):
"""Add tracks to the rr_graph from lines, connections, using a HLC global name."""
def hlc_name_f(line, offset):
return globalname
add_track_with_lines(g, ic, segment, lines, connections, hlc_name_f)
def add_track_with_localnames(g, ic, segment, connections, lines):
"""Add tracks to the rr_graph from lines, connections, using local names (from the positions).
"""
def hlc_name_f(line, pos):
for npos in line:
if pos.x == npos.x and pos.y == npos.y:
names = set(n for n in npos.names if not n.endswith("_x"))
assert len(names) <= 1, (line, npos)
if not names:
return None
return names.pop()
assert False, (line, npos, pos)
new_lines = []
for line in lines:
names = [n for n in line.names if not n.endswith('_x')]
if len(names) <= 1:
continue
for npos in line:
if len(npos.names) <= 1:
continue
fnames = [i for i in npos.names if not i.endswith("_x")]
if len(fnames) <= 1:
continue
logging.debug("temp - %s %s %s", npos, fnames[0], fnames[1:])
for name in fnames[1:]:
npos.names.remove(name)
new_pos = points.NamedPosition(npos.pos, [name])
new_line = points.StraightSegment(
direction=line.direction, positions=[new_pos]
)
logging.debug(
"npos:%s name:%s new_pos:%s new_line:%s", npos, name,
new_pos, new_line
)
new_lines.append(new_line)
connections[npos.pos].append((fnames[0], name))
assert new_lines[-1], (npos, new_lines)
assert npos.names, npos
logging.debug("new_lines: %s", new_lines)
lines.extend(new_lines)
add_track_with_lines(g, ic, segment, lines, connections, hlc_name_f)
def add_tracks(g, ic, all_group_segments, segtype_filter=None):
"""Adding tracks from icebox segment groups."""
for group in sorted(all_group_segments):
positions = {}
for x, y, netname in group:
p = PositionIcebox(x, y)
if p in positions:
positions[p].names.append(netname)
else:
positions[(x, y)] = points.NamedPosition(p, [netname])
positions = list(positions.values())
segtype = group_seg_type(positions)
if segtype_filter is not None and segtype != segtype_filter:
continue
if segtype == "unknown":
logging.debug("Skipping unknown track group: %s", group)
continue
if segtype == "global":
logging.debug("Skipping global track group: %s", group)
continue
segment = g.segments[segtype]
fpositions = filter_track_names(positions)
if not fpositions:
logging.debug("Filtered out track group: %s", positions)
continue
connections, lines = points.decompose_into_straight_lines(fpositions)
logging.info("connections:%s lines:%s", connections, lines)
hlc_name = group_hlc_name(fpositions)
if hlc_name:
add_track_with_globalname(
g, ic, segment, connections, lines, hlc_name
)
else:
add_track_with_localnames(g, ic, segment, connections, lines)
def add_edges(g, ic):
"""Adding edges to the rr_graph from icebox edges."""
for ipos in list(tiles(ic)):
"""
# FIXME: If IO type, connect PACKAGE_PIN_I and PACKAGE_PIN_O manually...
tile_type = ic.tile_type(*ipos)
vpos = pos_icebox2vpr(ipos)
if tile_type == "IO":
vpr_pio_pos = vpos
vpr_pin_pos = pos_icebox2vprpin(ipos)
print(tile_type, "pio:", ipos, vpos, "pin:", vpr_pin_pos)
if "EMPTY" in g.block_grid[vpr_pin_pos].block_type.name:
continue
print("PIO localnames:", g.routing.localnames[vpr_pio_pos])
print("PIN localnames:", g.routing.localnames[vpr_pin_pos])
for i in [0, 1]:
# pio -> pin
pio_iname = "O[{}]".format(i)
src_inode = g.routing.localnames[(vpr_pio_pos, pio_iname)]
pin_iname = "[{}]O[0]".format(i)
dst_inode = g.routing.localnames[(vpr_pin_pos, pin_iname)]
edgei = g.routing.create_edge_with_nodes(
src_inode, dst_inode, switch=g.switches["short"])
print(vpr_pio_pos, pio_iname, format_node(g, src_inode),
vpr_pin_pos, pin_iname, format_node(g, dst_inode),
edgei)
# pin -> pio
pin_oname = "[{}]I[0]".format(i)
src_onode = g.routing.localnames[(vpr_pin_pos, pin_oname)]
pio_oname = "I[{}]".format(i)
dst_onode = g.routing.localnames[(vpr_pio_pos, pio_oname)]
edgeo = g.routing.create_edge_with_nodes(
src_onode, dst_onode, switch=g.switches["short"])
print(vpr_pin_pos, pin_oname, format_node(g, src_onode),
vpr_pio_pos, pio_oname, format_node(g, dst_onode),
edgeo)
"""
for entry in ic.tile_db(*ipos):
def skip(m, *args, level=logging.DEBUG, **kw):
p = {
logging.DEBUG: logging.debug,
logging.WARNING: logging.warn,
logging.INFO: logging.info,
}[level]
p(
"On %s skipping entry %s: " + m, ipos, format_entry(entry),
*args, **kw
)
if not ic.tile_has_entry(*ipos, entry):
# skip('Non-existent edge!')
continue
switch_type = entry[1]
if switch_type not in ("routing", "buffer"):
skip('Unknown switch type %s', switch_type)
continue
src_localname = entry[2]
dst_localname = entry[3]
remaining = filter_track_names(
[NP(ipos, [src_localname]),
NP(ipos, [dst_localname])]
)
if len(remaining) != 2:
skip("Remaining %s", remaining)
continue
feature, _ = Feature.from_icedb_entry(
IceDbEntry(
ic.tile_type(*ipos), ipos, entry[0], entry[1:], None
)
).to_fasm_entry()
fasm_data = {'fasm_features': feature}
create_edge_with_names(
g,
src_localname,
dst_localname,
ipos,
g.switches[switch_type],
skip,
metadata=fasm_data
)
def print_nodes_edges(g):
print(
"Edges: %d (index: %d)" % (
len(g.routing._xml_parent(graph.RoutingEdge)),
len(g.routing.id2element[graph.RoutingEdge])
)
)
print(
"Nodes: %d (index: %d)" % (
len(g.routing._xml_parent(graph.RoutingNode)),
len(g.routing.id2element[graph.RoutingNode])
)
)
def ram_pin_offset(pin):
"""Get the offset for a given RAM pin."""
# The pin split between top/bottom tiles is different on the 1k to all the
# other parts.
ram_pins_0to8 = ["WADDR[0]", "WCLKE[0]", "WCLK[0]", "WE[0]"]
for i in range(8):
ram_pins_0to8.extend(
[
"RDATA[{}]".format(i),
"MASK[{}]".format(i),
"WDATA[{}]".format(i),
]
)
ram_pins_0to8.extend(['WADDR[{}]'.format(i) for i in range(0, 11)])
ram_pins_8to16 = ["RCLKE[0]", "RCLK[0]", "RE[0]"]
for i in range(8, 16):
ram_pins_8to16.extend(
[
"RDATA[{}]".format(i),
"MASK[{}]".format(i),
"WDATA[{}]".format(i),
]
)
ram_pins_8to16.extend(['RADDR[{}]'.format(i) for i in range(0, 11)])
if ic.device == '384':
assert False, "384 device doesn't have RAM!"
elif ic.device == '1k':
top_pins = ram_pins_8to16
bot_pins = ram_pins_0to8
else:
assert ic.device in ('5k',
'8k'), "{} is unknown device".format(ic.device)
top_pins = ram_pins_0to8
bot_pins = ram_pins_8to16
if pin.name in top_pins:
return Offset(0, 1)
elif pin.name in bot_pins:
return Offset(0, 0)
else:
assert False, "RAM pin {} doesn't match name expected for metadata".format(
pin.name
)
def dsp_pin_offset(pin):
return Offset(0, pin.port_index)
def get_pin_meta(block, pin):
"""Get the offset and edge for a given pin."""
grid_sz = PositionVPR(ic.max_x + 1 + 4, ic.max_y + 1 + 4)
if "PIN" in block.block_type.name:
if block.position.x == 1:
return (graph.RoutingNodeSide.RIGHT, Offset(0, 0))
elif block.position.y == 1:
return (graph.RoutingNodeSide.TOP, Offset(0, 0))
elif block.position.y == grid_sz.y - 2:
return (graph.RoutingNodeSide.BOTTOM, Offset(0, 0))
elif block.position.x == grid_sz.x - 2:
return (graph.RoutingNodeSide.LEFT, Offset(0, 0))
if "RAM" in block.block_type.name:
return (graph.RoutingNodeSide.RIGHT, ram_pin_offset(pin))
if "DSP" in block.block_type.name:
return (graph.RoutingNodeSide.RIGHT, dsp_pin_offset(pin))
if "PIO" in block.block_type.name:
if pin.name.startswith("O[") or pin.name.startswith("I["):
if block.position.x == 2:
return (graph.RoutingNodeSide.LEFT, Offset(0, 0))
elif block.position.y == 2:
return (graph.RoutingNodeSide.BOTTOM, Offset(0, 0))
elif block.position.y == grid_sz.y - 3:
return (graph.RoutingNodeSide.TOP, Offset(0, 0))
elif block.position.x == grid_sz.x - 3:
return (graph.RoutingNodeSide.RIGHT, Offset(0, 0))
return (graph.RoutingNodeSide.RIGHT, Offset(0, 0))
if "PLB" in block.block_type.name:
if "FCIN" in pin.port_name:
return (graph.RoutingNodeSide.BOTTOM, Offset(0, 0))
elif "FCOUT" in pin.port_name:
return (graph.RoutingNodeSide.TOP, Offset(0, 0))
return (graph.RoutingNodeSide.RIGHT, Offset(0, 0))
assert False, (block, pin)
def main(part, read_rr_graph, write_rr_graph):
global ic
print('Importing input g', part)
ic, g = init(part, read_rr_graph)
short = graph.Switch(
id=g.switches.next_id(),
type=graph.SwitchType.SHORT,
name="short",
timing=graph.SwitchTiming(R=0, Cin=0, Cout=0, Tdel=0),
sizing=graph.SwitchSizing(mux_trans_size=0, buf_size=0),
)
g.add_switch(short)
driver = graph.Switch(
id=g.switches.next_id(),
type=graph.SwitchType.BUFFER,
name="driver",
timing=graph.SwitchTiming(R=0, Cin=0, Cout=0, Tdel=0),
sizing=graph.SwitchSizing(mux_trans_size=0, buf_size=0),
)
g.add_switch(driver)
# my_test(ic, g)
print('Source g loaded')
print_nodes_edges(g)
grid_sz = g.block_grid.size
print("Grid size: %s" % (grid_sz, ))
print()
print('Clearing')
print('=' * 80)
print('Clearing nodes and edges')
g.routing.clear()
print('Clearing channels')
g.channels.clear()
print('Cleared original g')
print_nodes_edges(g)
print()
print()
print('Rebuilding block I/O nodes')
print('=' * 80)
g.create_block_pins_fabric(
g.switches['__vpr_delayless_switch__'], get_pin_meta
)
print_nodes_edges(g)
print()
print('Adding pin aliases')
print('=' * 80)
add_pin_aliases(g, ic)
segments = ic.group_segments(list(tiles(ic)))
add_tracks(g, ic, segments, segtype_filter="local")
add_tracks(g, ic, segments, segtype_filter="neigh")
add_tracks(g, ic, segments, segtype_filter="span4")
add_tracks(g, ic, segments, segtype_filter="span12")
# add_global_tracks(g, ic)
print()
print('Adding edges')
print('=' * 80)
add_edges(g, ic)
print()
print_nodes_edges(g)
print()
print('Saving')
open(write_rr_graph, 'w').write(
ET.tostring(g.to_xml(), pretty_print=True).decode('ascii')
)
print()
print('Exiting')
sys.exit(0)
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
'--verbose', '-v', action='store_true', help='verbose output'
)
parser.add_argument('--device', help='')
parser.add_argument('--read_rr_graph', help='')
parser.add_argument('--write_rr_graph', default='out.xml', help='')
args = parser.parse_args()
if args.verbose:
loglevel = logging.DEBUG
else:
loglevel = logging.INFO
logging.basicConfig(level=loglevel)
mode = args.device.lower()[2:]
main(mode, args.read_rr_graph, args.write_rr_graph)