2018-01-17 23:39:37 +01:00
|
|
|
#!/usr/bin/env python3
|
2020-04-16 09:29:20 +02:00
|
|
|
# -*- 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
|
2018-01-17 23:39:37 +01:00
|
|
|
|
2018-10-19 17:33:31 +02:00
|
|
|
from __future__ import print_function
|
2019-01-11 04:47:55 +01:00
|
|
|
|
2019-02-13 02:37:30 +01:00
|
|
|
import fasm
|
2018-10-19 17:33:31 +02:00
|
|
|
import argparse
|
2019-02-13 02:37:30 +01:00
|
|
|
import json
|
2018-01-17 23:39:37 +01:00
|
|
|
import os
|
2018-10-19 17:33:31 +02:00
|
|
|
import os.path
|
2019-12-10 17:29:28 +01:00
|
|
|
import csv
|
|
|
|
|
|
|
|
|
|
from collections import defaultdict
|
2018-01-17 23:39:37 +01:00
|
|
|
|
2020-06-16 02:28:42 +02:00
|
|
|
from prjxray import fasm_assembler, util
|
|
|
|
|
from prjxray.db import Database
|
|
|
|
|
from prjxray.roi import Roi
|
2019-01-11 04:47:55 +01:00
|
|
|
|
2019-12-16 13:40:35 +01:00
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def eprint(*args, **kwargs):
|
|
|
|
|
print(*args, file=sys.stderr, **kwargs)
|
|
|
|
|
|
2019-01-11 04:47:55 +01:00
|
|
|
|
|
|
|
|
class FASMSyntaxError(SyntaxError):
|
|
|
|
|
pass
|
|
|
|
|
|
2018-10-20 01:19:22 +02:00
|
|
|
|
2018-01-17 23:39:37 +01:00
|
|
|
def dump_frames_verbose(frames):
|
|
|
|
|
print()
|
|
|
|
|
print("Frames: %d" % len(frames))
|
|
|
|
|
for addr in sorted(frames.keys()):
|
|
|
|
|
words = frames[addr]
|
|
|
|
|
print(
|
2018-06-23 00:15:59 +02:00
|
|
|
'0x%08X ' % addr + ', '.join(['0x%08X' % w for w in words]) +
|
|
|
|
|
'...')
|
2018-01-17 23:39:37 +01:00
|
|
|
|
2018-10-20 01:19:22 +02:00
|
|
|
|
2018-01-17 23:39:37 +01:00
|
|
|
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))
|
|
|
|
|
|
2018-10-20 01:19:22 +02:00
|
|
|
|
2018-01-17 23:39:37 +01:00
|
|
|
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')
|
|
|
|
|
|
2018-10-20 01:19:22 +02:00
|
|
|
|
2019-10-15 01:38:02 +02:00
|
|
|
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
|
|
|
|
|
|
2019-12-11 09:22:46 +01:00
|
|
|
|
2019-12-10 17:29:28 +01:00
|
|
|
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)
|
2019-10-15 01:38:02 +02:00
|
|
|
|
2019-12-11 09:22:46 +01:00
|
|
|
|
2019-10-15 01:38:02 +02:00
|
|
|
def run(
|
|
|
|
|
db_root,
|
2019-12-10 13:49:51 +01:00
|
|
|
part,
|
2019-10-15 01:38:02 +02:00
|
|
|
filename_in,
|
|
|
|
|
f_out,
|
|
|
|
|
sparse=False,
|
|
|
|
|
roi=None,
|
|
|
|
|
debug=False,
|
|
|
|
|
emit_pudc_b_pullup=False):
|
2020-01-15 17:34:33 +01:00
|
|
|
db = Database(db_root, part)
|
2019-02-13 02:37:30 +01:00
|
|
|
assembler = fasm_assembler.FasmAssembler(db)
|
|
|
|
|
|
2019-12-10 17:29:28 +01:00
|
|
|
set_features = set()
|
2019-12-11 09:22:46 +01:00
|
|
|
|
2019-12-10 17:29:28 +01:00
|
|
|
def feature_callback(feature):
|
|
|
|
|
set_features.add(feature)
|
|
|
|
|
|
2019-12-11 09:22:46 +01:00
|
|
|
assembler.set_feature_callback(feature_callback)
|
2019-12-10 17:29:28 +01:00
|
|
|
|
|
|
|
|
# Build mapping of tile to IO bank
|
|
|
|
|
tile_to_bank = {}
|
|
|
|
|
bank_to_tile = defaultdict(lambda: set())
|
|
|
|
|
|
|
|
|
|
if part is not None:
|
2020-01-15 17:34:33 +01:00
|
|
|
with open(os.path.join(db_root, part, "package_pins.csv"), "r") as fp:
|
2019-12-10 17:29:28 +01:00
|
|
|
reader = csv.DictReader(fp)
|
|
|
|
|
package_pins = [l for l in reader]
|
|
|
|
|
|
2020-01-15 17:34:33 +01:00
|
|
|
with open(os.path.join(db_root, part, "part.json"), "r") as fp:
|
2019-12-16 13:40:35 +01:00
|
|
|
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
|
|
|
|
|
|
2019-12-10 17:29:28 +01:00
|
|
|
for pin in package_pins:
|
|
|
|
|
bank_to_tile[pin["bank"]].add(pin["tile"])
|
|
|
|
|
tile_to_bank[pin["tile"]] = pin["bank"]
|
|
|
|
|
|
2019-10-15 01:38:02 +02:00
|
|
|
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):
|
2019-12-16 13:07:48 +01:00
|
|
|
feature_callback(set_feature)
|
2019-10-15 01:38:02 +02:00
|
|
|
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
|
|
|
|
|
|
2019-10-15 22:45:48 +02:00
|
|
|
if pudc_b_tile_site is not None:
|
|
|
|
|
assembler.set_feature_callback(check_for_pudc_b)
|
2019-10-15 01:38:02 +02:00
|
|
|
|
2019-02-14 17:27:43 +01:00
|
|
|
extra_features = []
|
2019-02-13 02:37:30 +01:00
|
|
|
if roi:
|
|
|
|
|
with open(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))
|
|
|
|
|
|
2019-02-14 17:27:43 +01:00
|
|
|
if 'required_features' in roi_j:
|
2019-12-16 10:53:17 +01:00
|
|
|
extra_features = list(
|
|
|
|
|
fasm.parse_fasm_string('\n'.join(roi_j['required_features'])))
|
2019-02-13 02:37:30 +01:00
|
|
|
|
2019-12-10 13:20:44 +01:00
|
|
|
# Get required extra features for the part
|
2019-12-10 13:49:51 +01:00
|
|
|
required_features = db.get_required_fasm_features(part)
|
2019-12-16 10:53:17 +01:00
|
|
|
extra_features += list(
|
|
|
|
|
fasm.parse_fasm_string('\n'.join(required_features)))
|
2019-12-09 13:40:05 +01:00
|
|
|
|
2019-02-13 02:37:30 +01:00
|
|
|
assembler.parse_fasm_filename(filename_in, extra_features=extra_features)
|
2019-10-15 01:38:02 +02:00
|
|
|
|
2019-10-15 22:45:48 +02:00
|
|
|
if emit_pudc_b_pullup and not pudc_b_in_use and pudc_b_tile_site is not None:
|
2019-10-15 01:38:02 +02:00
|
|
|
# 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("""
|
2020-05-07 15:51:55 +02:00
|
|
|
{tile}.{site}.LVCMOS12_LVCMOS15_LVCMOS18_LVCMOS25_LVCMOS33_LVTTL_SSTL135_SSTL15.IN_ONLY
|
2019-10-15 01:38:02 +02:00
|
|
|
{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))
|
|
|
|
|
|
2019-12-10 17:29:28 +01:00
|
|
|
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.
|
2019-12-16 13:40:35 +01:00
|
|
|
stepdown_tags = defaultdict(lambda: set())
|
2019-12-10 17:29:28 +01:00
|
|
|
stepdown_banks = set()
|
|
|
|
|
used_iob_sites = set()
|
|
|
|
|
|
|
|
|
|
for set_feature in set_features:
|
2019-12-16 13:40:35 +01:00
|
|
|
if set_feature.value == 0:
|
|
|
|
|
continue
|
|
|
|
|
|
2019-12-10 17:29:28 +01:00
|
|
|
feature = set_feature.feature
|
2019-12-11 16:13:07 +01:00
|
|
|
parts = feature.split(".")
|
|
|
|
|
if len(parts) >= 3:
|
|
|
|
|
tile, site, tag = feature.split(".", maxsplit=2)
|
|
|
|
|
if "IOB33" in tile:
|
|
|
|
|
used_iob_sites.add((
|
|
|
|
|
tile,
|
|
|
|
|
site,
|
|
|
|
|
))
|
2019-12-16 13:40:35 +01:00
|
|
|
|
|
|
|
|
# Store STEPDOWN related tags.
|
2019-12-11 16:13:07 +01:00
|
|
|
if "STEPDOWN" in tag:
|
2019-12-16 13:40:35 +01:00
|
|
|
bank = tile_to_bank[tile]
|
|
|
|
|
stepdown_banks.add(bank)
|
|
|
|
|
stepdown_tags[bank].add(tag)
|
2019-12-10 17:29:28 +01:00
|
|
|
|
2019-12-16 13:40:35 +01:00
|
|
|
# Set the feature for unused IOBs, loop over all banks which were
|
|
|
|
|
# observed to have the STEPDOWN feature set.
|
2019-12-10 17:29:28 +01:00
|
|
|
missing_features = []
|
|
|
|
|
|
|
|
|
|
for bank in stepdown_banks:
|
|
|
|
|
for tile in bank_to_tile[bank]:
|
|
|
|
|
|
2019-12-16 13:40:35 +01:00
|
|
|
# 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)
|
2019-12-10 17:29:28 +01:00
|
|
|
|
|
|
|
|
if missing_features:
|
|
|
|
|
raise fasm_assembler.FasmLookupError('\n'.join(missing_features))
|
|
|
|
|
|
2018-10-19 17:33:31 +02:00
|
|
|
frames = assembler.get_frames(sparse=sparse)
|
2018-01-17 23:39:37 +01:00
|
|
|
|
|
|
|
|
if debug:
|
|
|
|
|
dump_frames_sparse(frames)
|
|
|
|
|
|
|
|
|
|
dump_frm(f_out, frames)
|
|
|
|
|
|
|
|
|
|
|
2018-10-19 17:33:31 +02:00
|
|
|
def main():
|
2018-01-17 23:39:37 +01:00
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
description=
|
|
|
|
|
'Convert FPGA configuration description ("FPGA assembly") into binary frame equivalent'
|
|
|
|
|
)
|
|
|
|
|
|
2020-01-15 16:55:12 +01:00
|
|
|
util.db_root_arg(parser)
|
|
|
|
|
util.part_arg(parser)
|
2018-01-17 23:39:37 +01:00
|
|
|
parser.add_argument(
|
|
|
|
|
'--sparse', action='store_true', help="Don't zero fill all frames")
|
2019-02-13 02:37:30 +01:00
|
|
|
parser.add_argument(
|
|
|
|
|
'--roi',
|
|
|
|
|
help="ROI design.json file defining which tiles are within the ROI.")
|
2019-10-15 01:38:02 +02:00
|
|
|
parser.add_argument(
|
|
|
|
|
'--emit_pudc_b_pullup',
|
|
|
|
|
help="Emit an IBUF and PULLUP on the PUDC_B pin if unused",
|
|
|
|
|
action='store_true')
|
2018-01-17 23:39:37 +01:00
|
|
|
parser.add_argument(
|
|
|
|
|
'--debug', action='store_true', help="Print debug dump")
|
2018-10-20 01:19:22 +02:00
|
|
|
parser.add_argument('fn_in', help='Input FPGA assembly (.fasm) file')
|
2018-01-18 21:42:45 +01:00
|
|
|
parser.add_argument(
|
|
|
|
|
'fn_out',
|
|
|
|
|
default='/dev/stdout',
|
|
|
|
|
nargs='?',
|
|
|
|
|
help='Output FPGA frame (.frm) file')
|
2018-01-17 23:39:37 +01:00
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
run(
|
2018-10-19 17:33:31 +02:00
|
|
|
db_root=args.db_root,
|
2019-12-10 13:49:51 +01:00
|
|
|
part=args.part,
|
2018-10-19 17:33:31 +02:00
|
|
|
filename_in=args.fn_in,
|
|
|
|
|
f_out=open(args.fn_out, 'w'),
|
2018-01-17 23:39:37 +01:00
|
|
|
sparse=args.sparse,
|
2019-02-13 02:37:30 +01:00
|
|
|
roi=args.roi,
|
2019-10-15 01:38:02 +02:00
|
|
|
debug=args.debug,
|
|
|
|
|
emit_pudc_b_pullup=args.emit_pudc_b_pullup)
|
2018-10-20 01:19:22 +02:00
|
|
|
|
2018-10-19 17:33:31 +02:00
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|