prjxray/fuzzers/041-clk-hrow-pips/top.py

571 lines
16 KiB
Python

""" Emits top.v's for various BUFHCE routing inputs. """
import os
import random
import re
random.seed(int(os.getenv("SEED"), 16))
from prjxray import util
from prjxray import verilog
from prjxray.db import Database
from prjxray.lut_maker import LutMaker
from io import StringIO
CMT_XY_FUN = util.create_xy_fun(prefix='')
BUFGCTRL_XY_FUN = util.create_xy_fun('BUFGCTRL_')
BUFHCE_XY_FUN = util.create_xy_fun('BUFHCE_')
def gen_sites(desired_site_type):
db = Database(util.get_db_root())
grid = db.grid()
for tile_name in sorted(grid.tiles()):
loc = grid.loc_of_tilename(tile_name)
gridinfo = grid.gridinfo_at_loc(loc)
for site, site_type in gridinfo.sites.items():
if site_type == desired_site_type:
yield loc, gridinfo.tile_type, site
def gen_bufhce_sites():
db = Database(util.get_db_root())
grid = db.grid()
for tile_name in sorted(grid.tiles()):
loc = grid.loc_of_tilename(tile_name)
gridinfo = grid.gridinfo_at_loc(loc)
sites = []
for site, site_type in gridinfo.sites.items():
if site_type == 'BUFHCE':
sites.append(site)
if sites:
yield tile_name, sorted(sites)
def read_site_to_cmt():
""" Yields clock sources and which CMT they route within. """
with open(os.path.join(os.getenv('FUZDIR'), 'build',
'cmt_regions.csv')) as f:
for l in f:
site, cmt = l.strip().split(',')
yield (site, cmt)
class ClockSources(object):
""" Class for tracking clock sources.
Some clock sources can be routed to any CMT, for these, cmt='ANY'.
For clock sources that belong to a CMT, cmt should be set to the CMT of
the source.
"""
def __init__(self):
self.sources = {}
self.merged_sources = {}
self.source_to_cmt = {}
self.used_sources_from_cmt = {}
self.sources_by_loc = {}
self.active_cmt_ports = {}
def add_clock_source(self, source, cmt, loc=None):
""" Adds a source from a specific CMT.
cmt='ANY' indicates that this source can be routed to any CMT.
"""
if cmt not in self.sources:
self.sources[cmt] = []
self.sources[cmt].append(source)
assert source not in self.source_to_cmt or self.source_to_cmt[
source] == cmt, source
self.source_to_cmt[source] = cmt
self.add_bufg_clock_source(source, cmt, loc)
def add_bufg_clock_source(self, source, cmt, loc):
if loc not in self.sources_by_loc:
self.sources_by_loc[loc] = []
self.sources_by_loc[loc].append((cmt, source))
def get_random_source(self, cmt):
""" Get a random source that is routable to the specific CMT.
get_random_source will return a source that is either cmt='ANY',
cmt equal to the input CMT, or the adjecent CMT.
"""
if cmt not in self.merged_sources:
choices = []
if 'ANY' in self.sources:
choices.extend(self.sources['ANY'])
if cmt in self.sources:
choices.extend(self.sources[cmt])
x, y = CMT_XY_FUN(cmt)
if x % 2 == 0:
x += 1
else:
x -= 1
paired_cmt = 'X{}Y{}'.format(x, y)
if paired_cmt in self.sources:
choices.extend(self.sources[paired_cmt])
self.merged_sources[cmt] = choices
if self.merged_sources[cmt]:
source = random.choice(self.merged_sources[cmt])
source_cmt = self.source_to_cmt[source]
if source_cmt not in self.used_sources_from_cmt:
self.used_sources_from_cmt[source_cmt] = set()
self.used_sources_from_cmt[source_cmt].add(source)
if source_cmt != 'ANY' and len(
self.used_sources_from_cmt[source_cmt]) > 14:
self.used_sources_from_cmt[source_cmt].remove(source)
return None
else:
return source
def get_bufg_source(self, loc, tile_type, site, todos, i_wire, used_only):
bufg_sources = []
top = '_TOP_' in tile_type
bottom = '_BOT_' in tile_type
assert top ^ bottom, tile_type
if top:
for src_loc, cmt_sources in self.sources_by_loc.items():
if src_loc is None:
continue
if src_loc.grid_y <= loc.grid_y:
bufg_sources.extend(cmt_sources)
elif bottom:
for src_loc, cmt_sources in self.sources_by_loc.items():
if src_loc is None:
continue
if src_loc.grid_y > loc.grid_y:
bufg_sources.extend(cmt_sources)
# CLK_HROW_TOP_R_CK_BUFG_CASCO0 -> CLK_BUFG_BUFGCTRL0_I0
# CLK_HROW_TOP_R_CK_BUFG_CASCO22 -> CLK_BUFG_BUFGCTRL11_I0
# CLK_HROW_TOP_R_CK_BUFG_CASCO23 -> CLK_BUFG_BUFGCTRL11_I1
# CLK_HROW_BOT_R_CK_BUFG_CASCO27 -> CLK_BUFG_BUFGCTRL13_I1
x, y = BUFGCTRL_XY_FUN(site)
assert x == 0
y = y % 16
assert i_wire in [0, 1], i_wire
casco_wire = '{tile_type}_CK_BUFG_CASCO{casco_idx}'.format(
tile_type=tile_type.replace('BUFG', 'HROW'),
casco_idx=(y * 2 + i_wire))
if casco_wire not in todos:
return None
target_wires = []
need_bufr = False
for src_wire in todos[casco_wire]:
if 'BUFRCLK' in src_wire:
need_bufr = True
break
for cmt, wire in bufg_sources:
if 'BUFR' in wire:
if need_bufr:
target_wires.append((cmt, wire))
else:
target_wires.append((cmt, wire))
random.shuffle(target_wires)
for cmt, source in target_wires:
if cmt == 'ANY':
return source
else:
# Make sure to not try to import move than 14 sources from
# the CMT, there is limited routing.
if cmt not in self.used_sources_from_cmt:
self.used_sources_from_cmt[cmt] = set()
if source in self.used_sources_from_cmt[cmt]:
return source
elif used_only:
continue
if len(self.used_sources_from_cmt[cmt]) < 14:
self.used_sources_from_cmt[cmt].add(source)
return source
else:
continue
return None
def check_allowed(mmcm_pll_dir, cmt):
""" Check whether the CMT specified is in the allowed direction.
This function is designed to bias sources to either the left or right
input lines.
"""
if mmcm_pll_dir == 'BOTH':
return True
elif mmcm_pll_dir == 'ODD':
x, y = CMT_XY_FUN(cmt)
return (x & 1) == 1
elif mmcm_pll_dir == 'EVEN':
x, y = CMT_XY_FUN(cmt)
return (x & 1) == 0
elif mmcm_pll_dir == 'NONE':
return False
else:
assert False, mmcm_pll_dir
def read_todo():
dsts = {}
with open(os.path.join('..', 'todo_all.txt')) as f:
for l in f:
tile_type, dst, src = l.strip().split('.')
if dst not in dsts:
dsts[dst] = set()
dsts[dst].add(src)
return dsts
def need_int_connections(todos):
for srcs in todos.values():
for src in srcs:
if re.search('INT_._.', src):
return True
return False
def bufhce_in_todo(todos, site):
if 'BUFHCE' in site:
# CLK_HROW_CK_MUX_OUT_R9 -> X1Y9
# CLK_HROW_CK_MUX_OUT_L11 -> X0Y35
x, y = BUFHCE_XY_FUN(site)
y = y % 12
if x == 0:
lr = 'L'
elif x == 1:
lr = 'R'
else:
assert False, x
return 'CLK_HROW_CK_MUX_OUT_{lr}{y}'.format(lr=lr, y=y) in todos
else:
return True
def need_gclk_connection(todos, site):
x, y = BUFGCTRL_XY_FUN(site)
assert x == 0
src_wire = 'CLK_HROW_R_CK_GCLK{}'.format(y)
for srcs in todos.values():
if src_wire in srcs:
return True
return False
def only_gclk_left(todos):
for srcs in todos.values():
for src in srcs:
if 'GCLK' not in src:
return False
return True
def main():
"""
BUFHCE's can be driven from:
MMCME2_ADV
PLLE2_ADV
BUFGCTRL
Local INT connect
"""
print('''
module top();
''')
site_to_cmt = dict(read_site_to_cmt())
clock_sources = ClockSources()
# To ensure that all left or right sources are used, sometimes only MMCM/PLL
# sources are allowed. The force of ODD/EVEN/BOTH further biases the
# clock sources to the left or right column inputs.
mmcm_pll_only = random.randint(0, 1)
mmcm_pll_dir = random.choice(('ODD', 'EVEN', 'BOTH', 'NONE'))
todos = read_todo()
if only_gclk_left(todos):
mmcm_pll_dir = 'NONE'
if not mmcm_pll_only:
if need_int_connections(todos):
for _ in range(10):
clock_sources.add_clock_source('one', 'ANY')
clock_sources.add_clock_source('zero', 'ANY')
print("""
wire zero = 0;
wire one = 1;""")
for loc, _, site in gen_sites('MMCME2_ADV'):
mmcm_clocks = [
'mmcm_clock_{site}_{idx}'.format(site=site, idx=idx)
for idx in range(13)
]
if check_allowed(mmcm_pll_dir, site_to_cmt[site]):
for clk in mmcm_clocks:
clock_sources.add_clock_source(clk, site_to_cmt[site], loc)
print(
"""
wire {c0}, {c1}, {c2}, {c3}, {c4}, {c5};
(* KEEP, DONT_TOUCH, LOC = "{site}" *)
MMCME2_ADV pll_{site} (
.CLKOUT0({c0}),
.CLKOUT0B({c1}),
.CLKOUT1({c2}),
.CLKOUT1B({c3}),
.CLKOUT2({c4}),
.CLKOUT2B({c5}),
.CLKOUT3({c6}),
.CLKOUT3B({c7}),
.CLKOUT4({c8}),
.CLKOUT5({c9}),
.CLKOUT6({c10}),
.CLKFBOUT({c11}),
.CLKFBOUTB({c12})
);
""".format(
site=site,
c0=mmcm_clocks[0],
c1=mmcm_clocks[1],
c2=mmcm_clocks[2],
c3=mmcm_clocks[3],
c4=mmcm_clocks[4],
c5=mmcm_clocks[5],
c6=mmcm_clocks[6],
c7=mmcm_clocks[7],
c8=mmcm_clocks[8],
c9=mmcm_clocks[9],
c10=mmcm_clocks[10],
c11=mmcm_clocks[11],
c12=mmcm_clocks[12],
))
for loc, _, site in gen_sites('PLLE2_ADV'):
pll_clocks = [
'pll_clock_{site}_{idx}'.format(site=site, idx=idx)
for idx in range(6)
]
if check_allowed(mmcm_pll_dir, site_to_cmt[site]):
for clk in pll_clocks:
clock_sources.add_clock_source(clk, site_to_cmt[site], loc)
print(
"""
wire {c0}, {c1}, {c2}, {c3}, {c4}, {c5};
(* KEEP, DONT_TOUCH, LOC = "{site}" *)
PLLE2_ADV pll_{site} (
.CLKOUT0({c0}),
.CLKOUT1({c1}),
.CLKOUT2({c2}),
.CLKOUT3({c3}),
.CLKOUT4({c4}),
.CLKOUT5({c5})
);
""".format(
site=site,
c0=pll_clocks[0],
c1=pll_clocks[1],
c2=pll_clocks[2],
c3=pll_clocks[3],
c4=pll_clocks[4],
c5=pll_clocks[5],
))
for loc, _, site in gen_sites('BUFR'):
clock_sources.add_bufg_clock_source(
'O_{site}'.format(site=site), site_to_cmt[site], loc)
print(
"""
wire O_{site};
(* KEEP, DONT_TOUCH, LOC = "{site}" *)
BUFR bufr_{site} (
.O(O_{site})
);""".format(site=site))
luts = LutMaker()
bufhs = StringIO()
bufgs = StringIO()
gclks = []
for _, _, site in sorted(gen_sites("BUFGCTRL"),
key=lambda x: BUFGCTRL_XY_FUN(x[2])):
wire_name = 'gclk_{}'.format(site)
gclks.append(wire_name)
include_source = True
if mmcm_pll_only:
include_source = False
elif only_gclk_left(todos):
include_source = need_gclk_connection(todos, site)
if include_source:
clock_sources.add_clock_source(wire_name, 'ANY')
print("""
wire {wire_name};
""".format(wire_name=wire_name))
print(
"""
wire I1_{site};
wire I0_{site};
(* KEEP, DONT_TOUCH, LOC = "{site}" *)
BUFGCTRL bufg_{site} (
.O({wire_name}),
.S1({s1net}),
.S0({s0net}),
.IGNORE1({ignore1net}),
.IGNORE0({ignore0net}),
.I1(I1_{site}),
.I0(I0_{site}),
.CE1({ce1net}),
.CE0({ce0net})
);
""".format(
site=site,
wire_name=wire_name,
s1net=luts.get_next_output_net(),
s0net=luts.get_next_output_net(),
ignore1net=luts.get_next_output_net(),
ignore0net=luts.get_next_output_net(),
ce1net=luts.get_next_output_net(),
ce0net=luts.get_next_output_net(),
),
file=bufgs)
any_bufhce = False
for tile_name, sites in gen_bufhce_sites():
for site in sites:
if not bufhce_in_todo(todos, site):
continue
any_bufhce = True
print(
"""
wire I_{site};
(* KEEP, DONT_TOUCH, LOC = "{site}" *)
BUFHCE buf_{site} (
.I(I_{site})
);
""".format(site=site, ),
file=bufhs)
if random.random() > .05:
wire_name = clock_sources.get_random_source(site_to_cmt[site])
if wire_name is None:
continue
print(
"""
assign I_{site} = {wire_name};""".format(
site=site,
wire_name=wire_name,
),
file=bufhs)
if not any_bufhce:
for tile_name, sites in gen_bufhce_sites():
for site in sites:
print(
"""
(* KEEP, DONT_TOUCH, LOC = "{site}" *)
BUFHCE #(
.INIT_OUT({INIT_OUT}),
.CE_TYPE({CE_TYPE}),
.IS_CE_INVERTED({IS_CE_INVERTED})
) buf_{site} (
.I({wire_name})
);
""".format(
INIT_OUT=random.randint(0, 1),
CE_TYPE=verilog.quote(
random.choice(('SYNC', 'ASYNC'))),
IS_CE_INVERTED=random.randint(0, 1),
site=site,
wire_name=gclks[0],
))
break
break
for l in luts.create_wires_and_luts():
print(l)
print(bufhs.getvalue())
print(bufgs.getvalue())
used_only = random.random() < .25
for loc, tile_type, site in sorted(gen_sites("BUFGCTRL"),
key=lambda x: BUFGCTRL_XY_FUN(x[2])):
if random.randint(0, 1):
wire_name = clock_sources.get_bufg_source(
loc, tile_type, site, todos, 1, used_only)
if wire_name is not None:
print(
"""
assign I1_{site} = {wire_name};""".format(
site=site,
wire_name=wire_name,
))
if random.randint(0, 1):
wire_name = clock_sources.get_bufg_source(
loc, tile_type, site, todos, 0, used_only)
if wire_name is not None:
print(
"""
assign I0_{site} = {wire_name};""".format(
site=site,
wire_name=wire_name,
))
print("endmodule")
if __name__ == '__main__':
main()