blob: f3ee0a38c2b0327a65571a9a7b3a8b831d9025a6 [file] [log] [blame] [edit]
#!/usr/bin/env python3
"""
This file for packing tracks into channels.
It does *not* manage channel XML nodes.
Note channels go between switchboxes. Switchboxes cannot be at the last grid
coordinate.
Therefore, you need a grid size of at least 3 rows or cols to allow any
channels to exist. With a 3 width configuration you would get a channel of
length 0, connecting the switchbox at 0 to the switchbox at 1.
With above in mind, objects here entirely omit the last row/col and placing a
channel in the first is illegal.
Specifically:
* For CHANX: X=0 is invalid, X=grid.width-1 is invalid
* For CHANY: Y=0 is invalid, Y=grid.height-1 is invalid
"""
import pprint
import enum
import io
from collections import namedtuple
import lxml.etree as ET
from . import Position
from . import Size
from . import node_pos, single_element
from ..asserts import assert_eq
from ..asserts import assert_len_eq
from ..asserts import assert_type
from ..asserts import assert_type_or_none
# FIXME: doctests and static_property are not playing nice together.
# from . import static_property
static_property = property
class ChannelNotStraight(TypeError):
pass
_Track = namedtuple(
"Track", ("start", "end", "direction", "segment_id", "idx")
)
class Track(_Track):
"""
Represents a single CHANX or CHANY (track) within a channel.
IE The tracks of a x_list or y_list entry in <channels> element.
start: start Pos
end: end Pos
idx: XML index integer
"""
class Type(enum.Enum):
"""
subset of NodeType in graph2
"""
# Horizontal routing
X = 'CHANX'
# Vertical routing
Y = 'CHANY'
def __repr__(self):
return 'Track.Type.' + self.name
class Direction(enum.Enum):
INC = 'INC_DIR'
DEC = 'DEC_DIR'
BI = 'BI_DIR'
def __repr__(self):
return 'Track.Direction.' + self.name
def __new__(
cls,
start,
end,
direction=Direction.INC,
segment_id=0,
idx=None,
name=None,
type_hint=None,
):
"""Make most but not all attributes immutable"""
if not isinstance(start, Position):
start = Position(*start)
assert_type(start, Position)
if not isinstance(end, Position):
end = Position(*end)
assert_type(end, Position)
if start.x != end.x and start.y != end.y:
raise ChannelNotStraight(
"Track not straight! {}->{}".format(start, end)
)
assert_type(direction, cls.Direction)
assert_type(segment_id, int)
assert_type_or_none(idx, int)
assert_type_or_none(name, str)
assert_type_or_none(type_hint, cls.Type)
obj = _Track.__new__(cls, start, end, direction, segment_id, idx)
obj.name = name
obj.type_hint = type_hint
return obj
@static_property
def type(self):
"""Type of the channel.
Returns: Track.Type
>>> Track((1, 0), (10, 0)).type
Track.Type.X
>>> Track((0, 1), (0, 10)).type
Track.Type.Y
>>> Track((1, 1), (1, 1)).type
Traceback (most recent call last):
...
ValueError: Ambiguous type
>>> Track((1, 1), (1, 1), type_hint=Track.Type.X).type
Track.Type.X
>>> Track((1, 1), (1, 1), type_hint=Track.Type.Y).type
Track.Type.Y
"""
if self.type_hint:
return self.type_hint
guess = self.type_guess
if guess is None:
raise ValueError("Ambiguous type")
return guess
@static_property
def type_guess(self):
"""Type of the channel.
Returns: Track.Type
>>> Track((1, 0), (10, 0)).type_guess
Track.Type.X
>>> Track((0, 1), (0, 10)).type_guess
Track.Type.Y
>>> str(Track((1, 1), (1, 1)).type_guess)
'None'
"""
if self.start.x == self.end.x and self.start.y == self.end.y:
return None
elif self.start.x == self.end.x:
return Track.Type.Y
elif self.start.y == self.end.y:
return Track.Type.X
else:
assert False, self
def positions(self):
"""Generate all positions this track occupies"""
startx, endx = sorted([self.start.x, self.end.x])
starty, endy = sorted([self.start.y, self.end.y])
for x in range(startx, endx + 1):
for y in range(starty, endy + 1):
yield Position(x, y)
@static_property
def start0(self):
"""The non-constant start coordinate.
>>> Track((1, 0), (10, 0)).start0
1
>>> Track((0, 1), (0, 10)).start0
1
>>> Track((1, 1), (1, 1)).start0
Traceback (most recent call last):
...
ValueError: Ambiguous type
>>> Track((1, 1), (1, 1), type_hint=Track.Type.X).start0
1
>>> Track((10, 0), (1, 0)).start0
10
>>> Track((0, 10), (0, 1)).start0
10
"""
if self.type == Track.Type.X:
return self.start.x
elif self.type == Track.Type.Y:
return self.start.y
else:
assert False
@static_property
def end0(self):
"""The non-constant end coordinate.
>>> Track((1, 0), (10, 0)).end0
10
>>> Track((0, 1), (0, 10)).end0
10
>>> Track((1, 1), (1, 1)).end0
Traceback (most recent call last):
...
ValueError: Ambiguous type
>>> Track((1, 1), (1, 1), type_hint=Track.Type.X).end0
1
>>> Track((10, 0), (1, 0)).end0
1
>>> Track((0, 10), (0, 1)).end0
1
"""
if self.type == Track.Type.X:
return self.end.x
elif self.type == Track.Type.Y:
return self.end.y
else:
assert False, self.type
@static_property
def common(self):
"""The common coordinate value.
>>> Track((0, 0), (10, 0)).common
0
>>> Track((0, 0), (0, 10)).common
0
>>> Track((1, 1), (1, 1)).common
Traceback (most recent call last):
...
ValueError: Ambiguous type
>>> Track((1, 1), (1, 1), type_hint=Track.Type.X).common
1
>>> Track((10, 0), (0, 0)).common
0
>>> Track((0, 10), (0, 0)).common
0
>>> Track((4, 10), (4, 0)).common
4
"""
if self.type == Track.Type.X:
assert_eq(self.start.y, self.end.y)
return self.start.y
elif self.type == Track.Type.Y:
assert_eq(self.start.x, self.end.x)
return self.start.x
else:
assert False
@static_property
def length(self):
"""Length of the track.
>>> Track((0, 0), (10, 0)).length
10
>>> Track((0, 0), (0, 10)).length
10
>>> Track((1, 1), (1, 1)).length
0
>>> Track((10, 0), (0, 0)).length
10
>>> Track((0, 10), (0, 0)).length
10
"""
try:
return abs(self.end0 - self.start0)
except ValueError:
return 0
def new_idx(self, idx):
"""Create a new channel with the same start/end but new index value.
>>> s = (1, 4)
>>> e = (1, 8)
>>> c1 = Track(s, e, idx=0)
>>> c2 = c1.new_idx(2)
>>> assert_eq(c1.start, c2.start)
>>> assert_eq(c1.end, c2.end)
>>> c1.idx
0
>>> c2.idx
2
"""
return self.__class__(
self.start,
self.end,
self.direction,
self.segment_id,
idx,
name=self.name,
type_hint=self.type_hint,
)
def __repr__(self):
"""
>>> repr(Track((0, 0), (10, 0)))
'T((0,0), (10,0))'
>>> repr(Track((0, 0), (0, 10)))
'T((0,0), (0,10))'
>>> repr(Track((1, 2), (3, 2), idx=5))
'T((1,2), (3,2), 5)'
>>> repr(Track((1, 2), (3, 2), name="ABC"))
'T(ABC)'
>>> repr(Track((1, 2), (3, 2), idx=5, name="ABC"))
'T(ABC,5)'
"""
if self.name:
idx_str = ""
if self.idx is not None:
idx_str = ",{}".format(self.idx)
return "T({}{})".format(self.name, idx_str)
idx_str = ""
if self.idx is not None:
idx_str = ", {}".format(self.idx)
return "T(({},{}), ({},{}){})".format(
self.start.x, self.start.y, self.end.x, self.end.y, idx_str
)
def __str__(self):
"""
>>> str(Track((0, 0), (10, 0)))
'CHANX 0,0->10,0'
>>> str(Track((0, 0), (0, 10)))
'CHANY 0,0->0,10'
>>> str(Track((1, 2), (3, 2), idx=5))
'CHANX 1,2->3,2 @5'
>>> str(Track((1, 2), (3, 2), name="ABC"))
'ABC'
>>> str(Track((1, 2), (3, 2), idx=5, name="ABC"))
'ABC@5'
"""
idx_str = ""
if self.idx is not None:
idx_str = " @{}".format(self.idx)
if self.name:
return "{}{}".format(self.name, idx_str[1:])
return "{} {},{}->{},{}{}".format(
self.type.value, self.start.x, self.start.y, self.end.x,
self.end.y, idx_str
)
# Short alias.
T = Track
class ChannelGrid(dict):
"""
Functionality:
* Manages single type of track (either `CHANX` or `CHANY`).
* Manages channel width along grid.
* Allocates tracks within channels.
The `ChannelGrid` is indexed by `Position` and returns a sequence width all
the `Track`s at that position.
"""
def __init__(self, size, chan_type):
"""
size: Size representing tile grid width/height
chan_type: of Channels.Type
"""
self.chan_type = chan_type
self.size = Size(*size)
self.clear()
@property
def width(self):
"""Grid width
>>> g = ChannelGrid((6, 7), Track.Type.Y)
>>> g.width
6
"""
return self.size.width
@property
def height(self):
"""Grid height
>>> g = ChannelGrid((6, 7), Track.Type.Y)
>>> g.height
7
"""
return self.size.height
def column(self, x):
"""Get a y coordinate indexed list giving tracks at that x + y position"""
column = []
for y in range(0, self.height):
column.append(self[Position(x, y)])
return column
def row(self, y):
"""Get an x coordinate indexed list giving tracks at that x + y position"""
row = []
for x in range(0, self.width):
row.append(self[Position(x, y)])
return row
"""
dim_*: CHANX/CHANY abstraction functions
These can be used to write code that is not aware of specifics related to CHANX vs CHANY
"""
def dim_rc(self):
"""Get dimension a, the number of row/col positions"""
return {
Track.Type.X: self.height,
Track.Type.Y: self.width,
}[self.chan_type]
def dim_chanl(self):
"""Get dimension b, the number of valid track positions within a specific channel"""
return {
Track.Type.X: self.width,
Track.Type.Y: self.height,
}[self.chan_type]
def foreach_position(self):
"""Generate all valid placement positions (exclude border)"""
xmin, ymin = {
Track.Type.X: (1, 0),
Track.Type.Y: (0, 1),
}[self.chan_type]
for row in range(ymin, self.height):
for col in range(xmin, self.width):
yield Position(col, row)
def foreach_track(self):
"""Generate all current legal channel positions (exclude border)"""
for pos in self.foreach_position():
for ti, t in enumerate(self[pos]):
yield (pos, ti, t)
def slicen(self):
"""Get grid width or height corresponding to chanx/chany type"""
return {
Track.Type.X: self.height,
Track.Type.Y: self.width,
}[self.chan_type]
def slice(self, i):
"""Get row or col corresponding to chanx/chany type"""
return {
Track.Type.X: self.row,
Track.Type.Y: self.column,
}[self.chan_type](
i
)
def track_slice(self, t):
"""Get the row or column the track runs along"""
return {
Track.Type.X: self.row,
Track.Type.Y: self.column,
}[t.type](
t.common
)
def tracks(self):
"""Get all channels in a set"""
ret = set()
for _pos, _ti, t in self.foreach_track():
ret.add(t)
return ret
def validate_pos(self, pos, msg=''):
"""
A channel must go between switchboxes (where channels can cross)
Channels are upper right of tile
Therefore, the first position in a channel cannot have a track because
there is no proceeding switchbox"""
if msg:
msg = msg + ': '
# Gross error out of grid
if pos.x < 0 or pos.y < 0 or pos.x >= self.width or pos.y >= self.height:
raise ValueError(
"%sGrid %s, point %s out of grid size coordinate" %
(msg, self.size, pos)
)
if self.chan_type == Track.Type.X and pos.x == 0:
raise ValueError("%sInvalid CHANX x=0 point %s" % (msg, pos))
if self.chan_type == Track.Type.Y and pos.y == 0:
raise ValueError("%sInvalid CHANY y=0 point %s" % (msg, pos))
def create_track(self, t, idx=None):
"""
Channel allocator
Finds an optimal place to put the channel, increasing the channel width if necessary
If idx is given, it must go there
Throw exception if location is already occupied
>>> g = ChannelGrid((11, 11), Track.Type.X)
>>> # Adding the first channel
>>> g.create_track(Track((1, 6), (4, 6), name="A"))
T(A,0)
>>> g[(1,6)]
[T(A,0)]
>>> g[(2,6)]
[T(A,0)]
>>> g[(4,6)]
[T(A,0)]
>>> g[(5,6)]
[None]
>>> # Adding second non-overlapping second channel
>>> g.create_track(Track((5, 6), (7, 6), name="B"))
T(B,0)
>>> g[(4,6)]
[T(A,0)]
>>> g[(5,6)]
[T(B,0)]
>>> g[(7,6)]
[T(B,0)]
>>> g[(8,6)]
[None]
>>> # Adding third channel which overlaps with second channel
>>> g.create_track(Track((5, 6), (7, 6), name="T"))
T(T,1)
>>> g[(4,6)]
[T(A,0), None]
>>> g[(5,6)]
[T(B,0), T(T,1)]
>>> g[(7,6)]
[T(B,0), T(T,1)]
>>> # Adding a channel which overlaps, but is a row over
>>> g.create_track(Track((5, 7), (7, 7), name="D"))
T(D,0)
>>> g[(5,6)]
[T(B,0), T(T,1)]
>>> g[(5,7)]
[T(D,0)]
>>> # Adding fourth channel which overlaps both the first
>>> # and second+third channel
>>> g.create_track(Track((3, 6), (6, 6), name="E"))
T(E,2)
>>> g[(2,6)]
[T(A,0), None, None]
>>> g[(3,6)]
[T(A,0), None, T(E,2)]
>>> g[(6,6)]
[T(B,0), T(T,1), T(E,2)]
>>> g[(7,6)]
[T(B,0), T(T,1), None]
>>> # This channel fits in the hole left by the last one.
>>> g.create_track(Track((1, 6), (3, 6), name="F"))
T(F,1)
>>> g[(1,6)]
[T(A,0), T(F,1), None]
>>> g[(2,6)]
[T(A,0), T(F,1), None]
>>> g[(3,6)]
[T(A,0), T(F,1), T(E,2)]
>>> g[(4,6)]
[T(A,0), None, T(E,2)]
>>> # Add another channel which causes a hole
>>> g.create_track(Track((1, 6), (7, 6), name="G"))
T(G,3)
>>> g[(1,6)]
[T(A,0), T(F,1), None, T(G,3)]
>>> g[(2,6)]
[T(A,0), T(F,1), None, T(G,3)]
>>> g[(3,6)]
[T(A,0), T(F,1), T(E,2), T(G,3)]
>>> g[(4,6)]
[T(A,0), None, T(E,2), T(G,3)]
>>> g[(5,6)]
[T(B,0), T(T,1), T(E,2), T(G,3)]
>>> g[(6,6)]
[T(B,0), T(T,1), T(E,2), T(G,3)]
>>> g[(7,6)]
[T(B,0), T(T,1), None, T(G,3)]
>>> g[(8,6)]
[None, None, None, None]
"""
assert t.idx is None
force_idx = idx
self.validate_pos(t.start, 'start')
self.validate_pos(t.end, 'end')
if t.type_guess != self.chan_type:
if t.length != 0:
raise TypeError(
"Can only add channels of type {} which {} ({}) is not.".
format(self.chan_type, t, t.type)
)
else:
t.type_hint = self.chan_type
c = self.track_slice(t)
assert_len_eq(c)
# Find start and end
s, e = min(t.start0, t.end0), max(t.start0, t.end0)
assert e >= s, (e, '>=', s)
assert s < len(c), (s, '<', len(c), c)
assert e < len(c), (e + 1, '<', len(c), c)
# Find a idx that this channel fits.
# Normally start at first channel (0) unless forcing to a specific channel
max_idx = force_idx if force_idx is not None else 0
while True:
# Check each position if the track can fit
# Expanding channel width as required index grows
for p in c[s:e + 1]:
while len(p) < max_idx + 1:
p.append(None)
# Can't place here?
if p[max_idx] is not None:
# Grow track width
if force_idx is not None:
raise IndexError(
"Can't fit channel at index %d" % force_idx
)
max_idx += 1
break
# Was able to place into all locations
else:
break
# Make sure everything has the same length.
for p in c:
while len(p) < max_idx + 1:
p.append(None)
assert_len_eq(c)
t = t.new_idx(max_idx)
assert t.idx == max_idx
for p in c[s:e + 1]:
p[t.idx] = t
return t
def pretty_print(self):
"""
If type == Track.Type.X
A--AC-C
B-----B
D--DE-E
F-----F
If type == Track.Type.Y
AB DF
|| ||
|| ||
A| D|
C| E|
|| ||
CB EF
"""
def get_str(t):
if not t:
s = ""
elif t.name:
s = t.name
else:
s = str(t)
return s
# Work out how many characters the largest label takes up.
s_maxlen = 1
for row in range(0, self.height):
for col in range(0, self.width):
for t in self[(col, row)]:
s_maxlen = max(s_maxlen, len(get_str(t)))
assert s_maxlen > 0, s_maxlen
s_maxlen += 3
if self.chan_type == Track.Type.Y:
beg_fmt = "{:^%i}" % s_maxlen
end_fmt = beg_fmt
mid_fmt = beg_fmt.format("||")
elif self.chan_type == Track.Type.X:
beg_fmt = "{:>%i}>" % (s_maxlen - 1)
end_fmt = "->{:<%i}" % (s_maxlen - 2)
mid_fmt = "-" * s_maxlen
else:
assert False
non_fmt = " " * s_maxlen
"""
rows[row][col][c]
row: global row location
col: column of output
c: character showing occupation along a track
Channel width may vary across tiles, but all columns within that region
should have the same length
"""
rows = []
for y in range(0, self.height):
cols = []
for x in range(0, self.width):
# Header
hdri = {Track.Type.X: x, Track.Type.Y: y}[self.chan_type]
channels = [("|{: ^%i}" % (s_maxlen - 1)).format(hdri)]
for t in self[(x, y)]:
if not t:
fmt = non_fmt
elif t.start == t.end:
s = get_str(t)
channels.append(
"{} ".format(
"".join(
[
beg_fmt.format(s),
mid_fmt.format(s),
end_fmt.format(s),
]
)[:s_maxlen - 1]
)
)
continue
elif t.start == (x, y):
fmt = beg_fmt
elif t.end == (x, y):
fmt = end_fmt
else:
fmt = mid_fmt
channels.append(fmt.format(get_str(t)))
cols.append(channels)
rows.append(cols)
# Dump the track state as a string
f = io.StringIO()
def p(*args, **kw):
print(*args, file=f, **kw)
if self.chan_type == Track.Type.X:
for r in range(0, len(rows)):
assert_len_eq(rows[r])
# tracks + 1 for header
track_rows = len(rows[r][0])
for tracki in range(0, track_rows):
for c in range(0, len(rows[r])):
p(rows[r][c][tracki], end="")
# Close header
if tracki == 0:
p("|", end="")
p()
p("\n")
elif self.chan_type == Track.Type.Y:
for r in range(0, len(rows)):
# tracks + 1 for header
for c in range(0, len(rows[r])):
track_cols = len(rows[r][c])
p("|*", end="")
for tracki in range(0, track_cols):
p(rows[r][c][tracki], end="")
# Close header
if tracki == 0:
p("|", end="")
p(" ", end="")
p("")
# p("|*|")
else:
assert False
return f.getvalue()
def clear(self):
"""Remove tracks from all currently occupied positions, making channel width 0"""
for x in range(0, self.width):
for y in range(0, self.height):
self[Position(x, y)] = []
def check(self):
"""Self integrity check"""
# Verify uniform track length
if self.chan_type == Track.Type.X:
for y in range(self.height):
assert_len_eq(self.row(y))
elif self.chan_type == Track.Type.Y:
for x in range(self.width):
assert_len_eq(self.column(x))
else:
assert False
def density(self):
"""Return (number occupied positions, total number positions)"""
occupied = 0
net = 0
for _pos, _ti, t in self.foreach_track():
net += 1
if t is not None:
occupied += 1
return occupied, net
def fill_empty(self, segment_id, name=None):
tracks = []
for pos, ti, t in self.foreach_track():
if t is None:
tracks.append(
self.create_track(
Track(
pos,
pos,
segment_id=segment_id,
name=name,
type_hint=self.chan_type,
direction=Track.Direction.BI
),
idx=ti
)
)
return tracks
def channel_widths(self):
"""Return (min channel width, max channel width, row/col widths)"""
cwmin = float('+inf')
cwmax = float('-inf')
xy_list = []
for i in range(self.slicen()):
# track width should be consistent along a slice
# just take the first element
loc = self.slice(i)[0]
cwmin = min(cwmin, len(loc))
cwmax = max(cwmax, len(loc))
xy_list.append(len(loc))
return (cwmin, cwmax, xy_list)
def assert_width(self, width):
"""Assert all channels have specified --route_chan_width"""
for pos in self.foreach_position():
tracks = self[pos]
assert len(
tracks
) == width, 'Bad width Position(x=%d, y=%d): expect %d, got %d' % (
pos.x, pos.y, width, len(tracks)
)
def assert_full(self):
"""Assert all allocated channels are fully occupied"""
self.check()
# occupied, net = self.density()
# print("Occupied %d / %d" % (occupied, net))
for pos, ti, t in self.foreach_track():
assert t is not None, 'Unoccupied Position(x=%d, y=%d) track=%d' % (
pos.x, pos.y, ti
)
class Channels:
"""Holds all channels for the whole grid (X + Y)"""
def __init__(self, size):
self.size = size
self.x = ChannelGrid(size, Track.Type.X)
self.y = ChannelGrid(size, Track.Type.Y)
def create_diag_track(self, start, end, segment_id, idx=None):
# Actually these can be tuple as well
# assert_type(start, Pos)
# assert_type(end, Pos)
# Create track(s)
try:
return (self.create_xy_track(start, end, segment_id, idx=idx), )
except ChannelNotStraight:
assert idx is None, idx
corner = (start.x, end.y)
ta = self.create_xy_track(start, corner, segment_id)[0]
tb = self.create_xy_track(corner, end, segment_id)[0]
return (ta, tb)
def create_xy_track(
self,
start,
end,
segment_id,
idx=None,
name=None,
typeh=None,
direction=None
):
"""
idx: None to automatically allocate
"""
# Actually these can be tuple as well
# assert_type(start, Pos)
# assert_type(end, Pos)
# Create track(s)
# Will throw exception if not straight
t = Track(
start,
end,
segment_id=segment_id,
name=name,
type_hint=typeh,
direction=direction
)
# Add the track to associated channel list
# Get the track now with the index assigned
t = {
Track.Type.X: self.x.create_track,
Track.Type.Y: self.y.create_track
}[t.type](
t, idx=idx
)
# print('create %s %s to %s idx %s' % (t.type, start, end, idx))
assert t.idx is not None
if typeh:
assert t.type == typeh, (t.type.value, typeh)
return t
def pad_channels(self, segment_id):
tracks = []
tracks.extend(self.x.fill_empty(segment_id))
tracks.extend(self.y.fill_empty(segment_id))
return tracks
def pretty_print(self):
s = ''
s += 'X\n'
s += self.x.pretty_print()
s += 'Y\n'
s += self.y.pretty_print()
return s
def clear(self):
"""Remove all channels"""
self.x.clear()
self.y.clear()
def from_xml_nodes(self, nodes_xml):
"""Add channels from <nodes> CHANX/CHANY"""
for node_xml in nodes_xml:
ntype = node_xml.get('type')
if ntype not in ('CHANX', 'CHANY'):
continue
ntype_e = Track.Type(ntype)
direction = Track.Direction(node_xml.get('direction'))
loc = single_element(node_xml, 'loc')
idx = int(loc.get('ptc'))
pos_low, pos_high = node_pos(node_xml)
# print('Importing %s @ %s:%s :: %d' % (ntype, pos_low, pos_high, idx))
segment_xml = single_element(node_xml, 'segment')
segment_id = int(segment_xml.get('segment_id'))
# idx will get assigned when adding to track
try:
self.create_xy_track(
pos_low,
pos_high,
segment_id,
idx=idx,
# XML has no name concept. Should it?
name=None,
typeh=ntype_e,
direction=direction
)
except Exception:
print("Bad XML: %s" % (ET.tostring(node_xml)))
raise
def to_xml_channels(self, channels_xml):
channels_xml.clear()
# channel entry
cw_xmin, cw_xmax, x_lists = self.x.channel_widths()
cw_ymin, cw_ymax, y_lists = self.y.channel_widths()
cw_max = max(cw_xmax, cw_ymax)
ET.SubElement(
channels_xml, 'channel', {
'chan_width_max': str(cw_max),
'x_min': str(cw_xmin),
'x_max': str(cw_xmax),
'y_min': str(cw_ymin),
'y_max': str(cw_ymax),
}
)
# x_list / y_list tries
for i, info in enumerate(x_lists):
ET.SubElement(
channels_xml, 'x_list', {
'index': str(i),
'info': str(info)
}
)
for i, info in enumerate(y_lists):
ET.SubElement(
channels_xml, 'y_list', {
'index': str(i),
'info': str(info)
}
)
def to_xml(self, xml_graph):
self.to_xml_channels(single_element(xml_graph, 'channels'))
def TX(start, end, idx=None, name=None, direction=None, segment_id=None):
if direction is None:
direction = Track.Direction.INC
if segment_id is None:
segment_id = 0
return T(
start,
end,
direction=direction,
segment_id=segment_id,
idx=idx,
name=name,
type_hint=Track.Type.X,
)
def TY(start, end, idx=None, name=None, direction=None, segment_id=None):
if direction is None:
direction = Track.Direction.INC
if segment_id is None:
segment_id = 0
return T(
start,
end,
direction=direction,
segment_id=segment_id,
idx=idx,
name=name,
type_hint=Track.Type.Y,
)
def docprint(x):
pprint.pprint(x.splitlines())
def create_test_channel_grid():
g = ChannelGrid((6, 3), Track.Type.X)
g.create_track(TX((1, 0), (5, 0), name="AA"))
g.create_track(TX((1, 0), (3, 0), name="BB"))
g.create_track(TX((2, 0), (5, 0), name="CC"))
g.create_track(TX((1, 0), (1, 0), name="DD"))
g.create_track(TX((1, 1), (3, 1), name="aa"))
g.create_track(TX((4, 1), (5, 1), name="bb"))
g.create_track(TX((1, 1), (5, 1), name="cc"))
g.check()
return g
def test_x_auto():
"""
>>> docprint(test_x_auto())
['| 0 | 1 | 2 | 3 | 4 | 5 |',
' AA>---------------->AA ',
' BB>------>BB ',
' DD CC>----------->CC ',
'',
'',
'| 0 | 1 | 2 | 3 | 4 | 5 |',
' aa>------>aa bb>->bb ',
' cc>---------------->cc ',
'',
'',
'| 0 | 1 | 2 | 3 | 4 | 5 |',
'',
'']
"""
g = create_test_channel_grid()
return g.pretty_print()
def test_pad():
"""
>>> docprint(test_pad())
['| 0 | 1 | 2 | 3 | 4 | 5 |',
' AA>---------------->AA ',
' BB>------>BB XX XX ',
' DD CC>----------->CC ',
'',
'',
'| 0 | 1 | 2 | 3 | 4 | 5 |',
' aa>------>aa bb>->bb ',
' cc>---------------->cc ',
'',
'',
'| 0 | 1 | 2 | 3 | 4 | 5 |',
'',
'']
"""
g = create_test_channel_grid()
g.fill_empty(0, name='XX')
g.check()
return g.pretty_print()
def test_x_manual():
"""
>>> pprint.pprint(test_x_manual().splitlines())
['| 0 | 1 | 2 | 3 | 4 | 5 |',
' AA>---------------->AA ',
' BB>------>BB ',
' DD CC>----------->CC ',
'',
'',
'| 0 | 1 | 2 | 3 | 4 | 5 |',
' aa>------>aa bb>->bb ',
' cc>---------------->cc ',
'',
'',
'| 0 | 1 | 2 | 3 | 4 | 5 |',
'',
'']
"""
g = ChannelGrid((6, 3), Track.Type.X)
g.create_track(TX((1, 0), (5, 0), name="AA"), idx=0)
g.create_track(TX((1, 0), (3, 0), name="BB"), idx=1)
g.create_track(TX((2, 0), (5, 0), name="CC"), idx=2)
g.create_track(TX((1, 0), (1, 0), name="DD"), idx=2)
g.create_track(TX((1, 1), (3, 1), name="aa"), idx=0)
g.create_track(TX((4, 1), (5, 1), name="bb"), idx=0)
g.create_track(TX((1, 1), (5, 1), name="cc"), idx=1)
try:
g.create_track(TX((1, 1), (5, 1), name="dd"), idx=1)
assert False, "Should have failed to place"
except IndexError:
pass
g.check()
return g.pretty_print()
def test_y_auto():
"""
>>> docprint(test_y_auto())
['|*| 0 | |*| 0 | |*| 0 | ',
'|*| 1 | AA BB DD |*| 1 | aa cc |*| 1 | ',
'|*| 2 | || || CC |*| 2 | || || |*| 2 | ',
'|*| 3 | || BB || |*| 3 | aa || |*| 3 | ',
'|*| 4 | || || |*| 4 | bb || |*| 4 | ',
'|*| 5 | AA CC |*| 5 | bb cc |*| 5 | ']
"""
g = ChannelGrid((3, 6), Track.Type.Y)
g.create_track(TY((0, 1), (0, 5), name="AA"))
g.create_track(TY((0, 1), (0, 3), name="BB"))
g.create_track(TY((0, 2), (0, 5), name="CC"))
g.create_track(TY((0, 1), (0, 1), name="DD"))
g.create_track(TY((1, 1), (1, 3), name="aa"))
g.create_track(TY((1, 4), (1, 5), name="bb"))
g.create_track(TY((1, 1), (1, 5), name="cc"))
g.check()
return g.pretty_print()
if __name__ == "__main__":
import doctest
print('doctest: begin')
failure_count, test_count = doctest.testmod()
assert test_count > 0
assert failure_count == 0, "Doctests failed!"
print('doctest: end')