feat: add I2S support with formal verification and testbench

- Updated filelist to include I2S related files.
- Enhanced formal file generation script to support I2S target.
- Introduced I2S formal design and properties for verification.
- Modified formal makefile to include I2S targets and source files.
- Added I2S related source files to software build.
- Updated style audit to include I2S files.
- Implemented I2S helper functions in C tests.
- Created I2S testbench for RTL simulation.
- Added regression test for I2S controller functionality.
This commit is contained in:
maksyuki committed 2026-08-17 15:53:39 +08:00
1 parent 6bb5dc2344
commit 270343a891
31 files changed
+1917 -568

No files matched your search

+2 -2
View File
@@ -269,7 +269,7 @@ endif
benchmark-report coremark-report \
pin-map check-pin-map soc-topology check-soc-topology user-extensions check-user-extensions \
check-clock-reset-domains tech-cell-test rtl-lint check-rtl-lint \
formal formal-bus formal-rib-adapter formal-rib2apb formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-dvp formal-clean formal-doctor \
formal formal-bus formal-rib-adapter formal-rib2apb formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-dvp formal-i2s formal-clean formal-doctor \
rtl-style-check-all rtl-readiness-check rtl-readiness-check-all
.NOTPARALLEL: setup
@@ -298,7 +298,7 @@ help:
' check-clock-reset-domains validate the root clock/reset and CDC inventory' \
' rtl-lint | check-rtl-lint run/check strict Verilator RTL lint warnings' \
' formal | formal-bus | formal-rib-adapter | formal-rib2apb run SBY protocol proofs' \
' formal-sysctrl | formal-pll-rcu | formal-gpio | formal-ws2812 | formal-uart | formal-i2c | formal-timer | formal-clint | formal-dvp run peripheral proofs' \
' formal-sysctrl | formal-pll-rcu | formal-gpio | formal-ws2812 | formal-uart | formal-i2c | formal-timer | formal-clint | formal-dvp | formal-i2s run peripheral proofs' \
' formal-doctor check the SBY, Yosys, sv2v, and Bitwuzla formal toolchain' \
' benchmark-report run the memory/DMA profile and write meta/performance.json' \
' coremark-report run the quick CoreMark profile and write meta/coremark.json' \
+1 -1
View File
@@ -1,6 +1,6 @@
APP_SRCS += $(ROOT_PATH)/app/apps/shell/main.c
APP_SRCS += $(ROOT_PATH)/crt/src/service/shell.c
APP_SRCS += $(ROOT_PATH)/crt/src/hal/i2s.c
APP_SRCS += $(ROOT_PATH)/app/apps/shell/i2s_player.c
APP_SRCS += $(ROOT_PATH)/app/board/src/at24cxx.c
APP_SRCS += $(ROOT_PATH)/app/board/src/pcf8563b.c
APP_SRCS += $(ROOT_PATH)/app/board/src/es8388.c
+127
View File
@@ -0,0 +1,127 @@
#include <retrosoc/board/es8388.h>
#include <retrosoc/core/soc.h>
#include <retrosoc/hal/dma.h>
#include <retrosoc/hal/i2s.h>
#include <retrosoc/lib/console.h>
#include <retrosoc/lib/printf.h>
#include <retrosoc/media/wav_audio.h>
static const uint32_t audio_addr[] = {0x61004000U, 0x64737000U};
static const uint32_t audio_len = 2U;
static uint32_t audio_idx;
static rs_status_t i2s_init(bool loopback) {
rs_i2s_config_t config = {
.loopback = loopback,
.stream_tx = !loopback,
.stream_rx = false,
.clock_prog = false,
.preset = RS_I2S_PRESET_16B_48K,
.bitmode_24 = false,
.sclk_div = 0U,
.lrck_div = 0U,
.mclk_div = 0U,
.upbound = 120U,
.lowbound = 80U,
};
if (rs_i2s_configure(&config) != RS_OK)
return RS_EIO;
return rs_i2s_enable(!loopback, false);
}
static void i2s_audio_load(void) {
rs_wav_info_t info;
const uint32_t start_address = audio_addr[audio_idx];
const uint32_t available_size =
(start_address >= TF_CARD_START) && (start_address < (TF_CARD_START + TF_CARD_OFFST))
? TF_CARD_OFFST - (start_address - TF_CARD_START)
: 0U;
if ((available_size == 0U) ||
(rs_wav_parse_spisd(start_address, available_size, &info) != RS_OK) ||
((info.data_size % sizeof(uint32_t)) != 0U) ||
(rs_dma_config(RS_DMA_MODE_I2S_TX, start_address + info.data_offset, 1U,
rs_i2s_txdata_address(), 0U, info.data_size / sizeof(uint32_t)) != RS_OK)) {
printf("wav file parse/configuration error\n");
}
}
static void i2s_audio_panel(void) {
printf("============================================================\n");
printf(" retroSoC Audio Player \n");
printf(" system: help[h] exit[e] mode[m] next-audio[n] \n");
printf("============================================================\n");
printf(" player: play[s] pause[t] reset[r] vol-up[u] vol-down[d] \n");
printf("============================================================\n");
}
void ip_i2s_player(int argc, char **argv) {
char type_ch;
uint32_t mode = 0, pause = 0, xfering = 0;
(void)argc;
(void)argv;
es8388_init();
printf("[APB IP] i2s test\n");
i2s_audio_panel();
while (true) {
type_ch = getchar();
if (xfering) {
if (reg_dma_status == (uint32_t)1) {
printf("dma tx done\n");
xfering = (uint32_t)0;
}
}
if (type_ch == 'e' && !xfering)
break;
else if (type_ch == 'h' && !xfering)
i2s_audio_panel();
else if (type_ch == 'n' && !xfering) {
if (audio_idx == audio_len - 1U)
audio_idx = 0U;
else
++audio_idx;
i2s_audio_load();
} else if (type_ch == 'm' && !xfering) {
if (mode != 0U) {
mode = 0U;
printf("switch to loopback mode\n");
} else {
mode = 1U;
printf("switch to fifo-xfer mode\n");
}
(void)i2s_init(mode == 0U);
} else if (type_ch == 's' && !xfering) {
(void)i2s_init(false);
xfering = 1U;
if (rs_dma_start() == RS_OK) {
printf("start xfer\n");
} else {
xfering = 0U;
}
} else if (type_ch == 't' && xfering) {
if (pause != 0U) {
pause = 0U;
(void)rs_dma_stop();
(void)i2s_init(false);
printf("resume audio play\n");
} else {
pause = 1U;
(void)i2s_init(true);
(void)rs_dma_stop();
printf("fsm: %d\n", reg_dma_fsm);
printf("pause audio play\n");
}
} else if (type_ch == 'r' && xfering) {
xfering = 0U;
(void)i2s_init(true);
(void)rs_dma_reset();
printf("reset audio play\n");
}
}
(void)i2s_init(true);
}
+1 -1
View File
@@ -65,7 +65,7 @@ int main(void) {
(void)rs_shell_register("crc", "crc test", true, rs_crc_shell_test);
(void)rs_shell_register("ps2", "ps2 test", false, rs_ps2_shell_test);
(void)rs_shell_register("lcd", "lcd test", true, ip_lcd_test);
(void)rs_shell_register("i2s", "i2s test", false, ip_i2s_test);
(void)rs_shell_register("i2s", "i2s test", false, ip_i2s_player);
(void)rs_shell_register("nor", "nor flash test", false, ip_norflash_test);
(void)rs_shell_register("pcf", "pcf8563b test", true, pcf8563b_test);
#ifdef CSR_ENABLE
-10
View File
@@ -34,16 +34,6 @@
#define reg_spisd_rxdata RS_SOC_REG32(RS_SOC_APB4_SPISD_BASE, UINT32_C(0x10))
#define reg_spisd_status RS_SOC_REG32(RS_SOC_APB4_SPISD_BASE, UINT32_C(0x14))
#define reg_spisd_sync RS_SOC_REG32(RS_SOC_APB4_SPISD_BASE, UINT32_C(0x18))
// i2s
#define reg_i2s_mode RS_SOC_REG32(RS_SOC_APB4_I2S_BASE, UINT32_C(0x00))
#define reg_i2s_format RS_SOC_REG32(RS_SOC_APB4_I2S_BASE, UINT32_C(0x04))
#define reg_i2s_upbound RS_SOC_REG32(RS_SOC_APB4_I2S_BASE, UINT32_C(0x08))
#define reg_i2s_lowbound RS_SOC_REG32(RS_SOC_APB4_I2S_BASE, UINT32_C(0x0C))
#define reg_i2s_recven RS_SOC_REG32(RS_SOC_APB4_I2S_BASE, UINT32_C(0x10))
#define reg_i2s_txdata RS_SOC_REG32(RS_SOC_APB4_I2S_BASE, UINT32_C(0x14))
#define reg_i2s_rxdata RS_SOC_REG32(RS_SOC_APB4_I2S_BASE, UINT32_C(0x18))
#define reg_i2s_status RS_SOC_REG32(RS_SOC_APB4_I2S_BASE, UINT32_C(0x1C))
#define reg_i2s_stream_ctrl RS_SOC_REG32(RS_SOC_APB4_I2S_BASE, UINT32_C(0x20))
// xpi
#define reg_xpi_cfgidx RS_SOC_REG32(RS_SOC_APB4_XPI_BASE, UINT32_C(0x00))
#define reg_xpi_accmd RS_SOC_REG32(RS_SOC_APB4_XPI_BASE, UINT32_C(0x04))
+75 -2
View File
@@ -1,6 +1,79 @@
#ifndef RETROSOC_I2S_H
#define RETROSOC_I2S_H
#ifndef RETROSOC_HAL_I2S_H
#define RETROSOC_HAL_I2S_H
#include <stdbool.h>
#include <stdint.h>
#include <retrosoc/core/status.h>
#define RS_I2S_INTERRUPT_TX_LOW UINT32_C(0x00000001)
#define RS_I2S_INTERRUPT_RX_HIGH UINT32_C(0x00000002)
#define RS_I2S_INTERRUPT_TX_UNDERRUN UINT32_C(0x00000004)
#define RS_I2S_INTERRUPT_RX_OVERRUN UINT32_C(0x00000008)
#define RS_I2S_INTERRUPT_ALL UINT32_C(0x0000000F)
#define RS_I2S_STATUS_TX_FULL UINT32_C(0x00000001)
#define RS_I2S_STATUS_TX_EMPTY UINT32_C(0x00000002)
#define RS_I2S_STATUS_RX_FULL UINT32_C(0x00000004)
#define RS_I2S_STATUS_RX_EMPTY UINT32_C(0x00000008)
#define RS_I2S_STATUS_TX_STALL UINT32_C(0x00000010)
#define RS_I2S_STATUS_RX_STALL UINT32_C(0x00000020)
#define RS_I2S_STATUS_ENABLE UINT32_C(0x00000040)
#define RS_I2S_STATUS_BUSY UINT32_C(0x00000080)
#define RS_I2S_STATUS_TX_FLUSH_BUSY UINT32_C(0x01000000)
#define RS_I2S_STATUS_RX_FLUSH_BUSY UINT32_C(0x02000000)
typedef enum {
RS_I2S_PRESET_16B_48K = 0,
RS_I2S_PRESET_16B_96K = 1,
RS_I2S_PRESET_24B_48K = 2,
RS_I2S_PRESET_24B_96K = 3,
} rs_i2s_preset_t;
typedef struct {
bool loopback;
bool stream_tx;
bool stream_rx;
bool clock_prog;
rs_i2s_preset_t preset;
bool bitmode_24;
uint8_t sclk_div;
uint8_t lrck_div;
uint8_t mclk_div;
uint8_t upbound;
uint8_t lowbound;
} rs_i2s_config_t;
typedef struct {
uint32_t status;
uint8_t tx_level;
uint8_t rx_level;
uint32_t interrupt_state;
bool enable;
bool tx_full;
bool tx_empty;
bool rx_full;
bool rx_empty;
} rs_i2s_status_t;
uint32_t rs_i2s_pack_stereo16(uint16_t first, uint16_t second);
void rs_i2s_unpack_stereo16(uint32_t word, uint16_t *first, uint16_t *second);
rs_status_t rs_i2s_div_from_hz(uint32_t audio_clock_hz, uint32_t sample_hz, uint32_t sample_bits,
uint8_t *sclk_div, uint8_t *lrck_div);
rs_status_t rs_i2s_probe(uint32_t *version, uint32_t *capability);
rs_status_t rs_i2s_configure(const rs_i2s_config_t *config);
rs_status_t rs_i2s_enable(bool tx, bool rx);
rs_status_t rs_i2s_disable(void);
rs_status_t rs_i2s_flush(bool tx, bool rx, rs_timeout_t timeout);
rs_status_t rs_i2s_write(uint32_t word, rs_timeout_t timeout);
rs_status_t rs_i2s_read(uint32_t *word, rs_timeout_t timeout);
uintptr_t rs_i2s_txdata_address(void);
uintptr_t rs_i2s_rxdata_address(void);
rs_status_t rs_i2s_get_status(rs_i2s_status_t *status);
rs_status_t rs_i2s_interrupt_enable(uint32_t mask);
rs_status_t rs_i2s_interrupt_clear(uint32_t mask);
rs_status_t rs_i2s_interrupt_test(uint32_t mask);
void ip_i2s_test(int argc, char **argv);
void ip_i2s_player(int argc, char **argv);
#endif
+27 -14
View File
@@ -1,6 +1,7 @@
#include <retrosoc/core/soc.h>
#include <retrosoc/core/wait.h>
#include <retrosoc/hal/dma.h>
#include <retrosoc/hal/i2s.h>
#include <retrosoc/lib/printf.h>
rs_status_t rs_dma_config(uint32_t mode, uintptr_t source, uint32_t source_increment,
@@ -69,31 +70,43 @@ rs_status_t rs_dma_get_error(rs_dma_error_t *error) {
}
void ip_dma_test(void) {
printf("dma test\n");
reg_i2s_mode = 1U;
reg_i2s_upbound = 120U;
reg_i2s_lowbound = 32U;
reg_i2s_recven = 0U;
reg_i2s_stream_ctrl = 1U;
rs_i2s_config_t i2s_config = {
.loopback = false,
.stream_tx = true,
.stream_rx = false,
.clock_prog = false,
.preset = RS_I2S_PRESET_16B_48K,
.bitmode_24 = false,
.sclk_div = 0U,
.lrck_div = 0U,
.mclk_div = 0U,
.upbound = 120U,
.lowbound = 32U,
};
if ((rs_dma_config(RS_DMA_MODE_I2S_TX, 0x40000000U, 1U, (uintptr_t)&reg_i2s_txdata, 0U, 512U) !=
printf("dma test\n");
if ((rs_i2s_configure(&i2s_config) != RS_OK) || (rs_i2s_enable(true, false) != RS_OK)) {
printf("dma i2s configure failed\n");
return;
}
if ((rs_dma_config(RS_DMA_MODE_I2S_TX, 0x40000000U, 1U, rs_i2s_txdata_address(), 0U, 512U) !=
RS_OK) ||
(rs_dma_start() != RS_OK) || (rs_dma_wait(RS_TIMEOUT_DEFAULT) != RS_OK)) {
printf("dma transmit failed\n");
return;
}
reg_i2s_recven = 1U;
reg_i2s_stream_ctrl = 2U;
if ((rs_dma_config(RS_DMA_MODE_I2S_RX, (uintptr_t)&reg_i2s_rxdata, 0U, 0x41000000U, 1U, 180U) !=
i2s_config.stream_tx = false;
i2s_config.stream_rx = true;
if ((rs_i2s_configure(&i2s_config) != RS_OK) || (rs_i2s_enable(false, true) != RS_OK) ||
(rs_dma_config(RS_DMA_MODE_I2S_RX, rs_i2s_rxdata_address(), 0U, 0x41000000U, 1U, 180U) !=
RS_OK) ||
(rs_dma_start() != RS_OK) || (rs_dma_wait(RS_TIMEOUT_DEFAULT) != RS_OK)) {
printf("dma receive failed\n");
reg_i2s_recven = 0U;
reg_i2s_stream_ctrl = 0U;
(void)rs_i2s_disable();
return;
}
reg_i2s_recven = 0U;
reg_i2s_stream_ctrl = 0U;
(void)rs_i2s_disable();
printf("dma test passed\n");
}
+176 -104
View File
@@ -1,121 +1,193 @@
#include <stddef.h>
#include <retrosoc/core/soc.h>
#include <retrosoc/lib/console.h>
#include <retrosoc/lib/printf.h>
#include <retrosoc/hal/dma.h>
#include <retrosoc/hal/i2s.h>
#include <retrosoc/media/wav_audio.h>
#include <retrosoc/board/es8388.h>
#include <retrosoc/lib/printf.h>
static const uint32_t audio_addr[] = {0x61004000U, 0x64737000U};
static const uint32_t audio_len = 2U;
static uint32_t audio_idx;
#define RS_I2S_CTRL_OFFSET UINT32_C(0x000)
#define RS_I2S_COMMAND_OFFSET UINT32_C(0x004)
#define RS_I2S_STATUS_OFFSET UINT32_C(0x008)
#define RS_I2S_STREAM_CTRL_OFFSET UINT32_C(0x00C)
#define RS_I2S_FORMAT_OFFSET UINT32_C(0x010)
#define RS_I2S_CLK_DIV_OFFSET UINT32_C(0x014)
#define RS_I2S_FIFO_TH_OFFSET UINT32_C(0x018)
#define RS_I2S_INTR_STATE_OFFSET UINT32_C(0x024)
#define RS_I2S_INTR_ENABLE_OFFSET UINT32_C(0x028)
#define RS_I2S_INTR_STATUS_OFFSET UINT32_C(0x02C)
#define RS_I2S_INTR_TEST_OFFSET UINT32_C(0x030)
#define RS_I2S_VERSION_OFFSET UINT32_C(0x0F8)
#define RS_I2S_CAPABILITY_OFFSET UINT32_C(0x0FC)
static void i2s_init(uint32_t mode) {
reg_i2s_mode = (uint32_t)mode;
reg_i2s_stream_ctrl = (mode == 0U) ? 0U : 1U;
reg_i2s_upbound = (uint32_t)120;
// NOTE: larger than 'clk/clk_aud 'size of i2x tx fifo
reg_i2s_lowbound = (uint32_t)80;
#define RS_I2S_CTRL_ENABLE_MASK UINT32_C(0x00000001)
#define RS_I2S_CTRL_TX_ENABLE_MASK UINT32_C(0x00000002)
#define RS_I2S_CTRL_RX_ENABLE_MASK UINT32_C(0x00000004)
#define RS_I2S_CTRL_LOOPBACK_MASK UINT32_C(0x00000008)
#define RS_I2S_CTRL_CLK_PROG_MASK UINT32_C(0x00000010)
#define RS_I2S_COMMAND_TX_FLUSH UINT32_C(0x00000001)
#define RS_I2S_COMMAND_RX_FLUSH UINT32_C(0x00000002)
#define RS_I2S_STATUS_LEVEL_MASK UINT32_C(0x000000FF)
static volatile uint32_t *rs_i2s_register(uint32_t offset) {
return (volatile uint32_t *)(RS_SOC_APB4_I2S_BASE + (uintptr_t)offset);
}
static void i2s_audio_load(void) {
rs_wav_info_t info;
const uint32_t start_address = audio_addr[audio_idx];
const uint32_t available_size =
(start_address >= TF_CARD_START) && (start_address < (TF_CARD_START + TF_CARD_OFFST))
? TF_CARD_OFFST - (start_address - TF_CARD_START)
: 0U;
static bool rs_i2s_config_valid(const rs_i2s_config_t *config) {
if ((config == NULL) || ((uint32_t)config->preset > (uint32_t)RS_I2S_PRESET_24B_96K))
return false;
if (config->lowbound > config->upbound)
return false;
return true;
}
if ((available_size == 0U) ||
(rs_wav_parse_spisd(start_address, available_size, &info) != RS_OK) ||
((info.data_size % sizeof(uint32_t)) != 0U) ||
(rs_dma_config(RS_DMA_MODE_I2S_TX, start_address + info.data_offset, 1U,
(uintptr_t)&reg_i2s_txdata, 0U,
info.data_size / sizeof(uint32_t)) != RS_OK)) {
printf("wav file parse/configuration error\n");
rs_status_t rs_i2s_probe(uint32_t *version, uint32_t *capability) {
if ((version == NULL) || (capability == NULL))
return RS_EINVAL;
*version = *rs_i2s_register(RS_I2S_VERSION_OFFSET);
*capability = *rs_i2s_register(RS_I2S_CAPABILITY_OFFSET);
return (*version == UINT32_C(0x00010000)) ? RS_OK : RS_EIO;
}
rs_status_t rs_i2s_configure(const rs_i2s_config_t *config) {
uint32_t control;
uint32_t format;
uint32_t clock_div;
if (!rs_i2s_config_valid(config))
return RS_EINVAL;
(void)rs_i2s_disable();
format = (uint32_t)config->preset;
if (config->bitmode_24)
format |= UINT32_C(0x00000004);
clock_div = (uint32_t)config->sclk_div | ((uint32_t)config->lrck_div << 8) |
((uint32_t)config->mclk_div << 16);
control = (config->loopback ? RS_I2S_CTRL_LOOPBACK_MASK : 0U) |
(config->clock_prog ? RS_I2S_CTRL_CLK_PROG_MASK : 0U);
*rs_i2s_register(RS_I2S_FORMAT_OFFSET) = format;
*rs_i2s_register(RS_I2S_CLK_DIV_OFFSET) = clock_div;
*rs_i2s_register(RS_I2S_FIFO_TH_OFFSET) =
(uint32_t)config->upbound | ((uint32_t)config->lowbound << 8);
*rs_i2s_register(RS_I2S_STREAM_CTRL_OFFSET) =
(config->stream_tx ? 1U : 0U) | (config->stream_rx ? 2U : 0U);
*rs_i2s_register(RS_I2S_INTR_STATE_OFFSET) = RS_I2S_INTERRUPT_ALL;
*rs_i2s_register(RS_I2S_CTRL_OFFSET) = control;
return RS_OK;
}
rs_status_t rs_i2s_enable(bool tx, bool rx) {
uint32_t control;
control = *rs_i2s_register(RS_I2S_CTRL_OFFSET);
control |= RS_I2S_CTRL_ENABLE_MASK;
if (tx)
control |= RS_I2S_CTRL_TX_ENABLE_MASK;
else
control &= ~RS_I2S_CTRL_TX_ENABLE_MASK;
if (rx)
control |= RS_I2S_CTRL_RX_ENABLE_MASK;
else
control &= ~RS_I2S_CTRL_RX_ENABLE_MASK;
*rs_i2s_register(RS_I2S_CTRL_OFFSET) = control;
return RS_OK;
}
rs_status_t rs_i2s_disable(void) {
*rs_i2s_register(RS_I2S_CTRL_OFFSET) &=
~(RS_I2S_CTRL_ENABLE_MASK | RS_I2S_CTRL_TX_ENABLE_MASK | RS_I2S_CTRL_RX_ENABLE_MASK);
return RS_OK;
}
rs_status_t rs_i2s_flush(bool tx, bool rx, rs_timeout_t timeout) {
uint32_t command;
uint32_t status;
uint32_t busy_mask;
command = (tx ? RS_I2S_COMMAND_TX_FLUSH : 0U) | (rx ? RS_I2S_COMMAND_RX_FLUSH : 0U);
busy_mask = (tx ? RS_I2S_STATUS_TX_FLUSH_BUSY : 0U) | (rx ? RS_I2S_STATUS_RX_FLUSH_BUSY : 0U);
if (command == 0U)
return RS_EINVAL;
*rs_i2s_register(RS_I2S_COMMAND_OFFSET) = command;
while (timeout-- != 0U) {
status = *rs_i2s_register(RS_I2S_STATUS_OFFSET);
if ((status & busy_mask) == 0U)
return RS_OK;
}
return RS_ETIMEOUT;
}
static void i2s_audio_panel(void) {
printf("============================================================\n");
printf(" retroSoC Audio Player \n");
printf(" system: help[h] exit[e] mode[m] next-audio[n] \n");
printf("============================================================\n");
printf(" player: play[s] pause[t] reset[r] vol-up[u] vol-down[d] \n");
printf("============================================================\n");
rs_status_t rs_i2s_write(uint32_t word, rs_timeout_t timeout) {
uint32_t status;
while (timeout-- != 0U) {
status = *rs_i2s_register(RS_I2S_STATUS_OFFSET);
if ((status & RS_I2S_STATUS_TX_FULL) == 0U) {
*(volatile uint32_t *)rs_i2s_txdata_address() = word;
return RS_OK;
}
}
return RS_ETIMEOUT;
}
rs_status_t rs_i2s_read(uint32_t *word, rs_timeout_t timeout) {
uint32_t status;
if (word == NULL)
return RS_EINVAL;
while (timeout-- != 0U) {
status = *rs_i2s_register(RS_I2S_STATUS_OFFSET);
if ((status & RS_I2S_STATUS_RX_EMPTY) == 0U) {
*word = *(volatile uint32_t *)rs_i2s_rxdata_address();
return RS_OK;
}
}
return RS_ETIMEOUT;
}
rs_status_t rs_i2s_get_status(rs_i2s_status_t *status) {
if (status == NULL)
return RS_EINVAL;
status->status = *rs_i2s_register(RS_I2S_STATUS_OFFSET);
status->tx_level = (uint8_t)((status->status >> 8) & RS_I2S_STATUS_LEVEL_MASK);
status->rx_level = (uint8_t)((status->status >> 16) & RS_I2S_STATUS_LEVEL_MASK);
status->interrupt_state = *rs_i2s_register(RS_I2S_INTR_STATE_OFFSET);
status->enable = (status->status & RS_I2S_STATUS_ENABLE) != 0U;
status->tx_full = (status->status & RS_I2S_STATUS_TX_FULL) != 0U;
status->tx_empty = (status->status & RS_I2S_STATUS_TX_EMPTY) != 0U;
status->rx_full = (status->status & RS_I2S_STATUS_RX_FULL) != 0U;
status->rx_empty = (status->status & RS_I2S_STATUS_RX_EMPTY) != 0U;
return RS_OK;
}
rs_status_t rs_i2s_interrupt_enable(uint32_t mask) {
if ((mask & ~RS_I2S_INTERRUPT_ALL) != 0U)
return RS_EINVAL;
*rs_i2s_register(RS_I2S_INTR_ENABLE_OFFSET) = mask;
return RS_OK;
}
rs_status_t rs_i2s_interrupt_clear(uint32_t mask) {
if ((mask & ~RS_I2S_INTERRUPT_ALL) != 0U)
return RS_EINVAL;
*rs_i2s_register(RS_I2S_INTR_STATE_OFFSET) = mask;
return RS_OK;
}
rs_status_t rs_i2s_interrupt_test(uint32_t mask) {
if ((mask & ~RS_I2S_INTERRUPT_ALL) != 0U)
return RS_EINVAL;
*rs_i2s_register(RS_I2S_INTR_TEST_OFFSET) = mask;
return RS_OK;
}
void ip_i2s_test(int argc, char **argv) {
uint32_t version;
uint32_t capability;
(void)argc;
(void)argv;
char type_ch;
uint32_t mode = 0, pause = 0, xfering = 0;
es8388_init();
printf("[APB IP] i2s test\n");
// load first data
// i2s_audio_load();
i2s_audio_panel();
while (true) {
type_ch = getchar();
if (xfering) {
if (reg_dma_status == (uint32_t)1) {
printf("dma tx done\n");
xfering = (uint32_t)0;
}
}
if (type_ch == 'e' && !xfering)
break;
else if (type_ch == 'h' && !xfering)
i2s_audio_panel();
else if (type_ch == 'n' && !xfering) {
if (audio_idx == audio_len - 1)
audio_idx = 0;
else
++audio_idx;
i2s_audio_load();
} else if (type_ch == 'm' && !xfering) {
if (mode) {
mode = (uint32_t)0;
printf("switch to loopback mode\n");
} else {
mode = (uint32_t)1;
printf("switch to fifo-xfer mode\n");
}
i2s_init(mode);
} else if (type_ch == 's' && !xfering) {
// force switching to fifo-xfer mode
i2s_init(1);
xfering = (uint32_t)1;
if (rs_dma_start() == RS_OK) {
printf("start xfer\n");
} else {
xfering = 0U;
}
} else if (type_ch == 't' && xfering) {
if (pause) {
pause = (uint32_t)0;
(void)rs_dma_stop();
i2s_init(1);
printf("resume audio play\n");
} else {
pause = (uint32_t)1;
i2s_init(0);
(void)rs_dma_stop();
printf("fsm: %d\n", reg_dma_fsm);
printf("pause audio play\n");
}
} else if (type_ch == 'r' && xfering) {
xfering = (uint32_t)0;
i2s_init(0);
(void)rs_dma_reset();
printf("reset audio play\n");
}
printf("i2s test\n");
if (rs_i2s_probe(&version, &capability) == RS_OK) {
printf("i2s v%lx capability %lx\n", (unsigned long)version, (unsigned long)capability);
} else {
printf("i2s probe failed\n");
}
i2s_init(0);
}
+47
View File
@@ -0,0 +1,47 @@
#include <stddef.h>
#include <retrosoc/generated/memory_map.h>
#include <retrosoc/hal/i2s.h>
#define RS_I2S_TXDATA_OFFSET UINT32_C(0x01C)
#define RS_I2S_RXDATA_OFFSET UINT32_C(0x020)
uint32_t rs_i2s_pack_stereo16(uint16_t first, uint16_t second) {
return ((uint32_t)second << 16) | first;
}
void rs_i2s_unpack_stereo16(uint32_t word, uint16_t *first, uint16_t *second) {
if (first != NULL)
*first = (uint16_t)word;
if (second != NULL)
*second = (uint16_t)(word >> 16);
}
rs_status_t rs_i2s_div_from_hz(uint32_t audio_clock_hz, uint32_t sample_hz, uint32_t sample_bits,
uint8_t *sclk_div, uint8_t *lrck_div) {
uint64_t sclk_hz;
uint64_t period;
if ((audio_clock_hz == 0U) || (sample_hz == 0U) || (sclk_div == NULL) || (lrck_div == NULL) ||
((sample_bits != 16U) && (sample_bits != 24U))) {
return RS_EINVAL;
}
sclk_hz = (uint64_t)sample_hz * 2U * (uint64_t)sample_bits;
if ((sclk_hz == 0U) || ((uint64_t)audio_clock_hz % (sclk_hz * 2U) != 0U))
return RS_EINVAL;
period = (uint64_t)audio_clock_hz / (sclk_hz * 2U);
if ((period == 0U) || (period > 256U))
return RS_EINVAL;
*sclk_div = (uint8_t)(period - 1U);
*lrck_div = (uint8_t)(sample_bits - 1U);
return RS_OK;
}
uintptr_t rs_i2s_txdata_address(void) {
return RS_SOC_APB4_I2S_BASE + (uintptr_t)RS_I2S_TXDATA_OFFSET;
}
uintptr_t rs_i2s_rxdata_address(void) {
return RS_SOC_APB4_I2S_BASE + (uintptr_t)RS_I2S_RXDATA_OFFSET;
}
+2 -1
View File
@@ -114,7 +114,8 @@ void rs_app_info(void) {
printf(" 1 x SPISD @%p\n", (void *)&reg_spisd_mode);
printf(" 1 x I2C0 @%p\n",
(void *)(uintptr_t)RS_SOC_APB4_I2C0_BASE);
printf(" 1 x I2S @%p\n", (void *)&reg_i2s_mode);
printf(" 1 x I2S @%p\n",
(void *)(uintptr_t)RS_SOC_APB4_I2S_BASE);
printf(" 1 x WS2812 @%p\n",
(void *)(uintptr_t)RS_SOC_APB4_WS2812_BASE);
printf(" 1 x XPI @%p\n", (void *)&reg_xpi_cfgidx);
+2
View File
@@ -44,6 +44,8 @@ the root README and subsystem guides.
control, error, interrupt, DMA, HAL, and verification contracts.
- [ip/i2c.md](ip/i2c.md) defines the dual I2C command, timing, error,
recovery, DMA, HAL, and verification contracts.
- [ip/i2s.md](ip/i2s.md) defines the I2S master register ABI, phase-separated
PHY, AXI4-Stream packing, CDC, HAL, and verification contracts.
- [ip/psram.md](ip/psram.md) defines the four-chip ESP-PSRAM64H AXI/APB4
controller, register ABI, timing, isolation, recovery, HAL, and verification
contracts.
+3 -2
View File
@@ -20,10 +20,11 @@ IDs, destinations, and user sidebands are currently zero.
## Configuration and PIO Fallback
I2S register offset `0x20` is `STREAM_CTRL`: bit 0 selects AXI4-Stream as the
I2S register offset `0x00C` is `STREAM_CTRL`: bit 0 selects AXI4-Stream as the
TX FIFO producer and bit 1 selects AXI4-Stream as the RX FIFO consumer. A clear
bit preserves the existing `TXDATA` or `RXDATA` PIO behavior. Software must not
change a stream-select bit while that direction is active.
change a stream-select bit while that direction is active. `TXDATA` is `0x01C`
and `RXDATA` is `0x020`.
DVP register offset `0x0C` is `STREAM_CTRL`: bit 0 selects AXI4-Stream as the
RX FIFO consumer. When clear, reads of `RXDATA` retain the existing pop
+1
View File
@@ -11,6 +11,7 @@ verification documentation for self-owned retroSoC peripheral IP.
- [uart.md](uart.md) defines the APB4 UART controller.
- [i2c.md](i2c.md) defines the dual APB4 I2C controllers.
- [dvp.md](dvp.md) defines the APB4 DVP capture controller.
- [i2s.md](i2s.md) defines the APB4 I2S master transceiver.
- [sdram.md](sdram.md) defines the AXI4 SDRAM data controller and APB4 configuration window.
The corresponding RTL and HAL implementations remain the executable sources
+107
View File
@@ -0,0 +1,107 @@
# APB4 I2S
The I2S block is a stereo master transceiver with an APB4 control plane and
dedicated AXI4-Stream TX/RX data ports. It keeps the existing phase-separated
PHY: one SCLK edge updates control and shifter state, and the opposite edge
samples ADC data or updates DAC data. Four format presets cover the previous
16/24-bit and 48/96 kHz combinations. Software can also program SCLK, LRCK,
and MCLK dividers. Host and audio clocks are independent; configuration uses
`cdc_2phase` and sample words use `cdc_fifo_warm_flush`.
## Integration
| Property | Value |
| --- | --- |
| APB4 base address | `0x10007000` |
| APB4 interrupt group bit | 8 |
| Pads | `mclk_o`, `sclk_o`, `lrck_o`, `dacdat_o`, `adcdat_i` |
| AXI4-Stream data width | 32 bits |
| FIFO depth | 128 words |
| DMA modes | 1 I2S TX, 2 I2S RX |
| ABI version | `0x00010000` |
MCLK is a buffered copy of `clk_aud_i` when `CLK_DIV[23:16]` is zero. SCLK and
LRCK run only while `CTRL.ENABLE` is set. Slave, TDM, PDM, and left/right
justified formats are not implemented.
## Register ABI
All registers are 32 bits and naturally aligned. Unmapped, unaligned, and
direction-invalid accesses complete with `pslverr`. `FORMAT` and `CLK_DIV`
writes are rejected while `CTRL.ENABLE` is set. `TXDATA` requires
`pstrb=4'hF` and is rejected when the TX FIFO is full or stream TX is enabled.
`RXDATA` reads are rejected when the RX FIFO is empty or stream RX is enabled.
RW registers support byte strobes except the 32-bit data windows.
| Offset | Name | Access | Description |
| --- | --- | --- | --- |
| `0x000` | `CTRL` | RW | Enable, TX/RX enable, loopback, and programmable-clock select. |
| `0x004` | `COMMAND` | WO | TX and RX FIFO flush pulses. |
| `0x008` | `STATUS` | RO | FIFO flags, stall, enable/busy, occupancy, and flush busy. |
| `0x00C` | `STREAM_CTRL` | RW | AXI4-Stream TX and RX enables. |
| `0x010` | `FORMAT` | RW | Preset `[1:0]` and programmable bit-width. |
| `0x014` | `CLK_DIV` | RW | `SCLK[7:0]`, `LRCK[15:8]`, `MCLK[23:16]`. |
| `0x018` | `FIFO_TH` | RW | `UPBOUND[7:0]` and `LOWBOUND[15:8]`. |
| `0x01C` | `TXDATA` | WO | PIO TX word when stream TX is disabled. |
| `0x020` | `RXDATA` | RO/pop | PIO RX word when stream RX is disabled. |
| `0x024` | `INTR_STATE` | RW1C | Sticky threshold and underrun/overrun events. |
| `0x028` | `INTR_ENABLE` | RW | Interrupt enable mask. |
| `0x02C` | `INTR_STATUS` | RO | `INTR_STATE & INTR_ENABLE`. |
| `0x030` | `INTR_TEST` | WO | Write-one software interrupt injection. |
| `0x0F8` | `IP_VERSION` | RO | ABI version, currently `0x00010000` (1.0). |
| `0x0FC` | `CAPABILITY` | RO | Master, 16/24-bit, 128-deep FIFO, stream, and programmable clock. |
`CTRL` bit 0 enables the PHY clocks, bits 1 and 2 enable TX and RX, bit 3
selects analog ADC-to-DAC loopback, and bit 4 selects programmable dividers.
When bit 4 is clear, `FORMAT[1:0]` selects the legacy presets:
| Preset | Width | Fs | `SCLK_DIV` | `LRCK_DIV` |
| --- | --- | --- | --- | --- |
| 0 | 16-bit | 48 kHz | 5 | 15 |
| 1 | 16-bit | 96 kHz | 2 | 15 |
| 2 | 24-bit | 48 kHz | 3 | 23 |
| 3 | 24-bit | 96 kHz | 1 | 23 |
The toggle divider keeps the previous period formula:
`SCLK = clk_aud / (2 * (SCLK_DIV + 1))` and
`LRCK = SCLK / (2 * (LRCK_DIV + 1))`. Occupancy is tracked on the system clock
from local push/pop counts plus a `cdc_2phase` snapshot of the audio-side
counter. DMA stall uses the previous hysteresis around `UPBOUND`/`LOWBOUND`.
The four interrupt bits are TX below `LOWBOUND`, RX above `UPBOUND`, TX
underrun, and RX overrun. Hardware events have priority over a simultaneous
software clear. `irq_o` is the reduction OR of `INTR_STATE & INTR_ENABLE`.
The RTL ABI constants are maintained directly in
`rtl/ip/serial/i2s_define.svh`. Matching HAL offsets and masks are maintained
directly in `crt/src/hal/i2s.c` and `crt/src/hal/i2s_math.c`, not in
`crt/include/retrosoc/core/soc.h`. Changes to either definition must update
the other definition, this document, and the RTL/software tests in the same
change.
## AXI4-Stream and Sample Packing
16-bit mode packs two samples into one 32-bit word: the older sample occupies
`[15:0]` and the newer sample occupies `[31:16]`. 24-bit mode places one sample
in `[23:0]`. `TKEEP` and `TSTRB` are `4'hF`. I2S RX does not assert `TLAST`.
DMA asserts `TLAST` on the final programmed TX word.
## HAL Sequence
Applications should use `rs_i2s_configure()`, `rs_i2s_enable()`, and either
`rs_i2s_write()`/`rs_i2s_read()` or DMA stream modes. DMA software should
program `rs_i2s_txdata_address()` / `rs_i2s_rxdata_address()` rather than
`soc.h` register macros. Program format and dividers while the controller is
disabled. `rs_i2s_div_from_hz()` derives legal toggle-divider values for an
exact `clk_aud` and sample rate.
## Verification
```
python3 -m pytest -q tests/test_i2s.py
make CONFIG=configs/ci/ihp130.mk formal-i2s
```
The directed test covers version/capability, `pslverr`, enable-protected clock
programming, interrupt test, SCLK activity, and FIFO flush completion. Formal
covers APB handshake completion and the IRQ reduction.
+380 -88
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// You may obtain a copy of the Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
@@ -10,6 +10,7 @@
`include "i2s_define.svh"
/* verilator lint_off DECLFILENAME */
interface i2s_if ();
logic mclk_o;
logic sclk_o;
@@ -27,7 +28,7 @@ interface i2s_if ();
output irq_o
);
endinterface
/* verilator lint_on DECLFILENAME */
module apb4_i2s (
// verilog_format: off -- preserve reviewed column alignment
@@ -37,115 +38,406 @@ module apb4_i2s (
input logic rst_aud_n_i,
output logic dma_tx_stall_o,
output logic dma_rx_stall_o,
apb4_if.slave apb4,
apb4_if.slave apb4,
axi4_stream_if.sink tx_axis,
axi4_stream_if.source rx_axis,
i2s_if.dut i2s
// verilog_format: on
);
logic [31:0] s_cfg_sys;
logic [31:0] s_cfg_aud;
logic s_cfg_aud_v;
logic [31:0] s_cfg_aud_q;
logic [ 1:0] s_cmd_sys;
logic s_cmd_v_sys;
logic s_cmd_rdy_sys;
logic [ 1:0] s_cmd_aud;
logic s_cmd_v_aud;
logic s_cmd_tx_flush;
logic s_cmd_rx_flush;
logic s_irq;
logic s_i2s_mode;
logic [1:0] s_i2s_format;
logic s_i2s_recven;
logic s_i2s_stream_tx_en;
logic s_i2s_stream_rx_en;
// tx fifo
logic s_tx_push_valid, s_tx_pio_push_valid, s_tx_full, s_tx_empty;
logic s_tx_pop_valid, s_tx_pop_ready;
logic [31:0] s_tx_push_data, s_tx_pio_push_data, s_tx_pop_data;
logic [7:0] s_tx_elem_num;
// rx fifo
logic s_rx_push_valid, s_rx_full, s_rx_empty;
logic s_rx_pop_valid, s_rx_pio_pop_valid, s_rx_pop_ready;
logic [31:0] s_rx_push_data, s_rx_pop_data;
logic [7:0] s_rx_elem_num;
logic s_stream_tx_en;
logic s_stream_rx_en;
logic s_tx_pio_push;
logic [31:0] s_tx_pio_data;
logic s_rx_pio_pop;
logic [31:0] s_rx_pop_data;
logic s_tx_src_valid;
logic s_tx_src_ready;
logic [31:0] s_tx_src_data;
logic s_tx_dst_valid;
logic s_tx_dst_ready;
logic [31:0] s_tx_dst_data;
logic s_tx_src_clear;
logic s_tx_src_clear_busy;
logic s_tx_dst_clear_busy;
logic s_tx_dst_busy_sys;
i2s_reg u_i2s_reg (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.apb4 (apb4),
.mode_o (s_i2s_mode),
.format_o (s_i2s_format),
.recven_o (s_i2s_recven),
.stream_tx_enable_o(s_i2s_stream_tx_en),
.stream_rx_enable_o(s_i2s_stream_rx_en),
.tx_push_valid_o (s_tx_pio_push_valid),
.tx_push_data_o (s_tx_pio_push_data),
.tx_full_i (s_tx_full),
.tx_elem_num_i (s_tx_elem_num),
.rx_pop_valid_o (s_rx_pio_pop_valid),
.rx_pop_data_i (s_rx_pop_data),
.rx_empty_i (s_rx_empty),
.rx_elem_num_i (s_rx_elem_num),
.dma_tx_stall_o (dma_tx_stall_o),
.dma_rx_stall_o (dma_rx_stall_o)
);
logic s_rx_src_valid;
logic s_rx_src_ready;
logic [31:0] s_rx_src_data;
logic s_rx_dst_valid;
logic s_rx_dst_ready;
logic [31:0] s_rx_dst_data;
logic s_rx_src_clear;
logic s_rx_src_clear_busy;
logic s_rx_dst_clear_busy;
logic s_rx_src_busy_sys;
assign tx_axis.tready = s_i2s_stream_tx_en && !s_tx_full;
assign s_tx_push_valid = s_i2s_stream_tx_en ?
(tx_axis.tvalid && tx_axis.tready) : s_tx_pio_push_valid;
assign s_tx_push_data = s_i2s_stream_tx_en ? tx_axis.tdata : s_tx_pio_push_data;
logic [7:0] s_tx_push_cnt_d, s_tx_push_cnt_q;
logic [7:0] s_rx_pop_cnt_d, s_rx_pop_cnt_q;
logic [7:0] s_tx_pop_cnt_aud_d, s_tx_pop_cnt_aud_q;
logic [7:0] s_rx_push_cnt_aud_d, s_rx_push_cnt_aud_q;
logic [ 7:0] s_tx_pop_cnt_sys;
logic s_tx_pop_cnt_sys_v;
logic [ 7:0] s_rx_push_cnt_sys;
logic s_rx_push_cnt_sys_v;
logic [ 7:0] s_tx_pop_cnt_sys_q;
logic [ 7:0] s_rx_push_cnt_sys_q;
logic [ 7:0] s_tx_level;
logic [ 7:0] s_rx_level;
logic s_tx_push;
logic s_rx_pop;
logic s_tx_flush_busy;
logic s_rx_flush_busy;
assign rx_axis.tdata = s_rx_pop_data;
logic s_core_tx_valid;
logic s_core_rx_valid;
logic [31:0] s_core_rx_data;
logic s_tx_underrun_aud;
logic s_rx_overrun_aud;
logic s_tx_underrun_tgl_d, s_tx_underrun_tgl_q;
logic s_rx_overrun_tgl_d, s_rx_overrun_tgl_q;
logic s_tx_underrun_re;
logic s_tx_underrun_fe;
logic s_rx_overrun_re;
logic s_rx_overrun_fe;
logic s_tx_underrun_sys;
logic s_rx_overrun_sys;
logic s_tx_flush_sent_q;
assign s_cmd_sys = {s_cmd_rx_flush, s_cmd_tx_flush};
assign s_tx_src_clear = s_cmd_tx_flush && !s_tx_flush_sent_q && !s_tx_src_clear_busy;
assign s_rx_src_clear = s_cmd_v_aud && s_cmd_aud[`APB4_I2S__COMMAND_RX_FLUSH] &&
!s_rx_src_clear_busy;
assign s_tx_flush_busy = s_cmd_tx_flush || s_tx_src_clear_busy || s_tx_dst_busy_sys;
assign s_rx_flush_busy = s_cmd_rx_flush || s_rx_dst_clear_busy || s_rx_src_busy_sys;
assign s_tx_underrun_sys = s_tx_underrun_re || s_tx_underrun_fe;
assign s_rx_overrun_sys = s_rx_overrun_re || s_rx_overrun_fe;
assign s_tx_push = s_tx_src_valid && s_tx_src_ready;
assign s_rx_pop = s_rx_dst_valid && s_rx_dst_ready;
assign s_tx_level = s_tx_flush_busy ? 8'd0 : (s_tx_push_cnt_q - s_tx_pop_cnt_sys_q);
assign s_rx_level = s_rx_flush_busy ? 8'd0 : (s_rx_push_cnt_sys_q - s_rx_pop_cnt_q);
assign s_tx_src_data = s_stream_tx_en ? tx_axis.tdata : s_tx_pio_data;
assign s_tx_src_valid = !s_tx_flush_busy && (s_stream_tx_en ? tx_axis.tvalid : s_tx_pio_push);
assign tx_axis.tready = s_stream_tx_en && s_tx_src_ready && !s_tx_flush_busy;
assign s_tx_dst_ready = s_core_tx_valid;
assign s_rx_src_valid = s_core_rx_valid;
assign s_rx_src_data = s_core_rx_data;
assign s_rx_dst_ready = !s_rx_flush_busy && (s_stream_rx_en ? rx_axis.tready : s_rx_pio_pop);
assign s_rx_pop_data = s_rx_dst_data;
assign rx_axis.tdata = s_rx_dst_data;
assign rx_axis.tkeep = '1;
assign rx_axis.tstrb = '1;
assign rx_axis.tlast = 1'b0;
assign rx_axis.tid = '0;
assign rx_axis.tdest = '0;
assign rx_axis.tuser = '0;
assign rx_axis.tvalid = s_i2s_stream_rx_en && !s_rx_empty;
assign s_rx_pop_valid = s_i2s_stream_rx_en ?
(rx_axis.tvalid && rx_axis.tready) : s_rx_pio_pop_valid;
assign rx_axis.tvalid = s_stream_rx_en && s_rx_dst_valid && !s_rx_flush_busy;
async_fifo #(
.DataWidth (32),
.DepthPower(7)
) u_tx_async_fifo (
.wr_clk_i (clk_i),
.wr_rst_n_i(rst_n_i),
.wr_en_i (s_tx_push_valid),
.wr_data_i (s_tx_push_data),
.wr_full_o (s_tx_full),
.rd_clk_i (clk_aud_i),
.rd_rst_n_i(rst_aud_n_i),
.rd_en_i (s_tx_pop_valid),
.rd_data_o (s_tx_pop_data),
.rd_empty_o(s_tx_empty),
.elem_num_o(s_tx_elem_num)
i2s_reg u_i2s_reg (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.apb4 (apb4),
.tx_full_i (!s_tx_src_ready),
.tx_empty_i (s_tx_level == 8'd0),
.tx_level_i (s_tx_level),
.rx_full_i (s_rx_src_busy_sys),
.rx_empty_i (!s_rx_dst_valid),
.rx_level_i (s_rx_level),
.tx_flush_busy_i (s_tx_flush_busy),
.rx_flush_busy_i (s_rx_flush_busy),
.tx_underrun_i (s_tx_underrun_sys),
.rx_overrun_i (s_rx_overrun_sys),
.tx_push_valid_o (s_tx_pio_push),
.tx_push_data_o (s_tx_pio_data),
.rx_pop_valid_o (s_rx_pio_pop),
.rx_pop_data_i (s_rx_pop_data),
.cfg_o (s_cfg_sys),
.cmd_tx_flush_o (s_cmd_tx_flush),
.cmd_rx_flush_o (s_cmd_rx_flush),
.cmd_valid_o (s_cmd_v_sys),
.cmd_ready_i (s_cmd_rdy_sys),
.stream_tx_enable_o(s_stream_tx_en),
.stream_rx_enable_o(s_stream_rx_en),
.dma_tx_stall_o (dma_tx_stall_o),
.dma_rx_stall_o (dma_rx_stall_o),
.irq_o (s_irq)
);
/* verilator lint_off PINCONNECTEMPTY */
cdc_2phase #(
.DATA_WIDTH(32)
) u_i2s_cfg_cdc (
.src_clk_i (clk_i),
.src_rst_n_i(rst_n_i),
.src_data_i (s_cfg_sys),
.src_valid_i(1'b1),
.src_ready_o(),
.dst_clk_i (clk_aud_i),
.dst_rst_n_i(rst_aud_n_i),
.dst_data_o (s_cfg_aud),
.dst_valid_o(s_cfg_aud_v),
.dst_ready_i(1'b1)
);
always_ff @(posedge clk_aud_i or negedge rst_aud_n_i) begin
if (!rst_aud_n_i) s_cfg_aud_q <= '0;
else if (s_cfg_aud_v) s_cfg_aud_q <= s_cfg_aud;
end
async_fifo #(
.DataWidth (32),
.DepthPower(7)
) u_rx_async_fifo (
.wr_clk_i (clk_aud_i),
.wr_rst_n_i(rst_aud_n_i),
.wr_en_i (s_rx_push_valid & s_i2s_recven),
.wr_data_i (s_rx_push_data),
.wr_full_o (s_rx_full),
.rd_clk_i (clk_i),
.rd_rst_n_i(rst_n_i),
.rd_en_i (s_rx_pop_valid),
.rd_data_o (s_rx_pop_data),
.rd_empty_o(s_rx_empty),
.elem_num_o(s_rx_elem_num)
cdc_2phase #(
.DATA_WIDTH(2)
) u_i2s_cmd_cdc (
.src_clk_i (clk_i),
.src_rst_n_i(rst_n_i),
.src_data_i (s_cmd_sys),
.src_valid_i(s_cmd_v_sys),
.src_ready_o(s_cmd_rdy_sys),
.dst_clk_i (clk_aud_i),
.dst_rst_n_i(rst_aud_n_i),
.dst_data_o (s_cmd_aud),
.dst_valid_o(s_cmd_v_aud),
.dst_ready_i(!s_rx_src_clear_busy)
);
/* verilator lint_on PINCONNECTEMPTY */
cdc_fifo_warm_flush #(
.DATA_WIDTH (32),
.BUFFER_DEPTH(128)
) u_tx_cdc_fifo (
.src_clk_i (clk_i),
.src_rst_n_i (rst_n_i),
.src_clear_i (s_tx_src_clear),
.src_clear_busy_o(s_tx_src_clear_busy),
.src_data_i (s_tx_src_data),
.src_valid_i (s_tx_src_valid),
.src_ready_o (s_tx_src_ready),
.dst_clk_i (clk_aud_i),
.dst_rst_n_i (rst_aud_n_i),
.dst_clear_busy_o(s_tx_dst_clear_busy),
.dst_data_o (s_tx_dst_data),
.dst_valid_o (s_tx_dst_valid),
.dst_ready_i (s_tx_dst_ready)
);
cdc_fifo_warm_flush #(
.DATA_WIDTH (32),
.BUFFER_DEPTH(128)
) u_rx_cdc_fifo (
.src_clk_i (clk_aud_i),
.src_rst_n_i (rst_aud_n_i),
.src_clear_i (s_rx_src_clear),
.src_clear_busy_o(s_rx_src_clear_busy),
.src_data_i (s_rx_src_data),
.src_valid_i (s_rx_src_valid),
.src_ready_o (s_rx_src_ready),
.dst_clk_i (clk_i),
.dst_rst_n_i (rst_n_i),
.dst_clear_busy_o(s_rx_dst_clear_busy),
.dst_data_o (s_rx_dst_data),
.dst_valid_o (s_rx_dst_valid),
.dst_ready_i (s_rx_dst_ready)
);
cdc_sync #(
.STAGE (2),
.DATA_WIDTH(1)
) u_tx_dst_busy_sync (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_tx_dst_clear_busy),
.dat_o (s_tx_dst_busy_sys)
);
cdc_sync #(
.STAGE (2),
.DATA_WIDTH(1)
) u_rx_src_busy_sync (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (!s_rx_src_ready || s_rx_src_clear_busy),
.dat_o (s_rx_src_busy_sys)
);
always_comb begin
s_tx_push_cnt_d = s_tx_push_cnt_q;
if (s_tx_flush_busy) s_tx_push_cnt_d = '0;
else if (s_tx_push) s_tx_push_cnt_d = s_tx_push_cnt_q + 8'd1;
end
always_comb begin
s_rx_pop_cnt_d = s_rx_pop_cnt_q;
if (s_rx_flush_busy) s_rx_pop_cnt_d = '0;
else if (s_rx_pop) s_rx_pop_cnt_d = s_rx_pop_cnt_q + 8'd1;
end
dffr #(
.DATA_WIDTH(8)
) u_tx_push_cnt_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_tx_push_cnt_d),
.dat_o (s_tx_push_cnt_q)
);
dffr #(
.DATA_WIDTH(8)
) u_rx_pop_cnt_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_rx_pop_cnt_d),
.dat_o (s_rx_pop_cnt_q)
);
always_comb begin
s_tx_pop_cnt_aud_d = s_tx_pop_cnt_aud_q;
if (s_cmd_v_aud && s_cmd_aud[`APB4_I2S__COMMAND_TX_FLUSH]) s_tx_pop_cnt_aud_d = '0;
else if (s_tx_dst_valid && s_tx_dst_ready) s_tx_pop_cnt_aud_d = s_tx_pop_cnt_aud_q + 8'd1;
end
always_comb begin
s_rx_push_cnt_aud_d = s_rx_push_cnt_aud_q;
if (s_cmd_v_aud && s_cmd_aud[`APB4_I2S__COMMAND_RX_FLUSH]) s_rx_push_cnt_aud_d = '0;
else if (s_rx_src_valid && s_rx_src_ready) s_rx_push_cnt_aud_d = s_rx_push_cnt_aud_q + 8'd1;
end
dffr #(
.DATA_WIDTH(8)
) u_tx_pop_cnt_aud_dffr (
.clk_i (clk_aud_i),
.rst_n_i(rst_aud_n_i),
.dat_i (s_tx_pop_cnt_aud_d),
.dat_o (s_tx_pop_cnt_aud_q)
);
dffr #(
.DATA_WIDTH(8)
) u_rx_push_cnt_aud_dffr (
.clk_i (clk_aud_i),
.rst_n_i(rst_aud_n_i),
.dat_i (s_rx_push_cnt_aud_d),
.dat_o (s_rx_push_cnt_aud_q)
);
/* verilator lint_off PINCONNECTEMPTY */
cdc_2phase #(
.DATA_WIDTH(8)
) u_tx_pop_cnt_cdc (
.src_clk_i (clk_aud_i),
.src_rst_n_i(rst_aud_n_i),
.src_data_i (s_tx_pop_cnt_aud_q),
.src_valid_i(1'b1),
.src_ready_o(),
.dst_clk_i (clk_i),
.dst_rst_n_i(rst_n_i),
.dst_data_o (s_tx_pop_cnt_sys),
.dst_valid_o(s_tx_pop_cnt_sys_v),
.dst_ready_i(1'b1)
);
cdc_2phase #(
.DATA_WIDTH(8)
) u_rx_push_cnt_cdc (
.src_clk_i (clk_aud_i),
.src_rst_n_i(rst_aud_n_i),
.src_data_i (s_rx_push_cnt_aud_q),
.src_valid_i(1'b1),
.src_ready_o(),
.dst_clk_i (clk_i),
.dst_rst_n_i(rst_n_i),
.dst_data_o (s_rx_push_cnt_sys),
.dst_valid_o(s_rx_push_cnt_sys_v),
.dst_ready_i(1'b1)
);
/* verilator lint_on PINCONNECTEMPTY */
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) s_tx_flush_sent_q <= 1'b0;
else if (!s_cmd_tx_flush) s_tx_flush_sent_q <= 1'b0;
else if (s_tx_src_clear) s_tx_flush_sent_q <= 1'b1;
end
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_tx_pop_cnt_sys_q <= '0;
s_rx_push_cnt_sys_q <= '0;
end else begin
if (s_tx_flush_busy) s_tx_pop_cnt_sys_q <= '0;
else if (s_tx_pop_cnt_sys_v) s_tx_pop_cnt_sys_q <= s_tx_pop_cnt_sys;
if (s_rx_flush_busy) s_rx_push_cnt_sys_q <= '0;
else if (s_rx_push_cnt_sys_v) s_rx_push_cnt_sys_q <= s_rx_push_cnt_sys;
end
end
assign s_tx_underrun_tgl_d = s_tx_underrun_aud ? ~s_tx_underrun_tgl_q : s_tx_underrun_tgl_q;
assign s_rx_overrun_tgl_d = s_rx_overrun_aud ? ~s_rx_overrun_tgl_q : s_rx_overrun_tgl_q;
dffr #(
.DATA_WIDTH(1)
) u_tx_underrun_tgl_dffr (
.clk_i (clk_aud_i),
.rst_n_i(rst_aud_n_i),
.dat_i (s_tx_underrun_tgl_d),
.dat_o (s_tx_underrun_tgl_q)
);
dffr #(
.DATA_WIDTH(1)
) u_rx_overrun_tgl_dffr (
.clk_i (clk_aud_i),
.rst_n_i(rst_aud_n_i),
.dat_i (s_rx_overrun_tgl_d),
.dat_o (s_rx_overrun_tgl_q)
);
/* verilator lint_off PINCONNECTEMPTY */
edge_det #(
.STAGE(2)
) u_tx_underrun_event (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_tx_underrun_tgl_q),
.dat_o (),
.re_o (s_tx_underrun_re),
.fe_o (s_tx_underrun_fe)
);
edge_det #(
.STAGE(2)
) u_rx_overrun_event (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_rx_overrun_tgl_q),
.dat_o (),
.re_o (s_rx_overrun_re),
.fe_o (s_rx_overrun_fe)
);
/* verilator lint_on PINCONNECTEMPTY */
i2s_core u_i2s_core (
.clk_i (clk_aud_i),
.rst_n_i (rst_aud_n_i),
.mode_i (s_i2s_mode),
.format_i (s_i2s_format),
.tx_valid_o(s_tx_pop_valid),
.tx_data_i (s_tx_pop_data),
.tx_empty_i(s_tx_empty),
.rx_valid_o(s_rx_push_valid),
.rx_data_o (s_rx_push_data),
.rx_full_i (s_rx_full),
.i2s (i2s)
.clk_i (clk_aud_i),
.rst_n_i (rst_aud_n_i),
.enable_i (s_cfg_aud_q[`APB4_I2S__CFG_ENABLE]),
.tx_enable_i (s_cfg_aud_q[`APB4_I2S__CFG_TX_ENABLE]),
.rx_enable_i (s_cfg_aud_q[`APB4_I2S__CFG_RX_ENABLE]),
.loopback_i (s_cfg_aud_q[`APB4_I2S__CFG_LOOPBACK]),
.clk_prog_i (s_cfg_aud_q[`APB4_I2S__CFG_CLK_PROG]),
.format_i (s_cfg_aud_q[`APB4_I2S__CFG_FORMAT]),
.bitmode_i (s_cfg_aud_q[`APB4_I2S__CFG_BITMODE]),
.sclk_div_i (s_cfg_aud_q[`APB4_I2S__CFG_SCLK_LSB+:8]),
.lrck_div_i (s_cfg_aud_q[`APB4_I2S__CFG_LRCK_LSB+:8]),
.mclk_div_i (s_cfg_aud_q[`APB4_I2S__CFG_MCLK_LSB+:8]),
.irq_i (s_irq),
.tx_valid_o (s_core_tx_valid),
.tx_data_i (s_tx_dst_data),
.tx_empty_i (!s_tx_dst_valid),
.rx_valid_o (s_core_rx_valid),
.rx_data_o (s_core_rx_data),
.rx_full_i (!s_rx_src_ready),
.tx_underrun_o(s_tx_underrun_aud),
.rx_overrun_o (s_rx_overrun_aud),
.i2s (i2s)
);
endmodule
+41 -50
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// You may obtain a copy of the Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
@@ -11,57 +11,58 @@
`include "i2s_define.svh"
module i2s_clkgen (
// verilog_format: off -- preserve reviewed column alignment
input logic clk_i,
input logic rst_n_i,
input logic enable_i,
input logic clk_prog_i,
input logic [1:0] format_i,
input logic [7:0] sclk_div_i,
input logic [7:0] lrck_div_i,
output logic sclk_pos_o,
output logic sclk_fall_o,
output logic sclk_o,
output logic lrck_o
// verilog_format: on
);
import i2s_pkg::i2s_preset_sclk_div;
import i2s_pkg::i2s_preset_lrck_div;
logic [2:0] s_sclk_div_num;
logic [2:0] s_sclk_div_cnt_d, s_sclk_div_cnt_q;
logic [7:0] s_sclk_div_num;
logic [7:0] s_sclk_div_cnt_d, s_sclk_div_cnt_q;
logic s_sclk_tc;
logic s_sclk_d, s_sclk_q;
logic [4:0] s_lrck_div_num;
logic [4:0] s_lrck_div_cnt_d, s_lrck_div_cnt_q;
logic [7:0] s_lrck_div_num;
logic [7:0] s_lrck_div_cnt_d, s_lrck_div_cnt_q;
logic s_lrck_d, s_lrck_q;
logic s_lrck_tc;
logic s_sclk_pos, s_sclk_fall;
assign s_sclk_tc = s_sclk_div_cnt_q == s_sclk_div_num;
assign s_sclk_pos = (~s_sclk_q) && s_sclk_tc;
assign s_sclk_fall = s_sclk_q && s_sclk_tc;
assign sclk_pos_o = s_sclk_pos;
assign sclk_fall_o = s_sclk_fall;
assign sclk_o = s_sclk_q;
assign lrck_o = s_lrck_q;
assign s_sclk_div_num = clk_prog_i ? sclk_div_i : i2s_preset_sclk_div(format_i);
assign s_lrck_div_num = clk_prog_i ? lrck_div_i : i2s_preset_lrck_div(format_i);
assign s_sclk_tc = enable_i && (s_sclk_div_cnt_q == s_sclk_div_num);
assign s_sclk_pos = enable_i && (~s_sclk_q) && s_sclk_tc;
assign s_sclk_fall = enable_i && s_sclk_q && s_sclk_tc;
assign sclk_pos_o = s_sclk_pos;
assign sclk_fall_o = s_sclk_fall;
assign sclk_o = s_sclk_q;
assign lrck_o = s_lrck_q;
always_comb begin
s_sclk_div_num = 3'd5;
unique case (format_i)
`I2S_16b_48K: s_sclk_div_num = 3'd5;
`I2S_16b_96K: s_sclk_div_num = 3'd2;
`I2S_24b_48K: s_sclk_div_num = 3'd3;
`I2S_24b_96K: s_sclk_div_num = 3'd1;
endcase
end
always_comb begin
s_sclk_d = s_sclk_q;
if (s_sclk_tc) begin
s_sclk_d = s_sclk_q;
s_sclk_div_cnt_d = s_sclk_div_cnt_q;
if (!enable_i) begin
s_sclk_d = 1'b0;
s_sclk_div_cnt_d = '0;
end else if (s_sclk_tc) begin
s_sclk_div_cnt_d = '0;
s_sclk_d = ~s_sclk_q;
end else begin
s_sclk_div_cnt_d = s_sclk_div_cnt_q + 3'd1;
s_sclk_div_cnt_d = s_sclk_div_cnt_q + 8'd1;
end
end
dffr #(
.DATA_WIDTH(3)
.DATA_WIDTH(8)
) u_sclk_div_cnt_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
@@ -77,34 +78,26 @@ module i2s_clkgen (
.dat_o (s_sclk_q)
);
always_comb begin
s_lrck_div_num = 5'd15;
unique case (format_i)
`I2S_16b_48K: s_lrck_div_num = 5'd15;
`I2S_16b_96K: s_lrck_div_num = 5'd15;
`I2S_24b_48K: s_lrck_div_num = 5'd23;
`I2S_24b_96K: s_lrck_div_num = 5'd23;
endcase
end
always_comb begin
s_lrck_d = s_lrck_q;
s_lrck_tc = s_lrck_div_cnt_q == s_lrck_div_num;
if (s_lrck_tc) begin
s_lrck_d = s_lrck_q;
s_lrck_tc = s_lrck_div_cnt_q == s_lrck_div_num;
s_lrck_div_cnt_d = s_lrck_div_cnt_q;
if (!enable_i) begin
s_lrck_d = 1'b0;
s_lrck_div_cnt_d = '0;
end else if (s_lrck_tc) begin
s_lrck_div_cnt_d = '0;
s_lrck_d = ~s_lrck_q;
end else begin
s_lrck_div_cnt_d = s_lrck_div_cnt_q + 5'd1;
s_lrck_div_cnt_d = s_lrck_div_cnt_q + 8'd1;
end
end
dffer #(
.DATA_WIDTH(5)
.DATA_WIDTH(8)
) u_lrck_div_cnt_dffer (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_sclk_fall),
.en_i ((!enable_i) || s_sclk_fall),
.dat_i (s_lrck_div_cnt_d),
.dat_o (s_lrck_div_cnt_q)
);
@@ -113,10 +106,8 @@ module i2s_clkgen (
) u_lrck_dffer (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_sclk_fall),
.en_i ((!enable_i) || s_sclk_fall),
.dat_i (s_lrck_d),
.dat_o (s_lrck_q)
);
endmodule
+112 -48
View File
@@ -1,33 +1,46 @@
// Copyright (c) 2023-2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// You may obtain a copy of the Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`include "i2s_define.svh"
module i2s_core (
// verilog_format: off -- preserve reviewed column alignment
input logic clk_i,
input logic rst_n_i,
input logic mode_i,
input logic [1:0] format_i,
input logic enable_i,
input logic tx_enable_i,
input logic rx_enable_i,
input logic loopback_i,
input logic clk_prog_i,
input logic [ 1:0] format_i,
input logic bitmode_i,
input logic [ 7:0] sclk_div_i,
input logic [ 7:0] lrck_div_i,
input logic [ 7:0] mclk_div_i,
input logic irq_i,
output logic tx_valid_o,
input logic [31:0] tx_data_i,
input logic tx_empty_i,
output logic rx_valid_o,
output logic [31:0] rx_data_o,
input logic rx_full_i,
output logic tx_underrun_o,
output logic rx_overrun_o,
i2s_if.dut i2s
// verilog_format: on
);
import i2s_pkg::i2s_preset_bitmode;
// i2s clk
logic s_sclk_pos, s_sclk_fall;
logic s_sclk, s_lrck;
// common
logic s_bitmode;
logic [23:0] s_loopback_data;
logic [23:0] s_recv_data, s_send_data;
logic [23:0] s_recv_data_d, s_recv_data_q;
@@ -36,18 +49,58 @@ module i2s_core (
logic s_recv_done_re, s_send_done_re;
logic s_recv_cnt_d, s_recv_cnt_q;
logic s_send_cnt_d, s_send_cnt_q;
logic [7:0] s_mclk_cnt_d, s_mclk_cnt_q;
logic s_mclk_d, s_mclk_q;
logic s_mclk_sel;
logic s_fifo_mode;
assign s_bitmode = clk_prog_i ? bitmode_i : i2s_preset_bitmode(format_i);
assign s_fifo_mode = enable_i && !loopback_i;
assign i2s.sclk_o = s_sclk;
assign i2s.lrck_o = s_lrck;
assign i2s.irq_o = irq_i;
// i2s
// NOTE: for jitter control
tc_clk_mux2 u_mclk_clk_mux (
.clk0_i (clk_i),
.clk1_i (s_mclk_q),
.clk_sel_i(|mclk_div_i),
.clk_o (s_mclk_sel)
);
tc_clk_buf u_mclk_clk_buf (
.clk_i(clk_i),
.clk_i(s_mclk_sel),
.clk_o(i2s.mclk_o)
);
assign i2s.sclk_o = s_sclk;
assign i2s.lrck_o = s_lrck;
assign i2s.irq_o = '0;
always_comb begin
s_mclk_d = s_mclk_q;
s_mclk_cnt_d = s_mclk_cnt_q;
if (mclk_div_i == 8'd0) begin
s_mclk_d = 1'b0;
s_mclk_cnt_d = '0;
end else if (s_mclk_cnt_q == mclk_div_i) begin
s_mclk_cnt_d = '0;
s_mclk_d = ~s_mclk_q;
end else begin
s_mclk_cnt_d = s_mclk_cnt_q + 8'd1;
end
end
dffr #(
.DATA_WIDTH(8)
) u_mclk_cnt_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_mclk_cnt_d),
.dat_o (s_mclk_cnt_q)
);
dffr #(
.DATA_WIDTH(1)
) u_mclk_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_mclk_d),
.dat_o (s_mclk_q)
);
edge_det_sync_re u_recv_done_edge_det_sync_re (
.clk_i (clk_i),
@@ -55,7 +108,6 @@ module i2s_core (
.dat_i (s_recv_done),
.re_o (s_recv_done_re)
);
edge_det_sync_re u_send_done_edge_det_sync_re (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
@@ -64,30 +116,33 @@ module i2s_core (
);
always_comb begin
rx_valid_o = '0;
rx_valid_o = 1'b0;
rx_data_o = '0;
rx_overrun_o = 1'b0;
s_loopback_data = '0;
s_recv_cnt_d = s_recv_cnt_q;
s_recv_data_d = s_recv_data_q;
if (!mode_i) s_loopback_data = s_recv_data;
else begin
if (s_recv_done_re) begin
if (format_i[1]) begin
s_recv_cnt_d = '0;
if (~rx_full_i) begin
rx_valid_o = 1'b1;
rx_data_o = {8'd0, s_recv_data};
end
if (loopback_i) begin
s_loopback_data = s_recv_data;
end else if (s_fifo_mode && rx_enable_i && s_recv_done_re) begin
if (s_bitmode) begin
s_recv_cnt_d = 1'b0;
if (~rx_full_i) begin
rx_valid_o = 1'b1;
rx_data_o = {8'd0, s_recv_data};
end else begin
if (~rx_full_i && s_recv_cnt_q == 1'b1) begin
s_recv_cnt_d = '0;
rx_valid_o = 1'b1;
rx_data_o = {s_recv_data[15:0], s_recv_data_q[15:0]};
end else begin
s_recv_cnt_d = s_recv_cnt_q + 1'b1;
s_recv_data_d[15:0] = s_recv_data[15:0];
end
rx_overrun_o = 1'b1;
end
end else if (~rx_full_i && (s_recv_cnt_q == 1'b1)) begin
s_recv_cnt_d = 1'b0;
rx_valid_o = 1'b1;
rx_data_o = {s_recv_data[15:0], s_recv_data_q[15:0]};
end else if (s_recv_cnt_q == 1'b1) begin
s_recv_cnt_d = 1'b0;
rx_overrun_o = 1'b1;
end else begin
s_recv_cnt_d = s_recv_cnt_q + 1'b1;
s_recv_data_d[15:0] = s_recv_data[15:0];
end
end
end
@@ -108,32 +163,41 @@ module i2s_core (
.dat_o (s_recv_data_q)
);
always_comb begin
tx_valid_o = '0;
tx_valid_o = 1'b0;
tx_underrun_o = 1'b0;
s_send_data = '0;
s_send_cnt_d = s_send_cnt_q;
s_send_data_d = s_send_data_q;
if (!mode_i) s_send_data = s_loopback_data;
else begin
if (format_i[1]) begin
if (loopback_i) begin
s_send_data = s_loopback_data;
end else if (s_fifo_mode && tx_enable_i) begin
if (s_bitmode) begin
s_send_data = s_send_data_q[23:0];
s_send_cnt_d = '0;
if (s_send_done_re && (~tx_empty_i)) begin // NOTE:
tx_valid_o = 1'b1;
s_send_cnt_d = s_send_cnt_q + 1'b1;
s_send_data_d = tx_data_i;
s_send_cnt_d = 1'b0;
if (s_send_done_re) begin
if (~tx_empty_i) begin
tx_valid_o = 1'b1;
s_send_data_d = tx_data_i;
end else begin
tx_underrun_o = 1'b1;
s_send_data_d = '0;
end
end
end else begin
s_send_data[15:0] = s_send_data_q[15:0];
if (s_send_done_re) begin
if (s_send_cnt_q == 1'b1) begin
s_send_cnt_d = '0;
s_send_cnt_d = 1'b0;
s_send_data_d = {16'd0, s_send_data_q[31:16]};
end else if (~tx_empty_i) begin // NOTE:
end else if (~tx_empty_i) begin
tx_valid_o = 1'b1;
s_send_cnt_d = s_send_cnt_q + 1'b1;
s_send_data_d = tx_data_i;
end else begin
tx_underrun_o = 1'b1;
s_send_cnt_d = 1'b0;
s_send_data_d = '0;
end
end
end
@@ -156,40 +220,40 @@ module i2s_core (
.dat_o (s_send_data_q)
);
i2s_clkgen u_i2s_clkgen (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.enable_i (enable_i),
.clk_prog_i (clk_prog_i),
.format_i (format_i),
.sclk_div_i (sclk_div_i),
.lrck_div_i (lrck_div_i),
.sclk_pos_o (s_sclk_pos),
.sclk_fall_o(s_sclk_fall),
.sclk_o (s_sclk),
.lrck_o (s_lrck)
);
i2s_recv u_i2s_recv (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.sclk_pos_i(s_sclk_pos),
.lrck_i (s_lrck),
.bitmode_i (format_i[1]),
.bitmode_i (s_bitmode),
.adcdat_i (i2s.adcdat_i),
.data_o (s_recv_data),
.done_o (s_recv_done)
);
i2s_send u_i2s_send (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.sclk_pos_i (s_sclk_pos),
.sclk_fall_i(s_sclk_fall),
.lrck_i (s_lrck),
.bitmode_i (format_i[1]),
.bitmode_i (s_bitmode),
.data_i (s_send_data),
.dacdat_o (i2s.dacdat_o),
.done_o (s_send_done)
);
endmodule
+79 -18
View File
@@ -1,31 +1,92 @@
// Copyright (c) 2023-2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// You may obtain a copy of the Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`ifndef APB4_I2S_DEF_SV
`define APB4_I2S_DEF_SV
`ifndef APB4_I2S_DEFINE_SVH
`define APB4_I2S_DEFINE_SVH
// verilog_format: off -- preserve reviewed column alignment
`define APB4_I2S_MODE 8'h00
`define APB4_I2S_FORMAT 8'h04
`define APB4_I2S_UPBOUND 8'h08
`define APB4_I2S_LOWBOUND 8'h0C
`define APB4_I2S_RECVEN 8'h10
`define APB4_I2S_TXDATA 8'h14
`define APB4_I2S_RXDATA 8'h18
`define APB4_I2S_STATUS 8'h1C
`define APB4_I2S_STREAM_CTRL 8'h20
// verilog_format: off -- preserve reviewed register and field alignment
`define APB4_I2S_CTRL 12'h000
`define APB4_I2S_COMMAND 12'h004
`define APB4_I2S_STATUS 12'h008
`define APB4_I2S_STREAM_CTRL 12'h00C
`define APB4_I2S_FORMAT 12'h010
`define APB4_I2S_CLK_DIV 12'h014
`define APB4_I2S_FIFO_TH 12'h018
`define APB4_I2S_TXDATA 12'h01C
`define APB4_I2S_RXDATA 12'h020
`define APB4_I2S_INTR_STATE 12'h024
`define APB4_I2S_INTR_ENABLE 12'h028
`define APB4_I2S_INTR_STATUS 12'h02C
`define APB4_I2S_INTR_TEST 12'h030
`define APB4_I2S_IP_VERSION 12'h0F8
`define APB4_I2S_CAPABILITY 12'h0FC
`define APB4_I2S__CTRL_ENABLE 0
`define APB4_I2S__CTRL_TX_ENABLE 1
`define APB4_I2S__CTRL_RX_ENABLE 2
`define APB4_I2S__CTRL_LOOPBACK 3
`define APB4_I2S__CTRL_CLK_PROG 4
`define APB4_I2S__COMMAND_TX_FLUSH 0
`define APB4_I2S__COMMAND_RX_FLUSH 1
`define APB4_I2S__STATUS_TX_FULL 0
`define APB4_I2S__STATUS_TX_EMPTY 1
`define APB4_I2S__STATUS_RX_FULL 2
`define APB4_I2S__STATUS_RX_EMPTY 3
`define APB4_I2S__STATUS_TX_STALL 4
`define APB4_I2S__STATUS_RX_STALL 5
`define APB4_I2S__STATUS_ENABLE 6
`define APB4_I2S__STATUS_BUSY 7
`define APB4_I2S__STATUS_TX_LEVEL_LSB 8
`define APB4_I2S__STATUS_RX_LEVEL_LSB 16
`define APB4_I2S__STATUS_TX_FLUSH_BUSY 24
`define APB4_I2S__STATUS_RX_FLUSH_BUSY 25
`define APB4_I2S__STREAM_TX 0
`define APB4_I2S__STREAM_RX 1
`define APB4_I2S__FORMAT_PRESET 1:0
`define APB4_I2S__FORMAT_BITMODE 2
`define APB4_I2S__CLK_SCLK_LSB 0
`define APB4_I2S__CLK_LRCK_LSB 8
`define APB4_I2S__CLK_MCLK_LSB 16
`define APB4_I2S__FIFO_UPBOUND_LSB 0
`define APB4_I2S__FIFO_LOWBOUND_LSB 8
`define APB4_I2S__INTR_TX_LOW 0
`define APB4_I2S__INTR_RX_HIGH 1
`define APB4_I2S__INTR_TX_UNDERRUN 2
`define APB4_I2S__INTR_RX_OVERRUN 3
`define APB4_I2S__INTR_ALL 4'hF
`define APB4_I2S__CFG_ENABLE 0
`define APB4_I2S__CFG_TX_ENABLE 1
`define APB4_I2S__CFG_RX_ENABLE 2
`define APB4_I2S__CFG_LOOPBACK 3
`define APB4_I2S__CFG_CLK_PROG 4
`define APB4_I2S__CFG_FORMAT 6:5
`define APB4_I2S__CFG_BITMODE 7
`define APB4_I2S__CFG_SCLK_LSB 8
`define APB4_I2S__CFG_LRCK_LSB 16
`define APB4_I2S__CFG_MCLK_LSB 24
`define I2S_16b_48K 2'd0
`define I2S_16b_96K 2'd1
`define I2S_24b_48K 2'd2
`define I2S_24b_96K 2'd3
`define I2S_IP_VERSION_VALUE 32'h0001_0000
`define I2S_CAPABILITY_VALUE 32'h0030_0707
// verilog_format: on
`define I2S_16b_48K 2'd0
`define I2S_16b_96K 2'd1
`define I2S_24b_48K 2'd2
`define I2S_24b_96K 2'd3
`endif
+36
View File
@@ -0,0 +1,36 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
`include "i2s_define.svh"
package i2s_pkg;
typedef enum logic [1:0] {
I2S_PRESET_16B_48K = 2'd0,
I2S_PRESET_16B_96K = 2'd1,
I2S_PRESET_24B_48K = 2'd2,
I2S_PRESET_24B_96K = 2'd3
} i2s_preset_e;
function automatic logic [7:0] i2s_preset_sclk_div(input logic [1:0] preset_i);
unique case (preset_i)
`I2S_16b_48K: return 8'd5;
`I2S_16b_96K: return 8'd2;
`I2S_24b_48K: return 8'd3;
default: return 8'd1;
endcase
endfunction
function automatic logic [7:0] i2s_preset_lrck_div(input logic [1:0] preset_i);
unique case (preset_i)
`I2S_16b_48K, `I2S_16b_96K: return 8'd15;
default: return 8'd23;
endcase
endfunction
function automatic logic i2s_preset_bitmode(input logic [1:0] preset_i);
unique case (preset_i)
`I2S_16b_48K, `I2S_16b_96K: return 1'b0;
default: return 1'b1;
endcase
endfunction
endpackage
+314 -223
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// You may obtain a copy of the Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
@@ -15,242 +15,333 @@ module i2s_reg (
input logic clk_i,
input logic rst_n_i,
apb4_if.slave apb4,
output logic mode_o,
output logic [ 1:0] format_o,
output logic recven_o,
output logic stream_tx_enable_o,
output logic stream_rx_enable_o,
input logic tx_full_i,
input logic tx_empty_i,
input logic [ 7:0] tx_level_i,
input logic rx_full_i,
input logic rx_empty_i,
input logic [ 7:0] rx_level_i,
input logic tx_flush_busy_i,
input logic rx_flush_busy_i,
input logic tx_underrun_i,
input logic rx_overrun_i,
output logic tx_push_valid_o,
output logic [31:0] tx_push_data_o,
input logic tx_full_i,
input logic [ 7:0] tx_elem_num_i,
output logic rx_pop_valid_o,
input logic [31:0] rx_pop_data_i,
input logic rx_empty_i,
input logic [ 7:0] rx_elem_num_i,
output logic [31:0] cfg_o,
output logic cmd_tx_flush_o,
output logic cmd_rx_flush_o,
output logic cmd_valid_o,
input logic cmd_ready_i,
output logic stream_tx_enable_o,
output logic stream_rx_enable_o,
output logic dma_tx_stall_o,
output logic dma_rx_stall_o
output logic dma_rx_stall_o,
output logic irq_o
// verilog_format: on
);
localparam logic [31:0] CtrlMask = 32'h0000_001F;
localparam logic [31:0] StreamMask = 32'h0000_0003;
localparam logic [31:0] FormatMask = 32'h0000_0007;
localparam logic [31:0] ClkDivMask = 32'h00FF_FFFF;
localparam logic [31:0] FifoThMask = 32'h0000_FFFF;
localparam logic [31:0] FifoThReset = 32'h0000_00FF;
// apb4
logic s_apb4_wr_hdshk, s_apb4_rd_hdshk;
logic s_apb4_ready_d, s_apb4_ready_q;
logic s_apb4_rdata_en;
logic [31:0] s_apb4_rdata_d, s_apb4_rdata_q;
// register
logic s_i2s_mode_en;
logic s_i2s_mode_d, s_i2s_mode_q;
logic s_i2s_format_en;
logic [1:0] s_i2s_format_d, s_i2s_format_q;
logic s_i2s_upbound_en;
logic [7:0] s_i2s_upbound_d, s_i2s_upbound_q;
logic s_i2s_lowbound_en;
logic [7:0] s_i2s_lowbound_d, s_i2s_lowbound_q;
logic s_i2s_recven_en;
logic s_i2s_recven_d, s_i2s_recven_q;
logic [1:0] s_i2s_stat_d, s_i2s_stat_q;
logic s_i2s_stream_en;
logic [1:0] s_i2s_stream_d, s_i2s_stream_q;
// common
logic s_tx_fifo_stall_d, s_tx_fifo_stall_q;
logic s_rx_fifo_stall_d, s_rx_fifo_stall_q;
logic s_req;
logic s_write;
logic s_accept;
logic [11:0] s_offset;
logic s_aligned;
logic s_access_err;
logic s_ready_q;
logic s_resp_err_d, s_resp_err_q;
logic [31:0] s_rdata_d, s_rdata_q;
logic [31:0] s_ctrl_d, s_ctrl_q;
logic [31:0] s_stream_d, s_stream_q;
logic [31:0] s_format_d, s_format_q;
logic [31:0] s_clk_d, s_clk_q;
logic [31:0] s_fifo_th_d, s_fifo_th_q;
logic [3:0] s_intr_stat_q;
logic [3:0] s_intr_en_q;
logic [1:0] s_cmd_q;
logic s_tx_stall_d, s_tx_stall_q;
logic s_rx_stall_d, s_rx_stall_q;
logic [ 7:0] s_upbound;
logic [ 7:0] s_lowbound;
logic [31:0] s_ctrl_merged;
logic s_tx_low_evt;
logic s_rx_high_evt;
// apb4
assign s_apb4_wr_hdshk = apb4.psel && apb4.penable && (~s_apb4_ready_q) && apb4.pwrite;
assign s_apb4_rd_hdshk = apb4.psel && apb4.penable && (~s_apb4_ready_q) && (~apb4.pwrite);
assign apb4.pready = s_apb4_ready_q;
assign apb4.pslverr = 1'b0;
assign apb4.prdata = s_apb4_rdata_q;
// reg
assign mode_o = s_i2s_mode_q;
assign format_o = s_i2s_format_q;
assign recven_o = s_i2s_recven_q;
assign stream_tx_enable_o = s_i2s_stream_q[0];
assign stream_rx_enable_o = s_i2s_stream_q[1];
// dma
assign dma_tx_stall_o = s_tx_fifo_stall_q;
assign dma_rx_stall_o = s_rx_fifo_stall_q;
assign s_i2s_mode_en = s_apb4_wr_hdshk && apb4.paddr[7:0] == `APB4_I2S_MODE;
assign s_i2s_mode_d = apb4.pwdata[0];
dffer #(
.DATA_WIDTH(1)
) u_i2s_mode_dffer (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_i2s_mode_en),
.dat_i (s_i2s_mode_d),
.dat_o (s_i2s_mode_q)
);
assign s_i2s_format_en = s_apb4_wr_hdshk && apb4.paddr[7:0] == `APB4_I2S_FORMAT;
assign s_i2s_format_d = apb4.pwdata[1:0];
dffer #(
.DATA_WIDTH(2)
) u_i2s_format_dffer (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_i2s_format_en),
.dat_i (s_i2s_format_d),
.dat_o (s_i2s_format_q)
);
assign s_i2s_upbound_en = s_apb4_wr_hdshk && apb4.paddr[7:0] == `APB4_I2S_UPBOUND;
assign s_i2s_upbound_d = apb4.pwdata[7:0];
dfferh #(
.DATA_WIDTH(8)
) u_i2s_upbound_dfferh (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_i2s_upbound_en),
.dat_i (s_i2s_upbound_d),
.dat_o (s_i2s_upbound_q)
);
assign s_i2s_lowbound_en = s_apb4_wr_hdshk && apb4.paddr[7:0] == `APB4_I2S_LOWBOUND;
assign s_i2s_lowbound_d = apb4.pwdata[7:0];
dffer #(
.DATA_WIDTH(8)
) u_i2s_lowbound_dffer (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_i2s_lowbound_en),
.dat_i (s_i2s_lowbound_d),
.dat_o (s_i2s_lowbound_q)
);
assign s_i2s_recven_en = s_apb4_wr_hdshk && apb4.paddr[7:0] == `APB4_I2S_RECVEN;
assign s_i2s_recven_d = apb4.pwdata[0];
dffer #(
.DATA_WIDTH(1)
) u_i2s_recven_dffer (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_i2s_recven_en),
.dat_i (s_i2s_recven_d),
.dat_o (s_i2s_recven_q)
);
assign s_i2s_stream_en = s_apb4_wr_hdshk && apb4.paddr[7:0] == `APB4_I2S_STREAM_CTRL;
assign s_i2s_stream_d = apb4.pwdata[1:0];
dffer #(
.DATA_WIDTH(2)
) u_i2s_stream_dffer (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_i2s_stream_en),
.dat_i (s_i2s_stream_d),
.dat_o (s_i2s_stream_q)
);
always_comb begin
s_tx_fifo_stall_d = s_tx_fifo_stall_q;
if (~s_tx_fifo_stall_q && tx_elem_num_i > s_i2s_upbound_q) begin
s_tx_fifo_stall_d = 1'b1;
end else if (s_tx_fifo_stall_q && tx_elem_num_i < s_i2s_lowbound_q) begin
s_tx_fifo_stall_d = 1'b0;
end
end
dffr #(
.DATA_WIDTH(1)
) u_tx_fifo_stall_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_tx_fifo_stall_d),
.dat_o (s_tx_fifo_stall_q)
);
always_comb begin
s_rx_fifo_stall_d = s_rx_fifo_stall_q;
if (~s_rx_fifo_stall_q && rx_elem_num_i < s_i2s_lowbound_q) begin
s_rx_fifo_stall_d = 1'b1;
end else if (s_rx_fifo_stall_q && rx_elem_num_i > s_i2s_upbound_q) begin
s_rx_fifo_stall_d = 1'b0;
end
end
dffr #(
.DATA_WIDTH(1)
) u_rx_fifo_stall_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_rx_fifo_stall_d),
.dat_o (s_rx_fifo_stall_q)
);
// TODO: need to handle when tx fifo is full(DMA is fine)
always_comb begin
tx_push_valid_o = 1'b0;
tx_push_data_o = '0;
if (s_apb4_wr_hdshk && apb4.paddr[7:0] == `APB4_I2S_TXDATA) begin
tx_push_valid_o = 1'b1;
if (apb4.pstrb[0]) tx_push_data_o[7:0] = apb4.pwdata[7:0];
if (apb4.pstrb[1]) tx_push_data_o[15:8] = apb4.pwdata[15:8];
if (apb4.pstrb[2]) tx_push_data_o[23:16] = apb4.pwdata[23:16];
if (apb4.pstrb[3]) tx_push_data_o[31:24] = apb4.pwdata[31:24];
end
end
always_comb begin
s_i2s_stat_d[0] = tx_full_i;
s_i2s_stat_d[1] = rx_empty_i;
end
dffr #(
.DATA_WIDTH(2)
) u_i2s_status_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_i2s_stat_d),
.dat_o (s_i2s_stat_q)
);
assign s_apb4_ready_d = apb4.psel && apb4.penable && (~s_apb4_ready_q);
dffr #(
.DATA_WIDTH(1)
) u_apb4_ready_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_apb4_ready_d),
.dat_o (s_apb4_ready_q)
);
assign s_apb4_rdata_en = s_apb4_rd_hdshk;
always_comb begin
rx_pop_valid_o = 1'b0;
s_apb4_rdata_d = s_apb4_rdata_q;
unique case (apb4.paddr[7:0])
`APB4_I2S_MODE: s_apb4_rdata_d = {31'd0, s_i2s_mode_q};
`APB4_I2S_FORMAT: s_apb4_rdata_d = {30'd0, s_i2s_format_q};
`APB4_I2S_RXDATA: begin
if (s_apb4_rd_hdshk) begin
rx_pop_valid_o = 1'b1;
if (!rx_empty_i) s_apb4_rdata_d = rx_pop_data_i;
else s_apb4_rdata_d = '0;
end
function automatic logic [31:0] merge_wstrb(
input logic [31:0] current_i, input logic [31:0] value_i, input logic [3:0] strobe_i);
logic [31:0] merged;
begin
merged = current_i;
for (int index = 0; index < 4; index++) begin
if (strobe_i[index]) merged[index*8+:8] = value_i[index*8+:8];
end
`APB4_I2S_STATUS: s_apb4_rdata_d = {30'd0, s_i2s_stat_q};
`APB4_I2S_STREAM_CTRL: s_apb4_rdata_d = {30'd0, s_i2s_stream_q};
default: s_apb4_rdata_d = s_apb4_rdata_q;
endcase
return merged;
end
endfunction
assign s_req = apb4.psel && apb4.penable && !s_ready_q;
assign s_write = |apb4.pstrb;
assign s_accept = s_req;
assign s_offset = apb4.paddr[11:0];
assign s_aligned = apb4.paddr[1:0] == 2'b00;
assign apb4.pready = s_ready_q;
assign apb4.prdata = s_rdata_q;
assign apb4.pslverr = s_resp_err_q;
assign stream_tx_enable_o = s_stream_q[`APB4_I2S__STREAM_TX];
assign stream_rx_enable_o = s_stream_q[`APB4_I2S__STREAM_RX];
assign dma_tx_stall_o = s_tx_stall_q;
assign dma_rx_stall_o = s_rx_stall_q;
assign irq_o = |(s_intr_stat_q & s_intr_en_q);
assign s_upbound = s_fifo_th_q[`APB4_I2S__FIFO_UPBOUND_LSB+:8];
assign s_lowbound = s_fifo_th_q[`APB4_I2S__FIFO_LOWBOUND_LSB+:8];
assign s_ctrl_merged = merge_wstrb(s_ctrl_q, apb4.pwdata, apb4.pstrb) & CtrlMask;
assign tx_push_valid_o = s_accept && s_write && !s_access_err && (s_offset == `APB4_I2S_TXDATA);
assign tx_push_data_o = apb4.pwdata;
assign rx_pop_valid_o = s_accept && !s_write && !s_access_err && (s_offset == `APB4_I2S_RXDATA);
assign cmd_valid_o = |s_cmd_q;
assign cmd_tx_flush_o = s_cmd_q[`APB4_I2S__COMMAND_TX_FLUSH];
assign cmd_rx_flush_o = s_cmd_q[`APB4_I2S__COMMAND_RX_FLUSH];
assign s_tx_low_evt = s_ctrl_q[`APB4_I2S__CTRL_ENABLE] &&
s_ctrl_q[`APB4_I2S__CTRL_TX_ENABLE] && (tx_level_i < s_lowbound);
assign s_rx_high_evt = s_ctrl_q[`APB4_I2S__CTRL_ENABLE] &&
s_ctrl_q[`APB4_I2S__CTRL_RX_ENABLE] && (rx_level_i > s_upbound);
always_comb begin
cfg_o = '0;
cfg_o[`APB4_I2S__CFG_ENABLE] = s_ctrl_q[`APB4_I2S__CTRL_ENABLE];
cfg_o[`APB4_I2S__CFG_TX_ENABLE] = s_ctrl_q[`APB4_I2S__CTRL_TX_ENABLE];
cfg_o[`APB4_I2S__CFG_RX_ENABLE] = s_ctrl_q[`APB4_I2S__CTRL_RX_ENABLE];
cfg_o[`APB4_I2S__CFG_LOOPBACK] = s_ctrl_q[`APB4_I2S__CTRL_LOOPBACK];
cfg_o[`APB4_I2S__CFG_CLK_PROG] = s_ctrl_q[`APB4_I2S__CTRL_CLK_PROG];
cfg_o[`APB4_I2S__CFG_FORMAT] = s_format_q[`APB4_I2S__FORMAT_PRESET];
cfg_o[`APB4_I2S__CFG_BITMODE] = s_format_q[`APB4_I2S__FORMAT_BITMODE];
cfg_o[`APB4_I2S__CFG_SCLK_LSB+:8] = s_clk_q[`APB4_I2S__CLK_SCLK_LSB+:8];
cfg_o[`APB4_I2S__CFG_LRCK_LSB+:8] = s_clk_q[`APB4_I2S__CLK_LRCK_LSB+:8];
cfg_o[`APB4_I2S__CFG_MCLK_LSB+:8] = s_clk_q[`APB4_I2S__CLK_MCLK_LSB+:8];
end
always_comb begin
s_access_err = !s_aligned;
s_rdata_d = 32'd0;
if (s_aligned) begin
unique case (s_offset)
`APB4_I2S_CTRL: s_rdata_d = s_ctrl_q;
`APB4_I2S_COMMAND: s_access_err = !s_write;
`APB4_I2S_STATUS: begin
s_rdata_d = {
6'd0,
rx_flush_busy_i,
tx_flush_busy_i,
rx_level_i,
tx_level_i,
tx_flush_busy_i || rx_flush_busy_i || s_ctrl_q[`APB4_I2S__CTRL_ENABLE],
s_ctrl_q[`APB4_I2S__CTRL_ENABLE],
s_rx_stall_q,
s_tx_stall_q,
rx_empty_i,
rx_full_i,
tx_empty_i,
tx_full_i
};
s_access_err = s_write;
end
`APB4_I2S_STREAM_CTRL: s_rdata_d = s_stream_q;
`APB4_I2S_FORMAT: s_rdata_d = s_format_q;
`APB4_I2S_CLK_DIV: s_rdata_d = s_clk_q;
`APB4_I2S_FIFO_TH: s_rdata_d = s_fifo_th_q;
`APB4_I2S_TXDATA: s_access_err = !s_write;
`APB4_I2S_RXDATA: begin
s_rdata_d = rx_pop_data_i;
s_access_err = s_write;
end
`APB4_I2S_INTR_STATE: s_rdata_d = {28'd0, s_intr_stat_q};
`APB4_I2S_INTR_ENABLE: s_rdata_d = {28'd0, s_intr_en_q};
`APB4_I2S_INTR_STATUS: begin
s_rdata_d = {28'd0, s_intr_stat_q & s_intr_en_q};
s_access_err = s_write;
end
`APB4_I2S_INTR_TEST: s_access_err = !s_write;
`APB4_I2S_IP_VERSION: begin
s_rdata_d = `I2S_IP_VERSION_VALUE;
s_access_err = s_write;
end
`APB4_I2S_CAPABILITY: begin
s_rdata_d = `I2S_CAPABILITY_VALUE;
s_access_err = s_write;
end
default: s_access_err = 1'b1;
endcase
end
if (s_accept && s_write && (s_offset == `APB4_I2S_FORMAT) &&
s_ctrl_q[`APB4_I2S__CTRL_ENABLE]) begin
s_access_err = 1'b1;
end
if (s_accept && s_write && (s_offset == `APB4_I2S_CLK_DIV) &&
s_ctrl_q[`APB4_I2S__CTRL_ENABLE]) begin
s_access_err = 1'b1;
end
if (s_accept && s_write && (s_offset == `APB4_I2S_CTRL) && s_ctrl_q[`APB4_I2S__CTRL_ENABLE] &&
s_ctrl_merged[`APB4_I2S__CTRL_ENABLE] &&
(s_ctrl_merged[`APB4_I2S__CTRL_CLK_PROG] != s_ctrl_q[`APB4_I2S__CTRL_CLK_PROG])) begin
s_access_err = 1'b1;
end
if (s_accept && s_write && (s_offset == `APB4_I2S_TXDATA) &&
((apb4.pstrb != 4'hF) || stream_tx_enable_o || tx_full_i)) begin
s_access_err = 1'b1;
end
if (s_accept && !s_write && (s_offset == `APB4_I2S_RXDATA) &&
(stream_rx_enable_o || rx_empty_i)) begin
s_access_err = 1'b1;
end
if (s_accept && s_write && (s_offset == `APB4_I2S_COMMAND)) begin
if (!apb4.pstrb[0]) s_access_err = 1'b1;
if (apb4.pwdata[`APB4_I2S__COMMAND_TX_FLUSH] && tx_flush_busy_i) s_access_err = 1'b1;
if (apb4.pwdata[`APB4_I2S__COMMAND_RX_FLUSH] && rx_flush_busy_i) s_access_err = 1'b1;
end
s_resp_err_d = s_accept && s_access_err;
end
always_comb begin
s_ctrl_d = s_ctrl_q;
s_stream_d = s_stream_q;
s_format_d = s_format_q;
s_clk_d = s_clk_q;
s_fifo_th_d = s_fifo_th_q;
if (s_accept && s_write && !s_access_err) begin
unique case (s_offset)
`APB4_I2S_CTRL: s_ctrl_d = s_ctrl_merged;
`APB4_I2S_STREAM_CTRL:
s_stream_d = merge_wstrb(s_stream_q, apb4.pwdata, apb4.pstrb) & StreamMask;
`APB4_I2S_FORMAT:
s_format_d = merge_wstrb(s_format_q, apb4.pwdata, apb4.pstrb) & FormatMask;
`APB4_I2S_CLK_DIV: s_clk_d = merge_wstrb(s_clk_q, apb4.pwdata, apb4.pstrb) & ClkDivMask;
`APB4_I2S_FIFO_TH:
s_fifo_th_d = merge_wstrb(s_fifo_th_q, apb4.pwdata, apb4.pstrb) & FifoThMask;
default: ;
endcase
end
end
dffr #(
.DATA_WIDTH(32)
) u_ctrl_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_ctrl_d),
.dat_o (s_ctrl_q)
);
dffr #(
.DATA_WIDTH(32)
) u_stream_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_stream_d),
.dat_o (s_stream_q)
);
dffr #(
.DATA_WIDTH(32)
) u_format_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_format_d),
.dat_o (s_format_q)
);
dffr #(
.DATA_WIDTH(32)
) u_clk_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_clk_d),
.dat_o (s_clk_q)
);
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) s_fifo_th_q <= FifoThReset;
else s_fifo_th_q <= s_fifo_th_d;
end
always_comb begin
s_tx_stall_d = s_tx_stall_q;
if (!s_tx_stall_q && (tx_level_i > s_upbound)) s_tx_stall_d = 1'b1;
else if (s_tx_stall_q && (tx_level_i < s_lowbound)) s_tx_stall_d = 1'b0;
end
always_comb begin
s_rx_stall_d = s_rx_stall_q;
if (!s_rx_stall_q && (rx_level_i < s_lowbound)) s_rx_stall_d = 1'b1;
else if (s_rx_stall_q && (rx_level_i > s_upbound)) s_rx_stall_d = 1'b0;
end
dffr #(
.DATA_WIDTH(1)
) u_tx_stall_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_tx_stall_d),
.dat_o (s_tx_stall_q)
);
dffr #(
.DATA_WIDTH(1)
) u_rx_stall_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_rx_stall_d),
.dat_o (s_rx_stall_q)
);
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_cmd_q <= '0;
s_intr_stat_q <= '0;
s_intr_en_q <= '0;
end else begin
if (cmd_valid_o && cmd_ready_i) s_cmd_q <= '0;
if (s_accept && s_write && !s_access_err && (s_offset == `APB4_I2S_COMMAND)) begin
s_cmd_q <= s_cmd_q | apb4.pwdata[1:0];
end
if (s_accept && s_write && !s_access_err && (s_offset == `APB4_I2S_INTR_STATE)) begin
s_intr_stat_q <= s_intr_stat_q & ~apb4.pwdata[3:0];
end
if (s_tx_low_evt) s_intr_stat_q[`APB4_I2S__INTR_TX_LOW] <= 1'b1;
if (s_rx_high_evt) s_intr_stat_q[`APB4_I2S__INTR_RX_HIGH] <= 1'b1;
if (tx_underrun_i) s_intr_stat_q[`APB4_I2S__INTR_TX_UNDERRUN] <= 1'b1;
if (rx_overrun_i) s_intr_stat_q[`APB4_I2S__INTR_RX_OVERRUN] <= 1'b1;
if (s_accept && s_write && !s_access_err && (s_offset == `APB4_I2S_INTR_ENABLE)) begin
s_intr_en_q <= apb4.pwdata[3:0];
end
if (s_accept && s_write && !s_access_err && (s_offset == `APB4_I2S_INTR_TEST)) begin
s_intr_stat_q <= s_intr_stat_q | apb4.pwdata[3:0];
end
end
end
dffr #(
.DATA_WIDTH(1)
) u_ready_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (apb4.psel && apb4.penable && !s_ready_q),
.dat_o (s_ready_q)
);
dffer #(
.DATA_WIDTH(32)
) u_apb4_rdata_dffer (
) u_rdata_dffer (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_apb4_rdata_en),
.dat_i (s_apb4_rdata_d),
.dat_o (s_apb4_rdata_q)
.en_i (s_accept && !s_write),
.dat_i (s_rdata_d),
.dat_o (s_rdata_q)
);
dffer #(
.DATA_WIDTH(1)
) u_resp_err_dffer (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_accept),
.dat_i (s_resp_err_d),
.dat_o (s_resp_err_q)
);
endmodule
+1
View File
@@ -18,6 +18,7 @@
/ip/serial/i2c_core.sv
/ip/serial/i2c_reg.sv
/ip/serial/apb4_i2c.sv
/ip/serial/i2s_pkg.sv
/ip/serial/i2s_reg.sv
/ip/serial/i2s_clkgen.sv
/ip/serial/i2s_recv.sv
@@ -175,6 +175,15 @@ def source_files(target: str) -> list[Path]:
ROOT / "rtl/ip/multimedia/dvp_reg.sv",
SCRIPT_DIR / "dvp_formal.sv",
]
if target == "i2s":
return [
COMMON_RTL / "interface/apb4_if.sv",
COMMON_RTL / "utils/register.sv",
SERIAL / "i2s_define.svh",
SERIAL / "i2s_pkg.sv",
SERIAL / "i2s_reg.sv",
SCRIPT_DIR / "i2s_formal.sv",
]
if target == "psram":
return [
COMMON_RTL / "interface/axi4_if.sv",
@@ -231,6 +240,7 @@ def parse_args() -> argparse.Namespace:
"timer",
"clint",
"dvp",
"i2s",
"psram",
),
required=True,
+81
View File
@@ -0,0 +1,81 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module i2s_formal_design (
input logic clk_i,
output logic rst_n_i,
output logic f_past_valid,
output logic rib_valid,
output logic [31:0] rib_addr,
output logic [31:0] rib_wdata,
output logic [ 3:0] rib_wstrb,
output logic rib_ready,
output logic rib_resp_err,
output logic [ 3:0] intr_state,
output logic [ 3:0] intr_enable,
output logic irq
);
apb4_if apb4 (
.pclk (clk_i),
.presetn(rst_n_i)
);
(* anyseq *) logic f_apb_sel;
(* anyseq *) logic [31:0] f_apb_addr, f_apb_wdata;
(* anyseq *) logic [3:0] f_apb_pstrb;
(* anyseq *) logic f_tx_full, f_tx_empty, f_rx_full, f_rx_empty;
(* anyseq *) logic [7:0] f_tx_level, f_rx_level;
(* anyseq *) logic f_tx_flush_busy, f_rx_flush_busy;
(* anyseq *) logic f_tx_underrun, f_rx_overrun;
assign apb4.psel = f_apb_sel;
assign apb4.penable = f_apb_sel;
assign apb4.pwrite = |f_apb_pstrb;
assign apb4.paddr = f_apb_addr;
assign apb4.pwdata = f_apb_wdata;
assign apb4.pstrb = f_apb_pstrb;
assign apb4.pprot = 3'b000;
assign rib_valid = apb4.psel;
assign rib_addr = apb4.paddr;
assign rib_wdata = apb4.pwdata;
assign rib_wstrb = apb4.pstrb;
assign rib_ready = apb4.pready;
assign rib_resp_err = apb4.pslverr;
assign intr_state = u_dut.s_intr_stat_q;
assign intr_enable = u_dut.s_intr_en_q;
i2s_reg u_dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.apb4 (apb4),
.tx_full_i (f_tx_full),
.tx_empty_i (f_tx_empty),
.tx_level_i (f_tx_level),
.rx_full_i (f_rx_full),
.rx_empty_i (f_rx_empty),
.rx_level_i (f_rx_level),
.tx_flush_busy_i (f_tx_flush_busy),
.rx_flush_busy_i (f_rx_flush_busy),
.tx_underrun_i (f_tx_underrun),
.rx_overrun_i (f_rx_overrun),
.tx_push_valid_o (),
.tx_push_data_o (),
.rx_pop_valid_o (),
.rx_pop_data_i (32'd0),
.cfg_o (),
.cmd_tx_flush_o (),
.cmd_rx_flush_o (),
.cmd_valid_o (),
.cmd_ready_i (1'b1),
.stream_tx_enable_o(),
.stream_rx_enable_o(),
.dma_tx_stall_o (),
.dma_rx_stall_o (),
.irq_o (irq)
);
initial begin
rst_n_i = 1'b0;
f_past_valid = 1'b0;
end
always_ff @(posedge clk_i) begin
rst_n_i <= 1'b1;
f_past_valid <= 1'b1;
end
endmodule
+38
View File
@@ -0,0 +1,38 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module i2s_formal;
(* anyseq *) (* gclk *) reg clk_i;
logic rst_n_i, f_past_valid, rib_valid, rib_ready, rib_resp_err, irq;
logic [31:0] rib_addr, rib_wdata;
logic [3:0] rib_wstrb;
logic [3:0] intr_state, intr_enable;
i2s_formal_design u_design (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.f_past_valid(f_past_valid),
.rib_valid (rib_valid),
.rib_addr (rib_addr),
.rib_wdata (rib_wdata),
.rib_wstrb (rib_wstrb),
.rib_ready (rib_ready),
.rib_resp_err(rib_resp_err),
.intr_state (intr_state),
.intr_enable (intr_enable),
.irq (irq)
);
always_ff @(posedge clk_i) begin
if (rst_n_i) begin
if (f_past_valid && $past(rst_n_i && rib_valid && !rib_ready)) begin
assume (rib_valid);
assume (rib_addr == $past(rib_addr));
assume (rib_wdata == $past(rib_wdata));
assume (rib_wstrb == $past(rib_wstrb));
assert (rib_ready);
end
assert (irq == ((intr_state & intr_enable) != 0));
cover (rib_resp_err);
cover (irq);
end
end
endmodule
+9 -2
View File
@@ -12,7 +12,7 @@ FORMAL_PSRAM_DEPTH ?= 32
FORMAL_TIMEOUT ?= 60
FORMAL_WS2812_TIMEOUT ?= 120
FORMAL_I2C_TIMEOUT ?= 300
FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp psram
FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp i2s psram
FORMAL_FILELIST_GENERATOR := $(RTL_PATH)/formal/generate_formal_filelist.py
FORMAL_SBY_GENERATOR := $(RTL_PATH)/formal/generate_sby_config.py
FORMAL_RESULT_GENERATOR := $(RTL_PATH)/formal/formal_results.py
@@ -40,6 +40,8 @@ FORMAL_SOURCE_FILES := $(RTL_PATH)/formal/bus_formal.sv \
$(RTL_PATH)/formal/clint_formal_props.sv \
$(RTL_PATH)/formal/dvp_formal.sv \
$(RTL_PATH)/formal/dvp_formal_props.sv \
$(RTL_PATH)/formal/i2s_formal.sv \
$(RTL_PATH)/formal/i2s_formal_props.sv \
$(RTL_PATH)/formal/psram_formal.sv \
$(RTL_PATH)/formal/psram_formal_props.sv \
$(RTL_PATH)/top/rib_bus.sv \
@@ -77,6 +79,9 @@ FORMAL_SOURCE_FILES := $(RTL_PATH)/formal/bus_formal.sv \
$(ROOT_PATH)/rtl/ip/serial/i2c_core.sv \
$(ROOT_PATH)/rtl/ip/serial/i2c_reg.sv \
$(ROOT_PATH)/rtl/ip/serial/apb4_i2c.sv \
$(ROOT_PATH)/rtl/ip/serial/i2s_define.svh \
$(ROOT_PATH)/rtl/ip/serial/i2s_pkg.sv \
$(ROOT_PATH)/rtl/ip/serial/i2s_reg.sv \
$(ROOT_PATH)/rtl/managed/clusterip/i2c/rtl/i2c_if.sv \
$(ROOT_PATH)/rtl/ip/peripheral/timer_core.sv \
$(ROOT_PATH)/rtl/ip/peripheral/timer_define.svh \
@@ -202,6 +207,8 @@ formal-clint: $(FORMAL_DIR)/clint/.stamp | manifest
formal-dvp: $(FORMAL_DIR)/dvp/.stamp | manifest
formal-i2s: $(FORMAL_DIR)/i2s/.stamp | manifest
formal-psram: $(FORMAL_DIR)/psram/.stamp | manifest
formal-doctor:
@@ -210,4 +217,4 @@ formal-doctor:
formal-clean:
python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(FORMAL_DIR)
.PHONY: formal formal-bus formal-rib-adapter formal-rib2apb formal-sysctrl formal-pll-rcu formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-clint formal-dvp formal-psram formal-doctor formal-clean
.PHONY: formal formal-bus formal-rib-adapter formal-rib2apb formal-sysctrl formal-pll-rcu formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-clint formal-dvp formal-i2s formal-psram formal-doctor formal-clean
+2
View File
@@ -92,6 +92,8 @@ CRT_SRCS := $(ROOT_PATH)/crt/arch/riscv/startup.S \
$(ROOT_PATH)/crt/src/hal/ps2.c \
$(ROOT_PATH)/crt/src/hal/i2c_math.c \
$(ROOT_PATH)/crt/src/hal/i2c.c \
$(ROOT_PATH)/crt/src/hal/i2s_math.c \
$(ROOT_PATH)/crt/src/hal/i2s.c \
$(ROOT_PATH)/crt/src/hal/ws2812_math.c \
$(ROOT_PATH)/crt/src/hal/ws2812.c \
$(ROOT_PATH)/crt/src/hal/dma.c \
+1
View File
@@ -167,6 +167,7 @@
"i2s_clkgen.sv",
"i2s_core.sv",
"i2s_define.svh",
"i2s_pkg.sv",
"i2s_recv.sv",
"i2s_reg.sv",
"i2s_send.sv",
+1
View File
@@ -26,6 +26,7 @@ TEST_SOURCES = (
"crt/src/hal/uart_math.c",
"crt/src/hal/timer_math.c",
"crt/src/hal/psram_math.c",
"crt/src/hal/i2s_math.c",
"crt/src/hal/sysctrl.c",
"rtl/managed/clusterip/ps2/sw/src/ps2.c",
"rtl/managed/clusterip/ps2/sw/src/ps2_keyboard.c",
+29 -2
View File
@@ -9,6 +9,7 @@
#include <retrosoc/hal/gpio.h>
#include <retrosoc/hal/i2c.h>
#include <retrosoc/hal/lcd.h>
#include <retrosoc/hal/i2s.h>
#include <retrosoc/hal/psram.h>
#include <retrosoc/hal/spisd.h>
#include <retrosoc/hal/timer.h>
@@ -197,6 +198,32 @@ static int test_timer_helpers(void) {
return 0;
}
static int test_i2s_helpers(void) {
uint8_t sclk_div;
uint8_t lrck_div;
uint16_t first;
uint16_t second;
if (rs_i2s_pack_stereo16(0x1111U, 0x2222U) != UINT32_C(0x22221111))
return 1;
rs_i2s_unpack_stereo16(UINT32_C(0x22221111), &first, &second);
if ((first != 0x1111U) || (second != 0x2222U))
return 2;
if ((rs_i2s_div_from_hz(18432000U, 48000U, 16U, &sclk_div, &lrck_div) != RS_OK) ||
(sclk_div != 5U) || (lrck_div != 15U))
return 3;
if ((rs_i2s_div_from_hz(18432000U, 96000U, 24U, &sclk_div, &lrck_div) != RS_OK) ||
(sclk_div != 1U) || (lrck_div != 23U))
return 4;
if ((rs_i2s_rxdata_address() - rs_i2s_txdata_address()) != (uintptr_t)4U)
return 6;
if ((rs_i2s_div_from_hz(0U, 48000U, 16U, &sclk_div, &lrck_div) != RS_EINVAL) ||
(rs_i2s_div_from_hz(18432000U, 48000U, 20U, &sclk_div, &lrck_div) != RS_EINVAL) ||
(rs_i2s_div_from_hz(18432000U, 48000U, 16U, NULL, &lrck_div) != RS_EINVAL))
return 5;
return 0;
}
static int test_psram_helpers(void) {
rs_psram_timing_t timing;
@@ -357,8 +384,8 @@ int main(void) {
const int results[] = {
test_string_helpers(), test_formatter(), test_compiler_helpers(), test_wait_helper(),
test_ws2812_helpers(), test_timer_helpers(), test_psram_helpers(), test_uart_helpers(),
test_i2c_helpers(), test_gpio_helpers(), test_ps2_decoders(), test_wav_parser(),
test_video_parser(),
test_i2s_helpers(), test_i2c_helpers(), test_gpio_helpers(), test_ps2_decoders(),
test_wav_parser(), test_video_parser(),
};
for (size_t index = 0U; index < (sizeof(results) / sizeof(results[0])); ++index) {
+145
View File
@@ -0,0 +1,145 @@
`timescale 1ns / 1ps
`include "i2s_define.svh"
module i2s_tb;
logic clk_i = 1'b0;
logic clk_aud_i = 1'b0;
logic rst_n_i = 1'b0;
logic rst_aud_n_i = 1'b0;
logic dma_tx_stall;
logic dma_rx_stall;
logic [31:0] value;
int sclk_edges;
apb4_if apb4 (
.pclk (clk_i),
.presetn(rst_n_i)
);
axi4_stream_if #(
.DATA_WIDTH(32),
.ID_WIDTH (1),
.DEST_WIDTH(1),
.USER_WIDTH(1)
) tx_axis (
.aclk (clk_i),
.aresetn(rst_n_i)
);
axi4_stream_if #(
.DATA_WIDTH(32),
.ID_WIDTH (1),
.DEST_WIDTH(1),
.USER_WIDTH(1)
) rx_axis (
.aclk (clk_i),
.aresetn(rst_n_i)
);
i2s_if i2s ();
assign i2s.adcdat_i = i2s.dacdat_o;
assign tx_axis.tvalid = 1'b0;
assign tx_axis.tdata = '0;
assign tx_axis.tkeep = '1;
assign tx_axis.tstrb = '1;
assign tx_axis.tlast = 1'b0;
assign tx_axis.tid = '0;
assign tx_axis.tdest = '0;
assign tx_axis.tuser = '0;
assign rx_axis.tready = 1'b1;
always #5 clk_i = ~clk_i;
always #8 clk_aud_i = ~clk_aud_i;
apb4_i2s u_dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.clk_aud_i (clk_aud_i),
.rst_aud_n_i (rst_aud_n_i),
.dma_tx_stall_o(dma_tx_stall),
.dma_rx_stall_o(dma_rx_stall),
.apb4 (apb4),
.tx_axis (tx_axis),
.rx_axis (rx_axis),
.i2s (i2s)
);
task automatic write(input logic [31:0] address, input logic [31:0] data);
begin
@(negedge clk_i);
apb4.paddr = address;
apb4.pwdata = data;
apb4.pstrb = 4'hF;
apb4.pwrite = 1'b1;
apb4.psel = 1'b1;
apb4.penable = 1'b0;
@(negedge clk_i);
apb4.penable = 1'b1;
while (!apb4.pready) @(negedge clk_i);
apb4.psel = 1'b0;
apb4.penable = 1'b0;
apb4.pwrite = 1'b0;
apb4.pstrb = '0;
end
endtask
task automatic read(input logic [31:0] address, output logic [31:0] data);
begin
@(negedge clk_i);
apb4.paddr = address;
apb4.pwdata = '0;
apb4.pstrb = '0;
apb4.pwrite = 1'b0;
apb4.psel = 1'b1;
apb4.penable = 1'b0;
@(negedge clk_i);
apb4.penable = 1'b1;
while (!apb4.pready) @(negedge clk_i);
data = apb4.prdata;
apb4.psel = 1'b0;
apb4.penable = 1'b0;
end
endtask
always @(posedge i2s.sclk_o) sclk_edges = sclk_edges + 1;
initial begin
apb4.psel = 1'b0;
apb4.paddr = '0;
apb4.pwdata = '0;
apb4.pstrb = '0;
sclk_edges = 0;
repeat (8) @(posedge clk_i);
rst_n_i = 1'b1;
rst_aud_n_i = 1'b1;
read(32'h0000_00f8, value);
if (value != 32'h0001_0000) $fatal(1, "bad I2S version %h", value);
read(32'h0000_00fc, value);
if (value != 32'h0030_0707) $fatal(1, "bad I2S capability %h", value);
read(32'h0000_0100, value);
if (!apb4.pslverr) $fatal(1, "invalid I2S access was accepted");
write(32'h0000_0000, 32'h0000_0001);
write(32'h0000_0010, 32'h0000_0001);
if (!apb4.pslverr) $fatal(1, "enabled FORMAT write was accepted");
write(32'h0000_0000, 32'h0000_0000);
write(32'h0000_0010, 32'h0000_0000);
write(32'h0000_0018, 32'h0020_0078);
write(32'h0000_0028, 32'h0000_0004);
write(32'h0000_0030, 32'h0000_0004);
read(32'h0000_0024, value);
if ((value & 32'h0000_0004) == 0) $fatal(1, "I2S interrupt test failed");
if (!i2s.irq_o) $fatal(1, "I2S IRQ was not raised");
write(32'h0000_0024, 32'h0000_000F);
write(32'h0000_001c, 32'hA5A5_5A5A);
write(32'h0000_0000, 32'h0000_0007);
repeat (4000) @(posedge clk_aud_i);
if (sclk_edges == 0) $fatal(1, "I2S SCLK did not toggle");
read(32'h0000_0008, value);
write(32'h0000_0000, 32'h0000_0008);
write(32'h0000_0000, 32'h0000_0009);
repeat (2000) @(posedge clk_aud_i);
write(32'h0000_0004, 32'h0000_0003);
repeat (200) @(posedge clk_i);
read(32'h0000_0008, value);
if ((value & 32'h0300_0000) != 0) $fatal(1, "I2S flush stayed busy %h", value);
$display("APB4 I2S controller test passed");
$finish;
end
endmodule
+67
View File
@@ -0,0 +1,67 @@
"""APB4 I2S register, CDC, clocking, and interrupt regression."""
from __future__ import annotations
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_i2s_controller_abi_and_clocking(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
common = ROOT / "rtl/managed/clusterip/common/rtl"
ip = ROOT / "rtl/ip/serial"
defines = [
"+define+PDK_BEHAV",
"+define+SV_ASSRT_DISABLE",
]
source = [
"+incdir+" + str(common),
"+incdir+" + str(common / "interface"),
"+incdir+" + str(common / "utils"),
"+incdir+" + str(common / "cdc"),
"+incdir+" + str(common / "clkrst"),
"+incdir+" + str(ip),
str(common / "interface/apb4_if.sv"),
str(common / "interface/axi4_stream_if.sv"),
str(common / "utils/register.sv"),
str(common / "utils/xchecker.sv"),
str(common / "utils/bin2gray.sv"),
str(common / "utils/gray2bin.sv"),
str(common / "utils/spill_register.sv"),
str(common / "cdc/cdc_sync.sv"),
str(common / "cdc/cdc_rst_ctrlr.sv"),
str(common / "cdc/async_reqack.sv"),
str(common / "cdc/cdc_fifo.sv"),
str(common / "cdc/cdc_2phase.sv"),
str(common / "cdc/cdc_warm_flush.sv"),
str(common / "utils/edge_det.sv"),
str(common / "clkrst/rst_sync.sv"),
str(ROOT / "rtl/tech/tc_clk.sv"),
str(ip / "i2s_pkg.sv"),
str(ip / "i2s_reg.sv"),
str(ip / "i2s_clkgen.sv"),
str(ip / "i2s_recv.sv"),
str(ip / "i2s_send.sv"),
str(ip / "i2s_core.sv"),
str(ip / "apb4_i2s.sv"),
str(ROOT / "tests/rtl/i2s_tb.sv"),
]
source_list = tmp_path / "i2s.fl"
source_list.write_text("\n".join([*defines, *source, ""]), encoding="utf-8")
converted = tmp_path / "i2s.v"
subprocess.run(
[sys.executable, str(ROOT / "rtl/mini/script/convt_sv2v.py"), "-f", str(source_list), "--output", str(converted)],
check=True,
)
simulation = tmp_path / "i2s_tb"
subprocess.run([iverilog, "-g2012", "-s", "i2s_tb", "-o", str(simulation), str(converted)], check=True)
result = subprocess.run([vvp, str(simulation)], check=True, capture_output=True, text=True)
assert "APB4 I2S controller test passed" in result.stdout