diff --git a/.gitignore b/.gitignore index ac78360..0b7309c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,16 @@ -tests/compat/*.bin -tests/compat/*.elf -tests/compat/*.o -tests/compat/*.ulp -tests/compat/*.log +tests/binutils-gdb +tests/esp-idf +tests/ulptool +tests/**/*.bin +tests/**/*.elf +tests/**/*.o +tests/**/*.ulp +tests/**/*.log +tests/**/*.pre +tests/log +tests/*.lst +tests/*.log +tests/defines*.db demo.ulp *.pyc *.pyo diff --git a/README.rst b/README.rst index e29d841..20ddee1 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ micropython-esp32-ulp is an assembler toolchain for the ESP32 ULP (Ultra Low-Pow Co-Processor, written in MicroPython. It can translate small assembly language programs to a loadable/executable -ULP machine code binary, directly on the ESP32 microcontroller. +ULP-FSM (not RISC-V) machine code binary, directly on a ESP32 microcontroller. This is intended as an alternative approach to assembling such programs using the `binutils-gdb toolchain `_ @@ -30,6 +30,8 @@ Features The following features are supported: * the entire `ESP32 ULP instruction set `_ +* the entire `ESP32-S2 ULP instruction set `_ + (this also covers the ESP32-S3) [#f1]_ [#f2]_ * constants defined with ``.set`` * constants defined with ``#define`` * expressions in assembly code and constant definitions @@ -37,6 +39,21 @@ The following features are supported: * many ESP32 ULP code examples found on the web will work unmodified * a simple disassembler is also provided +.. [#f1] Note: the ESP32-S2 and ESP32-S3 have the same ULP binary format between each other + but the binary format is different than that of the original ESP32 ULP. You need to + select the ``esp32s2`` cpu (`see docs `_) when assembling code for + use on an ESP32-S2/S3. + +.. [#f2] Note: The ESP32-S2 and ESP32-S3 have the same ULP binary format, but the peripheral + register addresses (those accessed with REG_RD and REG_WR) are different. For best + results, use the correct peripheral register addresses for the specific variant you + are working with. The assembler (when used with ``cpu=esp32s2``) will accept + addresses for any of the 3 variants, because they are translated into relative + offsets anyway and many registers live at the same relative offset on all 3 variants. + This conveniently means that the same assembly code can assembled unmodified for each + variant and produce a correctly working binary - as long as only peripheral registers + are used, which have the same relative offset across the variants. Use with care! + Quick start ----------- @@ -66,10 +83,12 @@ See `docs/index.rst `_. Requirements ------------ -The minimum supported version of MicroPython is v1.12. +The minimum supported version of MicroPython is v1.12. (For ESP32-S2 and S3 +devices, a version greater than v1.20 is required as versions before that +did not enable the ``esp32.ULP`` class). -An ESP32 is required to run the ULP machine code binary produced by micropython-esp32-ulp -(the ESP32-S2 will not work as it is not binary compatible with the ESP32). +An ESP32 device is required to run the ULP machine code binary produced by +micropython-esp32-ulp. License diff --git a/docs/disassembler.rst b/docs/disassembler.rst index b92a19e..ee733e9 100644 --- a/docs/disassembler.rst +++ b/docs/disassembler.rst @@ -25,6 +25,8 @@ You can also specify additional options to ``disassemble.py`` as follows: +--------------------------+----------------------------------------------------------------+ | Option | Description | +==========================+================================================================+ +| ``-c`` or ``--mcpu`` | Choose ULP variant: either esp32 or esp32s2 | ++--------------------------+----------------------------------------------------------------+ | ``-h`` | Show help text | +--------------------------+----------------------------------------------------------------+ || ``-m `` || Disassemble a provided sequence of hex bytes | @@ -43,18 +45,31 @@ 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: +Example disassembling an ESP32 ULP binary: .. code-block:: shell $ micropython -m tools.disassemble path/to/binary.ulp .text 0000 040000d0 LD r0, r1, 0 - 0004 0e0400d0 LD r2, r3, 1 + 0004 0e0000d0 LD r2, r3, 0 + 0008 04000068 ST r0, r1, 0 + 000c 0b000068 ST r3, r2, 0 + .data + 0010 00000000 + +Example disassembling an ESP32-S2 ULP binary: + +.. code-block:: shell + + $ micropython -m tools.disassemble -c esp32s2 path/to/binary.ulp + .text + 0000 040000d0 LD r0, r1, 0 + 0004 0e0000d0 LD r2, r3, 0 0008 84010068 ST r0, r1, 0 - 000c 8b090068 ST r3, r2, 2 + 000c 8b010068 ST r3, r2, 0 .data - 0000 00000000 + 0010 00000000 Disassembling a byte sequence @@ -129,18 +144,20 @@ For example: Disassembling on device ----------------------------- -The disassembler also works when used on an ESP32. +The disassembler also works when used on an ESP32 device. 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: +* upload ``tools/disassemble.py`` ``tools/decode.py`` and ``tools/decode_s2.py`` to the device + (any directory will do, as long as those 3 files are in the same directory) +* the following example code assumes you placed the 3 files into the device's "root" directory +* run the following (note, we must specify which the cpu the binary is for): .. code-block:: python from disassemble import disassemble_file # then either: - disassemble_file('path/to/file.ulp') # normal mode + disassemble_file('path/to/file.ulp', cpu='esp32s2') # normal mode # or: - disassemble_file('path/to/file.ulp', True) # verbose mode + disassemble_file('path/to/file.ulp', cpu='esp32s2', verbose=True) # verbose mode diff --git a/docs/index.rst b/docs/index.rst index 314bc68..e9ae38d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -58,6 +58,13 @@ follows: cd micropython-esp32-ulp micropython -m esp32_ulp path/to/code.S # this results in path/to/code.ulp +The assembler supports selecting a CPU to assemble for using the ``-c`` option +(valid cpu's are ``esp32`` and ``esp32s2``): + +.. code-block:: shell + + micropython -m esp32_ulp -c esp32s2 path/to/code.S # assemble for an ESP32-S2 + More examples +++++++++++++ @@ -86,12 +93,13 @@ assembly source file into a machine code binary file with a ``.ulp`` extension. That file can then be loaded directly without assembling the source again. 1. Create/upload an assembly source file and run the following to get a - loadable ULP binary as a ``.ulp`` file: + loadable ULP binary as a ``.ulp`` file (specify ``cpu='esp32s2'`` if you + have an ESP32-S2 or ESP32-S3 device): .. code-block:: python import esp32_ulp - esp32_ulp.assemble_file('code.S') # this results in code.ulp + esp32_ulp.assemble_file('code.S', cpu='esp32') # this results in code.ulp 2. The above prints out the offsets of all global symbols/labels. For the next step, you will need to note down the offset of the label, which represents @@ -153,7 +161,6 @@ Currently the following are not supported: * assembler macros using ``.macro`` * preprocessor macros using ``#define A(x,y) ...`` * including files using ``#include`` -* ESP32-S2 (not binary compatible with the ESP32) Testing @@ -164,7 +171,8 @@ output is identical with what Espressif's esp32-elf-as (from their `binutils-gdb `_) produces. micropython-esp32-ulp has been tested on the Unix port of MicroPython and on real ESP32 -devices with the chip type ESP32D0WDQ6 (revision 1) without SPIRAM. +devices with the chip type ESP32D0WDQ6 (revision 1) without SPIRAM as well as ESP32-S2 +(ESP32-S2FH4) and ESP32-S3 (ESP32-S3R8) devices. Consult the Github Actions `workflow definition file `_ for how to run the different tests. diff --git a/docs/preprocess.rst b/docs/preprocess.rst index 4aa3c7b..45569a5 100644 --- a/docs/preprocess.rst +++ b/docs/preprocess.rst @@ -95,6 +95,21 @@ are not needed on the device either.) micropython -m esp32_ulp.parse_to_db \ esp-idf/components/soc/esp32/include/soc/{soc,soc_ulp,rtc_cntl_reg,rtc_io_reg,sens_reg}.h + + .. warning:: + + `:warning:` Ensure that you include the header files for the correct + variant you are working with. In the example code above, simply switch + ``esp32`` to ``esp32s2`` or ``esp32s3`` in the path to the include files. + + There are subtle differences across the ESP32 variants such as which + constants are available or the value of certain constants. For example, + peripheral register addresses differ between the 3 variants even though + many constants for peripheral registers are available on all 3 variants. + Other constants such as those relating to the HOLD functionality of touch + pads are only available on the original ESP32. + + 2. Using the defines database during preprocessing The preprocessor will automatically use a defines database, when using the @@ -108,6 +123,7 @@ are not needed on the device either.) or instantiate the ``Preprocessor`` class directly, without passing it a DefinesDB instance via ``use_db``. + Design choices -------------- diff --git a/esp32_ulp/__init__.py b/esp32_ulp/__init__.py index dddafc8..1536c16 100644 --- a/esp32_ulp/__init__.py +++ b/esp32_ulp/__init__.py @@ -6,8 +6,8 @@ garbage_collect('after import') -def src_to_binary(src): - assembler = Assembler() +def src_to_binary(src, cpu): + assembler = Assembler(cpu) src = preprocess(src) assembler.assemble(src, remove_comments=False) # comments already removed by preprocessor garbage_collect('before symbols export') @@ -19,11 +19,11 @@ def src_to_binary(src): return make_binary(text, data, bss_len) -def assemble_file(filename): +def assemble_file(filename, cpu): with open(filename) as f: src = f.read() - binary = src_to_binary(src) + binary = src_to_binary(src, cpu) if filename.endswith('.s') or filename.endswith('.S'): filename = filename[:-2] diff --git a/esp32_ulp/__main__.py b/esp32_ulp/__main__.py index 6f69bea..b28ea78 100644 --- a/esp32_ulp/__main__.py +++ b/esp32_ulp/__main__.py @@ -2,10 +2,18 @@ from . import assemble_file -def main(fn): - assemble_file(fn) +def main(fn, cpu): + assemble_file(fn, cpu) if __name__ == '__main__': - main(sys.argv[1]) + cpu = 'esp32' + filename = sys.argv[1] + if len(sys.argv) > 3: + if sys.argv[1] in ('-c', '--mcpu'): + cpu = sys.argv[2].lower() + if cpu not in ('esp32', 'esp32s2'): + raise ValueError('Invalid cpu') + filename = sys.argv[3] + main(filename, cpu) diff --git a/esp32_ulp/assemble.py b/esp32_ulp/assemble.py index 0ec11ec..7d1101f 100644 --- a/esp32_ulp/assemble.py +++ b/esp32_ulp/assemble.py @@ -3,7 +3,6 @@ """ import re -from . import opcodes from .nocomment import remove_comments as do_remove_comments from .util import garbage_collect @@ -88,9 +87,19 @@ def set_global(self, symbol): class Assembler: - def __init__(self, symbols=None, bases=None, globals=None): + def __init__(self, cpu='esp32', symbols=None, bases=None, globals=None): + if cpu == 'esp32': + opcode_module = 'opcodes' + elif cpu == 'esp32s2': + opcode_module = 'opcodes_s2' + else: + raise ValueError('Invalid cpu') + + relative_import = 1 if '/' in __file__ else 0 + self.opcodes = __import__(opcode_module, None, None, [], relative_import) + self.symbols = SymbolTable(symbols or {}, bases or {}, globals or {}) - opcodes.symbols = self.symbols # XXX dirty hack + self.opcodes.symbols = self.symbols # XXX dirty hack # regex for parsing assembly lines # format: [[whitespace]label:][whitespace][opcode[whitespace arg[,arg...]]] @@ -223,7 +232,7 @@ def d_align(self, align=4, fill=None): self.fill(self.section, amount, fill) def d_set(self, symbol, expr): - value = int(opcodes.eval_arg(expr)) + value = int(self.opcodes.eval_arg(expr)) self.symbols.set_sym(symbol, ABS, None, value) def d_global(self, symbol): @@ -264,13 +273,13 @@ def assembler_pass(self, lines): else: # machine instruction opcode_lower = opcode.lower() - func = getattr(opcodes, 'i_' + opcode_lower, None) + func = getattr(self.opcodes, 'i_' + opcode_lower, None) if func is not None: if self.a_pass == 1: # during the first pass, symbols are not all known yet. # so we add empty instructions to the section, to determine # section sizes and symbol offsets for pass 2. - result = (0,) * opcodes.no_of_instr(opcode_lower, args) + result = (0,) * self.opcodes.no_of_instr(opcode_lower, args) else: result = func(*args) diff --git a/esp32_ulp/opcodes.py b/esp32_ulp/opcodes.py index 6910081..4efce0c 100644 --- a/esp32_ulp/opcodes.py +++ b/esp32_ulp/opcodes.py @@ -379,7 +379,7 @@ def i_reg_wr(reg, high_bit, low_bit, val): _wr_reg.addr = reg & 0xff _wr_reg.periph_sel = (reg & 0x300) >> 8 else: - _wr_reg.addr = (reg & 0xff) >> 2 + _wr_reg.addr = (reg >> 2) & 0xff _wr_reg.periph_sel = _soc_reg_to_ulp_periph_sel(reg) _wr_reg.data = get_imm(val) _wr_reg.low = get_imm(low_bit) @@ -394,7 +394,7 @@ def i_reg_rd(reg, high_bit, low_bit): _rd_reg.addr = reg & 0xff _rd_reg.periph_sel = (reg & 0x300) >> 8 else: - _rd_reg.addr = (reg & 0xff) >> 2 + _rd_reg.addr = (reg >> 2) & 0xff _rd_reg.periph_sel = _soc_reg_to_ulp_periph_sel(reg) _rd_reg.unused = 0 _rd_reg.low = get_imm(low_bit) diff --git a/esp32_ulp/opcodes_s2.py b/esp32_ulp/opcodes_s2.py new file mode 100644 index 0000000..9c64642 --- /dev/null +++ b/esp32_ulp/opcodes_s2.py @@ -0,0 +1,856 @@ +""" +ESP32 ULP Co-Processor Instructions +""" + +from ucollections import namedtuple +from uctypes import struct, addressof, LITTLE_ENDIAN, UINT32, BFUINT32, BF_POS, BF_LEN + +from .util import split_tokens, validate_expression + +# XXX dirty hack: use a global for the symbol table +symbols = None + +# Opcodes, Sub-Opcodes, Modes, ... + +OPCODE_WR_REG = 1 +OPCODE_RD_REG = 2 + +DR_REG_MAX_DIRECT = 0x3ff +RD_REG_PERIPH_RTC_CNTL = 0 +RD_REG_PERIPH_RTC_IO = 1 +RD_REG_PERIPH_SENS = 2 +RD_REG_PERIPH_RTC_I2C = 3 + +OPCODE_I2C = 3 +SUB_OPCODE_I2C_RD = 0 +SUB_OPCODE_I2C_WR = 1 + +OPCODE_DELAY = 4 + +OPCODE_ADC = 5 + +OPCODE_ST = 6 +SUB_OPCODE_ST_AUTO = 1 +# Note: SUB_OPCODE_ST_OFFSET should be 3 +# But in binutils-gdb they hardcoded the value to 2 +# This appears to be a bug, if one looks at the Technical +# Reference Manual of the ESP32-S2. +# +# This issue is reported as a pull-request with fix: +# https://github.com/espressif/binutils-gdb/pull/2 +# +# We'll hard code this to 2 for now, until this is resolved in +# binutils-gdb or the Technical Reference Manual is updated. +SUB_OPCODE_ST_OFFSET = 2 # should be 3 +SUB_OPCODE_ST = 4 + +OPCODE_ALU = 7 +SUB_OPCODE_ALU_REG = 0 +SUB_OPCODE_ALU_IMM = 1 +ALU_SEL_ADD = 0 +ALU_SEL_SUB = 1 +ALU_SEL_AND = 2 +ALU_SEL_OR = 3 +ALU_SEL_MOV = 4 +ALU_SEL_LSH = 5 +ALU_SEL_RSH = 6 +SUB_OPCODE_ALU_CNT = 2 +ALU_SEL_STAGE_INC = 0 +ALU_SEL_STAGE_DEC = 1 +ALU_SEL_STAGE_RST = 2 + +OPCODE_BRANCH = 8 +# https://github.com/espressif/binutils-esp32ulp/blob/d61f86f97eda43fc118df30d019fc062aaa6bc8d/include/opcode/esp32ulp_esp32.h#L85 +SUB_OPCODE_B = 0 +SUB_OPCODE_BX = 1 +SUB_OPCODE_BS = 2 +BX_JUMP_TYPE_DIRECT = 0 +BX_JUMP_TYPE_ZERO = 1 +BX_JUMP_TYPE_OVF = 2 +# https://github.com/espressif/binutils-esp32ulp/blob/d61f86f97eda43fc118df30d019fc062aaa6bc8d/gas/config/tc-esp32ulp.h#L91 +B_CMP_L = 0 +B_CMP_G = 1 +B_CMP_E = 2 +JUMPS_EQ = 4 +JUMPS_GT = 3 +JUMPS_LT = 1 +JUMPS_LE = 5 +JUMPS_GE = 7 + +OPCODE_END = 9 +SUB_OPCODE_END = 0 +SUB_OPCODE_SLEEP = 1 + +OPCODE_TSENS = 10 + +OPCODE_HALT = 11 + +OPCODE_LD = 13 + + +def make_ins_struct_def(layout): + lines = layout.strip().splitlines() + pos = 0 # bitfield definitions start from lsb + struct_def = {} + for line in lines: + bitfield = line.split('#', 1)[0] # get rid of comment + name, width = bitfield.split(':', 1) + name = name.strip() + width = int(width.strip()) + struct_def[name] = BFUINT32 | pos << BF_POS | width << BF_LEN + pos += width + if pos != 32: + raise ValueError('make_ins: bit field widths must sum up to 32. [%s]' % layout) + struct_def['all'] = UINT32 + return struct_def + + +def make_ins(layout): + """ + transform textual instruction layout description into a ready-to-use uctypes struct + """ + struct_def = make_ins_struct_def(layout) + instruction = bytearray(4) + return struct(addressof(instruction), struct_def, LITTLE_ENDIAN) + + +# instruction structure definitions + +_wr_reg = make_ins(""" + addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC + periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) + data : 8 # 8 bits of data to write + low : 5 # Low bit + high : 5 # High bit + opcode : 4 # Opcode (OPCODE_WR_REG) +""") + + +_rd_reg = make_ins(""" + addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC + periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) + unused : 8 # Unused + low : 5 # Low bit + high : 5 # High bit + opcode : 4 # Opcode (OPCODE_RD_REG) +""") + + +_i2c = make_ins(""" + sub_addr : 8 # address within I2C slave + data : 8 # Data to write (not used for read) + low : 3 # low bit + high : 3 # high bit + i2c_sel : 4 # select i2c slave via SENS_I2C_SLAVE_ADDRx + unused : 1 # Unused + rw : 1 # Write (1) or read (0) + opcode : 4 # Opcode (OPCODE_I2C) +""") + + +_delay = make_ins(""" + cycles : 16 # Number of cycles to sleep + unused : 12 # Unused + opcode : 4 # Opcode (OPCODE_DELAY) +""") + + +_tsens = make_ins(""" + dreg : 2 # Register where to store TSENS result + delay : 14 # Number of cycles needed to obtain a measurement + unused : 12 # Unused + opcode : 4 # Opcode (OPCODE_TSENS) +""") + + +_adc = make_ins(""" + dreg : 2 # Register where to store ADC result + mux : 4 # Select SARADC pad (mux + 1) + sar_sel : 1 # Select SARADC0 (0) or SARADC1 (1) + unused1 : 1 # Unused + cycles : 16 # TBD, cycles used for measurement + unused2 : 4 # Unused + opcode: 4 # Opcode (OPCODE_ADC) +""") + + +_st = make_ins(""" + sreg : 2 # Register which contains data to store + dreg : 2 # Register which contains address in RTC memory (expressed in words) + label : 2 # Data label + upper : 1 # Write low (0) or high (1) half-word + wr_way : 2 # Write the (0) full-word or with label (1) or without label (3) + unused1 : 1 # Unused + offset : 11 # Offset to add to dreg + unused2 : 4 # Unused + sub_opcode : 3 # Sub opcode (SUB_OPCODE_ST) + opcode : 4 # Opcode (OPCODE_ST) +""") + + +_alu_reg = make_ins(""" + dreg : 2 # Destination register + sreg : 2 # Register with operand A + treg : 2 # Register with operand B + unused1 : 15 # Unused + sel : 4 # Operation to perform, one of ALU_SEL_xxx + unused2 : 1 # Unused + sub_opcode : 2 # Sub opcode (SUB_OPCODE_ALU_REG) + opcode : 4 # Opcode (OPCODE_ALU) +""") + + +_alu_imm = make_ins(""" + dreg : 2 # Destination register + sreg : 2 # Register with operand A + imm : 16 # Immediate value of operand B + unused1 : 1 # Unused + sel : 4 # Operation to perform, one of ALU_SEL_xxx + unused2 : 1 # Unused + sub_opcode : 2 # Sub opcode (SUB_OPCODE_ALU_IMM) + opcode : 4 # Opcode (OPCODE_ALU) +""") + + +_alu_cnt = make_ins(""" + unused1 : 4 # Unused + imm : 8 # Immediate value (to inc / dec stage counter) + unused2 : 9 # Unused + sel : 4 # Operation to perform, one of ALU_SEL_xxx + unused3 : 1 # Unused + sub_opcode : 2 # Sub opcode (SUB_OPCODE_ALU_CNT) + opcode : 4 # Opcode (OPCODE_ALU) +""") + + +_bx = make_ins(""" + dreg : 2 # Register which contains target PC, expressed in words (used if .reg == 1) + addr : 11 # Target PC, expressed in words (used if .reg == 0) + unused1 : 8 # Unused + reg : 1 # Target PC in register (1) or immediate (0) + type : 3 # Jump condition (BX_JUMP_TYPE_xxx) + unused2 : 1 # Unused + sub_opcode : 2 # Sub opcode (SUB_OPCODE_BX) + opcode : 4 # Opcode (OPCODE_BRANCH) +""") + + +_b = make_ins(""" + imm : 16 # Immediate value to compare against + cmp : 2 # Comparison to perform: BRCOND_LT or BRCOND_GE + offset : 7 # Absolute value of target PC offset w.r.t. current PC, expressed in words + sign : 1 # Sign of target PC offset: 0: positive, 1: negative + sub_opcode : 2 # Sub opcode (SUB_OPCODE_B) + opcode : 4 # Opcode (OPCODE_BRANCH) +""") + + +_bs = make_ins(""" + imm : 8 # Immediate value to compare against + unused : 7 # Unused + cmp : 3 # Comparison to perform: BRCOND_LT, GT or EQ + offset : 7 # Absolute value of target PC offset w.r.t. current PC, expressed in words + sign : 1 # Sign of target PC offset: 0: positive, 1: negative + sub_opcode : 2 # Sub opcode (SUB_OPCODE_BS) + opcode : 4 # Opcode (OPCODE_BRANCH) +""") + + +_end = make_ins(""" + wakeup : 1 # Set to 1 to wake up chip + unused : 25 # Unused + sub_opcode : 2 # Sub opcode (SUB_OPCODE_END) + opcode : 4 # Opcode (OPCODE_END) +""") + + +_halt = make_ins(""" + unused : 28 # Unused + opcode : 4 # Opcode (OPCODE_HALT) +""") + + +_ld = make_ins(""" + dreg : 2 # Register where the data should be loaded to + sreg : 2 # Register which contains address in RTC memory (expressed in words) + unused1 : 6 # Unused + offset : 11 # Offset to add to sreg + unused2 : 6 # Unused + rd_upper : 1 # Read low (0) or high (1) half-word + opcode : 4 # Opcode (OPCODE_LD) +""") + + +# assembler opcode definitions + +REG, IMM, COND, SYM = 0, 1, 2, 3 +ARG = namedtuple('ARG', ('type', 'value', 'raw')) + + +def eval_arg(arg): + parts = [] + for token in split_tokens(arg): + if symbols.has_sym(token): + _, _, sym_value = symbols.get_sym(token) + parts.append(str(sym_value)) + else: + parts.append(token) + parts = "".join(parts) + if not validate_expression(parts): + raise ValueError('Unsupported expression: %s' % parts) + return eval(parts) + + +def arg_qualify(arg): + """ + look at arg and qualify its type: + REG(ister), IMM(ediate) value + + then convert arg into a int value, e.g. 'R1' -> 1 or '0x20' -> 32. + + return result as ARG namedtuple + """ + arg_lower = arg.lower() + if len(arg) == 2: + if arg_lower[0] == 'r' and arg[1] in '0123456789': + reg = int(arg[1]) + if 0 <= reg <= 3: + return ARG(REG, reg, arg) + raise ValueError('arg_qualify: valid registers are r0, r1, r2, r3. Given: %s' % arg) + if arg_lower in ['--', 'eq', 'ov', 'lt', 'gt', 'ge', 'le']: + return ARG(COND, arg_lower, arg) + try: + return ARG(IMM, int(arg), arg) + except ValueError: + pass + try: + entry = symbols.get_sym(arg) + except KeyError: + return ARG(IMM, int(eval_arg(arg)), arg) + else: + return ARG(SYM, entry, arg) + + +def get_reg(arg): + if isinstance(arg, str): + arg = arg_qualify(arg) + if arg.type == REG: + return arg.value + raise TypeError('wanted: register, got: %s' % arg.raw) + + +def get_imm(arg): + if isinstance(arg, str): + arg = arg_qualify(arg) + if arg.type == IMM: + return arg.value + if arg.type == SYM: + return symbols.resolve_absolute(arg.value) + raise TypeError('wanted: immediate, got: %s' % arg.raw) + + +get_abs = get_imm + + +def get_rel(arg): + if isinstance(arg, str): + arg = arg_qualify(arg) + if arg.type == IMM: + if arg.value & 3 != 0: # bitwise version of: arg.value % 4 != 0 + raise ValueError('Relative offset must be a multiple of 4') + return IMM, arg.value >> 2 # bitwise version of: arg.value // 4 + if arg.type == SYM: + return SYM, symbols.resolve_relative(arg.value) + raise TypeError('wanted: immediate, got: %s' % arg.raw) + + +def get_cond(arg): + if isinstance(arg, str): + arg = arg_qualify(arg) + if arg.type == COND: + return arg.value + raise TypeError('wanted: condition, got: %s' % arg.raw) + + +def _soc_reg_to_ulp_periph_sel(reg): + # Accept peripheral register addresses of either the S2 or S3 + # Since the address in the reg_rd or reg_wr instruction is an + # offset and not the actual address, and since the range of + # peripheral register addresses is the same for both the S2 + # and S3, we will accept addresses in either address range. + # This allows us to avoid intruducing an additional cpu type + # for the S3, which is otherwise identical (binary format) to + # the S2. + if 0x3f408000 <= reg <= 0x3f40afff: # ESP32-S2 address range + socmod = 'soc_s2' + elif 0x60008000 <= reg <= 0x6000afff: # ESP32-S3 address range + socmod = 'soc_s3' + # Accept original ESP32 range too + # because binutils-gdb, when using cpu esp32s2 is broken + # and does not accept the address ranges of the esp32s2. + # As a nice side-effect some assembly written for an ESP32 + # would work as-is when re-assembled for an ESP32-S2, + # because many (not all!) peripheral registers live at the + # same offset on all 3 ESP32s. + elif 0x3ff48000 <= reg <= 0x3ff4afff: # original ESP32 address range + socmod = 'soc' + else: + raise ValueError("invalid register base") + + relative_import = 1 if '/' in __file__ else 0 + soc = __import__(socmod, None, None, [], relative_import) + + # Map SoC peripheral register to periph_sel field of RD_REG and WR_REG instructions. + if reg < soc.DR_REG_RTCCNTL_BASE: + raise ValueError("invalid register base") + elif reg < soc.DR_REG_RTCIO_BASE: + ret = RD_REG_PERIPH_RTC_CNTL + elif reg < soc.DR_REG_SENS_BASE: + ret = RD_REG_PERIPH_RTC_IO + elif reg < soc.DR_REG_RTC_I2C_BASE: + ret = RD_REG_PERIPH_SENS + elif reg < soc.DR_REG_IO_MUX_BASE: + ret = RD_REG_PERIPH_RTC_I2C + else: + raise ValueError("invalid register base") + return ret + + +def i_reg_wr(reg, high_bit, low_bit, val): + reg = get_imm(reg) + if reg <= DR_REG_MAX_DIRECT: # see https://github.com/espressif/binutils-esp32ulp/blob/master/gas/config/tc-esp32ulp_esp32.c + _wr_reg.addr = reg & 0xff + _wr_reg.periph_sel = (reg & 0x300) >> 8 + else: + _wr_reg.addr = (reg >> 2) & 0xff + _wr_reg.periph_sel = _soc_reg_to_ulp_periph_sel(reg) + _wr_reg.data = get_imm(val) + _wr_reg.low = get_imm(low_bit) + _wr_reg.high = get_imm(high_bit) + _wr_reg.opcode = OPCODE_WR_REG + return _wr_reg.all + + +def i_reg_rd(reg, high_bit, low_bit): + reg = get_imm(reg) + if reg <= DR_REG_MAX_DIRECT: # see https://github.com/espressif/binutils-esp32ulp/blob/master/gas/config/tc-esp32ulp_esp32.c + _rd_reg.addr = reg & 0xff + _rd_reg.periph_sel = (reg & 0x300) >> 8 + else: + _rd_reg.addr = (reg >> 2) & 0xff + _rd_reg.periph_sel = _soc_reg_to_ulp_periph_sel(reg) + _rd_reg.unused = 0 + _rd_reg.low = get_imm(low_bit) + _rd_reg.high = get_imm(high_bit) + _rd_reg.opcode = OPCODE_RD_REG + return _rd_reg.all + + +def i_i2c_rd(sub_addr, high_bit, low_bit, slave_sel): + _i2c.sub_addr = get_imm(sub_addr) + _i2c.data = 0 + _i2c.low = get_imm(low_bit) + _i2c.high = get_imm(high_bit) + _i2c.i2c_sel = get_imm(slave_sel) + _i2c.unused = 0 + _i2c.rw = 0 + _i2c.opcode = OPCODE_I2C + return _i2c.all + + +def i_i2c_wr(sub_addr, value, high_bit, low_bit, slave_sel): + _i2c.sub_addr = get_imm(sub_addr) + _i2c.data = get_imm(value) + _i2c.low = get_imm(low_bit) + _i2c.high = get_imm(high_bit) + _i2c.i2c_sel = get_imm(slave_sel) + _i2c.unused = 0 + _i2c.rw = 1 + _i2c.opcode = OPCODE_I2C + return _i2c.all + + +def i_nop(): + _delay.cycles = 0 + _delay.unused = 0 + _delay.opcode = OPCODE_DELAY + return _delay.all + + +def i_wait(cycles): + _delay.cycles = get_imm(cycles) + _delay.unused = 0 + _delay.opcode = OPCODE_DELAY + return _delay.all + + +def i_tsens(reg_dest, delay): + _tsens.dreg = get_reg(reg_dest) + _tsens.delay = get_imm(delay) + _tsens.unused = 0 + _tsens.opcode = OPCODE_TSENS + return _tsens.all + + +def i_adc(reg_dest, adc_idx, mux, _not_used=None): + _adc.dreg = get_reg(reg_dest) + _adc.mux = get_imm(mux) + _adc.sar_sel = get_imm(adc_idx) + _adc.unused1 = 0 + _adc.cycles = 0 + _adc.unused2 = 0 + _adc.opcode = OPCODE_ADC + return _adc.all + + +def i_st_manual(reg_val, reg_addr, offset, label, upper, wr_way): + _st.dreg = get_reg(reg_addr) + _st.sreg = get_reg(reg_val) + _st.label = get_imm(label) + _st.upper = upper + _st.wr_way = wr_way + _st.unused1 = 0 + _st.offset = get_imm(offset) // 4 + _st.unused2 = 0 + _st.sub_opcode = SUB_OPCODE_ST + _st.opcode = OPCODE_ST + return _st.all + + +def i_stl(reg_val, reg_addr, offset, label=None): + return i_st_manual(reg_val, reg_addr, offset, label if label else "0", 0, 1 if label else 3) + + +def i_sth(reg_val, reg_addr, offset, label=None): + return i_st_manual(reg_val, reg_addr, offset, label if label else "0", 1, 1 if label else 3) + + +def i_st(reg_val, reg_addr, offset): + return i_stl(reg_val, reg_addr, offset) + + +def i_st32(reg_val, reg_addr, offset, label): + return i_st_manual(reg_val, reg_addr, offset, label, 0, 0) + + +def i_st_auto(reg_val, reg_addr, label, wr_way): + _st.dreg = get_reg(reg_addr) + _st.sreg = get_reg(reg_val) + _st.label = get_imm(label) + _st.upper = 0 + _st.wr_way = wr_way + _st.unused1 = 0 + _st.offset = 0 + _st.unused2 = 0 + _st.sub_opcode = SUB_OPCODE_ST_AUTO + _st.opcode = OPCODE_ST + return _st.all + + +def i_sto(offset): + _st.dreg = 0 + _st.sreg = 0 + _st.label = 0 + _st.upper = 0 + _st.wr_way = 0 + _st.unused1 = 0 + _st.offset = get_imm(offset) // 4 + _st.unused2 = 0 + _st.sub_opcode = SUB_OPCODE_ST_OFFSET + _st.opcode = OPCODE_ST + return _st.all + + +def i_sti(reg_val, reg_addr, label=None): + return i_st_auto(reg_val, reg_addr, label if label else "0", 1 if label else 3) + + +def i_sti32(reg_val, reg_addr, label): + return i_st_auto(reg_val, reg_addr, label, 0) + + +def i_halt(): + _halt.unused = 0 + _halt.opcode = OPCODE_HALT + return _halt.all + + +def i_ld_manual(reg_dest, reg_addr, offset, rd_upper): + _ld.dreg = get_reg(reg_dest) + _ld.sreg = get_reg(reg_addr) + _ld.unused1 = 0 + _ld.offset = get_imm(offset) // 4 + _ld.unused2 = 0 + _ld.rd_upper = rd_upper + _ld.opcode = OPCODE_LD + return _ld.all + + +def i_ldl(reg_dest, reg_addr, offset): + return i_ld_manual(reg_dest, reg_addr, offset, 0) + + +def i_ldh(reg_dest, reg_addr, offset): + return i_ld_manual(reg_dest, reg_addr, offset, 1) + + +def i_ld(reg_dest, reg_addr, offset): + return i_ldl(reg_dest, reg_addr, offset) + + +def i_move(reg_dest, reg_imm_src): + # this is the only ALU instruction with 2 args: move r0, r1 + dest = get_reg(reg_dest) + src = arg_qualify(reg_imm_src) + if src.type == REG: + _alu_reg.dreg = dest + _alu_reg.sreg = src.value + _alu_reg.treg = src.value # XXX undocumented, this is the value binutils-esp32 uses + _alu_reg.unused1 = 0 + _alu_reg.sel = ALU_SEL_MOV + _alu_reg.unused2 = 0 + _alu_reg.sub_opcode = SUB_OPCODE_ALU_REG + _alu_reg.opcode = OPCODE_ALU + return _alu_reg.all + if src.type == IMM or src.type == SYM: + _alu_imm.dreg = dest + _alu_imm.sreg = 0 + _alu_imm.imm = get_abs(src) + _alu_imm.unused1 = 0 + _alu_imm.sel = ALU_SEL_MOV + _alu_imm.unused2 = 0 + _alu_imm.sub_opcode = SUB_OPCODE_ALU_IMM + _alu_imm.opcode = OPCODE_ALU + return _alu_imm.all + raise TypeError('unsupported operand: %s' % src.raw) + + +def _alu3(reg_dest, reg_src1, reg_imm_src2, alu_sel): + """ + ALU instructions with 3 args, like e.g. add r1, r2, r3 + """ + dest = get_reg(reg_dest) + src1 = get_reg(reg_src1) + src2 = arg_qualify(reg_imm_src2) + if src2.type == REG: + _alu_reg.dreg = dest + _alu_reg.sreg = src1 + _alu_reg.treg = src2.value + _alu_reg.unused1 = 0 + _alu_reg.sel = alu_sel + _alu_reg.unused2 = 0 + _alu_reg.sub_opcode = SUB_OPCODE_ALU_REG + _alu_reg.opcode = OPCODE_ALU + return _alu_reg.all + if src2.type == IMM or src2.type == SYM: + _alu_imm.dreg = dest + _alu_imm.sreg = src1 + _alu_imm.imm = get_abs(src2) + _alu_imm.unused1 = 0 + _alu_imm.sel = alu_sel + _alu_imm.unused2 = 0 + _alu_imm.sub_opcode = SUB_OPCODE_ALU_IMM + _alu_imm.opcode = OPCODE_ALU + return _alu_imm.all + raise TypeError('unsupported operand: %s' % src2.raw) + + +def i_add(reg_dest, reg_src1, reg_imm_src2): + return _alu3(reg_dest, reg_src1, reg_imm_src2, ALU_SEL_ADD) + + +def i_sub(reg_dest, reg_src1, reg_imm_src2): + return _alu3(reg_dest, reg_src1, reg_imm_src2, ALU_SEL_SUB) + + +def i_and(reg_dest, reg_src1, reg_imm_src2): + return _alu3(reg_dest, reg_src1, reg_imm_src2, ALU_SEL_AND) + + +def i_or(reg_dest, reg_src1, reg_imm_src2): + return _alu3(reg_dest, reg_src1, reg_imm_src2, ALU_SEL_OR) + + +def i_lsh(reg_dest, reg_src1, reg_imm_src2): + return _alu3(reg_dest, reg_src1, reg_imm_src2, ALU_SEL_LSH) + + +def i_rsh(reg_dest, reg_src1, reg_imm_src2): + return _alu3(reg_dest, reg_src1, reg_imm_src2, ALU_SEL_RSH) + + +def _alu_stage(imm, alu_sel): + """ + Stage counter instructions with 1 arg: stage_inc / stage_dec + """ + imm = get_imm(imm) + _alu_cnt.unused1 = 0 + _alu_cnt.imm = imm + _alu_cnt.unused2 = 0 + _alu_cnt.sel = alu_sel + _alu_cnt.sub_opcode = SUB_OPCODE_ALU_CNT + _alu_cnt.opcode = OPCODE_ALU + return _alu_cnt.all + + +def i_stage_inc(imm): + return _alu_stage(imm, ALU_SEL_STAGE_INC) + + +def i_stage_dec(imm): + return _alu_stage(imm, ALU_SEL_STAGE_DEC) + + +def i_stage_rst(): + return _alu_stage('0', ALU_SEL_STAGE_RST) + + +def i_wake(): + _end.wakeup = 1 + _end.unused = 0 + _end.sub_opcode = SUB_OPCODE_END + _end.opcode = OPCODE_END + return _end.all + + +# NOTE: Technically the S2 no longer has the SLEEP instruction, but +# we're keeping it, since esp32ulp-elf-as happily assembles it. +# It's now emitted as a WAIT so we'll do the same. +def i_sleep(cycles): + return i_wait(cycles) + + +def i_jump(target, condition='--'): + target = arg_qualify(target) + condition = get_cond(condition) + if condition == 'eq': + jump_type = BX_JUMP_TYPE_ZERO + elif condition == 'ov': + jump_type = BX_JUMP_TYPE_OVF + elif condition == '--': # means unconditional + jump_type = BX_JUMP_TYPE_DIRECT + else: + raise ValueError("invalid flags condition") + if target.type == IMM or target.type == SYM: + _bx.dreg = 0 + # we track label addresses in 32bit words, but immediate values are in bytes and need to get divided by 4. + _bx.addr = get_abs(target) if target.type == SYM else get_abs(target) >> 2 # bitwise version of "// 4" + _bx.unused1 = 0 + _bx.reg = 0 + _bx.type = jump_type + _bx.sub_opcode = SUB_OPCODE_BX + _bx.unused2 = 0 + _bx.opcode = OPCODE_BRANCH + return _bx.all + if target.type == REG: + _bx.dreg = target.value + _bx.addr = 0 + _bx.unused1 = 0 + _bx.reg = 1 + _bx.type = jump_type + _bx.unused2 = 0 + _bx.sub_opcode = SUB_OPCODE_BX + _bx.opcode = OPCODE_BRANCH + return _bx.all + raise TypeError('unsupported operand: %s' % target.raw) + + +def _jump_relr(threshold, cond, offset): + """ + Equivalent of I_JUMP_RELR macro in binutils-gdb esp32ulp + """ + _b.imm = threshold + _b.cmp = cond + _b.offset = abs(offset) + _b.sign = 0 if offset >= 0 else 1 + _b.sub_opcode = SUB_OPCODE_B + _b.opcode = OPCODE_BRANCH + return _b.all + + +def i_jumpr(offset, threshold, condition): + offset_type, offset = get_rel(offset) + threshold = get_imm(threshold) + condition = get_cond(condition) + if condition in ('le', 'ge'): + if condition == 'le': + cmp_op = B_CMP_L + elif condition == 'ge': + cmp_op = B_CMP_G + + # jump to target + first_ins = _jump_relr(threshold, cmp_op, offset) + + # jump over prev JUMPR + if (offset_type == IMM and offset < 0) or offset_type == SYM: + # adjust for the additional JUMPR instruction + # for IMM offsets, the offset is relative to the 2nd instruction, so only backwards jumps need adjusting + # for SYM offsets, label offsets already include the extra instruction, so both directions need adjusting + offset -= 1 + second_ins = _jump_relr(threshold, B_CMP_E, offset) + return (first_ins, second_ins) + + elif condition == 'lt': + cmp_op = B_CMP_L + elif condition == 'gt': + cmp_op = B_CMP_G + elif condition == 'eq': + cmp_op = B_CMP_E + else: + raise ValueError("invalid comparison condition") + return _jump_relr(threshold, cmp_op, offset) + + +def _jump_rels(threshold, cond, offset): + """ + Equivalent of I_JUMP_RELS macro in binutils-gdb esp32ulp + """ + _bs.imm = threshold + _bs.cmp = cond + _bs.offset = abs(offset) + _bs.sign = 0 if offset >= 0 else 1 + _bs.sub_opcode = SUB_OPCODE_BS + _bs.opcode = OPCODE_BRANCH + return _bs.all + + +def i_jumps(offset, threshold, condition): + offset_type, offset = get_rel(offset) + if (offset_type == IMM): + # This makes our assembler behave exactly like binutils-gdb, even + # though its behaviour is incorrect. binutils-gdb does not divide + # immediate offsets by 4 (i.e. it does not convert bytes to words) + # for JUMPS instructions, even though it does so for all other JUMP + # instructions, and even though the assembler for the original + # ESP32 divides immediate offsets by 4 for JUMPS instructions. + # + # The issue is reported as a pull-request with a fix here: + # https://github.com/espressif/binutils-gdb/pull/1 + # + # Once the issue is fixed in binutils-gdb, this code here should be + # removed. + offset = offset << 2 # bug in binutils-gdb + + threshold = get_imm(threshold) + condition = get_cond(condition) + if condition == 'lt': + cmp_op = JUMPS_LT + elif condition == 'le': + cmp_op = JUMPS_LE + elif condition == 'ge': + cmp_op = JUMPS_GE + elif condition == 'eq': + cmp_op = JUMPS_EQ + elif condition == 'gt': + cmp_op = JUMPS_GT + else: + raise ValueError("invalid comparison condition") + + return _jump_rels(threshold, cmp_op, offset) + + +def no_of_instr(opcode, args): + if opcode == 'jumpr' and get_cond(args[2]) in ('le', 'ge'): + return 2 + + return 1 diff --git a/esp32_ulp/soc.py b/esp32_ulp/soc.py index c6072e6..1a8845c 100644 --- a/esp32_ulp/soc.py +++ b/esp32_ulp/soc.py @@ -2,6 +2,9 @@ Address / Register definitions for the ESP32 SoC """ +# Reference: +# https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/reg_base.h + DR_REG_DPORT_BASE = 0x3ff00000 DR_REG_AES_BASE = 0x3ff01000 DR_REG_RSA_BASE = 0x3ff02000 @@ -38,7 +41,7 @@ DR_REG_SPI_ENCRYPT_BASE = 0x3ff5B000 DR_REG_NRX_BASE = 0x3ff5CC00 DR_REG_BB_BASE = 0x3ff5D000 -DR_REG_PWM_BASE = 0x3ff5E000 +DR_REG_PWM0_BASE = 0x3ff5E000 DR_REG_TIMERGROUP0_BASE = 0x3ff5F000 DR_REG_TIMERGROUP1_BASE = 0x3ff60000 DR_REG_RTCMEM0_BASE = 0x3ff61000 @@ -47,13 +50,12 @@ DR_REG_SPI2_BASE = 0x3ff64000 DR_REG_SPI3_BASE = 0x3ff65000 DR_REG_SYSCON_BASE = 0x3ff66000 -DR_REG_APB_CTRL_BASE = 0x3ff66000 +DR_REG_APB_CTRL_BASE = 0x3ff66000 # Old name for SYSCON, to be removed DR_REG_I2C1_EXT_BASE = 0x3ff67000 DR_REG_SDMMC_BASE = 0x3ff68000 DR_REG_EMAC_BASE = 0x3ff69000 +DR_REG_CAN_BASE = 0x3ff6B000 DR_REG_PWM1_BASE = 0x3ff6C000 DR_REG_I2S1_BASE = 0x3ff6D000 DR_REG_UART2_BASE = 0x3ff6E000 -DR_REG_PWM2_BASE = 0x3ff6F000 -DR_REG_PWM3_BASE = 0x3ff70000 - +PERIPHS_SPI_ENCRYPT_BASEADDR = DR_REG_SPI_ENCRYPT_BASE diff --git a/esp32_ulp/soc_s2.py b/esp32_ulp/soc_s2.py new file mode 100644 index 0000000..1e35295 --- /dev/null +++ b/esp32_ulp/soc_s2.py @@ -0,0 +1,64 @@ +""" +Address / Register definitions for the ESP32-S2 SoC +""" + +# Reference: +# https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/include/soc/reg_base.h + +DR_REG_SYSTEM_BASE = 0x3f4c0000 +DR_REG_SENSITIVE_BASE = 0x3f4c1000 +DR_REG_INTERRUPT_BASE = 0x3f4c2000 +DR_REG_DMA_COPY_BASE = 0x3f4c3000 +DR_REG_EXTMEM_BASE = 0x61800000 +DR_REG_MMU_TABLE = 0x61801000 +DR_REG_ITAG_TABLE = 0x61802000 +DR_REG_DTAG_TABLE = 0x61803000 +DR_REG_AES_BASE = 0x6003a000 +DR_REG_SHA_BASE = 0x6003b000 +DR_REG_RSA_BASE = 0x6003c000 +DR_REG_HMAC_BASE = 0x6003e000 +DR_REG_DIGITAL_SIGNATURE_BASE = 0x6003d000 +DR_REG_CRYPTO_DMA_BASE = 0x6003f000 +DR_REG_ASSIST_DEBUG_BASE = 0x3f4ce000 +DR_REG_DEDICATED_GPIO_BASE = 0x3f4cf000 +DR_REG_INTRUSION_BASE = 0x3f4d0000 +DR_REG_UART_BASE = 0x3f400000 +DR_REG_SPI1_BASE = 0x3f402000 +DR_REG_SPI0_BASE = 0x3f403000 +DR_REG_GPIO_BASE = 0x3f404000 +DR_REG_GPIO_SD_BASE = 0x3f404f00 +DR_REG_FE2_BASE = 0x3f405000 +DR_REG_FE_BASE = 0x3f406000 +DR_REG_FRC_TIMER_BASE = 0x3f407000 +DR_REG_RTCCNTL_BASE = 0x3f408000 +DR_REG_RTCIO_BASE = 0x3f408400 +DR_REG_SENS_BASE = 0x3f408800 +DR_REG_RTC_I2C_BASE = 0x3f408C00 +DR_REG_IO_MUX_BASE = 0x3f409000 +DR_REG_HINF_BASE = 0x3f40B000 +DR_REG_I2S_BASE = 0x3f40F000 +DR_REG_UART1_BASE = 0x3f410000 +DR_REG_I2C_EXT_BASE = 0x3f413000 +DR_REG_UHCI0_BASE = 0x3f414000 +DR_REG_SLCHOST_BASE = 0x3f415000 +DR_REG_RMT_BASE = 0x3f416000 +DR_REG_PCNT_BASE = 0x3f417000 +DR_REG_SLC_BASE = 0x3f418000 +DR_REG_LEDC_BASE = 0x3f419000 +DR_REG_CP_BASE = 0x3f4c3000 +DR_REG_EFUSE_BASE = 0x3f41A000 +DR_REG_NRX_BASE = 0x3f41CC00 +DR_REG_BB_BASE = 0x3f41D000 +DR_REG_TIMERGROUP0_BASE = 0x3f41F000 +DR_REG_TIMERGROUP1_BASE = 0x3f420000 +DR_REG_RTC_SLOWMEM_BASE = 0x3f421000 +DR_REG_SYSTIMER_BASE = 0x3f423000 +DR_REG_SPI2_BASE = 0x3f424000 +DR_REG_SPI3_BASE = 0x3f425000 +DR_REG_SYSCON_BASE = 0x3f426000 +DR_REG_APB_CTRL_BASE = 0x3f426000 # Old name for SYSCON, to be removed +DR_REG_I2C1_EXT_BASE = 0x3f427000 +DR_REG_SPI4_BASE = 0x3f437000 +DR_REG_USB_WRAP_BASE = 0x3f439000 +DR_REG_APB_SARADC_BASE = 0x3f440000 +DR_REG_USB_BASE = 0x60080000 diff --git a/esp32_ulp/soc_s3.py b/esp32_ulp/soc_s3.py new file mode 100644 index 0000000..821b02c --- /dev/null +++ b/esp32_ulp/soc_s3.py @@ -0,0 +1,66 @@ +""" +Address / Register definitions for the ESP32-S3 SoC +""" + +# Reference: +# https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s3/include/soc/reg_base.h + +DR_REG_UART_BASE = 0x60000000 +DR_REG_SPI1_BASE = 0x60002000 +DR_REG_SPI0_BASE = 0x60003000 +DR_REG_GPIO_BASE = 0x60004000 +DR_REG_GPIO_SD_BASE = 0x60004f00 +DR_REG_FE2_BASE = 0x60005000 +DR_REG_FE_BASE = 0x60006000 +DR_REG_EFUSE_BASE = 0x60007000 +DR_REG_RTCCNTL_BASE = 0x60008000 +DR_REG_RTCIO_BASE = 0x60008400 +DR_REG_SENS_BASE = 0x60008800 +DR_REG_RTC_I2C_BASE = 0x60008C00 +DR_REG_IO_MUX_BASE = 0x60009000 +DR_REG_HINF_BASE = 0x6000B000 +DR_REG_UHCI1_BASE = 0x6000C000 +DR_REG_I2S_BASE = 0x6000F000 +DR_REG_UART1_BASE = 0x60010000 +DR_REG_BT_BASE = 0x60011000 +DR_REG_I2C_EXT_BASE = 0x60013000 +DR_REG_UHCI0_BASE = 0x60014000 +DR_REG_SLCHOST_BASE = 0x60015000 +DR_REG_RMT_BASE = 0x60016000 +DR_REG_PCNT_BASE = 0x60017000 +DR_REG_SLC_BASE = 0x60018000 +DR_REG_LEDC_BASE = 0x60019000 +DR_REG_NRX_BASE = 0x6001CC00 +DR_REG_BB_BASE = 0x6001D000 +DR_REG_PWM0_BASE = 0x6001E000 +DR_REG_TIMERGROUP0_BASE = 0x6001F000 +DR_REG_TIMERGROUP1_BASE = 0x60020000 +DR_REG_RTC_SLOWMEM_BASE = 0x60021000 +DR_REG_SYSTIMER_BASE = 0x60023000 +DR_REG_SPI2_BASE = 0x60024000 +DR_REG_SPI3_BASE = 0x60025000 +DR_REG_SYSCON_BASE = 0x60026000 +DR_REG_APB_CTRL_BASE = 0x60026000 # Old name for SYSCON, to be removed +DR_REG_I2C1_EXT_BASE = 0x60027000 +DR_REG_SDMMC_BASE = 0x60028000 +DR_REG_PERI_BACKUP_BASE = 0x6002A000 +DR_REG_TWAI_BASE = 0x6002B000 +DR_REG_PWM1_BASE = 0x6002C000 +DR_REG_I2S1_BASE = 0x6002D000 +DR_REG_UART2_BASE = 0x6002E000 +DR_REG_USB_SERIAL_JTAG_BASE = 0x60038000 +DR_REG_USB_WRAP_BASE = 0x60039000 +DR_REG_AES_BASE = 0x6003A000 +DR_REG_SHA_BASE = 0x6003B000 +DR_REG_RSA_BASE = 0x6003C000 +DR_REG_HMAC_BASE = 0x6003E000 +DR_REG_DIGITAL_SIGNATURE_BASE = 0x6003D000 +DR_REG_GDMA_BASE = 0x6003F000 +DR_REG_APB_SARADC_BASE = 0x60040000 +DR_REG_LCD_CAM_BASE = 0x60041000 +DR_REG_SYSTEM_BASE = 0x600C0000 +DR_REG_SENSITIVE_BASE = 0x600C1000 +DR_REG_INTERRUPT_BASE = 0x600C2000 +DR_REG_EXTMEM_BASE = 0x600C4000 +DR_REG_ASSIST_DEBUG_BASE = 0x600CE000 +DR_REG_WCL_BASE = 0x600D0000 diff --git a/examples/blink.py b/examples/blink.py index 165f4e9..04e3b8f 100644 --- a/examples/blink.py +++ b/examples/blink.py @@ -1,4 +1,6 @@ """ +Example for: ESP32 + Simple example showing how to control a GPIO pin from the ULP coprocessor. The GPIO port is configured to be attached to the RTC module, and then set @@ -22,11 +24,11 @@ source = """\ # constants from: -# https://github.com/espressif/esp-idf/blob/1cb31e5/components/soc/esp32/include/soc/soc.h +# https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/reg_base.h #define DR_REG_RTCIO_BASE 0x3ff48400 # constants from: -# https://github.com/espressif/esp-idf/blob/1cb31e5/components/soc/esp32/include/soc/rtc_io_reg.h +# https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/rtc_io_reg.h #define RTC_IO_TOUCH_PAD2_REG (DR_REG_RTCIO_BASE + 0x9c) #define RTC_IO_TOUCH_PAD2_MUX_SEL_M (BIT(19)) #define RTC_GPIO_OUT_REG (DR_REG_RTCIO_BASE + 0x0) @@ -35,7 +37,7 @@ #define RTC_GPIO_OUT_DATA_S 14 # constants from: -# https://github.com/espressif/esp-idf/blob/1cb31e5/components/soc/esp32/include/soc/rtc_io_channel.h +# https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/rtc_io_channel.h #define RTCIO_GPIO2_CHANNEL 12 # When accessed from the RTC module (ULP) GPIOs need to be addressed by their channel number @@ -93,7 +95,7 @@ halt # go back to sleep until next wakeup period """ -binary = src_to_binary(source) +binary = src_to_binary(source, cpu="esp32") # cpu is esp32 or esp32s2 load_addr, entry_addr = 0, 8 diff --git a/examples/blink_s2.py b/examples/blink_s2.py new file mode 100644 index 0000000..86b0c3c --- /dev/null +++ b/examples/blink_s2.py @@ -0,0 +1,112 @@ +""" +Example for: ESP32-S2 and ESP32-S3 + +The GPIO port is configured to be attached to the RTC module, and then set +to OUTPUT mode. To avoid re-initializing the GPIO on every wakeup, a magic +token gets set in memory. + +After every change of state, the ULP is put back to sleep again until the +next wakeup. The ULP wakes up every 500ms to change the state of the GPIO +pin. An LED attached to the GPIO pin would toggle on and off every 500ms. + +The end of the python script has a loop to show the value of the magic token +and the current state, so you can confirm the magic token gets set and watch +the state value changing. If the loop is stopped (Ctrl-C), the LED attached +to the GPIO pin continues to blink, because the ULP runs independently from +the main processor. +""" + +from esp32 import ULP +from machine import mem32 +from esp32_ulp import src_to_binary + +source = """\ +# constants from: +# https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/include/soc/reg_base.h +#define DR_REG_RTCIO_BASE 0x3f408400 + +# constants from: +# https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/include/soc/rtc_io_reg.h +#define RTC_IO_TOUCH_PAD2_REG (DR_REG_RTCIO_BASE + 0x8c) +#define RTC_IO_TOUCH_PAD2_MUX_SEL_M (BIT(19)) +#define RTC_GPIO_OUT_REG (DR_REG_RTCIO_BASE + 0x0) +#define RTC_GPIO_ENABLE_REG (DR_REG_RTCIO_BASE + 0xc) +#define RTC_GPIO_ENABLE_S 10 +#define RTC_GPIO_OUT_DATA_S 10 + +# constants from: +# https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/include/soc/rtc_io_channel.h +#define RTCIO_GPIO2_CHANNEL 2 + +# When accessed from the RTC module (ULP) GPIOs need to be addressed by their channel number +.set gpio, RTCIO_GPIO2_CHANNEL +.set token, 0xcafe # magic token + +.text +magic: .long 0 +state: .long 0 + +.global entry +entry: + # load magic flag + move r0, magic + ld r1, r0, 0 + + # test if we have initialised already + sub r1, r1, token + jump after_init, eq # jump if magic == token (note: "eq" means the last instruction (sub) resulted in 0) + +init: + # connect GPIO to ULP (0: GPIO connected to digital GPIO module, 1: GPIO connected to analog RTC module) + WRITE_RTC_REG(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_MUX_SEL_M, 1, 1); + + # GPIO shall be output, not input (this also enables a pull-down by default) + WRITE_RTC_REG(RTC_GPIO_ENABLE_REG, RTC_GPIO_ENABLE_S + gpio, 1, 1) + + # store that we're done with initialisation + move r0, magic + move r1, token + st r1, r0, 0 + +after_init: + move r1, state + ld r0, r1, 0 + + move r2, 1 + sub r0, r2, r0 # toggle state + st r0, r1, 0 # store updated state + + jumpr on, 0, gt # if r0 (state) > 0, jump to 'on' + jump off # else jump to 'off' + +on: + # turn on led (set GPIO) + WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + gpio, 1, 1) + jump exit + +off: + # turn off led (clear GPIO) + WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + gpio, 1, 0) + jump exit + +exit: + halt # go back to sleep until next wakeup period +""" + +binary = src_to_binary(source, cpu="esp32s2") # cpu is esp32 or esp32s2 + +load_addr, entry_addr = 0, 8 + +ULP_MEM_BASE = 0x50000000 +ULP_DATA_MASK = 0xffff # ULP data is only in lower 16 bits + +ulp = ULP() +ulp.set_wakeup_period(0, 500000) # use timer0, wakeup after 500000usec (0.5s) +ulp.load_binary(load_addr, binary) + +ulp.run(entry_addr) + +while True: + print(hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK), # magic token + hex(mem32[ULP_MEM_BASE + load_addr + 4] & ULP_DATA_MASK) # current state + ) diff --git a/examples/counter.py b/examples/counter.py index 77fb146..e3a72b5 100644 --- a/examples/counter.py +++ b/examples/counter.py @@ -1,4 +1,6 @@ """ +Example for: ESP32 + Very basic example showing data exchange main CPU <--> ULP coprocessor. To show that the ULP is doing something, it just increments the value . @@ -25,7 +27,7 @@ halt # halt ULP co-prozessor (until it gets waked up again) """ -binary = src_to_binary(source) +binary = src_to_binary(source, cpu="esp32") # cpu is esp32 or esp32s2 load_addr, entry_addr = 0, 4 diff --git a/examples/counter_s2.py b/examples/counter_s2.py new file mode 100644 index 0000000..8119db7 --- /dev/null +++ b/examples/counter_s2.py @@ -0,0 +1,46 @@ +""" +Example for: ESP32-S2 and ESP32-S3 + +Very basic example showing data exchange main CPU <--> ULP coprocessor. + +To show that the ULP is doing something, it just increments the value . +It does that once per ulp timer wakeup (and then the ULP halts until it gets +waked up via the timer again). + +The timer is set to a rather long period, so you can watch the data value +incrementing (see loop at the end). +""" + +from esp32 import ULP +from machine import mem32 + +from esp32_ulp import src_to_binary + +source = """\ +data: .long 0 + +entry: move r3, data # load address of data into r3 + ld r2, r3, 0 # load data contents ([r3+0]) into r2 + add r2, r2, 1 # increment r2 + st r2, r3, 0 # store r2 contents into data ([r3+0]) + + halt # halt ULP co-prozessor (until it gets waked up again) +""" + +binary = src_to_binary(source, cpu="esp32s2") # cpu is esp32 or esp32s2 + +load_addr, entry_addr = 0, 4 + +ULP_MEM_BASE = 0x50000000 +ULP_DATA_MASK = 0xffff # ULP data is only in lower 16 bits + +ulp = ULP() +ulp.set_wakeup_period(0, 50000) # use timer0, wakeup after 50.000 cycles +ulp.load_binary(load_addr, binary) + +mem32[ULP_MEM_BASE + load_addr] = 0x1000 +ulp.run(entry_addr) + +while True: + print(hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK)) + diff --git a/examples/readgpio.py b/examples/readgpio.py index 66c9b05..8a8fca7 100644 --- a/examples/readgpio.py +++ b/examples/readgpio.py @@ -1,16 +1,18 @@ """ +Example for: ESP32 + Very basic example showing how to read a GPIO pin from the ULP and access that data from the main CPU. In this case GPIO4 is being read. Note that the ULP needs to refer to GPIOs via their RTC channel number. You can see the mapping in this file: -https://github.com/espressif/esp-idf/blob/v4.4.1/components/soc/esp32/include/soc/rtc_io_channel.h#L51 +https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/rtc_io_channel.h#L51 If you change to a different GPIO number, make sure to modify both the channel number and also the RTC_IO_TOUCH_PAD0_* references appropriately. The best place to see the mappings might be this table here (notice the "real GPIO numbers" as comments to each line): -https://github.com/espressif/esp-idf/blob/v4.4.1/components/soc/esp32/rtc_io_periph.c#L61 +https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/rtc_io_periph.c#L53 The timer is set to a rather long period, so you can watch the data value change as you change the GPIO input (see loop at the end). @@ -52,7 +54,7 @@ halt """ -binary = src_to_binary(source) +binary = src_to_binary(source, cpu="esp32") # cpu is esp32 or esp32s2 load_addr, entry_addr = 0, 4 diff --git a/examples/readgpio_s2.py b/examples/readgpio_s2.py new file mode 100644 index 0000000..60ae43e --- /dev/null +++ b/examples/readgpio_s2.py @@ -0,0 +1,79 @@ +""" +Example for: ESP32-S2 + +Very basic example showing how to read a GPIO pin from the ULP and access +that data from the main CPU. + +In this case GPIO4 is being read. Note that the ULP needs to refer to GPIOs +via their RTC channel number. You can see the mapping in this file: +https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/include/soc/rtc_io_channel.h#L33 + +If you change to a different GPIO number, make sure to modify both the channel +number and also the RTC_IO_TOUCH_PAD4_* references appropriately. The best place +to see the mappings might be this table here (notice the "real GPIO numbers" as +comments to each line): +https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/rtc_io_periph.c#L60 + +The timer is set to a rather long period, so you can watch the data value +change as you change the GPIO input (see loop at the end). +""" + +from esp32 import ULP +from machine import mem32 + +from esp32_ulp import src_to_binary + +source = """\ +#define DR_REG_RTCIO_BASE 0x3f408400 +#define RTC_IO_TOUCH_PAD4_REG (DR_REG_RTCIO_BASE + 0x94) +#define RTC_IO_TOUCH_PAD4_MUX_SEL_M (BIT(19)) +#define RTC_IO_TOUCH_PAD4_FUN_IE_M (BIT(13)) +#define RTC_GPIO_IN_REG (DR_REG_RTCIO_BASE + 0x24) +#define RTC_GPIO_IN_NEXT_S 10 +#define DR_REG_SENS_BASE 0x3f408800 +#define SENS_SAR_IO_MUX_CONF_REG (DR_REG_SENS_BASE + 0x0144) +#define SENS_IOMUX_CLK_GATE_EN (BIT(31)) +.set channel, 4 + +state: .long 0 + +entry: + # enable IOMUX clock + WRITE_RTC_FIELD(SENS_SAR_IO_MUX_CONF_REG, SENS_IOMUX_CLK_GATE_EN, 1) + + # connect GPIO to the RTC subsystem so the ULP can read it + WRITE_RTC_REG(RTC_IO_TOUCH_PAD4_REG, RTC_IO_TOUCH_PAD4_MUX_SEL_M, 1, 1) + + # switch the GPIO into input mode + WRITE_RTC_REG(RTC_IO_TOUCH_PAD4_REG, RTC_IO_TOUCH_PAD4_FUN_IE_M, 1, 1) + + # read the GPIO's current state into r0 + READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + channel, 1) + + # set r3 to the memory address of "state" + move r3, state + + # store what was read into r0 into the "state" variable + st r0, r3, 0 + + # halt ULP co-processor (until it gets woken up again) + halt +""" + +binary = src_to_binary(source, cpu="esp32s2") # cpu is esp32 or esp32s2 + +load_addr, entry_addr = 0, 4 + +ULP_MEM_BASE = 0x50000000 +ULP_DATA_MASK = 0xffff # ULP data is only in lower 16 bits + +ulp = ULP() +ulp.set_wakeup_period(0, 50000) # use timer0, wakeup after 50.000 cycles +ulp.load_binary(load_addr, binary) + +mem32[ULP_MEM_BASE + load_addr] = 0x0 # initialise state to 0 +ulp.run(entry_addr) + +while True: + print(hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK)) + diff --git a/examples/readgpio_s3.py b/examples/readgpio_s3.py new file mode 100644 index 0000000..b1f9779 --- /dev/null +++ b/examples/readgpio_s3.py @@ -0,0 +1,79 @@ +""" +Example for: ESP32-S3 + +Very basic example showing how to read a GPIO pin from the ULP and access +that data from the main CPU. + +In this case GPIO4 is being read. Note that the ULP needs to refer to GPIOs +via their RTC channel number. You can see the mapping in this file: +https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s3/include/soc/rtc_io_channel.h#L33 + +If you change to a different GPIO number, make sure to modify both the channel +number and also the RTC_IO_TOUCH_PAD2_* references appropriately. The best place +to see the mappings might be this table here (notice the "real GPIO numbers" as +comments to each line): +https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s3/rtc_io_periph.c#L60 + +The timer is set to a rather long period, so you can watch the data value +change as you change the GPIO input (see loop at the end). +""" + +from esp32 import ULP +from machine import mem32 + +from esp32_ulp import src_to_binary + +source = """\ +#define DR_REG_RTCIO_BASE 0x60008400 +#define RTC_IO_TOUCH_PAD2_REG (DR_REG_RTCIO_BASE + 0x8c) +#define RTC_IO_TOUCH_PAD2_MUX_SEL_M (BIT(19)) +#define RTC_IO_TOUCH_PAD2_FUN_IE_M (BIT(13)) +#define RTC_GPIO_IN_REG (DR_REG_RTCIO_BASE + 0x24) +#define RTC_GPIO_IN_NEXT_S 10 +#define DR_REG_SENS_BASE 0x60008800 +#define SENS_SAR_PERI_CLK_GATE_CONF_REG (DR_REG_SENS_BASE + 0x104) +#define SENS_IOMUX_CLK_EN (BIT(31)) +.set channel, 2 + +state: .long 0 + +entry: + # enable IOMUX clock + WRITE_RTC_FIELD(SENS_SAR_PERI_CLK_GATE_CONF_REG, SENS_IOMUX_CLK_EN, 1) + + # connect GPIO to the RTC subsystem so the ULP can read it + WRITE_RTC_REG(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_MUX_SEL_M, 1, 1) + + # switch the GPIO into input mode + WRITE_RTC_REG(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_FUN_IE_M, 1, 1) + + # read the GPIO's current state into r0 + READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + channel, 1) + + # set r3 to the memory address of "state" + move r3, state + + # store what was read into r0 into the "state" variable + st r0, r3, 0 + + # halt ULP co-processor (until it gets woken up again) + halt +""" + +binary = src_to_binary(source, cpu="esp32s2") # cpu is esp32 or esp32s2 + +load_addr, entry_addr = 0, 4 + +ULP_MEM_BASE = 0x50000000 +ULP_DATA_MASK = 0xffff # ULP data is only in lower 16 bits + +ulp = ULP() +ulp.set_wakeup_period(0, 50000) # use timer0, wakeup after 50.000 cycles +ulp.load_binary(load_addr, binary) + +mem32[ULP_MEM_BASE + load_addr] = 0x0 # initialise state to 0 +ulp.run(entry_addr) + +while True: + print(hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK)) + diff --git a/tests/00_unit_tests.sh b/tests/00_unit_tests.sh index 1dc05e9..262b759 100755 --- a/tests/00_unit_tests.sh +++ b/tests/00_unit_tests.sh @@ -4,7 +4,7 @@ set -e -LIST=${1:-opcodes assemble link util preprocess definesdb disassemble} +LIST=${1:-opcodes opcodes_s2 assemble link util preprocess definesdb decode decode_s2} for file in $LIST; do echo testing $file... diff --git a/tests/01_compat_tests.sh b/tests/01_compat_tests.sh index 4beb2b1..3729292 100755 --- a/tests/01_compat_tests.sh +++ b/tests/01_compat_tests.sh @@ -10,38 +10,68 @@ calc_file_hash() { shasum < $1 | cut -d' ' -f1 } -for src_file in $(ls -1 compat/*.S); do - src_name="${src_file%.S}" - - echo "Testing $src_file" - echo -e "\tBuilding using micropython-esp32-ulp" - ulp_file="${src_name}.ulp" - log_file="${src_name}.log" - micropython -m esp32_ulp $src_file 1>$log_file # generates $ulp_file - - pre_file="${src_name}.pre" - obj_file="${src_name}.o" - elf_file="${src_name}.elf" - bin_file="${src_name}.bin" - - echo -e "\tBuilding using binutils" - gcc -E -o ${pre_file} $src_file - esp32ulp-elf-as --mcpu=esp32 -o $obj_file ${pre_file} - esp32ulp-elf-ld -T esp32.ulp.ld -o $elf_file $obj_file - esp32ulp-elf-objcopy -O binary $elf_file $bin_file - - if ! diff $ulp_file $bin_file 1>/dev/null; then - echo -e "\tBuild outputs differ!" - echo "" - echo "Compatibility test failed for $src_file" - echo "micropython-esp32-ulp log:" - cat $log_file - echo "micropython-esp32-ulp output:" - xxd $ulp_file - echo "binutils output:" - xxd $bin_file - exit 1 - else - echo -e "\tBuild outputs match (sha1: $(calc_file_hash $ulp_file))" - fi -done +make_log_dir() { + mkdir -p log +} + +fetch_esp_idf() { + [ -d esp-idf ] && return + + echo "Fetching esp-idf" + log_file=log/fetch-esp-idf.log + git clone --depth 1 \ + https://github.com/espressif/esp-idf.git 1>$log_file 2>&1 +} + +run_tests_for_cpu() { + local cpu=$1 + echo "Testing for CPU: $cpu" + + for src_file in $(ls -1 compat/*.S fixtures/*.S); do + src_name="${src_file%.S}" + + # files with a cpu encoded into their name are only run for that cpu + if [[ $src_file =~ \.esp32\. && $cpu != esp32 ]] || [[ $src_file =~ \.esp32s2?\. && $cpu != esp32s2 ]]; then + continue + fi + echo "Testing $src_file" + echo -e "\tBuilding using micropython-esp32-ulp ($cpu)" + ulp_file="${src_name}.ulp" + log_file="${src_name}.log" + micropython -m esp32_ulp -c $cpu $src_file 1>$log_file # generates $ulp_file + + pre_file="${src_name}.pre" + obj_file="${src_name}.o" + elf_file="${src_name}.elf" + bin_file="${src_name}.bin" + + echo -e "\tBuilding using binutils ($cpu)" + gcc -I esp-idf/components/soc/$cpu/include -I esp-idf/components/esp_common/include \ + -x assembler-with-cpp \ + -E -o ${pre_file} $src_file + esp32ulp-elf-as --mcpu=$cpu -o $obj_file ${pre_file} + esp32ulp-elf-ld -T esp32.ulp.ld -o $elf_file $obj_file + esp32ulp-elf-objcopy -O binary $elf_file $bin_file + + if ! diff $ulp_file $bin_file 1>/dev/null; then + echo -e "\tBuild outputs differ!" + echo "" + echo "Compatibility test failed for $src_file" + echo "micropython-esp32-ulp log:" + cat $log_file + echo "micropython-esp32-ulp output:" + xxd $ulp_file + echo "binutils output:" + xxd $bin_file + exit 1 + else + echo -e "\tBuild outputs match (sha1: $(calc_file_hash $ulp_file))" + fi + done + echo "" +} + +make_log_dir +fetch_esp_idf +run_tests_for_cpu esp32 +run_tests_for_cpu esp32s2 diff --git a/tests/02_compat_rtc_tests.sh b/tests/02_compat_rtc_tests.sh index 1cfbe7c..467ccae 100755 --- a/tests/02_compat_rtc_tests.sh +++ b/tests/02_compat_rtc_tests.sh @@ -36,20 +36,29 @@ fetch_binutils_esp32ulp_examples() { https://github.com/espressif/binutils-gdb.git 1>$log_file 2>&1 } +REUSE_DEFINES_DB=0 + build_defines_db() { + local cpu=$1 local defines_db=defines.db + local defines_db_cpu=defines.$cpu.db - if [ "$1" = "-r" ] && [ -s "${defines_db}" ]; then + if [ "$REUSE_DEFINES_DB" = 1 ] && [ -s "${defines_db_cpu}" ]; then # reuse existing defines.db + echo "Reusing existing defines DB for cpu $cpu" + cp ${defines_db_cpu} ${defines_db} return fi - echo "Building defines DB from include files" - log_file=log/build_defines_db.log + echo "Building defines DB from $cpu include files" + log_file=log/build_defines_db.$cpu.log rm -f "${defines_db}" micropython -m esp32_ulp.parse_to_db \ - esp-idf/components/soc/esp32/include/soc/*.h \ + esp-idf/components/soc/$cpu/include/soc/*.h \ esp-idf/components/esp_common/include/*.h 1>$log_file + + # cache defines.db + cp ${defines_db} ${defines_db_cpu} } calc_file_hash() { @@ -62,9 +71,9 @@ patch_test() { local test_name=$1 local out_file="${test_name}.tmp" - if [ "${test_name}" = esp32ulp_jumpr ]; then + if [[ "${test_name}" =~ ^(esp32ulp_jumpr|esp32s2ulp_jumpr|esp32s2ulp_jump)$ ]]; then ( - cd binutils-gdb/gas/testsuite/gas/esp32ulp/esp32 + cd binutils-gdb/gas/testsuite/gas/esp32ulp/$cpu cp ${test_name}.s ${out_file} echo -e "\tPatching test to work around binutils-esp32ulp .global bug" cat >> ${out_file} <> ${out_file} + ) + return 0 fi return 1 # nothing was patched @@ -98,65 +115,96 @@ make_log_dir fetch_esp_idf fetch_ulptool_examples fetch_binutils_esp32ulp_examples -build_defines_db $1 -for src_file in ulptool/src/ulp_examples/*/*.s binutils-gdb/gas/testsuite/gas/esp32ulp/esp32/*.s; do +run_tests_for_cpu() { + local cpu=$1 + echo "Testing for CPU: $cpu" + build_defines_db $cpu + + LIST=$(echo binutils-gdb/gas/testsuite/gas/esp32ulp/$cpu/*.s) + if [ $cpu = esp32 ]; then + # append extra tests to test preprocessor + # examples have constants specific to ESP32 (original) + # so we only run these tests with cpu = esp32 + # these tests primarily test our preprocessor, which is + # cpu independent, so we do not need to run them + # per each cpu. + LIST=$(echo ulptool/src/ulp_examples/*/*.s $LIST) + fi + + for src_file in $LIST; do + + src_name="${src_file%.s}" + src_dir="${src_name%/*}" + + echo "Testing $src_file" - src_name="${src_file%.s}" - src_dir="${src_name%/*}" + test_name="${src_name##*/}" - echo "Testing $src_file" + # for now, skip files that contain unsupported things (macros) + for I in i2c i2c_dev stack i2c_wr test1 test_jumpr test_macro; do + if [ "${test_name}" = "$I" ]; then + echo -e "\tSkipping... not yet supported" + continue 2 + fi + done + + if [ "$cpu" = esp32s2 ]; then + if [ "${test_name}" = "hall_sensor" ]; then + echo -e "\tSkipping... not supported on $cpu" + continue 1 + fi + fi - test_name="${src_name##*/}" + # BEGIN: work around known issues with binutils-gdb (esp32ulp) + ulp_file="${src_name}.ulp" - # for now, skip files that contain unsupported things (macros) - for I in i2c i2c_dev stack i2c_wr test1 test_jumpr test_macro; do - if [ "${test_name}" = "$I" ]; then - echo -e "\tSkipping... not yet supported" - continue 2 + if patch_test ${test_name}; then + # switch to the patched file instead of original one + src_file="${src_dir}/${test_name}.tmp" + src_name="${src_file%.tmp}" + ulp_file="${src_name}.tmp.ulp" # when extension is not .s, micropython-esp32-ulp doesn't remove original extension + fi + # END: work around known issues with binutils-gdb (esp32ulp) + + echo -e "\tBuilding using micropython-esp32-ulp ($cpu)" + log_file="${src_name}.log" + micropython -m esp32_ulp -c $cpu $src_file 1>$log_file # generates $ulp_file + + pre_file="${src_name}.pre" + obj_file="${src_name}.o" + elf_file="${src_name}.elf" + bin_file="${src_name}.bin" + + echo -e "\tBuilding using binutils ($cpu)" + gcc -I esp-idf/components/soc/$cpu/include -I esp-idf/components/esp_common/include \ + -x assembler-with-cpp \ + -E -o ${pre_file} $src_file + esp32ulp-elf-as --mcpu=$cpu -o $obj_file ${pre_file} + esp32ulp-elf-ld -T esp32.ulp.ld -o $elf_file $obj_file + esp32ulp-elf-objcopy -O binary $elf_file $bin_file + + if ! diff $ulp_file $bin_file 1>/dev/null; then + echo -e "\tBuild outputs differ!" + echo "" + echo "Compatibility test failed for $src_file" + echo "micropython-esp32-ulp log:" + cat $log_file + echo "micropython-esp32-ulp output:" + xxd $ulp_file + echo "binutils output:" + xxd $bin_file + exit 1 + else + echo -e "\tBuild outputs match (sha1: $(calc_file_hash $ulp_file))" fi done + echo "" +} - # BEGIN: work around known issues with binutils-gdb (esp32ulp) - ulp_file="${src_name}.ulp" +if [ "$1" = -r ]; then + REUSE_DEFINES_DB=1 +fi - if patch_test ${test_name}; then - # switch to the patched file instead of original one - src_file="${src_dir}/${test_name}.tmp" - src_name="${src_file%.tmp}" - ulp_file="${src_name}.tmp.ulp" # when extension is not .s, micropython-esp32-ulp doesn't remove original extension - fi - # END: work around known issues with binutils-gdb (esp32ulp) - - echo -e "\tBuilding using micropython-esp32-ulp" - log_file="${src_name}.log" - micropython -m esp32_ulp $src_file 1>$log_file # generates $ulp_file - - pre_file="${src_name}.pre" - obj_file="${src_name}.o" - elf_file="${src_name}.elf" - bin_file="${src_name}.bin" - - echo -e "\tBuilding using binutils" - gcc -I esp-idf/components/soc/esp32/include -I esp-idf/components/esp_common/include \ - -x assembler-with-cpp \ - -E -o ${pre_file} $src_file - esp32ulp-elf-as --mcpu=esp32 -o $obj_file ${pre_file} - esp32ulp-elf-ld -T esp32.ulp.ld -o $elf_file $obj_file - esp32ulp-elf-objcopy -O binary $elf_file $bin_file - - if ! diff $ulp_file $bin_file 1>/dev/null; then - echo -e "\tBuild outputs differ!" - echo "" - echo "Compatibility test failed for $src_file" - echo "micropython-esp32-ulp log:" - cat $log_file - echo "micropython-esp32-ulp output:" - xxd $ulp_file - echo "binutils output:" - xxd $bin_file - exit 1 - else - echo -e "\tBuild outputs match (sha1: $(calc_file_hash $ulp_file))" - fi -done +run_tests_for_cpu esp32 +run_tests_for_cpu esp32s2 diff --git a/tests/03_disassembler_tests.sh b/tests/03_disassembler_tests.sh index 7c76f11..2ecc7a0 100755 --- a/tests/03_disassembler_tests.sh +++ b/tests/03_disassembler_tests.sh @@ -3,8 +3,9 @@ set -e test_disassembling_a_file() { + local cpu=$1 local verbose - if [ "$1" == verbose ]; then + if [ "$2" == verbose ]; then verbose=-v echo -e "Testing disassembling a file in VERBOSE mode" else @@ -12,17 +13,17 @@ test_disassembling_a_file() { fi testname=all_opcodes - fixture=fixtures/${testname}.S - echo -e "\tBuilding $fixture using micropython-esp32-ulp" + fixture=fixtures/${testname}.${cpu}.S + echo -e "\tBuilding $fixture using micropython-esp32-ulp ($cpu)" log_file="${testname}.log" - ulp_file="fixtures/${testname}.ulp" - micropython -m esp32_ulp $fixture 1>$log_file # generates $ulp_file + ulp_file="fixtures/${testname}.${cpu}.ulp" + micropython -m esp32_ulp -c $cpu $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 + lst_file="${testname}.$cpu.lst" + lst_file_fixture=fixtures/${testname}${verbose}.$cpu.lst + echo -e "\tDisassembling $ulp_file using micropython-esp32-ulp disassembler ($cpu)" + micropython -m tools.disassemble -c $cpu $verbose $ulp_file > $lst_file if ! diff $lst_file_fixture $lst_file 1>/dev/null; then echo -e "\tDisassembled output differs from expected output!" @@ -36,20 +37,25 @@ test_disassembling_a_file() { } test_disassembling_a_manual_sequence() { + local cpu=$1 local verbose - if [ "$1" == verbose ]; then + if [ "$2" == 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" + if [ "$cpu" == "esp32s2" ]; then + sequence="e1af 8c74 8101 0068 2705 cc19 0005 681d 0000 00a0 0000 0078" + else + sequence="e1af 8c72 0100 0068 2705 cc19 0005 681d 0000 00a0 0000 0074" + fi - 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 + lst_file="manual_bytes.$cpu.lst" + lst_file_fixture=fixtures/manual_bytes${verbose}.$cpu.lst + echo -e "\tDisassembling manual byte sequence using micropython-esp32-ulp disassembler ($cpu)" + micropython -m tools.disassemble -c $cpu $verbose -m $sequence> $lst_file if ! diff $lst_file_fixture $lst_file 1>/dev/null; then echo -e "\tDisassembled output differs from expected output!" @@ -60,8 +66,20 @@ test_disassembling_a_manual_sequence() { fi } -test_disassembling_a_file -test_disassembling_a_file verbose +# esp32 +echo "Testing for CPU: esp32" +test_disassembling_a_file esp32 +test_disassembling_a_file esp32 verbose + +test_disassembling_a_manual_sequence esp32 +test_disassembling_a_manual_sequence esp32 verbose + +echo "" + +# esp32s2 +echo "Testing for CPU: esp32s2" +test_disassembling_a_file esp32s2 +test_disassembling_a_file esp32s2 verbose -test_disassembling_a_manual_sequence -test_disassembling_a_manual_sequence verbose +test_disassembling_a_manual_sequence esp32s2 +test_disassembling_a_manual_sequence esp32s2 verbose diff --git a/tests/compat/loadstore.esp32s2.S b/tests/compat/loadstore.esp32s2.S new file mode 100644 index 0000000..d0c032f --- /dev/null +++ b/tests/compat/loadstore.esp32s2.S @@ -0,0 +1,31 @@ +.set offs, 0x20 +.set lab1, 0x01 + +.text +LDL R1, R2, 0x20 +LDL R1, R2, offs +LDH R1, R2, 0x20 +LDH R1, R2, offs + +STL R1, R2, 0x20 +STL R1, R2, offs +STL R1, R2, offs, 1 +STL R1, R2, offs, lab1 + +STH R1, R2, 0x20 +STH R1, R2, offs +STH R1, R2, offs, 1 +STH R1, R2, offs, lab1 + +ST32 R1, R2, 0x10, 1 +ST32 R1, R2, offs, lab1 + +STI32 R1, R2, 1 +STI32 R1, R2, lab1 + +STI R1, R2 +STI R1, R2, 1 +STI R1, R2, lab1 + +STO 0x20 +STO offs diff --git a/tests/compat/reg.esp32.S b/tests/compat/reg.esp32.S new file mode 100644 index 0000000..e9b1a14 --- /dev/null +++ b/tests/compat/reg.esp32.S @@ -0,0 +1,15 @@ +#include "soc/rtc_cntl_reg.h" +#include "soc/soc_ulp.h" + + reg_rd 0x012, 1, 2 + reg_rd 0x234, 3, 4 + reg_rd 0x345, 5, 6 + + reg_wr 0x012, 1, 2, 1 + reg_wr 0x234, 3, 4, 1 + reg_wr 0x345, 5, 6, 1 + + WRITE_RTC_REG(0x3ff484a8, 1, 2, 3) + READ_RTC_REG(0x3ff484a8, 1, 2) + WRITE_RTC_REG(0x3ff48904, 1, 2, 3) + READ_RTC_REG(0x3ff48904, 1, 2) diff --git a/tests/compat/reg.esp32s2.S b/tests/compat/reg.esp32s2.S new file mode 100644 index 0000000..c8c9920 --- /dev/null +++ b/tests/compat/reg.esp32s2.S @@ -0,0 +1,15 @@ +#include "soc/rtc_cntl_reg.h" +#include "soc/soc_ulp.h" + + reg_rd 0x012, 1, 2 + reg_rd 0x234, 3, 4 + reg_rd 0x345, 5, 6 + + reg_wr 0x012, 1, 2, 1 + reg_wr 0x234, 3, 4, 1 + reg_wr 0x345, 5, 6, 1 + + WRITE_RTC_REG(0x3f4084a8, 1, 2, 3) + READ_RTC_REG(0x3f4084a8, 1, 2) + WRITE_RTC_REG(0x3f408904, 1, 2, 3) + READ_RTC_REG(0x3f408904, 1, 2) diff --git a/tests/disassemble.py b/tests/decode.py similarity index 99% rename from tests/disassemble.py rename to tests/decode.py index 7e7f6df..916bb95 100644 --- a/tests/disassemble.py +++ b/tests/decode.py @@ -1,4 +1,4 @@ -from tools.disassemble import decode_instruction, get_instruction_fields +from tools.decode import decode_instruction, get_instruction_fields import esp32_ulp.opcodes as opcodes import ubinascii diff --git a/tests/decode_s2.py b/tests/decode_s2.py new file mode 100644 index 0000000..5fc2da6 --- /dev/null +++ b/tests/decode_s2.py @@ -0,0 +1,171 @@ +from tools.decode_s2 import decode_instruction, get_instruction_fields +import esp32_ulp.opcodes_s2 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 name == expected_name, '%s != %s' % (name, expected_name) + assert ins is expected_struct, 'incorrect instruction struct (%s, %s)' % (sequence, 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, SUB_OPCODE_ST + assert_decode("80010068", opcodes._st, 'ST r0, r0, 0') + assert_decode("c0010068", opcodes._st, 'STH r0, r0, 0') + assert_decode("90000068", opcodes._st, 'STL r0, r0, 0, 1') + assert_decode("d0000068", opcodes._st, 'STH r0, r0, 0, 1') + assert_decode("00000068", opcodes._st, 'ST32 r0, r0, 0, 0') + assert_decode("10000068", opcodes._st, 'ST32 r0, r0, 0, 1') + + # OPCODE_ST = 6, SUB_OPCODE_ST_AUTO + assert_decode("80010062", opcodes._st, 'STI r0, r0') + assert_decode("90000062", opcodes._st, 'STI r0, r0, 1') + assert_decode("00000062", opcodes._st, 'STI32 r0, r0, 0') + assert_decode("10000062", opcodes._st, 'STI32 r0, r0, 1') + + # OPCODE_ST = 6, SUB_OPCODE_ST_OFFSET + assert_decode("00000064", opcodes._st, 'STO 0') + assert_decode("00040064", opcodes._st, 'STO 1') + + # 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("00000074", opcodes._alu_imm, 'ADD r0, r0, 0') + assert_decode("00002074", opcodes._alu_imm, 'SUB r0, r0, 0') + assert_decode("00004074", opcodes._alu_imm, 'AND r0, r0, 0') + assert_decode("00006074", opcodes._alu_imm, 'OR r0, r0, 0') + assert_decode("00008074", opcodes._alu_imm, "MOVE r0, 0") + assert_decode("0000a074", opcodes._alu_imm, 'LSH r0, r0, 0') + assert_decode("0000c074", opcodes._alu_imm, 'RSH r0, r0, 0') + + # OPCODE_ALU = 7, SUB_OPCODE_ALU_CNT + assert_decode("00004078", opcodes._alu_cnt, 'STAGE_RST') + assert_decode("00000078", opcodes._alu_cnt, 'STAGE_INC 0') + assert_decode("00002078", opcodes._alu_cnt, 'STAGE_DEC 0') + + # OPCODE_BRANCH = 8, SUB_OPCODE_BX (IMM) + assert_decode("00000084", opcodes._bx, 'JUMP 0') + assert_decode("00004084", opcodes._bx, 'JUMP 0, EQ') + assert_decode("00008084", opcodes._bx, 'JUMP 0, OV') + + # OPCODE_BRANCH = 8, SUB_OPCODE_BX (REG) + assert_decode("00002084", opcodes._bx, 'JUMP r0') + assert_decode("00006084", opcodes._bx, 'JUMP r0, EQ') + assert_decode("0000a084", opcodes._bx, 'JUMP r0, OV') + + # OPCODE_BRANCH = 8, SUB_OPCODE_BR + assert_decode("00000080", opcodes._b, 'JUMPR 0, 0, LT') + assert_decode("00000180", opcodes._b, 'JUMPR 0, 0, GT') + assert_decode("00000280", opcodes._b, 'JUMPR 0, 0, EQ') + + # OPCODE_BRANCH = 8, SUB_OPCODE_BX + assert_decode("00800088", opcodes._bs, 'JUMPS 0, 0, LT') + assert_decode("00800188", opcodes._bs, 'JUMPS 0, 0, GT') + assert_decode("00000288", opcodes._bs, 'JUMPS 0, 0, EQ') + assert_decode("00800288", opcodes._bs, 'JUMPS 0, 0, LE') + assert_decode("00800388", opcodes._bs, 'JUMPS 0, 0, GE') + + # OPCODE_END = 9, SUB_OPCODE_END + assert_decode("01000090", opcodes._end, 'WAKE') + + # OPCODE_END = 9, SUB_OPCODE_SLEEP + ###assert_decode("01000040", opcodes._end, 'SLEEP 1') ##TODO + + # 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') + + +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.esp32.lst similarity index 84% rename from tests/fixtures/all_opcodes-v.lst rename to tests/fixtures/all_opcodes-v.esp32.lst index 4bc7975..1e2ebf7 100644 --- a/tests/fixtures/all_opcodes-v.lst +++ b/tests/fixtures/all_opcodes-v.esp32.lst @@ -1,8 +1,8 @@ header ULP magic : b'ulp\x00' (0x00706c75) .text offset : 12 (0x0c) -.text size : 164 (0xa4) -.data offset : 176 (0xb0) +.text size : 188 (0xbc) +.data offset : 200 (0xc8) .data size : 8 (0x08) .bss size : 0 (0x00) ---------------------------------------- @@ -251,14 +251,28 @@ ULP magic : b'ulp\x00' (0x00706c75) opcode = 8 sign = 0 sub_opcode = 1 -0080 05000382 JUMPR 1, 5, GE +0080 06000382 JUMPR 1, 6, GE cmp = 1 (GE) - imm = 5 + imm = 6 offset = 1 opcode = 8 sign = 0 sub_opcode = 1 -0084 01000084 JUMPS 0, 1, LT +0084 08000582 JUMPR 2, 8, GE + cmp = 1 (GE) + imm = 8 + offset = 2 + opcode = 8 + sign = 0 + sub_opcode = 1 +0088 07000582 JUMPR 2, 7, GE + cmp = 1 (GE) + imm = 7 + offset = 2 + opcode = 8 + sign = 0 + sub_opcode = 1 +008c 01000084 JUMPS 0, 1, LT cmp = 0 (LT) imm = 1 offset = 0 @@ -266,7 +280,15 @@ ULP magic : b'ulp\x00' (0x00706c75) sign = 0 sub_opcode = 2 unused = 0 -0088 05800284 JUMPS 1, 5, GE +0090 05000584 JUMPS 2, 5, LE + cmp = 2 (LE) + imm = 5 + offset = 2 + opcode = 8 + sign = 0 + sub_opcode = 2 + unused = 0 +0094 05800284 JUMPS 1, 5, GE cmp = 1 (GE) imm = 5 offset = 1 @@ -274,33 +296,57 @@ ULP magic : b'ulp\x00' (0x00706c75) sign = 0 sub_opcode = 2 unused = 0 -008c 09000584 JUMPS 2, 9, LE +0098 07000484 JUMPS 2, 7, LT + cmp = 0 (LT) + imm = 7 + offset = 2 + opcode = 8 + sign = 0 + sub_opcode = 2 + unused = 0 +009c 07000584 JUMPS 2, 7, LE cmp = 2 (LE) - imm = 9 + imm = 7 offset = 2 opcode = 8 sign = 0 sub_opcode = 2 unused = 0 -0090 01000090 WAKE +00a0 09000784 JUMPS 3, 9, LE + cmp = 2 (LE) + imm = 9 + offset = 3 + opcode = 8 + sign = 0 + sub_opcode = 2 + unused = 0 +00a4 0b800884 JUMPS 4, 11, GE + cmp = 1 (GE) + imm = 11 (0x0b) + offset = 4 + opcode = 8 + sign = 0 + sub_opcode = 2 + unused = 0 +00a8 01000090 WAKE opcode = 9 sub_opcode = 0 unused = 0 wakeup = 1 -0094 07000092 SLEEP 7 +00ac 07000092 SLEEP 7 cycle_sel = 7 opcode = 9 sub_opcode = 1 unused = 0 -0098 090000a0 TSENS r1, 2 +00b0 090000a0 TSENS r1, 2 delay = 2 dreg = 1 opcode = 10 (0x0a) unused = 0 -009c 000000b0 HALT +00b4 000000b0 HALT opcode = 11 (0x0b) unused = 0 -00a0 060000d0 LD r2, r1, 0 +00b8 060000d0 LD r2, r1, 0 dreg = 2 offset = 0 opcode = 13 (0x0d) @@ -309,5 +355,5 @@ ULP magic : b'ulp\x00' (0x00706c75) unused2 = 0 ---------------------------------------- .data -00a4 00000000 -00a8 fecadec0 +00bc 00000000 +00c0 fecadec0 diff --git a/tests/fixtures/all_opcodes-v.esp32s2.lst b/tests/fixtures/all_opcodes-v.esp32s2.lst new file mode 100644 index 0000000..a2ebb15 --- /dev/null +++ b/tests/fixtures/all_opcodes-v.esp32s2.lst @@ -0,0 +1,571 @@ +header +ULP magic : b'ulp\x00' (0x00706c75) +.text offset : 12 (0x0c) +.text size : 260 (0x104) +.data offset : 272 (0x110) +.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 8b010068 ST r3, r2, 0 + dreg = 2 + offset = 0 + opcode = 6 + sreg = 3 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 + label = 0 + upper = 0 + wr_way = 3 +0020 06000070 ADD r2, r1, r0 + dreg = 2 + opcode = 7 + sel = 0 (ADD) + sreg = 1 + sub_opcode = 0 + treg = 0 + unused1 = 0 + unused2 = 0 +0024 06002070 SUB r2, r1, r0 + dreg = 2 + opcode = 7 + sel = 1 (SUB) + sreg = 1 + sub_opcode = 0 + treg = 0 + unused1 = 0 + unused2 = 0 +0028 06004070 AND r2, r1, r0 + dreg = 2 + opcode = 7 + sel = 2 (AND) + sreg = 1 + sub_opcode = 0 + treg = 0 + unused1 = 0 + unused2 = 0 +002c 06006070 OR r2, r1, r0 + dreg = 2 + opcode = 7 + sel = 3 (OR) + sreg = 1 + sub_opcode = 0 + treg = 0 + unused1 = 0 + unused2 = 0 +0030 16008070 MOVE r2, r1 + dreg = 2 + opcode = 7 + sel = 4 (MOVE) + sreg = 1 + sub_opcode = 0 + treg = 1 + unused1 = 0 + unused2 = 0 +0034 0600a070 LSH r2, r1, r0 + dreg = 2 + opcode = 7 + sel = 5 (LSH) + sreg = 1 + sub_opcode = 0 + treg = 0 + unused1 = 0 + unused2 = 0 +0038 0600c070 RSH r2, r1, r0 + dreg = 2 + opcode = 7 + sel = 6 (RSH) + sreg = 1 + sub_opcode = 0 + treg = 0 + unused1 = 0 + unused2 = 0 +003c 06000074 ADD r2, r1, 0 + dreg = 2 + imm = 0 + opcode = 7 + sel = 0 (ADD) + sreg = 1 + sub_opcode = 1 + unused1 = 0 + unused2 = 0 +0040 06002074 SUB r2, r1, 0 + dreg = 2 + imm = 0 + opcode = 7 + sel = 1 (SUB) + sreg = 1 + sub_opcode = 1 + unused1 = 0 + unused2 = 0 +0044 06004074 AND r2, r1, 0 + dreg = 2 + imm = 0 + opcode = 7 + sel = 2 (AND) + sreg = 1 + sub_opcode = 1 + unused1 = 0 + unused2 = 0 +0048 06006074 OR r2, r1, 0 + dreg = 2 + imm = 0 + opcode = 7 + sel = 3 (OR) + sreg = 1 + sub_opcode = 1 + unused1 = 0 + unused2 = 0 +004c 01008074 MOVE r1, 0 + dreg = 1 + imm = 0 + opcode = 7 + sel = 4 (MOVE) + sreg = 0 + sub_opcode = 1 + unused1 = 0 + unused2 = 0 +0050 0600a074 LSH r2, r1, 0 + dreg = 2 + imm = 0 + opcode = 7 + sel = 5 (LSH) + sreg = 1 + sub_opcode = 1 + unused1 = 0 + unused2 = 0 +0054 0600c074 RSH r2, r1, 0 + dreg = 2 + imm = 0 + opcode = 7 + sel = 6 (RSH) + sreg = 1 + sub_opcode = 1 + unused1 = 0 + unused2 = 0 +0058 00004078 STAGE_RST + imm = 0 + opcode = 7 + sel = 2 (STAGE_RST) + sub_opcode = 2 + unused1 = 0 + unused2 = 0 +005c 70000078 STAGE_INC 7 + imm = 7 + opcode = 7 + sel = 0 (STAGE_INC) + sub_opcode = 2 + unused1 = 0 + unused2 = 0 +0060 30002078 STAGE_DEC 3 + imm = 3 + opcode = 7 + sel = 1 (STAGE_DEC) + sub_opcode = 2 + unused1 = 0 + unused2 = 0 +0064 00002084 JUMP r0 + addr = 0 + dreg = 0 + opcode = 8 + reg = 1 + sub_opcode = 1 + type = 0 (--) + unused1 = 0 + unused2 = 0 +0068 01006084 JUMP r1, EQ + addr = 0 + dreg = 1 + opcode = 8 + reg = 1 + sub_opcode = 1 + type = 1 (EQ) + unused1 = 0 + unused2 = 0 +006c 0200a084 JUMP r2, OV + addr = 0 + dreg = 2 + opcode = 8 + reg = 1 + sub_opcode = 1 + type = 2 (OV) + unused1 = 0 + unused2 = 0 +0070 00000084 JUMP 0 + addr = 0 + dreg = 0 + opcode = 8 + reg = 0 + sub_opcode = 1 + type = 0 (--) + unused1 = 0 + unused2 = 0 +0074 00004084 JUMP 0, EQ + addr = 0 + dreg = 0 + opcode = 8 + reg = 0 + sub_opcode = 1 + type = 1 (EQ) + unused1 = 0 + unused2 = 0 +0078 00008084 JUMP 0, OV + addr = 0 + dreg = 0 + opcode = 8 + reg = 0 + sub_opcode = 1 + type = 2 (OV) + unused1 = 0 + unused2 = 0 +007c 01000080 JUMPR 0, 1, LT + cmp = 0 (LT) + imm = 1 + offset = 0 + opcode = 8 + sign = 0 + sub_opcode = 0 +0080 05000580 JUMPR 1, 5, GT + cmp = 1 (GT) + imm = 5 + offset = 1 + opcode = 8 + sign = 0 + sub_opcode = 0 +0084 07000a80 JUMPR 2, 7, EQ + cmp = 2 (EQ) + imm = 7 + offset = 2 + opcode = 8 + sign = 0 + sub_opcode = 0 +0088 01800088 JUMPS 0, 1, LT + cmp = 1 (LT) + imm = 1 + offset = 0 + opcode = 8 + sign = 0 + sub_opcode = 2 + unused = 0 +008c 05801188 JUMPS 4, 5, GT + cmp = 3 (GT) + imm = 5 + offset = 4 + opcode = 8 + sign = 0 + sub_opcode = 2 + unused = 0 +0090 07002288 JUMPS 8, 7, EQ + cmp = 4 (EQ) + imm = 7 + offset = 8 + opcode = 8 + sign = 0 + sub_opcode = 2 + unused = 0 +0094 09803288 JUMPS 12, 9, LE + cmp = 5 (LE) + imm = 9 + offset = 12 (0x0c) + opcode = 8 + sign = 0 + sub_opcode = 2 + unused = 0 +0098 0b804388 JUMPS 16, 11, GE + cmp = 7 (GE) + imm = 11 (0x0b) + offset = 16 (0x10) + opcode = 8 + sign = 0 + sub_opcode = 2 + unused = 0 +009c 01000090 WAKE + opcode = 9 + sub_opcode = 0 + unused = 0 + wakeup = 1 +00a0 07000040 WAIT 7 + cycles = 7 + opcode = 4 + unused = 0 +00a4 090000a0 TSENS r1, 2 + delay = 2 + dreg = 1 + opcode = 10 (0x0a) + unused = 0 +00a8 000000b0 HALT + opcode = 11 (0x0b) + unused = 0 +00ac 060000d0 LD r2, r1, 0 + dreg = 2 + offset = 0 + opcode = 13 (0x0d) + sreg = 1 + unused1 = 0 + unused2 = 0 + rd_upper = 0 +00b0 00000040 NOP + cycles = 0 + opcode = 4 + unused = 0 +00b4 092000d0 LD r1, r2, 8 + dreg = 1 + offset = 8 + opcode = 13 (0x0d) + sreg = 2 + unused1 = 0 + unused2 = 0 + rd_upper = 0 +00b8 092000d8 LDH r1, r2, 8 + dreg = 1 + offset = 8 + opcode = 13 (0x0d) + sreg = 2 + unused1 = 0 + unused2 = 0 + rd_upper = 1 +00bc 89210068 ST r1, r2, 8 + dreg = 2 + offset = 8 + opcode = 6 + sreg = 1 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 + label = 0 + upper = 0 + wr_way = 3 +00c0 89200068 ST r1, r2, 8 + dreg = 2 + offset = 8 + opcode = 6 + sreg = 1 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 + label = 0 + upper = 0 + wr_way = 1 +00c4 99200068 STL r1, r2, 8, 1 + dreg = 2 + offset = 8 + opcode = 6 + sreg = 1 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 + label = 1 + upper = 0 + wr_way = 1 +00c8 c9210068 STH r1, r2, 8 + dreg = 2 + offset = 8 + opcode = 6 + sreg = 1 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 + label = 0 + upper = 1 + wr_way = 3 +00cc c9200068 STH r1, r2, 8 + dreg = 2 + offset = 8 + opcode = 6 + sreg = 1 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 + label = 0 + upper = 1 + wr_way = 1 +00d0 d9200068 STH r1, r2, 8, 1 + dreg = 2 + offset = 8 + opcode = 6 + sreg = 1 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 + label = 1 + upper = 1 + wr_way = 1 +00d4 09200068 ST32 r1, r2, 8, 0 + dreg = 2 + offset = 8 + opcode = 6 + sreg = 1 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 + label = 0 + upper = 0 + wr_way = 0 +00d8 19200068 ST32 r1, r2, 8, 1 + dreg = 2 + offset = 8 + opcode = 6 + sreg = 1 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 + label = 1 + upper = 0 + wr_way = 0 +00dc 89010062 STI r1, r2 + dreg = 2 + offset = 0 + opcode = 6 + sreg = 1 + sub_opcode = 1 + unused1 = 0 + unused2 = 0 + label = 0 + upper = 0 + wr_way = 3 +00e0 89000062 STI r1, r2 + dreg = 2 + offset = 0 + opcode = 6 + sreg = 1 + sub_opcode = 1 + unused1 = 0 + unused2 = 0 + label = 0 + upper = 0 + wr_way = 1 +00e4 99000062 STI r1, r2, 1 + dreg = 2 + offset = 0 + opcode = 6 + sreg = 1 + sub_opcode = 1 + unused1 = 0 + unused2 = 0 + label = 1 + upper = 0 + wr_way = 1 +00e8 09000062 STI32 r1, r2, 0 + dreg = 2 + offset = 0 + opcode = 6 + sreg = 1 + sub_opcode = 1 + unused1 = 0 + unused2 = 0 + label = 0 + upper = 0 + wr_way = 0 +00ec 19000062 STI32 r1, r2, 1 + dreg = 2 + offset = 0 + opcode = 6 + sreg = 1 + sub_opcode = 1 + unused1 = 0 + unused2 = 0 + label = 1 + upper = 0 + wr_way = 0 +00f0 00200064 STO 8 + dreg = 0 + offset = 8 + opcode = 6 + sreg = 0 + sub_opcode = 2 + unused1 = 0 + unused2 = 0 + label = 0 + upper = 0 + wr_way = 0 +00f4 09e01fd0 LD r1, r2, -8 + dreg = 1 + offset = -8 (0x7f8) + opcode = 13 (0x0d) + sreg = 2 + unused1 = 0 + unused2 = 0 + rd_upper = 0 +00f8 09e01fd8 LDH r1, r2, -8 + dreg = 1 + offset = -8 (0x7f8) + opcode = 13 (0x0d) + sreg = 2 + unused1 = 0 + unused2 = 0 + rd_upper = 1 +00fc 89e11f68 ST r1, r2, -8 + dreg = 2 + offset = -8 (0x7f8) + opcode = 6 + sreg = 1 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 + label = 0 + upper = 0 + wr_way = 3 +0100 c9e11f68 STH r1, r2, -8 + dreg = 2 + offset = -8 (0x7f8) + opcode = 6 + sreg = 1 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 + label = 0 + upper = 1 + wr_way = 3 +---------------------------------------- +.data +0104 00000000 +0108 fecadec0 diff --git a/tests/fixtures/all_opcodes.S b/tests/fixtures/all_opcodes.esp32.S similarity index 85% rename from tests/fixtures/all_opcodes.S rename to tests/fixtures/all_opcodes.esp32.S index 7f8c916..1710bc9 100644 --- a/tests/fixtures/all_opcodes.S +++ b/tests/fixtures/all_opcodes.esp32.S @@ -46,11 +46,14 @@ JUMP 0, EQ JUMP 0, OV JUMPR 0, 1, LT -JUMPR 4, 5, GE +JUMPR 4, 5, GT +JUMPR 8, 7, EQ JUMPS 0, 1, LT -JUMPS 4, 5, GE -JUMPS 8, 9, LE +JUMPS 4, 5, GT +JUMPS 8, 7, EQ +JUMPS 12, 9, LE +JUMPS 16, 11, GE WAKE SLEEP 7 diff --git a/tests/fixtures/all_opcodes.lst b/tests/fixtures/all_opcodes.esp32.lst similarity index 65% rename from tests/fixtures/all_opcodes.lst rename to tests/fixtures/all_opcodes.esp32.lst index 2ef1bd7..b882a3e 100644 --- a/tests/fixtures/all_opcodes.lst +++ b/tests/fixtures/all_opcodes.esp32.lst @@ -31,15 +31,21 @@ 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 +0080 06000382 JUMPR 1, 6, GE +0084 08000582 JUMPR 2, 8, GE +0088 07000582 JUMPR 2, 7, GE +008c 01000084 JUMPS 0, 1, LT +0090 05000584 JUMPS 2, 5, LE +0094 05800284 JUMPS 1, 5, GE +0098 07000484 JUMPS 2, 7, LT +009c 07000584 JUMPS 2, 7, LE +00a0 09000784 JUMPS 3, 9, LE +00a4 0b800884 JUMPS 4, 11, GE +00a8 01000090 WAKE +00ac 07000092 SLEEP 7 +00b0 090000a0 TSENS r1, 2 +00b4 000000b0 HALT +00b8 060000d0 LD r2, r1, 0 .data -00a4 00000000 -00a8 fecadec0 +00bc 00000000 +00c0 fecadec0 diff --git a/tests/fixtures/all_opcodes.esp32s2.S b/tests/fixtures/all_opcodes.esp32s2.S new file mode 100644 index 0000000..7483840 --- /dev/null +++ b/tests/fixtures/all_opcodes.esp32s2.S @@ -0,0 +1,97 @@ +.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, GT +JUMPR 8, 7, EQ + +JUMPS 0, 1, LT +JUMPS 4, 5, GT +JUMPS 8, 7, EQ +JUMPS 12, 9, LE +JUMPS 16, 11, GE + +WAKE +SLEEP 7 + +TSENS r1, 2 + +HALT + +LD r2, r1, 0 + +# ESP32-S2 specific instructions +NOP # marker + +LDL R1, R2, 0x20 +LDH R1, R2, 0x20 + +STL R1, R2, 0x20 +STL R1, R2, 0x20, 0 +STL R1, R2, 0x20, 1 + +STH R1, R2, 0x20 +STH R1, R2, 0x20, 0 +STH R1, R2, 0x20, 1 + +ST32 R1, R2, 0x20, 0 +ST32 R1, R2, 0x20, 1 + +STI R1, R2 +STI R1, R2, 0 +STI R1, R2, 1 + +STI32 R1, R2, 0 +STI32 R1, R2, 1 + +STO 0x20 + +LDL R1, R2, -0x20 +LDH R1, R2, -0x20 + +STL R1, R2, -0x20 +STH R1, R2, -0x20 diff --git a/tests/fixtures/all_opcodes.esp32s2.lst b/tests/fixtures/all_opcodes.esp32s2.lst new file mode 100644 index 0000000..5de1704 --- /dev/null +++ b/tests/fixtures/all_opcodes.esp32s2.lst @@ -0,0 +1,69 @@ +.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 8b010068 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 06000074 ADD r2, r1, 0 +0040 06002074 SUB r2, r1, 0 +0044 06004074 AND r2, r1, 0 +0048 06006074 OR r2, r1, 0 +004c 01008074 MOVE r1, 0 +0050 0600a074 LSH r2, r1, 0 +0054 0600c074 RSH r2, r1, 0 +0058 00004078 STAGE_RST +005c 70000078 STAGE_INC 7 +0060 30002078 STAGE_DEC 3 +0064 00002084 JUMP r0 +0068 01006084 JUMP r1, EQ +006c 0200a084 JUMP r2, OV +0070 00000084 JUMP 0 +0074 00004084 JUMP 0, EQ +0078 00008084 JUMP 0, OV +007c 01000080 JUMPR 0, 1, LT +0080 05000580 JUMPR 1, 5, GT +0084 07000a80 JUMPR 2, 7, EQ +0088 01800088 JUMPS 0, 1, LT +008c 05801188 JUMPS 4, 5, GT +0090 07002288 JUMPS 8, 7, EQ +0094 09803288 JUMPS 12, 9, LE +0098 0b804388 JUMPS 16, 11, GE +009c 01000090 WAKE +00a0 07000040 WAIT 7 +00a4 090000a0 TSENS r1, 2 +00a8 000000b0 HALT +00ac 060000d0 LD r2, r1, 0 +00b0 00000040 NOP +00b4 092000d0 LD r1, r2, 8 +00b8 092000d8 LDH r1, r2, 8 +00bc 89210068 ST r1, r2, 8 +00c0 89200068 ST r1, r2, 8 +00c4 99200068 STL r1, r2, 8, 1 +00c8 c9210068 STH r1, r2, 8 +00cc c9200068 STH r1, r2, 8 +00d0 d9200068 STH r1, r2, 8, 1 +00d4 09200068 ST32 r1, r2, 8, 0 +00d8 19200068 ST32 r1, r2, 8, 1 +00dc 89010062 STI r1, r2 +00e0 89000062 STI r1, r2 +00e4 99000062 STI r1, r2, 1 +00e8 09000062 STI32 r1, r2, 0 +00ec 19000062 STI32 r1, r2, 1 +00f0 00200064 STO 8 +00f4 09e01fd0 LD r1, r2, -8 +00f8 09e01fd8 LDH r1, r2, -8 +00fc 89e11f68 ST r1, r2, -8 +0100 c9e11f68 STH r1, r2, -8 +.data +0104 00000000 +0108 fecadec0 diff --git a/tests/fixtures/manual_bytes-v.lst b/tests/fixtures/manual_bytes-v.esp32.lst similarity index 100% rename from tests/fixtures/manual_bytes-v.lst rename to tests/fixtures/manual_bytes-v.esp32.lst diff --git a/tests/fixtures/manual_bytes-v.esp32s2.lst b/tests/fixtures/manual_bytes-v.esp32s2.lst new file mode 100644 index 0000000..7d91dda --- /dev/null +++ b/tests/fixtures/manual_bytes-v.esp32s2.lst @@ -0,0 +1,46 @@ +0000 e1af8c74 MOVE r1, 51966 + dreg = 1 + imm = 51966 (0xcafe) + opcode = 7 + sel = 4 (MOVE) + sreg = 0 + sub_opcode = 1 + unused1 = 0 + unused2 = 0 +0004 81010068 ST r1, r0, 0 + dreg = 0 + offset = 0 + opcode = 6 + sreg = 1 + sub_opcode = 4 + unused1 = 0 + unused2 = 0 + label = 0 + upper = 0 + wr_way = 3 +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 00000078 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.esp32.lst similarity index 100% rename from tests/fixtures/manual_bytes.lst rename to tests/fixtures/manual_bytes.esp32.lst diff --git a/tests/fixtures/manual_bytes.esp32s2.lst b/tests/fixtures/manual_bytes.esp32s2.lst new file mode 100644 index 0000000..52799af --- /dev/null +++ b/tests/fixtures/manual_bytes.esp32s2.lst @@ -0,0 +1,6 @@ +0000 e1af8c74 MOVE r1, 51966 +0004 81010068 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 00000078 STAGE_INC 0 diff --git a/tests/opcodes.py b/tests/opcodes.py index 85cd710..576c840 100644 --- a/tests/opcodes.py +++ b/tests/opcodes.py @@ -174,6 +174,31 @@ def test_reg_address_translations(): assert ins.addr == 0x2a # low 8 bits of 0x12a +def test_reg_address_translations_sens(): + """ + Test addressing of peripheral registers using full DPORT bus addresses + """ + + ins = make_ins(""" + addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC + periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) + unused : 8 # Unused + low : 5 # Low bit + high : 5 # High bit + opcode : 4 # Opcode (OPCODE_RD_REG) + """) + + # direct ULP address is derived from full address as follows: + # full:0x3ff48904 == ulp:(0x3ff48904-DR_REG_RTCCNTL_BASE) / 4 + # full:0x3ff48904 == ulp:(0x3ff48904-0x3f408000) / 4 + # full:0x3ff48904 == ulp:0x904 / 4 + # full:0x3ff48904 == ulp:0x241 + # see: https://github.com/espressif/binutils-esp32ulp/blob/249ec34/gas/config/tc-esp32ulp_esp32s2.c#L78 + ins.all = opcodes.i_reg_rd("0x3ff48904", "0", "0") + assert ins.periph_sel == 2 # high 2 bits of 0x241 + assert ins.addr == 0x41 # low 8 bits of 0x241 + + test_make_ins_struct_def() test_make_ins() test_arg_qualify() @@ -183,3 +208,4 @@ def test_reg_address_translations(): test_eval_arg() test_reg_direct_ulp_addressing() test_reg_address_translations() +test_reg_address_translations_sens() diff --git a/tests/opcodes_s2.py b/tests/opcodes_s2.py new file mode 100644 index 0000000..2c724c3 --- /dev/null +++ b/tests/opcodes_s2.py @@ -0,0 +1,263 @@ +from uctypes import UINT32, BFUINT32, BF_POS, BF_LEN +from esp32_ulp.opcodes_s2 import make_ins, make_ins_struct_def +from esp32_ulp.opcodes_s2 import get_reg, get_imm, get_cond, arg_qualify, eval_arg, ARG, REG, IMM, SYM, COND +from esp32_ulp.assemble import SymbolTable, ABS, REL, TEXT +import esp32_ulp.opcodes_s2 as opcodes + +OPCODE_DELAY = 4 +LAYOUT_DELAY = """ + cycles : 16 # Number of cycles to sleep + unused : 12 # Unused + opcode : 4 # Opcode (OPCODE_DELAY) +""" + + +def test_make_ins_struct_def(): + sd = make_ins_struct_def(LAYOUT_DELAY) + assert set(sd) == {'cycles', 'unused', 'opcode', 'all'} + assert sd['cycles'] == BFUINT32 | 0 << BF_POS | 16 << BF_LEN + assert sd['unused'] == BFUINT32 | 16 << BF_POS | 12 << BF_LEN + assert sd['opcode'] == BFUINT32 | 28 << BF_POS | 4 << BF_LEN + assert sd['all'] == UINT32 + + +def test_make_ins(): + _delay = make_ins(LAYOUT_DELAY) + _delay.cycles = 0x23 + _delay.unused = 0 + _delay.opcode = OPCODE_DELAY + assert _delay.cycles == 0x23 + assert _delay.unused == 0 + assert _delay.opcode == OPCODE_DELAY + assert _delay.all == 0x40000023 + + +def test_arg_qualify(): + assert arg_qualify('r0') == ARG(REG, 0, 'r0') + assert arg_qualify('R3') == ARG(REG, 3, 'R3') + assert arg_qualify('0') == ARG(IMM, 0, '0') + assert arg_qualify('-1') == ARG(IMM, -1, '-1') + assert arg_qualify('1') == ARG(IMM, 1, '1') + assert arg_qualify('0x20') == ARG(IMM, 32, '0x20') + assert arg_qualify('0o100') == ARG(IMM, 64, '0o100') + assert arg_qualify('0b1000') == ARG(IMM, 8, '0b1000') + assert arg_qualify('eq') == ARG(COND, 'eq', 'eq') + assert arg_qualify('Eq') == ARG(COND, 'eq', 'Eq') + assert arg_qualify('EQ') == ARG(COND, 'eq', 'EQ') + + # for the next tests, ensure the opcodes module has a SymbolTable + opcodes.symbols = SymbolTable({}, {}, {}) + opcodes.symbols.set_sym('const', ABS, None, 42) # constant as defined by .set + opcodes.symbols.set_sym('entry', REL, TEXT, 4) # label pointing to code + + assert arg_qualify('1+1') == ARG(IMM, 2, '1+1') + assert arg_qualify('const >> 1') == ARG(IMM, 21, 'const >> 1') + assert arg_qualify('entry') == ARG(SYM, (REL, TEXT, 4), 'entry') # symbols should not (yet) be evaluated + assert arg_qualify('entry + const') == ARG(IMM, 46, 'entry + const') + + # clean up + opcodes.symbols = None + + +def test_get_reg(): + assert get_reg('r0') == 0 + assert get_reg('R3') == 3 + + +def test_get_imm(): + assert get_imm('42') == 42 + + +def test_get_cond(): + assert get_cond('Eq') == 'eq' + + +def test_eval_arg(): + opcodes.symbols = SymbolTable({}, {}, {}) + opcodes.symbols.set_sym('const', ABS, None, 42) # constant + opcodes.symbols.set_sym('raise', ABS, None, 99) # constant using a python keyword as name (is allowed) + + assert eval_arg('1+1') == 2 + assert eval_arg('1+const') == 43 + assert eval_arg('raise*2/3') == 66 + assert eval_arg('raise-const') == 57 + assert eval_arg('(raise-const)*2') == 114 + assert eval_arg('const % 5') == 2 + assert eval_arg('const + 0x19af') == 0x19af + 42 + assert eval_arg('const & ~2') == 40 + assert eval_arg('const << 3') == 336 + assert eval_arg('const >> 1') == 21 + assert eval_arg('(const|4)&0xf') == 0xe + + assert_raises(ValueError, eval_arg, 'evil()') + assert_raises(ValueError, eval_arg, 'def cafe()') + assert_raises(ValueError, eval_arg, '1 ^ 2') + assert_raises(ValueError, eval_arg, '!100') + + # clean up + opcodes.symbols = None + + +def assert_raises(exception, func, *args): + try: + func(*args) + except exception: + raised = True + else: + raised = False + assert raised + + +def test_reg_direct_ulp_addressing(): + """ + Test direct ULP addressing of peripheral registers + input must be <= 0x3ff (10 bits) + periph_sel == high 2 bits from input + addr == low 8 bits from input + """ + + ins = make_ins(""" + addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC + periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) + unused : 8 # Unused + low : 5 # Low bit + high : 5 # High bit + opcode : 4 # Opcode (OPCODE_RD_REG) + """) + + ins.all = opcodes.i_reg_rd("0x0", "0", "0") + assert ins.periph_sel == 0 + assert ins.addr == 0x0 + + ins.all = opcodes.i_reg_rd("0x012", "0", "0") + assert ins.periph_sel == 0 + assert ins.addr == 0x12 + + ins.all = opcodes.i_reg_rd("0x123", "0", "0") + assert ins.periph_sel == 1 + assert ins.addr == 0x23 + + ins.all = opcodes.i_reg_rd("0x2ee", "0", "0") + assert ins.periph_sel == 2 + assert ins.addr == 0xee + + ins.all = opcodes.i_reg_rd("0x3ff", "0", "0") + assert ins.periph_sel == 3 + assert ins.addr == 0xff + + # anything bigger than 0x3ff must be a valid full address + assert_raises(ValueError, opcodes.i_reg_rd, "0x400", "0", "0") + + +def test_reg_address_translations_s2(): + """ + Test addressing of ESP32-S2 peripheral registers using full DPORT bus addresses + """ + + ins = make_ins(""" + addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC + periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) + unused : 8 # Unused + low : 5 # Low bit + high : 5 # High bit + opcode : 4 # Opcode (OPCODE_RD_REG) + """) + + # direct ULP address is derived from full address as follows: + # full:0x3f4084a8 == ulp:(0x3f4084a8-DR_REG_RTCCNTL_BASE) / 4 + # full:0x3f4084a8 == ulp:(0x3f4084a8-0x3f408000) / 4 + # full:0x3f4084a8 == ulp:0x4a8 / 4 + # full:0x3f4084a8 == ulp:0x12a + # see: https://github.com/espressif/binutils-esp32ulp/blob/249ec34/gas/config/tc-esp32ulp_esp32s2.c#L78 + ins.all = opcodes.i_reg_rd("0x3f4084a8", "0", "0") + assert ins.periph_sel == 1 # high 2 bits of 0x12a + assert ins.addr == 0x2a # low 8 bits of 0x12a + + +def test_reg_address_translations_s2_sens(): + """ + Test addressing of ESP32-S2 peripheral registers using full DPORT bus addresses + """ + + ins = make_ins(""" + addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC + periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) + unused : 8 # Unused + low : 5 # Low bit + high : 5 # High bit + opcode : 4 # Opcode (OPCODE_RD_REG) + """) + + # direct ULP address is derived from full address as follows: + # full:0x3f408904 == ulp:(0x3f408904-DR_REG_RTCCNTL_BASE) / 4 + # full:0x3f408904 == ulp:(0x3f408904-0x3f408000) / 4 + # full:0x3f408904 == ulp:0x904 / 4 + # full:0x3f408904 == ulp:0x241 + # see: https://github.com/espressif/binutils-esp32ulp/blob/249ec34/gas/config/tc-esp32ulp_esp32s2.c#L78 + ins.all = opcodes.i_reg_rd("0x3f408904", "0", "0") + assert ins.periph_sel == 2 # high 2 bits of 0x241 + assert ins.addr == 0x41 # low 8 bits of 0x241 + + +def test_reg_address_translations_s3(): + """ + Test addressing of ESP32-S3 peripheral registers using full DPORT bus addresses + """ + + ins = make_ins(""" + addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC + periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) + unused : 8 # Unused + low : 5 # Low bit + high : 5 # High bit + opcode : 4 # Opcode (OPCODE_RD_REG) + """) + + # direct ULP address is derived from full address as follows: + # full:0x600084a8 == ulp:(0x600084a8-DR_REG_RTCCNTL_BASE) / 4 + # full:0x600084a8 == ulp:(0x600084a8-0x60008000) / 4 + # full:0x600084a8 == ulp:0x4a8 / 4 + # full:0x600084a8 == ulp:0x12a + # see: https://github.com/espressif/binutils-esp32ulp/blob/249ec34/gas/config/tc-esp32ulp_esp32s2.c#L78 + ins.all = opcodes.i_reg_rd("0x600084a8", "0", "0") + assert ins.periph_sel == 1 # high 2 bits of 0x12a + assert ins.addr == 0x2a # low 8 bits of 0x12a + + +def test_reg_address_translations_s3_sens(): + """ + Test addressing of ESP32-S3 peripheral registers using full DPORT bus addresses + """ + + ins = make_ins(""" + addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC + periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) + unused : 8 # Unused + low : 5 # Low bit + high : 5 # High bit + opcode : 4 # Opcode (OPCODE_RD_REG) + """) + + # direct ULP address is derived from full address as follows: + # full:0x60008904 == ulp:(0x60008904-DR_REG_RTCCNTL_BASE) / 4 + # full:0x60008904 == ulp:(0x60008904-0x60008000) / 4 + # full:0x60008904 == ulp:0x904 / 4 + # full:0x60008904 == ulp:0x241 + # see: https://github.com/espressif/binutils-esp32ulp/blob/249ec34/gas/config/tc-esp32ulp_esp32s2.c#L78 + ins.all = opcodes.i_reg_rd("0x60008904", "0", "0") + assert ins.periph_sel == 2 # high 2 bits of 0x241 + assert ins.addr == 0x41 # low 8 bits of 0x241 + + +test_make_ins_struct_def() +test_make_ins() +test_arg_qualify() +test_get_reg() +test_get_imm() +test_get_cond() +test_eval_arg() +test_reg_direct_ulp_addressing() +test_reg_address_translations_s2() +test_reg_address_translations_s3() +test_reg_address_translations_s2_sens() +test_reg_address_translations_s3_sens() diff --git a/tools/decode.py b/tools/decode.py new file mode 100644 index 0000000..3aaf095 --- /dev/null +++ b/tools/decode.py @@ -0,0 +1,149 @@ +import esp32_ulp.opcodes as opcodes + + +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 diff --git a/tools/decode_s2.py b/tools/decode_s2.py new file mode 100644 index 0000000..de3b3c7 --- /dev/null +++ b/tools/decode_s2.py @@ -0,0 +1,186 @@ +import esp32_ulp.opcodes_s2 as opcodes + + +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', 'GT', 'EQ') +bs_cmp_ops = ('??', 'LT', '??', 'GT', 'EQ', 'LE', '??', 'GE') + +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_STAGE_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_B: ( + 'BR', + opcodes._b, + 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, bs_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.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/LDH', + opcodes._ld, + lambda op: '%s r%s, r%s, %s' % ('LDH' if op.rd_upper else 'LD', op.dreg, op.sreg, twos_comp(op.offset, 11)) + ), + opcodes.OPCODE_ST: ('ST', opcodes._st, { + opcodes.SUB_OPCODE_ST_AUTO: ( + 'STI/STI32', + opcodes._st, + lambda op: 'STI32 r%s, r%s, %s' % (op.sreg, op.dreg, op.label) if op.wr_way == 0 + else 'STI r%s, r%s, %s' % (op.sreg, op.dreg, op.label) if op.label + else 'STI r%s, r%s' % (op.sreg, op.dreg) + ), + opcodes.SUB_OPCODE_ST_OFFSET: ( + 'STO', + opcodes._st, + lambda op: 'STO %s' % twos_comp(op.offset, 11) + ), + opcodes.SUB_OPCODE_ST: ( + 'ST/STH/ST32', + opcodes._st, + lambda op: '%s r%s, r%s, %s, %s' % ('STH' if op.upper else 'STL', op.sreg, op.dreg, twos_comp(op.offset, 11), op.label) if op.wr_way and op.label + else '%s r%s, r%s, %s' % ('STH' if op.upper else 'ST', op.sreg, op.dreg, twos_comp(op.offset, 11)) if op.wr_way + else 'ST32 r%s, r%s, %s, %s' % (op.sreg, op.dreg, twos_comp(op.offset, 11), op.label) + ) + }), + 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 twos_comp(val, bits): + """ + compute the correct value of a 2's complement + based on the number of bits in the source + """ + if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255 + val = val - (1 << bits) # compute negative value + return val + + +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', + 'rd_upper', 'label', 'upper', 'wr_way', + ) + 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 + if ins.sub_opcode == opcodes.SUB_OPCODE_BS: + extra = ' (%s)' % bs_cmp_ops[val] + else: + extra = ' (%s)' % cmp_ops[val] + elif field == 'offset': + if ins.opcode in (opcodes.OPCODE_ST, opcodes.OPCODE_LD): + val = twos_comp(val, 11) + + field_details.append((field, val, extra)) + + return field_details diff --git a/tools/disassemble.py b/tools/disassemble.py index f647576..442c8f3 100644 --- a/tools/disassemble.py +++ b/tools/disassemble.py @@ -1,158 +1,27 @@ 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, ()) +# placeholders: +# these functions will be dynamically loaded later based on the chosen cpu +decode_instruction, get_instruction_fields = None, None - 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 load_decoder(cpu): + if cpu == 'esp32': + mod = 'decode' + elif cpu == 'esp32s2': + mod = 'decode_s2' + else: + raise ValueError('Invalid cpu') + relative_import = 1 if '/' in __file__ else 0 + decode = __import__(mod, globals(), locals(), [], relative_import) -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 + global decode_instruction, get_instruction_fields + decode_instruction = decode.decode_instruction + get_instruction_fields = decode.get_instruction_fields def chunk_into_words(code, bytes_per_word, byteorder): @@ -216,7 +85,9 @@ def print_data_section(data_offset, code): print_code_line(data_offset + (idx << 2), i, asm) -def disassemble_manually(byte_sequence_string, verbose=False): +def disassemble_manually(byte_sequence_string, cpu, verbose=False): + load_decoder(cpu) + sequence = byte_sequence_string.strip().replace(' ','') chars_per_instruction = 8 list = [ @@ -230,7 +101,9 @@ def disassemble_manually(byte_sequence_string, verbose=False): decode_instruction_and_print(idx << 2, i, verbose) -def disassemble_file(filename, verbose=False): +def disassemble_file(filename, cpu, verbose=False): + load_decoder(cpu) + with open(filename, 'rb') as f: data = f.read() @@ -265,6 +138,7 @@ def print_help(): print('Usage: disassemble.py [] [-m | ]') print('') print('Options:') + print(' -c Choose ULP variant: either esp32 or esp32s2') 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') @@ -273,6 +147,7 @@ def print_help(): def handle_cmdline(params): + cpu = 'esp32' verbose = False filename = None byte_sequence = None @@ -281,6 +156,9 @@ def handle_cmdline(params): if params[0] == '-h': print_help() sys.exit(0) + elif params[0] == '-c': + cpu = params[1] + params = params[1:] # remove first param from list elif params[0] == '-m': if len(params) == 1: print_help() @@ -310,10 +188,11 @@ def handle_cmdline(params): params = params[1:] # remove first param from list + if byte_sequence: - disassemble_manually(byte_sequence, verbose) + disassemble_manually(byte_sequence, cpu, verbose) elif filename: - disassemble_file(filename, verbose) + disassemble_file(filename, cpu, verbose) if sys.argv: # if run from cmdline