From 1565fdc0eecb24da27c513237b8e8e12dee21325 Mon Sep 17 00:00:00 2001 From: Clifford Wolf Date: Fri, 25 Aug 2017 14:59:59 +0200 Subject: Implement high-level configuration format # Conflicts: # icebox/Makefile --- icebox/Makefile | 9 + icebox/icebox_asc2hlc.py | 1132 +++++++++++++++++++++++++++++++++++++++++++ icebox/icebox_hlc2asc.py | 1052 ++++++++++++++++++++++++++++++++++++++++ icebox/tc_logic_xpr.py | 42 ++ icebox/tc_rxlat_netnames.py | 69 +++ icebox/tc_xlat_netnames.py | 79 +++ 6 files changed, 2383 insertions(+) create mode 100755 icebox/icebox_asc2hlc.py create mode 100755 icebox/icebox_hlc2asc.py create mode 100644 icebox/tc_logic_xpr.py create mode 100644 icebox/tc_rxlat_netnames.py create mode 100644 icebox/tc_xlat_netnames.py (limited to 'icebox') diff --git a/icebox/Makefile b/icebox/Makefile index 430fb17..778e2ce 100644 --- a/icebox/Makefile +++ b/icebox/Makefile @@ -18,6 +18,11 @@ chipdb-8k.txt: icebox.py iceboxdb.py icebox_chipdb.py python3 icebox_chipdb.py -8 > chipdb-8k.new mv chipdb-8k.new chipdb-8k.txt +check: all + python3 tc_xlat_netnames.py + python3 tc_rxlat_netnames.py + python3 tc_logic_xpr.py + clean: rm -f chipdb-1k.txt chipdb-8k.txt chipdb-384.txt chipdb-5k.txt rm -f icebox.pyc iceboxdb.pyc @@ -33,6 +38,8 @@ install: all cp icebox_chipdb.py $(DESTDIR)$(PREFIX)/bin/icebox_chipdb$(PY_EXE) cp icebox_diff.py $(DESTDIR)$(PREFIX)/bin/icebox_diff$(PY_EXE) cp icebox_explain.py $(DESTDIR)$(PREFIX)/bin/icebox_explain$(PY_EXE) + cp icebox_asc2hlc.py $(DESTDIR)$(PREFIX)/bin/icebox_asc2hlc$(PY_EXE) + cp icebox_hlc2asc.py $(DESTDIR)$(PREFIX)/bin/icebox_hlc2asc$(PY_EXE) cp icebox_colbuf.py $(DESTDIR)$(PREFIX)/bin/icebox_colbuf$(PY_EXE) cp icebox_html.py $(DESTDIR)$(PREFIX)/bin/icebox_html$(PY_EXE) cp icebox_maps.py $(DESTDIR)$(PREFIX)/bin/icebox_maps$(PY_EXE) @@ -45,6 +52,8 @@ uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/icebox_chipdb$(PY_EXE) rm -f $(DESTDIR)$(PREFIX)/bin/icebox_diff$(PY_EXE) rm -f $(DESTDIR)$(PREFIX)/bin/icebox_explain$(PY_EXE) + rm -f $(DESTDIR)$(PREFIX)/bin/icebox_asc2hlc$(PY_EXE) + rm -f $(DESTDIR)$(PREFIX)/bin/icebox_hlc2asc$(PY_EXE) rm -f $(DESTDIR)$(PREFIX)/bin/icebox_colbuf$(PY_EXE) rm -f $(DESTDIR)$(PREFIX)/bin/icebox_html$(PY_EXE) rm -f $(DESTDIR)$(PREFIX)/bin/icebox_maps$(PY_EXE) diff --git a/icebox/icebox_asc2hlc.py b/icebox/icebox_asc2hlc.py new file mode 100755 index 0000000..0b96441 --- /dev/null +++ b/icebox/icebox_asc2hlc.py @@ -0,0 +1,1132 @@ +#!/usr/bin/env python3 +# 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. + +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 global 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 net name +# +# \return the global name of the net if it is a span wire, otherwise +# the unmodified net name +# +# There are 46624 span wires on the 1k (not counting dummies): +# +# span4_x[1..12]_g[1..20]_[0..11] +# span4_y[1..16]_g[1..16]_[0..11] +# span12_x[1..12]_g[1..28]_[0..1] +# span12_y[1..16]_g[1..24]_[0..1] +# +# span4_left_g[3..16]_[0..3] +# span4_right_g[5..18]_[0..3] +# span4_bottom_g[3..12]_[0..3] +# span4_top_g[5..14]_[0..3] +# +# span4_topleft[2,4,6,8]_[0..3] +# span4_bottomright[2,4,6,8]_[0..3] +# +# dummy_y[1..16]_g[0..3]_[0..11] +# +# "Dummy" nets are horizontal accesses to non-existing vertical span +# wires on the right edge which are listed by icebox but don't +# actually connect to anything outside the tile itself. + +def translate_netname(x, y, fw, fh, net): + def group_and_index(s, group_size): + n = int(s) + g = n // group_size + i = n % group_size + if g % 2 == 1: + i = i + 1 - (i % 2) * 2 + return g, i + + # logic and RAM tiles + + match = re.match(r'sp4_h_r_(\d+)$', net) + if match is not None: + g, i = group_and_index(match.group(1), 12) + return 'span4_y%d_g%d_%d' % (y, x - g + 4, i) + match = re.match(r'sp4_h_l_(\d+)$', net) + if match is not None: + g, i = group_and_index(match.group(1), 12) + return 'span4_y%d_g%d_%d' % (y, x - g + 3, i) + + match = re.match(r'sp4_v_b_(\d+)$', net) + if match is not None: + g, i = group_and_index(match.group(1), 12) + return 'span4_x%d_g%d_%d' % (x, y + g, i) + match = re.match(r'sp4_v_t_(\d+)$', net) + if match is not None: + g, i = group_and_index(match.group(1), 12) + return 'span4_x%d_g%d_%d' % (x, y + g + 1, i) + match = re.match(r'sp4_r_v_b_(\d+)$', net) + if match is not None: + g, i = group_and_index(match.group(1), 12) + if x == fw: + # this net doesn't connect anywhere + return 'dummy_y%d_g%d_%d' % (y, g, i) + else: + return 'span4_x%d_g%d_%d' % (x + 1, y + g, i) + + match = re.match(r'sp12_h_r_(\d+)$', net) + if match is not None: + g, i = group_and_index(match.group(1), 2) + return 'span12_y%d_g%d_%d' % (y, x - g + 12, i) + match = re.match(r'sp12_h_l_(\d+)$', net) + if match is not None: + g, i = group_and_index(match.group(1), 2) + return 'span12_y%d_g%d_%d' % (y, x - g + 11, i) + + match = re.match(r'sp12_v_b_(\d+)$', net) + if match is not None: + g, i = group_and_index(match.group(1), 2) + return 'span12_x%d_g%d_%d' % (x, y + g, i) + match = re.match(r'sp12_v_t_(\d+)$', net) + if match is not None: + g, i = group_and_index(match.group(1), 2) + return 'span12_x%d_g%d_%d' % (x, y + g + 1, i) + + # I/O tiles + + match = re.match(r'span4_horz_(\d+)$', net) + if match is not None: + g, i = group_and_index(match.group(1), 12) + if x == 0: + return 'span4_y%d_g%d_%d' % (y, x - g + 4, i) + else: + return 'span4_y%d_g%d_%d' % (y, x - g + 3, i) + + match = re.match(r'span4_vert_(\d+)$', net) + if match is not None: + g, i = group_and_index(match.group(1), 12) + if y == 0: + return 'span4_x%d_g%d_%d' % (x, y + g + 1, i) + else: + return 'span4_x%d_g%d_%d' % (x, y + g, i) + + match = re.match(r'span12_horz_(\d+)$', net) + if match is not None: + g, i = group_and_index(match.group(1), 2) + if x == 0: + return 'span12_y%d_g%d_%d' % (y, x - g + 12, i) + else: + return 'span12_y%d_g%d_%d' % (y, x - g + 11, i) + + match = re.match(r'span12_vert_(\d+)$', net) + if match is not None: + g, i = group_and_index(match.group(1), 2) + if y == 0: + return 'span12_x%d_g%d_%d' % (x, y + g + 1, i) + else: + return 'span12_x%d_g%d_%d' % (x, y + g, i) + + # I/O tiles - peripheral wires + + match = re.match(r'span4_horz_r_(\d+)$', net) + if match is not None: + n = int(match.group(1)); g = n // 4; i = n % 4 + if y == 0: + if fw - x + g - 4 < 0: + return 'span4_bottomright%d_%d' % ((fw - x + 1 + g) * 2, i) + elif x - g + 1 < 0: + return 'span4_left_g%d_%d' % (-x + 1 + g, i) + else: + return 'span4_bottom_g%d_%d' % (x + 4 - g, i) + else: + if x - g - 1 < 0: + return 'span4_topleft%d_%d' % ((x + 4 - g) * 2, i) + elif x - g + 1 >= fw: + return 'span4_right_g%d_%d' % (fh + fw - x + 1 + g, i) + else: + return 'span4_top_g%d_%d' % (x + 4 - g, i) + + match = re.match(r'span4_horz_l_(\d+)$', net) + if match is not None: + n = int(match.group(1)); g = n // 4; i = n % 4 + if y == 0: + if x - g < 0: + return 'span4_left_g%d_%d' % (-x + 2 + g, i) + else: + return 'span4_bottom_g%d_%d' % (x + 3 - g, i) + else: + if x - g - 2 < 0: + return 'span4_topleft%d_%d' % ((x + 3 - g) * 2, i) + else: + return 'span4_top_g%d_%d' % (x + 3 - g, i) + + match = re.match(r'span4_vert_b_(\d+)$', net) + if match is not None: + n = int(match.group(1)); g = n // 4; i = n % 4 + if x == 0: + if y + g - 3 < 0: + return 'span4_bottom_g%d_%d' % (-y + 5 - g, i) + if fh - y - g < 0: + return 'span4_topleft%d_%d' % ((fh + 5 - y - g) * 2, i) + else: + return 'span4_left_g%d_%d' % (y + g, i) + else: + if y + g - 5 < 0: + return 'span4_bottomright%d_%d' % ((y + g) * 2, i) + elif y + g >= fh + 3: + return 'span4_top_g%d_%d' % (fw + fh + 5 - y - g, i) + else: + return 'span4_right_g%d_%d' % (y + g, i) + + match = re.match(r'span4_vert_t_(\d+)$', net) + if match is not None: + n = int(match.group(1)); g = n // 4; i = n % 4 + if x == 0: + if fh - y - g - 1 < 0: + return 'span4_topleft%d_%d' % ((fh + 4 - y - g) * 2, i) + else: + return 'span4_left_g%d_%d' % (y + g + 1, i) + else: + if y + g >= fh + 2: + return 'span4_top_g%d_%d' % (fw + fh + 4 - y - g, i) + else: + return 'span4_right_g%d_%d' % (y + g + 1, i) + + return net + +## Return the human-readable name of the \c fabout net of IO tile +## (x, y). + +def lookup_fabout(x, y): + if (x, y) in GLB_NETWK_INTERNAL_TILES: + return 'glb_netwk_%d' % GLB_NETWK_INTERNAL_TILES.index((x, y)) + + return 'fabout' + + +## Remove an argument from a LUT string and an associated list of +## argument names. +# +# This is a helper function for \ref lut_to_logic_expression. +# +# \param lut string of 2^N `0' or `1' characters representing the +# logic of an Nx1 look-up table +# \param args list of N strings containing the human-readable names +# of the arguments +# \param i index of the argument to remove +# \param keep boolean value indicating which value of the removed +# argument is to be assumed in the resulting LUT +# +# \return a new pair (lut, args) with the argument removed + +def discard_argument(lut, args, i, keep): + assert len(lut) == 1 << len(args) + assert i >= 0 and i < len(args) + return ''.join(bit for j, bit in enumerate(lut) + if (j & (1 << i) != 0) == keep), \ + args[:i] + args[i + 1:] + +## Negate a tuple representation of a logic expression. +# +# This is a helper function for \ref lut_to_logic_expression. + +def negate_expr(expr): + if len(expr) == 2: + op, a = expr + assert op == 'not' + return a + if len(expr) != 3: + return 'not', expr + a, op, b = expr + if op == 'and': + return negate_expr(a), 'or', negate_expr(b) + if op == 'or': + return negate_expr(a), 'and', negate_expr(b) + assert op == 'xor' + if len(a) == 2 and a[0] == 'not': + return a[1], op, b + if len(b) == 2 and b[0] == 'not': + return a, op, b[1] + return negate_expr(a), op, b + +## Convert a tuple representation of a logic expression into a string. +# +# This is a helper function for \ref lut_to_logic_expression. +# +# \param expr the expression to convert +# \param parenthize whether a compound expression should be +# surrounded by parentheses + +def stringify(expr, parenthize): + if type(expr) == str: + return expr + assert type(expr) == tuple + + if len(expr) == 2: + op, a = expr + assert op == 'not' + assert type(a) == str + return "!" + a + + if len(expr) == 5: + a, op0, b, op1, c = expr + assert op0 == '?' and op1 == ':' + s = '%s ? %s : %s' % (stringify(a, False), stringify(b, False), + stringify(c, False)) + if parenthize: + return '(%s)' % s + return s + + assert len(expr) == 3 + + a, op, b = expr + l = [a, b] + i = 0 + while i < len(l): + if type(l[i]) == tuple and len(l[i]) == 3 and l[i][1] == op: + l = l[:i] + [l[i][0], l[i][2]] + l[i + 1:] + else: + i += 1 + + if op == 'and': + op = '&' + elif op == 'xor': + op = '^' + elif op == 'or': + op = '|' + + s = (' %s ' % op).join(stringify(x, True) for x in l) + if parenthize: + return '(%s)' % s + return s + +## Remove arguments which don't affect the result from a LUT string +## and an associated list of argument names. +# +# This is a helper function for \ref lut_to_logic_expression. +# +# \param lut string of 2^N `0' or `1' characters representing the +# logic of an Nx1 look-up table +# \param args list of N strings containing the human-readable names +# of the arguments +# +# \return a new pair (lut, args) with all unused arguments +# removed + +def discard_unused_arguments(lut, args): + assert len(lut) == 1 << len(args) + i = 0 + while i < len(args): + diff = False + for j in range(len(lut)): + if j & (1 << i) == 0 and lut[j] != lut[j | (1 << i)]: + diff = True + if not diff: + lut, args = discard_argument(lut, args, i, False) + else: + i += 1 + return lut, args + +## Convert a LUT string to a logic expression. +# +# \param lut string of 2^N `0' or `1' characters representing the +# logic of an Nx1 look-up table +# \param args list of N strings containing the human-readable names +# of the arguments +# +# \return a string containing a human-readable logic expression +# equivalent to the look-up table +# +# Example: lut_to_logic_expression('00010000', ['a', 'b', 'c']) -> 'a & b & !c' + +def lut_to_logic_expression(lut, args): + lut, args = discard_unused_arguments(lut, args) + + # filter out independent top-level arguments + toplevel_args = [] + i = 0 + while i < len(args) and len(args) >= 2: + ai_0 = set(bit for j, bit in enumerate(lut) if j & (1 << i) == 0) + ai_1 = set(bit for j, bit in enumerate(lut) if j & (1 << i) != 0) + assert len(ai_0) == 2 or len(ai_1) == 2 + + if len(ai_0) == 1: + # expression is constant if this argument is 0 + # e = (...) & arg or e = (...) | !arg + if tuple(ai_0)[0] == '0': + toplevel_args.append(('and', args[i])) + else: + toplevel_args.append(('or', ('not', args[i]))) + lut, args = discard_argument(lut, args, i, True) + i = 0 + continue + + if len(ai_1) == 1: + # expression is constant if this argument is 1 + # e = (...) & !arg or e = (...) | arg + if tuple(ai_1)[0] == '0': + toplevel_args.append(('and', ('not', args[i]))) + else: + toplevel_args.append(('or', args[i])) + lut, args = discard_argument(lut, args, i, False) + i = 0 + continue + + i += 1 + + i = 0 + while i < len(args) and len(args) >= 2: + is_xor = True + for j in range(len(lut)): + if j & (1 << i) == 0 and lut[j] == lut[j | (1 << i)]: + is_xor = False + break + + if is_xor: + toplevel_args.append(('xor', args[i])) + lut, args = discard_argument(lut, args, i, False) + continue + + i += 1 + + # detect simple top-level ternary conditions + i = 0 + while i < len(args) and len(args) >= 3: + j = i + 1 + while j < len(args): + ai_0_aj_0 = set(bit for k, bit in enumerate(lut) + if k & (1 << i) == 0 and k & (1 << j) == 0) + ai_0_aj_1 = set(bit for k, bit in enumerate(lut) + if k & (1 << i) == 0 and k & (1 << j) != 0) + ai_1_aj_0 = set(bit for k, bit in enumerate(lut) + if k & (1 << i) != 0 and k & (1 << j) == 0) + ai_1_aj_1 = set(bit for k, bit in enumerate(lut) + if k & (1 << i) != 0 and k & (1 << j) != 0) + assert len(ai_0_aj_0) == 2 or len(ai_0_aj_1) == 2 or \ + len(ai_1_aj_0) == 2 or len(ai_1_aj_1) == 2 + + if (len(ai_0_aj_0) == 2 or len(ai_0_aj_1) == 2) and \ + (len(ai_1_aj_0) == 2 or len(ai_1_aj_1) == 2) and \ + (len(ai_0_aj_0) == 2 or len(ai_1_aj_0) == 2) and \ + (len(ai_0_aj_1) == 2 or len(ai_1_aj_1) == 2): + j += 1 + continue + + ai_doesnt_matter_for_aj_0 = True + ai_doesnt_matter_for_aj_1 = True + aj_doesnt_matter_for_ai_0 = True + aj_doesnt_matter_for_ai_1 = True + + for k in range(len(lut)): + if k & (1 << i) != 0 or k & (1 << j) != 0: + continue + if lut[k] != lut[k | (1 << i)]: + ai_doesnt_matter_for_aj_0 = False + if lut[k | (1 << j)] != lut[k | (1 << i) | (1 << j)]: + ai_doesnt_matter_for_aj_1 = False + if lut[k] != lut[k | (1 << j)]: + aj_doesnt_matter_for_ai_0 = False + if lut[k | (1 << i)] != lut[k | (1 << i) | (1 << j)]: + aj_doesnt_matter_for_ai_1 = False + + if len(ai_0_aj_0) == 1 and len(ai_0_aj_1) == 1 and \ + aj_doesnt_matter_for_ai_1: + assert tuple(ai_0_aj_0)[0] != tuple(ai_0_aj_1)[0] + if tuple(ai_0_aj_0)[0] == '0': + toplevel_args.append((args[i], '?', ':', args[j])) + else: + toplevel_args.append((args[i], '?', ':', ('not', args[j]))) + lut, args = discard_argument(lut, args, i, True) + + # break loops + i = len(args) + j = len(args) + break + + if len(ai_1_aj_0) == 1 and len(ai_1_aj_1) == 1 and \ + aj_doesnt_matter_for_ai_0: + assert tuple(ai_1_aj_0)[0] != tuple(ai_1_aj_1)[0] + if tuple(ai_1_aj_0)[0] == '0': + toplevel_args.append((args[i], '?', args[j], ':')) + else: + toplevel_args.append((args[i], '?', ('not', args[j]), ':')) + lut, args = discard_argument(lut, args, i, False) + + # break loops + i = len(args) + j = len(args) + break + + if len(ai_0_aj_0) == 1 and len(ai_1_aj_0) == 1 and \ + ai_doesnt_matter_for_aj_1: + assert tuple(ai_0_aj_0)[0] != tuple(ai_1_aj_0)[0] + if tuple(ai_0_aj_0)[0] == '0': + toplevel_args.append((args[j], '?', ':', args[i])) + else: + toplevel_args.append((args[j], '?', ':', ('not', args[i]))) + lut, args = discard_argument(lut, args, j, True) + + # break loops + i = len(args) + j = len(args) + break + + if len(ai_0_aj_1) == 1 and len(ai_1_aj_1) == 1 and \ + ai_doesnt_matter_for_aj_0: + assert tuple(ai_0_aj_1)[0] != tuple(ai_1_aj_1)[0] + if tuple(ai_0_aj_1)[0] == '0': + toplevel_args.append((args[j], '?', args[i], ':')) + else: + toplevel_args.append((args[j], '?', ('not', args[i]), ':')) + lut, args = discard_argument(lut, args, j, False) + + # break loops + i = len(args) + j = len(args) + break + + j += 1 + i += 1 + + lut, args = discard_unused_arguments(lut, args) + + # group pairwise isolated arguments + i = 0 + while i < len(args): + j = i + 1 + while j < len(args): + ai_doesnt_matter_for_aj_0 = True + ai_doesnt_matter_for_aj_1 = True + aj_doesnt_matter_for_ai_0 = True + aj_doesnt_matter_for_ai_1 = True + both_dont_matter_if_equal = True + both_dont_matter_if_unequal = True + + for k in range(len(lut)): + if k & (1 << i) != 0 or k & (1 << j) != 0: + continue + if lut[k] != lut[k | (1 << i)]: + ai_doesnt_matter_for_aj_0 = False + if lut[k | (1 << j)] != lut[k | (1 << i) | (1 << j)]: + ai_doesnt_matter_for_aj_1 = False + if lut[k] != lut[k | (1 << j)]: + aj_doesnt_matter_for_ai_0 = False + if lut[k | (1 << i)] != lut[k | (1 << i) | (1 << j)]: + aj_doesnt_matter_for_ai_1 = False + if lut[k] != lut[k | (1 << i) | (1 << j)]: + both_dont_matter_if_equal = False + if lut[k | (1 << i)] != lut[k | (1 << j)]: + both_dont_matter_if_unequal = False + + # There are five possibilities of coupled arguments: one + # of the four combinations differs from the other three, + # or they are xor'ed + + if ai_doesnt_matter_for_aj_1 and \ + aj_doesnt_matter_for_ai_1 and \ + both_dont_matter_if_unequal: + # special case is ai=0 aj=0 + args = args[:i] + ((args[i], 'or', args[j]), ) + args[i + 1:] + lut, args = discard_argument(lut, args, j, False) + j = i + 1 + elif ai_doesnt_matter_for_aj_1 and \ + aj_doesnt_matter_for_ai_0 and \ + both_dont_matter_if_equal: + # special case is ai=1 aj=0 + args = args[:i] + ((args[i], 'and', negate_expr(args[j])), ) + \ + args[i + 1:] + lut, args = discard_argument(lut, args, j, False) + j = i + 1 + elif ai_doesnt_matter_for_aj_0 and \ + aj_doesnt_matter_for_ai_1 and \ + both_dont_matter_if_equal: + # special case is ai=0 aj=1 + args = args[:i] + ((args[i], 'or', negate_expr(args[j])), ) + \ + args[i + 1:] + lut, args = discard_argument(lut, args, j, True) + j = i + 1 + elif ai_doesnt_matter_for_aj_0 and \ + aj_doesnt_matter_for_ai_0 and \ + both_dont_matter_if_unequal: + # special case is ai=1 aj=1 + args = args[:i] + ((args[i], 'and', args[j]), ) + args[i + 1:] + lut, args = discard_argument(lut, args, j, True) + j = i + 1 + + elif both_dont_matter_if_equal and \ + both_dont_matter_if_unequal: + args = args[:i] + ((args[i], 'xor', args[j]), ) + args[i + 1:] + lut, args = discard_argument(lut, args, j, False) + j = i + 1 + else: + j += 1 + i += 1 + + # collect the result + + if not args: + # constant expression + assert len(lut) == 1 + return lut + + negate_result = lut.count('1') > lut.count('0') + if negate_result: + lut = ''.join('1' if bit == '0' else '0' for bit in lut) + + result = None + for i, bit in enumerate(lut): + if bit == '0': + continue + expr = None + for j, arg in enumerate(args): + if i & (1 << j) == 0: + arg = negate_expr(arg) + if expr is None: + expr = arg + else: + expr = (expr, 'and', arg) + if result is None: + result = expr + else: + result = (result, 'or', expr) + + if negate_result: + result = negate_expr(result) + + for toplevel_arg in reversed(toplevel_args): + if len(toplevel_arg) != 4: + result = tuple(reversed(toplevel_arg)) + (result, ) + elif toplevel_arg[2] == ':': + result = toplevel_arg[0:2] + (result, ) + toplevel_arg[2:4] + else: + assert toplevel_arg[3] == ':' + result = toplevel_arg + (result, ) + + return stringify(result, False) + + +class Fabric: + def __init__(self, ic): + self.ic = ic + self.tiles = {} + #self.colbuf = set() + + io_blocks = {} + ieren_blocks = {} + + for x0, y0, b0, x1, y1, b1 in self.ic.ieren_db(): + i = IOBlock() + assert (x0, y0, b0) not in io_blocks + io_blocks[x0, y0, b0] = i + assert (x1, y1, b1) not in ieren_blocks + ieren_blocks[x1, y1, b1] = i + + for xy in ic.io_tiles: + assert xy not in self.tiles + self.tiles[xy] = IOTile(self, xy, + (io_blocks.pop((xy[0], xy[1], 0), None), + io_blocks.pop((xy[0], xy[1], 1), None)), + (ieren_blocks.pop((xy[0], xy[1], 0), None), + ieren_blocks.pop((xy[0], xy[1], 1), None))) + assert not io_blocks + assert not ieren_blocks + + for xy in ic.logic_tiles: + assert xy not in self.tiles + self.tiles[xy] = LogicTile(self, xy) + + for xy in ic.ramb_tiles: + assert xy not in self.tiles + self.tiles[xy] = RAMBTile(self, xy) + + for xy in ic.ramt_tiles: + assert xy not in self.tiles + self.tiles[xy] = RAMTTile(self, xy) + + for x, y in self.tiles: + assert x >= 0 and x <= self.ic.max_x + assert y >= 0 and y <= self.ic.max_y + for x in range(self.ic.max_x + 1): + for y in range(self.ic.max_y + 1): + should_exist = (x > 0 and x < self.ic.max_x) or \ + (y > 0 and y < self.ic.max_y) + assert ((x, y) in self.tiles) == should_exist + + for xy in ic.ram_data: + assert type(self.tiles.get(xy, None)) == RAMBTile + + #colbuf_db = ic.colbuf_db() + #for x, y, i in self.colbuf: + # exists = False + # for src_x, src_y, dst_x, dst_y in colbuf_db: + # if src_x != x or src_y != y: + # continue + # assert (dst_x, dst_y) in self.tiles + # assert not self.tiles[dst_x, dst_y].colbuf[i] + # self.tiles[dst_x, dst_y].colbuf[i] = True + # exists = True + # assert exists + # + #for xy in self.tiles: + # for br in self.tiles[xy].buffer_and_routing: + # if br[0].startswith('glb_netwk_'): + # assert self.tiles[xy].colbuf[int(br[0][10:])] + + for bit in self.ic.extra_bits: + directive, arg = self.ic.lookup_extra_bit(bit) + assert directive == 'padin_glb_netwk' + x, y, n = GLB_NETWK_EXTERNAL_BLOCKS[int(arg)] + assert type(self.tiles.get((x, y), None)) == IOTile + block = self.tiles[x, y].io_blocks[n] + assert block is not None + block.padin_glb_netwk = True + + def printout(self, options): + print('device "%s" %d %d' % (self.ic.device, self.ic.max_x - 1, + self.ic.max_y - 1)) + + print('') + # internal_configuration_oscillator_frequency = low | medium | high + #print('coldboot = off') + print('warmboot = on') # IceStorm assumes this to be always on + + for xy in sorted(self.tiles.keys(), key = lambda xy: (xy[1], xy[0])): + self.tiles[xy].printout(options) + +class Tile: + def __init__(self, fabric, xy, data, is_logic_block): + self.fabric = fabric + self.ic = fabric.ic + self.xy = xy + self.data = data + + self.buffer_and_routing = set() + self.used_buffer_and_routing = set() + self.text = set() + self.bitinfo = list() + self.unknown_bits = False + + x, y = xy + db = self.ic.tile_db(x, y) + mapped_bits = set() + + # 'data' is a list of strings containing a series of zeroes and + # ones. 'bits' is a set of strings containing an entry + # "B[]" or "!B[]" for each bit. + + bits = set() + for k, line in enumerate(data): + for i in range(len(line)): + if line[i] == '1': + bits.add('B%d[%d]' % (k, i)) + else: + bits.add('!B%d[%d]' % (k, i)) + + for entry in db: + # LC bits don't have a useful entry in the database; skip them + # for now + if re.match(r'LC_', entry[1]): + continue + + # some nets have different names depending on the tile; filter + # out non-applicable net names + if entry[1] in ('routing', 'buffer') and ( + not self.ic.tile_has_net(x, y, entry[2]) or + not self.ic.tile_has_net(x, y, entry[3])): + continue + + # are all required bits set/unset? + match = True + for bit in entry[0]: + if not bit in bits: + match = False + if match: + for bit in entry[0]: + mapped_bits.add(bit) + + if entry[1:] == ['IoCtrl', 'IE_0']: + if match != (self.ic.device == '1k'): + self.ieren_blocks[0].enable_input = True + continue + if entry[1:] == ['IoCtrl', 'REN_0']: + if match: + self.ieren_blocks[0].disable_pull_up = True + continue + if entry[1:] == ['IoCtrl', 'IE_1']: + if match != (self.ic.device == '1k'): + self.ieren_blocks[1].enable_input = True + continue + if entry[1:] == ['IoCtrl', 'REN_1']: + if match: + self.ieren_blocks[1].disable_pull_up = True + continue + + if entry[1].startswith('IOB_') and entry[2].startswith('PINTYPE_'): + if match: + self.io_blocks[int(entry[1][4:])].pintype \ + |= 1 << int(entry[2][8:]) + continue + + if entry[1:] == ['RamConfig', 'PowerUp']: + if match != (self.ic.device == '1k'): + self.text.add('power_up') + continue + + if entry[1] == 'routing': + if match: + src = translate_netname(self.xy[0], self.xy[1], + self.ic.max_x - 1, + self.ic.max_y - 1, entry[2]) + dst = translate_netname(self.xy[0], self.xy[1], + self.ic.max_x - 1, + self.ic.max_y - 1, entry[3]) + if dst == 'fabout': + dst = lookup_fabout(*self.xy) + self.buffer_and_routing.add((src, '<->', dst)) + continue + if entry[1] == 'buffer': + if match: + src = translate_netname(self.xy[0], self.xy[1], + self.ic.max_x - 1, + self.ic.max_y - 1, entry[2]) + dst = translate_netname(self.xy[0], self.xy[1], + self.ic.max_x - 1, + self.ic.max_y - 1, entry[3]) + if dst == 'fabout': + dst = lookup_fabout(*self.xy) + self.buffer_and_routing.add((src, '->', dst)) + continue + + if entry[1] == 'ColBufCtrl': + assert entry[2].startswith('glb_netwk_') + #if match: + # fabric.colbuf.add(self.xy + (int(entry[2][10:]), )) + continue + + if match: + self.text.add(' '.join(entry[1:])) + + for prefix in ('local_', 'glb2local_'): + for fst in [fst for fst in self.buffer_and_routing + if fst[-1].startswith(prefix)]: + used = False + for snd in [snd for snd in self.buffer_and_routing + if snd[0] == fst[-1]]: + self.buffer_and_routing.remove(snd) + self.buffer_and_routing.add(fst[:-1] + snd) + used = True + if used: + self.buffer_and_routing.remove(fst) + + for k, line in enumerate(data): + self.bitinfo.append('') + extra_text = '' + for i in range(len(line)): + if 36 <= i <= 45 and is_logic_block: + self.bitinfo[-1] += '*' if line[i] == '1' else '-' + elif line[i] == '1' and 'B%d[%d]' % (k, i) not in mapped_bits: + self.unknown_bits = True + extra_text += ' B%d[%d]' % (k, i) + self.bitinfo[-1] += '?' + else: + self.bitinfo[-1] += '+' if line[i] == '1' else '-' + self.bitinfo[-1] += extra_text + + def get_hlc(self): + return sorted(set.union(self.text, + set(' '.join(t) + for t in set.difference( + self.buffer_and_routing, + self.used_buffer_and_routing)))) + + def printout(self, stmt, options): + text = self.get_hlc() + if text or self.unknown_bits or options.print_all: + if self.unknown_bits or options.print_map: + print() + if self.unknown_bits: + print("; Warning: No DB entries for some bits:") + for k, line in enumerate(self.bitinfo): + print("; %4s %s" % ('B%d' % k, line)) + print() + print("%s %d %d {" % (stmt, self.xy[0], self.xy[1])) + for line in text: + print(" " + line) + print("}") + +class LogicCell: + def __init__(self, tile, lcidx): + self.lut = ''.join(icebox.get_lutff_lut_bits(tile.data, lcidx)) + self.expr = lut_to_logic_expression( + self.lut, ('in_0', 'in_1', 'in_2', 'in_3')) + + self.options = [] + lutff_option_bits = ''.join(icebox.get_lutff_seq_bits(tile.data, lcidx)) + if lutff_option_bits[0] == '1': self.options.append('enable_carry') + if lutff_option_bits[1] == '1': self.options.append('enable_dff') + if lutff_option_bits[2] == '1': self.options.append('set_noreset') + if lutff_option_bits[3] == '1': self.options.append('async_setreset') + + self.buffer_and_routing0 = set() + self.buffer_and_routing1 = set() + for br in tuple(tile.buffer_and_routing): + if br[0] == 'lutff_%d/out' % lcidx: + self.buffer_and_routing1.add((br[0][8:], ) + br[1:]) + tile.used_buffer_and_routing.add(br) + elif br[-1].startswith('lutff_%d/' % lcidx): + self.buffer_and_routing0.add(br[:-1] + (br[-1][8:], )) + tile.used_buffer_and_routing.add(br) + + def get_hlc(self): + if self.lut == '0000000000000000' and not self.options: + t = [] + elif len(self.expr) > 64: + t = ['lut ' + self.lut] + else: + t = ['out = ' + self.expr] + return [' '.join(t) for t in sorted(self.buffer_and_routing0, + key = lambda x: x[-1])] + \ + t + self.options + \ + [' '.join(t) for t in sorted(self.buffer_and_routing1, + key = lambda x: x[-1])] + +class LogicTile(Tile): + def __init__(self, fabric, xy): + super().__init__(fabric, xy, fabric.ic.logic_tiles[xy], True) + self.cells = tuple(LogicCell(self, lcidx) for lcidx in range(8)) + + def get_hlc(self): + text = super().get_hlc() + + for i, cell in reversed(tuple(enumerate(self.cells))): + t = cell.get_hlc() + if t: + text = ['lutff_%d {' % i] + \ + [' %s' % s for s in t] + \ + ['}'] + \ + text + + return text + + def printout(self, options): + super().printout('logic_tile', options) + +class IOBlock: + def __init__(self): + # stored in the I/O tile where this block is located + self.pintype = 0 + + # stored in the I/O tile where this is an IE/REN block + self.enable_input = False + self.disable_pull_up = False + + # stored as an extra bit + self.padin_glb_netwk = False + +class IOTile(Tile): + def __init__(self, fabric, xy, io_blocks, ieren_blocks): + self.io_blocks = io_blocks + self.ieren_blocks = ieren_blocks + super().__init__(fabric, xy, fabric.ic.io_tiles[xy], False) + #self.cells = tuple(IOCell() for i in range(2)) + + for i, block in enumerate(io_blocks): + if block is None: + continue + block.buffer_and_routing0 = set() + block.buffer_and_routing1 = set() + for br in tuple(self.buffer_and_routing): + if br[0].startswith('io_%d/D_IN_' % i): + block.buffer_and_routing1.add((br[0][5:], ) + br[1:]) + self.used_buffer_and_routing.add(br) + elif br[-1].startswith('io_%d/' % i): + block.buffer_and_routing0.add(br[:-1] + (br[-1][5:], )) + self.used_buffer_and_routing.add(br) + + def get_hlc(self): + # if io_blocks[N] is None, this means there's no I/O pin there + + text = super().get_hlc() + for n in (1, 0): + block = self.io_blocks[n] + if block is None: + continue + + t = [] + input_pt = block.pintype & 3 + output_pt = block.pintype >> 2 & 15 + unknown_pt = block.pintype >> 6 + if input_pt != 0: + t.append('input_pin_type = %s' % ( + 'registered_pin', + 'simple_input_pin', + 'latched_registered_pin', + 'latched_pin')[input_pt]) + if output_pt != 0: + t.append('output_pin_type = %s' % ( + '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')[output_pt]) + if unknown_pt != 0: + t.append('unknown_pin_type = %d' % unknown_pt) + if block.enable_input: + t.append('enable_input') + if block.disable_pull_up: + t.append('disable_pull_up') + + t += [' '.join(t) for t in sorted(block.buffer_and_routing0, + key = lambda x: x[-1])] + t += [' '.join(t) for t in sorted(block.buffer_and_routing1, + key = lambda x: x[0])] + if block.padin_glb_netwk: + t += ['GLOBAL_BUFFER_OUTPUT -> glb_netwk_%d' + % GLB_NETWK_EXTERNAL_BLOCKS.index(self.xy + (n, ))] + + if t: + text = ['io_%d {' % n] + \ + [' %s' % s for s in t] + \ + ['}'] + \ + text + + return text + + def printout(self, options): + super().printout('io_tile', options) + +class IOCell: + pass + +class RAMBTile(Tile): + def __init__(self, fabric, xy): + super().__init__(fabric, xy, fabric.ic.ramb_tiles[xy], False) + if xy in fabric.ic.ram_data: + self.data = fabric.ic.ram_data[xy] + else: + self.data = None + + def get_hlc(self): + text = super().get_hlc() + if self.data is not None: + text.append('') + text.append('data {') + for line in self.data: + text.append(' ' + line) + text.append('}') + return text + + def printout(self, options): + super().printout('ramb_tile', options) + +class RAMTTile(Tile): + def __init__(self, fabric, xy): + super().__init__(fabric, xy, fabric.ic.ramt_tiles[xy], False) + + def printout(self, options): + super().printout('ramt_tile', options) + + +class Options: + def __init__(self): + self.print_map = False + self.print_all = False + +def main(): + program_short_name = os.path.basename(sys.argv[0]) + options = Options() + + try: + opts, args = getopt.getopt(sys.argv[1:], 'mA', ['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 a high-level representation from an ASCII bitstream. +Usage: %s [OPTION]... FILE + + -m print tile config bitmaps + -A don't skip uninteresting tiles + + --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_asc2hlc - create a high-level representation from an ASCII bitstream +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 opt == '-m': + options.print_map = True + elif opt == '-A': + options.print_all = True + + 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) + + ic = icebox.iceconfig() + if args[0] == '-': + ic.read_file('/dev/stdin') + else: + ic.read_file(args[0]) + + fabric = Fabric(ic) + fabric.printout(options) + +if __name__ == '__main__': + main() diff --git a/icebox/icebox_hlc2asc.py b/icebox/icebox_hlc2asc.py new file mode 100755 index 0000000..54d6722 --- /dev/null +++ b/icebox/icebox_hlc2asc.py @@ -0,0 +1,1052 @@ +#!/usr/bin/env python3 +# 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. + +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.buffers = [] + self.routings = [] + self.bits_set = set() + self.bits_cleared = set() + + def apply_directive(self, *fields): + fields = list(fields) + bits, = [entry[0] for entry in self.db if entry[1:] == 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") + + if set.intersection(bits_set, self.bits_cleared) or \ + set.intersection(bits_clear, self.bits_set): + raise ParseError("conflicting bits") + + 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] == '=': + 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] == '<->'): + prefix = 'lutff_%d/' % self.index + 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): + 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] == '<->'): + prefix = 'io_%d/' % self.index + if fields[0] in ('D_IN_0', 'D_IN_1'): + self.tile.read([prefix + fields[0]] + fields[1:]) + elif fields[-1] in ('CLOCK_ENABLE', + 'D_OUT_0', + 'D_OUT_1', + 'INPUT_CLK', + 'LATCH_INPUT_VALUE', + 'OUTPUT_CLK', + 'OUTPUT_ENABLE'): + 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 line in f: + fields = line.split('#')[0].split() + 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) + if len(stack) != 1: + raise ParseError + 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() diff --git a/icebox/tc_logic_xpr.py b/icebox/tc_logic_xpr.py new file mode 100644 index 0000000..e557caf --- /dev/null +++ b/icebox/tc_logic_xpr.py @@ -0,0 +1,42 @@ +# Test case for `icebox_asc2hlc' and `icebox_hlc2asc': Does conversion +# from LUT strings to logic expressions and back work correctly? +# 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. + +import sys +import icebox +from icebox_asc2hlc import lut_to_logic_expression +from icebox_hlc2asc import logic_expression_to_lut + +def main(): + sys.stderr.write("testing conversion from LUT strings " + "to logic expressions and back") + + for i in range(65536): + if i % 4096 == 0: + sys.stderr.write(".") + sys.stderr.flush() + + lut = bin(i)[2:].zfill(16) + s = lut_to_logic_expression(lut, ('a', 'b', 'c', 'd')) + l = logic_expression_to_lut(s, ('a', 'b', 'c', 'd')) + + if l != lut: + sys.stderr.write("\nERROR at LUT = %s\n" % lut) + sys.stderr.write("stringified = %s\n" % s) + sys.stderr.write("resulting LUT = %s\n" % l) + sys.exit(1) + + sys.stderr.write("\n") + +if __name__ == '__main__': + main() diff --git a/icebox/tc_rxlat_netnames.py b/icebox/tc_rxlat_netnames.py new file mode 100644 index 0000000..109dfa4 --- /dev/null +++ b/icebox/tc_rxlat_netnames.py @@ -0,0 +1,69 @@ +# Test case for `icebox_hlc2asc': Does net name translation work correctly? +# 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. + +import sys +import icebox +from icebox_asc2hlc import translate_netname +from icebox_hlc2asc import untranslate_netname + +def test_netname_translation(ic): + sys.stderr.write("testing backward netname translation " + "for the `%s' device...\n" % ic.device) + all_tiles = set() + for x in range(ic.max_x + 1): + for y in range(ic.max_y + 1): + if ic.tile(x, y) is not None: + all_tiles.add((x, y)) + + netnames = set() + failed = False + + for group in ic.group_segments(all_tiles, connect_gb = False): + is_span = set(net.startswith('sp') for x, y, net in group) + assert len(is_span) == 1 + if True not in is_span: + # only span nets are interesting here + continue + + netname = translate_netname(group[0][0], group[0][1], + ic.max_x - 1, ic.max_y - 1, group[0][2]) + if netname in netnames: + failed = True + print("duplicate netname: %s" % netname) + netnames.add(netname) + + for x, y, net in group: + s = untranslate_netname(x, y, ic.max_x - 1, ic.max_y - 1, netname) + if s != net: + failed = True + print("%-20s %s -> %s" % ("%d %d %s" % (x, y, net), netname, s)) + + if failed: + sys.stderr.write("ERROR\n") + sys.exit(1) + +def main(): + ic = icebox.iceconfig() + ic.setup_empty_384() + test_netname_translation(ic) + + ic = icebox.iceconfig() + ic.setup_empty_1k() + test_netname_translation(ic) + + ic = icebox.iceconfig() + ic.setup_empty_8k() + test_netname_translation(ic) + +if __name__ == '__main__': + main() diff --git a/icebox/tc_xlat_netnames.py b/icebox/tc_xlat_netnames.py new file mode 100644 index 0000000..53b73de --- /dev/null +++ b/icebox/tc_xlat_netnames.py @@ -0,0 +1,79 @@ +# Test case for `icebox_asc2hlc': Does net name translation work correctly? +# 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. + +import sys +import icebox +from icebox_asc2hlc import translate_netname + +def test_netname_translation(ic): + sys.stderr.write("testing forward netname translation " + "for the `%s' device...\n" % ic.device) + all_tiles = set() + for x in range(ic.max_x + 1): + for y in range(ic.max_y + 1): + if ic.tile(x, y) is not None: + all_tiles.add((x, y)) + + netnames = set() + failed = False + + for group in ic.group_segments(all_tiles, connect_gb = False): + is_span = set(net.startswith('sp') for x, y, net in group) + assert len(is_span) == 1 + if True not in is_span: + # only span nets are interesting here + continue + + s = set() + for seg in group: + s.add(translate_netname(seg[0], seg[1], + ic.max_x - 1, ic.max_y - 1, seg[2])) + if len(s) != 1: + failed = True + print("translated netnames don't match") + for seg in group: + print("%d %d %s" % seg, "->", + translate_netname(seg[0], seg[1], + ic.max_x - 1, ic.max_y - 1, seg[2])) + print() + + for dulpicate_netname in netnames.intersection(s): + failed = True + print("duplicate netname: %s" % dulpicate_netname) + for seg in group: + print("%d %d %s" % seg, "->", + translate_netname(seg[0], seg[1], + ic.max_x - 1, ic.max_y - 1, seg[2])) + print() + + netnames.update(s) + + if failed: + sys.stderr.write("ERROR\n") + sys.exit(1) + +def main(): + ic = icebox.iceconfig() + ic.setup_empty_384() + test_netname_translation(ic) + + ic = icebox.iceconfig() + ic.setup_empty_1k() + test_netname_translation(ic) + + ic = icebox.iceconfig() + ic.setup_empty_8k() + test_netname_translation(ic) + +if __name__ == '__main__': + main() -- cgit v1.2.3