mirror of
https://github.com/espressif/esp-idf
synced 2025-03-09 09:09:10 -04:00
209 lines
7.5 KiB
Python
Executable File
209 lines
7.5 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
import json
|
|
import os
|
|
import re
|
|
import shlex
|
|
import struct
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from typing import Any
|
|
from typing import List
|
|
from typing import Tuple
|
|
from typing import TypedDict
|
|
|
|
|
|
class DecompInstOp(TypedDict, total=False):
|
|
val: int
|
|
op: str
|
|
src: int
|
|
tgt: int
|
|
end_val: int
|
|
ctr_add: int
|
|
ctr_set: int
|
|
|
|
|
|
class DecompInst(TypedDict, total=False):
|
|
mux_val: List[int]
|
|
opcode: DecompInstOp
|
|
read_in: int
|
|
wr_out: int
|
|
mux_rel: int
|
|
ctr_src_sel: int
|
|
lut_src_sel: int
|
|
|
|
|
|
class UnpackedBinary(TypedDict, total=False):
|
|
binary_ver: int
|
|
hw_rev: int
|
|
hdr_len: int
|
|
inst_ct: int
|
|
lut_size_words: int
|
|
lut_width_bits: int
|
|
prefetch: int
|
|
trailing_bits: int
|
|
eof_on: int
|
|
padding: int
|
|
inst: List[DecompInst]
|
|
lut: List[int]
|
|
|
|
|
|
current_dir = os.path.dirname(os.path.realpath(__file__))
|
|
bsasm_path = os.path.join(current_dir, '..', 'bsasm.py')
|
|
|
|
|
|
class TestAssembler(unittest.TestCase):
|
|
def bit_from_inst(self, data: Tuple[int, int], off: int) -> int:
|
|
return data[off // 8] & (1 << (off & 0x7))
|
|
|
|
# returns a field from a list of bytes. Starts at bit off, field is of size length.
|
|
def bits_from_inst(self, data: Tuple[int, int], off: int, length: int) -> int:
|
|
ret = 0
|
|
for i in range(0, length):
|
|
if self.bit_from_inst(data, off + i) != 0:
|
|
ret |= 1 << i
|
|
return ret
|
|
|
|
# returns a decomposed instruction
|
|
def decode_inst(self, data: Tuple[int, int]) -> DecompInst:
|
|
ret: DecompInst = {}
|
|
mux_val = []
|
|
for i in range(0, 32):
|
|
mux_val.append(self.bits_from_inst(data, i * 7, 7))
|
|
ret['mux_val'] = mux_val
|
|
opcode = self.bits_from_inst(data, 224, 26)
|
|
op: DecompInstOp = {'val': opcode}
|
|
if opcode & (1 << 25):
|
|
op['op'] = 'LOOPB' if (opcode & (1 << 24)) else 'LOOPA'
|
|
op['tgt'] = (opcode >> 21) & 7
|
|
op['end_val'] = (opcode >> 5) & 0xFFFF
|
|
op['ctr_add'] = opcode & 31
|
|
else:
|
|
sub = (opcode >> 16) & 0x1F
|
|
if sub == 1 or sub == 2:
|
|
op['op'] = 'IF' if (sub == 1) else 'IFN'
|
|
op['src'] = opcode & 0x7F
|
|
op['tgt'] = (opcode >> 21) & 7
|
|
else:
|
|
fl = 'B' if (opcode & (1 << 24)) else 'A'
|
|
hl = (opcode >> 22) & 3
|
|
if hl == 3:
|
|
pass # don't add HL
|
|
elif hl == 2:
|
|
fl += 'H'
|
|
elif hl == 1:
|
|
fl += 'L'
|
|
elif hl == 0:
|
|
fl += 'XX' # shouldn't happen
|
|
|
|
if sub == 0:
|
|
op['op'] = 'ADD' + fl
|
|
op['ctr_add'] = opcode & 0xFFFF
|
|
elif sub == 3:
|
|
op['op'] = 'LDCTD' + fl
|
|
op['ctr_set'] = opcode & 0xFFFF
|
|
elif sub == 4:
|
|
op['op'] = 'LDCTI' + fl
|
|
elif sub == 5:
|
|
op['op'] = 'ADDCTI' + fl
|
|
ret['opcode'] = op
|
|
ret['read_in'] = self.bits_from_inst(data, 250, 2)
|
|
ret['wr_out'] = self.bits_from_inst(data, 252, 2)
|
|
ret['mux_rel'] = self.bits_from_inst(data, 254, 1)
|
|
ret['ctr_src_sel'] = self.bits_from_inst(data, 255, 1)
|
|
ret['lut_src_sel'] = self.bits_from_inst(data, 256, 1)
|
|
return ret
|
|
|
|
# returns a decomposed binary
|
|
def unpack_binary(self, filename: str) -> UnpackedBinary:
|
|
with open(filename, mode='rb') as f:
|
|
data = f.read()
|
|
ud = struct.unpack('<BBBBHBBHBB', data[:12])
|
|
unpacked: UnpackedBinary = {}
|
|
unpacked['binary_ver'] = ud[0]
|
|
unpacked['hw_rev'] = ud[1]
|
|
unpacked['hdr_len'] = ud[2]
|
|
unpacked['inst_ct'] = ud[3]
|
|
unpacked['lut_size_words'] = ud[4]
|
|
unpacked['lut_width_bits'] = ud[5]
|
|
unpacked['prefetch'] = ud[6]
|
|
unpacked['trailing_bits'] = ud[7]
|
|
unpacked['eof_on'] = ud[8]
|
|
unpacked['padding'] = ud[9]
|
|
off = unpacked['hdr_len'] * 4
|
|
inst = []
|
|
for insno in range(0, unpacked['inst_ct']):
|
|
inst.append(self.decode_inst(struct.unpack_from('B' * 36, data, off)))
|
|
off += 36
|
|
unpacked['inst'] = inst
|
|
unpacked['lut'] = list(
|
|
struct.unpack_from('<' + 'L' * unpacked['lut_size_words'], data, off)
|
|
)
|
|
return unpacked
|
|
|
|
def compare(self, out: Any, js: Any, base: str) -> None:
|
|
self.assertEqual(type(out), type(js), ' Diverging types between json and decoded obj: ' + base)
|
|
if type(js) == dict:
|
|
for k in js:
|
|
self.assertTrue(k in out, ' Key not found in decoded output: ' + base + '.' + k)
|
|
self.compare(out[k], js[k], base + '.' + k)
|
|
elif type(js) == list:
|
|
for k in range(0, len(js)):
|
|
self.compare(out[k], js[k], f'{base}[{k}]')
|
|
else:
|
|
self.assertEqual(js, out, f'Items different: {base} (json {js} decoded {out})')
|
|
|
|
def test_examples(self) -> None:
|
|
testfiles = []
|
|
for f in os.listdir(os.path.join(current_dir, 'testcases')):
|
|
if f.endswith('.bsasm'):
|
|
testfiles.append(os.path.join(current_dir, 'testcases', f))
|
|
for f in testfiles:
|
|
print(f'Testing {f}...')
|
|
# Extract testing options in the form '#test: key = value'
|
|
cmdlineopts = []
|
|
should_fail = False
|
|
pattern = r'^\s*#\s*test:\s*([^\s=]+)\s*=\s*(.*)\s*$'
|
|
with open(f) as tf:
|
|
for line in tf:
|
|
match = re.match(pattern, line)
|
|
if match:
|
|
if match.group(1) == 'should_fail':
|
|
if match.group(2) in ['1', 'true', 'True', 'TRUE']:
|
|
should_fail = True
|
|
elif match.group(1) == 'cmdlineopts':
|
|
cmdlineopts = shlex.split(match.group(2))
|
|
else:
|
|
self.fail(f'Unknown test option: {match.group(0)}')
|
|
# Generate temp filename and assemble
|
|
with tempfile.NamedTemporaryFile(delete=False) as f_out:
|
|
self.addCleanup(os.unlink, f_out.name)
|
|
args = [bsasm_path, f, f_out.name]
|
|
args.extend(cmdlineopts)
|
|
p = subprocess.run(args, timeout=10)
|
|
if not should_fail:
|
|
self.assertEqual(p.returncode, 0)
|
|
else:
|
|
print('Note: THE TEST EXPECTED BSASM TO ERROR OUT. If there\'s error text above, that is expected.')
|
|
self.assertNotEqual(p.returncode, 0)
|
|
|
|
if not should_fail:
|
|
b = self.unpack_binary(f_out.name)
|
|
jsfn = f[:-6] + '.json'
|
|
try:
|
|
with open(jsfn) as out_desc_f:
|
|
out_desc = json.load(out_desc_f)
|
|
# We were able to open the JSON file. See if the keys in it match up with the ones in the decoded fields.
|
|
self.compare(b, out_desc, '')
|
|
except FileNotFoundError:
|
|
print(f'File not found: {jsfn}. Printing out decoded contents instead.')
|
|
print(json.dumps(b, indent=4))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|