mirror of
https://github.com/espressif/esp-idf
synced 2025-03-09 17:19:09 -04:00
feat(driver): BitScrambler support
This adds an assembler for the BitScrambler assembly language, plus unit tests for it. It also adds the loopback driver, which can do BitScrambler operations on memory-to-memory transfers. Documentation is also included.
This commit is contained in:
parent
cc8bef395e
commit
a88e719e33
@ -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
|
||||
|
@ -126,6 +126,9 @@
|
||||
|
||||
- "tools/check_python_dependencies.py"
|
||||
|
||||
- "tools/bsasm.py"
|
||||
- "tools/test_bsasm/**/*"
|
||||
|
||||
.patterns-docker: &patterns-docker
|
||||
- "tools/docker/**/*"
|
||||
|
||||
|
29
components/esp_driver_bitscrambler/CMakeLists.txt
Normal file
29
components/esp_driver_bitscrambler/CMakeLists.txt
Normal 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"
|
||||
)
|
300
components/esp_driver_bitscrambler/bitscrambler.c
Normal file
300
components/esp_driver_bitscrambler/bitscrambler.c
Normal 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;
|
||||
}
|
27
components/esp_driver_bitscrambler/bitscrambler_esp32p4.c
Normal file
27
components/esp_driver_bitscrambler/bitscrambler_esp32p4.c
Normal 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},
|
||||
};
|
273
components/esp_driver_bitscrambler/bitscrambler_loopback.c
Normal file
273
components/esp_driver_bitscrambler/bitscrambler_loopback.c
Normal 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;
|
||||
}
|
@ -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
|
23
components/esp_driver_bitscrambler/bitscrambler_private.h
Normal file
23
components/esp_driver_bitscrambler/bitscrambler_private.h
Normal 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);
|
@ -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
|
@ -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
|
@ -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[];
|
18
components/esp_driver_bitscrambler/project_include.cmake
Normal file
18
components/esp_driver_bitscrambler/project_include.cmake
Normal 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()
|
@ -0,0 +1,5 @@
|
||||
components/esp_driver_bitscrambler/test_apps/bitscrambler:
|
||||
disable:
|
||||
- if: SOC_BITSCRAMBLER_SUPPORTED != 1
|
||||
depends_components:
|
||||
- esp_driver_bitscrambler
|
@ -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)
|
@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32-P4 |
|
||||
| ----------------- | -------- |
|
@ -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")
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
@ -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
|
@ -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
|
@ -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()
|
@ -0,0 +1 @@
|
||||
# CONFIG_ESP_TASK_WDT_EN is not set
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 \
|
||||
|
202
docs/en/api-reference/peripherals/bitscrambler.rst
Normal file
202
docs/en/api-reference/peripherals/bitscrambler.rst
Normal 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
|
@ -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
|
||||
|
1
docs/zh_CN/api-reference/peripherals/bitscrambler.rst
Normal file
1
docs/zh_CN/api-reference/peripherals/bitscrambler.rst
Normal file
@ -0,0 +1 @@
|
||||
.. include:: ../../../en/api-reference/peripherals/bitscrambler.rst
|
@ -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
|
||||
|
@ -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
|
||||
|
8
examples/peripherals/bitscrambler/CMakeLists.txt
Normal file
8
examples/peripherals/bitscrambler/CMakeLists.txt
Normal 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)
|
29
examples/peripherals/bitscrambler/README.md
Normal file
29
examples/peripherals/bitscrambler/README.md
Normal 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.
|
6
examples/peripherals/bitscrambler/main/CMakeLists.txt
Normal file
6
examples/peripherals/bitscrambler/main/CMakeLists.txt
Normal 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")
|
@ -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");
|
||||
}
|
100
examples/peripherals/bitscrambler/main/example.bsasm
Normal file
100
examples/peripherals/bitscrambler/main/example.bsasm
Normal 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
969
tools/bsasm.py
Executable 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
2
tools/ci/check_soc_headers_leak.py
Normal file → Executable 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 ["<](.*)[">].*')
|
||||
|
@ -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
|
||||
|
12
tools/test_bsasm/pytest.ini
Normal file
12
tools/test_bsasm/pytest.ini
Normal 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
182
tools/test_bsasm/test_bsasm.py
Executable 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()
|
2
tools/test_bsasm/testcases/eof_on_downstream.bsasm
Normal file
2
tools/test_bsasm/testcases/eof_on_downstream.bsasm
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
cfg eof_on downstream
|
3
tools/test_bsasm/testcases/eof_on_downstream.json
Normal file
3
tools/test_bsasm/testcases/eof_on_downstream.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"eof_on": 0
|
||||
}
|
2
tools/test_bsasm/testcases/eof_on_upstream.bsasm
Normal file
2
tools/test_bsasm/testcases/eof_on_upstream.bsasm
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
cfg eof_on upstream
|
3
tools/test_bsasm/testcases/eof_on_upstream.json
Normal file
3
tools/test_bsasm/testcases/eof_on_upstream.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"eof_on": 1
|
||||
}
|
13
tools/test_bsasm/testcases/hlab.bsasm
Normal file
13
tools/test_bsasm/testcases/hlab.bsasm
Normal 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
|
38
tools/test_bsasm/testcases/hlab.json
Normal file
38
tools/test_bsasm/testcases/hlab.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
5
tools/test_bsasm/testcases/lut16.bsasm
Normal file
5
tools/test_bsasm/testcases/lut16.bsasm
Normal 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
|
13
tools/test_bsasm/testcases/lut16.json
Normal file
13
tools/test_bsasm/testcases/lut16.json
Normal 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
|
||||
]
|
||||
}
|
5
tools/test_bsasm/testcases/lut32.bsasm
Normal file
5
tools/test_bsasm/testcases/lut32.bsasm
Normal 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
|
15
tools/test_bsasm/testcases/lut32.json
Normal file
15
tools/test_bsasm/testcases/lut32.json
Normal 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
|
||||
]
|
||||
}
|
5
tools/test_bsasm/testcases/lut8.bsasm
Normal file
5
tools/test_bsasm/testcases/lut8.bsasm
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
cfg lut_width_bits 8
|
||||
|
||||
lut 0, -128, 127, 255
|
||||
lut 1 #make it an odd amount of words
|
11
tools/test_bsasm/testcases/lut8.json
Normal file
11
tools/test_bsasm/testcases/lut8.json
Normal 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
|
||||
]
|
||||
}
|
11
tools/test_bsasm/testcases/opcode_ctr_add.bsasm
Normal file
11
tools/test_bsasm/testcases/opcode_ctr_add.bsasm
Normal 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
|
56
tools/test_bsasm/testcases/opcode_ctr_add.json
Normal file
56
tools/test_bsasm/testcases/opcode_ctr_add.json
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
12
tools/test_bsasm/testcases/opcode_src.bsasm
Normal file
12
tools/test_bsasm/testcases/opcode_src.bsasm
Normal 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
|
82
tools/test_bsasm/testcases/opcode_src.json
Normal file
82
tools/test_bsasm/testcases/opcode_src.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
14
tools/test_bsasm/testcases/opcode_tgt.bsasm
Normal file
14
tools/test_bsasm/testcases/opcode_tgt.bsasm
Normal 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
|
68
tools/test_bsasm/testcases/opcode_tgt.json
Normal file
68
tools/test_bsasm/testcases/opcode_tgt.json
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
15
tools/test_bsasm/testcases/opcodes.bsasm
Normal file
15
tools/test_bsasm/testcases/opcodes.bsasm
Normal 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
|
48
tools/test_bsasm/testcases/opcodes.json
Normal file
48
tools/test_bsasm/testcases/opcodes.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
3
tools/test_bsasm/testcases/prefetch_off.bsasm
Normal file
3
tools/test_bsasm/testcases/prefetch_off.bsasm
Normal file
@ -0,0 +1,3 @@
|
||||
#Test prefetch
|
||||
|
||||
cfg prefetch false
|
3
tools/test_bsasm/testcases/prefetch_off.json
Normal file
3
tools/test_bsasm/testcases/prefetch_off.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"prefetch": 0
|
||||
}
|
3
tools/test_bsasm/testcases/prefetch_on.bsasm
Normal file
3
tools/test_bsasm/testcases/prefetch_on.bsasm
Normal file
@ -0,0 +1,3 @@
|
||||
#Test prefetch
|
||||
|
||||
cfg prefetch true
|
3
tools/test_bsasm/testcases/prefetch_on.json
Normal file
3
tools/test_bsasm/testcases/prefetch_on.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"prefetch": 1
|
||||
}
|
13
tools/test_bsasm/testcases/sanity.bsasm
Normal file
13
tools/test_bsasm/testcases/sanity.bsasm
Normal 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
|
61
tools/test_bsasm/testcases/sanity.json
Normal file
61
tools/test_bsasm/testcases/sanity.json
Normal 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": []
|
||||
}
|
50
tools/test_bsasm/testcases/src1.bsasm
Normal file
50
tools/test_bsasm/testcases/src1.bsasm
Normal 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
|
228
tools/test_bsasm/testcases/src1.json
Normal file
228
tools/test_bsasm/testcases/src1.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
3
tools/test_bsasm/testcases/trail1.bsasm
Normal file
3
tools/test_bsasm/testcases/trail1.bsasm
Normal 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.
|
7
tools/test_bsasm/testcases/trail1.json
Normal file
7
tools/test_bsasm/testcases/trail1.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"binary_ver": 1,
|
||||
"hw_rev": 0,
|
||||
"hdr_len": 3,
|
||||
"inst_ct": 0,
|
||||
"trailing_bits": 8
|
||||
}
|
3
tools/test_bsasm/testcases/trail255.bsasm
Normal file
3
tools/test_bsasm/testcases/trail255.bsasm
Normal 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.
|
7
tools/test_bsasm/testcases/trail255.json
Normal file
7
tools/test_bsasm/testcases/trail255.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"binary_ver": 1,
|
||||
"hw_rev": 0,
|
||||
"hdr_len": 3,
|
||||
"inst_ct": 0,
|
||||
"trailing_bits": 2040
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user