| import math |
| import os |
| import random |
| import re |
| from utils.roi import Roi |
| from jinja2 import Environment, FileSystemLoader |
| |
| |
| def get_jinja_template(tpl_file, directory="./"): |
| env = Environment(loader=FileSystemLoader(searchpath=directory)) |
| return env.get_template(tpl_file) |
| |
| |
| def render_template(tpl_path, variables=dict()): |
| ''' |
| Render text for jinja template. |
| |
| Args: |
| tpl_path : path of the template |
| variables : dictionary with variable to fill the template with |
| |
| Example: |
| render_template("path/to/template/test.tpl", {"key1": value, "key2": value}) |
| ''' |
| |
| (directory, file_name) = os.path.split(tpl_path) |
| template = get_jinja_template(file_name, directory if directory else "./") |
| return template.render(variables) |
| |
| |
| def get_db_root(): |
| # Used during tilegrid db bootstrap |
| ret = os.getenv("URAY_DATABASE_ROOT", None) |
| if ret: |
| return ret |
| |
| return "%s/%s" % (os.getenv("URAY_DATABASE_DIR"), |
| os.getenv("URAY_DATABASE")) |
| |
| |
| def get_part(): |
| ret = os.getenv("URAY_PART", None) |
| |
| return ret |
| |
| |
| def roi_xy(): |
| x1 = int(os.getenv('URAY_ROI_GRID_X1', 0)) |
| x2 = int(os.getenv('URAY_ROI_GRID_X2', 58)) |
| y1 = int(os.getenv('URAY_ROI_GRID_Y1', 0)) |
| y2 = int(os.getenv('URAY_ROI_GRID_Y2', 52)) |
| |
| return (x1, x2), (y1, y2) |
| |
| |
| def create_xy_fun(prefix): |
| """ Create function that extracts X and Y coordinate from a prefixed string |
| |
| >>> fun = create_xy_fun(prefix='') |
| >>> fun('X5Y23') |
| (5, 23) |
| >>> fun('X0Y0') |
| (0, 0) |
| >>> fun('X50Y100') |
| (50, 100) |
| |
| >>> fun = create_xy_fun(prefix='SITE_') |
| >>> fun('SITE_X5Y23') |
| (5, 23) |
| >>> fun('SITE_X0Y0') |
| (0, 0) |
| >>> fun('SITE_X50Y100') |
| (50, 100) |
| |
| """ |
| compiled_re = re.compile( |
| '^{prefix}X([0-9]+)Y([0-9]+)$'.format(prefix=prefix)) |
| |
| def get_xy(s): |
| m = compiled_re.match(s) |
| assert m, (prefix, s) |
| |
| x = int(m.group(1)) |
| y = int(m.group(2)) |
| |
| return x, y |
| |
| return get_xy |
| |
| |
| def slice_xy(): |
| '''Return (X1, X2), (Y1, Y2) from URAY_ROI, exclusive end (for xrange)''' |
| # SLICE_X12Y100:SLICE_X27Y149 |
| # Note URAY_ROI_GRID_* is something else |
| m = re.match(r'SLICE_X([0-9]*)Y([0-9]*):SLICE_X([0-9]*)Y([0-9]*)', |
| os.getenv('URAY_ROI')) |
| ms = [int(m.group(i + 1)) for i in range(4)] |
| return ((ms[0], ms[2] + 1), (ms[1], ms[3] + 1)) |
| |
| |
| def get_roi(): |
| from .db import Database |
| (x1, x2), (y1, y2) = roi_xy() |
| db = Database(get_db_root(), get_part()) |
| return Roi(db=db, x1=x1, x2=x2, y1=y1, y2=y2) |
| |
| |
| def gen_sites_xy(site_types): |
| for _tile_name, site_name, _site_type in get_roi().gen_sites(site_types): |
| m = re.match(r'.*_X([0-9]*)Y([0-9]*)', site_name) |
| x, y = int(m.group(1)), int(m.group(2)) |
| yield (site_name, (x, y)) |
| |
| |
| def site_xy_minmax(site_types): |
| '''Return (X1, X2), (Y1, Y2) from XY_ROI, exclusive end (for xrange)''' |
| xmin = 9999 |
| xmax = -1 |
| ymin = 9999 |
| ymax = -1 |
| for _site_name, (x, y) in gen_sites_xy(site_types): |
| xmin = min(xmin, x) |
| xmax = max(xmax, x) |
| ymin = min(ymin, y) |
| ymax = max(ymax, y) |
| return (xmin, xmax + 1), (ymin, ymax + 1) |
| |
| |
| # we know that all bits for CLB MUXes are in frames 30 and 31, so filter all other bits |
| def bitfilter_clb_mux(frame_idx, bit_idx): |
| return frame_idx in [30, 31] |
| |
| |
| def db_root_arg(parser): |
| database_dir = os.getenv("URAY_DATABASE_DIR") |
| database = os.getenv("URAY_DATABASE") |
| db_root_kwargs = {} |
| if database_dir is None or database is None: |
| db_root_kwargs['required'] = True |
| else: |
| db_root_kwargs['required'] = False |
| db_root_kwargs['default'] = os.path.join(database_dir, database) |
| parser.add_argument('--db-root', help="Database root.", **db_root_kwargs) |
| |
| |
| def part_arg(parser): |
| part = os.getenv("URAY_PART") |
| part_kwargs = {} |
| if part is None: |
| part_kwargs['required'] = True |
| else: |
| part_kwargs['required'] = False |
| part_kwargs['default'] = part |
| parser.add_argument( |
| '--part', |
| help="Part name. When not given defaults to URAY_PART env. var.", |
| **part_kwargs) |
| |
| |
| def parse_db_line(line): |
| '''Return tag name, bit values (if any), mode (if any)''' |
| parts = line.split() |
| # Ex: CLBLL_L.SLICEL_X0.AMUX.A5Q |
| assert len(parts), "Empty line" |
| tag = parts[0] |
| if tag == 'bit': |
| raise ValueError("Wanted bits db but got mask db") |
| assert re.match(r'[A-Z0-9_.]+', |
| tag), "Invalid tag name: %s, line: %s" % (tag, line) |
| orig_bits = line.replace(tag + " ", "") |
| if re.match(r'^origin:', orig_bits): |
| origin = parts[1][7:] |
| bits = frozenset(parts[2:]) |
| orig_bits = line.replace(tag + " " + origin + " ", "") |
| else: |
| origin = None |
| bits = frozenset(parts[1:]) |
| # <0 candidates> etc |
| # Ex: INT_L.BYP_BOUNCE5.BYP_ALT5 always |
| if "<" in orig_bits or "always" == orig_bits: |
| return tag, None, orig_bits, origin |
| |
| # Ex: CLBLL_L.SLICEL_X0.AOUTMUX.A5Q !30_06 !30_08 !30_11 30_07 |
| for bit in bits: |
| # 19_39 |
| # 100_319 |
| assert re.match(r'[!]*[0-9]+_[0-9]+', bit), "Invalid bit: %s" % bit |
| return tag, bits, None, origin |
| |
| |
| def parse_db_lines(fn): |
| with open(fn, "r") as f: |
| for line in f: |
| yield line, parse_db_line(line) |
| |
| |
| def write_db_lines(fn, entries, track_origin=False): |
| new_lines = [] |
| for tag, (bits, origin) in entries.items(): |
| if track_origin: |
| assert origin is not None |
| new_line = " ".join([tag, "origin:" + origin] + sorted(bits)) |
| else: |
| new_line = " ".join([tag] + sorted(bits)) |
| new_lines.append(new_line) |
| |
| with open(fn, "w") as f: |
| for line in sorted(new_lines): |
| print(line, file=f) |
| |
| |
| def parse_tagbit(x): |
| # !30_07 |
| if x[0] == '!': |
| isset = False |
| numstr = x[1:] |
| else: |
| isset = True |
| numstr = x |
| frame, word = numstr.split("_") |
| # second part forms a tuple refereced in sets |
| return (isset, (int(frame, 10), int(word, 10))) |
| |
| |
| def addr_bit2word(bitaddr): |
| word = bitaddr // 32 |
| bit = bitaddr % 32 |
| return word, bit |
| |
| |
| def addr2str(addr, word, bit): |
| # Make like .bits file: bit_00020b14_073_05 |
| # also similar to .db file: CLBLL_L.SLICEL_X0.CEUSEDMUX 01_39 |
| assert 0 <= bit <= 31 |
| return "%08x_%03u_%02u" % (addr, word, bit) |
| |
| |
| # matches lib/include/prjURAY/xilinx/xc7series/block_type.h |
| block_type_i2s = { |
| 0: 'CLB_IO_CLK', |
| 1: 'BLOCK_RAM', |
| 2: 'CFG_CLB', |
| # special...maybe should error until we know what it is? |
| # 3: 'RESERVED', |
| } |
| block_type_s2i = {} |
| for k, v in block_type_i2s.items(): |
| block_type_s2i[v] = k |
| |
| |
| def addr2btype(base_addr): |
| ''' |
| Convert integer address to block type |
| |
| Table 5-24: Frame Address Register Description |
| Bit Index: [25:23] |
| https://www.xilinx.com/support/documentation/user_guides/ug470_7Series_Config.pdf |
| "Valid block types are CLB, I/O, CLK ( 000 ), block RAM content ( 001 ), and CFG_CLB ( 010 ). A normal bitstream does not include type 011 ." |
| ''' |
| block_type_i = (base_addr >> 23) & 0x7 |
| return block_type_i2s[block_type_i] |
| |
| |
| def specn(): |
| # ex: build/specimen_001 |
| specdir = os.getenv("SPECDIR") |
| return int(re.match(".*specimen_([0-9]*)", specdir).group(1), 10) |
| |
| |
| def gen_fuzz_choices(nvals): |
| next_p2_states = 2**math.ceil(math.log(nvals, 2)) |
| n = next_p2_states |
| |
| full_mask = 2**next_p2_states - 1 |
| |
| choices = [] |
| invert_choices = [] |
| |
| num_or = 1 |
| while n > 0: |
| mask = 2**n - 1 |
| |
| val = 0 |
| |
| for offset in range(0, num_or, 2): |
| shift = offset * next_p2_states // num_or |
| val |= mask << shift |
| |
| choices.append(full_mask ^ val) |
| invert_choices.append(val) |
| |
| n //= 2 |
| num_or *= 2 |
| |
| choices.extend(invert_choices) |
| |
| return choices |
| |
| |
| def gen_fuzz_states(nvals): |
| ''' |
| Generates an optimal encoding to solve single bits as quickly as possible |
| |
| tilegrid's initial solve for 4 bits works like this: |
| Initial reference value of all 0s: |
| 0000 |
| Then one-hot for each: |
| 0001 |
| 0010 |
| 0100 |
| 1000 |
| Which requires 5 samples total to diff these |
| |
| However, using correlation instead its possible to resolve n bits using ceil(log(n, 2)) + 1 samples |
| With 4 positions it takes only 3 samples: |
| 0000 |
| 0101 |
| 1010 |
| ''' |
| |
| choices = gen_fuzz_choices(nvals) |
| spec_idx = specn() - 1 |
| if spec_idx < len(choices): |
| bits = choices[spec_idx] |
| else: |
| next_p2_states = 2**math.ceil(math.log(nvals, 2)) |
| bits = random.randint(0, 2**next_p2_states) |
| |
| for i in range(nvals): |
| mask = (1 << i) |
| yield int(bool(bits & mask)) |
| |
| |
| def add_bool_arg(parser, yes_arg, default=False, **kwargs): |
| dashed = yes_arg.replace('--', '') |
| dest = dashed.replace('-', '_') |
| parser.add_argument( |
| yes_arg, dest=dest, action='store_true', default=default, **kwargs) |
| parser.add_argument( |
| '--no-' + dashed, dest=dest, action='store_false', **kwargs) |