BitScrambler Driver
Introduction
The BitScrambler is a peripheral that allows doing various transformation of data in a DMA stream based on an user-supplied program. ESP-IDF comes with an assembler for BitScrambler programs as well as build system and driver support for this peripheral. The BitScrambler peripheral in the ESP32-P4 has an independent TX and RX channel; both can be associated with the same or different peripherals.
Functional Overview
BitScrambler assembly - covers how a BitScrambler assembly program is structured
Build system integration - covers how BitScrambler programs are integrated in the ESP-IDF build system
Resource allocation and program loading Covers how to allocate BitScrambler instances and how to load a program into them
Loopback mode Covers how to use the BitScrambler in loopback mode
BitScrambler Assembly
The operations a BitScrambler performs on the data in the DMA stream are defined in a BitScrambler program. As a BitScrambler program is a binary blob that is hard to write by hand, ESP-IDF includes an assembler. This assembler converts easier-to-write text files into BitScrambler binary programs.
BitScrambler assembly files consist of comments, labels, instruction bundles, meta-instructions. The assembler ignores comments. Labels define a location in the program; instructions can e.g. jump to the location indicated by a label. Instruction bundles are a collection of sub-instructions that get assembled into one 257-bit binary BitScrambler instruction. Meta-instructions define global BitScrambler configuration, like the amount of trailing bytes, the prefetch mode, or the contents of the LUT ram.
BitScrambler assembly files are case-insensitive as well as not sensitive to indentation. This documentation will use upper and lower caps for readability, but the assembler itself doesn't care. Fields that can contain an integer normally are given in a base-10 number, but can also contain a hex value if prepended with 0x
or a binary value if prepended with 0b
.
Labels
Any string of (non-whitespace) characters followed by a colon is a label. The label is a symbolic reference to the next instruction bundle in the assembly file. Note that a label cannot be part of an instruction bundle; it needs to be located before the start of the instruction bundle.
Example:
loop_back:
set 0..3 4..7,
set 4..7 0..3,
read 8,
write 8,
jmp loop_back
The 'jmp' opcode in the instruction bundle will jump back to the start of itself, meaning that this instruction bundle will be executed over and over again in a tight loop.
Instruction bundle
An instruction bundle consists of sub-instructions, separated by a comma. The entirety of the instruction bundle is assembled into one 257-bit instruction that the BitScrambler will execute in a single clock cycle; in other words, all sub-instructions in an instruction word will execute in parallel, at the same time. This also means the order of sub-instructions in an instruction in the assembly source does not matter. The instruction bundle ends after the last sub-instruction that is not followed by a comma.
The specific details of the BitScrambler can be found in the ESP32-P4 Technical Reference Manual > BitScrambler (BITSCRM) [PDF]. As a summary: the BitScrambler consists of a 32-bit output register. Each of its bits takes the value of any bit in any of the sources. The sources are:
a 64-bit input register that is being fed from the incoming DMA stream,
two 16-bit counters,
30 registers that contain the output of various comparisons,
a fixed high and low bit,
the output of a look-up table (LUT) RAM,
the value of the output register in the previous cycle.
Sub-instructions
set [output] [source_bits]
- Routes one or more source bits to output bits. Note that it's possible to route multiple bits using the ..
operator: for instance set 0..3 O4..O6
will have the same effect as set 0 O4, set 1 O5, set 2 O6, set 3 O7
. The first argument is the output bit or output bit range; output bits are numbered from 0 to 31. The second argument is one or a range of source bits. Note that any output bits that do not have a corresponding set
sub-instruction in an instruction bundle will be set to a low logic level.
write [n]
- After routing all output bits, take the least significant n
output register bits and push them into the output DMA pipeline. n
can be one of 0, 8, 16 or 32. If an instruction bundle does not have a write
sub-instruction, it will be equivalent to a write 0
.
read [n]
- After routing all output bits and writing to the output register, take n
bits from the input DMA pipeline and push them into the 64-bit input register. n
can be one of 0, 8, 16 or 32. These bits will be shifted into the input FIFO starting from the MSB. As an example, a read 16
will shift bits 63-16 of the input register down to bits 47-0 and the new 16 bits read from the input DMA pipeline will occupy bits 63-48 in the input register. If an instruction bundle does not have a read
sub-instruction, it will be equivalent to a read 0
.
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, addctr_add
to the selected counter (A or B) and jump to the labeltgt
. If not, continue execution.ADD(A|B)[H|L] val
- Addval
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 labeltgt
.LDCTD(A|B)[H|L] val
- Loadval
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 labeltgt
. This is equal toIF h tgt
.NOP
- No operation. This is equal toADDA 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
The set
and if
/ifn
instructions have a source bit
field. The following values can be put there:
0
-63
- The bit selected is sourced from the selected bit in the input register.O0
-O31
- The bit selected is sourced from the value the output register was assigned in the previous cycle.A0
-A15
- The bit selected is sourced from the selected bit in the A counter register.B0
-B15
- The bit selected is sourced from the selected bit in the B counter register.L0
-L31
- The bit selected is sourced from the output from the LUT ram. As described in the Technical Reference Manual, the LUT RAM output is the LUT item at the position indicated by the most significant N bits of the bits routed to the output register in the previous cycle, with N being 9, 10 or 11 for a LUT width of 32, 16 or 8-bits respectively.A condition comparing (a portion of) counter B with bits that were routed to the output register in the previous cycle. These conditions consist of three parts: depending on if you want to compare the entirety of the B register or only the upper or lower 8 bits, the first part is 'B', 'BH' or BL' respectively. The second part is the comparison operator: '<=', '>' and '=' are supported here. The third is the offset into the output register that will be compared to the selected part of the B register: this can be O0 or O16 for 16-bit comparisons and O0, O8, O16 or O24 for 8-bit comparison.
H
orL
. These sources are fixed-high or fixed-low.
Note that not all sources can be used together in the same instruction. For instance, it is not possible to use a bit from one of the two counters as well as a bit from the upper 32 bits of the input FIFO in the same instruction bundle. The assembler will generate an error if an instruction bundle tries to do this anyway.
Example
An example BitScrambler program might look like this:
loop_back:
set 0..3 4..7,
set 4..7 0..3,
read 8,
write 8,
jmp loop_back
This program only has one instruction (as only the line with the jmp
does not end in a comma). It
takes the lower 4 bits of the data read from memory and sends it to the upper 4 bits of the first byte
of the output register. It also takes the next 4 bits of the input register and sends it to the lower
4 bits of the output register. It then writes 8 bits (one byte) to the output, while reading 8 bits
from the input. Finally, the program continues by jumping back to the start of the instruction. Note
that this all is executed in one BitScrambler cycle, and as the sub-instructions all are part of
the same instruction, they could be specified in any order within the instruction. The end result
of this small BitScrambler program is that it takes in data, e.g. 01 23 45 67
and swaps the high
and low nibble of every bytes, resulting in an output of 10 32 54 76
.
Meta-instructions
Meta-instructions set global BitScrambler configuration. Meta-instructions are allowed anywhere within the assembly file (except within an instruction bundle) and due to their nature will also have effect on the preceding assembly code. At the moment, two meta-instructions are defined. cfg
sets a global BitScrambler setting, while lut
defines lookuptable RAM content.
Global configuration meta-instructions
cfg prefetch true|false
- If prefetch is set totrue
, on BitScrambler start it will read 64 bits from the input DMA stream into the input register. If set tofalse
, the input register will be initialized to zero. This setting defaults totrue
if not specified.cfg eof_on upstream|downstream
- After the input stream ends, the BitScrambler will still process a certain amount of 'trailing' dummy bytes so it can flush any data contained in its registers. This setting indicates from where the data will be counted:upstream
makes the bitscrambler count the bytes being read,downstream
makes it count the bytes being written. This defaults toupstream
if not specified.cfg trailing_bytes N
- This indicates how many dummy bytes will be read or written (depending on theeof_on
setting) before the BitScrambler indicates an end-of-stream on its output. This defaults to0
if not specified.cfg lut_width_bits 8|16|32
- This selects the bus width of the LUT output RAM, in bits. The LUT can be 2048x8bit, 1024*16bit or 512*32bits in size. This defaults to32
if not specified.
LUT content meta-instructions
lut
instructions are used to specify the contents of the LUT RAM. This meta-instruction is followed by one or more numerical values, separated by spaces or commas. LUT RAM locations
are defined in the order they're encountered in the assembly program; the first value is always stored at location 0, the second value encountered is always stored at location 1, etc. The amount of arguments to a LUT meta-instruction is arbitrary as LUT meta-instructions can always be broken up or merged. For instance, lut 1,2,3,4
is the same as lut 1,2
on one line and lut 3,4
on the next line.
Note that LUT values must be within range with respect to the value given to the cfg lut_width_bits
configuration meta-statement.
Build system integration
The BitScrambler has full ESP-IDF build system support. A component (including the main component) can have BitScrambler assembly source files in its source directories. These files generally have the suffix .bsasm
. To assemble and link such a file into the main application, the CMakeLists.txt file for the component can call target_bitscrambler_add_src("assembly_file.bsasm")
. For instance, for an assembly file called my_program.bsasm
, a CMakeLists.txt file may look like this:
idf_component_register(SRCS "main.c" "some-file.c"
INCLUDE_DIRS "./include")
target_bitscrambler_add_src("my_program.bsasm")
To use the assembled BitScrambler program, you would refer to it as such:
// Create a variable 'my_bitscrambler_program' that resolves to
// the binary bitscrambler program.
// 2nd arg is same as name of assembly file without ".bsasm"
BITSCRAMBLER_PROGRAM(my_bitscrambler_program, "my_program");
[...]
bitscrambler_handle_t bs;
[...create bitscrambler instance]
bitscrambler_load_program(bs, my_bitscrambler_program);
Loopback mode
The BitScrambler supports a loopback mode which is useful for data transformations that do not involve a peripheral. The loopback mode occupies both the TX and RX channels of the BitScrambler, although only the TX BitScrambler actually executes code. Note that even if loopback mode does not involve a peripheral, one still needs to be selected; the peripheral does not need to be initialized or used, but if it is, its DMA features will be unavailable.
Resource allocation and program loading
In loopback mode, a BitScrambler object is created using bitscrambler_loopback_create()
. If there is a BitScrambler peripheral matching the requested characteristics, this function will return a handle to it. You can then use bitscrambler_load_program()
to load a program into it, then call bitscrambler_loopback_run()
to transform a memory buffer using the loaded program. You can call bitscrambler_loopback_run()
any number of times; it's also permissible to use bitscrambler_load_program()
to change programs between calls. Finally, to free the hardware resources and clean up memory, call bitscrambler_free()
.
API Reference
Header File
components/esp_driver_bitscrambler/include/driver/bitscrambler.h
This header file can be included with:
#include "driver/bitscrambler.h"
This header file is a part of the API provided by the
esp_driver_bitscrambler
component. To declare that your component depends onesp_driver_bitscrambler
, add the following to your CMakeLists.txt:REQUIRES esp_driver_bitscrambler
or
PRIV_REQUIRES esp_driver_bitscrambler
Functions
-
esp_err_t bitscrambler_new(const bitscrambler_config_t *config, bitscrambler_handle_t *handle)
Allocate BitScrambler handle for a hardware channel.
- 参数
config -- Configuration for requested BitScrambler
handle -- [out] BitScrambler controller handle
- 返回
ESP_OK
ESP_ERR_NO_MEM: No memory available
ESP_ERR_NOT_FOUND: No free hardware channel available
-
void bitscrambler_free(bitscrambler_handle_t handle)
Free previously allocated BitScrambler handle.
- 参数
handle -- Previously allocated handle
-
esp_err_t bitscrambler_load_program(bitscrambler_handle_t handle, const void *program)
Load a BitScrambler binary program into BitScrambler memory.
- 参数
handle -- BitScrambler handle
program -- Binary program to load
- 返回
ESP_OK
ESP_ERR_INVALID_ARG: Not a valid or recognized BitScrambler binary, or invalid handle
-
esp_err_t bitscrambler_load_lut(bitscrambler_handle_t handle, void *lut, size_t size_bytes)
Load data into the Look-Up Table.
- 参数
handle -- BitScrambler handle
lut -- Data to load
size_bytes -- Size of the data, in bytes
- 返回
ESP_OK
ESP_ERR_INVALID_ARG: Invalid handle or lut pointer
-
esp_err_t bitscrambler_start(bitscrambler_handle_t handle)
Start executing BitScrambler program.
- 参数
handle -- BitScrambler handle
- 返回
ESP_OK
ESP_ERR_INVALID_ARG: Invalid handle
-
esp_err_t bitscrambler_reset(bitscrambler_handle_t handle)
Reset BitScrambler program and FIFOs for a new transaction. Note that this does not affect the loaded program itself.
- 参数
handle -- BitScrambler handle
- 返回
ESP_OK
ESP_ERR_INVALID_ARG: Invalid handle
Structures
-
struct bitscrambler_config_t
BitScrambler configuration.
Macros
-
BITSCRAMBLER_PROGRAM(VAR, NAME)
Type Definitions
-
typedef struct bitscrambler_t *bitscrambler_handle_t
Header File
components/esp_driver_bitscrambler/include/driver/bitscrambler_loopback.h
This header file can be included with:
#include "driver/bitscrambler_loopback.h"
This header file is a part of the API provided by the
esp_driver_bitscrambler
component. To declare that your component depends onesp_driver_bitscrambler
, add the following to your CMakeLists.txt:REQUIRES esp_driver_bitscrambler
or
PRIV_REQUIRES esp_driver_bitscrambler
Functions
-
esp_err_t bitscrambler_loopback_create(bitscrambler_handle_t *handle, int attach_to, size_t max_transfer_sz_bytes)
Create handle for BitScrambler in loopback mode.
备注
Use bitscrambler_free to free created handle.
- 参数
handle -- [out] BitScrambler handle
attach_to -- Peripheral to attach to. One of SOC_BITSCRAMBLER_ATTACH_. The BitScrambler must be attached to some peripheral, even in loopback mode. This peripheral does not need to be initialized for the BitScrambler to work. However, it can be initialized and will work as normal, with the exception that DMA functionality for this peripheral cannot be used.
max_transfer_sz_bytes -- Maximum transfer size, in bytes, of either the incoming or outgoing data fed to bitscrambler_loopback_run.
- 返回
ESP_OK
ESP_ERR_NO_MEM: No memory available
ESP_ERR_NOT_FOUND: No free hardware channel available
ESP_ERR_INVALID_ARG: Invalid argument passed to function
ESP_FAIL: Bitscrambler object creation failed because of some other error
-
esp_err_t bitscrambler_loopback_run(bitscrambler_handle_t bs, void *buffer_in, size_t length_bytes_in, void *buffer_out, size_t length_bytes_out, size_t *bytes_written)
Run Bitscrambler program on a data buffer.
- 参数
bs -- BitScrambler handle. This BitScrambler should have a program loaded using bitscrambler_load_program()
buffer_in -- Data to feed into the BitScrambler
length_bytes_in -- Size of the data in buffer_in, in bytes
buffer_out -- Buffer for BitScrambler to write processed data to
length_bytes_out -- Size of output buffer
bytes_written -- [out] Pointer to variable to store the size of actual data written to output buffer. Can be NULL if not needed.
- 返回
ESP_OK
ESP_ERR_INVALID_SIZE if a buffer size exceeds max_transfer_sz_bytes
ESP_ERR_TIMEOUT if BitScrambler program does not complete
Header File
components/soc/esp32p4/include/soc/bitscrambler_peri_select.h
This header file can be included with:
#include "soc/bitscrambler_peri_select.h"
Macros
-
SOC_BITSCRAMBLER_ATTACH_LCD_CAM
-
SOC_BITSCRAMBLER_ATTACH_GPSPI2
-
SOC_BITSCRAMBLER_ATTACH_GPSPI3
-
SOC_BITSCRAMBLER_ATTACH_PARL_IO
-
SOC_BITSCRAMBLER_ATTACH_AES
-
SOC_BITSCRAMBLER_ATTACH_SHA
-
SOC_BITSCRAMBLER_ATTACH_ADC
-
SOC_BITSCRAMBLER_ATTACH_I2S0
-
SOC_BITSCRAMBLER_ATTACH_I2S1
-
SOC_BITSCRAMBLER_ATTACH_I2S2
-
SOC_BITSCRAMBLER_ATTACH_I3C_MST
-
SOC_BITSCRAMBLER_ATTACH_UHCI
-
SOC_BITSCRAMBLER_ATTACH_RMT
-
SOC_BITSCRAMBLER_ATTACH_MAX
Comments
A comment starts with a '#' and lasts until the end of the line. A comment can be placed anywhere where a space can be placed; this means that comments can be inside instruction bundles in between sub-instructions.