mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
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:
31 files changed
+1917
-568
No files matched your search
@@ -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,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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
@@ -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)®_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)®_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
@@ -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)®_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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -114,7 +114,8 @@ void rs_app_info(void) {
|
||||
printf(" 1 x SPISD @%p\n", (void *)®_spisd_mode);
|
||||
printf(" 1 x I2C0 @%p\n",
|
||||
(void *)(uintptr_t)RS_SOC_APB4_I2C0_BASE);
|
||||
printf(" 1 x I2S @%p\n", (void *)®_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 *)®_xpi_cfgidx);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 \
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user