mirror of https://github.com/YosysHQ/icestorm.git
1179 lines
41 KiB
Python
Executable File
1179 lines
41 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (C) 2017 Roland Lutz
|
|
#
|
|
# Permission to use, copy, modify, and/or distribute this software for any
|
|
# purpose with or without fee is hereby granted, provided that the above
|
|
# copyright notice and this permission notice appear in all copies.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
import getopt, os, re, sys
|
|
import icebox
|
|
|
|
|
|
## 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 <tt>(x, y)</tt>.
|
|
#
|
|
# \return \c 'fabout' if it is the \c fabout net, otherwise the
|
|
# unchanged net name
|
|
|
|
def revert_to_fabout(ic, x, y, net):
|
|
if net.startswith('glb_netwk_'):
|
|
for gx, gy, gi in ic.gbufin_db():
|
|
if net == 'glb_netwk_%d' % gi and (x, y) == (gx, gy):
|
|
return 'fabout'
|
|
raise ParseError("{} is a global network, but not at an expected location {} {}".format(net, gx, gy))
|
|
|
|
return net
|
|
|
|
|
|
EXPR_AND, EXPR_XOR, EXPR_OR, EXPR_TERN, EXPR_NOT, EXPR_ZERO, EXPR_ONE = range(7)
|
|
|
|
## Evaluate a list representation of a logic expression for given
|
|
## input values.
|
|
#
|
|
# This is a helper function for \ref logic_expression_to_lut.
|
|
#
|
|
# \param expr list representation of a logic expression (see below)
|
|
# \param args list of boolean values representing the input values
|
|
#
|
|
# \result \c False or \c True, depending on the expression and arguments
|
|
#
|
|
# Expression | Result
|
|
# ---------------------------------------+----------------------------------
|
|
# <tt>i</tt> | value of argument \a i
|
|
# <tt>(EXPR_AND, [expr, ...])</tt> | AND operation of all expressions
|
|
# <tt>(EXPR_XOR, [expr, ...])</tt> | XOR operation of all expressions
|
|
# <tt>(EXPR_OR, [expr, ...])</tt> | OR operation of all expressions
|
|
# <tt>(EXPR_TERN, ex_a, ex_b, ex_c)</tt> | result of \c ex_b if \c ex_a
|
|
# | evaluates to \c True, otherwise
|
|
# | result of \c ex_c
|
|
# <tt>(EXPR_NOT, expr)</tt> | negated result of \a expr
|
|
# <tt>(EXPR_ZERO, )</tt> | \c False
|
|
# <tt>(EXPR_ONE, )</tt> | \c True
|
|
|
|
def evaluate(expr, args):
|
|
if type(expr) == int:
|
|
return args[expr]
|
|
|
|
op = expr[0]
|
|
|
|
if op == EXPR_AND:
|
|
assert len(expr) == 2
|
|
for o in expr[1]:
|
|
if not evaluate(o, args):
|
|
return False
|
|
return True
|
|
|
|
if op == EXPR_XOR:
|
|
assert len(expr) == 2
|
|
result = False
|
|
for o in expr[1]:
|
|
if evaluate(o, args):
|
|
result = not result
|
|
return result
|
|
|
|
if op == EXPR_OR:
|
|
assert len(expr) == 2
|
|
for o in expr[1]:
|
|
if evaluate(o, args):
|
|
return True
|
|
return False
|
|
|
|
if op == EXPR_TERN:
|
|
assert len(expr) == 4
|
|
if evaluate(expr[1], args):
|
|
return evaluate(expr[2], args)
|
|
else:
|
|
return evaluate(expr[3], args)
|
|
|
|
if op == EXPR_NOT:
|
|
assert len(expr) == 2
|
|
return not evaluate(expr[1], args)
|
|
|
|
if op == EXPR_ZERO:
|
|
assert len(expr) == 1
|
|
return False
|
|
|
|
if op == EXPR_ONE:
|
|
assert len(expr) == 1
|
|
return True
|
|
|
|
assert False # unknown operator
|
|
|
|
## Convert a logic expression to a LUT string.
|
|
#
|
|
# \param lut string containing a human-readable logic expression
|
|
# \param args list of N strings containing the names of the arguments
|
|
#
|
|
# \return a string of 2^N `0' or `1' characters representing the logic
|
|
# of an Nx1 look-up table equivalent to the logic expression
|
|
#
|
|
# Example: logic_expression_to_lut('a & b & !c', ['a', 'b', 'c']) -> '00010000'
|
|
|
|
def logic_expression_to_lut(s, args):
|
|
# make sure argument names are unique
|
|
assert len(set(args)) == len(args)
|
|
|
|
stack = [[None, None, None], [[], None, None]]
|
|
stack[0][2] = l = []; stack[1][0].append((EXPR_OR, l))
|
|
stack[0][1] = l = []; stack[0][2].append((EXPR_XOR, l))
|
|
stack[0][0] = l = []; stack[0][1].append((EXPR_AND, l))
|
|
expect_expr = True
|
|
negate_count = 0
|
|
|
|
i = 0
|
|
while i < len(s):
|
|
if s[i] == ' ':
|
|
pass
|
|
elif s[i] == '!':
|
|
assert expect_expr
|
|
negate_count += 1
|
|
elif s[i] == '&':
|
|
assert not expect_expr
|
|
|
|
expect_expr = True
|
|
negate_count = 0
|
|
elif s[i] == '^':
|
|
assert not expect_expr
|
|
|
|
stack[0][0] = l = []; stack[0][1].append((EXPR_AND, l))
|
|
|
|
expect_expr = True
|
|
negate_count = 0
|
|
elif s[i] == '|':
|
|
assert not expect_expr
|
|
|
|
stack[0][1] = l = []; stack[0][2].append((EXPR_XOR, l))
|
|
stack[0][0] = l = []; stack[0][1].append((EXPR_AND, l))
|
|
|
|
expect_expr = True
|
|
negate_count = 0
|
|
elif s[i] == '?':
|
|
assert not expect_expr
|
|
assert stack[1][0][-1][0] == EXPR_OR
|
|
stack[1][0][-1] = (EXPR_TERN, stack[1][0][-1], (EXPR_OR, []),
|
|
(EXPR_OR, []))
|
|
stack[0][2] = l = stack[1][0][-1][2][1]
|
|
stack[0][1] = l = []; stack[0][2].append((EXPR_XOR, l))
|
|
stack[0][0] = l = []; stack[0][1].append((EXPR_AND, l))
|
|
|
|
expect_expr = True
|
|
negate_count = 0
|
|
elif s[i] == ':':
|
|
assert not expect_expr
|
|
assert stack[1][0][-1][0] == EXPR_TERN
|
|
stack[0][2] = l = stack[1][0][-1][3][1]
|
|
stack[0][1] = l = []; stack[0][2].append((EXPR_XOR, l))
|
|
stack[0][0] = l = []; stack[0][1].append((EXPR_AND, l))
|
|
|
|
expect_expr = True
|
|
negate_count = 0
|
|
elif s[i] == '(':
|
|
assert expect_expr
|
|
|
|
stack.insert(0, [None, None, None])
|
|
|
|
stack[0][2] = l = []
|
|
if negate_count % 2:
|
|
stack[1][0].append((EXPR_NOT, (EXPR_OR, l)))
|
|
else:
|
|
stack[1][0].append((EXPR_OR, l))
|
|
|
|
stack[0][1] = l = []; stack[0][2].append((EXPR_XOR, l))
|
|
stack[0][0] = l = []; stack[0][1].append((EXPR_AND, l))
|
|
|
|
expect_expr = True
|
|
negate_count = 0
|
|
|
|
elif s[i] == ')':
|
|
assert not expect_expr
|
|
stack.pop(0)
|
|
|
|
elif s[i] == '0':
|
|
assert expect_expr
|
|
if negate_count % 2:
|
|
stack[0][0].append((EXPR_ONE, ))
|
|
else:
|
|
stack[0][0].append((EXPR_ZERO, ))
|
|
|
|
expect_expr = False
|
|
negate_count = None
|
|
|
|
elif s[i] == '1':
|
|
assert expect_expr
|
|
if negate_count % 2:
|
|
stack[0][0].append((EXPR_ZERO, ))
|
|
else:
|
|
stack[0][0].append((EXPR_ONE, ))
|
|
|
|
expect_expr = False
|
|
negate_count = None
|
|
|
|
else:
|
|
assert expect_expr
|
|
|
|
found = None
|
|
for j, arg in enumerate(args):
|
|
if s.startswith(arg, i):
|
|
found = j
|
|
i += len(arg)
|
|
break
|
|
assert found is not None
|
|
|
|
if negate_count % 2:
|
|
stack[0][0].append((EXPR_NOT, found))
|
|
else:
|
|
stack[0][0].append(found)
|
|
|
|
expect_expr = False
|
|
negate_count = None
|
|
continue
|
|
|
|
i += 1
|
|
|
|
assert len(stack) == 2
|
|
return ''.join('1' if evaluate(stack[1][0][0],
|
|
tuple(i & (1 << j) != 0
|
|
for j in range(len(args)))) else '0'
|
|
for i in range(1 << len(args)))
|
|
|
|
def parse_verilog_bitvector_to_bits(in_str):
|
|
#replace x with 0
|
|
in_str = re.sub('[xX]', '0', in_str)
|
|
|
|
m = re.match("([0-9]+)'([hdob])([0-9a-fA-F]+)", in_str)
|
|
if m:
|
|
num_bits = int(m.group(1))
|
|
prefix = m.group(2)
|
|
val_str = m.group(3)
|
|
|
|
if prefix == 'h':
|
|
val = eval('0x' + val_str)
|
|
elif prefix == 'd':
|
|
val = eval(val_str.lstrip('0'))
|
|
elif prefix == 'o' or prefix =='b':
|
|
val = eval('0' + prefix + val_str)
|
|
|
|
if val.bit_length() > num_bits:
|
|
raise ParseError("Number of bits({}) given don't match expected ({})"
|
|
.format(val.bit_length(), num_bits))
|
|
else:
|
|
val = eval('0x' + in_str)
|
|
num_bits = len(in_str) * 4
|
|
|
|
bit_str = bin(val)[2:]
|
|
# zero pad
|
|
nz = num_bits - len(bit_str)
|
|
bit_vec = nz*['0'] + list(bit_str)
|
|
return bit_vec
|
|
|
|
def parse_verilog_bitvector_to_hex(in_str):
|
|
if type(in_str) == str:
|
|
bits = parse_verilog_bitvector_to_bits(in_str)
|
|
else:
|
|
bits = in_str
|
|
# pad to 4
|
|
bits = ((4-len(bits)) % 4) * ['0'] + bits
|
|
res = ''.join([hex(eval('0b' + ''.join(bits[ii:ii+4])))[2:] for ii in range(0, len(bits), 4)])
|
|
return res
|
|
|
|
class ParseError(Exception):
|
|
pass
|
|
|
|
def parse_bool(s):
|
|
if s == 'on':
|
|
return True
|
|
if s == 'off':
|
|
return False
|
|
raise ParseError("Unable to parse '{}' as boolean".format(s))
|
|
|
|
class Main:
|
|
def __init__(self):
|
|
self.ic = None
|
|
self.device = None
|
|
#self.coldboot = None
|
|
self.warmboot = None
|
|
self.tiles = {}
|
|
|
|
def read(self, fields):
|
|
if fields[0] == 'device' and len(fields) == 4 \
|
|
and len(fields[1]) >= 2 and fields[1][0] == '"' \
|
|
and fields[1][-1] == '"' \
|
|
and self.ic is None and self.device is None:
|
|
self.device = fields[1][1:-1].lower()
|
|
if self.device.startswith('lp') or self.device.startswith('hx'):
|
|
self.device = self.device[2:]
|
|
if self.device.startswith('1k'):
|
|
self.ic = icebox.iceconfig()
|
|
self.ic.setup_empty_1k()
|
|
elif self.device.startswith('8k'):
|
|
self.ic = icebox.iceconfig()
|
|
self.ic.setup_empty_8k()
|
|
elif self.device.startswith('384'):
|
|
self.ic = icebox.iceconfig()
|
|
self.ic.setup_empty_384()
|
|
else:
|
|
raise ParseError("Unknown device {}".format(self.device))
|
|
|
|
#elif fields[0] == 'coldboot' and fields[1] == '=' \
|
|
# and self.coldboot is None:
|
|
# # parsed but ignored (can't be represented in IceStorm .asc format)
|
|
# self.coldboot = parse_bool(fields[2])
|
|
elif fields[0] == 'warmboot' and fields[1] == '=' \
|
|
and self.warmboot is None:
|
|
# parsed but ignored (can't be represented in IceStorm .asc format)
|
|
self.warmboot = parse_bool(fields[2])
|
|
else:
|
|
raise ParseError("Unknown preamble directive {}".format(fields[0]))
|
|
|
|
def new_block(self, fields):
|
|
if len(fields) != 3:
|
|
raise ParseError("Expect 3 fields for top block. Received: {}".format(fields))
|
|
x = int(fields[1])
|
|
y = int(fields[2])
|
|
if (x, y) in self.tiles:
|
|
return self.tiles[x, y]
|
|
if fields[0] == 'logic_tile':
|
|
if (x, y) not in self.ic.logic_tiles:
|
|
raise ParseError("{} position({},{}) not in defined list for device".format(fields[0], x, y))
|
|
tile = LogicTile(self.ic, x, y)
|
|
elif fields[0] == 'ramb_tile':
|
|
if (x, y) not in self.ic.ramb_tiles:
|
|
raise ParseError("{} position({},{}) not in defined list for device".format(fields[0], x, y))
|
|
tile = RAMBTile(self.ic, x, y)
|
|
elif fields[0] == 'ramt_tile':
|
|
if (x, y) not in self.ic.ramt_tiles:
|
|
raise ParseError("{} position({},{}) not in defined list for device".format(fields[0], x, y))
|
|
tile = RAMTTile(self.ic, x, y)
|
|
elif fields[0] == 'io_tile':
|
|
if (x, y) not in self.ic.io_tiles:
|
|
raise ParseError("{} position({},{}) not in defined list for device".format(fields[0], x, y))
|
|
tile = IOTile(self.ic, x, y)
|
|
else:
|
|
raise ParseError("Unknown tile type {}".format(fields[0]))
|
|
self.tiles[x, y] = tile
|
|
return tile
|
|
|
|
def writeout(self):
|
|
if self.ic is None:
|
|
raise ParseError("iceconfig not set")
|
|
|
|
# fix up IE/REN bits
|
|
unused_ieren = set()
|
|
|
|
for x in range(1, self.ic.max_x):
|
|
unused_ieren.add((x, 0, 0))
|
|
unused_ieren.add((x, 0, 1))
|
|
unused_ieren.add((x, self.ic.max_y, 0))
|
|
unused_ieren.add((x, self.ic.max_y, 1))
|
|
|
|
for y in range(1, self.ic.max_y):
|
|
unused_ieren.add((0, y, 0))
|
|
unused_ieren.add((0, y, 1))
|
|
unused_ieren.add((self.ic.max_x, y, 0))
|
|
unused_ieren.add((self.ic.max_x, y, 1))
|
|
|
|
for x0, y0, b0, x1, y1, b1 in self.ic.ieren_db():
|
|
if (x0, y0) in self.tiles:
|
|
io_tile = self.tiles[x0, y0]
|
|
else:
|
|
io_tile = IOTile(self.ic, x0, y0)
|
|
self.tiles[x0, y0] = io_tile
|
|
if io_tile.blocks[b0] is not None:
|
|
io_block = io_tile.blocks[b0]
|
|
else:
|
|
io_block = IOBlock(io_tile, b0)
|
|
io_tile.blocks[b0] = io_block
|
|
|
|
if (x1, y1) in self.tiles:
|
|
ieren_tile = self.tiles[x1, y1]
|
|
else:
|
|
ieren_tile = IOTile(self.ic, x1, y1)
|
|
self.tiles[x1, y1] = ieren_tile
|
|
|
|
if io_block.enable_input != (self.ic.device == '1k'):
|
|
ieren_tile.apply_directive('IoCtrl', 'IE_%d' % b1)
|
|
if io_block.disable_pull_up:
|
|
ieren_tile.apply_directive('IoCtrl', 'REN_%d' % b1)
|
|
|
|
unused_ieren.remove((x1, y1, b1))
|
|
|
|
if self.ic.device == '1k':
|
|
for x1, y1, b1 in unused_ieren:
|
|
if (x1, y1) in self.tiles:
|
|
ieren_tile = self.tiles[x1, y1]
|
|
else:
|
|
ieren_tile = IOTile(self.ic, x1, y1)
|
|
self.tiles[x1, y1] = ieren_tile
|
|
ieren_tile.apply_directive('IoCtrl', 'IE_%d' % b1)
|
|
|
|
# fix up RAMB power-up bits
|
|
|
|
for x, y in self.ic.ramb_tiles:
|
|
if (x, y) in self.tiles:
|
|
tile = self.tiles[x, y]
|
|
else:
|
|
tile = RAMBTile(self.ic, x, y)
|
|
self.tiles[x, y] = tile
|
|
|
|
if tile.power_up != (self.ic.device == '1k'):
|
|
tile.apply_directive('RamConfig', 'PowerUp')
|
|
|
|
# enable column buffers
|
|
colbuf_db = self.ic.colbuf_db()
|
|
for x, y in list(self.tiles):
|
|
for src, dst in self.tiles[x, y].buffers + \
|
|
self.tiles[x, y].routings:
|
|
if not src.startswith('glb_netwk_'):
|
|
continue
|
|
driving_xy = [(src_x, src_y)
|
|
for src_x, src_y, dst_x, dst_y in colbuf_db
|
|
if dst_x == x and dst_y == y]
|
|
assert len(driving_xy) == 1
|
|
driving_xy, = driving_xy
|
|
|
|
if driving_xy not in self.tiles:
|
|
if driving_xy in self.ic.logic_tiles:
|
|
tile = LogicTile(self.ic, *driving_xy)
|
|
elif driving_xy in self.ic.ramb_tiles:
|
|
tile = RAMBTile(self.ic, *driving_xy)
|
|
elif driving_xy in self.ic.ramt_tiles:
|
|
tile = RAMTTile(self.ic, *driving_xy)
|
|
elif driving_xy in self.ic.io_tiles:
|
|
tile = IOTile(self.ic, *driving_xy)
|
|
else:
|
|
assert False
|
|
self.tiles[driving_xy] = tile
|
|
|
|
self.tiles[driving_xy].apply_directive('ColBufCtrl', src)
|
|
|
|
self.ic.write_file('/dev/stdout')
|
|
|
|
class Tile:
|
|
def __init__(self, ic, x, y):
|
|
self.ic = ic
|
|
self.x = x
|
|
self.y = y
|
|
self.data = ic.tile(x, y)
|
|
self.db = ic.tile_db(x, y)
|
|
|
|
self.bits_lookup = {}
|
|
def add_entry(entry, bits):
|
|
entry = tuple(entry)
|
|
if entry in self.bits_lookup:
|
|
if self.bits_lookup[entry] == bits:
|
|
logging.warn(
|
|
"{} {} - {} (adding) != {} (existing)".format(
|
|
(x, y), entry, bits, self.bits_lookup[entry]))
|
|
self.bits_lookup[entry] = bits
|
|
|
|
for bits, *entry in self.db:
|
|
if not ic.tile_has_entry(x, y, (bits, *entry)):
|
|
continue
|
|
add_entry(entry, bits)
|
|
|
|
self.buffers = []
|
|
self.routings = []
|
|
self.bits_set = set()
|
|
self.bits_cleared = set()
|
|
|
|
def __str__(self):
|
|
return "{}({}, {}, {})".format(self.__class__.__name__, self.ic.device, self.x, self.y)
|
|
|
|
def apply_directive(self, *fields):
|
|
if fields not in self.bits_lookup:
|
|
print("Possible matches:", file=sys.stderr)
|
|
for bits, *entry in self.db:
|
|
if entry[0] in ("routing", "buffer"):
|
|
if entry[1] == fields[1]:
|
|
print(" ", entry, bits, file=sys.stderr)
|
|
elif entry[1] == fields[2]:
|
|
print(" ", entry, bits, file=sys.stderr)
|
|
elif entry[2] == fields[2]:
|
|
print(" ", entry, bits, file=sys.stderr)
|
|
elif entry[2] == fields[1]:
|
|
print(" ", entry, bits, file=sys.stderr)
|
|
raise ParseError("No bit pattern for {} in {}".format(fields, self))
|
|
bits = self.bits_lookup[fields]
|
|
self.set_bits(bits)
|
|
|
|
def set_bits(self, bits):
|
|
bits_set = set()
|
|
bits_clear = set()
|
|
|
|
for bit in bits:
|
|
match = re.match(r'(!?)B(\d+)\[(\d+)\]$', bit)
|
|
if not match:
|
|
raise ValueError("invalid bit description: %s" % bit)
|
|
if match.group(1):
|
|
bits_clear.add((int(match.group(2)), int(match.group(3))))
|
|
else:
|
|
bits_set.add((int(match.group(2)), int(match.group(3))))
|
|
|
|
if set.intersection(bits_set, bits_clear):
|
|
raise ValueError(
|
|
"trying to set/clear the same bit(s) at once set:{} clear:{}".format(
|
|
bits_set, bits_clear))
|
|
|
|
if set.intersection(bits_set, self.bits_cleared) or \
|
|
set.intersection(bits_clear, self.bits_set):
|
|
raise ParseError("""\
|
|
conflicting bits {}
|
|
setting:{:<30} - current clear:{}
|
|
clearing:{:<30} - current set :{}""".format(
|
|
bits,
|
|
str(bits_set), self.bits_cleared,
|
|
str(bits_clear), self.bits_set))
|
|
|
|
self.bits_set.update(bits_set)
|
|
self.bits_cleared.update(bits_clear)
|
|
|
|
for row, col in bits_set:
|
|
assert row < len(self.data)
|
|
assert col < len(self.data[row])
|
|
self.data[row] = self.data[row][:col] + '1' + \
|
|
self.data[row][col + 1:]
|
|
|
|
def read(self, fields):
|
|
if len(fields) == 3 and fields[1] == '->':
|
|
src = untranslate_netname(self.x, self.y,
|
|
self.ic.max_x - 1,
|
|
self.ic.max_y - 1, fields[0])
|
|
dst = untranslate_netname(self.x, self.y,
|
|
self.ic.max_x - 1,
|
|
self.ic.max_y - 1, fields[2])
|
|
dst = revert_to_fabout(self.ic, self.x, self.y, dst)
|
|
if (src, dst) not in self.buffers:
|
|
self.buffers.append((src, dst))
|
|
self.apply_directive('buffer', src, dst)
|
|
elif len(fields) == 3 and fields[1] == '~>':
|
|
src = untranslate_netname(self.x, self.y,
|
|
self.ic.max_x - 1,
|
|
self.ic.max_y - 1, fields[0])
|
|
dst = untranslate_netname(self.x, self.y,
|
|
self.ic.max_x - 1,
|
|
self.ic.max_y - 1, fields[2])
|
|
dst = revert_to_fabout(self.ic, self.x, self.y, dst)
|
|
if (src, dst) not in self.routings:
|
|
self.routings.append((src, dst))
|
|
self.apply_directive('routing', src, dst)
|
|
elif len(fields) >= 5 and (fields[1] == '->' or fields[1] == '~>'):
|
|
self.read(fields[:3])
|
|
self.read(fields[2:])
|
|
else:
|
|
raise ParseError("Unknown Tile specification format")
|
|
|
|
def new_block(self, fields):
|
|
raise ParseError("Unepxected new block in {}".format(type(self).__name__))
|
|
|
|
class LogicTile(Tile):
|
|
def __init__(self, ic, x, y):
|
|
super().__init__(ic, x, y)
|
|
self.cells = [None, None, None, None, None, None, None, None]
|
|
self.neg_clk = False
|
|
self.carry_in_set = False # not in global bit list?!
|
|
|
|
def read(self, fields):
|
|
if fields == ['NegClk'] and not self.neg_clk:
|
|
self.neg_clk = True
|
|
self.apply_directive('NegClk')
|
|
elif fields == ['CarryInSet'] and not self.carry_in_set:
|
|
self.carry_in_set = True
|
|
self.apply_directive('CarryInSet')
|
|
else:
|
|
super().read(fields)
|
|
|
|
def new_block(self, fields):
|
|
for i in range(8):
|
|
if fields == ['lutff_%d' % i] and self.cells[i] is None:
|
|
self.cells[i] = LogicCell(self, i)
|
|
return self.cells[i]
|
|
raise ParseError("Unepxected new block in {}".format(type(self).__name__))
|
|
|
|
class LogicCell:
|
|
def __init__(self, tile, index):
|
|
self.tile = tile
|
|
self.index = index
|
|
self.lut_bits = ['0'] * 16
|
|
self.seq_bits = ['0'] * 4
|
|
|
|
def read(self, fields):
|
|
if fields[0] == 'lut' and len(fields) == 2 and self.lut_bits is None:
|
|
self.lut_bits = fields[1]
|
|
elif fields[0] == 'out' and len(fields) >= 3 and fields[1] == '=':
|
|
m = re.match("([0-9]+)'b([01]+)", fields[2])
|
|
if m:
|
|
lut_bits = parse_verilog_bitvector_to_bits(fields[2])
|
|
# Verilog 16'bXXXX is MSB first but the bitstream wants LSB.
|
|
self.lut_bits = list(lut_bits[::-1])
|
|
else:
|
|
self.lut_bits = logic_expression_to_lut(
|
|
' '.join(fields[2:]), ('in_0', 'in_1', 'in_2', 'in_3'))
|
|
elif fields == ['enable_carry']:
|
|
self.seq_bits[0] = '1'
|
|
elif fields == ['enable_dff']:
|
|
self.seq_bits[1] = '1'
|
|
elif fields == ['set_noreset']:
|
|
self.seq_bits[2] = '1'
|
|
elif fields == ['async_setreset']:
|
|
self.seq_bits[3] = '1'
|
|
elif len(fields) > 3 and (fields[1] == '->' or fields[1] == '~>'):
|
|
self.read(fields[:3])
|
|
self.read(fields[2:])
|
|
return
|
|
elif len(fields) == 3 and (fields[1] == '->' or fields[1] == '~>'):
|
|
prefix = 'lutff_%d/' % self.index
|
|
|
|
# Strip prefix if it is given
|
|
if fields[0].startswith(prefix):
|
|
fields[0] = fields[0][len(prefix):]
|
|
if fields[-1].startswith(prefix):
|
|
fields[-1] = fields[-1][len(prefix):]
|
|
|
|
if fields[0] == 'out':
|
|
self.tile.read([prefix + fields[0]] + fields[1:])
|
|
elif fields[-1].startswith('in_'):
|
|
self.tile.read(fields[:-1] + [prefix + fields[-1]])
|
|
else:
|
|
self.tile.read(fields)
|
|
return
|
|
|
|
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("Unepxected new block in {}".format(type(self).__name__))
|
|
|
|
class RAMData:
|
|
def __init__(self, data):
|
|
self.data = data
|
|
|
|
def read(self, fields):
|
|
if len(fields) == 1:
|
|
self.data.append(parse_verilog_bitvector_to_hex(fields[0]))
|
|
else:
|
|
raise ParseError("Unepxected format in {}".format(type(self).__name__))
|
|
|
|
def new_block(self, fields):
|
|
raise ParseError("Unepxected new block in {}".format(type(self).__name__))
|
|
|
|
class RAMBTile(Tile):
|
|
def __init__(self, ic, x, y):
|
|
super().__init__(ic, x, y)
|
|
self.power_up = False
|
|
|
|
def read(self, fields):
|
|
if fields == ['power_up'] and not self.power_up:
|
|
self.power_up = True
|
|
else:
|
|
super().read(fields)
|
|
|
|
def new_block(self, fields):
|
|
if fields == ['data'] and (self.x, self.y) not in self.ic.ram_data:
|
|
self.ic.ram_data[self.x, self.y] = data = []
|
|
return RAMData(data)
|
|
raise ParseError("Unepxected new block in {}".format(type(self).__name__))
|
|
|
|
class RAMTTile(Tile):
|
|
def __init__(self, ic, x, y):
|
|
super().__init__(ic, x, y)
|
|
|
|
def read(self, fields):
|
|
if fields == ['NegClk'] or fields[0] == 'RamConfig':
|
|
self.apply_directive(*fields) # TODO
|
|
else:
|
|
super().read(fields)
|
|
|
|
class IOTile(Tile):
|
|
def __init__(self, ic, x, y):
|
|
super().__init__(ic, x, y)
|
|
self.blocks = [None, None]
|
|
|
|
def read(self, fields):
|
|
if len(fields) == 2 and fields[0] == 'PLL':
|
|
self.apply_directive(*fields) # TODO
|
|
else:
|
|
super().read(fields)
|
|
|
|
def new_block(self, fields):
|
|
if fields == ['io_0'] and self.blocks[0] is None:
|
|
self.blocks[0] = IOBlock(self, 0)
|
|
return self.blocks[0]
|
|
if fields == ['io_1'] and self.blocks[1] is None:
|
|
self.blocks[1] = IOBlock(self, 1)
|
|
return self.blocks[1]
|
|
raise ParseError("Unexpected new block in {}".format(type(self).__name__))
|
|
|
|
class IOBlock:
|
|
def __init__(self, tile, index):
|
|
self.tile = tile
|
|
self.index = index
|
|
self.input_pin_type = None
|
|
self.output_pin_type = None
|
|
self.enable_input = False
|
|
self.disable_pull_up = False
|
|
|
|
def read(self, fields):
|
|
if fields[0] == 'input_pin_type' and fields[1] == '=' \
|
|
and len(fields) == 3 and self.input_pin_type is None:
|
|
self.input_pin_type = [
|
|
'registered_pin',
|
|
'simple_input_pin',
|
|
'latched_registered_pin',
|
|
'latched_pin'].index(fields[2])
|
|
for i in range(2):
|
|
if self.input_pin_type & 1 << i:
|
|
self.tile.apply_directive('IOB_%d' % self.index,
|
|
'PINTYPE_%d' % i)
|
|
elif fields[0] == 'output_pin_type' and fields[1] == '=' \
|
|
and len(fields) == 3 and self.output_pin_type is None:
|
|
self.output_pin_type = [
|
|
'no_output',
|
|
'1',
|
|
'2',
|
|
'3',
|
|
'DDR',
|
|
'REGISTERED',
|
|
'simple_output_pin',
|
|
'REGISTERED_INVERTED',
|
|
'DDR_ENABLE',
|
|
'REGISTERED_ENABLE',
|
|
'OUTPUT_TRISTATE',
|
|
'REGISTERED_ENABLE_INVERTED',
|
|
'DDR_ENABLE_REGISTERED',
|
|
'REGISTERED_ENABLE_REGISTERED',
|
|
'ENABLE_REGISTERED',
|
|
'REGISTERED_ENABLE_REGISTERED_INVERTED'].index(fields[2])
|
|
for i in range(4):
|
|
if self.output_pin_type & 1 << i:
|
|
self.tile.apply_directive('IOB_%d' % self.index,
|
|
'PINTYPE_%d' % (i + 2))
|
|
elif fields == ['enable_input'] and not self.enable_input:
|
|
self.enable_input = True
|
|
elif fields == ['disable_pull_up'] and not self.disable_pull_up:
|
|
self.disable_pull_up = True
|
|
elif fields[0] in ('GLOBAL_BUFFER_OUTPUT',
|
|
'io_%d/GLOBAL_BUFFER_OUTPUT' % self.index) \
|
|
and fields[1] == '->' \
|
|
and fields[2].startswith('glb_netwk_'):
|
|
|
|
glb_net = int(fields[2][10:])
|
|
padin_loc = self.tile.ic.padin_pio_db()[glb_net]
|
|
|
|
if padin_loc != (self.tile.x, self.tile.y, self.index):
|
|
raise ParseError("""\
|
|
GLOBAL_BUFFER_OUTPUT not valid for glb_netwk_{}
|
|
Driver at io_tile {},{} io{}
|
|
Should be at io_tile {},{} io{}
|
|
""".format(glb_net, self.tile.x, self.tile.y, self.index, *padin_loc))
|
|
bit = [bit for bit in self.tile.ic.extra_bits_db()
|
|
if self.tile.ic.extra_bits_db()[bit]
|
|
== ("padin_glb_netwk", fields[2][10:])]
|
|
assert len(bit) == 1
|
|
self.tile.ic.extra_bits.add(bit[0])
|
|
elif len(fields) > 3 and (fields[1] == '->' or fields[1] == '~>'):
|
|
self.read(fields[:3])
|
|
self.read(fields[2:])
|
|
elif len(fields) == 3 and (fields[1] == '->' or fields[1] == '~>'):
|
|
prefix = 'io_%d/' % self.index
|
|
|
|
# Strip prefix if it is given
|
|
if fields[0].startswith(prefix):
|
|
fields[0] = fields[0][len(prefix):]
|
|
if fields[-1].startswith(prefix):
|
|
fields[-1] = fields[-1][len(prefix):]
|
|
|
|
if fields[0] in ('D_IN_0', 'D_IN_1'):
|
|
self.tile.read([prefix + fields[0]] + fields[1:])
|
|
elif fields[-1] in ('cen',
|
|
'D_OUT_0',
|
|
'D_OUT_1',
|
|
'inclk',
|
|
#'LATCH_INPUT_VALUE',
|
|
'outclk',
|
|
'OUT_ENB'):
|
|
self.tile.read(fields[:-1] + [prefix + fields[-1]])
|
|
else:
|
|
self.tile.read(fields)
|
|
else:
|
|
raise ParseError("Unknown IOBlock specification format: {}".format(fields))
|
|
|
|
|
|
def new_block(self, fields):
|
|
raise ParseError("Unepxected new block in {}".format(type(self).__name__))
|
|
|
|
def main1(path):
|
|
f = open(path, 'r')
|
|
stack = [Main()]
|
|
for i, line in enumerate(f):
|
|
fields = line.split('#')[0].split()
|
|
try:
|
|
if not fields:
|
|
pass # empty line
|
|
elif fields == ['}']:
|
|
stack.pop()
|
|
if not stack:
|
|
raise ParseError("Parsing stack empty before expected")
|
|
elif fields[-1] == '{':
|
|
stack.append(stack[-1].new_block(fields[:-1]))
|
|
else:
|
|
stack[-1].read(fields)
|
|
except ParseError as e:
|
|
sys.stderr.write("Parse error in line %d:\n" % (i + 1))
|
|
sys.stderr.write(line)
|
|
if e.args:
|
|
sys.stderr.write("\n")
|
|
print(*e.args, file=sys.stderr)
|
|
sys.exit(1)
|
|
if len(stack) != 1:
|
|
sys.stderr.write("Parse error: unexpected end of file")
|
|
sys.exit(1)
|
|
f.close()
|
|
|
|
stack[0].writeout()
|
|
|
|
def main():
|
|
program_short_name = os.path.basename(sys.argv[0])
|
|
|
|
try:
|
|
opts, args = getopt.getopt(sys.argv[1:], '', ['help', 'version'])
|
|
except getopt.GetoptError as e:
|
|
sys.stderr.write("%s: %s\n" % (program_short_name, e.msg))
|
|
sys.stderr.write("Try `%s --help' for more information.\n"
|
|
% 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()
|