|
@@ -1,460 +0,0 @@
|
|
|
-#!/usr/bin/env python3
|
|
|
-
|
|
|
-from collections import namedtuple
|
|
|
-import re
|
|
|
-
|
|
|
-
|
|
|
-# SerDes group numbers:
|
|
|
-# [15:12] Group
|
|
|
-# [11: 8] SubGroup
|
|
|
-# [ 7: 4] Type
|
|
|
-# [ 3] n/a
|
|
|
-# [ 2: 0] LC number
|
|
|
-#
|
|
|
-# SubGroups:
|
|
|
-#
|
|
|
-# 0 Data Out path 0
|
|
|
-# 1 Data Out path 1
|
|
|
-# 2 Data Out Enable
|
|
|
-#
|
|
|
-# 4 Data In path 0
|
|
|
-# 5 Data In path 1
|
|
|
-# 6 Data In common
|
|
|
-#
|
|
|
-#
|
|
|
-# Types:
|
|
|
-#
|
|
|
-# 0 OSERDES Capture
|
|
|
-# 1 OSERDES Shift
|
|
|
-# 2 OSERDES NegEdge Delay
|
|
|
-#
|
|
|
-# 8 ISERDES Slow Capture
|
|
|
-# 9 ISERDES Fast Capture
|
|
|
-# a ISERDES Shift
|
|
|
-# b ISERDES PreMux
|
|
|
-#
|
|
|
-#
|
|
|
-# Placement priority
|
|
|
-#
|
|
|
-# type near
|
|
|
-# 2 io Output Neg Edge delay
|
|
|
-# b io Input Pre mux
|
|
|
-# a io Input Shift
|
|
|
-# 9 'a' Input Fast Capture
|
|
|
-# 1 io Output Shift
|
|
|
-# 0 '1' Output Capture
|
|
|
-# 8 '9's Input Slow Capture
|
|
|
-#
|
|
|
-
|
|
|
-
|
|
|
-class BEL(namedtuple('BEL', 'x y z')):
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def from_json_attr(kls, v):
|
|
|
- def to_int(s):
|
|
|
- return int(re.sub(r'[^\d-]+', '', s))
|
|
|
- return kls(*[to_int(x) for x in v.split('/', 3)])
|
|
|
-
|
|
|
-
|
|
|
-class ControlGroup(namedtuple('ControlGroup', 'clk rst ena neg')):
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def from_lc(kls, lc):
|
|
|
- netname = lambda lc, p: lc.ports[p].net.name if (lc.ports[p].net is not None) else None
|
|
|
- return kls(
|
|
|
- netname(lc, 'CLK'),
|
|
|
- netname(lc, 'SR'),
|
|
|
- netname(lc, 'CEN'),
|
|
|
- lc.params['NEG_CLK'] == '1'
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-class FullCellId(namedtuple('SDGId', 'gid sid typ lc')):
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def from_json_attr(kls, v):
|
|
|
- return kls(
|
|
|
- v >> 12,
|
|
|
- (v >> 8) & 0xf,
|
|
|
- (v >> 4) & 0xf,
|
|
|
- (v >> 0) & 0x7
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-class SerDesGroup:
|
|
|
-
|
|
|
- def __init__(self, gid):
|
|
|
- self.gid = gid
|
|
|
- self.blocks = {}
|
|
|
- self.io = None
|
|
|
-
|
|
|
- def add_lc(self, lc, fcid=None):
|
|
|
- # Get Full Cell ID if not provided
|
|
|
- if fcid is None:
|
|
|
- grp = int(lc.attrs['SERDES_GRP'], 2)
|
|
|
- fcid = FullCellId.from_json_attr(grp)
|
|
|
-
|
|
|
- # Add to the cell list
|
|
|
- if (fcid.sid, fcid.typ) not in self.blocks:
|
|
|
- self.blocks[(fcid.sid, fcid.typ)] = SerDesBlock(self, fcid.sid, fcid.typ)
|
|
|
-
|
|
|
- self.blocks[(fcid.sid, fcid.typ)].add_lc(lc, fcid=fcid)
|
|
|
-
|
|
|
- def analyze(self):
|
|
|
- # Process all blocks
|
|
|
- for blk in self.blocks.values():
|
|
|
- # Analyze
|
|
|
- blk.analyze()
|
|
|
-
|
|
|
- # Check IO
|
|
|
- if blk.io is not None:
|
|
|
- if (self.io is not None) and (self.io != blk.io):
|
|
|
- raise RuntimeError(f'Incompatible IO sites found in SerDes group {self.gid}: {self.io} vs {blk.io}')
|
|
|
- self.io = blk.io
|
|
|
-
|
|
|
-
|
|
|
-class SerDesBlock:
|
|
|
-
|
|
|
- NAMES = {
|
|
|
- 0x0: 'OSERDES Capture',
|
|
|
- 0x1: 'OSERDES Shift',
|
|
|
- 0x2: 'OSERDES NegEdge Delay',
|
|
|
- 0x8: 'ISERDES Slow Capture',
|
|
|
- 0x9: 'ISERDES Fast Capture',
|
|
|
- 0xa: 'ISERDES Shift',
|
|
|
- 0xb: 'ISERDES PreMux',
|
|
|
- }
|
|
|
-
|
|
|
- def __init__(self, group, sid, typ):
|
|
|
- # Identity
|
|
|
- self.group = group
|
|
|
- self.sid = sid
|
|
|
- self.typ = typ
|
|
|
-
|
|
|
- # Container
|
|
|
- self.lcs = 8 * [None]
|
|
|
- self.io = None
|
|
|
- self.cg = None
|
|
|
-
|
|
|
- def __str__(self):
|
|
|
- return f'SerDesBlock({self.sid:x}/{self.typ:x} {self.NAMES[self.typ]})'
|
|
|
-
|
|
|
- def _find_io_site_for_lc(self, lc):
|
|
|
- # Check in/out ports
|
|
|
- for pn in [ 'I0', 'I1', 'I2', 'I3', 'O' ]:
|
|
|
- n = lc.ports[pn].net
|
|
|
- if (n is None) or n.name.startswith('$PACKER_'):
|
|
|
- continue
|
|
|
- pl = [ n.driver ] + list(n.users)
|
|
|
- for p in pl:
|
|
|
- if (p.cell.type == 'SB_IO') and ('BEL' in p.cell.attrs):
|
|
|
- return BEL.from_json_attr(p.cell.attrs['BEL'])
|
|
|
- return None
|
|
|
-
|
|
|
- def add_lc(self, lc, fcid=None):
|
|
|
- # Get Full Cell ID if not provided
|
|
|
- if fcid is None:
|
|
|
- grp = int(lc.attrs['SERDES_GRP'], 2)
|
|
|
- fcid = FullCellId.from_json_attr(grp)
|
|
|
-
|
|
|
- # Add to LCs
|
|
|
- if self.lcs[fcid.lc] is not None:
|
|
|
- raise RuntimeError(f'Duplicate LC for FullCellId {fcid}')
|
|
|
-
|
|
|
- self.lcs[fcid.lc] = lc
|
|
|
-
|
|
|
- def find_io_site(self):
|
|
|
- for lc in self.lcs:
|
|
|
- if lc is None:
|
|
|
- continue
|
|
|
- s = self._find_io_site_for_lc(lc)
|
|
|
- if s is not None:
|
|
|
- return s
|
|
|
- return None
|
|
|
-
|
|
|
- def analyze(self):
|
|
|
- # Check and truncate LC array
|
|
|
- l = len(self)
|
|
|
- if not all([x is not None for x in self.lcs[0:l]]):
|
|
|
- raise RuntimeError(f'Invalid group in block {self.group.gid}/{self.sid}/{self.typ}')
|
|
|
-
|
|
|
- self.lcs = self.lcs[0:l]
|
|
|
-
|
|
|
- # Identify IO site connection if there is one
|
|
|
- self.io = self.find_io_site()
|
|
|
-
|
|
|
- # Identify the control group
|
|
|
- self.cg = ControlGroup.from_lc(self.lcs[0])
|
|
|
-
|
|
|
- def assign_bel(self, base_bel, zofs=0):
|
|
|
- for i, lc in enumerate(self.lcs):
|
|
|
- lc.setAttr('BEL', 'X%d/Y%d/lc%d' % (base_bel.x, base_bel.y, base_bel.z + zofs + i))
|
|
|
-
|
|
|
- def __len__(self):
|
|
|
- return sum([x is not None for x in self.lcs])
|
|
|
-
|
|
|
-
|
|
|
-class PlacerSite:
|
|
|
-
|
|
|
- def __init__(self, pos):
|
|
|
- self.pos = pos
|
|
|
- self.free = 8
|
|
|
- self.blocks = []
|
|
|
- self.cg = None
|
|
|
-
|
|
|
- def valid_for_block(self, blk):
|
|
|
- return (self.free >= len(blk)) and (
|
|
|
- (self.cg is None) or
|
|
|
- (blk.cg is None) or
|
|
|
- (self.cg == blk.cg)
|
|
|
- )
|
|
|
-
|
|
|
- def add_block(self, blk):
|
|
|
- # Assign the block into position
|
|
|
- pos = BEL(self.pos.x, self.pos.y, 8-self.free)
|
|
|
- blk.assign_bel(pos)
|
|
|
-
|
|
|
- # Add to blocks here
|
|
|
- self.blocks.append(blk)
|
|
|
-
|
|
|
- # Update constrainsts
|
|
|
- self.cg = blk.cg
|
|
|
- self.free -= len(blk)
|
|
|
-
|
|
|
- return pos
|
|
|
-
|
|
|
-
|
|
|
-class Placer:
|
|
|
-
|
|
|
- PRIORITY = [
|
|
|
- # Type Place Target
|
|
|
- (0x2, lambda p, b: b.group.io),
|
|
|
- (0xb, lambda p, b: b.group.io),
|
|
|
- (0xa, lambda p, b: b.group.io),
|
|
|
- (0x9, lambda p, b: p.pos_of( b.group.blocks[(4|(b.sid & 1), 0xa)] ) ),
|
|
|
- (0x1, lambda p, b: b.group.io),
|
|
|
- (0x0, lambda p, b: p.pos_of( b.group.blocks[(b.sid, 0x1)] ) ),
|
|
|
- (0x8, lambda p, b: p.pos_of( b.group.blocks[(4, 0xa)], b.group.blocks.get((5, 0xa)) ) ),
|
|
|
- ]
|
|
|
-
|
|
|
- PLACE_PREF = [
|
|
|
- # Xofs Yofs
|
|
|
- ( 0, 1),
|
|
|
- (-1, 1),
|
|
|
- ( 1, 1),
|
|
|
- (-1, 0),
|
|
|
- ( 1, 0),
|
|
|
- ( 0, -1),
|
|
|
- (-1, -1),
|
|
|
- ( 1, -1),
|
|
|
- ( 0, 1),
|
|
|
- ( 0, 2),
|
|
|
- ( 0, 3),
|
|
|
- ( 0, 4),
|
|
|
- (-1, 1),
|
|
|
- ( 1, 1),
|
|
|
- (-1, 2),
|
|
|
- ( 1, 2),
|
|
|
- (-1, 3),
|
|
|
- ( 1, 3),
|
|
|
- (-1, 4),
|
|
|
- ( 1, 4),
|
|
|
- ]
|
|
|
-
|
|
|
- def __init__(self, groups, top=False):
|
|
|
- # Save groups to place
|
|
|
- self.groups = groups
|
|
|
-
|
|
|
- # Generate site grid
|
|
|
- self.top = top
|
|
|
- self.m_fwd = {}
|
|
|
- self.m_back = {}
|
|
|
-
|
|
|
- for y in (range(26,31) if self.top else range(1,6)):
|
|
|
- for x in range(1,25):
|
|
|
- # Invalid, used by SPRAM
|
|
|
- if x in [6,19]:
|
|
|
- continue
|
|
|
- self.m_fwd[BEL(x,y,0)] = PlacerSite(BEL(x,y, 0))
|
|
|
-
|
|
|
- def _blocks_by_type(self, typ):
|
|
|
- r = []
|
|
|
- for grp in self.groups:
|
|
|
- for blk in grp.blocks.values():
|
|
|
- if blk.typ == typ:
|
|
|
- r.append(blk)
|
|
|
- return sorted(r, key=lambda b: (b.group.gid, b.sid))
|
|
|
-
|
|
|
- def place(self):
|
|
|
- # Scan by priority order
|
|
|
- for typ, fn in self.PRIORITY:
|
|
|
- # Collect all blocks per type and sorted by gid,sid
|
|
|
- blocks = self._blocks_by_type(typ)
|
|
|
-
|
|
|
- # Place each block
|
|
|
- for blk in blocks:
|
|
|
- # Get target location
|
|
|
- tgt = fn(self, blk)
|
|
|
-
|
|
|
- if type(tgt) == list:
|
|
|
- x = int(round(sum([b.x for b in tgt]) / len(tgt)))
|
|
|
- y = int(round(sum([b.y for b in tgt]) / len(tgt)))
|
|
|
- tgt = BEL(x, y, 0)
|
|
|
-
|
|
|
- # Scan placement preference and try to place
|
|
|
- for xofs, yofs in self.PLACE_PREF:
|
|
|
- # Flip for top
|
|
|
- if self.top:
|
|
|
- yofs = -yofs
|
|
|
-
|
|
|
- p = BEL(tgt.x + xofs, tgt.y + yofs, 0)
|
|
|
-
|
|
|
- if (p in self.m_fwd) and (self.m_fwd[p].valid_for_block(blk)):
|
|
|
- self.place_block(blk, p)
|
|
|
- break
|
|
|
-
|
|
|
- else:
|
|
|
- raise RuntimeError(f'Unable to place {blk}')
|
|
|
-
|
|
|
- # Debug
|
|
|
- if debug:
|
|
|
- for g in self.groups:
|
|
|
- print(f"Group {g.gid} for IO {g.io}")
|
|
|
- for b in g.blocks.values():
|
|
|
- print(f"\t{str(b):40s}: {len(b)} LCs placed @ {self.pos_of(b)}")
|
|
|
- print()
|
|
|
-
|
|
|
- def place_block(self, blk, pos):
|
|
|
- self.m_back[blk] = self.m_fwd[pos].add_block(blk)
|
|
|
-
|
|
|
- def pos_of(self, *blocks):
|
|
|
- if len(blocks) > 1:
|
|
|
- return [ self.m_back.get(b) for b in blocks if b is not None ]
|
|
|
- else:
|
|
|
- return self.m_back.get(blocks[0])
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-# ----------------------------------------------------------------------------
|
|
|
-# Main
|
|
|
-# ----------------------------------------------------------------------------
|
|
|
-
|
|
|
-debug = True
|
|
|
-
|
|
|
-
|
|
|
-# Collect
|
|
|
-# -------
|
|
|
-
|
|
|
-groups = {}
|
|
|
-sync_lbuf_net_map = {}
|
|
|
-sync_lbuf_top = {}
|
|
|
-sync_lbuf_bot = {}
|
|
|
-
|
|
|
-for n,c in ctx.cells:
|
|
|
- # Filter out dummy 'BEL' attributes
|
|
|
- if 'BEL' in c.attrs:
|
|
|
- if not c.attrs['BEL'].strip():
|
|
|
- c.unsetAttr('BEL')
|
|
|
-
|
|
|
- # Special processing
|
|
|
- if 'SERDES_ATTR' in c.attrs:
|
|
|
- attr = c.attrs['SERDES_ATTR']
|
|
|
- c.unsetAttr('SERDES_ATTR')
|
|
|
-
|
|
|
- # Local sync buffer
|
|
|
- if attr.startswith('sync_lbuf'):
|
|
|
- # Position
|
|
|
- if attr.endswith('top'):
|
|
|
- d = sync_lbuf_top
|
|
|
- elif attr.endswith('bot'):
|
|
|
- d = sync_lbuf_bot
|
|
|
-
|
|
|
- # Sync source
|
|
|
- src_net = c.ports['I0'].net
|
|
|
- src_plb = BEL.from_json_attr(src_net.driver.cell.attrs['BEL'])[0:2]
|
|
|
- dst_net = c.ports['O'].net
|
|
|
-
|
|
|
- # Collect
|
|
|
- sync_lbuf_net_map.setdefault(src_plb, []).append(src_net.name)
|
|
|
- d[src_plb] = dst_net.name
|
|
|
-
|
|
|
- # Does the cell need grouping ?
|
|
|
- if 'SERDES_GRP' in c.attrs:
|
|
|
- # Get group
|
|
|
- grp = int(c.attrs['SERDES_GRP'], 2)
|
|
|
- c.unsetAttr('SERDES_GRP')
|
|
|
-
|
|
|
- # Skip invalid/dummy
|
|
|
- if grp == 0xffffffff:
|
|
|
- continue
|
|
|
-
|
|
|
- # Add LC to our list
|
|
|
- fcid = FullCellId.from_json_attr(grp)
|
|
|
-
|
|
|
- if fcid.gid not in groups:
|
|
|
- groups[fcid.gid] = SerDesGroup(fcid.gid)
|
|
|
-
|
|
|
- groups[fcid.gid].add_lc(c, fcid=fcid)
|
|
|
-
|
|
|
-
|
|
|
-# Analyze and split into top/bottom
|
|
|
-# ---------------------------------
|
|
|
-
|
|
|
-groups_top = []
|
|
|
-groups_bot = []
|
|
|
-
|
|
|
-for g in groups.values():
|
|
|
- # Analyze
|
|
|
- g.analyze()
|
|
|
-
|
|
|
- # Sort
|
|
|
- if g.io.y == 0:
|
|
|
- groups_bot.append(g)
|
|
|
- else:
|
|
|
- groups_top.append(g)
|
|
|
-
|
|
|
-
|
|
|
-# Process local buffers
|
|
|
-# ---------------------
|
|
|
-
|
|
|
-def lbuf_build_map(src_net_map, dst_net_map):
|
|
|
- rv = {}
|
|
|
- for k, v in dst_net_map.items():
|
|
|
- for n in src_net_map[k]:
|
|
|
- rv[n] = v
|
|
|
- return rv
|
|
|
-
|
|
|
-def lbuf_reconnect(net_map, cells):
|
|
|
- # Scan all cells
|
|
|
- for lc in cells:
|
|
|
- # Scan all ports
|
|
|
- for pn in ['I0', 'I1', 'I2', 'I3', 'CEN']:
|
|
|
- n = lc.ports[pn].net
|
|
|
- if (n is not None) and (n.name in net_map):
|
|
|
- # Reconnect
|
|
|
- ctx.disconnectPort(lc.name, pn)
|
|
|
- ctx.connectPort(net_map[n.name], lc.name, pn)
|
|
|
-
|
|
|
-
|
|
|
-sync_lbuf_top = lbuf_build_map(sync_lbuf_net_map, sync_lbuf_top)
|
|
|
-sync_lbuf_bot = lbuf_build_map(sync_lbuf_net_map, sync_lbuf_bot)
|
|
|
-
|
|
|
-for g in groups_top:
|
|
|
- for blk in g.blocks.values():
|
|
|
- lbuf_reconnect(sync_lbuf_top, blk.lcs)
|
|
|
-
|
|
|
-for g in groups_bot:
|
|
|
- for blk in g.blocks.values():
|
|
|
- lbuf_reconnect(sync_lbuf_bot, blk.lcs)
|
|
|
-
|
|
|
-
|
|
|
-# Execute placer
|
|
|
-# --------------
|
|
|
-
|
|
|
-if groups_top:
|
|
|
- p_top = Placer(groups_top, top=True)
|
|
|
- p_top.place()
|
|
|
-
|
|
|
-if groups_bot:
|
|
|
- p_bot = Placer(groups_bot, top=False)
|
|
|
- p_bot.place()
|