Merge branch 'feature/bitscrambler_support' into 'master'

feature(driver): BitScrambler loopback support

See merge request espressif/esp-idf!31236
This commit is contained in:
morris 2025-01-03 16:08:52 +08:00
commit c8d4e1b094
74 changed files with 3419 additions and 2 deletions

View File

@ -205,6 +205,8 @@ test_tools:
- pytest --noconftest test_idf_py.py --junitxml=${IDF_PATH}/XUNIT_IDF_PY.xml || stat=1
- pytest --noconftest test_hints.py --junitxml=${IDF_PATH}/XUNIT_HINTS.xml || stat=1
- pytest --noconftest test_idf_qemu.py --junitxml=${IDF_PATH}/XUNIT_IDF_PY_QEMU.xml || stat=1
- cd ${IDF_PATH}/tools/test_bsasm
- pytest --noconftest test_bsasm.py --junitxml=${IDF_PATH}/XUNIT_BSASM.xml || stat=1
- cd ${IDF_PATH}/tools/test_mkdfu
- pytest --noconftest test_mkdfu.py --junitxml=${IDF_PATH}/XUNIT_MKDFU.xml || stat=1
- cd ${IDF_PATH}/tools/test_idf_size

View File

@ -126,6 +126,9 @@
- "tools/check_python_dependencies.py"
- "tools/bsasm.py"
- "tools/test_bsasm/**/*"
.patterns-docker: &patterns-docker
- "tools/docker/**/*"

View File

@ -0,0 +1,29 @@
idf_build_get_property(target IDF_TARGET)
set(srcs)
set(include_dirs)
set(priv_requires)
set(my_priv_requires "soc" "hal" "esp_hw_support" "esp_mm")
if(CONFIG_SOC_BITSCRAMBLER_SUPPORTED)
list(APPEND srcs "bitscrambler.c" "bitscrambler_loopback.c")
list(APPEND include_dirs "include")
endif()
# Note that (according to the docs) "The values of REQUIRES and PRIV_REQUIRES
# should not depend on any configuration choices (CONFIG_xxx macros)." We work
# around that by setting the actual priv_requires value in the target checks,
# rather than make it depend on CONFIG_SOC_BITSCRAMBLER_SUPPORTED.
if(target STREQUAL "esp32p4")
list(APPEND srcs "bitscrambler_esp32p4.c")
set(priv_requires ${my_priv_requires})
endif()
idf_component_register(SRCS ${srcs}
PRIV_REQUIRES ${priv_requires}
INCLUDE_DIRS ${include_dirs}
PRIV_INCLUDE_DIRS "priv_include"
)

View File

@ -0,0 +1,300 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdatomic.h>
#include "esp_log.h"
#include "driver/bitscrambler.h"
#include "bitscrambler_private.h"
#include "bitscrambler_loopback_private.h"
#include "soc/soc.h"
#include "hal/bitscrambler_ll.h"
#include "esp_private/periph_ctrl.h"
static const char *TAG = "bitscrambler";
#define BITSCRAMBLER_BINARY_VER 1 //max version we're compatible with
#define BITSCRAMBLER_HW_REV 0
// After a reset, it can take a few cycles for the BitScrambler to actually be
// reset. We check this many times for this; if it takes longer the hardware
// is broken or something.
#define BITSCRAMBLER_RESET_ITERATIONS 10000
/*
Format of a V1 BitScrambler program image:
- Header, as defined by bitscrambler_program_hdr_t below. Size is hdr->hdr_len words.
- Program lines. A line is 9 32-bit words, we have hdr->inst_ct lines.
- LUT data. LUT is hdr->lut_word_ct 32-bit words in size.
*/
typedef struct {
uint8_t version;
uint8_t hw_rev;
uint8_t hdr_len; //in 32-bit words
uint8_t inst_ct; //0-8
uint16_t lut_word_ct; //in 32-bit words
uint8_t lut_width; //0, 1, 2
uint8_t prefetch; //prefetch enabled?
uint16_t trailing_bits; //in bits
uint8_t eof_on;
uint8_t unused;
} bitscrambler_program_hdr_t;
#define INST_LEN_WORDS 9 //length of one instruction in 32-bit words as defined by HW
// For now, hardware only has one TX and on RX unit. Need to make this more flexible if we get
// non-specific and/or more channels.
atomic_flag tx_in_use = ATOMIC_FLAG_INIT;
atomic_flag rx_in_use = ATOMIC_FLAG_INIT;
// Claim both TX and RX channels for loopback use
// Returns true on success, false if any of the two directions already is claimed.
static bool claim_channel_loopback(void)
{
bool old_val_tx = atomic_flag_test_and_set(&tx_in_use);
if (old_val_tx) {
return false;
}
bool old_val_rx = atomic_flag_test_and_set(&rx_in_use);
if (old_val_rx) {
atomic_flag_clear(&tx_in_use);
return false;
}
return true;
}
// Claim a channel using the direction it indicated.
// Returns true on success, false if the direction already is claimed
static bool claim_channel(bitscrambler_direction_t dir)
{
if (dir == BITSCRAMBLER_DIR_TX) {
bool old_val = atomic_flag_test_and_set(&tx_in_use);
if (old_val) {
return false;
}
} else if (dir == BITSCRAMBLER_DIR_RX) {
bool old_val = atomic_flag_test_and_set(&rx_in_use);
if (old_val) {
return false;
}
}
return true;
}
//Initialize the BitScrambler object and hardware using the given config.
static esp_err_t init_from_config(bitscrambler_t *bs, const bitscrambler_config_t *config)
{
bs->cfg = *config; //Copy config over
bs->hw = BITSCRAMBLER_LL_GET_HW(0); //there's only one as of now; if there's more, we need to handle them as a pool.
//Attach to indicated peripheral.
bitscrambler_ll_select_peripheral(bs->hw, bs->cfg.dir, config->attach_to);
bitscrambler_ll_enable(bs->hw, bs->cfg.dir);
return ESP_OK;
}
static void enable_clocks(bitscrambler_t *bs)
{
PERIPH_RCC_ACQUIRE_ATOMIC(PERIPH_BITSCRAMBLER_MODULE, ref_count) {
if (ref_count == 0) { //we're the first to enable the BitScrambler module
bitscrambler_ll_set_bus_clock_sys_enable(1);
bitscrambler_ll_reset_sys();
}
if (bs->cfg.dir == BITSCRAMBLER_DIR_RX || bs->loopback) {
bitscrambler_ll_set_bus_clock_rx_enable(1);
bitscrambler_ll_reset_rx();
}
if (bs->cfg.dir == BITSCRAMBLER_DIR_TX || bs->loopback) {
bitscrambler_ll_set_bus_clock_tx_enable(1);
bitscrambler_ll_reset_tx();
}
}
}
static void disable_clocks(bitscrambler_t *bs)
{
PERIPH_RCC_RELEASE_ATOMIC(PERIPH_BITSCRAMBLER_MODULE, ref_count) {
if (bs->cfg.dir == BITSCRAMBLER_DIR_RX || bs->loopback) {
bitscrambler_ll_set_bus_clock_rx_enable(0);
}
if (bs->cfg.dir == BITSCRAMBLER_DIR_TX || bs->loopback) {
bitscrambler_ll_set_bus_clock_tx_enable(0);
}
if (ref_count == 0) { //we're the last to disable the BitScrambler module
bitscrambler_ll_set_bus_clock_sys_enable(0);
}
}
}
//Private function: init an existing BitScrambler object as a loopback BitScrambler.
esp_err_t bitscrambler_init_loopback(bitscrambler_handle_t handle, const bitscrambler_config_t *config)
{
if (!claim_channel_loopback()) {
return ESP_ERR_NOT_FOUND;
}
assert(config->dir == BITSCRAMBLER_DIR_TX);
handle->loopback = true;
enable_clocks(handle);
esp_err_t r = init_from_config(handle, config);
//Loopback mode also needs RX channel set to the selected peripheral, even if it's not used.
bitscrambler_ll_select_peripheral(handle->hw, BITSCRAMBLER_DIR_RX, config->attach_to);
return r;
}
esp_err_t bitscrambler_new(const bitscrambler_config_t *config, bitscrambler_handle_t *handle)
{
if (!config) {
return ESP_ERR_INVALID_ARG;
}
if (!handle) {
return ESP_ERR_INVALID_ARG;
}
// Allocate memory for private data
bitscrambler_t *bs = calloc(1, sizeof(bitscrambler_t));
if (!bs) {
return ESP_ERR_NO_MEM;
}
// Claim channel
if (!claim_channel(config->dir)) {
free(bs);
return ESP_ERR_NOT_FOUND;
}
enable_clocks(bs);
// Do initialization of BS object.
esp_err_t r = init_from_config(bs, config);
if (r != ESP_OK) {
bitscrambler_free(bs);
return r;
}
// Done.
*handle = bs;
return ESP_OK;
}
esp_err_t bitscrambler_load_program(bitscrambler_handle_t bs, const void *program_bin)
{
if (!bs || !program_bin) {
return ESP_ERR_INVALID_ARG;
}
bitscrambler_program_hdr_t hdr;
//Parse the program header. There are two versions, V0 is generated by the C assembler while
//v1 is generated by the Python assembler.
int inst_len_bytes = INST_LEN_WORDS * sizeof(uint32_t); //note this is different for v1 and v0
memcpy(&hdr, program_bin, sizeof(bitscrambler_program_hdr_t));
if (hdr.version != BITSCRAMBLER_BINARY_VER) {
ESP_LOGE(TAG, "Bitscrambler binary version %d not supported!", hdr.version);
return ESP_ERR_INVALID_ARG;
}
if (hdr.hw_rev != BITSCRAMBLER_HW_REV) {
ESP_LOGE(TAG, "Bitscrambler hardware rev %d not supported!", hdr.hw_rev);
return ESP_ERR_INVALID_ARG;
}
bitscrambler_ll_set_state(bs->hw, bs->cfg.dir, BITSCRAMBLER_SET_STATE_HALT);
//Load the program
const uint8_t *p = (const uint8_t*)program_bin;
p += hdr.hdr_len * sizeof(uint32_t); //skip header
uint32_t instr[INST_LEN_WORDS];
for (int inst = 0; inst < hdr.inst_ct; inst++) {
//v0 doesn't have the words 32-bit aligned, so memcpy to work around that
memcpy(instr, p, INST_LEN_WORDS * sizeof(uint32_t));
p += inst_len_bytes;
for (int w = 0; w < INST_LEN_WORDS; w++) {
bitscrambler_ll_instmem_write(bs->hw, bs->cfg.dir, inst, w, instr[w]);
}
}
ESP_LOGD(TAG, "Loaded %d instructions", hdr.inst_ct);
//Load the LUT.
bitscrambler_ll_set_lut_width(bs->hw, bs->cfg.dir, BITSCRAMBLER_LUT_WIDTH_32BIT);
uint32_t *lut = (uint32_t*)p;
for (int w = 0; w < hdr.lut_word_ct; w++) {
bitscrambler_ll_lutmem_write(bs->hw, bs->cfg.dir, w, lut[w]);
}
//Set options from header
bitscrambler_ll_set_lut_width(bs->hw, bs->cfg.dir, hdr.lut_width);
bitscrambler_ll_set_prefetch_mode(bs->hw, bs->cfg.dir, hdr.prefetch ? BITSCRAMBLER_PREFETCH_ENABLED : BITSCRAMBLER_PREFETCH_DISABLED);
bitscrambler_ll_set_eof_mode(bs->hw, bs->cfg.dir, hdr.eof_on);
bitscrambler_ll_set_tailing_bits(bs->hw, bs->cfg.dir, hdr.trailing_bits);
//fixed options
bitscrambler_ll_set_dummy_mode(bs->hw, bs->cfg.dir, BITSCRAMBLER_DUMMY_MODE_DUMMY);
bitscrambler_ll_set_halt_mode(bs->hw, bs->cfg.dir, BITSCRAMBLER_HALT_IGNORE_WRITES);
//enable loopback mode if requested
bitscrambler_ll_enable_loopback(bs->hw, bs->loopback);
return ESP_OK;
}
esp_err_t bitscrambler_load_lut(bitscrambler_handle_t handle, void *lut, size_t size_bytes)
{
if (!handle || !lut) {
return ESP_ERR_INVALID_ARG;
}
uint32_t *lut_words = (uint32_t*)lut;
bitscrambler_lut_width_t lut_width = bitscrambler_ll_get_lut_width(handle->hw, handle->cfg.dir);
bitscrambler_ll_set_lut_width(handle->hw, handle->cfg.dir, BITSCRAMBLER_LUT_WIDTH_32BIT);
size_t size_words = (size_bytes + 3) / 4;
for (int w = 0; w < size_words; w++) {
bitscrambler_ll_lutmem_write(handle->hw, handle->cfg.dir, w, lut_words[w]);
}
bitscrambler_ll_set_lut_width(handle->hw, handle->cfg.dir, lut_width);
return ESP_OK;
}
void bitscrambler_free(bitscrambler_handle_t handle)
{
disable_clocks(handle);
if (handle->loopback) {
atomic_flag_clear(&tx_in_use);
atomic_flag_clear(&rx_in_use);
bitscrambler_loopback_free(handle);
} else if (handle->cfg.dir == BITSCRAMBLER_DIR_TX) {
atomic_flag_clear(&tx_in_use);
} else if (handle->cfg.dir == BITSCRAMBLER_DIR_RX) {
atomic_flag_clear(&rx_in_use);
}
free(handle);
}
esp_err_t bitscrambler_start(bitscrambler_handle_t handle)
{
if (!handle) {
return ESP_ERR_INVALID_ARG;
}
bitscrambler_ll_set_state(handle->hw, handle->cfg.dir, BITSCRAMBLER_SET_STATE_RUN);
return ESP_OK;
}
esp_err_t bitscrambler_reset(bitscrambler_handle_t handle)
{
if (!handle) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t ret = ESP_OK;
bitscrambler_ll_set_state(handle->hw, handle->cfg.dir, BITSCRAMBLER_SET_STATE_HALT);
//If the halt bit is set, the Bitscrambler should (eventually) go to idle state. If it
//does not, something got stuck.
int timeout = BITSCRAMBLER_RESET_ITERATIONS;
while ((bitscrambler_ll_current_state(handle->hw, handle->cfg.dir) != BITSCRAMBLER_STATE_IDLE) && timeout != 0) {
timeout--;
}
if (timeout == 0) {
ESP_LOGE(TAG, "bitscrambler_reset: Timeout waiting for idle!");
ret = ESP_ERR_TIMEOUT;
}
//Reset the fifos & eof trace ctrs
bitscrambler_ll_reset_fifo(handle->hw, handle->cfg.dir);
bitscrambler_ll_clear_eof_trace(handle->hw, handle->cfg.dir);
return ret;
}

View File

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/gdma_channel.h"
#include "bitscrambler_soc_specific.h"
// Note: these are indexed by the values of the SOC_BITSCRAMBLER_ATTACH_ defines
// in soc/bitscrambler_peri_select.h
// This map is used by the bitscrambler loopback driver only.
const bitscrambler_periph_desc_t g_bitscrambler_periph_desc[] = {
[SOC_BITSCRAMBLER_ATTACH_LCD_CAM] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0), SOC_GDMA_TRIG_PERIPH_LCD0_BUS},
[SOC_BITSCRAMBLER_ATTACH_GPSPI2] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_SPI, 2), SOC_GDMA_TRIG_PERIPH_SPI2_BUS},
[SOC_BITSCRAMBLER_ATTACH_GPSPI3] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_SPI, 3), SOC_GDMA_TRIG_PERIPH_SPI3_BUS},
[SOC_BITSCRAMBLER_ATTACH_PARL_IO] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_PARLIO, 0), SOC_GDMA_TRIG_PERIPH_PARLIO0_BUS},
[SOC_BITSCRAMBLER_ATTACH_AES] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_AES, 0), SOC_GDMA_TRIG_PERIPH_AES0_BUS},
[SOC_BITSCRAMBLER_ATTACH_SHA] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_SHA, 0), SOC_GDMA_TRIG_PERIPH_SHA0_BUS},
[SOC_BITSCRAMBLER_ATTACH_ADC] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_ADC, 0), SOC_GDMA_TRIG_PERIPH_ADC0_BUS},
[SOC_BITSCRAMBLER_ATTACH_I2S0] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_I2S, 0), SOC_GDMA_TRIG_PERIPH_I2S0_BUS},
[SOC_BITSCRAMBLER_ATTACH_I2S1] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_I2S, 1), SOC_GDMA_TRIG_PERIPH_I2S1_BUS},
[SOC_BITSCRAMBLER_ATTACH_I2S2] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_I2S, 2), SOC_GDMA_TRIG_PERIPH_I2S2_BUS},
[SOC_BITSCRAMBLER_ATTACH_I3C_MST] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_I3C, 0), SOC_GDMA_TRIG_PERIPH_I3C0_BUS},
[SOC_BITSCRAMBLER_ATTACH_UHCI] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0), SOC_GDMA_TRIG_PERIPH_UHCI0_BUS},
[SOC_BITSCRAMBLER_ATTACH_RMT] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_RMT, 0), SOC_GDMA_TRIG_PERIPH_RMT0_BUS},
};

View File

@ -0,0 +1,273 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include "driver/bitscrambler.h"
#include "bitscrambler_private.h"
#include "bitscrambler_loopback_private.h"
#include "esp_private/gdma.h"
#include "hal/dma_types.h"
#include "hal/cache_ll.h"
#include "hal/gdma_ll.h"
#include "bitscrambler_soc_specific.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "esp_check.h"
#include "soc/ahb_dma_struct.h"
#include "esp_heap_caps.h"
#include "esp_cache.h"
#include "esp_dma_utils.h"
const static char *TAG = "bs_loop";
//Note: given that the first member is a bitscrambler_t, this can be safely passed to
//any of the non-loopback bitscrambler functions.
typedef struct {
bitscrambler_t bs;
dma_descriptor_t *tx_desc_link; // descriptor link list, the length of the link is determined by the copy buffer size
dma_descriptor_t *rx_desc_link; // descriptor link list, the length of the link is determined by the copy buffer size
gdma_channel_handle_t tx_channel; // GDMA TX channel handle
gdma_channel_handle_t rx_channel; // GDMA RX channel handle
SemaphoreHandle_t sema_done;
size_t max_transfer_sz_bytes;
} bitscrambler_loopback_t;
static esp_err_t new_dma_channel(const gdma_channel_alloc_config_t *cfg, gdma_channel_handle_t *handle, int bus)
{
esp_err_t ret = ESP_OK;
//Note that there are chips that do not have SOC_GDMA_BUS_* defined, but those chips also do
//not have a BitScrambler.
#ifdef SOC_GDMA_BUS_AHB
if (bus == SOC_GDMA_BUS_AHB || bus == SOC_GDMA_BUS_ANY) {
ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(cfg, handle), TAG, "alloc AHB DMA channel failed");
}
#endif
#ifdef SOC_GDMA_BUS_AXI
if (bus == SOC_GDMA_BUS_AXI) {
ESP_RETURN_ON_ERROR(gdma_new_axi_channel(cfg, handle), TAG, "alloc AXI DMA channel failed");
}
#endif
return ret;
}
static IRAM_ATTR bool trans_done_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
BaseType_t higher_prio_task_awoken = pdFALSE;
bitscrambler_loopback_t *bs = (bitscrambler_loopback_t*)user_data;
xSemaphoreGiveFromISR(bs->sema_done, &higher_prio_task_awoken);
return higher_prio_task_awoken;
}
esp_err_t bitscrambler_loopback_create(bitscrambler_handle_t *handle, int attach_to, size_t max_transfer_sz_bytes)
{
///make sure bs is indeed the first member of bitscrambler_loopback_t so we can cast it to a bitscrambler_t
_Static_assert(offsetof(bitscrambler_loopback_t, bs) == 0, "bs needs to be 1st member of bitscrambler_loopback_t");
if (!handle) {
return ESP_ERR_INVALID_ARG;
}
if (attach_to < 0 || attach_to > SOC_BITSCRAMBLER_ATTACH_MAX) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t ret = ESP_OK;
bitscrambler_loopback_t *bs = calloc(1, sizeof(bitscrambler_loopback_t));
if (!bs) {
return ESP_ERR_NO_MEM;
}
//Create the underlying BitScrambler object
bitscrambler_config_t cfg = {
.dir = BITSCRAMBLER_DIR_TX,
.attach_to = attach_to
};
ESP_GOTO_ON_ERROR(bitscrambler_init_loopback(&bs->bs, &cfg), err, TAG, "failed bitscrambler init for loopback");
bs->sema_done = xSemaphoreCreateBinary();
if (!bs->sema_done) {
goto err;
}
bs->max_transfer_sz_bytes = max_transfer_sz_bytes;
int desc_ct = (max_transfer_sz_bytes + DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED - 1) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED;
int bus = g_bitscrambler_periph_desc[attach_to].bus;
uint32_t caps = (bus == SOC_GDMA_BUS_AXI) ? MALLOC_CAP_DMA_DESC_AXI : MALLOC_CAP_DMA_DESC_AHB;
size_t align = (bus == SOC_GDMA_BUS_AXI) ? 8 : 4;
bs->rx_desc_link = heap_caps_aligned_calloc(align, desc_ct, sizeof(dma_descriptor_t), caps);
bs->tx_desc_link = heap_caps_aligned_calloc(align, desc_ct, sizeof(dma_descriptor_t), caps);
if (!bs->rx_desc_link || !bs->tx_desc_link) {
ret = ESP_ERR_NO_MEM;
goto err;
}
// create TX channel and RX channel, they should reside in the same DMA pair
gdma_channel_alloc_config_t tx_alloc_config = {
.flags.reserve_sibling = 1,
.direction = GDMA_CHANNEL_DIRECTION_TX,
};
ESP_GOTO_ON_ERROR(new_dma_channel(&tx_alloc_config, &bs->tx_channel, bus), err, TAG, "failed to create GDMA TX channel");
gdma_channel_alloc_config_t rx_alloc_config = {
.direction = GDMA_CHANNEL_DIRECTION_RX,
.sibling_chan = bs->tx_channel,
};
ESP_GOTO_ON_ERROR(new_dma_channel(&rx_alloc_config, &bs->rx_channel, bus), err, TAG, "failed to create GDMA RX channel");
gdma_connect(bs->rx_channel, g_bitscrambler_periph_desc[attach_to].dma_trigger);
gdma_connect(bs->tx_channel, g_bitscrambler_periph_desc[attach_to].dma_trigger);
gdma_strategy_config_t gdma_strategy_conf = {
.auto_update_desc = true,
.owner_check = false,
};
gdma_apply_strategy(bs->rx_channel, &gdma_strategy_conf);
gdma_apply_strategy(bs->tx_channel, &gdma_strategy_conf);
gdma_rx_event_callbacks_t rx_cbs = {
.on_recv_eof = trans_done_cb,
};
gdma_register_rx_event_callbacks(bs->rx_channel, &rx_cbs, bs);
*handle = (bitscrambler_handle_t)bs;
return ESP_OK;
err:
bitscrambler_loopback_free(&bs->bs);
free(bs);
return ret;
}
//note this is never called directly; bitscrambler_free calls this to clear
//the loopback-specific things of a loopback bitscrambler.
//bitscrambler_loopback_create also calls this in an error situation, so
//we should only delete not-NULL members.
void bitscrambler_loopback_free(bitscrambler_handle_t bs)
{
bitscrambler_loopback_t *bsl = (bitscrambler_loopback_t*)bs;
if (bsl->rx_channel) {
gdma_disconnect(bsl->rx_channel);
gdma_del_channel(bsl->rx_channel);
}
if (bsl->tx_channel) {
gdma_disconnect(bsl->tx_channel);
gdma_del_channel(bsl->tx_channel);
}
if (bsl->sema_done) {
vSemaphoreDelete(bsl->sema_done);
}
free(bsl->rx_desc_link);
free(bsl->tx_desc_link);
}
static int fill_dma_links(dma_descriptor_t *link, void *buffer, size_t len_bytes, int set_eof)
{
uint8_t *buffer_p = (uint8_t*)buffer;
int link_ct = 0;
for (int p = 0; p < len_bytes; p += DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED) {
int seg_len = len_bytes - p;
if (seg_len > DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED) {
seg_len = DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED;
}
link[link_ct].dw0.size = seg_len;
link[link_ct].dw0.length = seg_len;
link[link_ct].dw0.err_eof = 0;
link[link_ct].dw0.suc_eof = 0;
link[link_ct].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
link[link_ct].buffer = &buffer_p[p];
link[link_ct].next = &link[link_ct + 1];
link_ct++;
}
link[link_ct - 1].next = NULL; //fix last entry to end transaction
if (set_eof) {
link[link_ct - 1].dw0.suc_eof = 1;
}
return link_ct;
}
/*
A BitScrambler program could theoretically take a bunch of time to run, e.g. when transferring from PSRAM to PSRAM.
However, given that this is a memory copy, it feels stupid to have a 'soft' parameter as a timeout; we do need a timeout,
however, as the BitScrambler program may be buggy and e.g. never read or write anything.
As an upper limit for a timeout, we can assume the backing memory is quad psram @ 20MHz, meaning we have a throughput
of around 10MByte/second. Any BitScrambler program is going to be lots faster than that, simply because it doesn't
have the instructions to delay writing by much. Just for safety, we can add an extra factor of 10 to that, making
us assume a minimum throughput of 1MByte/sec.
*/
#define BS_MIN_BYTES_PER_SEC 1000000
/*
We'll also add a few FreeRTOS ticks to the delay, so tiny data transfers won't have an impossibly short timeout.
*/
#define BS_TIMEOUT_BASE_TICKS 3
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)
{
//Note that buffer_in and buffer_out are from the perspective of the BitScrambler,
//however tx/rx are from the perspective of the memory. So buffer_in=tx, buffer_out=rx.
esp_err_t ret = ESP_OK;
bitscrambler_loopback_t *bsl = (bitscrambler_loopback_t*)bs;
if (length_bytes_in > bsl->max_transfer_sz_bytes) {
return ESP_ERR_INVALID_SIZE;
}
if (length_bytes_out > bsl->max_transfer_sz_bytes) {
return ESP_ERR_INVALID_SIZE;
}
//Casual check to see if the buffer is aligned to cache requirements.
esp_dma_mem_info_t dma_mem_info = {
.dma_alignment_bytes = 4
};
//Note: we know the size of the data, but not of the buffer that contains it, so we set length=0.
if (!esp_dma_is_buffer_alignment_satisfied(buffer_in, 0, dma_mem_info)) {
ESP_LOGE(TAG, "buffer_in not aligned to DMA requirements");
return ESP_ERR_INVALID_ARG;
}
if (!esp_dma_is_buffer_alignment_satisfied(buffer_out, 0, dma_mem_info)) {
ESP_LOGE(TAG, "buffer_out not aligned to DMA requirements");
return ESP_ERR_INVALID_ARG;
}
gdma_reset(bsl->rx_channel);
gdma_reset(bsl->tx_channel);
bitscrambler_reset(bs);
int link_ct_in = fill_dma_links(bsl->tx_desc_link, buffer_in, length_bytes_in, 1);
int link_ct_out = fill_dma_links(bsl->rx_desc_link, buffer_out, length_bytes_out, 0);
//Note: we add the ESP_CACHE_MSYNC_FLAG_UNALIGNED flag for now as otherwise esp_cache_msync will complain about
//the size not being aligned... we miss out on a check to see if the address is aligned this way. This needs to
//be improved, but potentially needs a fix in esp_cache_msync not to check the size.
esp_cache_msync(bsl->rx_desc_link, link_ct_out * sizeof(dma_descriptor_t), ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
esp_cache_msync(bsl->tx_desc_link, link_ct_in * sizeof(dma_descriptor_t), ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
esp_cache_msync(buffer_in, length_bytes_in, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
gdma_start(bsl->rx_channel, (intptr_t)bsl->rx_desc_link);
gdma_start(bsl->tx_channel, (intptr_t)bsl->tx_desc_link);
bitscrambler_start(bs);
int timeout_ms = (length_bytes_out + length_bytes_in) / (BS_MIN_BYTES_PER_SEC / 1000);
int timeout = pdMS_TO_TICKS(timeout_ms) + BS_TIMEOUT_BASE_TICKS;
if (!xSemaphoreTake(bsl->sema_done, timeout)) {
gdma_reset(bsl->rx_channel);
gdma_reset(bsl->tx_channel);
bitscrambler_reset(bs);
ESP_LOGE(TAG, "bitscrambler_loopback_run: timed out waiting for BitScrambler program to complete!");
ret = ESP_ERR_TIMEOUT;
}
esp_cache_msync(buffer_out, length_bytes_out, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
if (bytes_written) {
size_t l = 0;
for (int i = 0; i < link_ct_out; i++) {
l += bsl->rx_desc_link[i].dw0.length;
if (bsl->rx_desc_link[i].dw0.suc_eof) {
break;
}
}
*bytes_written = l;
}
return ret;
}

View File

@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
//This file contains private functions for interop between bitscrambler.c
//and bitscrambler_loopback.c.
#pragma once
#include "bitscrambler_private.h"
#ifdef __cplusplus
extern "C" {
#endif
void bitscrambler_loopback_free(bitscrambler_handle_t bs);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
//This file contains private functions for interop between bitscrambler.c
//and bitscrambler_loopback.c.
#pragma once
#include <stdbool.h>
#include "soc/bitscrambler_peri_select.h"
#include "hal/bitscrambler_ll.h"
typedef struct bitscrambler_t bitscrambler_t;
struct bitscrambler_t {
bitscrambler_config_t cfg;
bitscrambler_dev_t *hw;
bool loopback; //true if this is a loopback bitscrambler, i.e. the RX
//channel is also claimed
};
esp_err_t bitscrambler_init_loopback(bitscrambler_handle_t handle, const bitscrambler_config_t *config);

View File

@ -0,0 +1,98 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_err.h"
#include "hal/bitscrambler_types.h"
#include "soc/bitscrambler_peri_select.h"
#ifdef __cplusplus
extern "C" {
#endif
#define BITSCRAMBLER_PROGRAM(VAR, NAME) extern const uint8_t VAR[] asm("_binary_bitscrambler_program_" NAME "_start")
typedef struct bitscrambler_t *bitscrambler_handle_t;
/**
* @brief BitScrambler configuration
*/
typedef struct {
bitscrambler_direction_t dir; /*!< Direction (tx or rx) */
int attach_to; /*!< Peripheral to attach to. One of SOC_BITSCRAMBLER_ATTACH_. */
} bitscrambler_config_t;
/**
* @brief Allocate BitScrambler handle for a hardware channel
*
* @param config Configuration for requested BitScrambler
* @param[out] handle BitScrambler controller handle
*
* @return
* - ESP_OK
* - ESP_ERR_NO_MEM: No memory available
* - ESP_ERR_NOT_FOUND: No free hardware channel available
*/
esp_err_t bitscrambler_new(const bitscrambler_config_t *config, bitscrambler_handle_t *handle);
/**
* @brief Free previously allocated BitScrambler handle
*
* @param handle Previously allocated handle
*/
void bitscrambler_free(bitscrambler_handle_t handle);
/**
* @brief Load a BitScrambler binary program into BitScrambler memory
*
* @param handle BitScrambler handle
* @param program Binary program to load
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG: Not a valid or recognized BitScrambler binary, or invalid handle
*/
esp_err_t bitscrambler_load_program(bitscrambler_handle_t handle, const void *program);
/**
* @brief Load data into the Look-Up Table
*
* @param handle BitScrambler handle
* @param lut Data to load
* @param size_bytes Size of the data, in bytes
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG: Invalid handle or lut pointer
*/
esp_err_t bitscrambler_load_lut(bitscrambler_handle_t handle, void *lut, size_t size_bytes);
/**
* @brief Start executing BitScrambler program
*
* @param handle BitScrambler handle
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG: Invalid handle
*/
esp_err_t bitscrambler_start(bitscrambler_handle_t handle);
/**
* @brief Reset BitScrambler program and FIFOs for a new transaction. Note that this does not
* affect the loaded program itself.
*
* @param handle BitScrambler handle
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG: Invalid handle
*/
esp_err_t bitscrambler_reset(bitscrambler_handle_t handle);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "driver/bitscrambler.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Create handle for BitScrambler in loopback mode
*
* @note Use bitscrambler_free to free created handle.
*
* @param[out] handle BitScrambler handle
* @param 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.
* @param max_transfer_sz_bytes Maximum transfer size, in bytes, of either the incoming or outgoing data
* fed to bitscrambler_loopback_run.
*
* @returns
* - 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_create(bitscrambler_handle_t *handle, int attach_to, size_t max_transfer_sz_bytes);
/**
* @brief Run Bitscrambler program on a data buffer
*
* @param bs BitScrambler handle. This BitScrambler should have a program loaded using
* bitscrambler_load_program()
* @param buffer_in Data to feed into the BitScrambler
* @param length_bytes_in Size of the data in buffer_in, in bytes
* @param buffer_out Buffer for BitScrambler to write processed data to
* @param length_bytes_out Size of output buffer
* @param[out] bytes_written Pointer to variable to store the size of actual data written
* to output buffer. Can be NULL if not needed.
*
* @returns
* - ESP_OK
* - ESP_ERR_INVALID_SIZE if a buffer size exceeds max_transfer_sz_bytes
* - ESP_ERR_TIMEOUT if BitScrambler program does not complete
*/
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);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_private/gdma.h"
#include "soc/bitscrambler_peri_select.h"
typedef struct {
gdma_trigger_t dma_trigger;
int bus;
} bitscrambler_periph_desc_t;
extern const bitscrambler_periph_desc_t g_bitscrambler_periph_desc[];

View File

@ -0,0 +1,18 @@
# target_bitscrambler_add_src
#
# Assemble BitScrambler sources and embed into the application.
function(target_bitscrambler_add_src s_sources)
if(NOT CMAKE_BUILD_EARLY_EXPANSION)
spaces2list(s_sources)
foreach(source ${s_sources})
get_filename_component(source ${source} ABSOLUTE BASE_DIR ${CMAKE_CURRENT_LIST_DIR})
get_filename_component(basename ${source} NAME_WE)
set(ps_output ${CMAKE_CURRENT_BINARY_DIR}/${basename}.bsbin)
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})
target_add_binary_data(${COMPONENT_LIB} ${ps_output} BINARY RENAME_TO bitscrambler_program_${basename})
endforeach()
endif()
endfunction()

View File

@ -0,0 +1,5 @@
components/esp_driver_bitscrambler/test_apps/bitscrambler:
disable:
- if: SOC_BITSCRAMBLER_SUPPORTED != 1
depends_components:
- esp_driver_bitscrambler

View File

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.16)
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_bitscrambler)

View File

@ -0,0 +1,2 @@
| Supported Targets | ESP32-P4 |
| ----------------- | -------- |

View File

@ -0,0 +1,18 @@
set(srcs "test_app_main.c")
if(CONFIG_SOC_BITSCRAMBLER_SUPPORTED)
list(APPEND srcs "test_bitscrambler.c")
endif()
set(priv_requires
unity
esp_driver_bitscrambler
)
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "."
PRIV_REQUIRES ${priv_requires}
WHOLE_ARCHIVE TRUE)
target_bitscrambler_add_src("timeout.bsasm")
target_bitscrambler_add_src("trivial.bsasm")

View File

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "unity.h"
#include "unity_test_utils.h"
#include "esp_heap_caps.h"
#include "sdkconfig.h"
#define TEST_MEMORY_LEAK_THRESHOLD (400)
void setUp(void)
{
unity_utils_record_free_mem();
}
void tearDown(void)
{
unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD);
}
void app_main(void)
{
unity_run_menu();
}

View File

@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "unity.h"
#include "unity_test_utils.h"
#include "driver/bitscrambler.h"
#include "driver/bitscrambler_loopback.h"
#include "esp_dma_utils.h"
BITSCRAMBLER_PROGRAM(bitscrambler_program_trivial, "trivial");
BITSCRAMBLER_PROGRAM(bitscrambler_program_timeout, "timeout");
TEST_CASE("Basic BitScrambler I/O", "[bs]")
{
int len = 0x4010;
uint8_t *data_in = heap_caps_aligned_calloc(8, 1, len, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
uint8_t *data_out = heap_caps_aligned_calloc(8, 1, len, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
TEST_ASSERT_NOT_NULL(data_in);
TEST_ASSERT_NOT_NULL(data_out);
for (int i = 0; i < len; i++) {
data_in[i] = (uint8_t)rand();
data_out[i] = 0xFF;
}
bitscrambler_handle_t bs;
TEST_ESP_OK(bitscrambler_loopback_create(&bs, SOC_BITSCRAMBLER_ATTACH_I2S0, len));
TEST_ESP_OK(bitscrambler_load_program(bs, bitscrambler_program_trivial));
size_t res_len;
TEST_ESP_OK(bitscrambler_loopback_run(bs, data_in, len, data_out, len, &res_len));
bitscrambler_free(bs);
TEST_ASSERT_EQUAL_HEX8_ARRAY(data_in, data_out, len);
free(data_in);
free(data_out);
}
TEST_CASE("Timeout on stuck program", "[bs]")
{
int len = 4096 * 10;
uint8_t *data_in = heap_caps_aligned_calloc(8, 1, len, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
uint8_t *data_out = heap_caps_aligned_calloc(8, 1, len, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
bitscrambler_handle_t bs;
TEST_ESP_OK(bitscrambler_loopback_create(&bs, SOC_BITSCRAMBLER_ATTACH_I2S0, len));
TEST_ESP_OK(bitscrambler_load_program(bs, bitscrambler_program_timeout));
esp_err_t err = bitscrambler_loopback_run(bs, data_in, len, data_out, len, NULL);
TEST_ASSERT(err == ESP_ERR_TIMEOUT);
bitscrambler_free(bs);
free(data_in);
free(data_out);
}

View File

@ -0,0 +1,10 @@
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
#Does nothing, so host logic will go into timeout
loop:
read 0,
write 0,
jmp loop

View File

@ -0,0 +1,11 @@
# Example bitscrambler program. Does nothing but forward all bytes.
cfg trailing_bytes 12 # Let M0/M1 empty when EOF on input is found
cfg prefetch true # We expect M0/M1 to be filled
cfg lut_width_bits 8 # Not really applicable here
loop:
set 0..31 0..31,
write 32,
read 32,
jmp loop

View File

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32p4
@pytest.mark.generic
def test_bitscrambler(dut: Dut) -> None:
dut.run_all_single_board_cases()

View File

@ -0,0 +1 @@
# CONFIG_ESP_TASK_WDT_EN is not set

View File

@ -108,8 +108,8 @@ esp_err_t esp_cache_msync(void *addr, size_t size, int flags)
}
uint32_t cache_line_size = cache_hal_get_cache_line_size(cache_level, cache_type);
if ((flags & ESP_CACHE_MSYNC_FLAG_UNALIGNED) == 0) {
bool aligned_addr = (((uint32_t)addr % cache_line_size) == 0) && ((size % cache_line_size) == 0);
ESP_RETURN_ON_FALSE_ISR(aligned_addr, ESP_ERR_INVALID_ARG, TAG, "start address: 0x%" PRIx32 ", or the size: 0x%" PRIx32 " is(are) not aligned with cache line size (0x%" PRIx32 ")B", (uint32_t)addr, (uint32_t)size, cache_line_size);
bool aligned_addr = (((uint32_t)addr % cache_line_size) == 0);
ESP_RETURN_ON_FALSE_ISR(aligned_addr, ESP_ERR_INVALID_ARG, TAG, "start address: 0x%" PRIx32 " is not aligned with cache line size (0x%" PRIx32 ")B", (uint32_t)addr, cache_line_size);
}
s_acquire_mutex_from_task_context();

View File

@ -331,6 +331,10 @@ config SOC_PM_SUPPORTED
bool
default y
config SOC_BITSCRAMBLER_SUPPORTED
bool
default y
config SOC_XTAL_SUPPORT_40M
bool
default y

View File

@ -99,6 +99,7 @@
#define SOC_LIGHT_SLEEP_SUPPORTED 1
#define SOC_DEEP_SLEEP_SUPPORTED 1
#define SOC_PM_SUPPORTED 1
#define SOC_BITSCRAMBLER_SUPPORTED 1
/*-------------------------- XTAL CAPS ---------------------------------------*/
#define SOC_XTAL_SUPPORT_40M 1

View File

@ -93,6 +93,8 @@ MM_SYNC_DOCS = ['api-reference/system/mm_sync.rst']
CAMERA_DOCS = ['api-reference/peripherals/camera_driver.rst']
BITSCRAMBLER_DOCS = ['api-reference/peripherals/bitscrambler.rst']
CLK_TREE_DOCS = ['api-reference/peripherals/clk_tree.rst']
UART_DOCS = ['api-reference/peripherals/uart.rst']
@ -302,6 +304,7 @@ conditional_include_dict = {'SOC_BT_SUPPORTED':BT_DOCS,
'SOC_SDM_SUPPORTED':SDM_DOCS,
'SOC_WIFI_MESH_SUPPORT':WIFI_MESH_DOCS,
'SOC_MIPI_CSI_SUPPORTED':CAMERA_DOCS,
'SOC_BITSCRAMBLER_SUPPORTED':BITSCRAMBLER_DOCS,
'SOC_SPI_SUPPORT_SLAVE_HD_VER2':SPI_SLAVE_HD_DOCS,
'SOC_WIFI_NAN_SUPPORT':NAN_DOCS,
'SOC_JPEG_CODEC_SUPPORTED':JPEG_DOCS,

View File

@ -17,6 +17,8 @@ INPUT += \
$(PROJECT_PATH)/components/usb/include/usb/usb_host.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_types_ch9.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_types_stack.h \
$(PROJECT_PATH)/components/esp_driver_bitscrambler/include/driver/bitscrambler.h \
$(PROJECT_PATH)/components/esp_driver_bitscrambler/include/driver/bitscrambler_loopback.h \
$(PROJECT_PATH)/components/esp_driver_cam/include/esp_cam_ctlr.h \
$(PROJECT_PATH)/components/esp_driver_cam/include/esp_cam_ctlr_types.h \
$(PROJECT_PATH)/components/esp_driver_cam/csi/include/esp_cam_ctlr_csi.h \
@ -51,5 +53,6 @@ INPUT += \
$(PROJECT_PATH)/components/esp_driver_ppa/include/driver/ppa.h \
$(PROJECT_PATH)/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h \
$(PROJECT_PATH)/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/bitscrambler_peri_select.h \
$(PROJECT_PATH)/components/sdmmc/include/sd_pwr_ctrl.h \
$(PROJECT_PATH)/components/sdmmc/include/sd_pwr_ctrl_by_on_chip_ldo.h \

View File

@ -0,0 +1,202 @@
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 {IDF_TARGET_NAME} has an independent TX and RX channel; both can be associated with the same or different peripherals.
Functional Overview
-------------------
.. list::
- `BitScrambler assembly <#bitscrambler-assembly>`__ - covers how a BitScrambler assembly program is structured
- `Build system integration <#bitscrambler-build>`__ - covers how BitScrambler programs are integrated in the ESP-IDF build system
- `Resource allocation and program loading <#bitscrambler-load>`__ Covers how to allocate BitScrambler instances and how to load a program into them
- `Loopback mode <#bitscrambler-loopback>`__ Covers how to use the BitScrambler in loopback mode
.. _bitscrambler-assembly:
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``.
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.
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:
.. code:: asm
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 *{IDF_TARGET_NAME} Technical Reference Manual* > *BitScrambler (BITSCRM)* [`PDF <{IDF_TARGET_TRM_EN_URL}#bitscrm>`__]. 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, 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``.
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`` or ``L``. 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:
.. code:: asm
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 to ``true``, on BitScrambler start it will read 64 bits from the input DMA stream into the input register. If set to ``false``, the input register will be initialized to zero. This setting defaults to ``true`` 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 to ``upstream`` if not specified.
- ``cfg trailing_bytes N`` - This indicates how many dummy bytes will be read or written (depending on the ``eof_on`` setting) before the BitScrambler indicates an end-of-stream on its output. This defaults to ``0`` 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 to ``32`` 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.
.. _bitscrambler-build:
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:
.. code:: cmake
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:
.. code:: c
// 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);
.. _bitscrambler-load:
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 :cpp:func:`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 :cpp:func:`bitscrambler_load_program` to load a program into it, then call :cpp:func:`bitscrambler_loopback_run` to transform a memory buffer using the loaded program. You can call :cpp:func:`bitscrambler_loopback_run` any number of times; it's also permissible to use :cpp:func:`bitscrambler_load_program` to change programs between calls. Finally, to free the hardware resources and clean up memory, call :cpp:func:`bitscrambler_free`.
API Reference
-------------
.. include-build-file:: inc/bitscrambler.inc
.. include-build-file:: inc/bitscrambler_loopback.inc
.. include-build-file:: inc/bitscrambler_peri_select.inc

View File

@ -10,6 +10,7 @@ Peripherals API
:SOC_ADC_DMA_SUPPORTED: adc_continuous
:SOC_ADC_SUPPORTED: adc_calibration
:SOC_ANA_CMPR_SUPPORTED: ana_cmpr
:SOC_BITSCRAMBLER_SUPPORTED: bitscrambler
:SOC_MIPI_CSI_SUPPORTED: camera_driver
:SOC_CLK_TREE_SUPPORTED: clk_tree
:SOC_DAC_SUPPORTED: dac

View File

@ -0,0 +1 @@
.. include:: ../../../en/api-reference/peripherals/bitscrambler.rst

View File

@ -10,6 +10,7 @@
:SOC_ADC_DMA_SUPPORTED: adc_continuous
:SOC_ADC_SUPPORTED: adc_calibration
:SOC_ANA_CMPR_SUPPORTED: ana_cmpr
:SOC_BITSCRAMBLER_SUPPORTED: bitscrambler
:SOC_CLK_TREE_SUPPORTED: clk_tree
:SOC_DAC_SUPPORTED: dac
:SOC_ECDSA_SUPPORTED: ecdsa

View File

@ -38,6 +38,12 @@ examples/peripherals/analog_comparator:
- esp_driver_gpio
- esp_driver_ana_cmpr
examples/peripherals/bitscrambler:
disable:
- if: SOC_BITSCRAMBLER_SUPPORTED != 1
depends_components:
- esp_driver_bitscrambler
examples/peripherals/camera/dvp_isp_dsi:
disable:
- if: SOC_ISP_DVP_SUPPORTED != 1 or SOC_MIPI_DSI_SUPPORTED != 1

View File

@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(bitscrambler_example)

View File

@ -0,0 +1,29 @@
| Supported Targets | ESP32-P4 |
| ----------------- | -------- |
# BitScrambler Loopback Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates how to load and control the BitScrambler. Specifically, the loopback mode of the BitScrambler is used to transform a buffer of data into a different format.
## How to Use Example
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT build flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,6 @@
idf_component_register(SRCS "bitscrambler_example_main.c"
PRIV_REQUIRES "esp_driver_bitscrambler"
INCLUDE_DIRS ".")
target_bitscrambler_add_src("example.bsasm")

View File

@ -0,0 +1,63 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include <stdio.h>
#include "driver/bitscrambler_loopback.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_heap_caps.h"
//Assign a symbol to the example bitscrambler program. Note that the actual
//assembly and including in the binary happens in the CMakeLists.txt file.
BITSCRAMBLER_PROGRAM(bitscrambler_program_example, "example");
static const char *TAG = "bs_example";
static const uint8_t testdata[] = {
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, //should give ff 00 ....
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, //should come out as 80 80 80...
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, //'unity matrix', should come out the same
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, //should come out as 00 ff 00 ff ...
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, //should come out as ff 00 ff 00 ...
};
void app_main(void)
{
ESP_LOGI(TAG, "BitScrambler example main");
uint8_t *data_in, *data_out;
int len = sizeof(testdata);
data_in = heap_caps_calloc(len, 1, MALLOC_CAP_DMA);
data_out = heap_caps_calloc(len, 1, MALLOC_CAP_DMA);
assert(data_in);
assert(data_out);
memcpy(data_in, testdata, len);
bitscrambler_handle_t bs;
ESP_ERROR_CHECK(bitscrambler_loopback_create(&bs, SOC_BITSCRAMBLER_ATTACH_I2S0, len));
ESP_ERROR_CHECK(bitscrambler_load_program(bs, bitscrambler_program_example));
size_t size;
ESP_ERROR_CHECK(bitscrambler_loopback_run(bs, data_in, len, data_out, len, &size));
bitscrambler_free(bs);
printf("BitScrambler program complete. Input %d, output %d bytes:\n", len, size);
for (int i = 0; i < size; i++) {
printf("%02X ", data_out[i]);
if ((i & 7) == 7) {
printf("\n");
}
}
printf("\n");
}

View File

@ -0,0 +1,100 @@
# Example bitscrambler program. Reads in 8 bytes and spits out 8 bytes are
# the 'rotated' version of the input bytes. Specifically, output byte 0
# consists of bit 0 of input byte 0, bit 0 of input byte 1, bit 0 of input
# byte 2 etc. Output byte 1 consists of bit 1 of input byte 0, bit 1 of
# input byte 1, bit 1 of input byte 2, etc.
cfg trailing_bytes 64 #If we have an EOF on the input, we still
#need to process the 64 bits in M0/M1
cfg prefetch true #We expect M0/M1 to be filled
cfg lut_width_bits 8 #Not really applicable here
loop:
# Note: we start with 64 bits in M0 and M1, so we can immediately start outputting.
#output byte 0
set 0 0,
set 1 8,
set 2 16,
set 3 24,
set 4 32,
set 5 40,
set 6 48,
set 7 56,
#output byte 1
set 8 1,
set 9 9,
set 10 17,
set 11 25,
set 12 33,
set 13 41,
set 14 49,
set 15 57,
#output byte 2
set 16 2,
set 17 10,
set 18 18,
set 19 26,
set 20 34,
set 21 42,
set 22 50,
set 23 58,
#output byte 3
set 24 3,
set 25 11,
set 26 19,
set 27 27,
set 28 35,
set 29 43,
set 30 51,
set 31 59,
#as we can only write 32 bits at the same time, we write these and
#route the other 32 bits in the next instruction.
write 32
#2nd instruction: route the other 32 bits and write.
#output byte 4
set 0 4,
set 1 12,
set 2 20,
set 3 28,
set 4 36,
set 5 44,
set 6 52,
set 7 60,
#output byte 5
set 8 5,
set 9 13,
set 10 21,
set 11 29,
set 12 37,
set 13 45,
set 14 53,
set 15 61,
#output byte 6
set 16 6,
set 17 14,
set 18 22,
set 19 30,
set 20 38,
set 21 46,
set 22 54,
set 23 62,
#output byte 7
set 24 7,
set 25 15,
set 26 23,
set 27 31,
set 28 39,
set 29 47,
set 30 55,
set 31 63,
#Write these last 32 bits.
write 32,
#Note we can read the first half of the next 64 bits into the
#input buffer as the load happens at the end of the instruction.
read 32
#Read the 2nd half of the 64 bits in, and loop back to the start.
read 32,
jmp loop

969
tools/bsasm.py Executable file
View File

@ -0,0 +1,969 @@
#!/usr/bin/env python
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import argparse
import copy
import math
import re
import struct
import sys
from typing import Any
from typing import cast
from typing import Dict
from typing import List
from typing import Tuple
from typing import Type
from typing import TypedDict
# Increase this if you change the on-disk binary output format of the BitScrambler so it's
# not compatible with previous versions
BITSCRAMBLER_BINARY_VER = 1
# If we have multiple BitScrambler versions, this'll indicate what hardware we're expecting
# to run on.
BITSCRAMBLER_HW_REV = 0
class Element(TypedDict, total=False):
more_in_instruction: bool
is_label: bool
is_meta: bool
text: str
line: int
column: int
class InputFlags(TypedDict, total=False):
rel_addr: int
ctrsel: int
lutsel: int
class Input(TypedDict, total=False):
text: str
ele: Element
input: int
muxsel: int
flags: InputFlags
class Opcode(TypedDict, total=False):
ele: Element
op: int
c: int
end_val: int
ctr_val: int
tgt: int
h: int
l: int
ctr_add: int
ctl_cond_src: Input
class Inst(TypedDict, total=False):
op: Opcode
mux: Dict[int, Input]
write: int
read: int
# 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
# ignored. Labels are the first word in a line followed by a colon.
# Meta-instructions start with 'cfg' and end at the end of the line;
# they cannot contain commas. Instruction bundles consist of multiple
# sub-instructions separated by a comma. An instruction bundle ends
# when there is a newline and the last non-whitespace character was
# not a comma. An element is defined as either a meta-instruction or a
# sub-instruction.
# This routine uses a state machine to keep track of what it has parsed.
# It handles comments separately as they essentially can be ignored by
# the rest of the state machine; that only needs to see the newline at
# the end of the sentence.
# The output of this routine is an array with element descriptions:
# line -> line the element starts on
# column -> column the element starts on
# text -> text of the element; no whitespace at the start or end and
# all whitespace between words changed to one single space
# more_in_instruction -> false if this element is a meta-instruction,
# label or at the end of an instruction bundle.
# is_label -> true if element is a label
# Note that this parser can't see the difference between a
# meta-instruction and an instruction in an instruction bundle: errors
# like a bundle containing a meta-instruction needs to be detected
# elsewhere.
# (Note that this handrolled parser might not be the best option wrt ease
# to understand and maintain, and that has been brought up by Espressif
# colleagues. I've spent some time trying to implement it as a PyParsing
# syntax, but it's very hard to get it to agree with the
# newline-is-sometimes-an-end-of-statement-and-sometimes-not. Antlr might
# be a better choice for that. However, rewriting this likely is more
# work than the ease of fixing bugs in the current parser warrants. If
# that assumption turns out to be very wrong and you're the unlucky
# 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.
# Easiest way to fix it is to make sure the src always ends in a newline.
src = src + '\n'
# Define the various states
ST_WH_PRE = 0 # Whitespace before an instruction.
ST_ELEMENT = 1 # Inside a subinstruction or meta-instruction
ST_WH_IN_EL = 2 # Whitespace, but we're unsure if this is at the end of the element
ST_AFTER_COMMA = 3 # Encountered a comma, plus possibly whitespace
state = ST_WH_PRE
# We keep track of row/col for error reporting
line = 0
column = 0
elements: List[Element] = []
curr_element: Element = {}
in_comment = False # True if we're anywhere between a # and a newline.
for ch in src:
# We use these as flags later in the code to start or finish an element.
start_element = False
finish_element = False
if in_comment:
# If we're in a comment, go back to no-comment mode at the end of the line.
# We'll need to parse the newline, so this is not part of the big
# if statement below.
if ch == '\n':
in_comment = False
# Big statemachine handler depending on ch.
if in_comment:
# Ignore any character in comment
pass
elif ch == '#':
# Start of a comment.
in_comment = True
elif ch in [' ', '\t']:
# Whitespace. This can be before an element (ignored) or inside an element (might need
# to insert space if element continues after this)
if state == ST_ELEMENT:
state = ST_WH_IN_EL
elif ch == '\n' or ch == '\r':
# Newline. If not after a comma, this finishes the element. If after a comma,
# this can be ignored as whitespace-before-the-next-element.
if state == ST_ELEMENT or state == ST_WH_IN_EL:
finish_element = True
state = ST_WH_PRE
elif state == ST_AFTER_COMMA:
state = ST_WH_PRE
elif ch == ',':
# A comma. If this is at the end of an element, this finishes the element and
# prepares for more elements in the instruction.
if state == ST_ELEMENT or state == ST_WH_IN_EL:
curr_element['more_in_instruction'] = True
finish_element = True
state = ST_AFTER_COMMA
elif state == ST_AFTER_COMMA:
raise RuntimeError(
f'Line {line} column {column}: Empty subinstruction found'
)
elif state == ST_WH_PRE:
raise RuntimeError(f'Line {line} column {column}: Stray comma found')
elif ch == ':':
# This indicates the current element is a label; a colon is not used anywhere else.
if state == ST_ELEMENT:
# Check if label is before any instruction
if len(elements) == 0 or not elements[-1]['more_in_instruction']:
# Looks okay.
curr_element['is_label'] = True
finish_element = True
state = ST_WH_PRE
else:
raise RuntimeError(
f'Line {line} column {column}: Stray semicolon found'
)
else:
raise RuntimeError(f'Line {line} column {column}: Stray semicolon found')
else:
# Any other characters.
if state == ST_ELEMENT:
curr_element['text'] += ch
elif state == ST_WH_PRE or state == ST_AFTER_COMMA:
start_element = True
state = ST_ELEMENT
elif state == ST_WH_IN_EL:
curr_element['text'] += ' ' + ch
state = ST_ELEMENT
# Handle starting and finishing of elements
if start_element:
if 'line' in curr_element:
raise RuntimeError(
f'Line {line} column {column}: Internal error: Element started twice!'
)
curr_element['line'] = line
curr_element['column'] = column
curr_element['text'] = ch
curr_element['more_in_instruction'] = False
curr_element['is_label'] = False
if finish_element:
if 'line' not in curr_element:
raise RuntimeError(
f'Line {line} column {column}: Internal error: Element finished while none started'
)
elements.append(curr_element)
curr_element = {}
# Handle line and column counts
if ch == '\n':
line = line + 1
column = 0
else:
column = column + 1
return elements
# Specific syntax error exception. Reports details about the element[s] to make debugging
# assembly sources easier.
class bsasm_syntax_error(Exception):
def __new__(cls: Type['bsasm_syntax_error'], *args: str, **kwargs: str) -> 'bsasm_syntax_error': # noqa: F821
return cast(bsasm_syntax_error, super().__new__(cls))
def __init__(self, *args: Any) -> None: # noqa: F821
if len(args) == 2:
ele = args[0]
message = args[1]
self.msg = 'Line {} column {}: "{}": {}'.format(ele['line'], ele['column'], ele['text'], message)
else:
ele1 = args[0]
ele2 = args[1]
message = args[1]
self.msg = 'Line {} col {}: "{}" and line {} col {}: "{}": {}'.format(ele1['line'],
ele1['column'],
ele1['text'],
ele2['line'],
ele2['column'],
ele2['text'],
message)
def __str__(self) -> str: # noqa: F821
return self.msg
# Definition of possible meta 'cfg' commands
class Meta_inst_def(TypedDict, total=False):
op: str
default: int
enum: Dict[str, int]
min: int
max: int
meta_inst_defs: List[Meta_inst_def] = [
# RX_FETCH_MODE: 0 - on startup fill M0/M1, 1 - don't
{'op': 'prefetch', 'default': 1, 'enum': {'true': 1, 'false': 0, '1': 1, '0': 0}},
# Amount of bytes read from input or written to output (depending on eof_on)
# after EOF on input before we send an EOF to the output.
{'op': 'trailing_bytes', 'default': 0, 'min': 0, 'max': 8192},
# Source where 'trailing' counts the bytes after input EOF before generating an output EOF
{'op': 'eof_on', 'default': 1, 'enum': {'upstream': 1, 'downstream': 0}},
# Width, in bits, of the LUT memory
{'op': 'lut_width_bits', 'default': 32, 'enum': {'8': 8, '16': 16, '32': 32}},
]
# Check if element is a meta element
def is_meta(ele: Element) -> bool:
if ele['text'].lower()[0:4] == 'cfg ':
return True
if ele['text'].lower()[0:4] == 'lut ':
return True
return False
# Parse a config meta-instruction: check if the values are within range and convert from enums to values
def parse_meta_cfg(ele: Element) -> Tuple[str, int]:
words = ele['text'].lower().split(' ')
meta_key = ''
if len(words) != 3:
raise bsasm_syntax_error(ele, f'too many arguments to cfg statement')
for meta_inst_def in meta_inst_defs:
if meta_inst_def['op'] == words[1]:
if 'enum' in meta_inst_def:
if words[2] in meta_inst_def['enum']:
meta_key = words[1]
meta_value = meta_inst_def['enum'][words[2]]
else:
raise bsasm_syntax_error(
ele, f'{words[2]} is not an allowed value for {words[1]}'
)
else:
v = parse_val(ele, words[2], meta_inst_def['min'], meta_inst_def['max'])
meta_key = words[1]
meta_value = v
if meta_key == '':
raise bsasm_syntax_error(
ele, f'{words[1]} is not a recognized meta-instruction'
)
return (meta_key, meta_value)
# Check the number of arguments an element has vs what it should have
def check_arg_ct(ele: Element, words: list, ct: int) -> None:
if len(words) != ct:
raise bsasm_syntax_error(ele, 'invalid number of arguments')
# Parses a textual range like '1..10' into an actual Python range
def parse_output_range(ele: Element, text: str) -> range:
b = re.findall(r'^([0-9]+)(?:\.\.([0-9]+))?$', text)
if not b:
raise bsasm_syntax_error(ele, f'{text} not a valid integer or range)')
start = int(b[0][0])
if b[0][1] != '':
end = int(b[0][1])
else:
end = int(b[0][0])
if start < 0 or end < 0 or start > 31 or end > 31:
raise bsasm_syntax_error(ele, f"'{text}' is not a valid integer or range)")
if start <= end:
return range(start, end + 1)
else:
return range(start, end - 1, -1)
# Resolve an input to a mux selection number and CTL_LUT_SEL/CTL_SRC_SEL/rel_addr
# settings, if those need those to be in a specific state.
def parse_input(ele: Element, text: str, meta: Dict[str, int]) -> Input:
# Note that strings in the input def arrays need to be lower case.
inputs = (
# REG_MEM0
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15',
'16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31',
# This region is multiplexed using SRC_SEL and LUT_SEL
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
# AUX
'bl<=o0', 'bl>o0', 'bl=o0', 'bl<=o8', 'bl>o8', 'bl=o8', 'bl<=o16', 'bl>o16',
'bl=o16', 'bl<=o24', 'bl>o24', 'bl=o24', 'bh<=o0', 'bh>o0', 'bh=o0', 'bh<=o8',
'bh>o8', 'bh=o8', 'bh<=o16', 'bh>o16', 'bh=o16', 'bh<=o24', 'bh>o24', 'bh=o24',
'b<=o0', 'b>o0', 'b=o0', 'b<=o16', 'b>o16', 'b=o16', 'l', 'h',
# Reg_last
'o0', 'o1', 'o2', 'o3', 'o4', 'o5', 'o6', 'o7', 'o8', 'o9', 'o10', 'o11', 'o12', 'o13', 'o14', 'o15',
'o16', 'o17', 'o18', 'o19', 'o20', 'o21', 'o22', 'o23', 'o24', 'o25', 'o26', 'o27', 'o28', 'o29', 'o30', 'o31',
)
inputs_ctrsel_set = (
# Counter reg
'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'a10', 'a11', 'a12', 'a13', 'a14', 'a15',
'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'b10', 'b11', 'b12', 'b13', 'b14', 'b15',
)
inputs_ctrsel_clr = (
# REG_MEM1
'32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47',
'48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62', '63',
)
# Note that the index of this depends on the selected LUT width.
input_lut_bits = (
'l0', 'l1', 'l2', 'l3', 'l4', 'l5', 'l6', 'l7', 'l8', 'l9', 'l10', 'l11', 'l12', 'l13', 'l14', 'l15',
'l16', 'l17', 'l18', 'l19', 'l20', 'l21', 'l22', 'l23', 'l24', 'l25', 'l26', 'l27', 'l28', 'l29', 'l30', 'l31',
)
# Note where in the counter reg / mem1 reg region the LUT starts, if enabled
lut_starts = {8: 24, 16: 16, 32: 0}
lut_start = lut_starts[meta['lut_width_bits']]
ret: Input = {'text': text, 'ele': ele, 'flags': {}}
# Handle relative addressing
rel_addr = False
if text[-2:] == '+a':
rel_addr = True
text = text[:-2] # chop off the '+a'
# Find in what list the input is, and process accordingly.
if text in inputs:
# These inputs are always accessible regardless of ctrsel/lutsel
ret['input'] = inputs.index(text)
elif text in inputs_ctrsel_set:
# These inputs need ctrsel to be 1
i = inputs_ctrsel_set.index(text)
ret['input'] = i + 32
ret['flags']['ctrsel'] = 1
if i >= lut_start:
ret['flags']['lutsel'] = 0
elif text in inputs_ctrsel_clr:
# These inputs need ctrsel to be 0.
i = inputs_ctrsel_clr.index(text)
ret['input'] = i + 32
ret['flags']['ctrsel'] = 0
if i >= lut_start:
# These overlap the LUT. lutsel cannot be 1 when these are addressed.
ret['flags']['lutsel'] = 0
elif text in input_lut_bits:
# These inputs need lutsel to be 1.
i = input_lut_bits.index(text)
if i > meta['lut_width_bits']:
raise bsasm_syntax_error(
ele,
f"'LUT input {text} referenced, but LUT is configured to be only {meta['lut_width_bits']} bits wide.",
)
ret['input'] = i + 32 + lut_start
ret['flags']['lutsel'] = 1
else:
raise bsasm_syntax_error(ele, f"'Input {text} is not valid.")
if ret['input'] >= 64 and rel_addr:
raise bsasm_syntax_error(
ele, f"'LUT input {text} cannot be relatively addressed."
)
if ret['input'] < 64:
ret['flags']['rel_addr'] = rel_addr
return ret
# Raises an error if two inputs can't be used at the same time
def check_input_compatible(in1: Input, in2: Input) -> None:
if 'flags' not in in1:
return
if 'flags' not in in2:
return
in1f = in1['flags']
in2f = in2['flags']
err = ''
if 'rel_addr' in in1f and 'rel_addr' in in2f and in1f['rel_addr'] != in2f['rel_addr']:
err = 'Cannot have these inputs as both relative and not-relative in the same instruction'
if 'ctrsel' in in1f and 'ctrsel' in in2f and in1f['ctrsel'] != in2f['ctrsel']:
err = 'Cannot have both counters/LUT as well as reg_mem1 inputs in the same instruction'
if 'lutsel' in in1f and 'lutsel' in in2f and in1f['lutsel'] != in2f['lutsel']:
err = 'With the selected LUT width, the LUT input overlaps the selected counter input'
if err != '':
raise bsasm_syntax_error(in1['ele'], in2['ele'], err)
# returns the range as a list of mux selections, plus any ctrsel/lutsel/rel_addr bits needed
# Returns a dictionary with the selected input in 'muxsel' plus a 'flags' dictionary. If a
# rel_addr/lutsel/ctrsel key is in the 'flags' field, that bit must be set or cleared in
# the instruction; if it's not set, the value of that bit doesn't matter for that input.
def parse_input_range(ele: Element, text: str, meta: Dict[str, int]) -> List[Input]:
# Validate the range and split into start and optionally end fields
b = re.findall(r'^([a-z0-9><=+]+)(?:\.\.([a-z0-9<>=+]+))?$', text)
if not b:
raise bsasm_syntax_error(
ele, f'{text} not a valid input selection or range of input selections)'
)
start = parse_input(ele, b[0][0], meta)
if b[0][1] != '':
end = parse_input(ele, b[0][1], meta)
else:
end = start
# Note: this function cannot parse 'weird' ranges, like 31..L1. That's why we limit the ranges
# to not cross the 32-bit boundary, except when referring to reg_mem0/reg_mem1. This will
# generally match what the user tries to do.
if (
start['input'] < 32
and 32 <= end['input'] < 64
and ('flags' not in end or 'ctrsel' not in end['flags'] or end['flags']['ctrsel'] == 0)
and ('flags' not in end or 'lutsel' not in end['flags'] or end['flags']['lutsel'] == 0)
):
pass
elif (
end['input'] < 32
and start['input'] >= 32
and start['input'] < 64
and ('flags' not in start or 'ctrsel' not in start['flags'] or start['flags']['ctrsel'] == 0)
and ('flags' not in start or 'lutsel' not in start['flags'] or start['flags']['lutsel'] == 0)
):
# in case a 'backwards' range like [35..5] is passed
pass
elif math.floor(start['input'] / 32) != math.floor(end['input'] / 32):
errtxt = f'{text} is not a valid range of input selections. '
if 'flags' in start and 'lutsel' in start['flags'] and start['flags']['lutsel'] == 1 \
and ('flags' not in end or 'lutsel' not in end['flags']) and end['input'] < 32:
errtxt += 'Did you forget an L at the end of the range? (e.g. L0..31 instead of L0..L31)'
else:
errtxt += 'Try splitting up the range.'
raise bsasm_syntax_error(ele, errtxt)
# The start and end *should* guaranteed to be compatible by now. Check anyway
# to catch any errors. If this triggers an exception, we have a bug...
check_input_compatible(start, end)
flags: InputFlags = {}
if 'rel_addr' in start['flags']:
flags['rel_addr'] = start['flags']['rel_addr']
if 'rel_addr' in end['flags']:
flags['rel_addr'] = end['flags']['rel_addr']
if 'ctrsel' in start['flags']:
flags['ctrsel'] = start['flags']['ctrsel']
if 'ctrsel' in end['flags']:
flags['ctrsel'] = end['flags']['ctrsel']
if 'lutsel' in start['flags']:
flags['lutsel'] = start['flags']['lutsel']
if 'lutsel' in end['flags']:
flags['lutsel'] = end['flags']['lutsel']
if start['input'] <= end['input']:
r = range(start['input'], end['input'] + 1)
else:
r = range(start['input'], end['input'] - 1, -1)
ret: List[Input] = []
for i in r:
n: Input = {'muxsel': i, 'flags': flags, 'ele': ele}
ret.append(n)
return ret
# Parse a numerical field into an int, between a given minimum and maximum.
def parse_val(ele: Element, text: str, minimum: int, maximum: int) -> int:
try:
if text[:2] == '0x':
n = int(text[2:], 16)
elif text[:2] == '0b':
n = int(text[2:], 2)
else:
n = int(text)
except ValueError:
raise bsasm_syntax_error(ele, f"'{text}' is not an integer")
if n < minimum or n > maximum:
raise bsasm_syntax_error(
ele, f"'{text}' is out of range [{minimum}..{maximum}]"
)
return n
# Return an IP for a label text
def resolve_label(ele: Element, text: str, labels: Dict[str, int]) -> int:
if text in labels:
return labels[text]
# No match. We could technically also see if the label is a direct IP, but I think
# that is more likely to be used erroneously than on purpose. If you read this and
# disagree, feel free to file an issue :)
raise bsasm_syntax_error(ele, f"'{text}': Label not found.")
# Bitfields defining the instructions
OP_LOOP = 0x2000000
OP_ADD = 0x0000000
OP_IF = 0x0010000
OP_IFN = 0x0020000
OP_LDCTD = 0x0030000
OP_LDCTI = 0x0040000
def add_op_to_inst(inst: Inst, op: Opcode, ele: Element) -> None:
if 'op' in inst:
raise bsasm_syntax_error(
inst['op']['ele'], ele, f'Cannot have multiple opcodes in one instruction'
)
op['ele'] = ele
inst['op'] = op
# 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]]:
# This assembler uses two passes: the first finds and resolves global
# stuff, the second one encodes the actual instructions.
# Set the meta-instruction values to their defaults
meta: Dict[str, int] = {}
for meta_inst_def in meta_inst_defs:
meta[meta_inst_def['op']] = meta_inst_def['default']
# Pass 1a: find IPs for labels, mark meta instructions
# ToDo: also resolve 'def' symbols here once we implement them
ip = 0
ip_for_label: Dict[str, int] = {}
inst_is_meta = False
inst_start = True
for ele in elements:
if inst_start and is_meta(ele):
# Start of meta-instruction (can only occur at first ele in instruction)
inst_is_meta = True
if inst_is_meta:
ele['is_meta'] = True
elif ele['is_label']:
# Label. Record its IP.
ip_for_label[ele['text']] = ip
ele['is_meta'] = False
else:
ele['is_meta'] = False
if ele['more_in_instruction']:
inst_start = False
else:
# End of an instruction
inst_start = True # mark next element as start of inst
if (not ele['is_meta']) and (not ele['is_label']):
ip += 1
inst_is_meta = False
# Pass 1B: Collate and parse meta instructions
inst_start = True
for ele in elements:
if inst_start and ele['is_meta']:
if ele['text'][0:4] == 'cfg ':
(key, val) = parse_meta_cfg(ele)
meta[key] = val
if ele['more_in_instruction']:
raise bsasm_syntax_error(
ele, 'garbage after cfg statement detected'
)
inst_start = not ele['more_in_instruction']
# Pass 1C: parse LUT data instructions. We do this after the meta instructions pass
# as it requires the size of the LUT to figure out min/max boundaries.
# Note a lut can be written both as 'lut 1 2 3' as well as 'lut 1,2,3' so we need
# to account for both cases.
lut_minmax_vals = {
8: (-128, 255),
16: (-32768, 65537),
32: (-2147483648, 4294967296 - 1),
}
minmax = lut_minmax_vals[meta['lut_width_bits']]
lut = []
is_lut = False
for ele in elements:
if ele['is_meta']:
if is_lut:
words = ele['text'].split(' ')
for w in words:
lut.append(parse_val(ele, w, minmax[0], minmax[1]))
if ele['text'][0:4] == 'lut ':
is_lut = True
words = ele['text'].split(' ')
for w in words[1:]:
lut.append(parse_val(ele, w, minmax[0], minmax[1]))
if not ele['more_in_instruction']:
is_lut = False
# Pass 2: Parse any instructions
valid_read_write = [0, 8, 16, 32]
insts: List[Inst] = []
def_inst: Inst = {'mux': {}}
inst = copy.deepcopy(def_inst)
op: Opcode
for ele in elements:
if not ele['is_meta'] and not ele['is_label']:
words = ele['text'].lower().split(' ')
if words[0] == 'set':
# set (output) (mux input)
check_arg_ct(ele, words, 3)
outs = parse_output_range(ele, words[1])
ins = parse_input_range(ele, words[2], meta)
if len(ins) != 1 and len(ins) != len(outs):
raise bsasm_syntax_error(ele, 'ranges not the same length')
i = 0
for out in outs:
if out in inst['mux']:
raise bsasm_syntax_error(
ele, f'output {out} already set earlier in instruction'
)
if len(ins) == 1:
# set range input
inst['mux'][out] = ins[0]
else:
# set range range
inst['mux'][out] = ins[i]
i = i + 1
elif words[0] == 'write':
# Write x bits to output fifo
check_arg_ct(ele, words, 2)
no = parse_val(ele, words[1], 0, 32)
if no not in valid_read_write:
raise bsasm_syntax_error(
ele, f'{no} is not a valid amount of bits to write'
)
inst['write'] = no
elif words[0] == 'read':
# Read x bits from input fifo
check_arg_ct(ele, words, 2)
no = parse_val(ele, words[1], 0, 32)
if no not in valid_read_write:
raise bsasm_syntax_error(
ele, f'{no} is not a valid amount of bits to write'
)
inst['read'] = no
elif re.match('loop[ab]', words[0]):
# LOOPc end_val ctr_add tgt
check_arg_ct(ele, words, 4)
op = {'op': OP_LOOP, 'ele': ele}
op['c'] = 1 if words[0][4] == 'b' else 0
op['end_val'] = parse_val(ele, words[1], -32768, 65535) & 0xffff
op['ctr_add'] = parse_val(ele, words[2], -16, 15) & 31
op['tgt'] = resolve_label(ele, words[3], ip_for_label)
add_op_to_inst(inst, op, ele)
elif re.match('add[ab]([hl])?', words[0]):
# ADDc[h|l] ctr_add
check_arg_ct(ele, words, 2)
op = {'op': OP_ADD, 'ele': ele}
op['c'] = 1 if words[0][3] == 'b' else 0
if len(words[0]) == 4:
op['h'] = 1
op['l'] = 1
else:
op['h'] = 1 if words[0][4] == 'h' else 0
op['l'] = 1 if words[0][4] == 'l' else 0
op['ctr_add'] = parse_val(ele, words[1], -32768, 65535) & 0xffff
add_op_to_inst(inst, op, ele)
elif re.match('if(n)?', words[0]):
# IF[N] ctl_cond_src tgt
check_arg_ct(ele, words, 3)
op = {'op': OP_IF if len(words[0]) == 2 else OP_IFN, 'ele': ele}
op['ctl_cond_src'] = parse_input(ele, words[1], meta)
op['tgt'] = resolve_label(ele, words[2], ip_for_label)
add_op_to_inst(inst, op, ele)
elif re.match('ldctd[ab]([hl])?', words[0]):
# LDCTDc[h|l] ctr_set
check_arg_ct(ele, words, 2)
op = {'op': OP_LDCTD}
op['c'] = 1 if words[0][5] == 'b' else 0
if len(words[0]) == 6:
op['h'] = 1
op['l'] = 1
else:
op['h'] = 1 if words[0][6] == 'h' else 0
op['l'] = 1 if words[0][6] == 'l' else 0
op['ctr_add'] = parse_val(ele, words[1], -32768, 65535) & 0xffff
add_op_to_inst(inst, op, ele)
elif re.match('ldcti[ab]([hl])?', words[0]):
# LDCTIc[h|l]
check_arg_ct(ele, words, 1)
op = {'op': OP_LDCTI}
op['c'] = 1 if words[0][5] == 'b' else 0
if len(words[0]) == 6:
op['h'] = 1
op['l'] = 1
else:
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('jmp', words[0]):
# JMP tgt. Pseudo-op, translates to 'IF h tgt'
check_arg_ct(ele, words, 2)
op = {'op': OP_IF}
op['ctl_cond_src'] = parse_input(ele, 'h', meta)
op['tgt'] = resolve_label(ele, words[1], ip_for_label)
add_op_to_inst(inst, op, ele)
elif re.match('nop', words[0]):
# NOP. Pseudo-op, translates to ADDA 0
check_arg_ct(ele, words, 1)
op = {'op': OP_ADD}
op['h'] = 1
op['l'] = 1
op['ctr_add'] = 0
add_op_to_inst(inst, op, ele)
else:
raise bsasm_syntax_error(ele, 'unknown instruction')
if (
(not ele['more_in_instruction'])
and (not ele['is_label'])
and (not ele['is_meta'])
):
insts.append(inst)
inst = copy.deepcopy(def_inst)
return (insts, meta, lut)
# Quick and dirty way to assemble a bytearray from a bunch of bitfields.
# The implementation is not optimal as it handles data bit-by-bit, but it works fine.
class bitstream:
bitpos: int
data: list
curbyte: int
def __init__(self) -> None:
self.data = []
self.curbyte = 0
self.bitpos = 0
# Add a field of `bits` bits with the field having the value `val` at the end
# of the bitstream
def add_bits(self, val: int, bits: int) -> None:
v = val
for i in range(0, bits):
self.curbyte = self.curbyte >> 1
if v & 1:
self.curbyte = self.curbyte | 0x80
v = v >> 1
self.bitpos += 1
if self.bitpos == 8:
self.bitpos = 0
self.data.append(self.curbyte)
self.curbyte = 0
def to_bytearray(self) -> bytearray:
return bytearray(self.data)
def size(self) -> int:
# Return size in bits
return len(self.data) * 8 + self.bitpos
# This encodes all the instructions into binary.
def insts_to_binary(insts: List[Inst], meta: Dict[str, int], lut: list) -> bytearray:
if len(insts) > 8:
raise RuntimeError('Program has more than eight instructions.')
ret = bytearray()
# We need to reformat the LUT into 32-bit values, if not already in that format.
lut_reformatted = []
if meta['lut_width_bits'] == 8:
while (len(lut) % 4) != 0:
lut.append(0)
for i in range(0, len(lut), 4):
v = lut[i] & 255
v += (lut[i + 1] & 255) << 8
v += (lut[i + 2] & 255) << 16
v += (lut[i + 3] & 255) << 24
lut_reformatted.append(v)
elif meta['lut_width_bits'] == 16:
while (len(lut) % 2) != 0:
lut.append(0)
for i in range(0, len(lut), 2):
v = lut[i] & 65535
v += (lut[i + 1] & 65535) << 16
lut_reformatted.append(v)
else: # 32-bit
lut_reformatted = lut
# Format of binary:
# Header, with self-described length. Any fields that are known to the firmware
# past this length will be assumed to be 0.
# Instructions, padded to 36 bytes per instruction line. Amount of instructions is
# defined in header.
# LUT data, in 32-bit words. Length is defined in header.
# Header. Note this should always be a multiple of 32 bytes.
lut_width_vals = {8: 0, 16: 1, 32: 2}
ret += struct.pack(
'<BBBBHBBHBB',
BITSCRAMBLER_BINARY_VER, # byte
BITSCRAMBLER_HW_REV, # byte
3, # byte: Length of header in 32-bit words
len(insts), # byte: Instruction count
len(lut_reformatted), # short: Length of LUT in 32-bit words
lut_width_vals[meta['lut_width_bits']], # byte: LUT width setting (0, 1, 2)
meta['prefetch'], # byte: prefetch enabled/disabled
meta['trailing_bytes'] * 8, # short: number of trailing *bits* after eof
meta['eof_on'], # byte
0,
) # byte: unused for now
for inst in insts:
bits = bitstream()
# If the opcode also needs a source, we add it to the mux list as the 32th input
if 'op' in inst and 'ctl_cond_src' in inst['op']:
inst['mux'][32] = inst['op']['ctl_cond_src']
# Check if mux bits are compatible and figure out flags needed
# Also set unused mux lines to 'l'
# Finally, insert mux bits into the bitstream.
flags = {'rel_addr': 0, 'ctrsel': 0, 'lutsel': 0}
for i in range(0, 33):
if i in inst['mux']:
# This could be optimized, but checking each input against each other input
# allows us to easily tell the user exactly which inputs clash.
for j in range(i + 1, 33):
if j in inst['mux']:
check_input_compatible(inst['mux'][i], inst['mux'][j])
if 'flags' in inst['mux'][i]:
if 'rel_addr' in inst['mux'][i]['flags']:
flags['rel_addr'] = inst['mux'][i]['flags']['rel_addr']
if 'ctrsel' in inst['mux'][i]['flags']:
flags['ctrsel'] = inst['mux'][i]['flags']['ctrsel']
if 'lutsel' in inst['mux'][i]['flags']:
flags['lutsel'] = inst['mux'][i]['flags']['lutsel']
if i < 32:
bits.add_bits(inst['mux'][i]['muxsel'], 7)
else:
# Input mux bit is undefined in the program. Set it to a
# fixed-low input.
high_input = parse_input({}, 'l', {'lut_width_bits': 8})
if i < 32:
bits.add_bits(high_input['input'], 7)
# Encode the opcode
opcode = 0
if 'op' not in inst:
# Default to NOP, which is encoded as ADDA 0
inst['op'] = {'op': OP_ADD, 'h': 1, 'l': 1, 'ctr_add': 0}
if 'op' in inst['op']:
opcode = inst['op']['op']
if 'c' in inst['op']:
opcode = opcode | ((1 << 24) if inst['op']['c'] else 0)
if 'h' in inst['op']:
opcode = opcode | ((1 << 23) if inst['op']['h'] else 0)
if 'l' in inst['op']:
opcode = opcode | ((1 << 22) if inst['op']['l'] else 0)
if 'tgt' in inst['op']:
opcode = opcode | (inst['op']['tgt'] << 21)
if 'end_val' in inst['op']:
opcode = opcode | (inst['op']['end_val'] << 5)
if 'ctr_add' in inst['op']: # also aliased to ctr_set
opcode = opcode | (inst['op']['ctr_add'] << 0)
if 'ctl_cond_src' in inst['op']:
opcode = opcode | (inst['op']['ctl_cond_src']['input'] << 0)
# Add the rest of the fields: read, write, source sel and reladr
bits.add_bits(opcode, 26)
val_for_read_write = {0: 0, 8: 1, 16: 2, 32: 3}
if 'read' not in inst:
inst['read'] = 0
bits.add_bits(val_for_read_write[inst['read']], 2)
if 'write' not in inst:
inst['write'] = 0
bits.add_bits(val_for_read_write[inst['write']], 2)
bits.add_bits(flags['rel_addr'], 1)
bits.add_bits(flags['ctrsel'], 1)
bits.add_bits(flags['lutsel'], 1)
if bits.size() != 257:
raise RuntimeError(f'Internal error: instruction size is {bits.size()}!')
# Pad instruction field to 36 bytes = 9 32-bit words
bits.add_bits(0, 31)
ret += bits.to_bytearray()
for i in lut_reformatted:
ret += struct.pack('<I', i & 0xffffffff)
return ret
# Return the contents of a file
def read_file(filename: str) -> str:
try:
with open(filename, 'r') as f:
file_content = f.read()
except OSError:
print(f'Error opening {filename}: {sys.exc_info()[0]}')
return file_content
# Write a bytestring to a file
def write_file(filename: str, data: bytearray) -> None:
with open(filename, 'wb') as f:
f.write(data)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
prog=sys.argv[0],
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)
args = parser.parse_args()
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)
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.')

2
tools/ci/check_soc_headers_leak.py Normal file → Executable file
View File

@ -1,3 +1,4 @@
#!/usr/bin/env python
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# This check script is used to ensure the public APIs won't expose the unstable soc files like register files
@ -17,6 +18,7 @@ allowed_soc_headers = (
'soc/reg_base.h',
'soc/clk_tree_defs.h',
'soc/uart_channel.h',
'soc/bitscrambler_peri_select.h',
)
include_header_pattern = re.compile(r'[\s]*#[\s]*include ["<](.*)[">].*')

View File

@ -48,6 +48,7 @@ examples/system/ota/otatool/otatool_example.sh
install.fish
install.sh
tools/activate.py
tools/bsasm.py
tools/check_python_dependencies.py
tools/ci/build_template_app.sh
tools/ci/check_api_violation.sh
@ -63,6 +64,7 @@ tools/ci/check_kconfigs.py
tools/ci/check_readme_links.py
tools/ci/check_requirement_files.py
tools/ci/check_rules_components_patterns.py
tools/ci/check_soc_headers_leak.py
tools/ci/check_soc_struct_headers.py
tools/ci/check_tools_files_patterns.py
tools/ci/check_type_comments.py
@ -108,6 +110,7 @@ tools/mkuf2.py
tools/python_version_checker.py
tools/set-submodules-to-github.sh
tools/test_apps/system/no_embedded_paths/check_for_file_paths.py
tools/test_bsasm/test_bsasm.py
tools/test_idf_py/test_hints.py
tools/test_idf_py/test_idf_py.py
tools/test_idf_py/test_idf_qemu.py

View File

@ -0,0 +1,12 @@
[pytest]
addopts = -s -p no:pytest_embedded
# log related
log_cli = True
log_cli_level = INFO
log_cli_format = %(asctime)s %(levelname)s %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S
## log all to `system-out` when case fail
junit_logging = stdout
junit_log_passing_tests = False

182
tools/test_bsasm/test_bsasm.py Executable file
View File

@ -0,0 +1,182 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import json
import os
import struct
import subprocess
import tempfile
import unittest
from typing import Any
from typing import List
from typing import Tuple
from typing import TypedDict
class DecompInstOp(TypedDict, total=False):
val: int
op: str
src: int
tgt: int
end_val: int
ctr_add: int
ctr_set: int
class DecompInst(TypedDict, total=False):
mux_val: List[int]
opcode: DecompInstOp
read_in: int
wr_out: int
mux_rel: int
ctr_src_sel: int
lut_src_sel: int
class UnpackedBinary(TypedDict, total=False):
binary_ver: int
hw_rev: int
hdr_len: int
inst_ct: int
lut_size_words: int
lut_width_bits: int
prefetch: int
trailing_bits: int
eof_on: int
padding: int
inst: List[DecompInst]
lut: List[int]
current_dir = os.path.dirname(os.path.realpath(__file__))
bsasm_path = os.path.join(current_dir, '..', 'bsasm.py')
class TestAssembler(unittest.TestCase):
def bit_from_inst(self, data: Tuple[int, int], off: int) -> int:
return data[off // 8] & (1 << (off & 0x7))
# returns a field from a list of bytes. Starts at bit off, field is of size length.
def bits_from_inst(self, data: Tuple[int, int], off: int, length: int) -> int:
ret = 0
for i in range(0, length):
if self.bit_from_inst(data, off + i) != 0:
ret |= 1 << i
return ret
# returns a decomposed instruction
def decode_inst(self, data: Tuple[int, int]) -> DecompInst:
ret: DecompInst = {}
mux_val = []
for i in range(0, 32):
mux_val.append(self.bits_from_inst(data, i * 7, 7))
ret['mux_val'] = mux_val
opcode = self.bits_from_inst(data, 224, 26)
op: DecompInstOp = {'val': opcode}
if opcode & (1 << 25):
op['op'] = 'LOOPB' if (opcode & (1 << 24)) else 'LOOPA'
op['tgt'] = (opcode >> 21) & 7
op['end_val'] = (opcode >> 5) & 0xFFFF
op['ctr_add'] = opcode & 31
else:
sub = (opcode >> 16) & 0x1F
if sub == 1 or sub == 2:
op['op'] = 'IF' if (sub == 1) else 'IFN'
op['src'] = opcode & 0x7F
op['tgt'] = (opcode >> 21) & 7
else:
fl = 'B' if (opcode & (1 << 24)) else 'A'
hl = (opcode >> 22) & 3
if hl == 3:
pass # don't add HL
elif hl == 2:
fl += 'H'
elif hl == 1:
fl += 'L'
elif hl == 0:
fl += 'XX' # shouldn't happen
if sub == 0:
op['op'] = 'ADD' + fl
op['ctr_add'] = opcode & 0xFFFF
elif sub == 3:
op['op'] = 'LDCTD' + fl
op['ctr_set'] = opcode & 0xFFFF
elif sub == 4:
op['op'] = 'LDCTI' + fl
ret['opcode'] = op
ret['read_in'] = self.bits_from_inst(data, 250, 2)
ret['wr_out'] = self.bits_from_inst(data, 252, 2)
ret['mux_rel'] = self.bits_from_inst(data, 254, 1)
ret['ctr_src_sel'] = self.bits_from_inst(data, 255, 1)
ret['lut_src_sel'] = self.bits_from_inst(data, 256, 1)
return ret
# returns a decomposed binary
def unpack_binary(self, filename: str) -> UnpackedBinary:
with open(filename, mode='rb') as f:
data = f.read()
ud = struct.unpack('<BBBBHBBHBB', data[:12])
unpacked: UnpackedBinary = {}
unpacked['binary_ver'] = ud[0]
unpacked['hw_rev'] = ud[1]
unpacked['hdr_len'] = ud[2]
unpacked['inst_ct'] = ud[3]
unpacked['lut_size_words'] = ud[4]
unpacked['lut_width_bits'] = ud[5]
unpacked['prefetch'] = ud[6]
unpacked['trailing_bits'] = ud[7]
unpacked['eof_on'] = ud[8]
unpacked['padding'] = ud[9]
off = unpacked['hdr_len'] * 4
inst = []
for insno in range(0, unpacked['inst_ct']):
inst.append(self.decode_inst(struct.unpack_from('B' * 36, data, off)))
off += 36
unpacked['inst'] = inst
unpacked['lut'] = list(
struct.unpack_from('<' + 'L' * unpacked['lut_size_words'], data, off)
)
return unpacked
def compare(self, out: Any, js: Any, base: str) -> None:
self.assertEqual(type(out), type(js), ' Diverging types between json and decoded obj: ' + base)
if type(js) == dict:
for k in js:
self.assertTrue(k in out, ' Key not found in decoded output: ' + base + '.' + k)
self.compare(out[k], js[k], base + '.' + k)
elif type(js) == list:
for k in range(0, len(js)):
self.compare(out[k], js[k], f'{base}[{k}]')
else:
self.assertEqual(js, out, f'Items different: {base} (json {js} decoded {out})')
def test_examples(self) -> None:
testfiles = []
for f in os.listdir(os.path.join(current_dir, 'testcases')):
if f.endswith('.bsasm'):
testfiles.append(os.path.join(current_dir, 'testcases', f))
for f in testfiles:
print(f'Testing {f}...')
with tempfile.NamedTemporaryFile(delete=False) as f_out:
self.addCleanup(os.unlink, f_out.name)
args = [bsasm_path, f, f_out.name]
p = subprocess.run(args, timeout=10)
self.assertEqual(p.returncode, 0)
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__':
unittest.main()

View File

@ -0,0 +1,2 @@
cfg eof_on downstream

View File

@ -0,0 +1,3 @@
{
"eof_on": 0
}

View File

@ -0,0 +1,2 @@
cfg eof_on upstream

View File

@ -0,0 +1,3 @@
{
"eof_on": 1
}

View File

@ -0,0 +1,13 @@
#Test counter and H/L flags
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:
ADDA 10
ADDAH 10
ADDAL 10
ADDB 10
ADDBH 10
ADDBL 10

View File

@ -0,0 +1,38 @@
{
"binary_ver": 1,
"hw_rev": 0,
"hdr_len": 3,
"inst_ct": 6,
"inst": [
{
"opcode": {
"op": "ADDA"
}
},
{
"opcode": {
"op": "ADDAH"
}
},
{
"opcode": {
"op": "ADDAL"
}
},
{
"opcode": {
"op": "ADDB"
}
},
{
"opcode": {
"op": "ADDBH"
}
},
{
"opcode": {
"op": "ADDBL"
}
}
]
}

View File

@ -0,0 +1,5 @@
cfg lut_width_bits 16
lut 0, -32768, 32767, 65535
lut 1 #to make it an odd amount of words

View File

@ -0,0 +1,13 @@
{
"binary_ver": 1,
"hw_rev": 0,
"hdr_len": 3,
"inst_ct": 0,
"lut_size_words": 3,
"lut_width_bits": 1,
"lut": [
2147483648,
4294934527,
1
]
}

View File

@ -0,0 +1,5 @@
cfg lut_width_bits 32
lut 0, -2147483648, 2147483647, 4294967295
lut 1 #to make it an odd amount of words

View File

@ -0,0 +1,15 @@
{
"binary_ver": 1,
"hw_rev": 0,
"hdr_len": 3,
"inst_ct": 0,
"lut_size_words": 5,
"lut_width_bits": 2,
"lut": [
0,
2147483648,
2147483647,
4294967295,
1
]
}

View File

@ -0,0 +1,5 @@
cfg lut_width_bits 8
lut 0, -128, 127, 255
lut 1 #make it an odd amount of words

View File

@ -0,0 +1,11 @@
{
"binary_ver": 1,
"hw_rev": 0,
"hdr_len": 3,
"inst_ct": 0,
"lut_size_words": 2,
"lut_width_bits": 0,
"lut": [
4286545920, 1
]
}

View File

@ -0,0 +1,11 @@
#Test ctr_add field of opcodes
main:
loopa 100 0 main
loopa 100 -1 main
loopa 100 15 main
loopa 100 -16 main
adda 65535
adda -32768
adda -1
adda 0

View File

@ -0,0 +1,56 @@
{
"binary_ver": 1,
"hw_rev": 0,
"hdr_len": 3,
"inst_ct": 8,
"inst": [
{
"opcode": {
"op": "LOOPA",
"ctr_add": 0
}
},
{
"opcode": {
"op": "LOOPA",
"ctr_add": 31
}
},
{
"opcode": {
"op": "LOOPA",
"ctr_add": 15
}
},
{
"opcode": {
"op": "LOOPA",
"ctr_add": 16
}
},
{
"opcode": {
"op": "ADDA",
"ctr_add": 65535
}
},
{
"opcode": {
"op": "ADDA",
"ctr_add": 32768
}
},
{
"opcode": {
"op": "ADDA",
"ctr_add": 65535
}
},
{
"opcode": {
"op": "ADDA",
"ctr_add": 0
}
}
]
}

View File

@ -0,0 +1,12 @@
cfg lut_width_bits 32
main:
if 0 main
ifn 32 main
if l0 main
ifn a0 main
if o0 main
ifn h main
if l main
ifn o31 main

View File

@ -0,0 +1,82 @@
{
"binary_ver": 1,
"hw_rev": 0,
"hdr_len": 3,
"inst_ct": 8,
"lut_size_words": 0,
"lut_width_bits": 2,
"inst": [
{
"opcode": {
"op": "IF",
"src": 0
},
"mux_rel": 0,
"ctr_src_sel": 0,
"lut_src_sel": 0
},
{
"opcode": {
"op": "IFN",
"src": 32
},
"mux_rel": 0,
"ctr_src_sel": 0,
"lut_src_sel": 0
},
{
"opcode": {
"op": "IF",
"src": 32
},
"mux_rel": 0,
"ctr_src_sel": 0,
"lut_src_sel": 1
},
{
"opcode": {
"op": "IFN",
"src": 32
},
"mux_rel": 0,
"ctr_src_sel": 1,
"lut_src_sel": 0
},
{
"opcode": {
"op": "IF",
"src": 96
},
"mux_rel": 0,
"ctr_src_sel": 0,
"lut_src_sel": 0
},
{
"opcode": {
"op": "IFN",
"src": 95
},
"mux_rel": 0,
"ctr_src_sel": 0,
"lut_src_sel": 0
},
{
"opcode": {
"op": "IF",
"src": 94
},
"mux_rel": 0,
"ctr_src_sel": 0,
"lut_src_sel": 0
},
{
"opcode": {
"op": "IFN",
"src": 127
},
"mux_rel": 0,
"ctr_src_sel": 0,
"lut_src_sel": 0
}
]
}

View File

@ -0,0 +1,14 @@
#Test TGT field of opcodes
#(Also the end_val of the loop features)
main:
LOOPA 10 1 main
IF 0 end
IFN 0 mid
mid:
JMP end
JMP main
LOOPB 0 1 mid
LOOPA 65535 1 main
end:
LOOPA -32768 1 end

View File

@ -0,0 +1,68 @@
{
"binary_ver": 1,
"hw_rev": 0,
"hdr_len": 3,
"inst_ct": 8,
"inst": [
{
"opcode": {
"op": "LOOPA",
"tgt": 0,
"end_val": 10,
"ctr_add": 1
}
},
{
"opcode": {
"op": "IF",
"src": 0,
"tgt": 7
}
},
{
"opcode": {
"op": "IFN",
"src": 0,
"tgt": 3
}
},
{
"opcode": {
"op": "IF",
"src": 95,
"tgt": 7
}
},
{
"opcode": {
"op": "IF",
"src": 95,
"tgt": 0
}
},
{
"opcode": {
"op": "LOOPB",
"tgt": 3,
"end_val": 0,
"ctr_add": 1
}
},
{
"opcode": {
"op": "LOOPA",
"tgt": 0,
"end_val": 65535,
"ctr_add": 1
}
},
{
"opcode": {
"op": "LOOPA",
"tgt": 7,
"end_val": 32768,
"ctr_add": 1
}
}
]
}

View File

@ -0,0 +1,15 @@
#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:
LOOPA 10 1 main
ADDA 10
IF 0 main
IFN 0 main
LDCTDA 10
LDCTIA
JMP main
NOP

View File

@ -0,0 +1,48 @@
{
"binary_ver": 1,
"hw_rev": 0,
"hdr_len": 3,
"inst_ct": 8,
"inst": [
{
"opcode": {
"op": "LOOPA"
}
},
{
"opcode": {
"op": "ADDA"
}
},
{
"opcode": {
"op": "IF"
}
},
{
"opcode": {
"op": "IFN"
}
},
{
"opcode": {
"op": "LDCTDA"
}
},
{
"opcode": {
"op": "LDCTIA"
}
},
{
"opcode": {
"op": "IF"
}
},
{
"opcode": {
"op": "ADDA"
}
}
]
}

View File

@ -0,0 +1,3 @@
#Test prefetch
cfg prefetch false

View File

@ -0,0 +1,3 @@
{
"prefetch": 0
}

View File

@ -0,0 +1,3 @@
#Test prefetch
cfg prefetch true

View File

@ -0,0 +1,3 @@
{
"prefetch": 1
}

View File

@ -0,0 +1,13 @@
#Example bitscrambler program. Does nothing but forward all bytes.
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
loop:
set 0..15 0..15,
set 16..23 H,
set 24..31 L,
write 32,
read 32,
jmp loop

View File

@ -0,0 +1,61 @@
{
"binary_ver": 1,
"hw_rev": 0,
"hdr_len": 3,
"inst_ct": 1,
"lut_size_words": 0,
"lut_width_bits": 0,
"prefetch": 1,
"trailing_bits": 0,
"eof_on": 1,
"padding": 0,
"inst": [
{
"mux_val": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
95,
95,
95,
95,
95,
95,
95,
95,
94,
94,
94,
94,
94,
94,
94,
94
],
"opcode": {
"val": 65631,
"op": "IF",
"src": 95
},
"read_in": 3,
"wr_out": 3,
"mux_rel": 0,
"ctr_src_sel": 0,
"lut_src_sel": 0
}
],
"lut": []
}

View File

@ -0,0 +1,50 @@
#Test sources
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 32 #32-bit lut
main:
set 0..31 0..31
set 0..31 32..63
set 0 bl<=O0,
set 1 bL>O0,
set 2 bl=O0,
set 3 bl<=O8,
set 4 bL>O8,
set 5 bl=O8,
set 6 bl<=O16,
set 7 bL>O16,
set 8 bl=O16,
set 9 bl<=O24,
set 10 bL>O24,
set 11 bl=O24,
set 12 bh<=O0,
set 13 bh>O0,
set 14 bh=O0,
set 15 bh<=O8,
set 16 bh>O8,
set 17 bh=O8,
set 18 bh<=O16,
set 19 bh>O16,
set 20 bh=O16,
set 21 bh<=O24,
set 22 bh>O24,
set 23 bh=O24,
set 24 b<=O0,
set 25 b>O0,
set 26 b=O0,
set 27 b<=O16,
set 28 b>O16,
set 29 b=O16,
set 30 l,
set 31 h
set 0..31 O0..O31
set 0..15 A0..A15,
set 16..31 B0..B15
set 0..31 L0..L31

View File

@ -0,0 +1,228 @@
{
"binary_ver": 1,
"hw_rev": 0,
"hdr_len": 3,
"inst_ct": 6,
"inst": [
{
"mux_val": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31
]
},
{
"mux_val": [
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63
]
},
{
"mux_val": [
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95
]
},
{
"mux_val": [
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
109,
110,
111,
112,
113,
114,
115,
116,
117,
118,
119,
120,
121,
122,
123,
124,
125,
126,
127
]
},
{
"mux_val": [
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63
],
"ctr_src_sel": 1,
"lut_src_sel": 0
},
{
"mux_val": [
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63
],
"ctr_src_sel": 0,
"lut_src_sel": 1
}
]
}

View File

@ -0,0 +1,3 @@
#Example bitscrambler program. Does nothing but forward all bytes.
cfg trailing_bytes 1 #End program as soon as the input EOFs.

View File

@ -0,0 +1,7 @@
{
"binary_ver": 1,
"hw_rev": 0,
"hdr_len": 3,
"inst_ct": 0,
"trailing_bits": 8
}

View File

@ -0,0 +1,3 @@
#Example bitscrambler program. Does nothing but forward all bytes.
cfg trailing_bytes 255 #End program as soon as the input EOFs.

View File

@ -0,0 +1,7 @@
{
"binary_ver": 1,
"hw_rev": 0,
"hdr_len": 3,
"inst_ct": 0,
"trailing_bits": 2040
}