| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| # | |
| # Copyright (C) 2017-2020 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 | |
| from __future__ import print_function | |
| import fasm | |
| import argparse | |
| import json | |
| import os | |
| import os.path | |
| import csv | |
| from collections import defaultdict | |
| from prjxray import fasm_assembler, util | |
| from prjxray.db import Database | |
| from prjxray.roi import Roi | |
| from prjxray.util import OpenSafeFile | |
| import sys | |
| def eprint(*args, **kwargs): | |
| print(*args, file=sys.stderr, **kwargs) | |
| class FASMSyntaxError(SyntaxError): | |
| pass | |
| def dump_frames_verbose(frames): | |
| print() | |
| print("Frames: %d" % len(frames)) | |
| for addr in sorted(frames.keys()): | |
| words = frames[addr] | |
| print( | |
| '0x%08X ' % addr + ', '.join(['0x%08X' % w for w in words]) + | |
| '...') | |
| def dump_frames_sparse(frames): | |
| print() | |
| print("Frames: %d" % len(frames)) | |
| for addr in sorted(frames.keys()): | |
| words = frames[addr] | |
| # Skip frames without filled words | |
| for w in words: | |
| if w: | |
| break | |
| else: | |
| continue | |
| print('Frame @ 0x%08X' % addr) | |
| for i, w in enumerate(words): | |
| if w: | |
| print(' % 3d: 0x%08X' % (i, w)) | |
| def dump_frm(f, frames): | |
| '''Write a .frm file given a list of frames, each containing a list of 101 32 bit words''' | |
| for addr in sorted(frames.keys()): | |
| words = frames[addr] | |
| f.write( | |
| '0x%08X ' % addr + ','.join(['0x%08X' % w for w in words]) + '\n') | |
| def find_pudc_b(db): | |
| """ Find PUDC_B pin func in grid, and return the tile and site prefix. | |
| The PUDC_B pin is a special 7-series pin that controls unused pin pullup. | |
| If the PUDC_B is unused, it is configured as an input with a PULLUP. | |
| """ | |
| grid = db.grid() | |
| pudc_b_tile_site = None | |
| for tile in grid.tiles(): | |
| gridinfo = grid.gridinfo_at_tilename(tile) | |
| for site, pin_function in gridinfo.pin_functions.items(): | |
| if 'PUDC_B' in pin_function: | |
| assert pudc_b_tile_site == None, ( | |
| pudc_b_tile_site, (tile, site)) | |
| iob_y = int(site[-1]) % 2 | |
| pudc_b_tile_site = (tile, 'IOB_Y{}'.format(iob_y)) | |
| return pudc_b_tile_site | |
| def get_iob_sites(db, tile_name): | |
| """ | |
| Yields prjxray site names for given IOB tile name | |
| """ | |
| grid = db.grid() | |
| gridinfo = grid.gridinfo_at_tilename(tile_name) | |
| for site in gridinfo.sites: | |
| site_y = int(site[-1]) % 2 | |
| yield "IOB_Y{}".format(site_y) | |
| def run( | |
| db_root, | |
| part, | |
| filename_in, | |
| f_out, | |
| sparse=False, | |
| roi=None, | |
| debug=False, | |
| emit_pudc_b_pullup=False): | |
| db = Database(db_root, part) | |
| assembler = fasm_assembler.FasmAssembler(db) | |
| set_features = set() | |
| def feature_callback(feature): | |
| set_features.add(feature) | |
| assembler.set_feature_callback(feature_callback) | |
| # Build mapping of tile to IO bank | |
| tile_to_bank = {} | |
| bank_to_tile = defaultdict(lambda: set()) | |
| if part is not None: | |
| with OpenSafeFile(os.path.join(db_root, part, "package_pins.csv"), "r") as fp: | |
| reader = csv.DictReader(fp) | |
| package_pins = [l for l in reader] | |
| with OpenSafeFile(os.path.join(db_root, part, "part.json"), "r") as fp: | |
| part_data = json.load(fp) | |
| for bank, loc in part_data["iobanks"].items(): | |
| tile = "HCLK_IOI3_" + loc | |
| bank_to_tile[bank].add(tile) | |
| tile_to_bank[tile] = bank | |
| for pin in package_pins: | |
| bank_to_tile[pin["bank"]].add(pin["tile"]) | |
| tile_to_bank[pin["tile"]] = pin["bank"] | |
| if emit_pudc_b_pullup: | |
| pudc_b_in_use = False | |
| pudc_b_tile_site = find_pudc_b(db) | |
| def check_for_pudc_b(set_feature): | |
| feature_callback(set_feature) | |
| parts = set_feature.feature.split('.') | |
| if parts[0] == pudc_b_tile_site[0] and parts[ | |
| 1] == pudc_b_tile_site[1]: | |
| nonlocal pudc_b_in_use | |
| pudc_b_in_use = True | |
| if pudc_b_tile_site is not None: | |
| assembler.set_feature_callback(check_for_pudc_b) | |
| extra_features = [] | |
| if roi: | |
| with OpenSafeFile(roi) as f: | |
| roi_j = json.load(f) | |
| x1 = roi_j['info']['GRID_X_MIN'] | |
| x2 = roi_j['info']['GRID_X_MAX'] | |
| y1 = roi_j['info']['GRID_Y_MIN'] | |
| y2 = roi_j['info']['GRID_Y_MAX'] | |
| assembler.mark_roi_frames(Roi(db=db, x1=x1, x2=x2, y1=y1, y2=y2)) | |
| if 'required_features' in roi_j: | |
| extra_features = list( | |
| fasm.parse_fasm_string('\n'.join(roi_j['required_features']))) | |
| # Get required extra features for the part | |
| required_features = db.get_required_fasm_features(part) | |
| extra_features += list( | |
| fasm.parse_fasm_string('\n'.join(required_features))) | |
| assembler.parse_fasm_filename(filename_in, extra_features=extra_features) | |
| if emit_pudc_b_pullup and not pudc_b_in_use and pudc_b_tile_site is not None: | |
| # Enable IN-only and PULLUP on PUDC_B IOB. | |
| # | |
| # TODO: The following FASM string only works on Artix 50T and Zynq 10 | |
| # fabrics. It is known to be wrong for the K70T fabric, but it is | |
| # unclear how to know which IOSTANDARD to use. | |
| missing_features = [] | |
| for line in fasm.parse_fasm_string(""" | |
| {tile}.{site}.LVCMOS12_LVCMOS15_LVCMOS18_LVCMOS25_LVCMOS33_LVTTL_SSTL135_SSTL15.IN_ONLY | |
| {tile}.{site}.LVCMOS25_LVCMOS33_LVTTL.IN | |
| {tile}.{site}.PULLTYPE.PULLUP | |
| """.format( | |
| tile=pudc_b_tile_site[0], | |
| site=pudc_b_tile_site[1], | |
| )): | |
| assembler.add_fasm_line(line, missing_features) | |
| if missing_features: | |
| raise fasm_assembler.FasmLookupError('\n'.join(missing_features)) | |
| if part is not None: | |
| # Make a set of all used IOB tiles and sites. Look for the "STEPDOWN" | |
| # feature. If one is set for an IOB then set it for all other IOBs of | |
| # the same bank. | |
| stepdown_tags = defaultdict(lambda: set()) | |
| stepdown_banks = set() | |
| used_iob_sites = set() | |
| for set_feature in set_features: | |
| if set_feature.value == 0: | |
| continue | |
| feature = set_feature.feature | |
| parts = feature.split(".") | |
| if len(parts) >= 3: | |
| tile, site, tag = feature.split(".", maxsplit=2) | |
| if "IOB33" in tile: | |
| used_iob_sites.add(( | |
| tile, | |
| site, | |
| )) | |
| # Store STEPDOWN related tags. | |
| if "STEPDOWN" in tag: | |
| bank = tile_to_bank[tile] | |
| stepdown_banks.add(bank) | |
| stepdown_tags[bank].add(tag) | |
| # Set the feature for unused IOBs, loop over all banks which were | |
| # observed to have the STEPDOWN feature set. | |
| missing_features = [] | |
| for bank in stepdown_banks: | |
| for tile in bank_to_tile[bank]: | |
| # This is an IOB33 tile. Set the STEPDOWN feature in it but | |
| # only if it is unused. | |
| if "IOB33" in tile: | |
| for site in get_iob_sites(db, tile): | |
| if (tile, site) in used_iob_sites: | |
| continue | |
| for tag in stepdown_tags[bank]: | |
| feature = "{}.{}.{}".format(tile, site, tag) | |
| for line in fasm.parse_fasm_string(feature): | |
| assembler.add_fasm_line(line, missing_features) | |
| # This is a HCLK_IOI3 tile, set the stepdown feature for it | |
| # too. | |
| if "HCLK_IOI3" in tile: | |
| feature = "{}.STEPDOWN".format(tile) | |
| for line in fasm.parse_fasm_string(feature): | |
| assembler.add_fasm_line(line, missing_features) | |
| if missing_features: | |
| raise fasm_assembler.FasmLookupError('\n'.join(missing_features)) | |
| frames = assembler.get_frames(sparse=sparse) | |
| if debug: | |
| dump_frames_sparse(frames) | |
| dump_frm(f_out, frames) | |
| def main(): | |
| parser = argparse.ArgumentParser( | |
| description= | |
| 'Convert FPGA configuration description ("FPGA assembly") into binary frame equivalent' | |
| ) | |
| util.db_root_arg(parser) | |
| util.part_arg(parser) | |
| parser.add_argument( | |
| '--sparse', action='store_true', help="Don't zero fill all frames") | |
| parser.add_argument( | |
| '--roi', | |
| help="ROI design.json file defining which tiles are within the ROI.") | |
| parser.add_argument( | |
| '--emit_pudc_b_pullup', | |
| help="Emit an IBUF and PULLUP on the PUDC_B pin if unused", | |
| action='store_true') | |
| parser.add_argument( | |
| '--debug', action='store_true', help="Print debug dump") | |
| parser.add_argument('fn_in', help='Input FPGA assembly (.fasm) file') | |
| parser.add_argument( | |
| 'fn_out', | |
| default='/dev/stdout', | |
| nargs='?', | |
| help='Output FPGA frame (.frm) file') | |
| args = parser.parse_args() | |
| run( | |
| db_root=args.db_root, | |
| part=args.part, | |
| filename_in=args.fn_in, | |
| f_out=open(args.fn_out, 'w'), | |
| sparse=args.sparse, | |
| roi=args.roi, | |
| debug=args.debug, | |
| emit_pudc_b_pullup=args.emit_pudc_b_pullup) | |
| if __name__ == '__main__': | |
| main() |