#!/usr/bin/env python3 # 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 GLB_NETWK_EXTERNAL_BLOCKS = [(13, 8, 1), (0, 8, 1), (7, 17, 0), (7, 0, 0), (0, 9, 0), (13, 9, 0), (6, 0, 1), (6, 17, 1)] GLB_NETWK_INTERNAL_TILES = [(7, 0), (7, 17), (13, 9), (0, 9), (6, 17), (6, 0), (0, 8), (13, 8)] ## 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(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(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(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(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(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(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(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(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(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(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(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 (x, y). # # \return \c 'fabout' if it is the \c fabout net, otherwise the # unchanged net name def revert_to_fabout(x, y, net): if net.startswith('glb_netwk_'): for i, xy in enumerate(GLB_NETWK_INTERNAL_TILES): if net == 'glb_netwk_%d' % i and (x, y) == xy: return 'fabout' raise ParseError 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 # ---------------------------------------+---------------------------------- # i | value of argument \a i # (EXPR_AND, [expr, ...]) | AND operation of all expressions # (EXPR_XOR, [expr, ...]) | XOR operation of all expressions # (EXPR_OR, [expr, ...]) | OR operation of all expressions # (EXPR_TERN, ex_a, ex_b, ex_c) | result of \c ex_b if \c ex_a # | evaluates to \c True, otherwise # | result of \c ex_c # (EXPR_NOT, expr) | negated result of \a expr # (EXPR_ZERO, ) | \c False # (EXPR_ONE, ) | \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))) class ParseError(Exception): pass def parse_bool(s): if s == 'on': return True if s == 'off': return False raise ParseError 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] if self.device == '1k': self.ic = icebox.iceconfig() self.ic.setup_empty_1k() elif self.device == '8k': self.ic = icebox.iceconfig() self.ic.setup_empty_8k() elif self.device == '384': self.ic = icebox.iceconfig() self.ic.setup_empty_384() else: raise ParseError #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 def new_block(self, fields): if len(fields) != 3: raise ParseError 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 tile = LogicTile(self.ic, x, y) elif fields[0] == 'ramb_tile': if (x, y) not in self.ic.ramb_tiles: raise ParseError tile = RAMBTile(self.ic, x, y) elif fields[0] == 'ramt_tile': if (x, y) not in self.ic.ramt_tiles: raise ParseError tile = RAMTTile(self.ic, x, y) elif fields[0] == 'io_tile': if (x, y) not in self.ic.io_tiles: raise ParseError tile = IOTile(self.ic, x, y) else: raise ParseError self.tiles[x, y] = tile return tile def writeout(self): if self.ic is None: raise ParseError # 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) # Let the routing bits be specified in both a->b and b->a direction. for bits, *entry in self.db: if not ic.tile_has_entry(x, y, (bits, *entry)): continue if entry[0] != "routing": continue add_entry((entry[0], entry[2], entry[1]), bits) 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(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.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.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:]) else: raise ParseError def new_block(self, fields): raise ParseError 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 class LogicCell: def __init__(self, tile, index): self.tile = tile self.index = index self.lut_bits = None self.seq_bits = ['0'] * 4 def read(self, fields): if fields[0] == 'lut' and len(fields) == 2 and self.lut_bits is None: self.lut_bits = fields[1] elif fields[0] == 'out' and len(fields) >= 3 and fields[1] == '=': m = re.match("([0-9]+)'b([01]+)", fields[2]) if m: lut_bits = m.group(2) if len(lut_bits) != int(m.group(1)): raise ParseError m = len(lut_bits) if m < 16: lut_bits = (16-m) * "0" + lut_bits # Verilog 16'bXXXX is MSB first but the bitstream wants LSB. self.lut_bits = 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: raise ParseError return 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 class RAMData: def __init__(self, data): self.data = data def read(self, fields): if len(fields) == 1: self.data.append(fields[0]) else: raise ParseError def new_block(self, fields): raise ParseError 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 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 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] == 'GLOBAL_BUFFER_OUTPUT' and fields[1] == '->' \ and fields[2].startswith('glb_netwk_'): if GLB_NETWK_EXTERNAL_BLOCKS[int(fields[2][10:])] \ != (self.tile.x, self.tile.y, self.index): raise ParseError 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: raise ParseError else: raise ParseError def new_block(self, fields): raise ParseError 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 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" % sys.argv[0]) 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 """ % sys.argv[0]) 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" % sys.argv[0]) 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" % sys.argv[0]) sys.exit(1) if args[0] == '-': main1('/dev/stdin') else: main1(args[0]) if __name__ == '__main__': main()