Skip to content

Commit 5d63f25

Browse files
committed
Merge branch 'feature/bitscrambler_add_c5_insn' into 'master'
BitScrambler: Add support for addcti instruction as found in ESP32-C5 See merge request espressif/esp-idf!36906
2 parents b3c4c58 + 3aaaa22 commit 5d63f25

File tree

12 files changed

+170
-22
lines changed

12 files changed

+170
-22
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"chipname": "esp32c5",
3+
"extra_instruction_groups": [
4+
"addcti"
5+
]
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"chipname": "esp32p4",
3+
"extra_instruction_groups": [
4+
]
5+
}

components/esp_driver_bitscrambler/project_include.cmake

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# target_bitscrambler_add_src
22
#
33
# Assemble BitScrambler sources and embed into the application.
4+
5+
# This info is not available within the target_bitscrambler_add_src
6+
# function, so save it to a global.
7+
set(esp_bitscrambler_driver_component_path ${CMAKE_CURRENT_LIST_DIR})
8+
49
function(target_bitscrambler_add_src s_sources)
510
if(NOT CMAKE_BUILD_EARLY_EXPANSION)
11+
idf_build_get_property(target IDF_TARGET)
612
spaces2list(s_sources)
713
foreach(source ${s_sources})
814
get_filename_component(source ${source} ABSOLUTE BASE_DIR ${CMAKE_CURRENT_LIST_DIR})
@@ -11,7 +17,8 @@ function(target_bitscrambler_add_src s_sources)
1117
idf_build_get_property(python PYTHON)
1218
idf_build_get_property(idf_path IDF_PATH)
1319
add_custom_command(OUTPUT ${ps_output} DEPENDS ${source}
14-
COMMAND ${python} ${idf_path}/tools/bsasm.py ${source} ${ps_output})
20+
COMMAND ${python} ${idf_path}/tools/bsasm.py ${source} ${ps_output}
21+
"-c" ${esp_bitscrambler_driver_component_path}/bsasm_targets/${target}.json)
1522
target_add_binary_data(${COMPONENT_LIB} ${ps_output} BINARY RENAME_TO bitscrambler_program_${basename})
1623
endforeach()
1724
endif()

docs/en/api-reference/peripherals/bitscrambler.rst

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,26 @@ Sub-instructions
7777

7878
An opcode - The opcodes are fully documented in the Technical Reference manual; here's a summary.
7979

80-
- ``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.
81-
- ``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.
82-
- ``IF[N] source_bit tgt`` - If the source bit `source_bit` is one (for IF) or zero (for IFN), jump to the label ``tgt``.
83-
- ``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.
84-
- ``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.
85-
- ``JMP tgt`` - Unconditional jump to label ``tgt``. This is equal to ``IF h tgt``.
86-
- ``NOP`` - No operation. This is equal to ``ADDA 0``.
80+
.. only:: esp32p4
81+
82+
- ``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.
83+
- ``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.
84+
- ``IF[N] source_bit tgt`` - If the source bit `source_bit` is one (for IF) or zero (for IFN), jump to the label ``tgt``.
85+
- ``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.
86+
- ``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.
87+
- ``JMP tgt`` - Unconditional jump to label ``tgt``. This is equal to ``IF h tgt``.
88+
- ``NOP`` - No operation. This is equal to ``ADDA 0``.
89+
90+
.. only:: esp32c5
91+
92+
- ``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.
93+
- ``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.
94+
- ``IF[N] source_bit tgt`` - If the source bit `source_bit` is one (for IF) or zero (for IFN), jump to the label ``tgt``.
95+
- ``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.
96+
- ``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.
97+
- ``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.
98+
- ``JMP tgt`` - Unconditional jump to label ``tgt``. This is equal to ``IF h tgt``.
99+
- ``NOP`` - No operation. This is equal to ``ADDA 0``.
87100

88101
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.
89102

tools/bsasm.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# SPDX-License-Identifier: Apache-2.0
44
import argparse
55
import copy
6+
import json
67
import math
78
import re
89
import struct
@@ -66,6 +67,12 @@ class Inst(TypedDict, total=False):
6667
read: int
6768

6869

70+
class Chipcfg(TypedDict, total=False):
71+
chipname: str
72+
extra_instruction_groups: List[str]
73+
support_all: bool
74+
75+
6976
# Parser.
7077
# A bsasm file consists of labels, instruction bundles, meta-instructions
7178
# and comments. Comments start at a # and run to a newline and will be
@@ -104,6 +111,7 @@ class Inst(TypedDict, total=False):
104111
# soul tasked with fixing up this code, feel free to create an issue to
105112
# rewrite this and assign it to me - Jeroen)
106113

114+
107115
def bsasm_parse(src: str) -> List[Element]:
108116
# Small hack: we trigger processing things on a newline. If a file is read without
109117
# a newline at the end of the last instruction, we'd erroneously ignore the last element.
@@ -548,6 +556,18 @@ def resolve_label(ele: Element, text: str, labels: Dict[str, int]) -> int:
548556
OP_IFN = 0x0020000
549557
OP_LDCTD = 0x0030000
550558
OP_LDCTI = 0x0040000
559+
OP_ADDCTI = 0x0050000
560+
561+
562+
def check_chip_supports_inst(chipcfg: Chipcfg, instgroup: str, ele: Element) -> None:
563+
if 'support_all' in chipcfg and chipcfg['support_all']:
564+
return
565+
566+
if instgroup not in chipcfg['extra_instruction_groups']:
567+
name = chipcfg['chipname']
568+
raise bsasm_syntax_error(
569+
ele, f'Chip {name} does not support this instruction'
570+
)
551571

552572

553573
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:
561581

562582
# Takes the elements generated by the parse routine and converts it to a
563583
# representation of the bits in the Bitscrambler program.
564-
def bsasm_assemble(elements: List[Element]) -> Tuple[List[Inst], Dict[str, int], List[int]]:
584+
def bsasm_assemble(elements: List[Element], chipcfg: Chipcfg) -> Tuple[List[Inst], Dict[str, int], List[int]]:
565585
# This assembler uses two passes: the first finds and resolves global
566586
# stuff, the second one encodes the actual instructions.
567587

@@ -739,6 +759,19 @@ def bsasm_assemble(elements: List[Element]) -> Tuple[List[Inst], Dict[str, int],
739759
op['h'] = 1 if words[0][6] == 'h' else 0
740760
op['l'] = 1 if words[0][6] == 'l' else 0
741761
add_op_to_inst(inst, op, ele)
762+
elif re.match('addcti[ab]([hl])?$', words[0]):
763+
# ADDCTIc[h|l]
764+
check_chip_supports_inst(chipcfg, 'addcti', ele)
765+
check_arg_ct(ele, words, 1)
766+
op = {'op': OP_ADDCTI}
767+
op['c'] = 1 if words[0][6] == 'b' else 0
768+
if len(words[0]) == 7:
769+
op['h'] = 1
770+
op['l'] = 1
771+
else:
772+
op['h'] = 1 if words[0][7] == 'h' else 0
773+
op['l'] = 1 if words[0][7] == 'l' else 0
774+
add_op_to_inst(inst, op, ele)
742775
elif re.match('jmp', words[0]):
743776
# JMP tgt. Pseudo-op, translates to 'IF h tgt'
744777
check_arg_ct(ele, words, 2)
@@ -955,15 +988,24 @@ def write_file(filename: str, data: bytearray) -> None:
955988
description='BitScrambler program assembler')
956989
parser.add_argument('infile', help='File name of assembly source to be assembled into a binary')
957990
parser.add_argument('outfile', help='File name of output binary', nargs='?', default=argparse.SUPPRESS)
991+
parser.add_argument('-c', help='Set chip capabilities json file; if set, returns an error when \
992+
an unsupported instruction is assembled', default=argparse.SUPPRESS)
958993
args = parser.parse_args()
959994

995+
chipcfg = Chipcfg()
996+
if 'c' in args:
997+
with open(args.c) as chipcfg_json:
998+
chipcfg = json.load(chipcfg_json)
999+
else:
1000+
chipcfg = {'chipname': 'chip', 'extra_instruction_groups': [], 'support_all': True}
1001+
9601002
if 'outfile' in args:
9611003
outfile = args.outfile
9621004
else:
9631005
outfile = re.sub('.bsasm', '', args.infile) + '.bsbin'
9641006
asm = read_file(args.infile)
9651007
tokens = bsasm_parse(asm)
966-
insts, meta, lut = bsasm_assemble(tokens)
1008+
insts, meta, lut = bsasm_assemble(tokens, chipcfg)
9671009
out_data = insts_to_binary(insts, meta, lut)
9681010
write_file(outfile, out_data)
9691011
print(f'Written {len(insts)} instructions and {len(lut)} 32-bit words of LUT.')

tools/test_bsasm/test_bsasm.py

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
# SPDX-License-Identifier: Apache-2.0
66
import json
77
import os
8+
import re
9+
import shlex
810
import struct
911
import subprocess
1012
import tempfile
@@ -106,6 +108,8 @@ def decode_inst(self, data: Tuple[int, int]) -> DecompInst:
106108
op['ctr_set'] = opcode & 0xFFFF
107109
elif sub == 4:
108110
op['op'] = 'LDCTI' + fl
111+
elif sub == 5:
112+
op['op'] = 'ADDCTI' + fl
109113
ret['opcode'] = op
110114
ret['read_in'] = self.bits_from_inst(data, 250, 2)
111115
ret['wr_out'] = self.bits_from_inst(data, 252, 2)
@@ -160,22 +164,44 @@ def test_examples(self) -> None:
160164
testfiles.append(os.path.join(current_dir, 'testcases', f))
161165
for f in testfiles:
162166
print(f'Testing {f}...')
167+
# Extract testing options in the form '#test: key = value'
168+
cmdlineopts = []
169+
should_fail = False
170+
pattern = r'^\s*#\s*test:\s*([^\s=]+)\s*=\s*(.*)\s*$'
171+
with open(f) as tf:
172+
for line in tf:
173+
match = re.match(pattern, line)
174+
if match:
175+
if match.group(1) == 'should_fail':
176+
if match.group(2) in ['1', 'true', 'True', 'TRUE']:
177+
should_fail = True
178+
elif match.group(1) == 'cmdlineopts':
179+
cmdlineopts = shlex.split(match.group(2))
180+
else:
181+
self.fail(f'Unknown test option: {match.group(0)}')
182+
# Generate temp filename and assemble
163183
with tempfile.NamedTemporaryFile(delete=False) as f_out:
164184
self.addCleanup(os.unlink, f_out.name)
165185
args = [bsasm_path, f, f_out.name]
186+
args.extend(cmdlineopts)
166187
p = subprocess.run(args, timeout=10)
167-
self.assertEqual(p.returncode, 0)
168-
b = self.unpack_binary(f_out.name)
169-
170-
jsfn = f[:-6] + '.json'
171-
try:
172-
with open(jsfn) as out_desc_f:
173-
out_desc = json.load(out_desc_f)
174-
# We were able to open the JSON file. See if the keys in it match up with the ones in the decoded fields.
175-
self.compare(b, out_desc, '')
176-
except FileNotFoundError:
177-
print(f'File not found: {jsfn}. Printing out decoded contents instead.')
178-
print(json.dumps(b, indent=4))
188+
if not should_fail:
189+
self.assertEqual(p.returncode, 0)
190+
else:
191+
print('Note: THE TEST EXPECTED BSASM TO ERROR OUT. If there\'s error text above, that is expected.')
192+
self.assertNotEqual(p.returncode, 0)
193+
194+
if not should_fail:
195+
b = self.unpack_binary(f_out.name)
196+
jsfn = f[:-6] + '.json'
197+
try:
198+
with open(jsfn) as out_desc_f:
199+
out_desc = json.load(out_desc_f)
200+
# We were able to open the JSON file. See if the keys in it match up with the ones in the decoded fields.
201+
self.compare(b, out_desc, '')
202+
except FileNotFoundError:
203+
print(f'File not found: {jsfn}. Printing out decoded contents instead.')
204+
print(json.dumps(b, indent=4))
179205

180206

181207
if __name__ == '__main__':

tools/test_bsasm/testcases/opcodes.bsasm renamed to tools/test_bsasm/testcases/opcodes1.bsasm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
2+
# SPDX-License-Identifier: Apache-2.0
13
#Test all opcodes
24

35
cfg trailing_bytes 0 #End program as soon as the input EOFs.
File renamed without changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
2+
# SPDX-License-Identifier: Apache-2.0
3+
#Test all opcodes
4+
5+
cfg trailing_bytes 0 #End program as soon as the input EOFs.
6+
cfg prefetch true #We expect M0/M1 to be filled
7+
cfg lut_width_bits 8 #Not really applicable here
8+
9+
main:
10+
ADDCTIA
11+
ADDCTIBH
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"binary_ver": 1,
3+
"hw_rev": 0,
4+
"hdr_len": 3,
5+
"inst_ct": 2,
6+
"inst": [
7+
{
8+
"opcode": {
9+
"op": "ADDCTIA"
10+
}
11+
},
12+
{
13+
"opcode": {
14+
"op": "ADDCTIBH"
15+
}
16+
}
17+
]
18+
}

0 commit comments

Comments
 (0)