aboutsummaryrefslogtreecommitdiffstats
path: root/icebox/icebox_asc2hlc.py
diff options
context:
space:
mode:
authorClifford Wolf <clifford@clifford.at>2017-08-25 14:59:59 +0200
committerClifford Wolf <clifford@clifford.at>2017-08-25 14:59:59 +0200
commit1565fdc0eecb24da27c513237b8e8e12dee21325 (patch)
treeee35e1f27a0ac79c5203bca7696ec329f9246dca /icebox/icebox_asc2hlc.py
parent873eb9effaef6f97df24ca8d7b6eae3721303454 (diff)
downloadicestorm-1565fdc0eecb24da27c513237b8e8e12dee21325.tar.gz
icestorm-1565fdc0eecb24da27c513237b8e8e12dee21325.tar.bz2
icestorm-1565fdc0eecb24da27c513237b8e8e12dee21325.zip
Implement high-level configuration format
# Conflicts: # icebox/Makefile
Diffstat (limited to 'icebox/icebox_asc2hlc.py')
-rwxr-xr-xicebox/icebox_asc2hlc.py1132
1 files changed, 1132 insertions, 0 deletions
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
+## <tt>(x, y)</tt>.
+
+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 <tt>(lut, args)</tt> 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 <tt>(lut, args)</tt> 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<row>[<col>]" or "!B<row>[<col>]" 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()