#!/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:
        grid = json.load(f)

    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)

9c9e62138a9f82ba0887e89cd678d61abf61e268
<mask token>


class FASMSyntaxError(Exception):
    pass


def parsebit(val):
    """Return "!012_23" => (12, 23, False)"""
    isset = True
    if val[0] == '!':
        isset = False
        val = val[1:]
    seg_word_column, word_bit_n = val.split('_')
    return int(seg_word_column), int(word_bit_n), isset


<mask token>


def get_database(segtype):
    if segtype in segbitsdb:
        return segbitsdb[segtype]
    segbitsdb[segtype] = {}

    def process(l):
        l = l.strip()
        parts = line.split()
        name = parts[0]
        bit_vals = parts[1:]
        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)
            segbitsdb[segtype][name] = {'0': [(seg_word_column, word_bit_n,
                0)], '1': [(seg_word_column, word_bit_n, 1)]}
        else:
            m = re.match('(.+)[.](.+)', name)
            name = m.group(1)
            key = m.group(2)
            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]


<mask token>

<mask token>


class FASMSyntaxError(Exception):
    pass


def parsebit(val):
    """Return "!012_23" => (12, 23, False)"""
    isset = True
    if val[0] == '!':
        isset = False
        val = val[1:]
    seg_word_column, word_bit_n = val.split('_')
    return int(seg_word_column), int(word_bit_n), isset


<mask token>


def get_database(segtype):
    if segtype in segbitsdb:
        return segbitsdb[segtype]
    segbitsdb[segtype] = {}

    def process(l):
        l = l.strip()
        parts = line.split()
        name = parts[0]
        bit_vals = parts[1:]
        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)
            segbitsdb[segtype][name] = {'0': [(seg_word_column, word_bit_n,
                0)], '1': [(seg_word_column, word_bit_n, 1)]}
        else:
            m = re.match('(.+)[.](.+)', name)
            name = m.group(1)
            key = m.group(2)
            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]
        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):
    frames = {}
    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] &= 4294967295 ^ 1 << bit_index
    with open('%s/%s/tilegrid.json' % (os.getenv('XRAY_DATABASE_DIR'), os.
        getenv('XRAY_DATABASE')), 'r') as f:
        grid = json.load(f)
    if not sparse:
        frames_init()
    for line_number, l in enumerate(f_in, 1):
        i = l.rfind('#')
        if i >= 0:
            l = l[0:i]
        l = l.strip()
        if not l:
            continue
        m = re.match('([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)
        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"""
            frame_addr = seg_baseaddr + seg_word_column
            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)
        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 tuple(sorted(db_vals.keys())) == ('0', '1'):
                value = '1'
            else:
                raise FASMSyntaxError(
                    'Enumerable entry %s must have explicit value' % name)
        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_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)

<mask token>


class FASMSyntaxError(Exception):
    pass


def parsebit(val):
    """Return "!012_23" => (12, 23, False)"""
    isset = True
    if val[0] == '!':
        isset = False
        val = val[1:]
    seg_word_column, word_bit_n = val.split('_')
    return int(seg_word_column), int(word_bit_n), isset


<mask token>
segbitsdb = dict()


def get_database(segtype):
    if segtype in segbitsdb:
        return segbitsdb[segtype]
    segbitsdb[segtype] = {}

    def process(l):
        l = l.strip()
        parts = line.split()
        name = parts[0]
        bit_vals = parts[1:]
        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)
            segbitsdb[segtype][name] = {'0': [(seg_word_column, word_bit_n,
                0)], '1': [(seg_word_column, word_bit_n, 1)]}
        else:
            m = re.match('(.+)[.](.+)', name)
            name = m.group(1)
            key = m.group(2)
            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]
        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):
    frames = {}
    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] &= 4294967295 ^ 1 << bit_index
    with open('%s/%s/tilegrid.json' % (os.getenv('XRAY_DATABASE_DIR'), os.
        getenv('XRAY_DATABASE')), 'r') as f:
        grid = json.load(f)
    if not sparse:
        frames_init()
    for line_number, l in enumerate(f_in, 1):
        i = l.rfind('#')
        if i >= 0:
            l = l[0:i]
        l = l.strip()
        if not l:
            continue
        m = re.match('([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)
        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"""
            frame_addr = seg_baseaddr + seg_word_column
            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)
        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 tuple(sorted(db_vals.keys())) == ('0', '1'):
                value = '1'
            else:
                raise FASMSyntaxError(
                    'Enumerable entry %s must have explicit value' % name)
        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_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)

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
    if val[0] == '!':
        isset = False
        val = val[1:]
    seg_word_column, word_bit_n = val.split('_')
    return int(seg_word_column), int(word_bit_n), isset


<mask token>
segbitsdb = dict()


def get_database(segtype):
    if segtype in segbitsdb:
        return segbitsdb[segtype]
    segbitsdb[segtype] = {}

    def process(l):
        l = l.strip()
        parts = line.split()
        name = parts[0]
        bit_vals = parts[1:]
        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)
            segbitsdb[segtype][name] = {'0': [(seg_word_column, word_bit_n,
                0)], '1': [(seg_word_column, word_bit_n, 1)]}
        else:
            m = re.match('(.+)[.](.+)', name)
            name = m.group(1)
            key = m.group(2)
            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]
        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):
    frames = {}
    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] &= 4294967295 ^ 1 << bit_index
    with open('%s/%s/tilegrid.json' % (os.getenv('XRAY_DATABASE_DIR'), os.
        getenv('XRAY_DATABASE')), 'r') as f:
        grid = json.load(f)
    if not sparse:
        frames_init()
    for line_number, l in enumerate(f_in, 1):
        i = l.rfind('#')
        if i >= 0:
            l = l[0:i]
        l = l.strip()
        if not l:
            continue
        m = re.match('([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)
        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"""
            frame_addr = seg_baseaddr + seg_word_column
            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)
        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 tuple(sorted(db_vals.keys())) == ('0', '1'):
                value = '1'
            else:
                raise FASMSyntaxError(
                    'Enumerable entry %s must have explicit value' % name)
        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_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)

#!/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:
        grid = json.load(f)

    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)
