From 95d8b69c68a17cf3fbcfb6d9f4752c9a90e9da69 Mon Sep 17 00:00:00 2001 From: s-ol Date: Thu, 28 May 2020 14:42:14 +0200 Subject: tests, cleanup, verify.py --- .gitignore | 2 + README.md | 8 ++ notes.md | 2 +- riscv.py | 229 ++++++++++++++++++++++++++++++++++++++++++++++++---------- subx.py | 102 ++++++++++++++++++++------ test.py | 41 +++++------ test_riscv.py | 3 + verify.py | 186 +++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 487 insertions(+), 86 deletions(-) create mode 100644 test_riscv.py create mode 100644 verify.py diff --git a/.gitignore b/.gitignore index e6c512b..71d364a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.elf +*.bin +*.pyc __pycache__ diff --git a/README.md b/README.md index df03318..19f3496 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,14 @@ This is a wip clone of [SubX][mu] for the RISC-V RV31I base ISA. $ ./test.py | ./elf.py > out.elf $ ./qemu.sh out.elf +Pipeline +-------- + +back to front: + +- `elf`: takes `hex.subx`-style input, outputs an ELF file +- `survey`: + Debugging --------- diff --git a/notes.md b/notes.md index 4351b27..cc051da 100644 --- a/notes.md +++ b/notes.md @@ -117,7 +117,7 @@ opcode: 0b0010111 / 0x17 - imm20: top 20 bits of offset added to PC - result stored in rd -## U-Format: op(imm20/21) -> rd +## J-Format: op(imm20/21) -> rd `opcode[7] rd[5] imm31:12[20]` immediates on a 32bit scale (jal) diff --git a/riscv.py b/riscv.py index c0d5a15..245d60d 100644 --- a/riscv.py +++ b/riscv.py @@ -1,44 +1,199 @@ +def u(num, bits): + 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()) + + return (num, bits) + +def i(num, bits): + if num < 0: + num = (1 << bits) + num + + return u(num, bits) + +def bit_concat(*parts): + val, size = 0, 0 + for (pval, psize) in parts: + val = val | pval << size + size += psize + return (val, size) + +def bit_slice(bits, top, bottom): + (val, size) = bits + + if top < bottom: + 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) + +def byteify(word): + (val, size) = word + if size != 32: + raise ValueError("Expected 32-bit word") + + b0 = bit_slice(word, 7, 0) + b1 = bit_slice(word, 15, 8) + b2 = bit_slice(word, 23, 16) + b3 = bit_slice(word, 31, 24) + return [b0[:1], b1[:1], b2[:1], b3[:1]] + def format_r(op, rd, r1, r2, funct3, funct7): - # opcode[7] rd[5] funct3[3] rs1[5] rs2[5] funct7[7] - b0 = (rd & 0x1) << 7 | op - b1 = (r1 & 0x1) << 7 | funct3 << 4 | rd >> 1 - b2 = (r2 & 0xf) << 4 | r1 >> 1 - b3 = funct7 << 1 | r2 >> 4 - return [(b0,), (b1,), (b2,), (b3,)] + # HH funct7 rs2 rs1 funct3 rd opcode LL + return byteify(bit_concat( + u(op, 7), + u(rd, 5), + u(funct3, 3), + u(r1, 5), + u(r2, 5), + u(funct7, 7), + )) def format_i(op, rd, r1, imm12, funct3): - # opcode[7] rd[5] funct3[3] rs1[5] imm[12] - b0 = (rd & 0x1) << 7 | op - b1 = (r1 & 0x1) << 7 | funct3 << 4 | rd >> 1 - b2 = (imm12 & 0xf) << 4 | r1 >> 1 - b3 = imm12 >> 4 - return [(b0,), (b1,), (b2,), (b3,)] + # HH imm[11:0] rs1 funct3 rd opcode LL + return byteify(bit_concat( + u(op, 7), + u(rd, 5), + u(funct3, 3), + u(r1, 5), + i(imm12, 12), + )) def format_s(op, r1, r2, imm12, funct3): - # opcode[7] rd[5] funct3[3] rs1[5] rs2[5] funct7[7] - b0 = (imm12 & 0x1) << 7 | op - b1 = (r1 & 0x1) << 7 | funct3 << 4 | (imm12 & 0x1f) >> 1 - b2 = (r2 & 0xf) << 4 | r1 >> 1 - b3 = (imm12 & 0xf0) | r2 >> 4 - return [(b0,), (b1,), (b2,), (b3,)] + # HH imm[11:5] rs2 rs1 funct3 imm[4:0] opcode LL + imm = i(imm12, 12) + imm_lo = bit_slice(imm, 4, 0) + imm_hi = bit_slice(imm, 11, 5) + return byteify(bit_concat( + u(op, 7), + imm_lo, + u(funct3, 3), + u(r1, 5), + u(r2, 5), + imm_hi + )) + + # imm12 = i(imm12, 12) + # r1 = i(r1, 5, neg=False) + # r2 = i(r2, 5, neg=False) + # + # b0 = (imm12 & 0x1) << 7 | op + # b1 = (r1 & 0x1) << 7 | funct3 << 4 | (imm12 & 0x1f) >> 1 + # b2 = (r2 & 0xf) << 4 | r1 >> 1 + # b3 = (imm12 & 0xf0) | r2 >> 4 + # return [(b0,), (b1,), (b2,), (b3,)] def format_u(op, rd, imm20): - # opcode[7] rd[5] imm31:12[20] - b0 = (rd & 0x1) << 7 | op - b1 = (imm20 & 0xf) << 4 | rd >> 1 - b2 = (imm20 >> 4) & 0xff - b3 = imm20 >> 12 - return [(b0,), (b1,), (b2,), (b3,)] - -def format_j(op, rd, imm): - # opcode[7] rd[5] imm19:12[8] imm11[1] imm10:1[10] imm20[1] - imm12 = (imm >> 11) & 0xff - imm11 = (imm >> 12) & 0x1 - imm1 = imm & 0x3ff - imm20 = imm >> 19 - - b0 = (rd & 0x1) << 7 | op # rd[1] op[7:0] - b1 = (imm12 & 0xf) << 4 | rd >> 1 # imm[15:12] rd[5:2] - b2 = (imm1 << 5) & 0xe0 | imm11 << 4 | (imm12 >> 4) # imm[3:1] imm[11] imm[19:16] - b3 = imm20 << 7 | imm1 >> 3 - return [(b0,), (b1,), (b2,), (b3,)] + # HH imm[31:12] rd opcode LL + return byteify(bit_concat( + u(op, 7), + u(rd, 5), + i(imm20, 20) + )) + +def format_j(op, rd, imm20): + # HH imm[20] imm[10:1] imm[11] imm[19:12] rd opcode LL + imm = i(imm20, 20) + imm_lo = bit_slice(imm, 9, 0) + imm_10 = bit_slice(imm, 10, 10) + imm_hi = bit_slice(imm, 18, 11) + imm_19 = bit_slice(imm, 19, 19) + return byteify(bit_concat( + u(op, 7), + u(rd, 5), + imm_hi, + imm_10, + imm_lo, + imm_19 + )) + +import unittest +class TestHelpers(unittest.TestCase): + def test_bit_concat(self): + self.assertEqual( + bit_concat((0b10, 2), (0b00, 2)), + (0b0010, 4) + ) + self.assertEqual( + bit_concat((0b1, 1), (0b0110, 4), (0b110, 3)), + (0b11001101, 8) + ) + + def test_bit_slice(self): + self.assertEqual( + bit_slice((0x7f, 8), 7, 4), + (0x7, 4) + ) + self.assertEqual( + bit_slice((0b100, 3), 2, 2), + (0b1, 1) + ) + with self.assertRaises(ValueError): + bit_slice((0xf, 4), 4, 0) + with self.assertRaises(ValueError): + bit_slice((0xf, 4), 2, 3) + with self.assertRaises(ValueError): + bit_slice((0xf, 4), 3, -1) + self.assertEqual( + bit_slice((0x12345678, 32), 7, 0), + (0x78, 8) + ) + self.assertEqual( + bit_slice((0x12345678, 32), 15, 8), + (0x56, 8) + ) + self.assertEqual( + bit_slice((0x12345678, 32), 23, 16), + (0x34, 8) + ) + + def test_byteify(self): + self.assertEqual( + byteify((0x12345678, 32)), + [(0x78,), (0x56,), (0x34,), (0x12,)] + ) + +class TestFormats(unittest.TestCase): + def test_format_u(self): + self.assertEqual( + format_u(0x37, 0x5, 0x10010), + [(183,), (2,), (1,), (16,)] + ) + + def test_format_i(self): + self.assertEqual( + format_i(0x13, 5, 0, 72, 0x0), + [(147,), (2,), (128,), (4,)] + ) + self.assertEqual( + format_i(0x13, 9, 2, 72, 0x3), + [(147,), (52,), (129,), (4,)] + ) + + def test_format_s(self): + self.assertEqual( + format_s(0x23, 3, 4, 0, 0x2), + [(35,), (160,), (65,), (0,)] + ) + self.assertEqual( + format_s(0x23, 2, 0, -4, 0x2), + [(35,), (46,), (1,), (254,)] + ) + + def test_format_j(self): + self.assertEqual( + format_j(0x6f, 0, -26), + [(111,), (240,), (223,), (252,)] + ) + self.assertEqual( + format_j(0x6f, 9, 0), + [(239,), (4,), (0,), (0,)] + ) diff --git a/subx.py b/subx.py index 53f72ce..ed3b184 100644 --- a/subx.py +++ b/subx.py @@ -1,40 +1,94 @@ import re white = re.compile('[ \t\.\n]+') -hex = re.compile('^(0x)?[0-9a-f]+$') +hex = re.compile(r'^\-?(0x)?[0-9a-f]+$') def parse_part(part): - part = part.split('/') - if hex.match(part[0]): - part[0] = int(part[0], 16) - return tuple(part) + part = part.split('/') + if hex.match(part[0]): + part[0] = int(part[0], 16) + return tuple(part) def parse_instr(line): - parts = white.split(line) - parts = [parse_part(part) for part in parts if part != ''] - return parts + parts = white.split(line) + parts = [parse_part(part) for part in parts if part != ''] + return parts def parse_segment(line): - parts = white.split(line) - return (parts[1], int(parts[2], 16)) + parts = white.split(line) + return (parts[1], int(parts[2], 16)) + +def is_lref(part): + return isinstance(part[0], str) + +def unlabel(part, expect=None): + if expect and part[1] != expect: + raise ValueError("expected {} to be labelled {}", part, expect) + return part[0] def format_part(part): - if not isinstance(part[0], str): - part = ('{:02x}'.format(part[0]),) + part[1:] - return '/'.join(part) + if not is_lref(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 + packed = ' '.join(format_part(part) for part in inst) + if comment: + packed = packed + ' # ' + comment + return packed def clean(line): - return line.strip().split('#')[0] + return line.strip().split('#')[0] def classify(line): - if line.startswith('=='): # segment - return 'segment' - elif line.endswith(':'): # label - return 'label' - else: - return 'instr' + if line.startswith('=='): # segment + return 'segment' + elif line.endswith(':'): # label + return 'label' + else: + return 'instr' + +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<