#!/usr/bin/env python3 import os import re import sys import json import collections class FASMSyntaxError(Exception): pass def parsebit(val): '''Return "!012_23" => (12, 23, False)''' isset = True # Default is 0. Skip explicit call outs if val[0] == '!': isset = False val = val[1:] # 28_05 => 28, 05 seg_word_column, word_bit_n = val.split('_') return int(seg_word_column), int(word_bit_n), isset ''' Loosely based on segprint function Maybe better to return as two distinct dictionaries? { 'tile.meh': { 'O5': [(11, 2, False), (12, 2, True)], 'O6': [(11, 2, True), (12, 2, False)], }, } ''' segbitsdb = dict() def get_database(segtype): if segtype in segbitsdb: return segbitsdb[segtype] segbitsdb[segtype] = {} def process(l): l = l.strip() # CLBLM_L.SLICEL_X1.ALUT.INIT[10] 29_14 parts = line.split() name = parts[0] bit_vals = parts[1:] # Assumption # only 1 bit => non-enumerated value if len(bit_vals) == 1: seg_word_column, word_bit_n, isset = parsebit(bit_vals[0]) if not isset: raise Exception( "Expect single bit DB entries to be set, got %s" % l) # Treat like an enumerated value with keys 0 or 1 segbitsdb[segtype][name] = { '0': [(seg_word_column, word_bit_n, 0)], '1': [(seg_word_column, word_bit_n, 1)], } else: # An enumerated value # Split the base name and selected key m = re.match(r'(.+)[.](.+)', name) name = m.group(1) key = m.group(2) # May or may not be the first key encountered bits_map = segbitsdb[segtype].setdefault(name, {}) bits_map[key] = [parsebit(x) for x in bit_vals] with open("%s/%s/segbits_%s.db" % (os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"), segtype), "r") as f: for line in f: process(line) with open("%s/%s/segbits_int_%s.db" % (os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"), segtype[-1]), "r") as f: for line in f: process(line) return segbitsdb[segtype] 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 run(f_in, f_out, sparse=False, debug=False): # address to array of 101 32 bit words frames = {} # Directives we've seen so far # Complain if there is a duplicate # Contains line number of last entry used_names = {} def frames_init(): '''Set all frames to 0''' for segj in grid['segments'].values(): seg_baseaddr, seg_word_base = segj['baseaddr'] seg_baseaddr = int(seg_baseaddr, 0) for coli in range(segj['frames']): frame_init(seg_baseaddr + coli) def frame_init(addr): '''Set given frame to 0''' if not addr in frames: frames[addr] = [0 for _i in range(101)] def frame_set(frame_addr, word_addr, bit_index): '''Set given bit in given frame address and word''' frames[frame_addr][word_addr] |= 1 << bit_index def frame_clear(frame_addr, word_addr, bit_index): '''Set given bit in given frame address and word''' frames[frame_addr][word_addr] &= 0xFFFFFFFF ^ (1 << bit_index) with open("%s/%s/tilegrid.json" % (os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE")), "r") as f: new_grid = json.load(f) # TODO: Migrate to new tilegrid format via library. grid = {'tiles': new_grid, 'segments': {}} for tile in grid['tiles'].values(): if 'segment' in tile: segment = tile['segment'] grid['segments'][segment] = { 'baseaddr': [ tile['baseaddr'], tile['offset'], ], 'type': tile['segment_type'], 'frames': tile['frames'], 'words': tile['words'], } if not sparse: # Initiaize bitstream to 0 frames_init() for line_number, l in enumerate(f_in, 1): # Comment # Remove all text including and after # i = l.rfind('#') if i >= 0: l = l[0:i] l = l.strip() # Ignore blank lines if not l: continue # tile.site.stuff value # INT_L_X10Y102.CENTER_INTER_L.IMUX_L1 EE2END0 # Optional value m = re.match(r'([a-zA-Z0-9_]+)[.]([a-zA-Z0-9_.\[\]]+)([ ](.+))?', l) if not m: raise FASMSyntaxError("Bad line: %s" % l) tile = m.group(1) name = m.group(2) value = m.group(4) used_name = (tile, name) old_line_number = used_names.get(used_name, None) if old_line_number: raise FASMSyntaxError( "Duplicate name lines %d and %d, second line: %s" % (old_line_number, line_number, l)) used_names[used_name] = line_number tilej = grid['tiles'][tile] seg = tilej['segment'] segj = grid['segments'][seg] seg_baseaddr, seg_word_base = segj['baseaddr'] seg_baseaddr = int(seg_baseaddr, 0) # Ensure that all frames exist for this segment # FIXME: type dependent for coli in range(segj['frames']): frame_init(seg_baseaddr + coli) def update_segbit(seg_word_column, word_bit_n, isset): '''Set or clear a single bit in a segment at the given word column and word bit position''' # Now we have the word column and word bit index # Combine with the segments relative frame position to fully get the position frame_addr = seg_baseaddr + seg_word_column # 2 words per segment word_addr = seg_word_base + word_bit_n // 32 bit_index = word_bit_n % 32 if isset: frame_set(frame_addr, word_addr, bit_index) else: frame_clear(frame_addr, word_addr, bit_index) # Now lets look up the bits we need frames for segdb = get_database(segj['type']) db_k = '%s.%s' % (tilej['type'], name) try: db_vals = segdb[db_k] except KeyError: raise FASMSyntaxError( "Segment DB %s, key %s not found from line '%s'" % (segj['type'], db_k, l)) from None if not value: # If its binary, allow omitted value default to 1 if tuple(sorted(db_vals.keys())) == ('0', '1'): value = '1' else: raise FASMSyntaxError( "Enumerable entry %s must have explicit value" % name) # Get the specific entry we need try: db_vals = db_vals[value] except KeyError: raise FASMSyntaxError( "Invalid entry %s. Valid entries are %s" % (value, db_vals.keys())) for seg_word_column, word_bit_n, isset in db_vals: update_segbit(seg_word_column, word_bit_n, isset) if debug: #dump_frames_verbose(frames) dump_frames_sparse(frames) dump_frm(f_out, frames) if __name__ == '__main__': import argparse parser = argparse.ArgumentParser( description= 'Convert FPGA configuration description ("FPGA assembly") into binary frame equivalent' ) parser.add_argument( '--sparse', action='store_true', help="Don't zero fill all frames") parser.add_argument( '--debug', action='store_true', help="Print debug dump") parser.add_argument( 'fn_in', default='/dev/stdin', nargs='?', 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( open(args.fn_in, 'r'), open(args.fn_out, 'w'), sparse=args.sparse, debug=args.debug)