From 3aaaa2203a6463d8d28a8a51540ca35ac82f0e6a Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 12 Feb 2025 16:34:14 +0800 Subject: [PATCH] feat: BitScrambler: Add support for addcti instruction as found in ESP32-C5 --- .../bsasm_targets/esp32c5.json | 6 +++ .../bsasm_targets/esp32p4.json | 5 ++ .../project_include.cmake | 9 +++- .../peripherals/bitscrambler.rst | 27 ++++++++--- tools/bsasm.py | 46 +++++++++++++++++- tools/test_bsasm/test_bsasm.py | 48 ++++++++++++++----- .../{opcodes.bsasm => opcodes1.bsasm} | 2 + .../testcases/{opcodes.json => opcodes1.json} | 0 tools/test_bsasm/testcases/opcodes2.bsasm | 11 +++++ tools/test_bsasm/testcases/opcodes2.json | 18 +++++++ .../testcases/unsupported-inst.bsasm | 13 +++++ tools/test_bsasm/unsupported.json | 5 ++ 12 files changed, 169 insertions(+), 21 deletions(-) create mode 100644 components/esp_driver_bitscrambler/bsasm_targets/esp32c5.json create mode 100644 components/esp_driver_bitscrambler/bsasm_targets/esp32p4.json rename tools/test_bsasm/testcases/{opcodes.bsasm => opcodes1.bsasm} (72%) rename tools/test_bsasm/testcases/{opcodes.json => opcodes1.json} (100%) create mode 100644 tools/test_bsasm/testcases/opcodes2.bsasm create mode 100644 tools/test_bsasm/testcases/opcodes2.json create mode 100644 tools/test_bsasm/testcases/unsupported-inst.bsasm create mode 100644 tools/test_bsasm/unsupported.json diff --git a/components/esp_driver_bitscrambler/bsasm_targets/esp32c5.json b/components/esp_driver_bitscrambler/bsasm_targets/esp32c5.json new file mode 100644 index 0000000000..9a51c75058 --- /dev/null +++ b/components/esp_driver_bitscrambler/bsasm_targets/esp32c5.json @@ -0,0 +1,6 @@ +{ + "chipname": "esp32c5", + "extra_instruction_groups": [ + "addcti" + ] +} diff --git a/components/esp_driver_bitscrambler/bsasm_targets/esp32p4.json b/components/esp_driver_bitscrambler/bsasm_targets/esp32p4.json new file mode 100644 index 0000000000..36efa97fb9 --- /dev/null +++ b/components/esp_driver_bitscrambler/bsasm_targets/esp32p4.json @@ -0,0 +1,5 @@ +{ + "chipname": "esp32p4", + "extra_instruction_groups": [ + ] +} diff --git a/components/esp_driver_bitscrambler/project_include.cmake b/components/esp_driver_bitscrambler/project_include.cmake index 92daa4826c..cbd73832e2 100644 --- a/components/esp_driver_bitscrambler/project_include.cmake +++ b/components/esp_driver_bitscrambler/project_include.cmake @@ -1,8 +1,14 @@ # target_bitscrambler_add_src # # Assemble BitScrambler sources and embed into the application. + +# This info is not available within the target_bitscrambler_add_src +# function, so save it to a global. +set(esp_bitscrambler_driver_component_path ${CMAKE_CURRENT_LIST_DIR}) + function(target_bitscrambler_add_src s_sources) if(NOT CMAKE_BUILD_EARLY_EXPANSION) + idf_build_get_property(target IDF_TARGET) spaces2list(s_sources) foreach(source ${s_sources}) get_filename_component(source ${source} ABSOLUTE BASE_DIR ${CMAKE_CURRENT_LIST_DIR}) @@ -11,7 +17,8 @@ function(target_bitscrambler_add_src s_sources) idf_build_get_property(python PYTHON) idf_build_get_property(idf_path IDF_PATH) add_custom_command(OUTPUT ${ps_output} DEPENDS ${source} - COMMAND ${python} ${idf_path}/tools/bsasm.py ${source} ${ps_output}) + COMMAND ${python} ${idf_path}/tools/bsasm.py ${source} ${ps_output} + "-c" ${esp_bitscrambler_driver_component_path}/bsasm_targets/${target}.json) target_add_binary_data(${COMPONENT_LIB} ${ps_output} BINARY RENAME_TO bitscrambler_program_${basename}) endforeach() endif() diff --git a/docs/en/api-reference/peripherals/bitscrambler.rst b/docs/en/api-reference/peripherals/bitscrambler.rst index eb3d0a536e..66bbbaea5f 100644 --- a/docs/en/api-reference/peripherals/bitscrambler.rst +++ b/docs/en/api-reference/peripherals/bitscrambler.rst @@ -77,13 +77,26 @@ Sub-instructions An opcode - The opcodes are fully documented in the Technical Reference manual; here's a summary. -- ``LOOP(A|B) end_val ctr_add tgt`` - If the selected counter (A or B) ls smaller than end_val, add ``ctr_add`` to the selected counter (A or B) and jump to the label ``tgt``. If not, continue execution. -- ``ADD(A|B)[H|L] val`` - Add ``val`` to the selected counter. If 'H' or 'L' is appended, only the high or low 8-bit, respectively, of the counter is written back. -- ``IF[N] source_bit tgt`` - If the source bit `source_bit` is one (for IF) or zero (for IFN), jump to the label ``tgt``. -- ``LDCTD(A|B)[H|L] val`` - Load ``val`` into the indicated counter. If H or L is appended, only the high or low 8-bit, respectively, will be updated. -- ``LDCTI(A|B)[H|L]`` - Load the indicated counter (A or B) with bits 16-31 sent to the output register. If H or L is appended, only the high or low 8-bit, respectively, will be updated. -- ``JMP tgt`` - Unconditional jump to label ``tgt``. This is equal to ``IF h tgt``. -- ``NOP`` - No operation. This is equal to ``ADDA 0``. +.. only:: esp32p4 + + - ``LOOP(A|B) end_val ctr_add tgt`` - If the selected counter (A or B) ls smaller than end_val, add ``ctr_add`` to the selected counter (A or B) and jump to the label ``tgt``. If not, continue execution. + - ``ADD(A|B)[H|L] val`` - Add ``val`` to the selected counter. If 'H' or 'L' is appended, only the high or low 8-bit, respectively, of the counter is written back. + - ``IF[N] source_bit tgt`` - If the source bit `source_bit` is one (for IF) or zero (for IFN), jump to the label ``tgt``. + - ``LDCTD(A|B)[H|L] val`` - Load ``val`` into the indicated counter. If H or L is appended, only the high or low 8-bit, respectively, will be updated. + - ``LDCTI(A|B)[H|L]`` - Load the indicated counter (A or B) with bits 16-31 sent to the output register. If H or L is appended, only the high or low 8-bit, respectively, will be updated. + - ``JMP tgt`` - Unconditional jump to label ``tgt``. This is equal to ``IF h tgt``. + - ``NOP`` - No operation. This is equal to ``ADDA 0``. + +.. only:: esp32c5 + + - ``LOOP(A|B) end_val ctr_add tgt`` - If the selected counter (A or B) ls smaller than end_val, add ``ctr_add`` to the selected counter (A or B) and jump to the label ``tgt``. If not, continue execution. + - ``ADD(A|B)[H|L] val`` - Add ``val`` to the selected counter. If 'H' or 'L' is appended, only the high or low 8-bit, respectively, of the counter is written back. + - ``IF[N] source_bit tgt`` - If the source bit `source_bit` is one (for IF) or zero (for IFN), jump to the label ``tgt``. + - ``LDCTD(A|B)[H|L] val`` - Load ``val`` into the indicated counter. If H or L is appended, only the high or low 8-bit, respectively, will be updated. + - ``LDCTI(A|B)[H|L]`` - Load the indicated counter (A or B) with bits 16-31 sent to the output register. If H or L is appended, only the high or low 8-bit, respectively, will be updated. + - ``ADDCTI(A|B)[H|L]`` - Add bits 16-31 sent to the output register to the indicated counter (A or B) . If H or L is appended, only the high or low 8-bit, respectively, will be evaluated and updated. + - ``JMP tgt`` - Unconditional jump to label ``tgt``. This is equal to ``IF h tgt``. + - ``NOP`` - No operation. This is equal to ``ADDA 0``. Note that an instruction bundle can only contain one opcode, one ``read``, and one ``write``. It can contain multiple ``set`` instructions, although multiple ``set`` instruction cannot assign a value to the same output bits. diff --git a/tools/bsasm.py b/tools/bsasm.py index 1ebdf21535..2eb4a63547 100755 --- a/tools/bsasm.py +++ b/tools/bsasm.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 import argparse import copy +import json import math import re import struct @@ -66,6 +67,12 @@ class Inst(TypedDict, total=False): read: int +class Chipcfg(TypedDict, total=False): + chipname: str + extra_instruction_groups: List[str] + support_all: bool + + # Parser. # A bsasm file consists of labels, instruction bundles, meta-instructions # and comments. Comments start at a # and run to a newline and will be @@ -104,6 +111,7 @@ class Inst(TypedDict, total=False): # soul tasked with fixing up this code, feel free to create an issue to # rewrite this and assign it to me - Jeroen) + def bsasm_parse(src: str) -> List[Element]: # Small hack: we trigger processing things on a newline. If a file is read without # a newline at the end of the last instruction, we'd erroneously ignore the last element. @@ -548,6 +556,18 @@ OP_IF = 0x0010000 OP_IFN = 0x0020000 OP_LDCTD = 0x0030000 OP_LDCTI = 0x0040000 +OP_ADDCTI = 0x0050000 + + +def check_chip_supports_inst(chipcfg: Chipcfg, instgroup: str, ele: Element) -> None: + if 'support_all' in chipcfg and chipcfg['support_all']: + return + + if instgroup not in chipcfg['extra_instruction_groups']: + name = chipcfg['chipname'] + raise bsasm_syntax_error( + ele, f'Chip {name} does not support this instruction' + ) def add_op_to_inst(inst: Inst, op: Opcode, ele: Element) -> None: @@ -561,7 +581,7 @@ def add_op_to_inst(inst: Inst, op: Opcode, ele: Element) -> None: # Takes the elements generated by the parse routine and converts it to a # representation of the bits in the Bitscrambler program. -def bsasm_assemble(elements: List[Element]) -> Tuple[List[Inst], Dict[str, int], List[int]]: +def bsasm_assemble(elements: List[Element], chipcfg: Chipcfg) -> Tuple[List[Inst], Dict[str, int], List[int]]: # This assembler uses two passes: the first finds and resolves global # stuff, the second one encodes the actual instructions. @@ -739,6 +759,19 @@ def bsasm_assemble(elements: List[Element]) -> Tuple[List[Inst], Dict[str, int], op['h'] = 1 if words[0][6] == 'h' else 0 op['l'] = 1 if words[0][6] == 'l' else 0 add_op_to_inst(inst, op, ele) + elif re.match('addcti[ab]([hl])?$', words[0]): + # ADDCTIc[h|l] + check_chip_supports_inst(chipcfg, 'addcti', ele) + check_arg_ct(ele, words, 1) + op = {'op': OP_ADDCTI} + op['c'] = 1 if words[0][6] == 'b' else 0 + if len(words[0]) == 7: + op['h'] = 1 + op['l'] = 1 + else: + op['h'] = 1 if words[0][7] == 'h' else 0 + op['l'] = 1 if words[0][7] == 'l' else 0 + add_op_to_inst(inst, op, ele) elif re.match('jmp', words[0]): # JMP tgt. Pseudo-op, translates to 'IF h tgt' check_arg_ct(ele, words, 2) @@ -955,15 +988,24 @@ if __name__ == '__main__': description='BitScrambler program assembler') parser.add_argument('infile', help='File name of assembly source to be assembled into a binary') parser.add_argument('outfile', help='File name of output binary', nargs='?', default=argparse.SUPPRESS) + parser.add_argument('-c', help='Set chip capabilities json file; if set, returns an error when \ + an unsupported instruction is assembled', default=argparse.SUPPRESS) args = parser.parse_args() + chipcfg = Chipcfg() + if 'c' in args: + with open(args.c) as chipcfg_json: + chipcfg = json.load(chipcfg_json) + else: + chipcfg = {'chipname': 'chip', 'extra_instruction_groups': [], 'support_all': True} + if 'outfile' in args: outfile = args.outfile else: outfile = re.sub('.bsasm', '', args.infile) + '.bsbin' asm = read_file(args.infile) tokens = bsasm_parse(asm) - insts, meta, lut = bsasm_assemble(tokens) + insts, meta, lut = bsasm_assemble(tokens, chipcfg) out_data = insts_to_binary(insts, meta, lut) write_file(outfile, out_data) print(f'Written {len(insts)} instructions and {len(lut)} 32-bit words of LUT.') diff --git a/tools/test_bsasm/test_bsasm.py b/tools/test_bsasm/test_bsasm.py index 017e8ca22c..275aeb0a83 100755 --- a/tools/test_bsasm/test_bsasm.py +++ b/tools/test_bsasm/test_bsasm.py @@ -5,6 +5,8 @@ # SPDX-License-Identifier: Apache-2.0 import json import os +import re +import shlex import struct import subprocess import tempfile @@ -106,6 +108,8 @@ class TestAssembler(unittest.TestCase): op['ctr_set'] = opcode & 0xFFFF elif sub == 4: op['op'] = 'LDCTI' + fl + elif sub == 5: + op['op'] = 'ADDCTI' + fl ret['opcode'] = op ret['read_in'] = self.bits_from_inst(data, 250, 2) ret['wr_out'] = self.bits_from_inst(data, 252, 2) @@ -160,22 +164,44 @@ class TestAssembler(unittest.TestCase): testfiles.append(os.path.join(current_dir, 'testcases', f)) for f in testfiles: print(f'Testing {f}...') + # Extract testing options in the form '#test: key = value' + cmdlineopts = [] + should_fail = False + pattern = r'^\s*#\s*test:\s*([^\s=]+)\s*=\s*(.*)\s*$' + with open(f) as tf: + for line in tf: + match = re.match(pattern, line) + if match: + if match.group(1) == 'should_fail': + if match.group(2) in ['1', 'true', 'True', 'TRUE']: + should_fail = True + elif match.group(1) == 'cmdlineopts': + cmdlineopts = shlex.split(match.group(2)) + else: + self.fail(f'Unknown test option: {match.group(0)}') + # Generate temp filename and assemble with tempfile.NamedTemporaryFile(delete=False) as f_out: self.addCleanup(os.unlink, f_out.name) args = [bsasm_path, f, f_out.name] + args.extend(cmdlineopts) p = subprocess.run(args, timeout=10) - self.assertEqual(p.returncode, 0) - b = self.unpack_binary(f_out.name) + if not should_fail: + self.assertEqual(p.returncode, 0) + else: + print('Note: THE TEST EXPECTED BSASM TO ERROR OUT. If there\'s error text above, that is expected.') + self.assertNotEqual(p.returncode, 0) - jsfn = f[:-6] + '.json' - try: - with open(jsfn) as out_desc_f: - out_desc = json.load(out_desc_f) - # We were able to open the JSON file. See if the keys in it match up with the ones in the decoded fields. - self.compare(b, out_desc, '') - except FileNotFoundError: - print(f'File not found: {jsfn}. Printing out decoded contents instead.') - print(json.dumps(b, indent=4)) + if not should_fail: + b = self.unpack_binary(f_out.name) + jsfn = f[:-6] + '.json' + try: + with open(jsfn) as out_desc_f: + out_desc = json.load(out_desc_f) + # We were able to open the JSON file. See if the keys in it match up with the ones in the decoded fields. + self.compare(b, out_desc, '') + except FileNotFoundError: + print(f'File not found: {jsfn}. Printing out decoded contents instead.') + print(json.dumps(b, indent=4)) if __name__ == '__main__': diff --git a/tools/test_bsasm/testcases/opcodes.bsasm b/tools/test_bsasm/testcases/opcodes1.bsasm similarity index 72% rename from tools/test_bsasm/testcases/opcodes.bsasm rename to tools/test_bsasm/testcases/opcodes1.bsasm index de9f0db165..66afe07338 100644 --- a/tools/test_bsasm/testcases/opcodes.bsasm +++ b/tools/test_bsasm/testcases/opcodes1.bsasm @@ -1,3 +1,5 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 #Test all opcodes cfg trailing_bytes 0 #End program as soon as the input EOFs. diff --git a/tools/test_bsasm/testcases/opcodes.json b/tools/test_bsasm/testcases/opcodes1.json similarity index 100% rename from tools/test_bsasm/testcases/opcodes.json rename to tools/test_bsasm/testcases/opcodes1.json diff --git a/tools/test_bsasm/testcases/opcodes2.bsasm b/tools/test_bsasm/testcases/opcodes2.bsasm new file mode 100644 index 0000000000..3ee7f3f5b9 --- /dev/null +++ b/tools/test_bsasm/testcases/opcodes2.bsasm @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +#Test all opcodes + +cfg trailing_bytes 0 #End program as soon as the input EOFs. +cfg prefetch true #We expect M0/M1 to be filled +cfg lut_width_bits 8 #Not really applicable here + +main: + ADDCTIA + ADDCTIBH diff --git a/tools/test_bsasm/testcases/opcodes2.json b/tools/test_bsasm/testcases/opcodes2.json new file mode 100644 index 0000000000..ef22e82aef --- /dev/null +++ b/tools/test_bsasm/testcases/opcodes2.json @@ -0,0 +1,18 @@ +{ + "binary_ver": 1, + "hw_rev": 0, + "hdr_len": 3, + "inst_ct": 2, + "inst": [ + { + "opcode": { + "op": "ADDCTIA" + } + }, + { + "opcode": { + "op": "ADDCTIBH" + } + } + ] +} diff --git a/tools/test_bsasm/testcases/unsupported-inst.bsasm b/tools/test_bsasm/testcases/unsupported-inst.bsasm new file mode 100644 index 0000000000..36c538c3f5 --- /dev/null +++ b/tools/test_bsasm/testcases/unsupported-inst.bsasm @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +#Test opcode not supported selected chip + +#test: should_fail=true +#test: cmdlineopts=-c unsupported.json + +cfg trailing_bytes 0 #End program as soon as the input EOFs. +cfg prefetch true #We expect M0/M1 to be filled +cfg lut_width_bits 8 #Not really applicable here + +main: + ADDCTIA diff --git a/tools/test_bsasm/unsupported.json b/tools/test_bsasm/unsupported.json new file mode 100644 index 0000000000..d08aebd868 --- /dev/null +++ b/tools/test_bsasm/unsupported.json @@ -0,0 +1,5 @@ +{ + "chipname": "esp32_basic_chip", + "extra_instruction_groups": [ + ] +}