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
This commit is contained in:
Jeroen Domburg 2025-02-18 14:39:42 +08:00
commit 5d63f251f9
12 changed files with 169 additions and 21 deletions

View File

@ -0,0 +1,6 @@
{
"chipname": "esp32c5",
"extra_instruction_groups": [
"addcti"
]
}

View File

@ -0,0 +1,5 @@
{
"chipname": "esp32p4",
"extra_instruction_groups": [
]
}

View File

@ -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()

View File

@ -77,6 +77,8 @@ Sub-instructions
An opcode - The opcodes are fully documented in the Technical Reference manual; here's a summary.
.. 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``.
@ -85,6 +87,17 @@ An opcode - The opcodes are fully documented in the Technical Reference manual;
- ``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.
Source bits

View File

@ -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.')

View File

@ -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,13 +164,35 @@ 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)
if not should_fail:
self.assertEqual(p.returncode, 0)
b = self.unpack_binary(f_out.name)
else:
print('Note: THE TEST EXPECTED BSASM TO ERROR OUT. If there\'s error text above, that is expected.')
self.assertNotEqual(p.returncode, 0)
if not should_fail:
b = self.unpack_binary(f_out.name)
jsfn = f[:-6] + '.json'
try:
with open(jsfn) as out_desc_f:

View File

@ -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.

View File

@ -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

View File

@ -0,0 +1,18 @@
{
"binary_ver": 1,
"hw_rev": 0,
"hdr_len": 3,
"inst_ct": 2,
"inst": [
{
"opcode": {
"op": "ADDCTIA"
}
},
{
"opcode": {
"op": "ADDCTIBH"
}
}
]
}

View File

@ -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

View File

@ -0,0 +1,5 @@
{
"chipname": "esp32_basic_chip",
"extra_instruction_groups": [
]
}