diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-05-29 11:50:25 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-05-29 11:50:25 +0000 |
| commit | 517f3e141c3e3bede4e7b0c0ea7237544f28ce90 (patch) | |
| tree | d8e12207c006bc5e03a0fea3ddbab261ac09e6c7 | |
| parent | new label-slice syntax in format.py; switch to doctest (diff) | |
| download | subv-517f3e141c3e3bede4e7b0c0ea7237544f28ce90.tar.gz subv-517f3e141c3e3bede4e7b0c0ea7237544f28ce90.zip | |
switch bit.py, subv.py to doctest
| -rw-r--r-- | bits.py | 156 | ||||
| -rwxr-xr-x | format.py | 90 | ||||
| -rw-r--r-- | subv.py | 235 | ||||
| -rwxr-xr-x | survey.py | 2 |
4 files changed, 316 insertions, 167 deletions
@@ -1,80 +1,122 @@ def u(num, bits): + """ parse an unsigned integer into a bitfield. + + >>> u(0x08, 8) + (8, 8) + >>> u(0xff, 8) + (255, 8) + + >>> u(0xf0, 7) + Traceback (most recent call last): + ... + ValueError: value 240 (u8) too large for u7 field + >>> u(-1, 8) + Traceback (most recent call last): + ... + ValueError: negative value not allowed: -1 + """ if num < 0: raise ValueError("negative value not allowed: {}".format(num)) if num.bit_length() > bits: - raise ValueError("value too large for u{} field: {} ({} bits)", - bits, num, num.bit_length()) + raise ValueError("value {} (u{}) too large for u{} field" + .format(num, num.bit_length(), bits)) return (num, bits) def i(num, bits): - if num < 0: - num = (1 << bits) + num + """ parse a signed integer into a bitfield. + + >>> i(8, 8) + (8, 8) + >>> i(-4, 8) + (252, 8) + >>> i(127, 8) + (127, 8) + >>> i(-128, 8) + (128, 8) + + >>> i(128, 8) + Traceback (most recent call last): + ... + ValueError: value 128 (i9) too large for i8 field [-128;127] + >>> i(-129, 8) + Traceback (most recent call last): + ... + ValueError: value -129 (i9) too large for i8 field [-128;127] + """ + min = -(1 << (bits - 1)) + max = -1 - min + + if num > max or num < min: + raise ValueError("value {} (i{}) too large for i{} field [{};{}]" + .format(num, num.bit_length()+1, bits, min, max)) + + if num < 0: + num = (1 << bits) + num - return u(num, bits) + return u(num, bits) def from_part(part): - val, size = part - return (val, int(size)) + """ parse a size-tagged subv part into a bit. + + >>> from_part((34, 2)) + (34, 2) + >>> from_part((34, 2, 'extra')) + (34, 2) + """ + val = int(part[0]) + size = part[1] + return (val, int(size)) def concat(*parts): + """ concatenate multiple bitfields. + + >>> concat((0b10, 2), (0b00, 2)) + (2, 4) + >>> concat((0b1, 1), (0b0110, 4), (0b110, 3)) + (205, 8) + """ val, size = 0, 0 for (pval, psize) in parts: val = val | pval << size size += psize return (val, size) -def slice(bits, top, bottom): +def slice(bits, hi, lo): + """ slice a bitfield given hi and lo bit indices. + + >>> slice((0x7f, 8), 7, 4) + (7, 4) + >>> slice((0b100, 3), 2, 2) + (1, 1) + >>> slice((0x12345678, 32), 7, 0) + (120, 8) + >>> slice((0x12345678, 32), 15, 8) + (86, 8) + >>> slice((0x12345678, 32), 23, 16) + (52, 8) + + >>> slice((0xf, 4), 4, 0) + Traceback (most recent call last): + ... + ValueError: slice [4:0] out of range (4 bit value) + >>> slice((0xf, 4), 2, 3) + Traceback (most recent call last): + ... + ValueError: cant slice reverse range + >>> slice((0xf, 4), 3, -1) + Traceback (most recent call last): + ... + ValueError: slice [3:-1] out of range (4 bit value) + """ (val, size) = bits - if top < bottom: + if hi < lo: raise ValueError("cant slice reverse range") - elif bottom < 0: - raise ValueError("negative slice index") - elif top >= size: - raise ValueError("cant slice [{}:{}] from {} bit value".format(top, bottom, size)) - - width = top - bottom + 1 - val = (val >> bottom) & ((1 << width) - 1) - return (val, width) - -import unittest -class TestHelpers(unittest.TestCase): - def test_concat(self): - self.assertEqual( - concat((0b10, 2), (0b00, 2)), - (0b0010, 4) - ) - self.assertEqual( - concat((0b1, 1), (0b0110, 4), (0b110, 3)), - (0b11001101, 8) - ) - - def test_slice(self): - self.assertEqual( - slice((0x7f, 8), 7, 4), - (0x7, 4) - ) - self.assertEqual( - slice((0b100, 3), 2, 2), - (0b1, 1) - ) - with self.assertRaises(ValueError): - slice((0xf, 4), 4, 0) - with self.assertRaises(ValueError): - slice((0xf, 4), 2, 3) - with self.assertRaises(ValueError): - slice((0xf, 4), 3, -1) - self.assertEqual( - slice((0x12345678, 32), 7, 0), - (0x78, 8) - ) - self.assertEqual( - slice((0x12345678, 32), 15, 8), - (0x56, 8) - ) - self.assertEqual( - slice((0x12345678, 32), 23, 16), - (0x34, 8) - ) + elif lo < 0 or hi >= size: + raise ValueError("slice [{}:{}] out of range ({} bit value)".format(hi, lo, size)) + + size = hi - lo + 1 + val = (val >> lo) & ((1 << size) - 1) + return (val, size) @@ -1,5 +1,14 @@ #!/usr/bin/env python3 -""" +""" format.py +Verifies instruction formats, orders and pre-bit-packs arguments. +Every instruction-line in the code segment needs to start with the opcode, +which has to be correctly tagged. Arguments have to be provided in the correct +order currently, and their first tag is verified also. + +Labels can be referenced in the immXX/offXX fields of most instructions. If no +bitslice is given for the labels, the default slice is picked based on the +instruction. + >>> from io import StringIO >>> # doctest: +REPORT_NDIFF ... print(subv.join_all(format(StringIO(''' @@ -57,18 +66,6 @@ main: import subv import bits -instr_map = { - 'opr': ('r', 0x33), - 'load': ('i', 0x03), - 'opi': ('i', 0x13), - 'jalr': ('i', 0x67), - 'store': ('s', 0x23), - 'branch': ('b', 0x63), - 'lui': ('u', 0x37), - 'auipc': ('u', 0x17), - 'jal': ('j', 0x6f), -} - def ref_slice(ref, hi, lo): if hi < lo: raise ValueError("cant slice reverse range") @@ -83,7 +80,8 @@ def ref_slice(ref, hi, lo): } def default_slice(val, hi, lo): - """ ... + """ add a default slice spec to labels if missing, + >>> default_slice(('label', 'imm12'), 12, 1) ('label[12:1]', 'imm12') >>> default_slice(('label[14:3]', 'imm12'), 12, 1) @@ -93,7 +91,7 @@ def default_slice(val, hi, lo): >>> default_slice(('label[31:0]', 'imm12'), 11, 0) Traceback (most recent call last): ... - AssertionError: slice doesnt match metadata + AssertionError: expected 12 bit slice """ parsed = subv.parse_reference(val) if 'hi' not in parsed: @@ -101,13 +99,14 @@ def default_slice(val, hi, lo): parsed['lo'] = lo p_size = parsed['hi'] - parsed['lo'] + 1 - assert parsed['size'] == p_size, "slice doesnt match metadata" + assert parsed['size'] == p_size, "expected {} bit slice".format(parsed['size']) ref = '{label}[{hi}:{lo}]'.format(**parsed) return (ref, *val[1:]) def slice_bit_or_ref(val, hi, lo): - """ ... + """ bit.slice but for bitfields and label references. + >>> slice_bit_or_ref(('label[31:0]', 'imm32'), 12, 1) ('label[12:1]', 'imm12') >>> slice_bit_or_ref(('label[31:0]', 'off32'), 11, 0) @@ -156,12 +155,12 @@ def pack_u(instr): >>> pack_u([(0x37, 'lui'), (5, 'rd', 't0'), ('pos[31:0]', 'imm20')]) Traceback (most recent call last): ... - AssertionError: slice doesnt match metadata + AssertionError: expected 20 bit slice """ (op, rd, imm) = instr op = bits.u(subv.untag(op), 7) rd = bits.u(subv.untag(rd, 'rd'), 5) - if subv.is_lref(imm): + if subv.is_reference(imm): imm = default_slice(imm, 31, 12) else: imm = bits.i(subv.untag(imm, 'imm20'), 20) @@ -170,6 +169,7 @@ def pack_u(instr): def pack_i(instr): """ verify & pack I-type instructions. + >>> pack_i([(0x13, 'opi'), (0, 'subop', 'add'), (6, 'rd', 't1'), (0, 'rs', 'x0'), (101, 'imm12')]) [(19, 7), (6, 5), (0, 3), (0, 5), (101, 12)] @@ -181,7 +181,7 @@ def pack_i(instr): sub = bits.u(subv.untag(sub, 'subop'), 3) rd = bits.u(subv.untag(rd, 'rd'), 5) rs = bits.u(subv.untag(rs, 'rs'), 5) - if subv.is_lref(imm): + if subv.is_reference(imm): imm = default_slice(imm, 11, 0) else: imm = bits.i(subv.untag(imm, 'imm12'), 12) @@ -189,7 +189,8 @@ def pack_i(instr): return [op, rd, sub, rs, imm] def pack_s(instr): - """ + """ verify & pack S-type instructions. + >>> pack_s([(0x23, 'store'), (2, 'subop', 'word'), (5, 'rs', 't0'), (6, 'rs', 't1'), (0, 'off12')]) [(35, 7), (0, 5), (2, 3), (5, 5), (6, 5), (0, 7)] @@ -202,7 +203,7 @@ def pack_s(instr): rs1 = bits.u(subv.untag(rs1, 'rs'), 5) rs2 = bits.u(subv.untag(rs2, 'rs'), 5) - if subv.is_lref(imm): + if subv.is_reference(imm): imm = default_slice(imm, 11, 0) else: imm = bits.i(subv.untag(imm, 'off12'), 12) @@ -212,7 +213,8 @@ def pack_s(instr): return [op, imm_lo, sub, rs1, rs2, imm_hi] def pack_j(instr): - """ + """ verify & pack J-type instructions. + >>> pack_j([(0x6f, 'jal'), (0, 'rd', 'x0'), (0, 'off20')]) [(111, 7), (0, 5), (0, 8), (0, 1), (0, 10), (0, 1)] @@ -226,7 +228,7 @@ def pack_j(instr): op = bits.u(subv.untag(op), 7) rd = bits.u(subv.untag(rd, 'rd'), 5) - if subv.is_lref(imm): + if subv.is_reference(imm): imm = default_slice(imm, 20, 1) else: imm = bits.i(subv.untag(imm, 'off20'), 20) @@ -239,7 +241,8 @@ def pack_j(instr): return [op, rd, imm_hi, imm_11, imm_lo, imm_20] def pack_b(instr): - """ + """ verify & pack B-type instructions. + >>> pack_b([(0x63, 'branch'), (0, 'subop', '=='), (6, 'rs'), (0, 'rs'), (0, 'off12')]) [(99, 7), (0, 1), (0, 4), (0, 3), (6, 5), (0, 5), (0, 6), (0, 1)] @@ -255,7 +258,7 @@ def pack_b(instr): rs1 = bits.u(subv.untag(rs1, 'rs'), 5) rs2 = bits.u(subv.untag(rs2, 'rs'), 5) - if subv.is_lref(imm): + if subv.is_reference(imm): imm = default_slice(imm, 12, 1) else: imm = bits.i(subv.untag(imm, 'off12'), 12) @@ -266,38 +269,37 @@ def pack_b(instr): imm_12 = slice_bit_or_ref(imm, 11, 11) # + ('off[12]',) return [op, imm_11, imm_lo, sub, rs1, rs2, imm_md, imm_12] +instr_map = { + # 'opr': (pack_r, 0x33), + 'load': (pack_i, 0x03), + 'opi': (pack_i, 0x13), + 'jalr': (pack_i, 0x67), + 'store': (pack_s, 0x23), + 'branch': (pack_b, 0x63), + 'lui': (pack_u, 0x37), + 'auipc': (pack_u, 0x17), + 'jal': (pack_j, 0x6f), +} + def format(iter): segment = None for line in iter: line = subv.parse(line) if segment == 'code' and line['type'] == 'instr': - instr = line['instr'] - op = instr[0] - assert len(op) == 2, 'instruction without op label: {}'.format(instr) + op = line['instr'][0] + assert len(op) == 2, 'instruction without op label: {}'.format(op) (op, label) = op if label not in instr_map: raise ValueError("unknown instruction label: {}".format(label)) - (format, expected) = instr_map[label] + (formatter, expected) = instr_map[label] if op != expected: raise ValueError("opcode {} doesn't match label {} (expected {})" .format(op, label, expected)) - if format == 'u': - out = pack_u(instr) - elif format == 'i': - out = pack_i(instr) - elif format == 's': - out = pack_s(instr) - elif format == 'j': - out = pack_j(instr) - elif format == 'b': - out = pack_b(instr) - else: - raise NotImplementedError() - - yield subv.format_instr(out) + line['instr'] = formatter(line['instr']) + yield subv.format(line) else: if line['type'] == 'segment': segment = line['segment'][0] @@ -1,30 +1,98 @@ import re -white = re.compile('[ \t\.\n]+') +white = re.compile(r'[ \t\.\n]+') hex = re.compile(r'^\-?(0x)?[0-9a-f]+$') slice_re = re.compile(r'^([^\[]+)(?:\[(\d+):(\d+)\])?$') field_re = re.compile(r'^(imm|off)(\d+)$') # parsing - def parse_part(part): + """ parse a literal with attached metadata. + + >>> parse_part('0') + (0,) + >>> parse_part('00') + (0,) + >>> parse_part('0x00') + (0,) + + >>> parse_part('12') + (18,) + >>> parse_part('0x12') + (18,) + + >>> parse_part('-12') + (-18,) + >>> parse_part('-0x12') + (-18,) + + >>> parse_part('00/with/tag') + (0, 'with', 'tag') + >>> parse_part('-12/and/tag') + (-18, 'and', 'tag') + + >>> parse_part('label/tag*') + ('label', 'tag*') + >>> parse_part('$label/tag*') + ('$label', 'tag*') + >>> parse_part('label:suff/tag*') + ('label:suff', 'tag*') + >>> parse_part('$label:suff/tag*') + ('$label:suff', 'tag*') + >>> parse_part('label[3:1]/tag*') + ('label[3:1]', 'tag*') + >>> parse_part('$label:suff[11:0]/tag*') + ('$label:suff[11:0]', 'tag*') + """ part = part.split('/') if hex.match(part[0]): part[0] = int(part[0], 16) return tuple(part) def parse_instr(line): + """ parse an instruction-line. + + >>> parse_instr('ff/op 0/subop/add 1/rd/x1 label[11:0]/imm12') + [(255, 'op'), (0, 'subop', 'add'), (1, 'rd', 'x1'), ('label[11:0]', 'imm12')] + """ parts = white.split(line) parts = [parse_part(part) for part in parts if part != ''] return parts def parse_segment(line): + """ parse a segment-line. + + >>> parse_segment('== code 0x8000') + ('code', 32768) + >>> parse_segment('== text') + ('text',) + """ parts = white.split(line) - return (parts[1], int(parts[2], 16)) + if len(parts) == 3: + return (parts[1], int(parts[2], 16)) + elif len(parts) == 2: + return (parts[1],) + else: + raise ValueError("invalid segment line") def parse_label(line): + """ parse a label-line. + + >>> parse_label('some_label:') + 'some_label' + >>> parse_label('$some:label:') + '$some:label' + """ return line[:-1] def parse_reference(part): + """ parse a sliced-label part. + + >>> parse_reference(('lbl', 'imm32')) + {'label': 'lbl', 'mode': 'imm', 'size': 32, 'meta': ()} + + >>> parse_reference(('x[11:0]', 'off12')) + {'label': 'x', 'mode': 'off', 'size': 12, 'meta': (), 'hi': 11, 'lo': 0} + """ ref = part[0] field = part[1] @@ -45,6 +113,21 @@ def parse_reference(part): return ref def classify(line): + """ classify cleaned lines. + + >>> classify('') + 'empty' + >>> classify('== code') + 'segment' + >>> classify('== text 0x1000') + 'segment' + >>> classify('some_label:') + 'label' + >>> classify('$some:label:') + 'label' + >>> classify('ff/op 0/subop/add 1/rd/x1 label[11:0]/imm12') + 'instr' + """ if line == '': return 'empty' elif line.startswith('=='): # segment @@ -55,6 +138,8 @@ def classify(line): return 'instr' def parse(line): + """ clean, classify and parse lines. + """ raw = line.strip() split = raw.split('#', 1) if len(split) == 1: @@ -82,81 +167,101 @@ def parse(line): type: parsed, } -def is_lref(part): +def is_reference(part): + """ check whether a part is a label reference. + + >>> is_reference(('hello',)) + True + >>> is_reference(('hello:world',)) + True + >>> is_reference(('$label',)) + True + >>> is_reference(('$label:extra',)) + True + >>> is_reference(('$label:extra', 'off20')) + True + >>> is_reference(('plain', 'off20')) + True + + >>> is_reference((0,)) + False + >>> is_reference((1,)) + False + >>> is_reference((1, 'disp20')) + False + >>> is_reference((0x13f, 'imm12')) + False + """ return isinstance(part[0], str) def untag(part, expect=None): + """ returns the value of a part and optionally verifies the first tag. + + >>> untag((2, 'num')) + 2 + >>> untag(('$label', 'imm20')) + '$label' + >>> untag((2, 'hello', 'things')) + 2 + + >>> untag((2, 'num'), expect='num') + 2 + >>> untag(('$label', 'imm20'), expect='imm20') + '$label' + + >>> untag((2, 'imm12'), expect='imm20') + Traceback (most recent call last): + ... + ValueError: expected (2, 'imm12') to be labelled imm20 + >>> untag(('$label', 'imm20'), expect='off12') + Traceback (most recent call last): + ... + ValueError: expected ('$label', 'imm20') to be labelled off12 + """ if expect and part[1] != expect: raise ValueError("expected {} to be labelled {}".format(part, expect)) return part[0] def format_part(part): - if not is_lref(part): + """ oppposite of parse_part. + + >>> format_part((0,)) + '00' + >>> format_part((0x00,)) + '00' + + >>> format_part(('label', 'tag*')) + 'label/tag*' + >>> format_part(('$label', 'tag*')) + '$label/tag*' + >>> format_part(('label:suff', 'tag')) + 'label:suff/tag' + >>> format_part(('$label:suff', 'tag')) + '$label:suff/tag' + """ + if not is_reference(part): first = '{:02x}'.format(part[0]) part = (first, *part[1:]) return '/'.join([str(p) for p in part]) -def format_instr(inst, comment=None): - packed = ' '.join(format_part(part) for part in inst) - if comment: - packed = packed + ' # ' + comment - return packed +def format(line): + """ oppposite of parse. + + >>> format({ + ... 'type': 'instr', + ... 'instr': [(255, 'op'), (0, 'subop', 'add'), (1, 'rd', 'x1'), ('label[11:0]', 'imm12')], + ... 'comment': "this does things." + ... }) + 'ff/op 00/subop/add 01/rd/x1 label[11:0]/imm12 # this does things.' + """ + if line['type'] == 'instr': + packed = ' '.join(format_part(part) for part in line['instr']) + if line['comment']: + packed = packed + ' # ' + line['comment'] + return packed + else: + raise NotImplementedError() def join_all(gen): res = '\n'.join(gen) return res - -import unittest -class TestParsing(unittest.TestCase): - def test_parse_part(self): - self.assertEqual(parse_part('0'), (0,)) - self.assertEqual(parse_part('00'), (0,)) - self.assertEqual(parse_part('0x00'), (0,)) - - self.assertEqual(parse_part('12'), (0x12,)) - self.assertEqual(parse_part('0x12'), (0x12,)) - - self.assertEqual(parse_part('-12'), (-0x12,)) - self.assertEqual(parse_part('-0x12'), (-0x12,)) - - self.assertEqual(parse_part('00/with/tag'), (0, 'with', 'tag')) - self.assertEqual(parse_part('-12/and<</tag*'), (-0x12, 'and<<', 'tag*')) - - self.assertEqual(parse_part('label/tag*'), ('label', 'tag*')) - self.assertEqual(parse_part('$label/tag*'), ('$label', 'tag*')) - self.assertEqual(parse_part('label:suff/tag*'), ('label:suff', 'tag*')) - self.assertEqual(parse_part('$label:suff/tag*'), ('$label:suff', 'tag*')) - - def test_parse_reference(self): - self.assertEqual( - parse_reference(('lbl', 'imm32')), - { 'label': 'lbl', 'hi': None, 'lo': None, 'mode': 'imm', 'size': 32, 'meta': () } - ) - self.assertEqual( - parse_reference(('x[11:0]', 'off12')), - { 'label': 'x', 'hi': 11, 'lo': 0, 'mode': 'off', 'size': 12, 'meta': () } - ) - -class TestChecks(unittest.TestCase): - def test_is_lref(self): - self.assertTrue(is_lref(('hello',))) - self.assertTrue(is_lref(('hello:world',))) - self.assertTrue(is_lref(('$label',))) - self.assertTrue(is_lref(('$label:extra',))) - self.assertTrue(is_lref(('$label:extra','disp20u'))) - self.assertTrue(is_lref(('plain','disp20u'))) - - self.assertFalse(is_lref((0,))) - self.assertFalse(is_lref((1,))) - self.assertFalse(is_lref((1,'disp20u'))) - self.assertFalse(is_lref((0x13f,'imm12'))) - -class TestFormatting(unittest.TestCase): - def test_format_part(self): - self.assertEqual(format_part((0,)), '00') - self.assertEqual(format_part((0x00,)), '00') - - self.assertEqual(format_part(('label', 'tag*')), 'label/tag*') - self.assertEqual(format_part(('$label', 'tag*')), '$label/tag*') - self.assertEqual(format_part(('label:suff', 'tag')), 'label:suff/tag') - self.assertEqual(format_part(('$label:suff', 'tag')), '$label:suff/tag') @@ -15,7 +15,7 @@ def scan(iter): return map def observe(part, rel_addr, map): - if subv.is_lref(part): + if subv.is_reference(part): label, trim, tag = part trim = int(trim) |
