| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| # |
| # Copyright (C) 2017-2022 The Project X-Ray Authors. |
| # |
| # Use of this source code is governed by a ISC-style |
| # license that can be found in the LICENSE file or at |
| # https://opensource.org/licenses/ISC |
| # |
| # SPDX-License-Identifier: ISC |
| import os |
| import random |
| import math |
| random.seed(int(os.getenv("SEED"), 16)) |
| from prjxray import util |
| from prjxray import verilog |
| from prjxray import lut_maker |
| from prjxray.db import Database |
| |
| NOT_INCLUDED_TILES = ['RIOI_SING'] |
| |
| SITE_TYPES = ['OLOGICE2', 'ILOGICE2'] |
| |
| |
| 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) |
| |
| |
| def gen_sites(): |
| ''' Return dict of ISERDES/OSERDES locations. ''' |
| db = Database(util.get_db_root(), util.get_part()) |
| grid = db.grid() |
| |
| xy_fun = util.create_xy_fun('\S+') |
| |
| tiles = grid.tiles() |
| |
| for tile_name in sorted(tiles): |
| loc = grid.loc_of_tilename(tile_name) |
| gridinfo = grid.gridinfo_at_loc(loc) |
| tile_type = gridinfo.tile_type |
| |
| tile = {'tile': tile_name, 'tile_type': tile_type, 'ioi_sites': {}} |
| |
| for site_name, site_type in gridinfo.sites.items(): |
| if site_type in SITE_TYPES: |
| xy = xy_fun(site_name) |
| if xy not in tile['ioi_sites']: |
| tile['ioi_sites'][xy] = {} |
| |
| tile['ioi_sites'][xy][site_type] = site_name |
| |
| yield tile |
| |
| |
| class ClockSources(object): |
| def __init__(self): |
| self.site_to_cmt = dict(read_site_to_cmt()) |
| |
| self.leaf_gclks = {} |
| self.ioclks = {} |
| self.rclks = {} |
| self.selected_leaf_gclks = {} |
| self.lut_maker = lut_maker.LutMaker() |
| |
| for cmt in set(self.site_to_cmt.values()): |
| self.leaf_gclks[cmt] = [] |
| self.ioclks[cmt] = [] |
| self.rclks[cmt] = [] |
| |
| def init_clocks(self): |
| """ Initialize all IOI clock sources. """ |
| for site, cmt in self.site_to_cmt.items(): |
| clk = 'clk_' + site |
| if 'BUFHCE' in site: |
| print( |
| """ |
| wire {clk}; |
| (* KEEP, DONT_TOUCH, LOC = "{site}" *) |
| BUFH bufh_{site}( |
| .O({clk}) |
| ); |
| """.format( |
| clk=clk, |
| site=site, |
| )) |
| |
| self.leaf_gclks[cmt].append(clk) |
| |
| if 'BUFIO' in site: |
| print( |
| """ |
| wire {clk}; |
| (* KEEP, DONT_TOUCH, LOC = "{site}" *) |
| BUFIO bufio_{site}( |
| .O({clk}) |
| ); |
| """.format( |
| clk=clk, |
| site=site, |
| )) |
| |
| self.ioclks[cmt].append(clk) |
| |
| if 'BUFR' in site: |
| print( |
| """ |
| wire {clk}; |
| (* KEEP, DONT_TOUCH, LOC = "{site}" *) |
| BUFR bufr_{site}( |
| .O({clk}) |
| ); |
| """.format( |
| clk=clk, |
| site=site, |
| )) |
| |
| self.rclks[cmt].append(clk) |
| |
| # Choose 6 leaf_gclks to be used in each CMT. |
| for cmt in self.leaf_gclks: |
| self.selected_leaf_gclks[cmt] = random.sample( |
| self.leaf_gclks[cmt], 6) |
| |
| def get_clock( |
| self, |
| site, |
| allow_ioclks, |
| allow_rclks, |
| allow_fabric=True, |
| allow_empty=True): |
| cmt = self.site_to_cmt[site] |
| choices = [] |
| if allow_fabric: |
| choices.append('lut') |
| |
| if allow_empty: |
| choices.append('') |
| |
| choices.extend(self.selected_leaf_gclks[cmt]) |
| if allow_ioclks: |
| choices.extend(self.ioclks[cmt]) |
| |
| if allow_rclks: |
| choices.extend(self.rclks[cmt]) |
| |
| clock = random.choice(choices) |
| is_lut = False |
| if clock == "lut": |
| clock = self.lut_maker.get_next_output_net() |
| is_lut = True |
| return clock, is_lut |
| |
| |
| def add_port(ports, port, signal): |
| ports.append('.{}({})'.format(port, signal)) |
| |
| |
| def run(): |
| print("module top();") |
| |
| clocks = ClockSources() |
| clocks.init_clocks() |
| """ |
| |
| ISERDESE2 clock sources: |
| |
| CLK/CLKB: |
| - Allows LEAF_GCLK, IOCLKS, RCLKS and fabric |
| - Dedicated pips |
| |
| CLKDIV: |
| - No dedicated pips, uses fabric clock in. |
| |
| CLKDIVP: |
| - Has pips, MIG only, PHASER or fabric. |
| |
| OCLK/OCLKB: |
| - Allows LEAF_GCLK, IOCLKS, RCLKS and fabric |
| - Must match OSERDESE2:CLK/CLKB |
| |
| OSERDESE2 clock sources: |
| |
| CLKDIV/CLKDIVB: |
| - Allows LEAF_GCLK and RCLKS and fabric |
| - Dedicated pips |
| |
| CLKDIVF/CLKDIVFB: |
| - Allows LEAF_GCLK and RCLKS and fabric |
| - No explicit port, follows CLKDIV/CLKDIVB? |
| """ |
| |
| output = [] |
| route_file = open("routes.txt", "w") |
| |
| for tile in gen_sites(): |
| if tile['tile_type'] in NOT_INCLUDED_TILES: |
| continue |
| |
| for xy in tile['ioi_sites']: |
| ilogic_site_type = random.choice([None, 'ISERDESE2', 'IDDR']) |
| use_oserdes = random.randint(0, 1) |
| |
| ilogic_site = tile['ioi_sites'][xy]['ILOGICE2'] |
| ologic_site = tile['ioi_sites'][xy]['OLOGICE2'] |
| |
| if use_oserdes: |
| oclk, _ = clocks.get_clock( |
| ologic_site, allow_ioclks=True, allow_rclks=True) |
| |
| oclkb = oclk |
| else: |
| oclk, is_lut = clocks.get_clock( |
| ilogic_site, allow_ioclks=True, allow_rclks=True) |
| |
| if random.randint(0, 1): |
| oclkb = oclk |
| else: |
| if random.randint(0, 1): |
| oclkb, _ = clocks.get_clock( |
| ilogic_site, |
| allow_ioclks=True, |
| allow_rclks=True, |
| allow_fabric=not is_lut) |
| else: |
| # Explicitly provide IMUX stimulus to resolve IMUX pips |
| oclk = random.randint(0, 1) |
| oclkb = random.randint(0, 1) |
| |
| DATA_RATE = random.choice(['DDR', 'SDR']) |
| clk, clk_is_lut = clocks.get_clock( |
| ilogic_site, |
| allow_ioclks=True, |
| allow_rclks=True, |
| allow_empty=DATA_RATE == 'SDR') |
| |
| clkb = clk |
| while clkb == clk: |
| clkb, clkb_is_lut = clocks.get_clock( |
| ilogic_site, |
| allow_ioclks=True, |
| allow_rclks=True, |
| allow_empty=False) |
| |
| imux_available = { |
| 0: set(("IOI_IMUX20_0", "IOI_IMUX22_0")), |
| 1: set(("IOI_IMUX20_1", "IOI_IMUX22_1")), |
| } |
| |
| # Force CLK route through IMUX when connected to a LUT |
| if clk_is_lut: |
| y = (xy[1] + 1) % 2 |
| |
| route = random.choice(list(imux_available[y])) |
| imux_available[y].remove(route) |
| |
| route = "{}/{}".format(tile["tile"], route) |
| route_file.write("{} {}\n".format(clk, route)) |
| |
| # Force CLKB route through IMUX when connected to a LUT |
| if clkb_is_lut: |
| y = (xy[1] + 1) % 2 |
| |
| route = random.choice(list(imux_available[y])) |
| imux_available[y].remove(route) |
| |
| route = "{}/{}".format(tile["tile"], route) |
| route_file.write("{} {}\n".format(clkb, route)) |
| |
| if ilogic_site_type is None: |
| pass |
| |
| elif ilogic_site_type == 'ISERDESE2': |
| INTERFACE_TYPE = random.choice( |
| [ |
| 'MEMORY', |
| 'MEMORY_DDR3', |
| 'MEMORY_QDR', |
| 'NETWORKING', |
| 'OVERSAMPLE', |
| ]) |
| ports = [] |
| |
| add_port(ports, 'CLK', clk) |
| add_port(ports, 'CLKB', clkb) |
| add_port(ports, 'OCLK', oclk) |
| add_port(ports, 'OCLKB', oclkb) |
| |
| output.append( |
| """ |
| (* KEEP, DONT_TOUCH, LOC="{site}" *) |
| ISERDESE2 #( |
| .DATA_RATE({DATA_RATE}), |
| .INTERFACE_TYPE({INTERFACE_TYPE}), |
| .IS_CLK_INVERTED({IS_CLK_INVERTED}), |
| .IS_CLKB_INVERTED({IS_CLKB_INVERTED}), |
| .IS_OCLK_INVERTED({IS_OCLK_INVERTED}), |
| .IS_OCLKB_INVERTED({IS_OCLKB_INVERTED}), |
| .INIT_Q1({INIT_Q1}), |
| .INIT_Q2({INIT_Q2}), |
| .INIT_Q3({INIT_Q3}), |
| .INIT_Q4({INIT_Q4}), |
| .SRVAL_Q1({SRVAL_Q1}), |
| .SRVAL_Q2({SRVAL_Q2}), |
| .SRVAL_Q3({SRVAL_Q3}), |
| .SRVAL_Q4({SRVAL_Q4}) |
| ) iserdes_{site}( |
| {ports});""".format( |
| site=ilogic_site, |
| ports=',\n'.join(ports), |
| DATA_RATE=verilog.quote(DATA_RATE), |
| INTERFACE_TYPE=verilog.quote(INTERFACE_TYPE), |
| IS_CLK_INVERTED=random.randint(0, 1), |
| IS_CLKB_INVERTED=random.randint(0, 1), |
| IS_OCLK_INVERTED=random.randint(0, 1), |
| IS_OCLKB_INVERTED=random.randint(0, 1), |
| INIT_Q1=random.randint(0, 1), |
| INIT_Q2=random.randint(0, 1), |
| INIT_Q3=random.randint(0, 1), |
| INIT_Q4=random.randint(0, 1), |
| SRVAL_Q1=random.randint(0, 1), |
| SRVAL_Q2=random.randint(0, 1), |
| SRVAL_Q3=random.randint(0, 1), |
| SRVAL_Q4=random.randint(0, 1), |
| )) |
| elif ilogic_site_type == 'IDDR': |
| ports = [] |
| add_port(ports, 'C', clk) |
| add_port(ports, 'CB', clkb) |
| |
| output.append( |
| """ |
| (* KEEP, DONT_TOUCH, LOC="{site}" *) |
| IDDR_2CLK #( |
| .INIT_Q1({INIT_Q1}), |
| .INIT_Q2({INIT_Q2}), |
| .SRTYPE({SRTYPE}) |
| ) iserdes_{site}( |
| {ports});""".format( |
| site=ilogic_site, |
| ports=',\n'.join(ports), |
| INIT_Q1=random.randint(0, 1), |
| INIT_Q2=random.randint(0, 1), |
| SRTYPE=verilog.quote(random.choice(['ASYNC', 'SYNC'])), |
| )) |
| else: |
| assert False, ilogic_site_type |
| |
| if use_oserdes: |
| ports = [] |
| |
| add_port( |
| ports, 'CLKDIV', |
| clocks.get_clock( |
| ologic_site, |
| allow_ioclks=False, |
| allow_rclks=True, |
| )[0]) |
| |
| add_port(ports, 'CLK', oclk) |
| |
| output.append( |
| """ |
| (* KEEP, DONT_TOUCH, LOC = "{site}" *) |
| OSERDESE2 #( |
| .IS_CLK_INVERTED({IS_CLK_INVERTED}), |
| .DATA_RATE_OQ("SDR"), |
| .DATA_RATE_TQ("SDR") |
| ) oserdes_{site} ( |
| {ports});""".format( |
| IS_CLK_INVERTED=random.randint(0, 1), |
| site=ologic_site, |
| ports=',\n'.join(ports), |
| )) |
| |
| for s in clocks.lut_maker.create_wires_and_luts(): |
| print(s) |
| |
| for s in output: |
| print(s) |
| |
| print("endmodule") |
| |
| |
| if __name__ == '__main__': |
| run() |