#!/usr/pkg/bin/python3.13
# Copyright (C) 2017  Roland Lutz
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import getopt, os, re, sys
import icebox as icebox
from icebox import re_match_cached, re_sub_cached


## Get the tile-local name of a net.
#
# \param x, y    coordinates of the tile to which the net belongs
# \param fw, fh  width and height of the tile fabric (excluding I/O tiles)
# \param net     global net name
#
# \return the tile-local name of the net if it is a span wire,
#         otherwise the unmodified net name

def untranslate_netname(x, y, fw, fh, net):
    def index(g, i, group_size):
        if g % 2 == 1:
            i = i + 1 - (i % 2) * 2
        return g * group_size + i

    match = re_match_cached(r'span4_y(\d+)_g(\d+)_(\d+)$', net)
    if match is not None:
        my = int(match.group(1))
        mw = int(match.group(2))
        mi = int(match.group(3))
        assert my == y
        assert mi >= 0 and mi < 12

        mg = x - mw + 4
        assert mg >= 0 and mg <= 4

        if x == 0:
            return 'span4_horz_%d' % index(mg, mi, 12)
        if x == fw + 1:
            return 'span4_horz_%d' % index(mg - 1, mi, 12)

        if mg == 4:
            return 'sp4_h_l_%d' % index(mg - 1, mi, 12)
        else:
            return 'sp4_h_r_%d' % index(mg, mi, 12)

    match = re_match_cached(r'span4_x(\d+)_g(\d+)_(\d+)$', net)
    if match is not None:
        mx = int(match.group(1))
        mw = int(match.group(2))
        mi = int(match.group(3))
        assert mi >= 0 and mi < 12
        mg = mw - y
        assert mg >= 0 and mg <= 4

        if y == 0:
            return 'span4_vert_%d' % index(mg - 1, mi, 12)
        if y == fh + 1:
            return 'span4_vert_%d' % index(mg, mi, 12)

        if mx == x + 1:
            assert mg < 4
            return 'sp4_r_v_b_%d' % index(mg, mi, 12)

        assert mx == x
        if mg == 4:
            return 'sp4_v_t_%d' % index(mg - 1, mi, 12)
        else:
            return 'sp4_v_b_%d' % index(mg, mi, 12)

    match = re_match_cached(r'dummy_y(\d+)_g(\d+)_(\d+)$', net)
    if match is not None:
        my = int(match.group(1))
        mw = int(match.group(2))
        mi = int(match.group(3))
        assert my == y

        mg = mw
        assert mg >= 0 and mg < 4

        return 'sp4_r_v_b_%d' % index(mg, mi, 12)

    match = re_match_cached(r'span12_y(\d+)_g(\d+)_(\d+)$', net)
    if match is not None:
        my = int(match.group(1))
        mw = int(match.group(2))
        mi = int(match.group(3))
        assert my == y
        assert mi >= 0 and mi < 2

        mg = x - mw + 12
        assert mg >= 0 and mg <= 12

        if x == 0:
            return 'span12_horz_%d' % index(mg, mi, 2)
        if x == fw + 1:
            return 'span12_horz_%d' % index(mg - 1, mi, 2)

        if mg == 12:
            return 'sp12_h_l_%d' % index(mg - 1, mi, 2)
        else:
            return 'sp12_h_r_%d' % index(mg, mi, 2)

    match = re_match_cached(r'span12_x(\d+)_g(\d+)_(\d+)$', net)
    if match is not None:
        mx = int(match.group(1))
        mw = int(match.group(2))
        mi = int(match.group(3))
        assert mx == x
        assert mi >= 0 and mi < 2

        mg = mw - y
        assert mg >= 0 and mg <= 12

        if y == 0:
            return 'span12_vert_%d' % index(mg - 1, mi, 2)
        if y == fh + 1:
            return 'span12_vert_%d' % index(mg, mi, 2)

        if mg == 12:
            return 'sp12_v_t_%d' % index(mg - 1, mi, 2)
        else:
            return 'sp12_v_b_%d' % index(mg, mi, 2)

    match = re_match_cached(r'span4_bottom_g(\d+)_(\d+)$', net)
    if match is not None:
        mw = int(match.group(1))
        mi = int(match.group(2))
        assert mi >= 0 and mi < 4

        if x == 0:
            assert y != 0
            mg = -y + 5 - mw
            assert y + mg - 3 < 0
            return 'span4_vert_b_%d' % (mg * 4 + mi)
        else:
            assert y == 0
            mg = x + 4 - mw
            assert x - mg + 1 >= 0

            if mg == 4:
                return 'span4_horz_l_%d' % (mg * 4 + mi - 4)
            else:
                assert fw - x + mg - 4 >= 0
                return 'span4_horz_r_%d' % (mg * 4 + mi)

    match = re_match_cached(r'span4_left_g(\d+)_(\d+)$', net)
    if match is not None:
        mw = int(match.group(1))
        mi = int(match.group(2))
        assert mi >= 0 and mi < 4

        if y == 0:
            assert x != 0
            mg = mw + x - 1
            assert x - mg + 1 < 0

            if mg == 4:
                return 'span4_horz_l_%d' % (mg * 4 + mi - 4)
            else:
                assert fw - x + mg - 4 >= 0
                return 'span4_horz_r_%d' % (mg * 4 + mi)

        else:
            assert x == 0
            mg = mw - y
            assert fh - y - mg >= 0

            if mg == 4:
                return 'span4_vert_t_%d' % (mg * 4 + mi - 4)
            else:
                assert y + mg - 3 >= 0
                return 'span4_vert_b_%d' % (mg * 4 + mi)

    match = re_match_cached(r'span4_right_g(\d+)_(\d+)$', net)
    if match is not None:
        mw = int(match.group(1))
        mi = int(match.group(2))
        assert mi >= 0 and mi < 4

        if y == fh + 1:
            mg = mw - fh - fw + x - 1
            assert x - mg - 1 >= 0
            assert x - mg + 1 >= fw
            return 'span4_horz_r_%d' % (mg * 4 + mi)

        assert x == fw + 1
        mg = mw - y

        if mg == 4:
            assert y + mg - 1 < fh + 2
            return 'span4_vert_t_%d' % (mg * 4 + mi - 4)
        else:
            assert y + mg - 5 >= 0
            assert y + mg < fh + 3
            return 'span4_vert_b_%d' % (mg * 4 + mi)

    match = re_match_cached(r'span4_top_g(\d+)_(\d+)$', net)
    if match is not None:
        mw = int(match.group(1))
        mi = int(match.group(2))
        assert mi >= 0 and mi < 4

        if x == fw + 1:
            assert y != 0
            mg = fw + fh + 5 - y - mw
            assert y + mg >= fh + 3

            if mg == 4:
                return 'span4_vert_t_%d' % (mg * 4 + mi - 4)
            else:
                assert y + mg - 5 >= 0
                return 'span4_vert_b_%d' % (mg * 4 + mi)

        assert y != 0
        mg = x + 4 - mw
        assert x - mg - 1 >= 0

        if mg == 4:
            return 'span4_horz_l_%d' % (mg * 4 + mi - 4)
        else:
            assert x - mg + 1 < fw
            return 'span4_horz_r_%d' % (mg * 4 + mi)

    match = re_match_cached(r'span4_bottomright(\d+)_(\d+)$', net)
    if match is not None:
        mw = int(match.group(1))
        mi = int(match.group(2))
        assert mw % 2 == 0
        assert mi >= 0 and mi < 4

        if y == 0:
            assert x != 0
            mg = mw // 2 - fw + x - 1
            assert fw - x + mg - 4 < 0
            return 'span4_horz_r_%d' % (mg * 4 + mi)
        else:
            assert x == fw + 1
            mg = mw // 2 - y
            assert y + mg - 5 < 0
            return 'span4_vert_b_%d' % (mg * 4 + mi)

    match = re_match_cached(r'span4_topleft(\d+)_(\d+)$', net)
    if match is not None:
        mw = int(match.group(1))
        mi = int(match.group(2))
        assert mw % 2 == 0
        assert mi >= 0 and mi < 4

        if x == 0:
            assert y != 0
            mg = fh + 5 - y - mw // 2
            assert fh - y - mg < 0

            if mg == 4:
                return 'span4_vert_t_%d' % (mg * 4 + mi - 4)
            else:
                return 'span4_vert_b_%d' % (mg * 4 + mi)
        else:
            assert y == fh + 1
            mg = x + 4 - mw // 2
            assert x - mg - 1 < 0

            if mg == 4:
                return 'span4_horz_l_%d' % (mg * 4 + mi - 4)
            else:
                return 'span4_horz_r_%d' % (mg * 4 + mi)

    return net

## Check if the name of a destination net is the human-readable form
## of the \c fabout net of IO tile <tt>(x, y)</tt>.
#
# \return \c 'fabout' if it is the \c fabout net, otherwise the
#         unchanged net name

def revert_to_fabout(ic, x, y, net):
    if net.startswith('glb_netwk_'):
        for gx, gy, gi in ic.gbufin_db():
            if net == 'glb_netwk_%d' % gi and (x, y) == (gx, gy):
                return 'fabout'
        raise ParseError("{} is a global network, but not at an expected location {} {}".format(net, gx, gy))

    return net


EXPR_AND, EXPR_XOR, EXPR_OR, EXPR_TERN, EXPR_NOT, EXPR_ZERO, EXPR_ONE = range(7)

## Evaluate a list representation of a logic expression for given
## input values.
#
# This is a helper function for \ref logic_expression_to_lut.
#
# \param expr  list representation of a logic expression (see below)
# \param args  list of boolean values representing the input values
#
# \result \c False or \c True, depending on the expression and arguments
#
# Expression                             | Result
# ---------------------------------------+----------------------------------
# <tt>i</tt>                             | value of argument \a i
# <tt>(EXPR_AND, [expr, ...])</tt>       | AND operation of all expressions
# <tt>(EXPR_XOR, [expr, ...])</tt>       | XOR operation of all expressions
# <tt>(EXPR_OR, [expr, ...])</tt>        | OR operation of all expressions
# <tt>(EXPR_TERN, ex_a, ex_b, ex_c)</tt> | result of \c ex_b if \c ex_a
#                                        |   evaluates to \c True, otherwise
#                                        |   result of \c ex_c
# <tt>(EXPR_NOT, expr)</tt>              | negated result of \a expr
# <tt>(EXPR_ZERO, )</tt>                 | \c False
# <tt>(EXPR_ONE, )</tt>                  | \c True

def evaluate(expr, args):
    if type(expr) == int:
        return args[expr]

    op = expr[0]

    if op == EXPR_AND:
        assert len(expr) == 2
        for o in expr[1]:
            if not evaluate(o, args):
                return False
        return True

    if op == EXPR_XOR:
        assert len(expr) == 2
        result = False
        for o in expr[1]:
            if evaluate(o, args):
                result = not result
        return result

    if op == EXPR_OR:
        assert len(expr) == 2
        for o in expr[1]:
            if evaluate(o, args):
                return True
        return False

    if op == EXPR_TERN:
        assert len(expr) == 4
        if evaluate(expr[1], args):
            return evaluate(expr[2], args)
        else:
            return evaluate(expr[3], args)

    if op == EXPR_NOT:
        assert len(expr) == 2
        return not evaluate(expr[1], args)

    if op == EXPR_ZERO:
        assert len(expr) == 1
        return False

    if op == EXPR_ONE:
        assert len(expr) == 1
        return True

    assert False  # unknown operator

## Convert a logic expression to a LUT string.
#
# \param lut   string containing a human-readable logic expression
# \param args  list of N strings containing the names of the arguments
#
# \return a string of 2^N `0' or `1' characters representing the logic
#         of an Nx1 look-up table equivalent to the logic expression
#
# Example: logic_expression_to_lut('a & b & !c', ['a', 'b', 'c']) -> '00010000'

def logic_expression_to_lut(s, args):
    # make sure argument names are unique
    assert len(set(args)) == len(args)

    stack = [[None, None, None], [[], None, None]]
    stack[0][2] = l = []; stack[1][0].append((EXPR_OR, l))
    stack[0][1] = l = []; stack[0][2].append((EXPR_XOR, l))
    stack[0][0] = l = []; stack[0][1].append((EXPR_AND, l))
    expect_expr = True
    negate_count = 0

    i = 0
    while i < len(s):
        if s[i] == ' ':
            pass
        elif s[i] == '!':
            assert expect_expr
            negate_count += 1
        elif s[i] == '&':
            assert not expect_expr

            expect_expr = True
            negate_count = 0
        elif s[i] == '^':
            assert not expect_expr

            stack[0][0] = l = []; stack[0][1].append((EXPR_AND, l))

            expect_expr = True
            negate_count = 0
        elif s[i] == '|':
            assert not expect_expr

            stack[0][1] = l = []; stack[0][2].append((EXPR_XOR, l))
            stack[0][0] = l = []; stack[0][1].append((EXPR_AND, l))

            expect_expr = True
            negate_count = 0
        elif s[i] == '?':
            assert not expect_expr
            assert stack[1][0][-1][0] == EXPR_OR
            stack[1][0][-1] = (EXPR_TERN, stack[1][0][-1], (EXPR_OR, []),
                                                           (EXPR_OR, []))
            stack[0][2] = l = stack[1][0][-1][2][1]
            stack[0][1] = l = []; stack[0][2].append((EXPR_XOR, l))
            stack[0][0] = l = []; stack[0][1].append((EXPR_AND, l))

            expect_expr = True
            negate_count = 0
        elif s[i] == ':':
            assert not expect_expr
            assert stack[1][0][-1][0] == EXPR_TERN
            stack[0][2] = l = stack[1][0][-1][3][1]
            stack[0][1] = l = []; stack[0][2].append((EXPR_XOR, l))
            stack[0][0] = l = []; stack[0][1].append((EXPR_AND, l))

            expect_expr = True
            negate_count = 0
        elif s[i] == '(':
            assert expect_expr

            stack.insert(0, [None, None, None])

            stack[0][2] = l = []
            if negate_count % 2:
                stack[1][0].append((EXPR_NOT, (EXPR_OR, l)))
            else:
                stack[1][0].append((EXPR_OR, l))

            stack[0][1] = l = []; stack[0][2].append((EXPR_XOR, l))
            stack[0][0] = l = []; stack[0][1].append((EXPR_AND, l))

            expect_expr = True
            negate_count = 0

        elif s[i] == ')':
            assert not expect_expr
            stack.pop(0)

        elif s[i] == '0':
            assert expect_expr
            if negate_count % 2:
                stack[0][0].append((EXPR_ONE, ))
            else:
                stack[0][0].append((EXPR_ZERO, ))

            expect_expr = False
            negate_count = None

        elif s[i] == '1':
            assert expect_expr
            if negate_count % 2:
                stack[0][0].append((EXPR_ZERO, ))
            else:
                stack[0][0].append((EXPR_ONE, ))

            expect_expr = False
            negate_count = None

        else:
            assert expect_expr

            found = None
            for j, arg in enumerate(args):
                if s.startswith(arg, i):
                    found = j
                    i += len(arg)
                    break
            assert found is not None

            if negate_count % 2:
                stack[0][0].append((EXPR_NOT, found))
            else:
                stack[0][0].append(found)

            expect_expr = False
            negate_count = None
            continue

        i += 1

    assert len(stack) == 2
    return ''.join('1' if evaluate(stack[1][0][0],
                                   tuple(i & (1 << j) != 0
                                         for j in range(len(args)))) else '0'
                   for i in range(1 << len(args)))

def parse_verilog_bitvector_to_bits(in_str):
    #replace x with 0
    in_str = re_sub_cached('[xX]', '0', in_str)

    m = re_match_cached("([0-9]+)'([hdob])([0-9a-fA-F]+)", in_str)
    if m:
        num_bits = int(m.group(1))
        prefix = m.group(2)
        val_str = m.group(3)

        if prefix == 'h':
            val = eval('0x' + val_str)
        elif prefix == 'd':
            val = eval(val_str.lstrip('0'))
        elif prefix == 'o' or prefix =='b':
            val = eval('0' + prefix + val_str)

        if val.bit_length() > num_bits:
            raise ParseError("Number of bits({}) given don't match expected ({})"
                             .format(val.bit_length(), num_bits))
    else:
        val = eval('0x' + in_str)
        num_bits = len(in_str) * 4

    bit_str = bin(val)[2:]
    # zero pad
    nz = num_bits - len(bit_str)
    bit_vec = nz*['0'] + list(bit_str)
    return bit_vec

def parse_verilog_bitvector_to_hex(in_str):
    if type(in_str) == str:
        bits = parse_verilog_bitvector_to_bits(in_str)
    else:
        bits = in_str
    # pad to 4
    bits = ((4-len(bits)) % 4) * ['0'] + bits
    res = ''.join([hex(eval('0b' + ''.join(bits[ii:ii+4])))[2:] for ii in range(0, len(bits), 4)])
    return res

class ParseError(Exception):
    pass

def parse_bool(s):
    if s == 'on':
        return True
    if s == 'off':
        return False
    raise ParseError("Unable to parse '{}' as boolean".format(s))

class Main:
    def __init__(self):
        self.ic = None
        self.device = None
        #self.coldboot = None
        self.warmboot = None
        self.tiles = {}

    def read(self, fields):
        if fields[0] == 'device' and len(fields) == 4 \
                and len(fields[1]) >= 2 and fields[1][0] == '"' \
                                        and fields[1][-1] == '"' \
                and self.ic is None and self.device is None:
            self.device = fields[1][1:-1].lower()
            if self.device.startswith('lp') or self.device.startswith('hx'):
                self.device = self.device[2:]
            if self.device.startswith('1k'):
                self.ic = icebox.iceconfig()
                self.ic.setup_empty_1k()
            elif self.device.startswith('5k'):
                self.ic = icebox.iceconfig()
                self.ic.setup_empty_5k()
            elif self.device.startswith('8k'):
                self.ic = icebox.iceconfig()
                self.ic.setup_empty_8k()
            elif self.device.startswith('384'):
                self.ic = icebox.iceconfig()
                self.ic.setup_empty_384()
            else:
                raise ParseError("Unknown device {}".format(self.device))

        #elif fields[0] == 'coldboot' and fields[1] == '=' \
        #        and self.coldboot is None:
        #    # parsed but ignored (can't be represented in IceStorm .asc format)
        #    self.coldboot = parse_bool(fields[2])
        elif fields[0] == 'warmboot' and fields[1] == '=' \
                and self.warmboot is None:
            # parsed but ignored (can't be represented in IceStorm .asc format)
            self.warmboot = parse_bool(fields[2])
        else:
            raise ParseError("Unknown preamble directive {}".format(fields[0]))

    def new_block(self, fields):
        if len(fields) != 3:
            raise ParseError("Expect 3 fields for top block. Received: {}".format(fields))
        x = int(fields[1])
        y = int(fields[2])
        if (x, y) in self.tiles:
            return self.tiles[x, y]
        if fields[0] == 'logic_tile':
            if (x, y) not in self.ic.logic_tiles:
                raise ParseError("{} position({},{}) not in defined list for device".format(fields[0], x, y))
            tile = LogicTile(self.ic, x, y)
        elif fields[0] == 'ramb_tile':
            if (x, y) not in self.ic.ramb_tiles:
                raise ParseError("{} position({},{}) not in defined list for device".format(fields[0], x, y))
            tile = RAMBTile(self.ic, x, y)
        elif fields[0] == 'ramt_tile':
            if (x, y) not in self.ic.ramt_tiles:
                raise ParseError("{} position({},{}) not in defined list for device".format(fields[0], x, y))
            tile = RAMTTile(self.ic, x, y)
        elif fields[0] == 'io_tile':
            if (x, y) not in self.ic.io_tiles:
                raise ParseError("{} position({},{}) not in defined list for device".format(fields[0], x, y))
            tile = IOTile(self.ic, x, y)
        else:
            raise ParseError("Unknown tile type {}".format(fields[0]))
        self.tiles[x, y] = tile
        return tile

    def writeout(self):
        if self.ic is None:
            raise ParseError("iceconfig not set")

        # fix up IE/REN bits
        unused_ieren = set()

        for x in range(1, self.ic.max_x):
            unused_ieren.add((x, 0, 0))
            unused_ieren.add((x, 0, 1))
            unused_ieren.add((x, self.ic.max_y, 0))
            unused_ieren.add((x, self.ic.max_y, 1))

        for y in range(1, self.ic.max_y):
            unused_ieren.add((0, y, 0))
            unused_ieren.add((0, y, 1))
            unused_ieren.add((self.ic.max_x, y, 0))
            unused_ieren.add((self.ic.max_x, y, 1))

        for x0, y0, b0, x1, y1, b1 in self.ic.ieren_db():
            if (x0, y0) in self.tiles:
                io_tile = self.tiles[x0, y0]
            else:
                io_tile = IOTile(self.ic, x0, y0)
                self.tiles[x0, y0] = io_tile
            if io_tile.blocks[b0] is not None:
                io_block = io_tile.blocks[b0]
            else:
                io_block = IOBlock(io_tile, b0)
                io_tile.blocks[b0] = io_block

            if (x1, y1) in self.tiles:
                ieren_tile = self.tiles[x1, y1]
            else:
                ieren_tile = IOTile(self.ic, x1, y1)
                self.tiles[x1, y1] = ieren_tile

            if io_block.enable_input != (self.ic.device == '1k'):
                ieren_tile.apply_directive('IoCtrl', 'IE_%d' % b1)
            if io_block.disable_pull_up:
                ieren_tile.apply_directive('IoCtrl', 'REN_%d' % b1)

            unused_ieren.remove((x1, y1, b1))

        if self.ic.device == '1k':
            for x1, y1, b1 in unused_ieren:
                if (x1, y1) in self.tiles:
                    ieren_tile = self.tiles[x1, y1]
                else:
                    ieren_tile = IOTile(self.ic, x1, y1)
                    self.tiles[x1, y1] = ieren_tile
                ieren_tile.apply_directive('IoCtrl', 'IE_%d' % b1)

        # fix up RAMB power-up bits

        for x, y in self.ic.ramb_tiles:
            if (x, y) in self.tiles:
                tile = self.tiles[x, y]
            else:
                tile = RAMBTile(self.ic, x, y)
                self.tiles[x, y] = tile

            if tile.power_up != (self.ic.device == '1k'):
                tile.apply_directive('RamConfig', 'PowerUp')

        # enable column buffers
        colbuf_db = self.ic.colbuf_db()
        for x, y in list(self.tiles):
            for src, dst in self.tiles[x, y].buffers + \
                            self.tiles[x, y].routings:
                if not src.startswith('glb_netwk_'):
                    continue
                driving_xy = [(src_x, src_y)
                              for src_x, src_y, dst_x, dst_y in colbuf_db
                              if dst_x == x and dst_y == y]
                assert len(driving_xy) == 1
                driving_xy, = driving_xy

                if driving_xy not in self.tiles:
                    if driving_xy in self.ic.logic_tiles:
                        tile = LogicTile(self.ic, *driving_xy)
                    elif driving_xy in self.ic.ramb_tiles:
                        tile = RAMBTile(self.ic, *driving_xy)
                    elif driving_xy in self.ic.ramt_tiles:
                        tile = RAMTTile(self.ic, *driving_xy)
                    elif driving_xy in self.ic.io_tiles:
                        tile = IOTile(self.ic, *driving_xy)
                    else:
                        assert False
                    self.tiles[driving_xy] = tile

                self.tiles[driving_xy].apply_directive('ColBufCtrl', src)

        self.ic.write_file('/dev/stdout')

class Tile:
    def __init__(self, ic, x, y):
        self.ic = ic
        self.x = x
        self.y = y
        self.data = ic.tile(x, y)
        self.db = ic.tile_db(x, y)

        self.bits_lookup = {}
        def add_entry(entry, bits):
            entry = tuple(entry)
            if entry in self.bits_lookup:
                if self.bits_lookup[entry] == bits:
                    logging.warn(
                        "{} {} - {} (adding) != {} (existing)".format(
                            (x, y), entry, bits, self.bits_lookup[entry]))
            self.bits_lookup[entry] = bits

        for bits, *entry in self.db:
            if not ic.tile_has_entry(x, y, (bits, *entry)):
                continue
            add_entry(entry, bits)

        self.symbols = {}
        self.buffers = []
        self.routings = []
        self.bits_set = set()
        self.bits_cleared = set()

    def __str__(self):
        return "{}({}, {}, {})".format(self.__class__.__name__, self.ic.device, self.x, self.y)

    def apply_directive(self, *fields):
        if fields not in self.bits_lookup:
            print("Possible matches:", file=sys.stderr)
            for bits, *entry in self.db:
                if entry[0] in ("routing", "buffer"):
                    if entry[1] == fields[1]:
                        print(" ", entry, bits, file=sys.stderr)
                    elif entry[1] == fields[2]:
                        print(" ", entry, bits, file=sys.stderr)
                    elif entry[2] == fields[2]:
                        print(" ", entry, bits, file=sys.stderr)
                    elif entry[2] == fields[1]:
                        print(" ", entry, bits, file=sys.stderr)
            raise ParseError("No bit pattern for {} in {}".format(fields, self))
        bits = self.bits_lookup[fields]
        self.set_bits(bits)

    def set_bits(self, bits):
        bits_set = set()
        bits_clear = set()

        for bit in bits:
            match = re_match_cached(r'(!?)B(\d+)\[(\d+)\]$', bit)
            if not match:
                raise ValueError("invalid bit description: %s" % bit)
            if match.group(1):
                bits_clear.add((int(match.group(2)), int(match.group(3))))
            else:
                bits_set.add((int(match.group(2)), int(match.group(3))))

        if set.intersection(bits_set, bits_clear):
            raise ValueError(
                "trying to set/clear the same bit(s) at once set:{} clear:{}".format(
                    bits_set, bits_clear))

        if set.intersection(bits_set, self.bits_cleared) or \
           set.intersection(bits_clear, self.bits_set):
               raise ParseError("""\
conflicting bits {}
 setting:{:<30} - current clear:{}
clearing:{:<30} - current set  :{}""".format(
                bits,
                str(bits_set), self.bits_cleared,
                str(bits_clear), self.bits_set))

        self.bits_set.update(bits_set)
        self.bits_cleared.update(bits_clear)

        for row, col in bits_set:
            assert row < len(self.data)
            assert col < len(self.data[row])
            self.data[row] = self.data[row][:col] + '1' + \
                             self.data[row][col + 1:]

    def read(self, fields):
        if len(fields) == 3 and fields[1] == '->':
            src = untranslate_netname(self.x, self.y,
                                      self.ic.max_x - 1,
                                      self.ic.max_y - 1, fields[0])
            dst = untranslate_netname(self.x, self.y,
                                      self.ic.max_x - 1,
                                      self.ic.max_y - 1, fields[2])
            dst = revert_to_fabout(self.ic, self.x, self.y, dst)
            if (src, dst) not in self.buffers:
                self.buffers.append((src, dst))
                self.apply_directive('buffer', src, dst)
        elif len(fields) == 3 and fields[1] == '~>':
            src = untranslate_netname(self.x, self.y,
                                      self.ic.max_x - 1,
                                      self.ic.max_y - 1, fields[0])
            dst = untranslate_netname(self.x, self.y,
                                      self.ic.max_x - 1,
                                      self.ic.max_y - 1, fields[2])
            dst = revert_to_fabout(self.ic, self.x, self.y, dst)
            if (src, dst) not in self.routings:
                self.routings.append((src, dst))
                self.apply_directive('routing', src, dst)
        elif len(fields) >= 5 and (fields[1] == '->' or fields[1] == '~>'):
            self.read(fields[:3])
            self.read(fields[2:])
        elif len(fields) == 3 and fields[1] == '.sym>':
            nn = untranslate_netname(self.x, self.y,
                                      self.ic.max_x - 1,
                                      self.ic.max_y - 1, fields[0])
            net = self.ic.get_net_number( (self.x, self.y, nn) )
            self.ic.symbols.setdefault(net, set()).add(fields[2])
        else:
            raise ParseError("Unknown Tile specification format")

    def new_block(self, fields):
        raise ParseError("Unexpected new block in {}".format(type(self).__name__))

class LogicTile(Tile):
    def __init__(self, ic, x, y):
        super().__init__(ic, x, y)
        self.cells = [None, None, None, None, None, None, None, None]
        self.neg_clk = False
        self.carry_in_set = False  # not in global bit list?!

    def read(self, fields):
        if fields == ['NegClk'] and not self.neg_clk:
            self.neg_clk = True
            self.apply_directive('NegClk')
        elif fields == ['CarryInSet'] and not self.carry_in_set:
            self.carry_in_set = True
            self.apply_directive('CarryInSet')
        else:
            super().read(fields)

    def new_block(self, fields):
        for i in range(8):
            if fields == ['lutff_%d' % i] and self.cells[i] is None:
                self.cells[i] = LogicCell(self, i)
                return self.cells[i]
        raise ParseError("Unexpected new block in {}".format(type(self).__name__))

class LogicCell:
    def __init__(self, tile, index):
        self.tile = tile
        self.index = index
        self.lut_bits = ['0'] * 16
        self.seq_bits = ['0'] * 4

    def read(self, fields):
        if fields[0] == 'lut' and len(fields) == 2:
            self.lut_bits = fields[1]
        elif fields[0] == 'out' and len(fields) >= 3 and fields[1] == '=':
            m = re_match_cached("([0-9]+)'b([01]+)", fields[2])
            if m:
                lut_bits = parse_verilog_bitvector_to_bits(fields[2])
                # Verilog 16'bXXXX is MSB first but the bitstream wants LSB.
                self.lut_bits = list(lut_bits[::-1])
            else:
                self.lut_bits = logic_expression_to_lut(
                    ' '.join(fields[2:]), ('in_0', 'in_1', 'in_2', 'in_3'))
        elif fields == ['enable_carry']:
            self.seq_bits[0] = '1'
        elif fields == ['enable_dff']:
            self.seq_bits[1] = '1'
        elif fields == ['set_noreset']:
            self.seq_bits[2] = '1'
        elif fields == ['async_setreset']:
            self.seq_bits[3] = '1'
        elif len(fields) > 3 and (fields[1] == '->' or fields[1] == '~>'):
            self.read(fields[:3])
            self.read(fields[2:])
            return
        elif len(fields) == 3 and (fields[1] == '->' or fields[1] == '~>'):
            prefix = 'lutff_%d/' % self.index

            # Strip prefix if it is given
            if fields[0].startswith(prefix):
                fields[0] = fields[0][len(prefix):]
            if fields[-1].startswith(prefix):
                fields[-1] = fields[-1][len(prefix):]

            if fields[0] == 'out':
                self.tile.read([prefix + fields[0]] + fields[1:])
            elif fields[-1].startswith('in_'):
                self.tile.read(fields[:-1] + [prefix + fields[-1]])
            else:
                self.tile.read(fields)
            return
        elif len(fields) == 3  and fields[1] == '.sym>':
            nn = untranslate_netname(self.tile.x, self.tile.y,
                                      self.tile.ic.max_x - 1,
                                      self.tile.ic.max_y - 1, fields[0])
            net = self.tile.ic.get_net_number( (self.tile.x, self.tile.y, nn) )
            self.tile.ic.symbols.setdefault(net, set()).add(fields[2])


        bits = ''.join([
            self.lut_bits[15], self.lut_bits[12],
            self.lut_bits[11], self.lut_bits[ 8],
            self.lut_bits[ 0], self.lut_bits[ 3],
            self.lut_bits[ 4], self.lut_bits[ 7],
            self.seq_bits[ 0], self.seq_bits[ 1],
            self.lut_bits[14], self.lut_bits[13],
            self.lut_bits[10], self.lut_bits[ 9],
            self.lut_bits[ 1], self.lut_bits[ 2],
            self.lut_bits[ 5], self.lut_bits[ 6],
            self.seq_bits[ 2], self.seq_bits[ 3]
        ])
        self.tile.data[self.index * 2] = \
            self.tile.data[self.index * 2][:36] + bits[:10] + \
            self.tile.data[self.index * 2][46:]
        self.tile.data[self.index * 2 + 1] = \
            self.tile.data[self.index * 2 + 1][:36] + bits[10:] + \
            self.tile.data[self.index * 2 + 1][46:]

    def new_block(self, fields):
        raise ParseError("Unexpected new block in {}".format(type(self).__name__))

class RAMData:
    def __init__(self, data):
        self.data = data

    def read(self, fields):
        if len(fields) == 1:
            self.data.append(parse_verilog_bitvector_to_hex(fields[0]))
        else:
            raise ParseError("Unexpected format in {}".format(type(self).__name__))

    def new_block(self, fields):
        raise ParseError("Unexpected new block in {}".format(type(self).__name__))

class RAMBTile(Tile):
    def __init__(self, ic, x, y):
        super().__init__(ic, x, y)
        self.power_up = False

    def read(self, fields):
        if fields == ['power_up'] and not self.power_up:
            self.power_up = True
        else:
            super().read(fields)

    def new_block(self, fields):
        if fields == ['data'] and (self.x, self.y) not in self.ic.ram_data:
            self.ic.ram_data[self.x, self.y] = data = []
            return RAMData(data)
        raise ParseError("Unexpected new block in {}".format(type(self).__name__))

class RAMTTile(Tile):
    def __init__(self, ic, x, y):
        super().__init__(ic, x, y)

    def read(self, fields):
        if fields == ['NegClk'] or fields[0] == 'RamConfig':
            self.apply_directive(*fields)  # TODO
        else:
            super().read(fields)

class IOTile(Tile):
    def __init__(self, ic, x, y):
        super().__init__(ic, x, y)
        self.blocks = [None, None]

    def read(self, fields):
        if len(fields) == 2 and fields[0] == 'PLL':
            self.apply_directive(*fields)  # TODO
        else:
            super().read(fields)

    def new_block(self, fields):
        if fields == ['io_0'] and self.blocks[0] is None:
            self.blocks[0] = IOBlock(self, 0)
            return self.blocks[0]
        if fields == ['io_1'] and self.blocks[1] is None:
            self.blocks[1] = IOBlock(self, 1)
            return self.blocks[1]
        raise ParseError("Unexpected new block in {}".format(type(self).__name__))

class IOBlock:
    def __init__(self, tile, index):
        self.tile = tile
        self.index = index
        self.input_pin_type = None
        self.output_pin_type = None
        self.enable_input = False
        self.disable_pull_up = False

    def read(self, fields):
        if fields[0] == 'input_pin_type' and fields[1] == '=' \
                and len(fields) == 3 and self.input_pin_type is None:
            self.input_pin_type = [
                'registered_pin',
                'simple_input_pin',
                'latched_registered_pin',
                'latched_pin'].index(fields[2])
            for i in range(2):
                if self.input_pin_type & 1 << i:
                    self.tile.apply_directive('IOB_%d' % self.index,
                                              'PINTYPE_%d' % i)
        elif fields[0] == 'output_pin_type' and fields[1] == '=' \
                and len(fields) == 3 and self.output_pin_type is None:
            self.output_pin_type = [
                'no_output',
                '1',
                '2',
                '3',
                'DDR',
                'REGISTERED',
                'simple_output_pin',
                'REGISTERED_INVERTED',
                'DDR_ENABLE',
                'REGISTERED_ENABLE',
                'OUTPUT_TRISTATE',
                'REGISTERED_ENABLE_INVERTED',
                'DDR_ENABLE_REGISTERED',
                'REGISTERED_ENABLE_REGISTERED',
                'ENABLE_REGISTERED',
                'REGISTERED_ENABLE_REGISTERED_INVERTED'].index(fields[2])
            for i in range(4):
                if self.output_pin_type & 1 << i:
                    self.tile.apply_directive('IOB_%d' % self.index,
                                              'PINTYPE_%d' % (i + 2))
        elif fields == ['enable_input'] and not self.enable_input:
            self.enable_input = True
        elif fields == ['disable_pull_up'] and not self.disable_pull_up:
            self.disable_pull_up = True
        elif fields[0] in ('GLOBAL_BUFFER_OUTPUT',
                           'io_%d/GLOBAL_BUFFER_OUTPUT' % self.index) \
                and fields[1] == '->' \
                and fields[2].startswith('glb_netwk_'):

            glb_net = int(fields[2][10:])
            padin_loc = self.tile.ic.padin_pio_db()[glb_net]

            if padin_loc != (self.tile.x, self.tile.y, self.index):
                raise ParseError("""\
GLOBAL_BUFFER_OUTPUT not valid for glb_netwk_{}
   Driver at io_tile {},{} io{}
Should be at io_tile {},{} io{}
""".format(glb_net, self.tile.x, self.tile.y, self.index, *padin_loc))
            bit = [bit for bit in self.tile.ic.extra_bits_db()
                   if self.tile.ic.extra_bits_db()[bit]
                          == ("padin_glb_netwk", fields[2][10:])]
            assert len(bit) == 1
            self.tile.ic.extra_bits.add(bit[0])
        elif len(fields) > 3 and (fields[1] == '->' or fields[1] == '~>'):
            self.read(fields[:3])
            self.read(fields[2:])
        elif len(fields) == 3 and (fields[1] == '->' or fields[1] == '~>'):
            prefix = 'io_%d/' % self.index

            # Strip prefix if it is given
            if fields[0].startswith(prefix):
                fields[0] = fields[0][len(prefix):]
            if fields[-1].startswith(prefix):
                fields[-1] = fields[-1][len(prefix):]

            if fields[0] in ('D_IN_0', 'D_IN_1'):
                self.tile.read([prefix + fields[0]] + fields[1:])
            elif fields[-1] in ('cen',
                                'D_OUT_0',
                                'D_OUT_1',
                                'inclk',
                                #'LATCH_INPUT_VALUE',
                                'outclk',
                                'OUT_ENB'):
                self.tile.read(fields[:-1] + [prefix + fields[-1]])
            else:
                self.tile.read(fields)
        elif len(fields) == 3  and fields[1] == '.sym>':
            nn = untranslate_netname(self.tile.x, self.tile.y,
                                      self.tile.ic.max_x - 1,
                                      self.tile.ic.max_y - 1, fields[0])
            net = self.tile.ic.get_net_number( (self.tile.x, self.tile.y, nn) )
            self.tile.ic.symbols.setdefault(net, set()).add(fields[2])
        else:
            raise ParseError("Unknown IOBlock specification format: {}".format(fields))


    def new_block(self, fields):
        raise ParseError("Unexpected new block in {}".format(type(self).__name__))

def main1(path):
    f = open(path, 'r')
    stack = [Main()]
    for i, line in enumerate(f):
        fields = line.split('#')[0].split()
        try:
            if not fields:
                pass  # empty line
            elif fields == ['}']:
                stack.pop()
                if not stack:
                    raise ParseError("Parsing stack empty before expected")
            elif fields[-1] == '{':
                stack.append(stack[-1].new_block(fields[:-1]))
            else:
                stack[-1].read(fields)
        except ParseError as e:
            sys.stderr.write("Parse error in line %d:\n" % (i + 1))
            sys.stderr.write(line)
            if e.args:
                sys.stderr.write("\n")
                print(*e.args, file=sys.stderr)
            sys.exit(1)
    if len(stack) != 1:
        sys.stderr.write("Parse error: unexpected end of file")
        sys.exit(1)
    f.close()

    stack[0].writeout()

def main():
    program_short_name = os.path.basename(sys.argv[0])

    try:
        opts, args = getopt.getopt(sys.argv[1:], '', ['help', 'version'])
    except getopt.GetoptError as e:
        sys.stderr.write("%s: %s\n" % (program_short_name, e.msg))
        sys.stderr.write("Try `%s --help' for more information.\n"
                         % program_short_name)
        sys.exit(1)

    for opt, arg in opts:
        if opt == '--help':
            sys.stderr.write("""\
Create an ASCII bitstream from a high-level bitstream representation.
Usage: %s [OPTION]... FILE

      --help            display this help and exit
      --version         output version information and exit

If you have a bug report, please file an issue on github:
  https://github.com/rlutz/icestorm/issues
""" % program_short_name)
            sys.exit(0)

        if opt == '--version':
            sys.stderr.write("""\
icebox_hlc2asc - create an ASCII bitstream from a high-level representation
Copyright (C) 2017 Roland Lutz

This program is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
""")
            sys.exit(0)

    if not args:
        sys.stderr.write("%s: missing argument\n" % (program_short_name))
        sys.stderr.write("Try `%s --help' for more information.\n"
                         % program_short_name)
        sys.exit(1)

    if len(args) != 1:
        sys.stderr.write("%s: too many arguments\n" % (program_short_name))
        sys.stderr.write("Try `%s --help' for more information.\n"
                         % program_short_name)
        sys.exit(1)

    if args[0] == '-':
        main1('/dev/stdin')
    else:
        main1(args[0])

if __name__ == '__main__':
    main()
