From 6b84e6be56e83bf66128eea17642bd941e350a9d Mon Sep 17 00:00:00 2001 From: Arne Meeuw Date: Sat, 30 Mar 2024 12:05:11 +0700 Subject: [PATCH 1/9] Add src_to_binary_ext that returns addrs_syms --- esp32_ulp/__init__.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/esp32_ulp/__init__.py b/esp32_ulp/__init__.py index 634605d..1a16e37 100644 --- a/esp32_ulp/__init__.py +++ b/esp32_ulp/__init__.py @@ -12,19 +12,20 @@ from .link import make_binary garbage_collect('after import') - -def src_to_binary(src, cpu): +def src_to_binary_ext(src, cpu): assembler = Assembler(cpu) src = preprocess(src) assembler.assemble(src, remove_comments=False) # comments already removed by preprocessor garbage_collect('before symbols export') addrs_syms = assembler.symbols.export() - for addr, sym in addrs_syms: - print('%04d %s' % (addr, sym)) - text, data, bss_len = assembler.fetch() - return make_binary(text, data, bss_len) + return make_binary(text, data, bss_len), addrs_syms +def src_to_binary(src, cpu): + binary, addrs_syms = src_to_binary_ext(src, cpu) + for addr, sym in addrs_syms: + print('%04d %s' % (addr, sym)) + return binary def assemble_file(filename, cpu): with open(filename) as f: From fe753d96209bfa3fa65d1c8f0a5f455472bfb645 Mon Sep 17 00:00:00 2001 From: Arne Meeuw Date: Sat, 30 Mar 2024 20:36:26 +0700 Subject: [PATCH 2/9] Adhere to styling conventions --- esp32_ulp/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esp32_ulp/__init__.py b/esp32_ulp/__init__.py index 1a16e37..e14cca3 100644 --- a/esp32_ulp/__init__.py +++ b/esp32_ulp/__init__.py @@ -12,6 +12,7 @@ from .link import make_binary garbage_collect('after import') + def src_to_binary_ext(src, cpu): assembler = Assembler(cpu) src = preprocess(src) @@ -21,12 +22,14 @@ def src_to_binary_ext(src, cpu): text, data, bss_len = assembler.fetch() return make_binary(text, data, bss_len), addrs_syms + def src_to_binary(src, cpu): binary, addrs_syms = src_to_binary_ext(src, cpu) for addr, sym in addrs_syms: print('%04d %s' % (addr, sym)) return binary + def assemble_file(filename, cpu): with open(filename) as f: src = f.read() From 82e69f13603ea8d626018806db82eaa430804bd2 Mon Sep 17 00:00:00 2001 From: pidou46 Date: Sat, 14 Dec 2024 17:26:07 +0100 Subject: [PATCH 3/9] Open ulp file in binary mode instead of text mode Avoid rising "unicode exception" when running from mp1.24 --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index b50e01d..77906dc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -118,7 +118,7 @@ That file can then be loaded directly without assembling the source again. from esp32 import ULP ulp = ULP() - with open('test.ulp', 'r') as f: + with open('test.ulp', 'rb') as f: # load the binary into RTC memory ulp.load_binary(0, f.read()) From 9be4dc08131ae1af3b14503ac525667f0c39b456 Mon Sep 17 00:00:00 2001 From: Wilko Nienhaus Date: Sun, 15 Dec 2024 11:53:13 +0200 Subject: [PATCH 4/9] Fix compat tests due to esp-idf moving include files Peripheral register definitions were moved to a new directory inside the ESP-IDF: components/soc/{cpu_type}/register. This change caused our compat tests to fail because they depended on predefined register constants. This commit resolves the issue by adding the new directory to GCC's include search path using the -I option. Additionally, all header files in this new directory are now added to the DefinesDB for the compat tests with RTC macros. Fixes #100. --- tests/01_compat_tests.sh | 1 + tests/02_compat_rtc_tests.sh | 2 ++ 2 files changed, 3 insertions(+) diff --git a/tests/01_compat_tests.sh b/tests/01_compat_tests.sh index 0732d34..a6fc2d1 100755 --- a/tests/01_compat_tests.sh +++ b/tests/01_compat_tests.sh @@ -53,6 +53,7 @@ run_tests_for_cpu() { echo -e "\tBuilding using binutils ($cpu)" gcc -I esp-idf/components/soc/$cpu/include -I esp-idf/components/esp_common/include \ + -I esp-idf/components/soc/$cpu/register \ -x assembler-with-cpp \ -E -o ${pre_file} $src_file esp32ulp-elf-as --mcpu=$cpu -o $obj_file ${pre_file} diff --git a/tests/02_compat_rtc_tests.sh b/tests/02_compat_rtc_tests.sh index 1e6a2d1..be2a98d 100755 --- a/tests/02_compat_rtc_tests.sh +++ b/tests/02_compat_rtc_tests.sh @@ -61,6 +61,7 @@ build_defines_db() { rm -f "${defines_db}" micropython -m esp32_ulp.parse_to_db \ esp-idf/components/soc/$cpu/include/soc/*.h \ + esp-idf/components/soc/$cpu/register/soc/*.h \ esp-idf/components/esp_common/include/*.h 1>$log_file # cache defines.db @@ -184,6 +185,7 @@ run_tests_for_cpu() { echo -e "\tBuilding using binutils ($cpu)" gcc -I esp-idf/components/soc/$cpu/include -I esp-idf/components/esp_common/include \ + -I esp-idf/components/soc/$cpu/register \ -x assembler-with-cpp \ -E -o ${pre_file} $src_file esp32ulp-elf-as --mcpu=$cpu -o $obj_file ${pre_file} From 4b370ec4d45f9cc4937882c47ad44dd6c3ff8f19 Mon Sep 17 00:00:00 2001 From: mjaspers2mtu <44484497+mjaspers2mtu@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:26:02 +0200 Subject: [PATCH 5/9] Add LED Fade Example An example that uses ULP self-modifying code to create a PWM-like LED dimming effect on GPIO 15. --- examples/fade_s2.py | 82 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 examples/fade_s2.py diff --git a/examples/fade_s2.py b/examples/fade_s2.py new file mode 100644 index 0000000..8b9ca71 --- /dev/null +++ b/examples/fade_s2.py @@ -0,0 +1,82 @@ +# +# This file is part of the micropython-esp32-ulp project, +# https://github.com/micropython/micropython-esp32-ulp +# +# SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. +# SPDX-License-Identifier: MIT + +""" +Example for: ESP32-S2 Wemos mini development board V1.0.0 with led on pin 15 + +This example creates a PWM-like dimming effect using self-modifying ULP code. +The ULP program rewrites the `WAIT` instructions to control on/off LED durations, +simulating a variable duty cycle. + +Note: +The `WAIT` instruction uses an immediate operand (fixed value) for delay cycles. However, we can change the lower half of memory +to modify these values at runtime, simulating variable wait times via registers. +""" + +from esp32 import ULP +from machine import mem32 +from esp32_ulp import src_to_binary +from time import sleep + +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_XTAL_32P_PAD_REG (DR_REG_RTCIO_BASE + 0xC0) +#define RTC_IO_X32P_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_GPIO15_CHANNEL 15 + +.global entry +program_init: + # connect GPIO to ULP (0: GPIO connected to digital GPIO module, 1: GPIO connected to analog RTC module) + WRITE_RTC_REG(RTC_IO_XTAL_32P_PAD_REG, RTC_IO_X32P_MUX_SEL_M, 1, 1); + + # enable GPIO as output, not input (this also enables a pull-down by default) + WRITE_RTC_REG(RTC_GPIO_ENABLE_REG, RTC_GPIO_ENABLE_S + RTCIO_GPIO15_CHANNEL, 1, 1) + +set_waits: add r0, r0, 0xFF # Increase r0 (delay time) + move r3, wait_off + st r0, r3, 0 # Overwrite wait_off with new delay value + + move r2, 0xFFFF + sub r1, r2, r0 # Calculate complementary delay time + move r3, wait_on + st r1, r3, 0 # Overwrite wait_on with new value + + WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + RTCIO_GPIO15_CHANNEL, 1, 0) # turn off led +wait_off: wait 0 # Placeholder; value overwritten dynamically + WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + RTCIO_GPIO15_CHANNEL, 1, 1) # turn on led +wait_on: wait 0 # Placeholder; value overwritten dynamically + +jump set_waits # Loop program + +""" + +binary = src_to_binary(source, cpu="esp32s2") # cpu is esp32 or esp32s2 + +load_addr, entry_addr = 0, 0 + +ULP_MEM_BASE = 0x50000000 + +ulp = ULP() +ulp.load_binary(load_addr, binary) + +ulp.run(entry_addr) + +while True: + print(hex(mem32[ULP_MEM_BASE + 40])) # show that the WAIT cycles are changing + sleep(0.5) From 277067c9c98d75f80843d37430375cf5d8126c9b Mon Sep 17 00:00:00 2001 From: mjaspers2mtu <44484497+mjaspers2mtu@users.noreply.github.com> Date: Sun, 15 Jun 2025 16:34:46 +0200 Subject: [PATCH 6/9] Add example for TSENS instruction Example code for reading the temperature of the chip using the ULP of the ESP32-S2 --- examples/tsens_s2.py | 80 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 examples/tsens_s2.py diff --git a/examples/tsens_s2.py b/examples/tsens_s2.py new file mode 100644 index 0000000..58594e6 --- /dev/null +++ b/examples/tsens_s2.py @@ -0,0 +1,80 @@ +# +# This file is part of the micropython-esp32-ulp project, +# https://github.com/micropython/micropython-esp32-ulp +# +# SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. +# SPDX-License-Identifier: MIT + + +""" +Example for: ESP32-S2 + +Example showing how to use the TSENS instruction from the ULP +and access temperature data from the main CPU. + +Note that the temperature sensor clock needs to be enabled for the TSENS instruction to complete. + +""" + +from esp32 import ULP +from machine import mem32 +from esp32_ulp import src_to_binary +from time import sleep + +source = """\ +# constants from: +# https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/include/soc/reg_base.h +#define DR_REG_SENS_BASE 0x3f408800 + +# constants from: +# https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/include/soc/sens_reg.h +#define SENS_SAR_TSENS_CTRL2_REG (DR_REG_SENS_BASE + 0x0054) +#define SENS_TSENS_CLKGATE_EN_M (BIT(15)) + +.set token, 0xACED + +.text +magic: .long 0 +temperature_data: .long 0 + +.global entry +entry: + move r3, magic + ld r0, r3, 0 + jumpr start, token, eq #check if we have already initialized + +init: + # Set SENS_TSENS_CLKGATE_EN to enable temperature sensor clock. + WRITE_RTC_REG(SENS_SAR_TSENS_CTRL2_REG, SENS_TSENS_CLKGATE_EN_M, 1, 1) + + # Store temperature_data memory location in r2 + move r2, temperature_data + + # store that we're done with initialisation + move r0, token + st r0, r3, 0 + +start: + tsens r0, 1000 # make measurement for 1000 clock cycles + st r0, r2, 0 # store the temperature in memory to be read by main CPU + 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: + magic_token = hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK) + current_temperature = 0.4386*(mem32[ULP_MEM_BASE + load_addr + 4] & ULP_DATA_MASK)-20.52 + print(magic_token, current_temperature) + sleep(0.5) \ No newline at end of file From e0ae28133a5d3416b1a54f6b79289f674d1e6969 Mon Sep 17 00:00:00 2001 From: Wilko Nienhaus Date: Thu, 19 Jun 2025 23:35:34 +0300 Subject: [PATCH 7/9] Update builder image to ubuntu-22.04 GitHub has deprecated and removed the ubuntu-20.04 builder image. This commit updates the build process to use the ubuntu-22.04 image instead. --- .github/workflows/run_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 72f6917..bf54464 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -11,7 +11,7 @@ on: [push, pull_request] jobs: tests: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 with: From e6d5d96a8c1fedd352b2fb5b83ca110c43b4b14c Mon Sep 17 00:00:00 2001 From: mjaspers2mtu <44484497+mjaspers2mtu@users.noreply.github.com> Date: Sun, 6 Jul 2025 16:41:14 +0200 Subject: [PATCH 8/9] Update fade_s2.py made the default LED pin 4, with the option for the user to change led_pin. --- examples/fade_s2.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/examples/fade_s2.py b/examples/fade_s2.py index 8b9ca71..5f8d4ca 100644 --- a/examples/fade_s2.py +++ b/examples/fade_s2.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MIT """ -Example for: ESP32-S2 Wemos mini development board V1.0.0 with led on pin 15 +Example for: ESP32-S2 This example creates a PWM-like dimming effect using self-modifying ULP code. The ULP program rewrites the `WAIT` instructions to control on/off LED durations, @@ -23,32 +23,34 @@ from time import sleep source = """\ +# Pin with LED: (0 to 21) +.set led_pin, 4 + # 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: +# Espressif Technical Reference Manual (TRM) Chapter 5.15 Register 5.63: +#define RTCIO_TOUCH_PADn_REG (DR_REG_RTCIO_BASE + 0x84 + 4 * led_pin) +#define RTCIO_TOUCH_PADn_MUX_SEL_M (BIT(19)) + # constants from: # https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/include/soc/rtc_io_reg.h -#define RTC_IO_XTAL_32P_PAD_REG (DR_REG_RTCIO_BASE + 0xC0) -#define RTC_IO_X32P_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_GPIO15_CHANNEL 15 - .global entry program_init: - # connect GPIO to ULP (0: GPIO connected to digital GPIO module, 1: GPIO connected to analog RTC module) - WRITE_RTC_REG(RTC_IO_XTAL_32P_PAD_REG, RTC_IO_X32P_MUX_SEL_M, 1, 1); + # connect GPIO to ULP (0: GPIO connected to digital GPIO module, 1: GPIO connected to analog RTC module) + WRITE_RTC_REG(RTCIO_TOUCH_PADn_REG, RTCIO_TOUCH_PADn_MUX_SEL_M, 1, 1); - # enable GPIO as output, not input (this also enables a pull-down by default) - WRITE_RTC_REG(RTC_GPIO_ENABLE_REG, RTC_GPIO_ENABLE_S + RTCIO_GPIO15_CHANNEL, 1, 1) + # enable GPIO as output, not input (this also enables a pull-down by default) + WRITE_RTC_REG(RTC_GPIO_ENABLE_REG, RTC_GPIO_ENABLE_S + led_pin, 1, 1) -set_waits: add r0, r0, 0xFF # Increase r0 (delay time) +set_waits: add r0, r0, 200 # Increase r0 (delay time) move r3, wait_off st r0, r3, 0 # Overwrite wait_off with new delay value @@ -57,9 +59,9 @@ move r3, wait_on st r1, r3, 0 # Overwrite wait_on with new value - WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + RTCIO_GPIO15_CHANNEL, 1, 0) # turn off led + WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + led_pin, 1, 0) # turn off led (clear GPIO) wait_off: wait 0 # Placeholder; value overwritten dynamically - WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + RTCIO_GPIO15_CHANNEL, 1, 1) # turn on led + WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + led_pin, 1, 1) # turn on led (set GPIO) wait_on: wait 0 # Placeholder; value overwritten dynamically jump set_waits # Loop program From da5d9289160a9323a5ac368403c888ab28a3d898 Mon Sep 17 00:00:00 2001 From: Wilko Nienhaus Date: Tue, 17 Jun 2025 09:01:53 +0300 Subject: [PATCH 9/9] Fix parsing of integer literals with base prefix MicroPython 1.25.0 introduced a breaking change, aligning the behaviour of the int() function with the behaviour of CPython (assume a decimal number, unless a base is specified. Only if a base of 0 is specified will the base be inferred from the string). This commit implements a new custom parsing function `parse_int`. It can correctly parse the following string literals: * 0x[0-9]+ -> treated as hex * 0b[0-9]+ -> treated as binary * 0o[0-9]+ -> treated as octal (Python style) * 0[0-9]+ -> treated as octal (GNU as style) * anything else parsed as decimal It only handles the GNU as style octal case directly, letting the original `int()` function handle the other cases (using base 0). In fact, the GNU as octal case was not handled correctly previously, and this commit fixes that. Some new tests for previous functionality were added to show that both new and previous cases are being handled correctly. Note: GNU as does not actually accept the octal prefix 0o..., but we accept it as a convenience, as this is accepted in Python code. This means however, that our assembler accepts code which GNU as does not accept. But the other way around, we still accept all code that GNU as accepts, which was one of our goals. --- esp32_ulp/assemble.py | 6 +++--- esp32_ulp/opcodes.py | 11 +++++++--- esp32_ulp/opcodes_s2.py | 11 +++++++--- esp32_ulp/util.py | 12 +++++++++++ tests/opcodes.py | 15 +++++++++++--- tests/opcodes_s2.py | 15 +++++++++++--- tests/util.py | 45 ++++++++++++++++++++++++++++++++++++++++- 7 files changed, 99 insertions(+), 16 deletions(-) diff --git a/esp32_ulp/assemble.py b/esp32_ulp/assemble.py index 8b79071..33cec42 100644 --- a/esp32_ulp/assemble.py +++ b/esp32_ulp/assemble.py @@ -219,13 +219,13 @@ def fill(self, section, amount, fill_byte): raise ValueError('fill in bss section not allowed') if section is TEXT: # TODO: text section should be filled with NOPs raise ValueError('fill/skip/align in text section not supported') - fill = int(fill_byte or 0).to_bytes(1, 'little') * amount + fill = int(self.opcodes.eval_arg(str(fill_byte or 0))).to_bytes(1, 'little') * amount self.offsets[section] += len(fill) if section is not BSS: self.sections[section].append(fill) def d_skip(self, amount, fill=None): - amount = int(amount) + amount = int(self.opcodes.eval_arg(amount)) self.fill(self.section, amount, fill) d_space = d_skip @@ -246,7 +246,7 @@ def d_global(self, symbol): self.symbols.set_global(symbol) def append_data(self, wordlen, args): - data = [int(arg).to_bytes(wordlen, 'little') for arg in args] + data = [int(self.opcodes.eval_arg(arg)).to_bytes(wordlen, 'little') for arg in args] self.append_section(b''.join(data)) def d_byte(self, *args): diff --git a/esp32_ulp/opcodes.py b/esp32_ulp/opcodes.py index 10fc3d1..03849a3 100644 --- a/esp32_ulp/opcodes.py +++ b/esp32_ulp/opcodes.py @@ -13,7 +13,7 @@ from uctypes import struct, addressof, LITTLE_ENDIAN, UINT32, BFUINT32, BF_POS, BF_LEN from .soc import * -from .util import split_tokens, validate_expression +from .util import split_tokens, validate_expression, parse_int # XXX dirty hack: use a global for the symbol table symbols = None @@ -285,7 +285,12 @@ def eval_arg(arg): _, _, sym_value = symbols.get_sym(token) parts.append(str(sym_value)) else: - parts.append(token) + try: + # attempt to parse, to convert numbers with base prefix correctly + int_token = parse_int(token) + parts.append(str(int_token)) + except ValueError: + parts.append(token) parts = "".join(parts) if not validate_expression(parts): raise ValueError('Unsupported expression: %s' % parts) @@ -311,7 +316,7 @@ def arg_qualify(arg): if arg_lower in ['--', 'eq', 'ov', 'lt', 'gt', 'ge', 'le']: return ARG(COND, arg_lower, arg) try: - return ARG(IMM, int(arg), arg) + return ARG(IMM, parse_int(arg), arg) except ValueError: pass try: diff --git a/esp32_ulp/opcodes_s2.py b/esp32_ulp/opcodes_s2.py index 91549af..3a9d643 100644 --- a/esp32_ulp/opcodes_s2.py +++ b/esp32_ulp/opcodes_s2.py @@ -12,7 +12,7 @@ from ucollections import namedtuple from uctypes import struct, addressof, LITTLE_ENDIAN, UINT32, BFUINT32, BF_POS, BF_LEN -from .util import split_tokens, validate_expression +from .util import split_tokens, validate_expression, parse_int # XXX dirty hack: use a global for the symbol table symbols = None @@ -301,7 +301,12 @@ def eval_arg(arg): _, _, sym_value = symbols.get_sym(token) parts.append(str(sym_value)) else: - parts.append(token) + try: + # attempt to parse, to convert numbers with base prefix correctly + int_token = parse_int(token) + parts.append(str(int_token)) + except ValueError: + parts.append(token) parts = "".join(parts) if not validate_expression(parts): raise ValueError('Unsupported expression: %s' % parts) @@ -327,7 +332,7 @@ def arg_qualify(arg): if arg_lower in ['--', 'eq', 'ov', 'lt', 'gt', 'ge', 'le']: return ARG(COND, arg_lower, arg) try: - return ARG(IMM, int(arg), arg) + return ARG(IMM, parse_int(arg), arg) except ValueError: pass try: diff --git a/esp32_ulp/util.py b/esp32_ulp/util.py index 78e7c85..5f1628d 100644 --- a/esp32_ulp/util.py +++ b/esp32_ulp/util.py @@ -77,6 +77,18 @@ def validate_expression(param): return True +def parse_int(literal): + """ + GNU as compatible parsing of string literals into integers + Specifically, GNU as treats literals starting with 0 as octal + All other literals are correctly parsed by Python + See: https://sourceware.org/binutils/docs/as/Integers.html + """ + if len(literal) >= 2 and (literal.startswith("0") or literal.startswith("-0")) and literal.lstrip("-0").isdigit(): + return int(literal, 8) + return int(literal, 0) + + def file_exists(filename): try: os.stat(filename) diff --git a/tests/opcodes.py b/tests/opcodes.py index 3dc7453..f8109b5 100644 --- a/tests/opcodes.py +++ b/tests/opcodes.py @@ -7,7 +7,7 @@ from uctypes import UINT32, BFUINT32, BF_POS, BF_LEN from esp32_ulp.opcodes import make_ins, make_ins_struct_def -from esp32_ulp.opcodes import get_reg, get_imm, get_cond, arg_qualify, eval_arg, ARG, REG, IMM, SYM, COND +from esp32_ulp.opcodes import get_reg, get_imm, get_cond, arg_qualify, parse_int, eval_arg, ARG, REG, IMM, SYM, COND from esp32_ulp.assemble import SymbolTable, ABS, REL, TEXT import esp32_ulp.opcodes as opcodes @@ -46,6 +46,7 @@ def test_arg_qualify(): 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('0100') == ARG(IMM, 64, '0100') assert arg_qualify('0o100') == ARG(IMM, 64, '0o100') assert arg_qualify('0b1000') == ARG(IMM, 8, '0b1000') assert arg_qualify('eq') == ARG(COND, 'eq', 'eq') @@ -96,6 +97,11 @@ def test_eval_arg(): assert eval_arg('const >> 1') == 21 assert eval_arg('(const|4)&0xf') == 0xe + assert eval_arg('0x7') == 7 + assert eval_arg('010') == 8 + assert eval_arg('-0x7') == -7 # negative + assert eval_arg('~0x7') == -8 # complement + assert_raises(ValueError, eval_arg, 'evil()') assert_raises(ValueError, eval_arg, 'def cafe()') assert_raises(ValueError, eval_arg, '1 ^ 2') @@ -105,14 +111,17 @@ def test_eval_arg(): opcodes.symbols = None -def assert_raises(exception, func, *args): +def assert_raises(exception, func, *args, message=None): try: func(*args) - except exception: + except exception as e: raised = True + actual_message = e.args[0] else: raised = False assert raised + if message: + assert actual_message == message, '%s == %s' % (actual_message, message) def test_reg_direct_ulp_addressing(): diff --git a/tests/opcodes_s2.py b/tests/opcodes_s2.py index 4525049..b9d74d3 100644 --- a/tests/opcodes_s2.py +++ b/tests/opcodes_s2.py @@ -7,7 +7,7 @@ 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.opcodes_s2 import get_reg, get_imm, get_cond, arg_qualify, parse_int, eval_arg, ARG, REG, IMM, SYM, COND from esp32_ulp.assemble import SymbolTable, ABS, REL, TEXT import esp32_ulp.opcodes_s2 as opcodes @@ -46,6 +46,7 @@ def test_arg_qualify(): 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('0100') == ARG(IMM, 64, '0100') assert arg_qualify('0o100') == ARG(IMM, 64, '0o100') assert arg_qualify('0b1000') == ARG(IMM, 8, '0b1000') assert arg_qualify('eq') == ARG(COND, 'eq', 'eq') @@ -96,6 +97,11 @@ def test_eval_arg(): assert eval_arg('const >> 1') == 21 assert eval_arg('(const|4)&0xf') == 0xe + assert eval_arg('0x7') == 7 + assert eval_arg('010') == 8 + assert eval_arg('-0x7') == -7 # negative + assert eval_arg('~0x7') == -8 # complement + assert_raises(ValueError, eval_arg, 'evil()') assert_raises(ValueError, eval_arg, 'def cafe()') assert_raises(ValueError, eval_arg, '1 ^ 2') @@ -105,14 +111,17 @@ def test_eval_arg(): opcodes.symbols = None -def assert_raises(exception, func, *args): +def assert_raises(exception, func, *args, message=None): try: func(*args) - except exception: + except exception as e: raised = True + actual_message = e.args[0] else: raised = False assert raised + if message: + assert actual_message == message, '%s == %s' % (actual_message, message) def test_reg_direct_ulp_addressing(): diff --git a/tests/util.py b/tests/util.py index 6aadc8b..5f487ae 100644 --- a/tests/util.py +++ b/tests/util.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MIT import os -from esp32_ulp.util import split_tokens, validate_expression, file_exists +from esp32_ulp.util import split_tokens, validate_expression, parse_int, file_exists tests = [] @@ -18,6 +18,19 @@ def test(param): tests.append(param) +def assert_raises(exception, func, *args, message=None): + try: + func(*args) + except exception as e: + raised = True + actual_message = e.args[0] + else: + raised = False + assert raised + if message: + assert actual_message == message, '%s == %s' % (actual_message, message) + + @test def test_split_tokens(): assert split_tokens("") == [] @@ -69,6 +82,36 @@ def test_validate_expression(): assert validate_expression('def CAFE()') is False +@test +def test_parse_int(): + # decimal + assert parse_int("0") == 0, "0 == 0" + assert parse_int("5") == 5, "5 == 5" + assert parse_int("-0") == 0, "-0 == 0" + assert parse_int("-5") == -5, "-5 == -5" + # hex + assert parse_int("0x5") == 5, "0x5 == 5" + assert parse_int("0x5a") == 90, "0x5a == 90" + assert parse_int("-0x5a") == -90, "-0x5a == -90" + # binary + assert parse_int("0b1001") == 9, "0b1001 == 9" + assert parse_int("-0b1001") == -9, "-0b1001 == 9" + # octal + assert parse_int("07") == 7, "07 == 7" + assert parse_int("0100") == 64, "0100 == 64" + assert parse_int("0o210") == 136, "0o210 == 136" + assert parse_int("00000010") == 8, "00000010 == 8" + assert parse_int("-07") == -7, "-07 == -7" + assert parse_int("-0100") == -64, "-0100 == -64" + assert parse_int("-0o210") == -136, "-0o210 == -136" + assert parse_int("-00000010") == -8, "-00000010 == -8" + # negative cases + assert_raises(ValueError, parse_int, '0b123', message="invalid syntax for integer with base 2: '123'") + assert_raises(ValueError, parse_int, '0900', message="invalid syntax for integer with base 8: '0900'") + assert_raises(ValueError, parse_int, '0o900', message="invalid syntax for integer with base 8: '900'") + assert_raises(ValueError, parse_int, '0xg', message="invalid syntax for integer with base 16: 'g'") + + @test def test_file_exists(): testfile = '.testfile'