Chip/support esp32c61 v5.5

This commit is contained in:
Jiang Jiang Jian
2025-07-22 12:21:36 +08:00
parent 7bed0d3937
commit 3c39b32195
375 changed files with 44801 additions and 39121 deletions
@@ -116,6 +116,7 @@ esp_err_t parlio_tx_unit_disable(parlio_tx_unit_handle_t unit);
*/
typedef struct {
parlio_tx_done_callback_t on_trans_done; /*!< Event callback, invoked when one transmission is finished */
parlio_tx_buffer_switched_callback_t on_buffer_switched; /*!< Event callback, invoked when the buffer is switched in loop transmission */
} parlio_tx_event_callbacks_t;
/**
@@ -37,6 +37,14 @@ typedef struct parlio_rx_delimiter_t *parlio_rx_delimiter_handle_t;
typedef struct {
} parlio_tx_done_event_data_t;
/**
* @brief Type of Parallel IO TX buffer switched event data
*/
typedef struct {
void *old_buffer_addr; /*!< Address of the previous buffer used before switching */
void *new_buffer_addr; /*!< Address of the new buffer switched to */
} parlio_tx_buffer_switched_event_data_t;
/**
* @brief Prototype of parlio tx event callback
* @param[in] tx_unit Parallel IO TX unit that created by `parlio_new_tx_unit`
@@ -48,6 +56,17 @@ typedef struct {
*/
typedef bool (*parlio_tx_done_callback_t)(parlio_tx_unit_handle_t tx_unit, const parlio_tx_done_event_data_t *edata, void *user_ctx);
/**
* @brief Prototype of parlio tx buffer switched event callback
* @param[in] tx_unit Parallel IO TX unit that created by `parlio_new_tx_unit`
* @param[in] edata Point to Parallel IO TX event data. The lifecycle of this pointer memory is inside this function,
* user should copy it into static memory if used outside this function.
* @param[in] user_ctx User registered context, passed from `parlio_tx_unit_register_event_callbacks`
*
* @return Whether a high priority task has been waken up by this callback function
*/
typedef bool (*parlio_tx_buffer_switched_callback_t)(parlio_tx_unit_handle_t tx_unit, const parlio_tx_buffer_switched_event_data_t *edata, void *user_ctx);
#ifdef __cplusplus
}
#endif
+7
View File
@@ -5,6 +5,10 @@ entries:
parlio_tx: parlio_tx_default_isr (noflash)
parlio_tx: parlio_tx_do_transaction (noflash)
parlio_tx: parlio_mount_buffer (noflash)
if SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION = y:
parlio_tx: parlio_tx_gdma_eof_callback (noflash)
if PARLIO_RX_ISR_HANDLER_IN_IRAM = y:
parlio_rx: parlio_rx_default_eof_callback (noflash)
parlio_rx: parlio_rx_default_desc_done_callback (noflash)
@@ -18,3 +22,6 @@ entries:
gdma_link: gdma_link_mount_buffers (noflash)
gdma_link: gdma_link_concat (noflash)
gdma_link: gdma_link_get_head_addr (noflash)
if SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION = y:
gdma_link: gdma_link_get_buffer (noflash)
@@ -168,6 +168,10 @@ typedef esp_err_t (*parlio_tx_bs_disable_fn_t)(parlio_tx_unit_handle_t tx_unit);
typedef struct parlio_tx_unit_t {
struct parlio_unit_t base; // base unit
size_t data_width; // data width
gpio_num_t data_gpio_nums[SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH]; // data GPIO numbers
gpio_num_t valid_gpio_num; // valid signal GPIO number
gpio_num_t clk_out_gpio_num; // output clock GPIO number
gpio_num_t clk_in_gpio_num; // input clock GPIO number
intr_handle_t intr; // allocated interrupt handle
esp_pm_lock_handle_t pm_lock; // power management lock
gdma_channel_handle_t dma_chan; // DMA channel
@@ -187,7 +191,9 @@ typedef struct parlio_tx_unit_t {
parlio_tx_trans_desc_t *cur_trans; // points to current transaction
uint32_t idle_value_mask; // mask of idle value
_Atomic parlio_tx_fsm_t fsm; // Driver FSM state
_Atomic bool buffer_need_switch; // whether the buffer need to be switched
parlio_tx_done_callback_t on_trans_done; // callback function when the transmission is done
parlio_tx_buffer_switched_callback_t on_buffer_switched; // callback function when the buffer is switched in loop transmission
void *user_data; // user data passed to the callback function
bitscrambler_handle_t bs_handle; // bitscrambler handle
parlio_tx_bs_enable_fn_t bs_enable_fn; // bitscrambler enable function
+5 -2
View File
@@ -491,6 +491,7 @@ static esp_err_t parlio_select_periph_clock(parlio_rx_unit_handle_t rx_unit, con
{
parlio_hal_context_t *hal = &rx_unit->base.group->hal;
parlio_clock_source_t clk_src = config->clk_src;
rx_unit->clk_src = clk_src;
uint32_t src_freq_hz = 0;
uint32_t exp_freq_hz = 0;
hal_utils_clk_div_t clk_div = {
@@ -536,14 +537,12 @@ static esp_err_t parlio_select_periph_clock(parlio_rx_unit_handle_t rx_unit, con
}
#endif
esp_clk_tree_enable_src((soc_module_clk_t)clk_src, true);
/* Set clock configuration */
PARLIO_CLOCK_SRC_ATOMIC() {
parlio_ll_rx_set_clock_source(hal->regs, clk_src);
parlio_ll_rx_set_clock_div(hal->regs, &clk_div);
}
rx_unit->clk_src = clk_src;
/* warning if precision lost due to division */
if ((clk_src != PARLIO_CLK_SRC_EXTERNAL) &&
(config->exp_clk_freq_hz != rx_unit->cfg.exp_clk_freq_hz)) {
@@ -595,6 +594,9 @@ static esp_err_t parlio_destroy_rx_unit(parlio_rx_unit_handle_t rx_unit)
if (rx_unit->base.group) {
parlio_unregister_unit_from_group(&rx_unit->base);
}
if (rx_unit->clk_src) {
ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)rx_unit->clk_src, false), TAG, "clock source disable failed");
}
/* Free the RX unit */
free(rx_unit);
return ESP_OK;
@@ -661,6 +663,7 @@ esp_err_t parlio_new_rx_unit(const parlio_rx_unit_config_t *config, parlio_rx_un
}
parlio_ll_rx_start(hal->regs, false);
/* parlio_ll_clock_source_t and parlio_clock_source_t are binary compatible if the clock source is from internal */
ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)(config->clk_src), true), err, TAG, "clock source enable failed");
ESP_GOTO_ON_ERROR(parlio_select_periph_clock(unit, config), err, TAG, "set clock source failed");
/* Set the data width */
parlio_ll_rx_set_bus_width(hal->regs, config->data_width);
+105 -23
View File
@@ -10,6 +10,9 @@
#include "driver/parlio_tx.h"
#include "parlio_priv.h"
#if SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
static bool parlio_tx_gdma_eof_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data);
#endif
static void parlio_tx_default_isr(void *args);
static esp_err_t parlio_tx_create_trans_queue(parlio_tx_unit_t *tx_unit, const parlio_tx_unit_config_t *config)
@@ -72,6 +75,9 @@ static esp_err_t parlio_destroy_tx_unit(parlio_tx_unit_t *tx_unit)
ESP_RETURN_ON_ERROR(gdma_del_link_list(tx_unit->dma_link[i]), TAG, "delete dma link list failed");
}
}
if (tx_unit->clk_src) {
ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)tx_unit->clk_src, false), TAG, "clock source disable failed");
}
free(tx_unit);
return ESP_OK;
}
@@ -94,6 +100,7 @@ static esp_err_t parlio_tx_unit_configure_gpio(parlio_tx_unit_t *tx_unit, const
// connect the signal to the GPIO by matrix, it will also enable the output path properly
esp_rom_gpio_connect_out_signal(config->data_gpio_nums[i],
parlio_periph_signals.groups[group_id].tx_units[unit_id].data_sigs[i], false, false);
tx_unit->data_gpio_nums[i] = config->data_gpio_nums[i];
}
}
@@ -118,6 +125,7 @@ static esp_err_t parlio_tx_unit_configure_gpio(parlio_tx_unit_t *tx_unit, const
parlio_periph_signals.groups[group_id].tx_units[unit_id].data_sigs[PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG],
config->flags.invert_valid_out, false);
#endif // !PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG
tx_unit->valid_gpio_num = config->valid_gpio_num;
}
if (config->clk_out_gpio_num >= 0) {
gpio_func_sel(config->clk_out_gpio_num, PIN_FUNC_GPIO);
@@ -130,6 +138,7 @@ static esp_err_t parlio_tx_unit_configure_gpio(parlio_tx_unit_t *tx_unit, const
// connect the signal to the GPIO by matrix, it will also enable the output path properly
esp_rom_gpio_connect_out_signal(config->clk_out_gpio_num,
parlio_periph_signals.groups[group_id].tx_units[unit_id].clk_out_sig, false, false);
tx_unit->clk_out_gpio_num = config->clk_out_gpio_num;
}
if (config->clk_in_gpio_num >= 0) {
gpio_input_enable(config->clk_in_gpio_num);
@@ -141,6 +150,7 @@ static esp_err_t parlio_tx_unit_configure_gpio(parlio_tx_unit_t *tx_unit, const
esp_rom_gpio_connect_in_signal(config->clk_in_gpio_num,
parlio_periph_signals.groups[group_id].tx_units[unit_id].clk_in_sig, false);
tx_unit->clk_in_gpio_num = config->clk_in_gpio_num;
}
return ESP_OK;
}
@@ -202,6 +212,7 @@ static esp_err_t parlio_select_periph_clock(parlio_tx_unit_t *tx_unit, const par
{
parlio_hal_context_t *hal = &tx_unit->base.group->hal;
parlio_clock_source_t clk_src = config->clk_src;
tx_unit->clk_src = clk_src;
if (config->clk_in_gpio_num >= 0 && clk_src != PARLIO_CLK_SRC_EXTERNAL) {
ESP_LOGW(TAG, "input clock GPIO is set, use external clk src");
clk_src = PARLIO_CLK_SRC_EXTERNAL;
@@ -244,7 +255,6 @@ static esp_err_t parlio_select_periph_clock(parlio_tx_unit_t *tx_unit, const par
#else
tx_unit->out_clk_freq_hz = hal_utils_calc_clk_div_integer(&clk_info, &clk_div.integer);
#endif
esp_clk_tree_enable_src((soc_module_clk_t)clk_src, true);
PARLIO_CLOCK_SRC_ATOMIC() {
// turn on the tx module clock to sync the clock divider configuration because of the CDC (Cross Domain Crossing)
parlio_ll_tx_enable_clock(hal->regs, true);
@@ -256,8 +266,6 @@ static esp_err_t parlio_select_periph_clock(parlio_tx_unit_t *tx_unit, const par
if (tx_unit->out_clk_freq_hz != config->output_clk_freq_hz) {
ESP_LOGW(TAG, "precision loss, real output frequency: %"PRIu32, tx_unit->out_clk_freq_hz);
}
tx_unit->clk_src = clk_src;
return ESP_OK;
}
@@ -305,6 +313,7 @@ esp_err_t parlio_new_tx_unit(const parlio_tx_unit_config_t *config, parlio_tx_un
parlio_group_t *group = unit->base.group;
parlio_hal_context_t *hal = &group->hal;
// select the clock source
ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)(config->clk_src), true), err, TAG, "clock source enable failed");
ESP_GOTO_ON_ERROR(parlio_select_periph_clock(unit, config), err, TAG, "set clock source failed");
// install interrupt service
@@ -357,9 +366,6 @@ esp_err_t parlio_new_tx_unit(const parlio_tx_unit_config_t *config, parlio_tx_un
// set sample clock edge
parlio_ll_tx_set_sample_clock_edge(hal->regs, config->sample_edge);
// In default, use DATA LEN EOF as the Parlio TX EOF
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DATA_LEN);
// clear any pending interrupt
parlio_ll_clear_interrupt_status(hal->regs, PARLIO_LL_EVENT_TX_MASK);
@@ -374,6 +380,7 @@ esp_err_t parlio_new_tx_unit(const parlio_tx_unit_config_t *config, parlio_tx_un
portMUX_INITIALIZE(&unit->spinlock);
atomic_init(&unit->fsm, PARLIO_TX_FSM_INIT);
atomic_init(&unit->buffer_need_switch, false);
// return TX unit handle
*ret_unit = unit;
ESP_LOGD(TAG, "new tx unit(%d,%d) at %p, out clk=%"PRIu32"Hz, queue_depth=%zu, idle_mask=%"PRIx32,
@@ -392,6 +399,22 @@ esp_err_t parlio_del_tx_unit(parlio_tx_unit_handle_t unit)
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(atomic_load(&unit->fsm) == PARLIO_TX_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "unit not in init state");
ESP_LOGD(TAG, "del tx unit(%d,%d)", unit->base.group->group_id, unit->base.unit_id);
for (size_t i = 0; i < unit->data_width; i++) {
if (unit->data_gpio_nums[i] >= 0) {
gpio_output_disable(unit->data_gpio_nums[i]);
}
}
if (unit->valid_gpio_num >= 0) {
gpio_output_disable(unit->valid_gpio_num);
}
if (unit->clk_out_gpio_num >= 0) {
gpio_output_disable(unit->clk_out_gpio_num);
}
if (unit->clk_in_gpio_num >= 0) {
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT,
parlio_periph_signals.groups[unit->base.group->group_id].tx_units[unit->base.unit_id].clk_in_sig,
false);
}
return parlio_destroy_tx_unit(unit);
}
@@ -420,12 +443,30 @@ esp_err_t parlio_tx_unit_register_event_callbacks(parlio_tx_unit_handle_t tx_uni
if (cbs->on_trans_done) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_trans_done callback not in IRAM");
}
if (cbs->on_buffer_switched) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_buffer_switched), ESP_ERR_INVALID_ARG, TAG, "on_buffer_switched callback not in IRAM");
}
if (user_data) {
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
}
#endif
if (cbs->on_buffer_switched) {
#if SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
// workaround for DIG-559
ESP_RETURN_ON_FALSE(tx_unit->data_width > 1, ESP_ERR_NOT_SUPPORTED, TAG, "on_buffer_switched callback is not supported for 1-bit data width");
gdma_tx_event_callbacks_t gdma_cbs = {
.on_trans_eof = parlio_tx_gdma_eof_callback,
};
ESP_RETURN_ON_ERROR(gdma_register_tx_event_callbacks(tx_unit->dma_chan, &gdma_cbs, tx_unit), TAG, "install DMA callback failed");
#else
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "on_buffer_switched callback is not supported");
#endif
}
tx_unit->on_trans_done = cbs->on_trans_done;
tx_unit->on_buffer_switched = cbs->on_buffer_switched;
tx_unit->user_data = user_data;
return ESP_OK;
}
@@ -437,8 +478,8 @@ static void parlio_mount_buffer(parlio_tx_unit_t *tx_unit, parlio_tx_trans_desc_
.buffer = (void *)t->payload,
.length = (t->payload_bits + 7) / 8,
.flags = {
// if transmission is loop, we don't need to generate the EOF, as well as the final mark
.mark_eof = !t->flags.loop_transmission,
// if transmission is loop, we don't need to generate the EOF for 1-bit data width, DIG-559
.mark_eof = tx_unit->data_width == 1 ? !t->flags.loop_transmission : true,
.mark_final = !t->flags.loop_transmission,
}
};
@@ -456,12 +497,6 @@ static void parlio_mount_buffer(parlio_tx_unit_t *tx_unit, parlio_tx_trans_desc_
static void parlio_tx_do_transaction(parlio_tx_unit_t *tx_unit, parlio_tx_trans_desc_t *t)
{
parlio_hal_context_t *hal = &tx_unit->base.group->hal;
if (t->flags.loop_transmission) {
// Once a loop transmission is started, it cannot be stopped until it is disabled
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DMA_EOF);
}
tx_unit->cur_trans = t;
// If the external clock is a non-free-running clock, it needs to be switched to the internal free-running clock first.
@@ -490,7 +525,30 @@ static void parlio_tx_do_transaction(parlio_tx_unit_t *tx_unit, parlio_tx_trans_
// reset tx fifo after disabling tx core clk to avoid unexpected rempty interrupt
parlio_ll_tx_reset_fifo(hal->regs);
parlio_ll_tx_set_idle_data_value(hal->regs, t->idle_value);
parlio_ll_tx_set_trans_bit_len(hal->regs, t->payload_bits);
// set EOF condition
if (t->flags.loop_transmission) {
if (tx_unit->data_width == 1) {
// for 1-bit data width, we need to set the EOF condition to DMA EOF
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DMA_EOF);
} else {
// for other data widths, we still use the data length EOF condition,
// but let the `bit counter` + `data width` for each cycle is never equal to the configured bit lens.
// Thus, we can skip the exact match, prevents EOF
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DATA_LEN);
parlio_ll_tx_set_trans_bit_len(hal->regs, 0x01);
}
} else {
// non-loop transmission
#if SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
// for DMA EOF supported target, we need to set the EOF condition to DMA EOF
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DMA_EOF);
#else
// for DMA EOF not supported target, we need to set the bit length to the configured bit lens
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DATA_LEN);
parlio_ll_tx_set_trans_bit_len(hal->regs, t->payload_bits);
#endif // SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
}
if (tx_unit->bs_handle) {
// load the bitscrambler program and start it
@@ -583,10 +641,6 @@ esp_err_t parlio_tx_unit_disable(parlio_tx_unit_handle_t tx_unit)
parlio_ll_tx_start(hal->regs, false);
parlio_ll_enable_interrupt(hal->regs, PARLIO_LL_EVENT_TX_MASK, false);
// Once a loop teansmission transaction is started, it can only be stopped in disable function
// change the EOF condition to be the data length, so the EOF will be triggered normally
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DATA_LEN);
// release power management lock
if (tx_unit->pm_lock) {
esp_pm_lock_release(tx_unit->pm_lock);
@@ -609,18 +663,20 @@ esp_err_t parlio_tx_unit_transmit(parlio_tx_unit_handle_t tx_unit, const void *p
#if SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
if (config->flags.loop_transmission) {
ESP_RETURN_ON_FALSE(parlio_ll_tx_support_dma_eof(NULL), ESP_ERR_NOT_SUPPORTED, TAG, "loop transmission is not supported by this chip revision");
ESP_RETURN_ON_FALSE(parlio_ll_tx_support_dma_eof(NULL) || tx_unit->data_width > 1, ESP_ERR_NOT_SUPPORTED, TAG,
"1-bit data width loop transmission is not supported by this chip revision");
}
#else
ESP_RETURN_ON_FALSE(config->flags.loop_transmission == false, ESP_ERR_NOT_SUPPORTED, TAG, "loop transmission is not supported on this chip");
#endif
// check the max payload size if it's not a loop transmission
// workaround for EOF limitation, when DMA EOF issue is fixed, we can remove this check
#if !SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
// check the max payload size if it's not a loop transmission and the DMA EOF is not supported
if (!config->flags.loop_transmission) {
ESP_RETURN_ON_FALSE(tx_unit->max_transfer_bits <= PARLIO_LL_TX_MAX_BITS_PER_FRAME,
ESP_ERR_INVALID_ARG, TAG, "invalid transfer size");
ESP_ERR_INVALID_ARG, TAG, "invalid transfer size, max transfer size should be less than %d", PARLIO_LL_TX_MAX_BITS_PER_FRAME / 8);
}
#endif // !SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
size_t cache_line_size = 0;
size_t alignment = 0;
@@ -643,6 +699,7 @@ esp_err_t parlio_tx_unit_transmit(parlio_tx_unit_handle_t tx_unit, const void *p
tx_unit->cur_trans->payload = payload;
tx_unit->cur_trans->payload_bits = payload_bits;
parlio_mount_buffer(tx_unit, tx_unit->cur_trans);
atomic_store(&tx_unit->buffer_need_switch, true);
} else {
TickType_t queue_wait_ticks = portMAX_DELAY;
if (config->flags.queue_nonblocking) {
@@ -691,6 +748,31 @@ esp_err_t parlio_tx_unit_transmit(parlio_tx_unit_handle_t tx_unit, const void *p
return ESP_OK;
}
#if SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
static bool parlio_tx_gdma_eof_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
parlio_tx_unit_t *tx_unit = (parlio_tx_unit_t *) user_data;
bool need_yield = false;
bool expected_state = true;
// invoke callback to notify the application
parlio_tx_buffer_switched_callback_t on_buffer_switched = tx_unit->on_buffer_switched;
if (on_buffer_switched) {
if (atomic_compare_exchange_strong(&tx_unit->buffer_need_switch, &expected_state, false)) {
parlio_tx_buffer_switched_event_data_t edata = {
// we use 2 dma links to do the buffer switch in loop transmission
.old_buffer_addr = gdma_link_get_buffer(tx_unit->dma_link[1 - tx_unit->cur_trans->dma_link_idx], 0),
.new_buffer_addr = gdma_link_get_buffer(tx_unit->dma_link[tx_unit->cur_trans->dma_link_idx], 0),
};
if (on_buffer_switched(tx_unit, &edata, tx_unit->user_data)) {
need_yield = true;
}
}
}
return need_yield;
}
#endif // SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
static void parlio_tx_default_isr(void *args)
{
parlio_tx_unit_t *tx_unit = (parlio_tx_unit_t *)args;
@@ -10,6 +10,10 @@ if(CONFIG_SOC_BITSCRAMBLER_SUPPORTED)
list(APPEND srcs "test_parlio_bitscrambler.c")
endif()
if(CONFIG_PARLIO_TX_ISR_CACHE_SAFE)
list(APPEND srcs "test_parlio_tx_cache_safe.c")
endif()
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(SRCS ${srcs}
@@ -21,4 +25,6 @@ idf_component_register(SRCS ${srcs}
if(CONFIG_SOC_BITSCRAMBLER_SUPPORTED)
target_bitscrambler_add_src("test_parlio_tx_LSB_to_MSB.bsasm")
target_bitscrambler_add_src("test_parlio_tx_multiply.bsasm")
target_bitscrambler_add_src("test_parlio_tx_in8_out32.bsasm")
target_bitscrambler_add_src("test_parlio_tx_in32_out8.bsasm")
endif()
@@ -21,13 +21,21 @@
BITSCRAMBLER_PROGRAM(bitscrambler_program_test_tx_LSB_to_MSB, "test_parlio_tx_LSB_to_MSB");
BITSCRAMBLER_PROGRAM(bitscrambler_program_test_tx_multiply, "test_parlio_tx_multiply");
BITSCRAMBLER_PROGRAM(bitscrambler_program_test_tx_in8_out32, "test_parlio_tx_in8_out32");
BITSCRAMBLER_PROGRAM(bitscrambler_program_test_tx_in32_out8, "test_parlio_tx_in32_out8");
typedef struct {
TaskHandle_t task;
size_t recv_bytes;
} test_parlio_bitscrambler_rx_ctx_t;
TEST_PARLIO_CALLBACK_ATTR
static bool test_parlio_rx_done_callback(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_data_t *edata, void *user_ctx)
{
BaseType_t high_task_wakeup = pdFALSE;
TaskHandle_t task = (TaskHandle_t)user_ctx;
vTaskNotifyGiveFromISR(task, &high_task_wakeup);
test_parlio_bitscrambler_rx_ctx_t *ctx = (test_parlio_bitscrambler_rx_ctx_t *)user_ctx;
ctx->recv_bytes = edata->recv_bytes;
vTaskNotifyGiveFromISR(ctx->task, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
@@ -105,10 +113,13 @@ static void test_parlio_bitscrambler(void)
};
TEST_ESP_OK(parlio_new_rx_level_delimiter(&lvl_deli_cfg, &deli));
printf("register receive_done event callback\r\n");
test_parlio_bitscrambler_rx_ctx_t rx_ctx = {
.task = xTaskGetCurrentTaskHandle(),
};
parlio_rx_event_callbacks_t rx_cbs = {
.on_receive_done = test_parlio_rx_done_callback,
};
TEST_ESP_OK(parlio_rx_unit_register_event_callbacks(rx_unit, &rx_cbs, xTaskGetCurrentTaskHandle()));
TEST_ESP_OK(parlio_rx_unit_register_event_callbacks(rx_unit, &rx_cbs, &rx_ctx));
parlio_receive_config_t recv_config = {
.delimiter = deli,
@@ -121,11 +132,12 @@ static void test_parlio_bitscrambler(void)
// Rx in MSB mode
printf("enable parlio and transmit\r\n");
TEST_ESP_OK(parlio_tx_unit_enable(tx_unit));
TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, 1));
TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, true));
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, rx_payload, TEST_PAYLOAD_SIZE, &recv_config));
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, tx_payload, TEST_PAYLOAD_SIZE * sizeof(uint8_t) * 8, &transmit_config));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)));
TEST_ASSERT_EQUAL(TEST_PAYLOAD_SIZE, rx_ctx.recv_bytes);
for (int i = 0; i < TEST_PAYLOAD_SIZE; i++) {
printf("%.3d ", (rx_payload[i]));
@@ -148,6 +160,7 @@ static void test_parlio_bitscrambler(void)
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, tx_payload, TEST_PAYLOAD_SIZE * sizeof(uint8_t) * 8, &transmit_config));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)));
TEST_ASSERT_EQUAL(TEST_PAYLOAD_SIZE, rx_ctx.recv_bytes);
for (int i = 0; i < TEST_PAYLOAD_SIZE; i++) {
printf("%.3d ", (rx_payload[i]));
@@ -166,6 +179,7 @@ static void test_parlio_bitscrambler(void)
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, tx_payload, TEST_PAYLOAD_SIZE * sizeof(uint8_t) * 8, &transmit_config));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)));
TEST_ASSERT_EQUAL(TEST_PAYLOAD_SIZE, rx_ctx.recv_bytes);
for (int i = 0; i < TEST_PAYLOAD_SIZE; i++) {
printf("%.3d ", (rx_payload[i]));
@@ -187,3 +201,156 @@ TEST_CASE("parlio_tx_bitscrambler_test", "[parlio_bitscrambler]")
{
test_parlio_bitscrambler();
}
#if SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
static void test_parlio_bitscrambler_different_input_output_sizes(void)
{
parlio_tx_unit_handle_t tx_unit = NULL;
parlio_tx_unit_config_t tx_config = {
.clk_src = PARLIO_CLK_SRC_DEFAULT,
.data_width = 4,
.clk_in_gpio_num = -1, // use internal clock source
.valid_gpio_num = TEST_VALID_GPIO,
.clk_out_gpio_num = TEST_CLK_GPIO,
.data_gpio_nums = {
TEST_DATA0_GPIO,
TEST_DATA1_GPIO,
TEST_DATA2_GPIO,
TEST_DATA3_GPIO,
},
.output_clk_freq_hz = 1 * 1000 * 1000,
.trans_queue_depth = 8,
.max_transfer_size = 256,
.bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB,
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
};
parlio_rx_unit_handle_t rx_unit = NULL;
parlio_rx_unit_config_t rx_config = {
.trans_queue_depth = 10,
.max_recv_size = 1024,
.data_width = 4,
.clk_src = PARLIO_CLK_SRC_DEFAULT,
.ext_clk_freq_hz = 0,
.clk_in_gpio_num = -1,
.exp_clk_freq_hz = 1 * 1000 * 1000,
.clk_out_gpio_num = -1,
.valid_gpio_num = TEST_VALID_GPIO,
.data_gpio_nums = {
TEST_DATA0_GPIO,
TEST_DATA1_GPIO,
TEST_DATA2_GPIO,
TEST_DATA3_GPIO,
},
.flags = {
.clk_gate_en = false,
}
};
printf("install parlio unit\r\n");
TEST_ESP_OK(parlio_new_tx_unit(&tx_config, &tx_unit));
TEST_ESP_OK(parlio_new_rx_unit(&rx_config, &rx_unit));
printf("decorate tx unit with bitscrambler\r\n");
TEST_ESP_OK(parlio_tx_unit_decorate_bitscrambler(tx_unit));
parlio_transmit_config_t transmit_config = {
.idle_value = 0x00,
.bitscrambler_program = bitscrambler_program_test_tx_in32_out8,
};
parlio_rx_delimiter_handle_t deli = NULL;
parlio_rx_level_delimiter_config_t lvl_deli_cfg = {
.valid_sig_line_id = PARLIO_RX_UNIT_MAX_DATA_WIDTH - 1,
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB,
.eof_data_len = TEST_PAYLOAD_SIZE,
.timeout_ticks = 0,
.flags = {
.active_low_en = 0,
},
};
TEST_ESP_OK(parlio_new_rx_level_delimiter(&lvl_deli_cfg, &deli));
printf("register receive_done event callback\r\n");
test_parlio_bitscrambler_rx_ctx_t rx_ctx = {
.task = xTaskGetCurrentTaskHandle(),
};
parlio_rx_event_callbacks_t rx_cbs = {
.on_receive_done = test_parlio_rx_done_callback,
};
TEST_ESP_OK(parlio_rx_unit_register_event_callbacks(rx_unit, &rx_cbs, &rx_ctx));
parlio_receive_config_t recv_config = {
.delimiter = deli,
.flags.partial_rx_en = false,
};
uint8_t tx_payload[TEST_PAYLOAD_SIZE * 4] = {0};
for (int i = 0; i < TEST_PAYLOAD_SIZE * 4; i++) {
tx_payload[i] = i;
}
__attribute__((aligned(TEST_PAYLOAD_SIZE))) uint8_t rx_payload[TEST_PAYLOAD_SIZE] = {0};
TEST_ESP_OK(parlio_tx_unit_enable(tx_unit));
TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, true));
// test input size is larger than output size
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, rx_payload, TEST_PAYLOAD_SIZE, &recv_config));
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, tx_payload, 4 * TEST_PAYLOAD_SIZE * sizeof(uint8_t) * 8, &transmit_config));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)));
TEST_ASSERT_EQUAL(TEST_PAYLOAD_SIZE, rx_ctx.recv_bytes);
for (int i = 0; i < TEST_PAYLOAD_SIZE; i++) {
printf("%.3d ", (rx_payload[i]));
TEST_ASSERT_EQUAL(tx_payload[i * 4 + 3], rx_payload[i]);
if ((i + 1) % 16 == 0) {
printf("\n");
}
}
printf("\n");
// test input size is smaller than output size
transmit_config.bitscrambler_program = bitscrambler_program_test_tx_in8_out32;
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, rx_payload, TEST_PAYLOAD_SIZE, &recv_config));
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, tx_payload, (TEST_PAYLOAD_SIZE / 4) * sizeof(uint8_t) * 8, &transmit_config));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)));
TEST_ASSERT_EQUAL(TEST_PAYLOAD_SIZE, rx_ctx.recv_bytes);
for (int i = 0; i < TEST_PAYLOAD_SIZE / 4; i++) {
uint32_t *test_value = (uint32_t *)&rx_payload[i * 4];
printf("0x%lx ", *test_value);
switch (i % 4) {
case 0:
TEST_ASSERT_EQUAL(0x11111, *test_value);
break;
case 1:
TEST_ASSERT_EQUAL(0x22222, *test_value);
break;
case 2:
TEST_ASSERT_EQUAL(0x33333, *test_value);
break;
case 3:
TEST_ASSERT_EQUAL(0x00000, *test_value);
break;
}
if ((i + 1) % 4 == 0) {
printf("\n");
}
}
TEST_ESP_OK(parlio_tx_unit_disable(tx_unit));
TEST_ESP_OK(parlio_tx_unit_undecorate_bitscrambler(tx_unit));
TEST_ESP_OK(parlio_del_tx_unit(tx_unit));
TEST_ESP_OK(parlio_rx_unit_disable(rx_unit));
TEST_ESP_OK(parlio_del_rx_delimiter(deli));
TEST_ESP_OK(parlio_del_rx_unit(rx_unit));
}
TEST_CASE("parlio_tx_bitscrambler_different_input_output_sizes_test", "[parlio_bitscrambler]")
{
test_parlio_bitscrambler_different_input_output_sizes();
}
#endif // SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
@@ -518,6 +518,22 @@ TEST_CASE("parallel tx unit use external non-free running clock", "[parlio_tx]")
};
#if SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
typedef struct {
uint32_t switch_count;
void *old_buffer_addr[5];
void *new_buffer_addr[5];
} test_parlio_tx_buffer_switched_context_t;
TEST_PARLIO_CALLBACK_ATTR
static bool test_parlio_tx_buffer_switched_callback(parlio_tx_unit_handle_t tx_unit, const parlio_tx_buffer_switched_event_data_t *edata, void *user_ctx)
{
test_parlio_tx_buffer_switched_context_t *context = (test_parlio_tx_buffer_switched_context_t *)user_ctx;
context->old_buffer_addr[context->switch_count] = edata->old_buffer_addr;
context->new_buffer_addr[context->switch_count] = edata->new_buffer_addr;
context->switch_count++;
return false;
}
TEST_CASE("parlio_tx_loop_transmission", "[parlio_tx]")
{
printf("install parlio tx unit\r\n");
@@ -545,6 +561,16 @@ TEST_CASE("parlio_tx_loop_transmission", "[parlio_tx]")
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
};
TEST_ESP_OK(parlio_new_tx_unit(&config, &tx_unit));
printf("register trans_done event callback\r\n");
parlio_tx_event_callbacks_t cbs = {
.on_buffer_switched = test_parlio_tx_buffer_switched_callback,
};
test_parlio_tx_buffer_switched_context_t context = {
.switch_count = 0,
};
TEST_ESP_OK(parlio_tx_unit_register_event_callbacks(tx_unit, &cbs, &context));
TEST_ESP_OK(parlio_tx_unit_enable(tx_unit));
printf("send packets and check event is fired\r\n");
@@ -560,37 +586,48 @@ TEST_CASE("parlio_tx_loop_transmission", "[parlio_tx]")
payload_loop2[i] = 255 - i;
payload_oneshot[i] = i * 2 + 1;
}
if (parlio_ll_tx_support_dma_eof(NULL)) { // for some chips, only support in particular ECO version
transmit_config.flags.loop_transmission = true;
int lopp_count = 3;
while (lopp_count--) {
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload_loop1, 256 * sizeof(uint8_t) * 8, &transmit_config));
vTaskDelay(pdMS_TO_TICKS(10));
// Should be sent after the previous frame has been completely sent
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload_loop2, 256 * sizeof(uint8_t) * 8, &transmit_config));
vTaskDelay(pdMS_TO_TICKS(10));
}
transmit_config.flags.loop_transmission = false;
// should be pending in queue
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload_oneshot, 256 * sizeof(uint8_t) * 8, &transmit_config));
transmit_config.flags.loop_transmission = true;
// there is a oneshot trans in queue, should also be pending in queue
transmit_config.flags.loop_transmission = true;
int lopp_count = 3;
while (lopp_count--) {
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload_loop1, 256 * sizeof(uint8_t) * 8, &transmit_config));
TEST_ESP_ERR(ESP_ERR_TIMEOUT, parlio_tx_unit_wait_all_done(tx_unit, 50));
// stop infinite loop transmission
parlio_tx_unit_disable(tx_unit);
// We should see 1 oneshot frame and 1 loop transmission (both pending in queue)
parlio_tx_unit_enable(tx_unit);
vTaskDelay(pdMS_TO_TICKS(10));
// stop the second infinite loop transmission
parlio_tx_unit_disable(tx_unit);
parlio_tx_unit_enable(tx_unit);
} else {
TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, parlio_tx_unit_transmit(tx_unit, payload_loop1, 256 * sizeof(uint8_t) * 8, &transmit_config));
// Should be sent after the previous frame has been completely sent
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload_loop2, 256 * sizeof(uint8_t) * 8, &transmit_config));
vTaskDelay(pdMS_TO_TICKS(10));
}
transmit_config.flags.loop_transmission = false;
// should be pending in queue
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload_oneshot, 256 * sizeof(uint8_t) * 8, &transmit_config));
transmit_config.flags.loop_transmission = true;
// there is a oneshot trans in queue, should also be pending in queue
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload_loop1, 256 * sizeof(uint8_t) * 8, &transmit_config));
TEST_ESP_ERR(ESP_ERR_TIMEOUT, parlio_tx_unit_wait_all_done(tx_unit, 50));
// stop infinite loop transmission
parlio_tx_unit_disable(tx_unit);
// We should see 1 oneshot frame and 1 loop transmission (both pending in queue)
parlio_tx_unit_enable(tx_unit);
vTaskDelay(pdMS_TO_TICKS(10));
// stop the second infinite loop transmission
parlio_tx_unit_disable(tx_unit);
parlio_tx_unit_enable(tx_unit);
// total 5 switch events
TEST_ASSERT_EQUAL(5, context.switch_count);
for (int i = 0; i < context.switch_count; i++) {
void *old_buffer_addr = context.old_buffer_addr[i];
void *new_buffer_addr = context.new_buffer_addr[i];
if (i % 2 == 0) {
TEST_ASSERT_EQUAL(payload_loop1, old_buffer_addr);
TEST_ASSERT_EQUAL(payload_loop2, new_buffer_addr);
} else {
TEST_ASSERT_EQUAL(payload_loop2, old_buffer_addr);
TEST_ASSERT_EQUAL(payload_loop1, new_buffer_addr);
}
}
TEST_ESP_OK(parlio_tx_unit_wait_all_done(tx_unit, -1));
@@ -598,3 +635,51 @@ TEST_CASE("parlio_tx_loop_transmission", "[parlio_tx]")
TEST_ESP_OK(parlio_del_tx_unit(tx_unit));
}
#endif // SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
#if SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
TEST_CASE("parlio_tx can transmit buffer larger than max_size decided by datalen_eof", "[parlio_tx]")
{
printf("install parlio tx unit\r\n");
parlio_tx_unit_handle_t tx_unit = NULL;
parlio_tx_unit_config_t config = {
.clk_src = PARLIO_CLK_SRC_DEFAULT,
.data_width = 4,
.clk_in_gpio_num = -1, // use internal clock source
.valid_gpio_num = TEST_VALID_GPIO, // generate the valid signal
.clk_out_gpio_num = TEST_CLK_GPIO,
.data_gpio_nums = {
TEST_DATA0_GPIO,
TEST_DATA1_GPIO,
TEST_DATA2_GPIO,
TEST_DATA3_GPIO,
},
.output_clk_freq_hz = 10 * 1000 * 1000,
.trans_queue_depth = 1,
.max_transfer_size = 100 * 1024,
.bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB,
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.flags.clk_gate_en = true,
};
TEST_ESP_OK(parlio_new_tx_unit(&config, &tx_unit));
TEST_ESP_OK(parlio_tx_unit_enable(tx_unit));
const size_t buffer_size = 100 * 1024; // 100KB, larger than the 65535 bytes limit
uint8_t *buffer = heap_caps_malloc(buffer_size, MALLOC_CAP_8BIT | MALLOC_CAP_DMA);
TEST_ASSERT_NOT_NULL(buffer);
for (int i = 0; i < buffer_size; i++) {
buffer[i] = i;
}
parlio_transmit_config_t transmit_config = {
.idle_value = 0x00,
};
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, buffer, buffer_size * 8, &transmit_config));
TEST_ESP_OK(parlio_tx_unit_wait_all_done(tx_unit, -1));
TEST_ESP_OK(parlio_tx_unit_disable(tx_unit));
TEST_ESP_OK(parlio_del_tx_unit(tx_unit));
free(buffer);
}
#endif // SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
@@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "unity_test_utils.h"
#include "driver/parlio_tx.h"
#include "driver/gpio.h"
#include "hal/parlio_ll.h"
#include "soc/soc_caps.h"
#include "esp_attr.h"
#include "test_board.h"
static void IRAM_ATTR test_delay_post_cache_disable(void *args)
{
esp_rom_delay_us(100000);
}
static void test_parlio_tx_cache_safe(void)
{
printf("install parlio tx unit\r\n");
parlio_tx_unit_handle_t tx_unit = NULL;
parlio_tx_unit_config_t config = {
.clk_src = PARLIO_CLK_SRC_DEFAULT,
.data_width = 1,
.clk_in_gpio_num = -1, // use internal clock source
.valid_gpio_num = TEST_VALID_GPIO, // generate the valid signal
.clk_out_gpio_num = TEST_CLK_GPIO,
.data_gpio_nums = {
TEST_DATA0_GPIO,
},
.output_clk_freq_hz = 10 * 1000 * 1000,
.trans_queue_depth = 4,
.max_transfer_size = 65535,
.bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB,
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
};
TEST_ESP_OK(parlio_new_tx_unit(&config, &tx_unit));
TEST_ESP_OK(parlio_tx_unit_enable(tx_unit));
const size_t buffer_size = 160 * 1000;
const size_t chunk_size = buffer_size / 4; // 40KB per trunk
uint8_t *buffer = heap_caps_malloc(buffer_size, MALLOC_CAP_8BIT | MALLOC_CAP_DMA);
TEST_ASSERT_NOT_NULL(buffer);
for (int i = 0; i < buffer_size; i++) {
buffer[i] = i;
}
parlio_transmit_config_t transmit_config = {
.idle_value = 0x00,
};
for (int i = 0; i < 20; i++) {
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, buffer + (i % 4) * chunk_size, chunk_size * 8, &transmit_config));
}
// during the cache disabled period, the parlio tx unit can be scheduled as well
unity_utils_run_cache_disable_stub(test_delay_post_cache_disable, NULL);
TEST_ESP_OK(parlio_tx_unit_wait_all_done(tx_unit, -1));
TEST_ESP_OK(parlio_tx_unit_disable(tx_unit));
TEST_ESP_OK(parlio_del_tx_unit(tx_unit));
free(buffer);
}
TEST_CASE("parlio tx works with cache disabled", "[parlio]")
{
test_parlio_tx_cache_safe();
}
@@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
cfg prefetch true # enable data prefetch
cfg eof_on upstream # set EOF on upstream
cfg trailing_bytes 9 # due to prefetch is enable, upstream is 8 bytes ahead of downstream
loop:
set 0..7 24..31,
write 8,
read 32,
jmp loop
@@ -0,0 +1,21 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
cfg prefetch false # disable data prefetch
cfg eof_on upstream # set EOF on upstream
cfg trailing_bytes 9
cfg lut_width_bits 32
# Define contents that stored in the lookup table
lut 0x00011111 # index 0
lut 0x00022222 # index 1
lut 0x00033333 # index 2
lut 0x00000000 # index 3
set 16..18 L # init the LUT index: 0 (0b000)
loop:
read 8,
set 31..0 L31..L0,
write 32,
jmp loop