mirror of
https://github.com/espressif/esp-idf
synced 2025-03-09 17:19:09 -04:00
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:
commit
5d63f251f9
@ -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
|
# target_bitscrambler_add_src
|
||||||
#
|
#
|
||||||
# Assemble BitScrambler sources and embed into the application.
|
# 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)
|
function(target_bitscrambler_add_src s_sources)
|
||||||
if(NOT CMAKE_BUILD_EARLY_EXPANSION)
|
if(NOT CMAKE_BUILD_EARLY_EXPANSION)
|
||||||
|
idf_build_get_property(target IDF_TARGET)
|
||||||
spaces2list(s_sources)
|
spaces2list(s_sources)
|
||||||
foreach(source ${s_sources})
|
foreach(source ${s_sources})
|
||||||
get_filename_component(source ${source} ABSOLUTE BASE_DIR ${CMAKE_CURRENT_LIST_DIR})
|
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(python PYTHON)
|
||||||
idf_build_get_property(idf_path IDF_PATH)
|
idf_build_get_property(idf_path IDF_PATH)
|
||||||
add_custom_command(OUTPUT ${ps_output} DEPENDS ${source}
|
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})
|
target_add_binary_data(${COMPONENT_LIB} ${ps_output} BINARY RENAME_TO bitscrambler_program_${basename})
|
||||||
endforeach()
|
endforeach()
|
||||||
endif()
|
endif()
|
||||||
|
@ -77,13 +77,26 @@ Sub-instructions
|
|||||||
|
|
||||||
An opcode - The opcodes are fully documented in the Technical Reference manual; here's a summary.
|
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.
|
.. only:: esp32p4
|
||||||
- ``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``.
|
- ``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.
|
||||||
- ``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.
|
- ``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.
|
||||||
- ``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.
|
- ``IF[N] source_bit tgt`` - If the source bit `source_bit` is one (for IF) or zero (for IFN), jump to the label ``tgt``.
|
||||||
- ``JMP tgt`` - Unconditional jump to label ``tgt``. This is equal to ``IF h 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.
|
||||||
- ``NOP`` - No operation. This is equal to ``ADDA 0``.
|
- ``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.
|
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
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
import argparse
|
import argparse
|
||||||
import copy
|
import copy
|
||||||
|
import json
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
import struct
|
import struct
|
||||||
@ -66,6 +67,12 @@ class Inst(TypedDict, total=False):
|
|||||||
read: int
|
read: int
|
||||||
|
|
||||||
|
|
||||||
|
class Chipcfg(TypedDict, total=False):
|
||||||
|
chipname: str
|
||||||
|
extra_instruction_groups: List[str]
|
||||||
|
support_all: bool
|
||||||
|
|
||||||
|
|
||||||
# Parser.
|
# Parser.
|
||||||
# A bsasm file consists of labels, instruction bundles, meta-instructions
|
# A bsasm file consists of labels, instruction bundles, meta-instructions
|
||||||
# and comments. Comments start at a # and run to a newline and will be
|
# 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
|
# soul tasked with fixing up this code, feel free to create an issue to
|
||||||
# rewrite this and assign it to me - Jeroen)
|
# rewrite this and assign it to me - Jeroen)
|
||||||
|
|
||||||
|
|
||||||
def bsasm_parse(src: str) -> List[Element]:
|
def bsasm_parse(src: str) -> List[Element]:
|
||||||
# Small hack: we trigger processing things on a newline. If a file is read without
|
# 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.
|
# 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_IFN = 0x0020000
|
||||||
OP_LDCTD = 0x0030000
|
OP_LDCTD = 0x0030000
|
||||||
OP_LDCTI = 0x0040000
|
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:
|
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
|
# Takes the elements generated by the parse routine and converts it to a
|
||||||
# representation of the bits in the Bitscrambler program.
|
# 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
|
# This assembler uses two passes: the first finds and resolves global
|
||||||
# stuff, the second one encodes the actual instructions.
|
# 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['h'] = 1 if words[0][6] == 'h' else 0
|
||||||
op['l'] = 1 if words[0][6] == 'l' else 0
|
op['l'] = 1 if words[0][6] == 'l' else 0
|
||||||
add_op_to_inst(inst, op, ele)
|
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]):
|
elif re.match('jmp', words[0]):
|
||||||
# JMP tgt. Pseudo-op, translates to 'IF h tgt'
|
# JMP tgt. Pseudo-op, translates to 'IF h tgt'
|
||||||
check_arg_ct(ele, words, 2)
|
check_arg_ct(ele, words, 2)
|
||||||
@ -955,15 +988,24 @@ if __name__ == '__main__':
|
|||||||
description='BitScrambler program assembler')
|
description='BitScrambler program assembler')
|
||||||
parser.add_argument('infile', help='File name of assembly source to be assembled into a binary')
|
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('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()
|
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:
|
if 'outfile' in args:
|
||||||
outfile = args.outfile
|
outfile = args.outfile
|
||||||
else:
|
else:
|
||||||
outfile = re.sub('.bsasm', '', args.infile) + '.bsbin'
|
outfile = re.sub('.bsasm', '', args.infile) + '.bsbin'
|
||||||
asm = read_file(args.infile)
|
asm = read_file(args.infile)
|
||||||
tokens = bsasm_parse(asm)
|
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)
|
out_data = insts_to_binary(insts, meta, lut)
|
||||||
write_file(outfile, out_data)
|
write_file(outfile, out_data)
|
||||||
print(f'Written {len(insts)} instructions and {len(lut)} 32-bit words of LUT.')
|
print(f'Written {len(insts)} instructions and {len(lut)} 32-bit words of LUT.')
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -106,6 +108,8 @@ class TestAssembler(unittest.TestCase):
|
|||||||
op['ctr_set'] = opcode & 0xFFFF
|
op['ctr_set'] = opcode & 0xFFFF
|
||||||
elif sub == 4:
|
elif sub == 4:
|
||||||
op['op'] = 'LDCTI' + fl
|
op['op'] = 'LDCTI' + fl
|
||||||
|
elif sub == 5:
|
||||||
|
op['op'] = 'ADDCTI' + fl
|
||||||
ret['opcode'] = op
|
ret['opcode'] = op
|
||||||
ret['read_in'] = self.bits_from_inst(data, 250, 2)
|
ret['read_in'] = self.bits_from_inst(data, 250, 2)
|
||||||
ret['wr_out'] = self.bits_from_inst(data, 252, 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))
|
testfiles.append(os.path.join(current_dir, 'testcases', f))
|
||||||
for f in testfiles:
|
for f in testfiles:
|
||||||
print(f'Testing {f}...')
|
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:
|
with tempfile.NamedTemporaryFile(delete=False) as f_out:
|
||||||
self.addCleanup(os.unlink, f_out.name)
|
self.addCleanup(os.unlink, f_out.name)
|
||||||
args = [bsasm_path, f, f_out.name]
|
args = [bsasm_path, f, f_out.name]
|
||||||
|
args.extend(cmdlineopts)
|
||||||
p = subprocess.run(args, timeout=10)
|
p = subprocess.run(args, timeout=10)
|
||||||
self.assertEqual(p.returncode, 0)
|
if not should_fail:
|
||||||
b = self.unpack_binary(f_out.name)
|
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'
|
if not should_fail:
|
||||||
try:
|
b = self.unpack_binary(f_out.name)
|
||||||
with open(jsfn) as out_desc_f:
|
jsfn = f[:-6] + '.json'
|
||||||
out_desc = json.load(out_desc_f)
|
try:
|
||||||
# We were able to open the JSON file. See if the keys in it match up with the ones in the decoded fields.
|
with open(jsfn) as out_desc_f:
|
||||||
self.compare(b, out_desc, '')
|
out_desc = json.load(out_desc_f)
|
||||||
except FileNotFoundError:
|
# We were able to open the JSON file. See if the keys in it match up with the ones in the decoded fields.
|
||||||
print(f'File not found: {jsfn}. Printing out decoded contents instead.')
|
self.compare(b, out_desc, '')
|
||||||
print(json.dumps(b, indent=4))
|
except FileNotFoundError:
|
||||||
|
print(f'File not found: {jsfn}. Printing out decoded contents instead.')
|
||||||
|
print(json.dumps(b, indent=4))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
#Test all opcodes
|
#Test all opcodes
|
||||||
|
|
||||||
cfg trailing_bytes 0 #End program as soon as the input EOFs.
|
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