mirror of
https://github.com/espressif/esp-idf
synced 2025-03-08 23:59:08 -05:00
feat: BitScrambler: Add support for addcti instruction as found in ESP32-C5
This commit is contained in:
parent
68b79fc138
commit
3aaaa2203a
@ -0,0 +1,6 @@
|
||||
{
|
||||
"chipname": "esp32c5",
|
||||
"extra_instruction_groups": [
|
||||
"addcti"
|
||||
]
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"chipname": "esp32p4",
|
||||
"extra_instruction_groups": [
|
||||
]
|
||||
}
|
@ -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()
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.')
|
||||
|
@ -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__':
|
||||
|
@ -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.
|
11
tools/test_bsasm/testcases/opcodes2.bsasm
Normal file
11
tools/test_bsasm/testcases/opcodes2.bsasm
Normal 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
|
18
tools/test_bsasm/testcases/opcodes2.json
Normal file
18
tools/test_bsasm/testcases/opcodes2.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"binary_ver": 1,
|
||||
"hw_rev": 0,
|
||||
"hdr_len": 3,
|
||||
"inst_ct": 2,
|
||||
"inst": [
|
||||
{
|
||||
"opcode": {
|
||||
"op": "ADDCTIA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"opcode": {
|
||||
"op": "ADDCTIBH"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
13
tools/test_bsasm/testcases/unsupported-inst.bsasm
Normal file
13
tools/test_bsasm/testcases/unsupported-inst.bsasm
Normal 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
|
5
tools/test_bsasm/unsupported.json
Normal file
5
tools/test_bsasm/unsupported.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"chipname": "esp32_basic_chip",
|
||||
"extra_instruction_groups": [
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user