From acfaab0c3ffe04e7382e7095d9a19c86d80c3a66 Mon Sep 17 00:00:00 2001 From: s-ol Date: Mon, 24 May 2021 18:21:14 +0200 Subject: turn bits into a class --- bits.py | 158 ++++++++++++++++++++++++++++++++++---------------------------- format.py | 50 ++++++++++---------- pack.py | 31 ++++++------ subv.py | 18 ++++++- survey.py | 30 ++++++------ 5 files changed, 160 insertions(+), 127 deletions(-) diff --git a/bits.py b/bits.py index b8cf441..02c24df 100644 --- a/bits.py +++ b/bits.py @@ -1,12 +1,86 @@ -empty = (0, 0) +class Bitfield(object): + def __init__(self, val, size): + self.val = val + self.size = size + + def __repr__(self): + return "Bitfield(0x{:x}, {})".format(self.val, self.size) + + def __str__(self): + return "0x{:x}'{}".format(self.val, self.size) + + def __getitem__(self, range): + """ slice a bitfield given hi and lo bit indices. + + >>> Bitfield(0x7f, 8)[7:4] + Bitfield(0x7, 4) + >>> Bitfield(0b100, 3)[2:2] + Bitfield(0x1, 1) + >>> Bitfield(0x12345678, 32)[7:0] + Bitfield(0x78, 8) + >>> Bitfield(0x12345678, 32)[15:8] + Bitfield(0x56, 8) + >>> Bitfield(0x12345678, 32)[23:16] + Bitfield(0x34, 8) + + >>> Bitfield(0xf, 4)[4:0] + Traceback (most recent call last): + ... + ValueError: slice [4:0] out of range of 0xf'4 + >>> Bitfield(0xf, 4)[2:3] + Traceback (most recent call last): + ... + ValueError: cant slice reverse range + >>> Bitfield(0xf, 4)[3:-1] + Traceback (most recent call last): + ... + ValueError: slice [3:-1] out of range of 0xf'4 + """ + if not isinstance(range, slice): + range = slice(range, range) + hi, lo = range.start, range.stop + + if hi < lo: + raise ValueError("cant slice reverse range") + elif lo < 0 or hi >= self.size: + raise ValueError("slice [{0.start}:{0.stop}] out of range of {1}".format(range, self)) + + size = hi - lo + 1 + val = (self.val >> lo) & ((1 << size) - 1) + return Bitfield(val, size) + + def slice_allowempty(self, range): + if not isinstance(range, slice): + range = slice(range, range) + hi, lo = range.start, range.stop + + if hi < lo: + return empty + + return self[range] + + def __and__(self, other): + """ concatenate multiple bitfields. + + >>> Bitfield(0b00, 2) & Bitfield(0b10, 2) + Bitfield(0x2, 4) + >>> Bitfield(0b110, 3) & Bitfield(0b0110, 4) & Bitfield(0b1, 1) + Bitfield(0xcd, 8) + """ + if not isinstance(other, Bitfield): + raise NotImplementedError() + + return Bitfield(self.val << other.size | other.val, self.size + other.size) + +empty = Bitfield(0, 0) def u(num, bits): """ parse an unsigned integer into a bitfield. >>> u(0x08, 8) - (8, 8) + Bitfield(0x8, 8) >>> u(0xff, 8) - (255, 8) + Bitfield(0xff, 8) >>> u(0xf0, 7) Traceback (most recent call last): @@ -24,19 +98,19 @@ def u(num, bits): raise ValueError("value {} (u{}) too large for u{} field" .format(num, num.bit_length(), bits)) - return (num, bits) + return Bitfield(num, bits) def i(num, bits): """ parse a signed integer into a bitfield. >>> i(8, 8) - (8, 8) + Bitfield(0x8, 8) >>> i(-4, 8) - (252, 8) - >>> i(127, 8) - (127, 8) + Bitfield(0xfc, 8) + >>> i(0x7f, 8) + Bitfield(0x7f, 8) >>> i(-128, 8) - (128, 8) + Bitfield(0x80, 8) >>> i(128, 8) Traceback (most recent call last): @@ -62,67 +136,11 @@ def i(num, bits): def from_part(part): """ parse a size-tagged subv part into a bit. - >>> from_part((34, 2)) - (34, 2) - >>> from_part((34, 2, 'extra')) - (34, 2) + >>> from_part((0x12, 2)) + Bitfield(0x12, 2) + >>> from_part((0x12, 2, 'extra')) + Bitfield(0x12, 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, hi, lo, allow_empty=False): - """ 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), 2, 3, allow_empty=True) - (0, 0) - >>> slice((0xf, 4), 3, -1) - Traceback (most recent call last): - ... - ValueError: slice [3:-1] out of range (4 bit value) - """ - (val, size) = bits - - if hi < lo: - if allow_empty: - return empty - raise ValueError("cant slice reverse range") - 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) + return Bitfield(val, int(size)) diff --git a/format.py b/format.py index 9e0ead0..58b7708 100755 --- a/format.py +++ b/format.py @@ -63,6 +63,8 @@ main: 6f/7 00/5 main[19:12]/off8 main[11:11]/off1 main[10:1]/off10 main[20:20]/off1 """ +from operator import __and__ +from functools import reduce import subv import bits @@ -80,7 +82,7 @@ def ref_slice(ref, hi, lo): } def default_slice(val, hi, lo): - """ add a default slice spec to labels if missing, + """ add a default slice spec to labels if missing. >>> default_slice(('label', 'imm12'), 12, 1) ('label[12:1]', 'imm12') @@ -99,8 +101,8 @@ def default_slice(val, hi, lo): """ parsed = subv.parse_reference(val) if 'hi' not in parsed: - parsed['hi'] = hi - parsed['lo'] = lo + parsed['hi'] = hi + parsed['lo'] = lo ref = subv.format_reference(parsed) p_size = parsed['hi'] - parsed['lo'] + 1 @@ -126,8 +128,8 @@ def slice_bit_or_ref(val, hi, lo): ... AssertionError: cant slice label w/o bounds """ - if not isinstance(val[0], str): - return bits.slice(val, hi, lo) + if isinstance(val, bits.Bitfield): + return val[hi:lo] ref = subv.parse_reference(val) assert 'hi' in ref, "cant slice label w/o bounds" @@ -148,13 +150,13 @@ def pack_u(instr): """ verify & pack U-type instructions. >>> pack_u([(0x37, 'lui'), (5, 'rd', 't0'), (0x10010, 'imm20')]) - [(55, 7), (5, 5), (65552, 20)] + [Bitfield(0x37, 7), Bitfield(0x5, 5), Bitfield(0x10010, 20)] >>> pack_u([(0x37, 'lui'), (5, 'rd', 't0'), ('pos', 'imm20')]) - [(55, 7), (5, 5), ('pos[31:12]', 'imm20')] + [Bitfield(0x37, 7), Bitfield(0x5, 5), ('pos[31:12]', 'imm20')] >>> pack_u([(0x37, 'lui'), (5, 'rd', 't0'), ('pos[19:0]', 'imm20')]) - [(55, 7), (5, 5), ('pos[19:0]', 'imm20')] + [Bitfield(0x37, 7), Bitfield(0x5, 5), ('pos[19:0]', 'imm20')] >>> pack_u([(0x37, 'lui'), (5, 'rd', 't0'), ('pos[31:0]', 'imm20')]) Traceback (most recent call last): @@ -174,11 +176,11 @@ 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)] + >>> reduce(__and__, pack_i([(0x13, 'opi'), (0, 'subop', 'add'), (6, 'rd', 't1'), (0, 'rs', 'x0'), (101, 'imm12')])) + Bitfield(0x26600065, 32) >>> pack_i([(0x13, 'opi'), (0, 'subop', 'add'), (6, 'rd', 't1'), (0, 'rs', 'x0'), ('label', 'imm12')]) - [(19, 7), (6, 5), (0, 3), (0, 5), ('label[11:0]', 'imm12')] + [Bitfield(0x13, 7), Bitfield(0x6, 5), Bitfield(0x0, 3), Bitfield(0x0, 5), ('label[11:0]', 'imm12')] """ (op, sub, rd, rs, imm) = instr op = bits.u(subv.untag(op), 7) @@ -195,11 +197,11 @@ def pack_i(instr): 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)] + >>> reduce(__and__, pack_s([(0x23, 'store'), (2, 'subop', 'word'), (5, 'rs', 't0'), (6, 'rs', 't1'), (0, 'off12')])) + Bitfield(0x46045300, 32) >>> pack_s([(0x23, 'store'), (2, 'subop', 'word'), (5, 'rs', 't0'), (6, 'rs', 't1'), ('home', 'off12')]) - [(35, 7), ('home[4:0]', 'off5'), (2, 3), (5, 5), (6, 5), ('home[11:5]', 'off7')] + [Bitfield(0x23, 7), ('home[4:0]', 'off5'), Bitfield(0x2, 3), Bitfield(0x5, 5), Bitfield(0x6, 5), ('home[11:5]', 'off7')] """ (op, sub, rs1, rs2, imm) = instr op = bits.u(subv.untag(op), 7) @@ -219,14 +221,14 @@ def pack_s(instr): 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)] + >>> reduce(__and__, pack_j([(0x6f, 'jal'), (0, 'rd', 'x0'), (0, 'off20')])) + Bitfield(0xde000000, 32) - >>> pack_j([(0x6f, 'jal'), (0, 'rd', 'x0'), (-2, 'off20')]) - [(111, 7), (0, 5), (255, 8), (1, 1), (1022, 10), (1, 1)] + >>> reduce(__and__, pack_j([(0x6f, 'jal'), (0, 'rd', 'x0'), (-2, 'off20')])) + Bitfield(0xde0ffffd, 32) >>> pack_j([(0x6f, 'jal'), (2, 'rd', 'x2'), ('home', 'off20')]) - [(111, 7), (2, 5), ('home[19:12]', 'off8'), ('home[11:11]', 'off1'), ('home[10:1]', 'off10'), ('home[20:20]', 'off1')] + [Bitfield(0x6f, 7), Bitfield(0x2, 5), ('home[19:12]', 'off8'), ('home[11:11]', 'off1'), ('home[10:1]', 'off10'), ('home[20:20]', 'off1')] """ (op, rd, imm) = instr op = bits.u(subv.untag(op), 7) @@ -247,14 +249,14 @@ def pack_j(instr): 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)] + >>> reduce(__and__, pack_b([(0x63, 'branch'), (0, 'subop', '=='), (6, 'rs'), (0, 'rs'), (0, 'off12')])) + Bitfield(0xc6006000, 32) - >>> pack_b([(0x63, 'branch'), (0, 'subop', '=='), (6, 'rs'), (0, 'rs'), (-2, 'off12')]) - [(99, 7), (1, 1), (14, 4), (0, 3), (6, 5), (0, 5), (63, 6), (1, 1)] + >>> reduce(__and__, pack_b([(0x63, 'branch'), (0, 'subop', '=='), (6, 'rs'), (0, 'rs'), (-2, 'off12')])) + Bitfield(0xc7e0607f, 32) >>> pack_b([(0x63, 'branch'), (0, 'subop', '=='), (6, 'rs'), (0, 'rs'), ('home', 'off12')]) - [(99, 7), ('home[11:11]', 'off1'), ('home[4:1]', 'off4'), (0, 3), (6, 5), (0, 5), ('home[10:5]', 'off6'), ('home[12:12]', 'off1')] + [Bitfield(0x63, 7), ('home[11:11]', 'off1'), ('home[4:1]', 'off4'), Bitfield(0x0, 3), Bitfield(0x6, 5), Bitfield(0x0, 5), ('home[10:5]', 'off6'), ('home[12:12]', 'off1')] """ (op, sub, rs1, rs2, imm) = instr op = bits.u(subv.untag(op), 7) diff --git a/pack.py b/pack.py index f717df4..5dbaf95 100755 --- a/pack.py +++ b/pack.py @@ -44,22 +44,21 @@ import bits def byteify(word): """ split longer bitfield into bytes. - >>> byteify((0x12345678, 32)) + >>> byteify(bits.Bitfield(0x12345678, 32)) [(120,), (86,), (52,), (18,)] - >>> byteify((0x4801813, 32)) + >>> byteify(bits.Bitfield(0x4801813, 32)) [(19,), (24,), (128,), (4,)] - >>> byteify((0x13, 9)), + >>> byteify(bits.Bitfield(0x13, 9)), Traceback (most recent call last): ... - AssertionError: not byte aligned: 9 bits + AssertionError: not byte aligned: 0x13'9 """ - (val, size) = word - assert size % 8 == 0, "not byte aligned: {} bits".format(size) + assert word.size % 8 == 0, "not byte aligned: {}".format(word) out = [] - for i in range(0, size, 8): - byte = bits.slice(word, i+7, i) - out.append(byte[:1]) + for i in range(0, word.size, 8): + byte = word[i+7:i] + out.append((byte.val,)) return out @subv.with_parsed_lines @@ -79,27 +78,27 @@ def pack(iter): last_segment = None for segment, line in iter: if last_segment != segment: - assert buf[1] == 0, "segment '{}' end isn't byte-aligned".format(segment) + assert buf.size == 0, "segment '{}' end isn't byte-aligned".format(segment) if out: yield subv.format(flush_bytes()) if line['type'] == 'instr': for part in line['instr']: - buf = bits.concat(buf, bits.from_part(part)) + buf = bits.from_part(part) & buf else: yield line['raw'] - while buf[1] >= 8: - byte = bits.slice(buf, 7, 0) - buf = bits.slice(buf, buf[1] - 1, 8, allow_empty=True) - out.append(byte[:1]) + while buf.size >= 8: + byte = buf[7:0] + buf = buf.slice_allowempty(slice(buf.size-1, 8)) + out.append((byte.val,)) if len(out) == 4: yield subv.format(flush_bytes()) last_segment = segment - assert buf[1] == 0, "segment '{}' end isn't byte-aligned".format(segment) + assert buf.size == 0, "segment '{}' end isn't byte-aligned".format(segment) if __name__ == '__main__': import sys diff --git a/subv.py b/subv.py index 4f38988..dd45b16 100644 --- a/subv.py +++ b/subv.py @@ -1,4 +1,5 @@ import re +import bits white = re.compile(r'[ \t\.\n]+') hex = re.compile(r'^\-?(0x)?[0-9a-f]+$') num = re.compile(r'^\d+$') @@ -230,6 +231,11 @@ def untag(part, expect=None): >>> untag(('$label', 'imm20'), expect='imm20') '$label' + >>> untag((2, 'num'), expect=['num', 'imm20']) + 2 + >>> untag(('$label', 'imm20'), expect=['num', 'imm20']) + '$label' + >>> untag((2, 'imm12'), expect='imm20') Traceback (most recent call last): ... @@ -238,9 +244,15 @@ def untag(part, expect=None): Traceback (most recent call last): ... ValueError: expected ('$label', 'imm20') to be labelled off12 + >>> untag((2, 'imm12'), expect=['num', 'off12']) + Traceback (most recent call last): + ... + ValueError: expected (2, 'imm12') to be labelled one of ['num', 'off12'] """ - if expect and part[1] != expect: + if isinstance(expect, str) and part[1] != expect: raise ValueError("expected {} to be labelled {}".format(part, expect)) + elif isinstance(expect, list) and part[1] not in expect: + raise ValueError("expected {} to be labelled one of {}".format(part, expect)) return part[0] def format_reference(ref): @@ -287,7 +299,9 @@ def format_part(part): >>> format_part(('$label:suff', 'tag')) '$label:suff/tag' """ - if not is_reference(part): + if isinstance(part, bits.Bitfield): + part = ('{:02x}'.format(part.val), part.size) + elif not is_reference(part): first = '{:02x}'.format(part[0]) part = (first, *part[1:]) return '/'.join([str(p) for p in part]) diff --git a/survey.py b/survey.py index 55a8ab0..bfd9313 100755 --- a/survey.py +++ b/survey.py @@ -58,22 +58,22 @@ def observe(part, rel_addr, map): >>> observe((3, '3'), 0, {}) (3, '3') - >>> observe(('label[31:0]', 'imm32'), 0, {'label': 1234}) - (1234, 32) - >>> observe(('label-2[31:0]', 'imm32'), 0, {'label': 1234}) - (1232, 32) - >>> observe(('label[1:0]', 'imm32'), 0, {'label': 1234}) - (2, 2) - >>> observe(('label[31:0]', 'off32'), 1000, {'label': 1234}) - (234, 32) - >>> observe(('label[31:12]', 'off32'), 1000, {'label': 0x10fff}) - (16, 20) - >>> observe(('label[31:0]', 'off32'), 2000, {'label': 1000}) - (4294966296, 32) + >>> observe(('label[31:0]', 'imm32'), 0, {'label': 0x1234}) + Bitfield(0x1234, 32) + >>> observe(('label-2[31:0]', 'imm32'), 0, {'label': 0x1234}) + Bitfield(0x1232, 32) + >>> observe(('label[1:0]', 'imm32'), 0, {'label': 0x1236}) + Bitfield(0x2, 2) + >>> observe(('label[31:0]', 'off32'), 0x1000, {'label': 0x1234}) + Bitfield(0x234, 32) + >>> observe(('label[31:12]', 'off32'), 0x1000, {'label': 0x10fff}) + Bitfield(0xf, 20) + >>> observe(('label[31:0]', 'off32'), 0x2000, {'label': 0x1000}) + Bitfield(0xfffff000, 32) >>> observe(('label+4[7:0]', 'off32'), 0x1000, {'label': 0x1234}) - (56, 8) + Bitfield(0x38, 8) >>> observe(('label+4[15:8]', 'off32'), 0x1000, {'label': 0x1234}) - (2, 8) + Bitfield(0x2, 8) >>> observe(('label[32:0]', 'imm32'), 0, {}) Traceback (most recent call last): @@ -97,7 +97,7 @@ def observe(part, rel_addr, map): else: addr = bits.i(addr - rel_addr, 32) - return bits.slice(addr, ref['hi'], ref['lo']) + return addr[ref['hi']:ref['lo']] @subv.with_parsed_lines def survey(iter): -- cgit v1.2.3