serdes-nextpnr-place.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. #!/usr/bin/env python3
  2. from collections import namedtuple
  3. import re
  4. # SerDes group numbers:
  5. # [15:12] Group
  6. # [11: 8] SubGroup
  7. # [ 7: 4] Type
  8. # [ 3] n/a
  9. # [ 2: 0] LC number
  10. #
  11. # SubGroups:
  12. #
  13. # 0 Data Out path 0
  14. # 1 Data Out path 1
  15. # 2 Data Out Enable
  16. #
  17. # 4 Data In path 0
  18. # 5 Data In path 1
  19. # 6 Data In common
  20. #
  21. #
  22. # Types:
  23. #
  24. # 0 OSERDES Capture
  25. # 1 OSERDES Shift
  26. # 2 OSERDES NegEdge Delay
  27. #
  28. # 8 ISERDES Slow Capture
  29. # 9 ISERDES Fast Capture
  30. # a ISERDES Shift
  31. # b ISERDES PreMux
  32. #
  33. #
  34. # Placement priority
  35. #
  36. # type near
  37. # 2 io Output Neg Edge delay
  38. # b io Input Pre mux
  39. # a io Input Shift
  40. # 9 'a' Input Fast Capture
  41. # 1 io Output Shift
  42. # 0 '1' Output Capture
  43. # 8 '9's Input Slow Capture
  44. #
  45. class BEL(namedtuple('BEL', 'x y z')):
  46. @classmethod
  47. def from_json_attr(kls, v):
  48. def to_int(s):
  49. return int(re.sub(r'[^\d-]+', '', s))
  50. return kls(*[to_int(x) for x in v.split('/', 3)])
  51. class ControlGroup(namedtuple('ControlGroup', 'clk rst ena neg')):
  52. @classmethod
  53. def from_lc(kls, lc):
  54. netname = lambda lc, p: lc.ports[p].net.name if (lc.ports[p].net is not None) else None
  55. return kls(
  56. netname(lc, 'CLK'),
  57. netname(lc, 'SR'),
  58. netname(lc, 'CEN'),
  59. lc.params['NEG_CLK'] == '1'
  60. )
  61. class FullCellId(namedtuple('SDGId', 'gid sid typ lc')):
  62. @classmethod
  63. def from_json_attr(kls, v):
  64. return kls(
  65. v >> 12,
  66. (v >> 8) & 0xf,
  67. (v >> 4) & 0xf,
  68. (v >> 0) & 0x7
  69. )
  70. class SerDesGroup:
  71. def __init__(self, gid):
  72. self.gid = gid
  73. self.blocks = {}
  74. self.io = None
  75. def add_lc(self, lc, fcid=None):
  76. # Get Full Cell ID if not provided
  77. if fcid is None:
  78. grp = int(lc.attrs['SERDES_GRP'], 2)
  79. fcid = FullCellId.from_json_attr(grp)
  80. # Add to the cell list
  81. if (fcid.sid, fcid.typ) not in self.blocks:
  82. self.blocks[(fcid.sid, fcid.typ)] = SerDesBlock(self, fcid.sid, fcid.typ)
  83. self.blocks[(fcid.sid, fcid.typ)].add_lc(lc, fcid=fcid)
  84. def analyze(self):
  85. # Process all blocks
  86. for blk in self.blocks.values():
  87. # Analyze
  88. blk.analyze()
  89. # Check IO
  90. if blk.io is not None:
  91. if (self.io is not None) and (self.io != blk.io):
  92. raise RuntimeError(f'Incompatible IO sites found in SerDes group {self.gid}: {self.io} vs {blk.io}')
  93. self.io = blk.io
  94. class SerDesBlock:
  95. NAMES = {
  96. 0x0: 'OSERDES Capture',
  97. 0x1: 'OSERDES Shift',
  98. 0x2: 'OSERDES NegEdge Delay',
  99. 0x8: 'ISERDES Slow Capture',
  100. 0x9: 'ISERDES Fast Capture',
  101. 0xa: 'ISERDES Shift',
  102. 0xb: 'ISERDES PreMux',
  103. }
  104. def __init__(self, group, sid, typ):
  105. # Identity
  106. self.group = group
  107. self.sid = sid
  108. self.typ = typ
  109. # Container
  110. self.lcs = 8 * [None]
  111. self.io = None
  112. self.cg = None
  113. def __str__(self):
  114. return f'SerDesBlock({self.sid:x}/{self.typ:x} {self.NAMES[self.typ]})'
  115. def _find_io_site_for_lc(self, lc):
  116. # Check in/out ports
  117. for pn in [ 'I0', 'I1', 'I2', 'I3', 'O' ]:
  118. n = lc.ports[pn].net
  119. if (n is None) or n.name.startswith('$PACKER_'):
  120. continue
  121. pl = [ n.driver ] + list(n.users)
  122. for p in pl:
  123. if (p.cell.type == 'SB_IO') and ('BEL' in p.cell.attrs):
  124. return BEL.from_json_attr(p.cell.attrs['BEL'])
  125. return None
  126. def add_lc(self, lc, fcid=None):
  127. # Get Full Cell ID if not provided
  128. if fcid is None:
  129. grp = int(lc.attrs['SERDES_GRP'], 2)
  130. fcid = FullCellId.from_json_attr(grp)
  131. # Add to LCs
  132. if self.lcs[fcid.lc] is not None:
  133. raise RuntimeError(f'Duplicate LC for FullCellId {fcid}')
  134. self.lcs[fcid.lc] = lc
  135. def find_io_site(self):
  136. for lc in self.lcs:
  137. if lc is None:
  138. continue
  139. s = self._find_io_site_for_lc(lc)
  140. if s is not None:
  141. return s
  142. return None
  143. def analyze(self):
  144. # Check and truncate LC array
  145. l = len(self)
  146. if not all([x is not None for x in self.lcs[0:l]]):
  147. raise RuntimeError(f'Invalid group in block {self.group.gid}/{self.sid}/{self.typ}')
  148. self.lcs = self.lcs[0:l]
  149. # Identify IO site connection if there is one
  150. self.io = self.find_io_site()
  151. # Identify the control group
  152. self.cg = ControlGroup.from_lc(self.lcs[0])
  153. def assign_bel(self, base_bel, zofs=0):
  154. for i, lc in enumerate(self.lcs):
  155. lc.setAttr('BEL', 'X%d/Y%d/lc%d' % (base_bel.x, base_bel.y, base_bel.z + zofs + i))
  156. def __len__(self):
  157. return sum([x is not None for x in self.lcs])
  158. class PlacerSite:
  159. def __init__(self, pos):
  160. self.pos = pos
  161. self.free = 8
  162. self.blocks = []
  163. self.cg = None
  164. def valid_for_block(self, blk):
  165. return (self.free >= len(blk)) and (
  166. (self.cg is None) or
  167. (blk.cg is None) or
  168. (self.cg == blk.cg)
  169. )
  170. def add_block(self, blk):
  171. # Assign the block into position
  172. pos = BEL(self.pos.x, self.pos.y, 8-self.free)
  173. blk.assign_bel(pos)
  174. # Add to blocks here
  175. self.blocks.append(blk)
  176. # Update constrainsts
  177. self.cg = blk.cg
  178. self.free -= len(blk)
  179. return pos
  180. class Placer:
  181. PRIORITY = [
  182. # Type Place Target
  183. (0x2, lambda p, b: b.group.io),
  184. (0xb, lambda p, b: b.group.io),
  185. (0xa, lambda p, b: b.group.io),
  186. (0x9, lambda p, b: p.pos_of( b.group.blocks[(4|(b.sid & 1), 0xa)] ) ),
  187. (0x1, lambda p, b: b.group.io),
  188. (0x0, lambda p, b: p.pos_of( b.group.blocks[(b.sid, 0x1)] ) ),
  189. (0x8, lambda p, b: p.pos_of( b.group.blocks[(4, 0xa)], b.group.blocks.get((5, 0xa)) ) ),
  190. ]
  191. PLACE_PREF = [
  192. # Xofs Yofs
  193. ( 0, 1),
  194. (-1, 1),
  195. ( 1, 1),
  196. (-1, 0),
  197. ( 1, 0),
  198. ( 0, -1),
  199. (-1, -1),
  200. ( 1, -1),
  201. ( 0, 1),
  202. ( 0, 2),
  203. ( 0, 3),
  204. ( 0, 4),
  205. (-1, 1),
  206. ( 1, 1),
  207. (-1, 2),
  208. ( 1, 2),
  209. (-1, 3),
  210. ( 1, 3),
  211. (-1, 4),
  212. ( 1, 4),
  213. ]
  214. def __init__(self, groups, top=False):
  215. # Save groups to place
  216. self.groups = groups
  217. # Generate site grid
  218. self.top = top
  219. self.m_fwd = {}
  220. self.m_back = {}
  221. for y in (range(26,31) if self.top else range(1,6)):
  222. for x in range(1,25):
  223. # Invalid, used by SPRAM
  224. if x in [6,19]:
  225. continue
  226. self.m_fwd[BEL(x,y,0)] = PlacerSite(BEL(x,y, 0))
  227. def _blocks_by_type(self, typ):
  228. r = []
  229. for grp in self.groups:
  230. for blk in grp.blocks.values():
  231. if blk.typ == typ:
  232. r.append(blk)
  233. return sorted(r, key=lambda b: (b.group.gid, b.sid))
  234. def place(self):
  235. # Scan by priority order
  236. for typ, fn in self.PRIORITY:
  237. # Collect all blocks per type and sorted by gid,sid
  238. blocks = self._blocks_by_type(typ)
  239. # Place each block
  240. for blk in blocks:
  241. # Get target location
  242. tgt = fn(self, blk)
  243. if type(tgt) == list:
  244. x = int(round(sum([b.x for b in tgt]) / len(tgt)))
  245. y = int(round(sum([b.y for b in tgt]) / len(tgt)))
  246. tgt = BEL(x, y, 0)
  247. # Scan placement preference and try to place
  248. for xofs, yofs in self.PLACE_PREF:
  249. # Flip for top
  250. if self.top:
  251. yofs = -yofs
  252. p = BEL(tgt.x + xofs, tgt.y + yofs, 0)
  253. if (p in self.m_fwd) and (self.m_fwd[p].valid_for_block(blk)):
  254. self.place_block(blk, p)
  255. break
  256. else:
  257. raise RuntimeError(f'Unable to place {blk}')
  258. # Debug
  259. if debug:
  260. for g in self.groups:
  261. print(f"Group {g.gid} for IO {g.io}")
  262. for b in g.blocks.values():
  263. print(f"\t{str(b):40s}: {len(b)} LCs placed @ {self.pos_of(b)}")
  264. print()
  265. def place_block(self, blk, pos):
  266. self.m_back[blk] = self.m_fwd[pos].add_block(blk)
  267. def pos_of(self, *blocks):
  268. if len(blocks) > 1:
  269. return [ self.m_back.get(b) for b in blocks if b is not None ]
  270. else:
  271. return self.m_back.get(blocks[0])
  272. # ----------------------------------------------------------------------------
  273. # Main
  274. # ----------------------------------------------------------------------------
  275. debug = True
  276. # Collect
  277. # -------
  278. groups = {}
  279. for n,c in ctx.cells:
  280. # Filter out dummy 'BEL' attributes
  281. if 'BEL' in c.attrs:
  282. if not c.attrs['BEL'].strip():
  283. c.unsetAttr('BEL')
  284. # Does the cell need grouping ?
  285. if 'SERDES_GRP' in c.attrs:
  286. # Get group
  287. grp = int(c.attrs['SERDES_GRP'], 2)
  288. c.unsetAttr('SERDES_GRP')
  289. # Skip invalid/dummy
  290. if grp == 0xffffffff:
  291. continue
  292. # Add LC to our list
  293. fcid = FullCellId.from_json_attr(grp)
  294. if fcid.gid not in groups:
  295. groups[fcid.gid] = SerDesGroup(fcid.gid)
  296. groups[fcid.gid].add_lc(c, fcid=fcid)
  297. # Analyze and split into top/bottom
  298. # ---------------------------------
  299. groups_top = []
  300. groups_bot = []
  301. for g in groups.values():
  302. # Analyze
  303. g.analyze()
  304. # Sort
  305. if g.io.y == 0:
  306. groups_bot.append(g)
  307. else:
  308. groups_top.append(g)
  309. # Execute placer
  310. # --------------
  311. if groups_top:
  312. p_top = Placer(groups_top, top=True)
  313. p_top.place()
  314. if groups_bot:
  315. p_bot = Placer(groups_bot, top=False)
  316. p_bot.place()