123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- #!/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 = {}
- 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')
- # 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)
- # 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()
|