mirror of https://github.com/openXC7/prjxray.git
039-hclk-config: fuzz BUFIO_Yn.IN_USE and the BUFR RCLK2IOn leaf, with pin- and MMCM-fed sources
The fuzzer had no consumer for its BUFRs and no BUFIO at all, which left
three things in the database's HCLK_IOI3 model unexercised: BUFIO has no
rows, the four DMUX I2IOCLK positions could never resolve (openXC7's
047 finding: no IBUF specimen), and the regional-clock leaf into the IOI
column (RCLK2IOn <- CK_BUFRCLKn) was recorded as 'always' although it
carries a bit. This extends the specimen class in the same fuzzer:
- BUFIO half: for each clock-capable IOB33M of the tile's neighbourhood,
a BUFIO at its dedicated site (the k-th IOB by y drives the k-th BUFIO
by y -- bijective on every HCLK_IOI3 of xc7a100t; LOC'ing elsewhere
fails placement), randomly unused / fed from that pin (I2IOCLK leg) /
fed from the region's MMCM CLKOUT0..3 (LOC'd on its MMCME2_ADV; a
PLLE2 cannot reach BUFIO), sunk into an ISERDESE2 of the same bank
(DRC REQP-4 wants a load). Tag: BUFIO_Y{n}.IN_USE, read back from
the placed BUFIO sites dumped by generate.tcl. Mixed sources are what
make IN_USE independent of the route.
- Consumed BUFRs: half of the IN_USE BUFRs now drive an ODDR through an
OBUF LOC'd on the S half of the same clock-capable pair (unused by the
IBUFs). Tag: HCLK_IOI_RCLK2IO{n}.HCLK_IOI_CK_BUFRCLK{n} per RCLK
index (slot rel-y -> RCLK {0:2, 1:3, 2:0, 3:1}, the mapping the same
data measures via the IOCLK_PLL rows).
100 specimens on xc7a100tfgg676-1 seeded from openXC7/prjxray-db with
the fuzzer's own rows stripped: the 40 pre-existing rows return
byte-identical; new rows BUFIO_Y0..3.IN_USE (2 bits each) and
RCLK2IO0..3 (32_29 / 30_31 / 31_19 / 28_17), the latter matching one
Vivado 2026.1 golden per slot bit for bit. Database change in the
companion prjxray-db PR.
This commit is contained in:
parent
8294aeb68d
commit
7191d33707
|
|
@ -25,8 +25,33 @@ def main():
|
||||||
with open('params.json') as f:
|
with open('params.json') as f:
|
||||||
params = json.load(f)
|
params = json.load(f)
|
||||||
|
|
||||||
|
placed = {}
|
||||||
|
try:
|
||||||
|
with open('bufio_sites.txt') as f:
|
||||||
|
for line in f:
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) == 2:
|
||||||
|
placed[parts[0]] = parts[1]
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
used_sites = set(placed.values())
|
||||||
|
|
||||||
for row in params:
|
for row in params:
|
||||||
|
if row.get('kind') == 'BUFIO_TILE':
|
||||||
|
sites = row['bufio_sites']
|
||||||
|
ys = sorted(int(st.split('Y')[-1]) for st in sites)
|
||||||
|
y_min = ys[0] if ys else 0
|
||||||
|
for st in sites:
|
||||||
|
y = int(st.split('Y')[-1]) - y_min
|
||||||
|
segmk.add_tile_tag(
|
||||||
|
row['tile'], 'BUFIO_Y{}.IN_USE'.format(y), 1 if st in used_sites else 0)
|
||||||
|
continue
|
||||||
base_name = 'BUFR_Y{}'.format(row['y'])
|
base_name = 'BUFR_Y{}'.format(row['y'])
|
||||||
|
# v5: regional-clock leaf into the IOI column, per RCLK index (slot rel-y -> RCLK {0:2,1:3,2:0,3:1})
|
||||||
|
rclk = {0: 2, 1: 3, 2: 0, 3: 1}[row['y']]
|
||||||
|
segmk.add_tile_tag(
|
||||||
|
row['tile'], 'HCLK_IOI_RCLK2IO{}.HCLK_IOI_CK_BUFRCLK{}'.format(rclk, rclk),
|
||||||
|
1 if (row['IN_USE'] and row.get('consumed')) else 0)
|
||||||
|
|
||||||
segmk.add_tile_tag(
|
segmk.add_tile_tag(
|
||||||
row['tile'], '{}.IN_USE'.format(base_name), row['IN_USE'])
|
row['tile'], '{}.IN_USE'.format(base_name), row['IN_USE'])
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,11 @@ proc run {} {
|
||||||
place_design
|
place_design
|
||||||
route_design
|
route_design
|
||||||
|
|
||||||
|
set fp [open bufio_sites.txt w]
|
||||||
|
foreach c [get_cells -quiet -hierarchical -filter {REF_NAME == BUFIO}] {
|
||||||
|
puts $fp "[get_property NAME $c] [get_sites -quiet -of_objects $c]"
|
||||||
|
}
|
||||||
|
close $fp
|
||||||
write_checkpoint -force design.dcp
|
write_checkpoint -force design.dcp
|
||||||
write_bitstream -force design.bit
|
write_bitstream -force design.bit
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,18 @@ def gen_sites():
|
||||||
xy_fun = util.create_xy_fun('BUFR_')
|
xy_fun = util.create_xy_fun('BUFR_')
|
||||||
db = Database(util.get_db_root(), util.get_part())
|
db = Database(util.get_db_root(), util.get_part())
|
||||||
grid = db.grid()
|
grid = db.grid()
|
||||||
|
# MMCME2_ADV site per clock region: an MMCM feeding a bank's BUFIOs must sit
|
||||||
|
# in that bank's own region (IOCLK_PLL is a region-local path). Only the
|
||||||
|
# MMCM's CLKOUT0..3 reach BUFIO -- a PLLE2's outputs are unroutable to BUFIO
|
||||||
|
# sites (measured), which is also why 047 registers only MMCM outputs as
|
||||||
|
# BUFIO-capable sources.
|
||||||
|
mmcm_by_region = {}
|
||||||
|
for tn in grid.tiles():
|
||||||
|
gi = grid.gridinfo_at_loc(grid.loc_of_tilename(tn))
|
||||||
|
for st, sty in gi.sites.items():
|
||||||
|
if sty == 'MMCME2_ADV' and gi.clock_region is not None:
|
||||||
|
mmcm_by_region[str(gi.clock_region)] = st
|
||||||
|
|
||||||
for tile_name in sorted(grid.tiles()):
|
for tile_name in sorted(grid.tiles()):
|
||||||
loc = grid.loc_of_tilename(tile_name)
|
loc = grid.loc_of_tilename(tile_name)
|
||||||
gridinfo = grid.gridinfo_at_loc(loc)
|
gridinfo = grid.gridinfo_at_loc(loc)
|
||||||
|
|
@ -28,6 +40,8 @@ def gen_sites():
|
||||||
|
|
||||||
xs = []
|
xs = []
|
||||||
ys = []
|
ys = []
|
||||||
|
bufio_sites = []
|
||||||
|
bufio_xy = util.create_xy_fun('BUFIO_')
|
||||||
for site, site_type in gridinfo.sites.items():
|
for site, site_type in gridinfo.sites.items():
|
||||||
if site_type == 'BUFR':
|
if site_type == 'BUFR':
|
||||||
x, y = xy_fun(site)
|
x, y = xy_fun(site)
|
||||||
|
|
@ -35,6 +49,9 @@ def gen_sites():
|
||||||
ys.append(y)
|
ys.append(y)
|
||||||
|
|
||||||
sites.append((site, x, y))
|
sites.append((site, x, y))
|
||||||
|
elif site_type == 'BUFIO':
|
||||||
|
bx, by = bufio_xy(site)
|
||||||
|
bufio_sites.append((site, bx, by))
|
||||||
|
|
||||||
if not sites:
|
if not sites:
|
||||||
continue
|
continue
|
||||||
|
|
@ -51,6 +68,8 @@ def gen_sites():
|
||||||
dx = -1
|
dx = -1
|
||||||
|
|
||||||
iobs = []
|
iobs = []
|
||||||
|
iobs_s = []
|
||||||
|
ilogics = []
|
||||||
|
|
||||||
for dy in (-1, -3, 2, 4):
|
for dy in (-1, -3, 2, 4):
|
||||||
iob = grid.gridinfo_at_loc((loc.grid_x + dx, loc.grid_y + dy))
|
iob = grid.gridinfo_at_loc((loc.grid_x + dx, loc.grid_y + dy))
|
||||||
|
|
@ -58,17 +77,28 @@ def gen_sites():
|
||||||
for site, site_type in iob.sites.items():
|
for site, site_type in iob.sites.items():
|
||||||
if site_type == 'IOB33M':
|
if site_type == 'IOB33M':
|
||||||
iobs.append(site)
|
iobs.append(site)
|
||||||
|
elif site_type == 'IOB33S':
|
||||||
|
iobs_s.append(site)
|
||||||
|
|
||||||
yield tile_name, min(xs), min(ys), sorted(sites), sorted(iobs)
|
ioi = grid.gridinfo_at_loc((loc.grid_x, loc.grid_y + dy))
|
||||||
|
for site, site_type in sorted(ioi.sites.items()):
|
||||||
|
if site_type == 'ILOGICE3':
|
||||||
|
ilogics.append(site)
|
||||||
|
break
|
||||||
|
|
||||||
|
mmcm_site = mmcm_by_region.get(str(gridinfo.clock_region))
|
||||||
|
yield tile_name, min(xs), min(ys), sorted(sites), sorted(iobs), sorted(bufio_sites), ilogics, mmcm_site, sorted(iobs_s)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
params_list = []
|
params_list = []
|
||||||
num_clocks = 0
|
num_clocks = 0
|
||||||
|
num_outs = 0
|
||||||
outputs = []
|
outputs = []
|
||||||
luts = LutMaker()
|
luts = LutMaker()
|
||||||
for tile_name, x_min, y_min, sites, iobs in gen_sites():
|
for tile_name, x_min, y_min, sites, iobs, bufio_sites, ilogics, mmcm_site, iobs_s in gen_sites():
|
||||||
|
outs_used = 0
|
||||||
ioclks = []
|
ioclks = []
|
||||||
for iob in iobs:
|
for iob in iobs:
|
||||||
ioclk = 'clk_{}'.format(iob)
|
ioclk = 'clk_{}'.format(iob)
|
||||||
|
|
@ -121,8 +151,36 @@ def main():
|
||||||
params['CE'] = luts.get_next_output_net()
|
params['CE'] = luts.get_next_output_net()
|
||||||
params['CLR'] = luts.get_next_output_net()
|
params['CLR'] = luts.get_next_output_net()
|
||||||
|
|
||||||
outputs.append(
|
params['consumed'] = 0
|
||||||
'''
|
if outs_used < len(iobs_s) and random.randint(0, 1):
|
||||||
|
params['consumed'] = 1
|
||||||
|
params['obuf_site'] = iobs_s[outs_used]
|
||||||
|
params['out_idx'] = num_outs
|
||||||
|
num_outs += 1
|
||||||
|
outs_used += 1
|
||||||
|
if params['consumed']:
|
||||||
|
outputs.append(
|
||||||
|
'''
|
||||||
|
wire {site}_o;
|
||||||
|
(* KEEP, DONT_TOUCH, LOC = "{site}" *)
|
||||||
|
BUFR #(
|
||||||
|
.BUFR_DIVIDE({BUFR_DIVIDE})
|
||||||
|
) buf_{site} (
|
||||||
|
.CE({CE}),
|
||||||
|
.CLR({CLR}),
|
||||||
|
.I({I}),
|
||||||
|
.O({site}_o)
|
||||||
|
);
|
||||||
|
wire {site}_q;
|
||||||
|
(* KEEP, DONT_TOUCH *)
|
||||||
|
ODDR #(.DDR_CLK_EDGE("SAME_EDGE")) oddr_{site} (
|
||||||
|
.C({site}_o), .CE(1'b1), .D1(1'b1), .D2(1'b0), .R(1'b0), .S(1'b0), .Q({site}_q));
|
||||||
|
(* KEEP, DONT_TOUCH, LOC = "{obuf_site}" *)
|
||||||
|
OBUF #(.IOSTANDARD("LVCMOS33")) obuf_{site} (.I({site}_q), .O(outs[{out_idx}]));
|
||||||
|
'''.format(**params))
|
||||||
|
else:
|
||||||
|
outputs.append(
|
||||||
|
'''
|
||||||
(* KEEP, DONT_TOUCH, LOC = "{site}" *)
|
(* KEEP, DONT_TOUCH, LOC = "{site}" *)
|
||||||
BUFR #(
|
BUFR #(
|
||||||
.BUFR_DIVIDE({BUFR_DIVIDE})
|
.BUFR_DIVIDE({BUFR_DIVIDE})
|
||||||
|
|
@ -131,14 +189,97 @@ def main():
|
||||||
.CLR({CLR}),
|
.CLR({CLR}),
|
||||||
.I({I})
|
.I({I})
|
||||||
);
|
);
|
||||||
'''.format(**params))
|
'''.format(**params))
|
||||||
|
|
||||||
params_list.append(params)
|
params_list.append(params)
|
||||||
|
|
||||||
|
# --- BUFIO half (extension v4, fully LOC'd) ---
|
||||||
|
rec = {}
|
||||||
|
rec['tile'] = tile_name
|
||||||
|
rec['kind'] = 'BUFIO_TILE'
|
||||||
|
rec['bufio_sites'] = [site for site, _, _ in bufio_sites]
|
||||||
|
rec['bufio_insts'] = []
|
||||||
|
rec['bufio_src'] = {}
|
||||||
|
rec['bufio_loc'] = {}
|
||||||
|
bys = sorted(by for _, _, by in bufio_sites)
|
||||||
|
by_min = bys[0] if bys else 0
|
||||||
|
clkout_for_rel_y = {0: 0, 1: 1, 2: 2, 3: 3}
|
||||||
|
mmcm_declared = False
|
||||||
|
mmcm_name = 'mmcm_{}'.format(tile_name)
|
||||||
|
for k, (iob, ioclk) in enumerate(zip(iobs, ioclks)):
|
||||||
|
if k >= len(ilogics):
|
||||||
|
break
|
||||||
|
# A clock-capable IOB feeds ONE dedicated BUFIO of its bank: the k-th
|
||||||
|
# clock-capable IOB (by y) of the tile's neighbourhood drives the k-th
|
||||||
|
# BUFIO site (by y). Measured on every HCLK_IOI3 of xc7a100t (24/24,
|
||||||
|
# bijective); a CCIO-fed BUFIO LOC'd anywhere else fails placement.
|
||||||
|
bufio_by_y = sorted((st for st, _, _ in bufio_sites), key=lambda st: int(st.split('Y')[-1]))
|
||||||
|
iobs_by_y = sorted(iobs, key=lambda st: int(st.split('Y')[-1]))
|
||||||
|
rank = iobs_by_y.index(iob)
|
||||||
|
if rank >= len(bufio_by_y):
|
||||||
|
continue
|
||||||
|
bsite = bufio_by_y[rank]
|
||||||
|
state = random.choice(('unused', 'CCIO', 'MMCM')) if mmcm_site else random.choice(('unused', 'CCIO'))
|
||||||
|
if state == 'unused':
|
||||||
|
continue
|
||||||
|
inst = 'bufio_{}'.format(iob)
|
||||||
|
rec['bufio_insts'].append(inst)
|
||||||
|
rec['bufio_src'][inst] = state
|
||||||
|
rec['bufio_loc'][inst] = bsite
|
||||||
|
if state == 'MMCM':
|
||||||
|
if not mmcm_declared:
|
||||||
|
mmcm_declared = True
|
||||||
|
outputs.append(
|
||||||
|
'''
|
||||||
|
wire {pll}_fb, {pll}_out0, {pll}_out1, {pll}_out2, {pll}_out3;
|
||||||
|
(* KEEP, DONT_TOUCH, LOC = "{mmcm_site}" *)
|
||||||
|
MMCME2_BASE #(
|
||||||
|
.CLKIN1_PERIOD(10.0), .CLKFBOUT_MULT_F(8.0), .DIVCLK_DIVIDE(1),
|
||||||
|
.CLKOUT0_DIVIDE_F(8.0), .CLKOUT1_DIVIDE(8), .CLKOUT2_DIVIDE(8), .CLKOUT3_DIVIDE(8)
|
||||||
|
) {pll} (
|
||||||
|
.CLKIN1({clkin}), .CLKFBIN({pll}_fb), .CLKFBOUT({pll}_fb),
|
||||||
|
.CLKOUT0({pll}_out0), .CLKOUT1({pll}_out1), .CLKOUT2({pll}_out2), .CLKOUT3({pll}_out3),
|
||||||
|
.RST(1'b0), .PWRDWN(1'b0)
|
||||||
|
);'''.format(pll=mmcm_name, clkin=ioclks[0], mmcm_site=mmcm_site))
|
||||||
|
rel_y = int(bsite.split('Y')[-1]) - by_min
|
||||||
|
bufio_i = '{}_out{}'.format(mmcm_name, clkout_for_rel_y[rel_y])
|
||||||
|
else:
|
||||||
|
bufio_i = ioclk
|
||||||
|
outputs.append(
|
||||||
|
'''
|
||||||
|
wire {inst}_o;
|
||||||
|
(* KEEP, DONT_TOUCH, LOC = "{bsite}" *)
|
||||||
|
BUFIO {inst} (
|
||||||
|
.I({bufio_i}),
|
||||||
|
.O({inst}_o)
|
||||||
|
);
|
||||||
|
(* KEEP, DONT_TOUCH, LOC = "{ilogic}" *)
|
||||||
|
ISERDESE2 #(
|
||||||
|
.DATA_RATE("SDR"),
|
||||||
|
.DATA_WIDTH(4),
|
||||||
|
.INTERFACE_TYPE("OVERSAMPLE"),
|
||||||
|
.IOBDELAY("NONE"),
|
||||||
|
.NUM_CE(2),
|
||||||
|
.SERDES_MODE("MASTER")
|
||||||
|
) iserdes_{inst} (
|
||||||
|
.CLK({inst}_o),
|
||||||
|
.CLKB(),
|
||||||
|
.CLKDIV(),
|
||||||
|
.D(1'b0),
|
||||||
|
.DDLY(),
|
||||||
|
.OFB(),
|
||||||
|
.OCLKB(),
|
||||||
|
.RST(),
|
||||||
|
.SHIFTIN1(),
|
||||||
|
.SHIFTIN2()
|
||||||
|
);
|
||||||
|
'''.format(inst=inst, bufio_i=bufio_i, bsite=bsite, ilogic=ilogics[k]))
|
||||||
|
params_list.append(rec)
|
||||||
|
|
||||||
print(
|
print(
|
||||||
'''
|
'''
|
||||||
module top(input [{n1}:0] clks);
|
module top(input [{n1}:0] clks, output [{n2}:0] outs);
|
||||||
'''.format(n1=num_clocks - 1))
|
'''.format(n1=num_clocks - 1, n2=max(num_outs - 1, 0)))
|
||||||
|
|
||||||
print("""
|
print("""
|
||||||
(* KEEP, DONT_TOUCH *)
|
(* KEEP, DONT_TOUCH *)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue