diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index ab8d4bd..40ef419 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -91,3 +91,10 @@ jobs: export PATH=$PATH:${{ steps.fetch_binutils.outputs.bin_dir }} cd tests ./02_compat_rtc_tests.sh + + - name: Run disassembler tests + id: disassembler_tests + run: | + export PATH=$PATH:${{ steps.build_micropython.outputs.bin_dir }} + cd tests + ./03_disassembler_tests.sh diff --git a/README.rst b/README.rst index 5ffa906..e29d841 100644 --- a/README.rst +++ b/README.rst @@ -35,6 +35,7 @@ The following features are supported: * expressions in assembly code and constant definitions * RTC convenience macros (e.g. ``WRITE_RTC_REG``) * many ESP32 ULP code examples found on the web will work unmodified +* a simple disassembler is also provided Quick start diff --git a/docs/disassembler.rst b/docs/disassembler.rst new file mode 100644 index 0000000..b92a19e --- /dev/null +++ b/docs/disassembler.rst @@ -0,0 +1,146 @@ +===================== +Disassembler +===================== + +micropython-esp32-ulp contains a disassembler for disassembling code for the +ESP32 ULP (Ultra Low-Power) Co-Processor. + +The main purpose of this tool is to inspect what instructions our assembler +created, what value each field is set to, and to compare this with the output +created by the assembler from Espressif (part of their `binutils-gdb fork `_), +which we use as our reference implementation. + + +Usage +------------------------ + +To disassemble a ULP binary, simply run: + +.. code-block:: bash + + micropython -m tools.disassemble path/to/binary.ulp + +You can also specify additional options to ``disassemble.py`` as follows: + ++--------------------------+----------------------------------------------------------------+ +| Option | Description | ++==========================+================================================================+ +| ``-h`` | Show help text | ++--------------------------+----------------------------------------------------------------+ +|| ``-m `` || Disassemble a provided sequence of hex bytes | +|| || (in this case any filename specified is ignored) | ++--------------------------+----------------------------------------------------------------+ +| ``-v`` | Verbose mode (shows ULP header and fields of each instruction) | ++--------------------------+----------------------------------------------------------------+ + + +Disassembling a file +------------------------ + +The simplest and default mode of the disassembler is to disassemble the +specified file. + +Note that the ULP header is validates and files with unknown magic bytes will be +rejected. The correct 4 magic bytes at the start of a ULP binary are ``ulp\x00``. + +Example: + +.. code-block:: shell + + $ micropython -m tools.disassemble path/to/binary.ulp + .text + 0000 040000d0 LD r0, r1, 0 + 0004 0e0400d0 LD r2, r3, 1 + 0008 84010068 ST r0, r1, 0 + 000c 8b090068 ST r3, r2, 2 + .data + 0000 00000000 + + +Disassembling a byte sequence +----------------------------- + +The ``-m`` option allows disassembling a sequences hex letters representing +ULP instructions. + +This option expects the actual instructions directly, without any ULP header. + +The sequence must contain a number of hex letters exactly divisible by 8, i.e. +8, 16, 24, etc, because each 32-bit word is made up of 8 hex letters. Spaces +can be included in the sequence and they are ignored. + +The typical use case for this feature is to copy/paste some instructions from +a hexdump (e.g. xxd output) for analysis. + +Example: + +.. code-block:: shell + + # hexdump binary.ulp + $ xxd path/to/binary.ulp + 00000000: 756c 7000 0c00 2400 0400 0000 9300 8074 ulp...$........t + 00000010: 2a80 0488 2004 8074 1c00 0084 0000 0040 *... ..t.......@ + (...) + + # analyse the last 2 instructions + $ micropython -m tools.disassemble -m "1c00 0084 0000 0040" + 0000 1c000084 JUMPS 0, 28, LT + 0004 00000040 NOP + + +Verbose mode +------------------------ + +In verbose mode the following extra outputs are enabled: + +* ULP header (except when using ``-m``) +* The fields of each instruction and their values + +For example: + +.. code-block:: + + header + ULP magic : b'ulp\x00' (0x00706c75) + .text offset : 12 (0x0c) + .text size : 36 (0x24) + .data offset : 48 (0x30) + .data size : 4 (0x04) + .bss size : 0 (0x00) + ---------------------------------------- + .text + 0000 93008072 MOVE r3, 9 + dreg = 3 + imm = 9 + opcode = 7 + sel = 4 (MOV) + sreg = 0 + sub_opcode = 1 + unused = 0 + (...detail truncated...) + 0020 000000b0 HALT + opcode = 11 (0x0b) + unused = 0 + ---------------------------------------- + .data + 0000 00000000 + + +Disassembling on device +----------------------------- + +The disassembler also works when used on an ESP32. + +To use the disassembler on a real device: + +* ensure ``micropython-esp32-ulp`` is installed on the device (see `docs/index.rst `_). +* upload ``tools/disassemble.py`` to the device (any directory will do) +* run the following: + + .. code-block:: python + + from disassemble import disassemble_file + # then either: + disassemble_file('path/to/file.ulp') # normal mode + # or: + disassemble_file('path/to/file.ulp', True) # verbose mode diff --git a/docs/index.rst b/docs/index.rst index 82712ec..314bc68 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -136,6 +136,15 @@ found as part of Arduino/ESP-IDF projects. The preprocessor and how to use it is documented here: `Preprocessor support `_. +Disassembler +------------ +There is a disassembler for disassembling ULP binary code. This is mainly used to +inspect what instructions our assembler created, however it can be used to analyse +any ULP binaries. + +The disassembler and how to use it is documented here: `Disassembler `_. + + Limitations ----------- diff --git a/tests/00_unit_tests.sh b/tests/00_unit_tests.sh index ee1a239..1dc05e9 100755 --- a/tests/00_unit_tests.sh +++ b/tests/00_unit_tests.sh @@ -4,7 +4,9 @@ set -e -for file in opcodes assemble link util preprocess definesdb; do +LIST=${1:-opcodes assemble link util preprocess definesdb disassemble} + +for file in $LIST; do echo testing $file... micropython $file.py done diff --git a/tests/03_disassembler_tests.sh b/tests/03_disassembler_tests.sh new file mode 100755 index 0000000..7c76f11 --- /dev/null +++ b/tests/03_disassembler_tests.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +set -e + +test_disassembling_a_file() { + local verbose + if [ "$1" == verbose ]; then + verbose=-v + echo -e "Testing disassembling a file in VERBOSE mode" + else + echo -e "Testing disassembling a file in NORMAL mode" + fi + + testname=all_opcodes + fixture=fixtures/${testname}.S + echo -e "\tBuilding $fixture using micropython-esp32-ulp" + + log_file="${testname}.log" + ulp_file="fixtures/${testname}.ulp" + micropython -m esp32_ulp $fixture 1>$log_file # generates $ulp_file + + lst_file="${testname}.lst" + lst_file_fixture=fixtures/${testname}${verbose}.lst + echo -e "\tDisassembling $ulp_file using micropython-esp32-ulp disassembler" + micropython tools/disassemble.py $verbose $ulp_file > $lst_file + + if ! diff $lst_file_fixture $lst_file 1>/dev/null; then + echo -e "\tDisassembled output differs from expected output!" + echo "" + echo "Disassembly test failed for $fixture" + echo "micropython-esp32-ulp log:" + cat $log_file + echo "Diff of disassembly: expected vs actual" + diff -u $lst_file_fixture $lst_file + fi +} + +test_disassembling_a_manual_sequence() { + local verbose + if [ "$1" == verbose ]; then + verbose=-v + echo -e "Testing disassembling a manual byte sequence in VERBOSE mode" + else + echo -e "Testing disassembling a manual byte sequence in NORMAL mode" + fi + + sequence="e1af 8c72 0100 0068 2705 cc19 0005 681d 0000 00a0 0000 0074" + + lst_file="manual_bytes.lst" + lst_file_fixture=fixtures/manual_bytes${verbose}.lst + echo -e "\tDisassembling manual byte sequence using micropython-esp32-ulp disassembler" + micropython tools/disassemble.py $verbose -m $sequence > $lst_file + + if ! diff $lst_file_fixture $lst_file 1>/dev/null; then + echo -e "\tDisassembled output differs from expected output!" + echo "" + echo "Disassembly test failed for manual byte sequence" + echo "Diff of disassembly: expected vs actual" + diff -u $lst_file_fixture $lst_file + fi +} + +test_disassembling_a_file +test_disassembling_a_file verbose + +test_disassembling_a_manual_sequence +test_disassembling_a_manual_sequence verbose diff --git a/tests/disassemble.py b/tests/disassemble.py new file mode 100644 index 0000000..7e7f6df --- /dev/null +++ b/tests/disassemble.py @@ -0,0 +1,585 @@ +from tools.disassemble import decode_instruction, get_instruction_fields +import esp32_ulp.opcodes as opcodes +import ubinascii + +tests = [] + + +def test(param): + tests.append(param) + + +def hex_to_int(sequence): + byte_sequence = ubinascii.unhexlify(sequence) + return int.from_bytes(byte_sequence, 'little') + + +def assert_decode(sequence, expected_struct, expected_name): + i = hex_to_int(sequence) + + ins, name = decode_instruction(i) + + assert ins is expected_struct, 'incorrect instruction struct' + assert name == expected_name, '%s != %s' % (name, expected_name) + + +def assert_decode_exception(sequence, expected_message): + i = hex_to_int(sequence) + + try: + decode_instruction(i) + except Exception as e: + assert str(e) == expected_message, str(e) + raised = True + else: + raised = False + + assert raised, 'Exception not raised' + + +def assert_decode_fields(sequence, expected_field_details): + i = hex_to_int(sequence) + + ins, _ = decode_instruction(i) + + actual_field_details = get_instruction_fields(ins) + + assert actual_field_details == expected_field_details, '\n- %s \n+ %s' % (actual_field_details, expected_field_details) + + +@test +def test_unknown_instruction(): + assert_decode_exception("10000001", 'Unknown instruction') + + +@test +def test_empty_instruction(): + assert_decode_exception("00000000", '') + + +# All hex sequences were generated using our assembler. +# Note: disassembled instructions always show field values according +# to what is actually encoded into the binary instruction, not as per +# original assembly code. +# For example in JUMP instructions in the source code one would +# specify jump offsets in bytes (e.g. 4 bytes) but in the actual +# instruction offset encoded in the binary instruction will be in +# words (1 word = 4 bytes). +# The disassembled instructions would therefore show as "JUMP 1" +# for what was originally "JUMP 4" in the source code.@test +@test +def test_all_instructions(): + # OPCODE_WR_REG = 1 + assert_decode("00000010", opcodes._wr_reg, 'REG_WR 0x0, 0, 0, 0') + + # OPCODE_RD_REG = 2 + assert_decode("00000020", opcodes._rd_reg, 'REG_RD 0x0, 0, 0') + + # OPCODE_I2C = 3 + assert_decode("00000030", opcodes._i2c, 'I2C_RD 0, 0, 0, 0') + assert_decode("00000038", opcodes._i2c, 'I2C_WR 0, 0, 0, 0') + + # OPCODE_DELAY = 4 + assert_decode("00000040", opcodes._delay, 'NOP') + assert_decode("01000040", opcodes._delay, 'WAIT 1') + + # OPCODE_ADC = 5 + assert_decode("00000050", opcodes._adc, 'ADC r0, 0, 0') + + # OPCODE_ST = 6 + assert_decode("00000068", opcodes._st, 'ST r0, r0, 0') + + # OPCODE_ALU = 7, SUB_OPCODE_ALU_REG + assert_decode("00000070", opcodes._alu_reg, 'ADD r0, r0, r0') + assert_decode("00002070", opcodes._alu_reg, 'SUB r0, r0, r0') + assert_decode("00004070", opcodes._alu_reg, 'AND r0, r0, r0') + assert_decode("00006070", opcodes._alu_reg, 'OR r0, r0, r0') + assert_decode("00008070", opcodes._alu_reg, "MOVE r0, r0") + assert_decode("0000a070", opcodes._alu_reg, 'LSH r0, r0, r0') + assert_decode("0000c070", opcodes._alu_reg, 'RSH r0, r0, r0') + + # OPCODE_ALU = 7, SUB_OPCODE_ALU_IMM + assert_decode("00000072", opcodes._alu_imm, 'ADD r0, r0, 0') + assert_decode("00002072", opcodes._alu_imm, 'SUB r0, r0, 0') + assert_decode("00004072", opcodes._alu_imm, 'AND r0, r0, 0') + assert_decode("00006072", opcodes._alu_imm, 'OR r0, r0, 0') + assert_decode("00008072", opcodes._alu_imm, "MOVE r0, 0") + assert_decode("0000a072", opcodes._alu_imm, 'LSH r0, r0, 0') + assert_decode("0000c072", opcodes._alu_imm, 'RSH r0, r0, 0') + + # OPCODE_ALU = 7, SUB_OPCODE_ALU_CNT + assert_decode("00004074", opcodes._alu_cnt, 'STAGE_RST') + assert_decode("00000074", opcodes._alu_cnt, 'STAGE_INC 0') + assert_decode("00002074", opcodes._alu_cnt, 'STAGE_DEC 0') + + # OPCODE_BRANCH = 8, SUB_OPCODE_BX (IMM) + assert_decode("00000080", opcodes._bx, 'JUMP 0') + assert_decode("00004080", opcodes._bx, 'JUMP 0, EQ') + assert_decode("00008080", opcodes._bx, 'JUMP 0, OV') + + # OPCODE_BRANCH = 8, SUB_OPCODE_BX (REG) + assert_decode("00002080", opcodes._bx, 'JUMP r0') + assert_decode("00006080", opcodes._bx, 'JUMP r0, EQ') + assert_decode("0000a080", opcodes._bx, 'JUMP r0, OV') + + # OPCODE_BRANCH = 8, SUB_OPCODE_BR + assert_decode("00000082", opcodes._br, 'JUMPR 0, 0, LT') + assert_decode("00000182", opcodes._br, 'JUMPR 0, 0, GE') + + # OPCODE_BRANCH = 8, SUB_OPCODE_BX + assert_decode("00000084", opcodes._bs, 'JUMPS 0, 0, LT') + assert_decode("00800084", opcodes._bs, 'JUMPS 0, 0, GE') + assert_decode("00000184", opcodes._bs, 'JUMPS 0, 0, LE') + + # OPCODE_END = 9, SUB_OPCODE_END + assert_decode("01000090", opcodes._end, 'WAKE') + + # OPCODE_END = 9, SUB_OPCODE_SLEEP + assert_decode("00000092", opcodes._sleep, 'SLEEP 0') + + # OPCODE_TSENS = 10 + assert_decode("000000a0", opcodes._tsens, 'TSENS r0, 0') + + # OPCODE_HALT = 11 + assert_decode("000000b0", opcodes._halt, 'HALT') + + # OPCODE_LD = 13 + assert_decode("000000d0", opcodes._ld, 'LD r0, r0, 0') + + +@test +def test_instruction_field_decoding(): + # OPCODE_WR_REG = 1 + assert_decode_fields("230d8810", [ # REG_WR 0x123, 1, 2, 3 + ('addr' , 35, ' (0x23)'), + ('data' , 3, ''), + ('high' , 1, ''), + ('low' , 2, ''), + ('opcode' , 1, ''), + ('periph_sel', 1, ''), + ]) + + # OPCODE_RD_REG = 2 + assert_decode_fields("21030421", [ # REG_RD 0x321, 2, 1 + ('addr' , 33, ' (0x21)'), + ('high' , 2, ''), + ('low' , 1, ''), + ('opcode' , 2, ''), + ('periph_sel', 3, ''), + ('unused' , 0, ''), + ]) + + # OPCODE_I2C = 3 + assert_decode_fields("03001130", [ # I2C_RD 3, 2, 1, 0 + ('data' , 0, ''), + ('high' , 2, ''), + ('i2c_sel' , 0, ''), + ('low' , 1, ''), + ('opcode' , 3, ''), + ('rw' , 0, ''), + ('sub_addr' , 3, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("00011339", [ # I2C_WR 0, 2, 3, 4 + ('data' , 1, ''), + ('high' , 2, ''), + ('i2c_sel' , 4, ''), + ('low' , 3, ''), + ('opcode' , 3, ''), + ('rw' , 1, ''), + ('sub_addr' , 0, ''), + ('unused' , 0, ''), + ]) + + # OPCODE_DELAY = 4 + assert_decode_fields("00000040", [ # NOP + ('cycles' , 0, ''), + ('opcode' , 4, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("07000040", [ # WAIT 7 + ('cycles' , 7, ''), + ('opcode' , 4, ''), + ('unused' , 0, ''), + ]) + + # OPCODE_ADC = 5 + assert_decode_fields("07000050", [ # ADC r3, 1, 0 + ('cycles' , 0, ''), + ('dreg' , 3, ''), + ('mux' , 1, ''), + ('opcode' , 5, ''), + ('sar_sel' , 0, ''), + ('unused1' , 0, ''), + ('unused2' , 0, ''), + ]) + + # OPCODE_ST = 6 + assert_decode_fields("0b000068", [ # ST r3, r2, 0 + ('dreg' , 2, ''), + ('offset' , 0, ''), + ('opcode' , 6, ''), + ('sreg' , 3, ''), + ('sub_opcode', 4, ''), + ('unused1' , 0, ''), + ('unused2' , 0, ''), + ]) + + # OPCODE_ALU = 7, SUB_OPCODE_ALU_REG + assert_decode_fields("06000070", [ # ADD r2, r1, r0 + ('dreg' , 2, ''), + ('opcode' , 7, ''), + ('sel' , 0, ' (ADD)'), + ('sreg' , 1, ''), + ('sub_opcode', 0, ''), + ('treg' , 0, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("06002070", [ # SUB r2, r1, r0 + ('dreg' , 2, ''), + ('opcode' , 7, ''), + ('sel' , 1, ' (SUB)'), + ('sreg' , 1, ''), + ('sub_opcode', 0, ''), + ('treg' , 0, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("06004070", [ # AND r2, r1, r0 + ('dreg' , 2, ''), + ('opcode' , 7, ''), + ('sel' , 2, ' (AND)'), + ('sreg' , 1, ''), + ('sub_opcode', 0, ''), + ('treg' , 0, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("06006070", [ # OR r2, r1, r0 + ('dreg' , 2, ''), + ('opcode' , 7, ''), + ('sel' , 3, ' (OR)'), + ('sreg' , 1, ''), + ('sub_opcode', 0, ''), + ('treg' , 0, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("0600a070", [ # LSH r2, r1, r0 + ('dreg' , 2, ''), + ('opcode' , 7, ''), + ('sel' , 5, ' (LSH)'), + ('sreg' , 1, ''), + ('sub_opcode', 0, ''), + ('treg' , 0, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("0600c070", [ # RSH r2, r1, r0 + ('dreg' , 2, ''), + ('opcode' , 7, ''), + ('sel' , 6, ' (RSH)'), + ('sreg' , 1, ''), + ('sub_opcode', 0, ''), + ('treg' , 0, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("06000070", [ # ADD r2, r1, r0 + ('dreg' , 2, ''), + ('opcode' , 7, ''), + ('sel' , 0, ' (ADD)'), + ('sreg' , 1, ''), + ('sub_opcode', 0, ''), + ('treg' , 0, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("06002070", [ # SUB r2, r1, r0 + ('dreg' , 2, ''), + ('opcode' , 7, ''), + ('sel' , 1, ' (SUB)'), + ('sreg' , 1, ''), + ('sub_opcode', 0, ''), + ('treg' , 0, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("06004070", [ # AND r2, r1, r0 + ('dreg' , 2, ''), + ('opcode' , 7, ''), + ('sel' , 2, ' (AND)'), + ('sreg' , 1, ''), + ('sub_opcode', 0, ''), + ('treg' , 0, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("06006070", [ # OR r2, r1, r0 + ('dreg' , 2, ''), + ('opcode' , 7, ''), + ('sel' , 3, ' (OR)'), + ('sreg' , 1, ''), + ('sub_opcode', 0, ''), + ('treg' , 0, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("16008070", [ # MOVE r2, r1 + ('dreg' , 2, ''), + ('opcode' , 7, ''), + ('sel' , 4, ' (MOVE)'), + ('sreg' , 1, ''), + ('sub_opcode', 0, ''), + ('treg' , 1, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("0600a070", [ # LSH r2, r1, r0 + ('dreg' , 2, ''), + ('opcode' , 7, ''), + ('sel' , 5, ' (LSH)'), + ('sreg' , 1, ''), + ('sub_opcode', 0, ''), + ('treg' , 0, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("0600c070", [ # RSH r2, r1, r0 + ('dreg' , 2, ''), + ('opcode' , 7, ''), + ('sel' , 6, ' (RSH)'), + ('sreg' , 1, ''), + ('sub_opcode', 0, ''), + ('treg' , 0, ''), + ('unused' , 0, ''), + ]) + + # OPCODE_ALU = 7, SUB_OPCODE_ALU_IMM + assert_decode_fields("06000072", [ # ADD r2, r1, 0 + ('dreg' , 2, ''), + ('imm' , 0, ''), + ('opcode' , 7, ''), + ('sel' , 0, ' (ADD)'), + ('sreg' , 1, ''), + ('sub_opcode', 1, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("06002072", [ # SUB r2, r1, 0 + ('dreg' , 2, ''), + ('imm' , 0, ''), + ('opcode' , 7, ''), + ('sel' , 1, ' (SUB)'), + ('sreg' , 1, ''), + ('sub_opcode', 1, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("06004072", [ # AND r2, r1, 0 + ('dreg' , 2, ''), + ('imm' , 0, ''), + ('opcode' , 7, ''), + ('sel' , 2, ' (AND)'), + ('sreg' , 1, ''), + ('sub_opcode', 1, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("06006072", [ # OR r2, r1, 0 + ('dreg' , 2, ''), + ('imm' , 0, ''), + ('opcode' , 7, ''), + ('sel' , 3, ' (OR)'), + ('sreg' , 1, ''), + ('sub_opcode', 1, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("01008072", [ # MOVE r1, 0 + ('dreg' , 1, ''), + ('imm' , 0, ''), + ('opcode' , 7, ''), + ('sel' , 4, ' (MOVE)'), + ('sreg' , 0, ''), + ('sub_opcode', 1, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("0600a072", [ # LSH r2, r1, 0 + ('dreg' , 2, ''), + ('imm' , 0, ''), + ('opcode' , 7, ''), + ('sel' , 5, ' (LSH)'), + ('sreg' , 1, ''), + ('sub_opcode', 1, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("0600c072", [ # RSH r2, r1, 0 + ('dreg' , 2, ''), + ('imm' , 0, ''), + ('opcode' , 7, ''), + ('sel' , 6, ' (RSH)'), + ('sreg' , 1, ''), + ('sub_opcode', 1, ''), + ('unused' , 0, ''), + ]) + + # OPCODE_ALU = 7, SUB_OPCODE_ALU_CNT + assert_decode_fields("00004074", [ # STAGE_RST + ('imm' , 0, ''), + ('opcode' , 7, ''), + ('sel' , 2, ' (STAGE_RST)'), + ('sub_opcode', 2, ''), + ('unused1' , 0, ''), + ('unused2' , 0, ''), + ]) + assert_decode_fields("70000074", [ # STAGE_INC 7 + ('imm' , 7, ''), + ('opcode' , 7, ''), + ('sel' , 0, ' (STAGE_INC)'), + ('sub_opcode', 2, ''), + ('unused1' , 0, ''), + ('unused2' , 0, ''), + ]) + assert_decode_fields("30002074", [ # STAGE_DEC 3 + ('imm' , 3, ''), + ('opcode' , 7, ''), + ('sel' , 1, ' (STAGE_DEC)'), + ('sub_opcode', 2, ''), + ('unused1' , 0, ''), + ('unused2' , 0, ''), + ]) + + # OPCODE_BRANCH = 8, SUB_OPCODE_BX + assert_decode_fields("00002080", [ # JUMP r0 + ('addr' , 0, ''), + ('dreg' , 0, ''), + ('opcode' , 8, ''), + ('reg' , 1, ''), + ('sub_opcode', 0, ''), + ('type' , 0, ' (--)'), + ('unused' , 0, ''), + ]) + assert_decode_fields("01006080", [ # JUMP r1, EQ + ('addr' , 0, ''), + ('dreg' , 1, ''), + ('opcode' , 8, ''), + ('reg' , 1, ''), + ('sub_opcode', 0, ''), + ('type' , 1, ' (EQ)'), + ('unused' , 0, ''), + ]) + assert_decode_fields("0200a080", [ # JUMP r2, OV + ('addr' , 0, ''), + ('dreg' , 2, ''), + ('opcode' , 8, ''), + ('reg' , 1, ''), + ('sub_opcode', 0, ''), + ('type' , 2, ' (OV)'), + ('unused' , 0, ''), + ]) + assert_decode_fields("00000080", [ # JUMP 0 + ('addr' , 0, ''), + ('dreg' , 0, ''), + ('opcode' , 8, ''), + ('reg' , 0, ''), + ('sub_opcode', 0, ''), + ('type' , 0, ' (--)'), + ('unused' , 0, ''), + ]) + assert_decode_fields("04004080", [ # JUMP 1, EQ + ('addr' , 1, ''), + ('dreg' , 0, ''), + ('opcode' , 8, ''), + ('reg' , 0, ''), + ('sub_opcode', 0, ''), + ('type' , 1, ' (EQ)'), + ('unused' , 0, ''), + ]) + assert_decode_fields("08008080", [ # JUMP 2, OV + ('addr' , 2, ''), + ('dreg' , 0, ''), + ('opcode' , 8, ''), + ('reg' , 0, ''), + ('sub_opcode', 0, ''), + ('type' , 2, ' (OV)'), + ('unused' , 0, ''), + ]) + + # OPCODE_BRANCH = 8, SUB_OPCODE_BR + assert_decode_fields("01000082", [ # JUMPR 0, 1, LT + ('cmp' , 0, ' (LT)'), + ('imm' , 1, ''), + ('offset' , 0, ''), + ('opcode' , 8, ''), + ('sign' , 0, ''), + ('sub_opcode', 1, ''), + ]) + assert_decode_fields("05000382", [ # JUMPR 1, 5, GE + ('cmp' , 1, ' (GE)'), + ('imm' , 5, ''), + ('offset' , 1, ''), + ('opcode' , 8, ''), + ('sign' , 0, ''), + ('sub_opcode', 1, ''), + ]) + + # OPCODE_BRANCH = 8, SUB_OPCODE_BS + assert_decode_fields("01000084", [ # JUMPS 0, 1, LT + ('cmp' , 0, ' (LT)'), + ('imm' , 1, ''), + ('offset' , 0, ''), + ('opcode' , 8, ''), + ('sign' , 0, ''), + ('sub_opcode', 2, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("05800284", [ # JUMPS 1, 5, GE + ('cmp' , 1, ' (GE)'), + ('imm' , 5, ''), + ('offset' , 1, ''), + ('opcode' , 8, ''), + ('sign' , 0, ''), + ('sub_opcode', 2, ''), + ('unused' , 0, ''), + ]) + assert_decode_fields("09000584", [ # JUMPS 2, 9, LE + ('cmp' , 2, ' (LE)'), + ('imm' , 9, ''), + ('offset' , 2, ''), + ('opcode' , 8, ''), + ('sign' , 0, ''), + ('sub_opcode', 2, ''), + ('unused' , 0, ''), + ]) + + # OPCODE_END = 9, SUB_OPCODE_END + assert_decode_fields("01000090", [ # WAKE + ('opcode' , 9, ''), + ('sub_opcode', 0, ''), + ('unused' , 0, ''), + ('wakeup' , 1, ''), + ]) + + # OPCODE_END = 9, SUB_OPCODE_SLEEP + assert_decode_fields("07000092", [ # SLEEP 7 + ('cycle_sel' , 7, ''), + ('opcode' , 9, ''), + ('sub_opcode', 1, ''), + ('unused' , 0, ''), + ]) + + # OPCODE_TSENS = 10 + assert_decode_fields("090000a0", [ # TSENS r0, 0 + ('delay' , 2, ''), + ('dreg' , 1, ''), + ('opcode' , 10, ' (0x0a)'), + ('unused' , 0, ''), + ]) + + # OPCODE_HALT = 11 + assert_decode_fields("000000b0", [ # HALT + ('opcode' , 11, ' (0x0b)'), + ('unused' , 0, ''), + ]) + + # OPCODE_LD = 13 + assert_decode_fields("060000d0", [ # LD r2, r1, 0 + ('dreg' , 2, ''), + ('offset' , 0, ''), + ('opcode' , 13, ' (0x0d)'), + ('sreg' , 1, ''), + ('unused1' , 0, ''), + ('unused2' , 0, ''), + ]) + + +if __name__ == '__main__': + # run all methods marked with @test + for t in tests: + t() diff --git a/tests/fixtures/all_opcodes-v.lst b/tests/fixtures/all_opcodes-v.lst new file mode 100644 index 0000000..4bc7975 --- /dev/null +++ b/tests/fixtures/all_opcodes-v.lst @@ -0,0 +1,313 @@ +header +ULP magic : b'ulp\x00' (0x00706c75) +.text offset : 12 (0x0c) +.text size : 164 (0xa4) +.data offset : 176 (0xb0) +.data size : 8 (0x08) +.bss size : 0 (0x00) +---------------------------------------- +.text +0000 230d8810 REG_WR 0x123, 1, 2, 3 + addr = 35 (0x23) + data = 3 + high = 1 + low = 2 + opcode = 1 + periph_sel = 1 +0004 21030421 REG_RD 0x321, 2, 1 + addr = 33 (0x21) + high = 2 + low = 1 + opcode = 2 + periph_sel = 3 + unused = 0 +0008 03001130 I2C_RD 3, 2, 1, 0 + data = 0 + high = 2 + i2c_sel = 0 + low = 1 + opcode = 3 + rw = 0 + sub_addr = 3 + unused = 0 +000c 00011339 I2C_WR 0, 2, 3, 4 + data = 1 + high = 2 + i2c_sel = 4 + low = 3 + opcode = 3 + rw = 1 + sub_addr = 0 + unused = 0 +0010 00000040 NOP + cycles = 0 + opcode = 4 + unused = 0 +0014 07000040 WAIT 7 + cycles = 7 + opcode = 4 + unused = 0 +0018 07000050 ADC r3, 1, 0 + cycles = 0 + dreg = 3 + mux = 1 + opcode = 5 + sar_sel = 0 + unused1 = 0 + unused2 = 0 +001c 0b000068 ST r3, r2, 0 + dreg = 2 + offset = 0 + opcode = 6 + sreg = 3 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 +0020 06000070 ADD r2, r1, r0 + dreg = 2 + opcode = 7 + sel = 0 (ADD) + sreg = 1 + sub_opcode = 0 + treg = 0 + unused = 0 +0024 06002070 SUB r2, r1, r0 + dreg = 2 + opcode = 7 + sel = 1 (SUB) + sreg = 1 + sub_opcode = 0 + treg = 0 + unused = 0 +0028 06004070 AND r2, r1, r0 + dreg = 2 + opcode = 7 + sel = 2 (AND) + sreg = 1 + sub_opcode = 0 + treg = 0 + unused = 0 +002c 06006070 OR r2, r1, r0 + dreg = 2 + opcode = 7 + sel = 3 (OR) + sreg = 1 + sub_opcode = 0 + treg = 0 + unused = 0 +0030 16008070 MOVE r2, r1 + dreg = 2 + opcode = 7 + sel = 4 (MOVE) + sreg = 1 + sub_opcode = 0 + treg = 1 + unused = 0 +0034 0600a070 LSH r2, r1, r0 + dreg = 2 + opcode = 7 + sel = 5 (LSH) + sreg = 1 + sub_opcode = 0 + treg = 0 + unused = 0 +0038 0600c070 RSH r2, r1, r0 + dreg = 2 + opcode = 7 + sel = 6 (RSH) + sreg = 1 + sub_opcode = 0 + treg = 0 + unused = 0 +003c 06000072 ADD r2, r1, 0 + dreg = 2 + imm = 0 + opcode = 7 + sel = 0 (ADD) + sreg = 1 + sub_opcode = 1 + unused = 0 +0040 06002072 SUB r2, r1, 0 + dreg = 2 + imm = 0 + opcode = 7 + sel = 1 (SUB) + sreg = 1 + sub_opcode = 1 + unused = 0 +0044 06004072 AND r2, r1, 0 + dreg = 2 + imm = 0 + opcode = 7 + sel = 2 (AND) + sreg = 1 + sub_opcode = 1 + unused = 0 +0048 06006072 OR r2, r1, 0 + dreg = 2 + imm = 0 + opcode = 7 + sel = 3 (OR) + sreg = 1 + sub_opcode = 1 + unused = 0 +004c 01008072 MOVE r1, 0 + dreg = 1 + imm = 0 + opcode = 7 + sel = 4 (MOVE) + sreg = 0 + sub_opcode = 1 + unused = 0 +0050 0600a072 LSH r2, r1, 0 + dreg = 2 + imm = 0 + opcode = 7 + sel = 5 (LSH) + sreg = 1 + sub_opcode = 1 + unused = 0 +0054 0600c072 RSH r2, r1, 0 + dreg = 2 + imm = 0 + opcode = 7 + sel = 6 (RSH) + sreg = 1 + sub_opcode = 1 + unused = 0 +0058 00004074 STAGE_RST + imm = 0 + opcode = 7 + sel = 2 (STAGE_RST) + sub_opcode = 2 + unused1 = 0 + unused2 = 0 +005c 70000074 STAGE_INC 7 + imm = 7 + opcode = 7 + sel = 0 (STAGE_INC) + sub_opcode = 2 + unused1 = 0 + unused2 = 0 +0060 30002074 STAGE_DEC 3 + imm = 3 + opcode = 7 + sel = 1 (STAGE_DEC) + sub_opcode = 2 + unused1 = 0 + unused2 = 0 +0064 00002080 JUMP r0 + addr = 0 + dreg = 0 + opcode = 8 + reg = 1 + sub_opcode = 0 + type = 0 (--) + unused = 0 +0068 01006080 JUMP r1, EQ + addr = 0 + dreg = 1 + opcode = 8 + reg = 1 + sub_opcode = 0 + type = 1 (EQ) + unused = 0 +006c 0200a080 JUMP r2, OV + addr = 0 + dreg = 2 + opcode = 8 + reg = 1 + sub_opcode = 0 + type = 2 (OV) + unused = 0 +0070 00000080 JUMP 0 + addr = 0 + dreg = 0 + opcode = 8 + reg = 0 + sub_opcode = 0 + type = 0 (--) + unused = 0 +0074 00004080 JUMP 0, EQ + addr = 0 + dreg = 0 + opcode = 8 + reg = 0 + sub_opcode = 0 + type = 1 (EQ) + unused = 0 +0078 00008080 JUMP 0, OV + addr = 0 + dreg = 0 + opcode = 8 + reg = 0 + sub_opcode = 0 + type = 2 (OV) + unused = 0 +007c 01000082 JUMPR 0, 1, LT + cmp = 0 (LT) + imm = 1 + offset = 0 + opcode = 8 + sign = 0 + sub_opcode = 1 +0080 05000382 JUMPR 1, 5, GE + cmp = 1 (GE) + imm = 5 + offset = 1 + opcode = 8 + sign = 0 + sub_opcode = 1 +0084 01000084 JUMPS 0, 1, LT + cmp = 0 (LT) + imm = 1 + offset = 0 + opcode = 8 + sign = 0 + sub_opcode = 2 + unused = 0 +0088 05800284 JUMPS 1, 5, GE + cmp = 1 (GE) + imm = 5 + offset = 1 + opcode = 8 + sign = 0 + sub_opcode = 2 + unused = 0 +008c 09000584 JUMPS 2, 9, LE + cmp = 2 (LE) + imm = 9 + offset = 2 + opcode = 8 + sign = 0 + sub_opcode = 2 + unused = 0 +0090 01000090 WAKE + opcode = 9 + sub_opcode = 0 + unused = 0 + wakeup = 1 +0094 07000092 SLEEP 7 + cycle_sel = 7 + opcode = 9 + sub_opcode = 1 + unused = 0 +0098 090000a0 TSENS r1, 2 + delay = 2 + dreg = 1 + opcode = 10 (0x0a) + unused = 0 +009c 000000b0 HALT + opcode = 11 (0x0b) + unused = 0 +00a0 060000d0 LD r2, r1, 0 + dreg = 2 + offset = 0 + opcode = 13 (0x0d) + sreg = 1 + unused1 = 0 + unused2 = 0 +---------------------------------------- +.data +00a4 00000000 +00a8 fecadec0 diff --git a/tests/fixtures/all_opcodes.S b/tests/fixtures/all_opcodes.S new file mode 100644 index 0000000..7f8c916 --- /dev/null +++ b/tests/fixtures/all_opcodes.S @@ -0,0 +1,62 @@ +.data +empty: .long 0 +magic: .long 0xc0decafe + +.text +REG_WR 0x123, 1, 2, 3 + +REG_RD 0x321, 2, 1 + +I2C_RD 3, 2, 1, 0 +I2C_WR 0, 1, 2, 3, 4 + +NOP +WAIT 7 + +ADC r3, 2, 1 + +ST r3, r2, 1 + +ADD r2, r1, r0 +SUB r2, r1, r0 +AND r2, r1, r0 +OR r2, r1, r0 +MOVE r2, r1 +LSH r2, r1, r0 +RSH r2, r1, r0 + +ADD r2, r1, 0 +SUB r2, r1, 0 +AND r2, r1, 0 +OR r2, r1, 0 +MOVE r1, 0 +LSH r2, r1, 0 +RSH r2, r1, 0 + +STAGE_RST +STAGE_INC 7 +STAGE_DEC 3 + +JUMP r0 +JUMP r1, EQ +JUMP r2, OV + +JUMP 0 +JUMP 0, EQ +JUMP 0, OV + +JUMPR 0, 1, LT +JUMPR 4, 5, GE + +JUMPS 0, 1, LT +JUMPS 4, 5, GE +JUMPS 8, 9, LE + +WAKE +SLEEP 7 + +TSENS r1, 2 + +HALT + +LD r2, r1, 0 diff --git a/tests/fixtures/all_opcodes.lst b/tests/fixtures/all_opcodes.lst new file mode 100644 index 0000000..2ef1bd7 --- /dev/null +++ b/tests/fixtures/all_opcodes.lst @@ -0,0 +1,45 @@ +.text +0000 230d8810 REG_WR 0x123, 1, 2, 3 +0004 21030421 REG_RD 0x321, 2, 1 +0008 03001130 I2C_RD 3, 2, 1, 0 +000c 00011339 I2C_WR 0, 2, 3, 4 +0010 00000040 NOP +0014 07000040 WAIT 7 +0018 07000050 ADC r3, 1, 0 +001c 0b000068 ST r3, r2, 0 +0020 06000070 ADD r2, r1, r0 +0024 06002070 SUB r2, r1, r0 +0028 06004070 AND r2, r1, r0 +002c 06006070 OR r2, r1, r0 +0030 16008070 MOVE r2, r1 +0034 0600a070 LSH r2, r1, r0 +0038 0600c070 RSH r2, r1, r0 +003c 06000072 ADD r2, r1, 0 +0040 06002072 SUB r2, r1, 0 +0044 06004072 AND r2, r1, 0 +0048 06006072 OR r2, r1, 0 +004c 01008072 MOVE r1, 0 +0050 0600a072 LSH r2, r1, 0 +0054 0600c072 RSH r2, r1, 0 +0058 00004074 STAGE_RST +005c 70000074 STAGE_INC 7 +0060 30002074 STAGE_DEC 3 +0064 00002080 JUMP r0 +0068 01006080 JUMP r1, EQ +006c 0200a080 JUMP r2, OV +0070 00000080 JUMP 0 +0074 00004080 JUMP 0, EQ +0078 00008080 JUMP 0, OV +007c 01000082 JUMPR 0, 1, LT +0080 05000382 JUMPR 1, 5, GE +0084 01000084 JUMPS 0, 1, LT +0088 05800284 JUMPS 1, 5, GE +008c 09000584 JUMPS 2, 9, LE +0090 01000090 WAKE +0094 07000092 SLEEP 7 +0098 090000a0 TSENS r1, 2 +009c 000000b0 HALT +00a0 060000d0 LD r2, r1, 0 +.data +00a4 00000000 +00a8 fecadec0 diff --git a/tests/fixtures/manual_bytes-v.lst b/tests/fixtures/manual_bytes-v.lst new file mode 100644 index 0000000..7f44ea4 --- /dev/null +++ b/tests/fixtures/manual_bytes-v.lst @@ -0,0 +1,42 @@ +0000 e1af8c72 MOVE r1, 51966 + dreg = 1 + imm = 51966 (0xcafe) + opcode = 7 + sel = 4 (MOVE) + sreg = 0 + sub_opcode = 1 + unused = 0 +0004 01000068 ST r1, r0, 0 + dreg = 0 + offset = 0 + opcode = 6 + sreg = 1 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 +0008 2705cc19 REG_WR 0x127, 19, 19, 1 + addr = 39 (0x27) + data = 1 + high = 19 (0x13) + low = 19 (0x13) + opcode = 1 + periph_sel = 1 +000c 0005681d REG_WR 0x100, 26, 26, 1 + addr = 0 + data = 1 + high = 26 (0x1a) + low = 26 (0x1a) + opcode = 1 + periph_sel = 1 +0010 000000a0 TSENS r0, 0 + delay = 0 + dreg = 0 + opcode = 10 (0x0a) + unused = 0 +0014 00000074 STAGE_INC 0 + imm = 0 + opcode = 7 + sel = 0 (STAGE_INC) + sub_opcode = 2 + unused1 = 0 + unused2 = 0 diff --git a/tests/fixtures/manual_bytes.lst b/tests/fixtures/manual_bytes.lst new file mode 100644 index 0000000..beb1b77 --- /dev/null +++ b/tests/fixtures/manual_bytes.lst @@ -0,0 +1,6 @@ +0000 e1af8c72 MOVE r1, 51966 +0004 01000068 ST r1, r0, 0 +0008 2705cc19 REG_WR 0x127, 19, 19, 1 +000c 0005681d REG_WR 0x100, 26, 26, 1 +0010 000000a0 TSENS r0, 0 +0014 00000074 STAGE_INC 0 diff --git a/tests/tools b/tests/tools new file mode 120000 index 0000000..4887d6e --- /dev/null +++ b/tests/tools @@ -0,0 +1 @@ +../tools \ No newline at end of file diff --git a/tools/disassemble.py b/tools/disassemble.py new file mode 100644 index 0000000..f647576 --- /dev/null +++ b/tools/disassemble.py @@ -0,0 +1,320 @@ +from uctypes import struct, addressof, LITTLE_ENDIAN, UINT16, UINT32 +from esp32_ulp.opcodes import RD_REG_PERIPH_RTC_CNTL, RD_REG_PERIPH_RTC_IO, RD_REG_PERIPH_RTC_I2C, \ + RD_REG_PERIPH_SENS, DR_REG_MAX_DIRECT +import esp32_ulp.opcodes as opcodes +import esp32_ulp.soc as soc +import ubinascii +import sys + + +alu_cnt_ops = ('STAGE_INC', 'STAGE_DEC', 'STAGE_RST') +alu_ops = ('ADD', 'SUB', 'AND', 'OR', 'MOVE', 'LSH', 'RSH') +jump_types = ('--', 'EQ', 'OV') +cmp_ops = ('LT', 'GE', 'LE', 'EQ', 'GT') + +lookup = { + opcodes.OPCODE_ADC: ('ADC', opcodes._adc, lambda op: 'ADC r%s, %s, %s' % (op.dreg, op.mux, op.sar_sel)), + opcodes.OPCODE_ALU: ('ALU', opcodes._alu_imm, { + opcodes.SUB_OPCODE_ALU_CNT: ( + 'ALU_CNT', + opcodes._alu_cnt, + lambda op: '%s%s' % (alu_cnt_ops[op.sel], '' if op.sel == opcodes.ALU_SEL_RST else ' %s' % op.imm) + ), + opcodes.SUB_OPCODE_ALU_IMM: ( + 'ALU_IMM', + opcodes._alu_imm, + lambda op: '%s r%s, %s' % (alu_ops[op.sel], op.dreg, op.imm) if op.sel == opcodes.ALU_SEL_MOV + else '%s r%s, r%s, %s' % (alu_ops[op.sel], op.dreg, op.sreg, op.imm) + ), + opcodes.SUB_OPCODE_ALU_REG: ( + 'ALU_REG', + opcodes._alu_reg, + lambda op: '%s r%s, r%s' % (alu_ops[op.sel], op.dreg, op.sreg) if op.sel == opcodes.ALU_SEL_MOV + else '%s r%s, r%s, r%s' % (alu_ops[op.sel], op.dreg, op.sreg, op.treg) + ), + }), + opcodes.OPCODE_BRANCH: ('BRANCH', opcodes._bx, { + opcodes.SUB_OPCODE_BX: ( + 'BX', + opcodes._bx, + lambda op: 'JUMP %s%s' % (op.addr if op.reg == 0 else 'r%s' % op.dreg, ', %s' % jump_types[op.type] + if op.type != 0 else '') + ), + opcodes.SUB_OPCODE_BR: ( + 'BR', + opcodes._br, + lambda op: 'JUMPR %s, %s, %s' % ('%s%s' % ('-' if op.sign == 1 else '', op.offset), op.imm, cmp_ops[op.cmp]) + ), + opcodes.SUB_OPCODE_BS: ( + 'BS', + opcodes._bs, + lambda op: 'JUMPS %s, %s, %s' % ('%s%s' % ('-' if op.sign == 1 else '', op.offset), op.imm, cmp_ops[op.cmp]) + ), + }), + opcodes.OPCODE_DELAY: ( + 'DELAY', + opcodes._delay, + lambda op: 'NOP' if op.cycles == 0 else 'WAIT %s' % op.cycles + ), + opcodes.OPCODE_END: ('END', opcodes._end, { + opcodes.SUB_OPCODE_END: ( + 'WAKE', + opcodes._end + ), + opcodes.SUB_OPCODE_SLEEP: ( + 'SLEEP', + opcodes._sleep, + lambda op: 'SLEEP %s' % op.cycle_sel + ), + }), + opcodes.OPCODE_HALT: ('HALT', opcodes._halt), + opcodes.OPCODE_I2C: ( + 'I2C', + opcodes._i2c, + lambda op: 'I2C_%s %s, %s, %s, %s' % ('RD' if op.rw == 0 else 'WR', op.sub_addr, op.high, op.low, op.i2c_sel) + ), + opcodes.OPCODE_LD: ('LD', opcodes._ld, lambda op: 'LD r%s, r%s, %s' % (op.dreg, op.sreg, op.offset)), + opcodes.OPCODE_ST: ('ST', opcodes._st, lambda op: 'ST r%s, r%s, %s' % (op.sreg, op.dreg, op.offset)), + opcodes.OPCODE_RD_REG: ( + 'RD_REG', + opcodes._rd_reg, + lambda op: 'REG_RD 0x%x, %s, %s' % (op.periph_sel << 8 | op.addr, op.high, op.low) + ), + opcodes.OPCODE_WR_REG: ( + 'WR_REG', + opcodes._wr_reg, + lambda op: 'REG_WR 0x%x, %s, %s, %s' % (op.periph_sel << 8 | op.addr, op.high, op.low, op.data) + ), + opcodes.OPCODE_TSENS: ('TSENS', opcodes._tsens, lambda op: 'TSENS r%s, %s' % (op.dreg, op.delay)), +} + + +def decode_instruction(i): + if i == 0: + raise Exception('') + + ins = opcodes._end + ins.all = i # abuse a struct to get opcode + + params = lookup.get(ins.opcode, None) + + if not params: + raise Exception('Unknown instruction') + + if len(params) == 3: + name, ins, third = params + ins.all = i + + if callable(third): + params = (third(ins), ins) + else: + params = third.get(ins.sub_opcode, ()) + + if len(params) == 3: + name, ins, pretty = params + ins.all = i + name = pretty(ins) + else: + name, ins = params + ins.all = i + + return ins, name + + +def get_instruction_fields(ins): + possible_fields = ( + 'addr', 'cmp', 'cycle_sel', 'cycles', 'data', 'delay', 'dreg', + 'high', 'i2c_sel', 'imm', 'low', 'mux', 'offset', 'opcode', + 'periph_sel', 'reg', 'rw', 'sar_sel', 'sel', 'sign', 'sreg', + 'sub_addr', 'sub_opcode', 'treg', 'type', 'unused', 'unused1', + 'unused2', 'wakeup' + ) + field_details = [] + for field in possible_fields: + extra = '' + try: + # eval is ugly but constrained to possible_fields and variable ins + val = eval('i.%s' % field, {}, {'i': ins}) + if (val>9): + extra = ' (0x%02x)' % val + except KeyError: + continue + + if field == 'sel': # ALU + if ins.sub_opcode == opcodes.SUB_OPCODE_ALU_CNT: + extra = ' (%s)' % alu_cnt_ops[val] + else: + extra = ' (%s)' % alu_ops[val] + elif field == 'type': # JUMP + extra = ' (%s)' % jump_types[val] + elif field == 'cmp': # JUMPR/JUMPS + extra = ' (%s)' % cmp_ops[val] + + field_details.append((field, val, extra)) + + return field_details + + +def chunk_into_words(code, bytes_per_word, byteorder): + chunks = [ + ubinascii.hexlify(code[i:i + bytes_per_word]) + for i in range(0, len(code), bytes_per_word) + ] + + words = [int.from_bytes(ubinascii.unhexlify(i), byteorder) for i in chunks] + + return words + + +def print_ulp_header(h): + print('header') + print('ULP magic : %s (0x%08x)' % (h.magic.to_bytes(4, 'little'), h.magic)) + print('.text offset : %s (0x%02x)' % (h.text_offset, h.text_offset)) + print('.text size : %s (0x%02x)' % (h.text_size, h.text_size)) + print('.data offset : %s (0x%02x)' % (h.text_offset+h.text_size, h.text_offset+h.text_size)) + print('.data size : %s (0x%02x)' % (h.data_size, h.data_size)) + print('.bss size : %s (0x%02x)' % (h.bss_size, h.bss_size)) + print('----------------------------------------') + + +def print_code_line(byte_offset, i, asm): + lineformat = '{0:04x} {1} {2}' + hex = ubinascii.hexlify(i.to_bytes(4, 'little')) + print(lineformat.format(byte_offset, hex.decode('utf-8'), asm)) + + +def decode_instruction_and_print(byte_offset, i, verbose=False): + try: + ins, name = decode_instruction(i) + except Exception as e: + print_code_line(byte_offset, i, e) + return + + print_code_line(byte_offset, i, name) + + if verbose: + for field, val, extra in get_instruction_fields(ins): + print(" {:10} = {:3}{}".format(field, val, extra)) + + +def print_text_section(code, verbose=False): + print('.text') + + words = chunk_into_words(code, bytes_per_word=4, byteorder='little') + + for idx, i in enumerate(words): + decode_instruction_and_print(idx << 2,i , verbose) + + +def print_data_section(data_offset, code): + print('.data') + + words = chunk_into_words(code, bytes_per_word=4, byteorder='little') + + for idx, i in enumerate(words): + asm = "" if i == 0 else "" + print_code_line(data_offset + (idx << 2), i, asm) + + +def disassemble_manually(byte_sequence_string, verbose=False): + sequence = byte_sequence_string.strip().replace(' ','') + chars_per_instruction = 8 + list = [ + sequence[i:i+chars_per_instruction] + for i in range(0, len(sequence), chars_per_instruction) + ] + + for idx, instruction in enumerate(list): + byte_sequence = ubinascii.unhexlify(instruction.replace(' ','')) + i = int.from_bytes(byte_sequence, 'little') + decode_instruction_and_print(idx << 2, i, verbose) + + +def disassemble_file(filename, verbose=False): + with open(filename, 'rb') as f: + data = f.read() + + binary_header_struct_def = dict( + magic = 0 | UINT32, + text_offset = 4 | UINT16, + text_size = 6 | UINT16, + data_size = 8 | UINT16, + bss_size = 10 | UINT16, + ) + h = struct(addressof(data), binary_header_struct_def, LITTLE_ENDIAN) + + if (h.magic != 0x00706c75): + print('Invalid signature: 0x%08x (should be: 0x%08x)' % (h.magic, 0x00706c75)) + return + + if verbose: + print_ulp_header(h) + + code = data[h.text_offset:(h.text_offset+h.text_size)] + print_text_section(code, verbose) + + if verbose: + print('----------------------------------------') + + data_offset = h.text_offset+h.text_size + code = data[data_offset:(data_offset+h.data_size)] + print_data_section(data_offset-h.text_offset, code) + + +def print_help(): + print('Usage: disassemble.py [] [-m | ]') + print('') + print('Options:') + print(' -h Show this help text') + print(' -m Sequence of hex bytes (8 per instruction)') + print(' -v Verbose mode. Show ULP header and fields of each instruction') + print(' Path to ULP binary') + pass + + +def handle_cmdline(params): + verbose = False + filename = None + byte_sequence = None + + while params: + if params[0] == '-h': + print_help() + sys.exit(0) + elif params[0] == '-m': + if len(params) == 1: + print_help() + sys.exit(1) + params = params[1:] # remove -m from list + + sequence_len = len(params) + for i in range(0, len(params)): + if params[i][0] == '-': # start of a next option + sequence_len = i-1 + break + + if sequence_len < 0: + print_help() + sys.exit(1) + + byte_sequence = "".join(params[:sequence_len+1]) + params = params[sequence_len:] + elif params[0] == '-v': + verbose = True + elif params[0][0] == '-': + # ignore unknown options for now + pass + else: + if not filename: + filename = params[0] + + params = params[1:] # remove first param from list + + if byte_sequence: + disassemble_manually(byte_sequence, verbose) + elif filename: + disassemble_file(filename, verbose) + + +if sys.argv: # if run from cmdline + handle_cmdline(sys.argv[1:]) diff --git a/tools/esp32_ulp b/tools/esp32_ulp new file mode 120000 index 0000000..0bc67d4 --- /dev/null +++ b/tools/esp32_ulp @@ -0,0 +1 @@ +../esp32_ulp \ No newline at end of file