feat: add SDIO support and related tests

- Introduced a new testbench for the SDIO wrapper () to validate APB interactions.
- Updated  to include IRQ handling for SDIO0 and SDIO1.
- Enhanced  to account for performance management of SDIO0 and SDIO1.
- Modified memory map tests to include SDIO base addresses and performance wait offsets.
- Updated pin mapping tests to include SDIO1 signals.
- Created a comprehensive suite of SDIO tests () covering various functionalities including CRC, command engine, DMA, and standalone operations.
- Added a new test for verifying SDIO register parity against the RTL definition.
- Adjusted SOC topology tests to reflect the addition of SDIO in the IRQ and APB fabric.
This commit is contained in:
maksyuki committed 2026-08-20 10:03:51 +08:00
1 parent faad445e42
commit a8325ab6b7
83 files changed
+11777 -153

No files matched your search

+2 -2
View File
@@ -269,7 +269,7 @@ endif
benchmark-report coremark-report \
pin-map check-pin-map soc-topology check-soc-topology user-extensions check-user-extensions \
check-clock-reset-domains tech-cell-test rtl-lint check-rtl-lint \
formal formal-bus formal-rib-adapter formal-rib2apb formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-dvp formal-i2s formal-opipsram formal-dma 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-opipsram formal-dma formal-sdio 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 | formal-i2s | formal-opipsram | formal-dma run peripheral proofs' \
'formal-sysctrl | formal-pll-rcu | formal-gpio | formal-ws2812 | formal-uart | formal-i2c | formal-timer | formal-clint | formal-dvp | formal-i2s | formal-opipsram | formal-dma | formal-sdio 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' \
+8
View File
@@ -88,6 +88,14 @@ channel 1, I2C1 channel 2, and media/benchmark clients serialize on bulk
channel 3. See [DMA MVP](../docs/ip/dma.md) before adding a concurrent DMA
client.
The SD/SDIO HAL exposes `rs_sd_memory_*` for SD Memory v2 block devices and
`rs_sdio_function_*` for SDIO functions; these APIs must not be conflated.
Bring-up can run the controller ID/capability/IRQ self-test without a card.
The delivered software is limited to fixed-present 3.3 V SDR, 1/4-bit
operation, aligned SG DMA buffers, and the validated 72 MHz 24/36 MHz versus
`>=100 MHz` 50 MHz clock boundary. It makes no filesystem, hotplug,
compliance, or vendor Wi-Fi claim.
Self-owned application C/H code follows the project
[MISRA C:2012 Amendment 2 policy](../docs/misra-c-2012.md). Managed upstream
sources and exclusions listed in `quality/embedded_c_policy.json` are outside
+4
View File
@@ -1,5 +1,6 @@
#include <retrosoc/core/archinfo.h>
#include <retrosoc/core/soc.h>
#include <retrosoc/hal/sdio.h>
#include <retrosoc/hal/uart.h>
#include <retrosoc/service/booter.h>
#include <retrosoc/service/test.h>
@@ -10,6 +11,9 @@ int main(void) {
}
rs_app_info();
ip_archinfo_test(0, 0);
if (rs_sdio_controller_selftest(RS_SDIO_INSTANCE_0) != RS_OK) {
return 1;
}
rs_test_finish(RS_TEST_PASSED, 0U);
return 0;
}
+12
View File
@@ -51,6 +51,18 @@ I2C0, I2C1, and bulk/media clients have deterministic channel assignments; see
the [DMA MVP contract](../docs/ip/dma.md). The current SDK intentionally
accepts only naturally aligned 32-bit DMA transfers.
`<retrosoc/hal/sdio.h>` provides separate low-level native SD host,
SD Memory v2, and SDIO function APIs for `sdio0` and `sdio1`. The controller
scope is fixed-present 3.3 V SDR at 1/4-bit width; it does not claim SD
Association compliance or a vendor Wi-Fi driver. The 72 MHz SoC profiles use
the validated 24/36 MHz operating range, while a 50 MHz SD clock requires a
separately validated `clk_i >= 100 MHz` environment. SG DMA descriptors and
their buffers must be 16-byte/32-bit aligned as applicable; callers must use a
bounce buffer for an unaligned head. PIO and DMA use little-endian byte lanes:
bits `[7:0]`/strobe lane 0 is the first wire byte, and `DATA_START` is the
coordinated DMA/card-data launch. The SDK does not provide hotplug,
filesystem, combo-card, UHS, DDR, or 1.8 V support.
`crt/inc/` is legacy or generated compatibility material. It is not an active
public include root for the firmware build; new code must not add dependencies
on it.
+2
View File
@@ -9,6 +9,8 @@ typedef struct {
uint64_t mgmt_wait;
uint64_t user_wait;
uint64_t dma_wait;
uint64_t sdio0_wait;
uint64_t sdio1_wait;
uint64_t apb4_periph_wait;
uint64_t apb4_system_wait;
uint64_t sdram_wait;
+253
View File
@@ -0,0 +1,253 @@
#ifndef RETROSOC_HAL_SDIO_H
#define RETROSOC_HAL_SDIO_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <retrosoc/core/status.h>
#include <retrosoc/hal/sdio_regs.h>
#define RS_SD_MEMORY_BLOCK_SIZE UINT32_C(512)
#define RS_SDIO_PIO_WORD_SIZE UINT32_C(4)
/*
* SDIO words are little-endian byte lanes: bits [7:0] (lane 0) contain the
* first byte placed on the SD wire, and each byte is serialized MSB first.
* The same convention is used by PIO, AXI stream data, and tail strobes.
*/
typedef enum {
RS_SDIO_INSTANCE_0 = 0,
RS_SDIO_INSTANCE_1 = 1,
RS_SDIO_INSTANCE_COUNT = 2
} rs_sdio_instance_t;
typedef enum { RS_SDIO_BUS_WIDTH_1 = 0, RS_SDIO_BUS_WIDTH_4 = 1 } rs_sdio_bus_width_t;
typedef enum {
RS_SDIO_RESPONSE_NONE = RS_SDIO_RESP_NONE,
RS_SDIO_RESPONSE_R1 = RS_SDIO_RESP_R1,
RS_SDIO_RESPONSE_R1B = RS_SDIO_RESP_R1B,
RS_SDIO_RESPONSE_R2 = RS_SDIO_RESP_R2,
RS_SDIO_RESPONSE_R3 = RS_SDIO_RESP_R3,
RS_SDIO_RESPONSE_R4 = RS_SDIO_RESP_R4,
RS_SDIO_RESPONSE_R5 = RS_SDIO_RESP_R5,
RS_SDIO_RESPONSE_R6 = RS_SDIO_RESP_R6,
RS_SDIO_RESPONSE_R7 = RS_SDIO_RESP_R7
} rs_sdio_response_type_t;
typedef enum { RS_SDIO_DATA_FROM_CARD = 0, RS_SDIO_DATA_TO_CARD = 1 } rs_sdio_data_direction_t;
typedef enum { RS_SD_MEMORY_SDSC = 0, RS_SD_MEMORY_SDHC = 1 } rs_sd_memory_card_type_t;
typedef struct {
uint32_t words[5];
} rs_sdio_response_t;
typedef struct {
uint8_t index;
uint32_t argument;
rs_sdio_response_type_t response;
bool crc_check;
bool index_check;
} rs_sdio_command_t;
typedef struct {
uint16_t block_size;
uint16_t block_count;
rs_sdio_data_direction_t direction;
bool dma;
bool block_mode;
bool fixed_address;
} rs_sdio_data_config_t;
typedef struct {
uint16_t half_period;
uint32_t requested_hz;
uint32_t actual_hz;
} rs_sdio_clock_t;
typedef struct {
uint32_t source_clock_hz;
uint32_t target_clock_hz;
rs_sdio_bus_width_t bus_width;
uint32_t timeout_cmd;
uint32_t timeout_data;
uint32_t timeout_busy;
bool enable_interrupts;
} rs_sdio_config_t;
typedef struct {
uint32_t raw_status;
uint32_t command_status;
uint32_t data_status;
uint32_t fifo_status;
uint32_t dma_status;
uint32_t error_status;
uint32_t irq_status;
uint32_t irq_enable;
uint32_t clock_actual_hz;
uint32_t current_descriptor;
uint32_t bytes_done;
uint32_t dma_error_address;
uint32_t dma_error;
uint32_t last_command;
bool busy;
bool present;
} rs_sdio_status_t;
typedef struct {
uint32_t raw_status;
uint32_t current_descriptor;
uint32_t bytes_done;
uint32_t error_address;
uint32_t error_code;
bool busy;
bool done;
bool error;
} rs_sdio_dma_status_t;
typedef struct {
uint32_t cid[5];
uint32_t csd[5];
uint32_t ocr;
uint16_t rca;
uint32_t capacity_blocks;
uint32_t block_length;
rs_sd_memory_card_type_t card_type;
rs_sdio_bus_width_t bus_width;
uint32_t actual_clock_hz;
bool high_capacity;
bool high_speed;
bool bus_width_fallback;
bool speed_fallback;
} rs_sd_memory_info_t;
typedef struct {
uint32_t ocr;
uint16_t rca;
uint8_t function_count;
uint8_t cccr_version;
uint8_t io_enable;
uint8_t io_ready;
uint8_t bus_interface_control;
uint8_t function_code[8];
uint16_t function_block_size[8];
bool high_speed;
bool high_speed_fallback;
bool dat1_interrupt_enabled;
} rs_sdio_function_info_t;
typedef struct {
uint8_t function;
uint32_t address;
bool write;
bool raw;
uint8_t data;
} rs_sdio_cmd52_t;
typedef struct {
uint8_t function;
uint32_t address;
uint16_t count;
bool write;
bool block_mode;
bool fixed_address;
} rs_sdio_cmd53_t;
static inline void rs_sdio_memory_barrier(void) {
#if defined(__riscv)
__asm__ volatile("fence rw, rw" ::: "memory");
#else
__atomic_thread_fence(__ATOMIC_SEQ_CST);
#endif
}
rs_status_t rs_sdio_clock_calculate(uint32_t source_clock_hz, uint32_t target_clock_hz,
rs_sdio_clock_t *clock);
rs_status_t rs_sdio_response_field(const rs_sdio_response_t *response, uint32_t high_bit,
uint32_t low_bit, uint32_t *value);
rs_status_t rs_sd_memory_address(rs_sd_memory_card_type_t card_type, uint32_t block,
uint32_t *argument);
rs_status_t rs_sd_memory_parse_csd(const rs_sdio_response_t *response, rs_sd_memory_info_t *info);
rs_status_t rs_sdio_cmd52_argument(const rs_sdio_cmd52_t *command, uint32_t *argument);
rs_status_t rs_sdio_cmd53_argument(const rs_sdio_cmd53_t *command, uint32_t *argument);
rs_status_t rs_sdio_validate_dma_buffer(const void *buffer, size_t byte_count);
rs_status_t rs_sdio_descriptor_prepare(rs_sdio_descriptor_t *descriptor, uintptr_t buffer,
size_t byte_count, uintptr_t next, bool end, bool irq);
rs_status_t rs_sdio_descriptor_validate(const rs_sdio_descriptor_t *descriptor);
rs_status_t rs_sdio_descriptor_chain_validate(const rs_sdio_descriptor_t *descriptors,
uint16_t count, uint32_t total_bytes);
rs_status_t rs_sdio_descriptor_publish(rs_sdio_descriptor_t *descriptor);
rs_status_t rs_sdio_descriptor_publish_chain(rs_sdio_descriptor_t *descriptors, uint16_t count);
rs_status_t rs_sdio_probe(rs_sdio_instance_t instance, uint32_t *ip_id, uint32_t *version,
uint32_t *capability);
rs_status_t rs_sdio_reset(rs_sdio_instance_t instance, rs_timeout_t timeout);
rs_status_t rs_sdio_configure(rs_sdio_instance_t instance, const rs_sdio_config_t *config);
rs_status_t rs_sdio_clock_set(rs_sdio_instance_t instance, uint32_t source_clock_hz,
uint32_t target_clock_hz, rs_sdio_clock_t *clock);
rs_status_t rs_sdio_bus_width_set(rs_sdio_instance_t instance, rs_sdio_bus_width_t width);
rs_status_t rs_sdio_timeouts_set(rs_sdio_instance_t instance, uint32_t command_timeout,
uint32_t data_timeout, uint32_t busy_timeout);
rs_status_t rs_sdio_enable(rs_sdio_instance_t instance, bool enable);
rs_status_t rs_sdio_status_get(rs_sdio_instance_t instance, rs_sdio_status_t *status);
rs_status_t rs_sdio_command_start(rs_sdio_instance_t instance, const rs_sdio_command_t *command);
rs_status_t rs_sdio_command_wait(rs_sdio_instance_t instance, rs_sdio_response_t *response,
rs_timeout_t timeout);
rs_status_t rs_sdio_command_execute(rs_sdio_instance_t instance, const rs_sdio_command_t *command,
rs_sdio_response_t *response, rs_timeout_t timeout);
rs_status_t rs_sdio_response_get(rs_sdio_instance_t instance, rs_sdio_response_t *response);
rs_status_t rs_sdio_data_configure(rs_sdio_instance_t instance,
const rs_sdio_data_config_t *config);
rs_status_t rs_sdio_data_start(rs_sdio_instance_t instance);
rs_status_t rs_sdio_pio_write(rs_sdio_instance_t instance, const void *buffer, size_t byte_count,
rs_timeout_t timeout);
rs_status_t rs_sdio_pio_read(rs_sdio_instance_t instance, void *buffer, size_t byte_count,
rs_timeout_t timeout);
rs_status_t rs_sdio_data_wait(rs_sdio_instance_t instance, rs_timeout_t timeout);
rs_status_t rs_sdio_dma_setup(rs_sdio_instance_t instance, rs_sdio_descriptor_t *descriptors,
uint16_t count, uint32_t total_bytes);
/* DATA_START is the coordinated DMA and card-data launch operation. */
rs_status_t rs_sdio_dma_start(rs_sdio_instance_t instance);
rs_status_t rs_sdio_dma_abort(rs_sdio_instance_t instance);
rs_status_t rs_sdio_dma_status_get(rs_sdio_instance_t instance, rs_sdio_dma_status_t *status);
rs_status_t rs_sdio_dma_wait(rs_sdio_instance_t instance, rs_timeout_t timeout);
rs_status_t rs_sdio_irq_enable(rs_sdio_instance_t instance, uint32_t mask);
rs_status_t rs_sdio_irq_pending(rs_sdio_instance_t instance, uint32_t *mask);
rs_status_t rs_sdio_irq_clear(rs_sdio_instance_t instance, uint32_t mask);
rs_status_t rs_sdio_irq_test(rs_sdio_instance_t instance, uint32_t mask);
rs_status_t rs_sdio_error_clear(rs_sdio_instance_t instance, uint32_t mask);
rs_status_t rs_sdio_controller_selftest(rs_sdio_instance_t instance);
rs_status_t rs_sd_memory_initialize(rs_sdio_instance_t instance, uint32_t source_clock_hz,
rs_sd_memory_info_t *info, rs_timeout_t timeout);
rs_status_t rs_sd_memory_read_blocks(rs_sdio_instance_t instance, const rs_sd_memory_info_t *info,
uint32_t block, uint32_t count, void *buffer,
rs_timeout_t timeout);
rs_status_t rs_sd_memory_write_blocks(rs_sdio_instance_t instance, const rs_sd_memory_info_t *info,
uint32_t block, uint32_t count, const void *buffer,
rs_timeout_t timeout);
rs_status_t rs_sdio_function_initialize(rs_sdio_instance_t instance, uint32_t source_clock_hz,
rs_sdio_function_info_t *info, rs_timeout_t timeout);
rs_status_t rs_sdio_function_enable(rs_sdio_instance_t instance, rs_sdio_function_info_t *info,
uint8_t function, rs_timeout_t timeout);
rs_status_t rs_sdio_function_cmd52_read(rs_sdio_instance_t instance, uint8_t function,
uint32_t address, uint8_t *data, rs_timeout_t timeout);
rs_status_t rs_sdio_function_cmd52_write(rs_sdio_instance_t instance, uint8_t function,
uint32_t address, uint8_t data, rs_timeout_t timeout);
rs_status_t rs_sdio_function_cmd53_transfer(rs_sdio_instance_t instance, uint8_t function,
uint32_t address, void *buffer, size_t byte_count,
bool write, bool block_mode, bool fixed_address,
uint16_t block_size, rs_timeout_t timeout);
rs_status_t rs_sdio_function_irq_enable(rs_sdio_instance_t instance, bool enable);
rs_status_t rs_sdio_function_irq_pending(rs_sdio_instance_t instance, bool *pending);
rs_status_t rs_sdio_function_irq_ack(rs_sdio_instance_t instance);
#endif
+314
View File
@@ -0,0 +1,314 @@
#ifndef RETROSOC_HAL_SDIO_REGS_H
#define RETROSOC_HAL_SDIO_REGS_H
#include <stdint.h>
/*
* The ABI names below are a direct hand-maintained C mirror of
* rtl/ip/storage/sdio_define.svh. Keep the ABI prefix stable so the parity
* test can compare the two files without a generated register description.
*/
#define RS_SDIO_ABI_IP_ID UINT32_C(0x000)
#define RS_SDIO_ABI_IP_VERSION UINT32_C(0x004)
#define RS_SDIO_ABI_CAPABILITY UINT32_C(0x008)
#define RS_SDIO_ABI_HOST_CTRL UINT32_C(0x00C)
#define RS_SDIO_ABI_CLOCK_CTRL UINT32_C(0x010)
#define RS_SDIO_ABI_CLOCK_ACTUAL UINT32_C(0x014)
#define RS_SDIO_ABI_BUS_CTRL UINT32_C(0x018)
#define RS_SDIO_ABI_TIMEOUT_CMD UINT32_C(0x01C)
#define RS_SDIO_ABI_TIMEOUT_DATA UINT32_C(0x020)
#define RS_SDIO_ABI_TIMEOUT_BUSY UINT32_C(0x024)
#define RS_SDIO_ABI_STATUS UINT32_C(0x028)
#define RS_SDIO_ABI_PRESENT UINT32_C(0x02C)
#define RS_SDIO_ABI_CMD_ARG UINT32_C(0x040)
#define RS_SDIO_ABI_CMD_CFG UINT32_C(0x044)
#define RS_SDIO_ABI_CMD_START UINT32_C(0x048)
#define RS_SDIO_ABI_CMD_STATUS UINT32_C(0x04C)
#define RS_SDIO_ABI_RESP0 UINT32_C(0x050)
#define RS_SDIO_ABI_RESP1 UINT32_C(0x054)
#define RS_SDIO_ABI_RESP2 UINT32_C(0x058)
#define RS_SDIO_ABI_RESP3 UINT32_C(0x05C)
#define RS_SDIO_ABI_RESP4 UINT32_C(0x060)
#define RS_SDIO_ABI_BLOCK_SIZE UINT32_C(0x080)
#define RS_SDIO_ABI_BLOCK_COUNT UINT32_C(0x084)
#define RS_SDIO_ABI_DATA_CFG UINT32_C(0x088)
#define RS_SDIO_ABI_DATA_START UINT32_C(0x08C)
#define RS_SDIO_ABI_PIO_DATA UINT32_C(0x090)
#define RS_SDIO_ABI_FIFO_STATUS UINT32_C(0x094)
#define RS_SDIO_ABI_FIFO_WATERMARK UINT32_C(0x098)
#define RS_SDIO_ABI_DESC_BASE UINT32_C(0x0C0)
#define RS_SDIO_ABI_DESC_COUNT UINT32_C(0x0C4)
#define RS_SDIO_ABI_DMA_CTRL UINT32_C(0x0C8)
#define RS_SDIO_ABI_DMA_STATUS UINT32_C(0x0CC)
#define RS_SDIO_ABI_CURRENT_DESC UINT32_C(0x0D0)
#define RS_SDIO_ABI_BYTES_DONE UINT32_C(0x0D4)
#define RS_SDIO_ABI_DMA_ERROR_ADDR UINT32_C(0x0D8)
#define RS_SDIO_ABI_DMA_ERROR UINT32_C(0x0DC)
#define RS_SDIO_ABI_IRQ_STATUS UINT32_C(0x100)
#define RS_SDIO_ABI_IRQ_ENABLE UINT32_C(0x104)
#define RS_SDIO_ABI_IRQ_TEST UINT32_C(0x108)
#define RS_SDIO_ABI_ERROR_STATUS UINT32_C(0x10C)
#define RS_SDIO_ABI_ERROR_ABORT 7U
#define RS_SDIO_ABI_LAST_CMD UINT32_C(0x110)
#define RS_SDIO_ABI_CRC_ERROR_COUNT UINT32_C(0x114)
#define RS_SDIO_ABI_TIMEOUT_COUNT UINT32_C(0x118)
#define RS_SDIO_ABI_AXI_ERROR_COUNT UINT32_C(0x11C)
#define RS_SDIO_ABI_STALL_COUNT UINT32_C(0x120)
#define RS_SDIO_ABI_DEBUG UINT32_C(0x124)
#define RS_SDIO_ABI_HOST_CTRL_ENABLE 0U
#define RS_SDIO_ABI_HOST_CTRL_ABORT 1U
#define RS_SDIO_ABI_HOST_CTRL_IRQ 2U
#define RS_SDIO_ABI_CLOCK_CTRL_ENABLE 0U
#define RS_SDIO_ABI_CLOCK_CTRL_HALF_PERIOD_LSB 8U
#define RS_SDIO_ABI_BUS_CTRL_WIDTH_LSB 0U
#define RS_SDIO_ABI_BUS_CTRL_WIDTH_1 0U
#define RS_SDIO_ABI_BUS_CTRL_WIDTH_4 1U
#define RS_SDIO_ABI_CMD_CFG_INDEX_LSB 0U
#define RS_SDIO_ABI_CMD_CFG_RESP_LSB 8U
#define RS_SDIO_ABI_CMD_CFG_CRC_CHECK 12U
#define RS_SDIO_ABI_CMD_CFG_INDEX_CHECK 13U
#define RS_SDIO_ABI_RESP_NONE 0U
#define RS_SDIO_ABI_RESP_R1 1U
#define RS_SDIO_ABI_RESP_R1B 2U
#define RS_SDIO_ABI_RESP_R2 3U
#define RS_SDIO_ABI_RESP_R3 4U
#define RS_SDIO_ABI_RESP_R4 5U
#define RS_SDIO_ABI_RESP_R5 6U
#define RS_SDIO_ABI_RESP_R6 7U
#define RS_SDIO_ABI_RESP_R7 8U
#define RS_SDIO_ABI_DATA_CFG_DIRECTION 0U
#define RS_SDIO_ABI_DATA_CFG_WIDTH_LSB 1U
#define RS_SDIO_ABI_DATA_CFG_DMA 4U
#define RS_SDIO_ABI_DATA_CFG_BLOCK 5U
#define RS_SDIO_ABI_DATA_CFG_FIXED 6U
#define RS_SDIO_ABI_DMA_CTRL_START 0U
#define RS_SDIO_ABI_DMA_CTRL_ABORT 1U
#define RS_SDIO_ABI_DESC_OWN 0U
#define RS_SDIO_ABI_DESC_CHAIN 1U
#define RS_SDIO_ABI_DESC_END 2U
#define RS_SDIO_ABI_DESC_IRQ 3U
#define RS_SDIO_ABI_DESC_DONE 16U
#define RS_SDIO_ABI_DESC_ERROR 17U
#define RS_SDIO_ABI_IRQ_CMD_DONE 0U
#define RS_SDIO_ABI_IRQ_DATA_DONE 1U
#define RS_SDIO_ABI_IRQ_DMA_DONE 2U
#define RS_SDIO_ABI_IRQ_CMD_ERROR 3U
#define RS_SDIO_ABI_IRQ_DATA_ERROR 4U
#define RS_SDIO_ABI_IRQ_DMA_ERROR 5U
#define RS_SDIO_ABI_IRQ_DAT1 6U
#define RS_SDIO_ABI_IRQ_ABORT 7U
#define RS_SDIO_ABI_CAP_SD_MEMORY_V2 0U
#define RS_SDIO_ABI_CAP_SDIO_V2 1U
#define RS_SDIO_ABI_CAP_BUS_1BIT 2U
#define RS_SDIO_ABI_CAP_BUS_4BIT 3U
#define RS_SDIO_ABI_CAP_SDR 4U
#define RS_SDIO_ABI_CAP_SG_DMA 5U
#define RS_SDIO_ABI_CAP_PIO 6U
#define RS_SDIO_ABI_CAP_CRC 7U
#define RS_SDIO_ABI_CAP_MAX_BURST_16 8U
#define RS_SDIO_REG_IP_ID RS_SDIO_ABI_IP_ID
#define RS_SDIO_REG_IP_VERSION RS_SDIO_ABI_IP_VERSION
#define RS_SDIO_REG_CAPABILITY RS_SDIO_ABI_CAPABILITY
#define RS_SDIO_REG_HOST_CTRL RS_SDIO_ABI_HOST_CTRL
#define RS_SDIO_REG_CLOCK_CTRL RS_SDIO_ABI_CLOCK_CTRL
#define RS_SDIO_REG_CLOCK_ACTUAL RS_SDIO_ABI_CLOCK_ACTUAL
#define RS_SDIO_REG_BUS_CTRL RS_SDIO_ABI_BUS_CTRL
#define RS_SDIO_REG_TIMEOUT_CMD RS_SDIO_ABI_TIMEOUT_CMD
#define RS_SDIO_REG_TIMEOUT_DATA RS_SDIO_ABI_TIMEOUT_DATA
#define RS_SDIO_REG_TIMEOUT_BUSY RS_SDIO_ABI_TIMEOUT_BUSY
#define RS_SDIO_REG_STATUS RS_SDIO_ABI_STATUS
#define RS_SDIO_REG_PRESENT RS_SDIO_ABI_PRESENT
#define RS_SDIO_REG_CMD_ARG RS_SDIO_ABI_CMD_ARG
#define RS_SDIO_REG_CMD_CFG RS_SDIO_ABI_CMD_CFG
#define RS_SDIO_REG_CMD_START RS_SDIO_ABI_CMD_START
#define RS_SDIO_REG_CMD_STATUS RS_SDIO_ABI_CMD_STATUS
#define RS_SDIO_REG_RESP0 RS_SDIO_ABI_RESP0
#define RS_SDIO_REG_RESP1 RS_SDIO_ABI_RESP1
#define RS_SDIO_REG_RESP2 RS_SDIO_ABI_RESP2
#define RS_SDIO_REG_RESP3 RS_SDIO_ABI_RESP3
#define RS_SDIO_REG_RESP4 RS_SDIO_ABI_RESP4
#define RS_SDIO_REG_BLOCK_SIZE RS_SDIO_ABI_BLOCK_SIZE
#define RS_SDIO_REG_BLOCK_COUNT RS_SDIO_ABI_BLOCK_COUNT
#define RS_SDIO_REG_DATA_CFG RS_SDIO_ABI_DATA_CFG
#define RS_SDIO_REG_DATA_START RS_SDIO_ABI_DATA_START
#define RS_SDIO_REG_PIO_DATA RS_SDIO_ABI_PIO_DATA
#define RS_SDIO_REG_FIFO_STATUS RS_SDIO_ABI_FIFO_STATUS
#define RS_SDIO_REG_FIFO_WATERMARK RS_SDIO_ABI_FIFO_WATERMARK
#define RS_SDIO_REG_DESC_BASE RS_SDIO_ABI_DESC_BASE
#define RS_SDIO_REG_DESC_COUNT RS_SDIO_ABI_DESC_COUNT
#define RS_SDIO_REG_DMA_CTRL RS_SDIO_ABI_DMA_CTRL
#define RS_SDIO_REG_DMA_STATUS RS_SDIO_ABI_DMA_STATUS
#define RS_SDIO_REG_CURRENT_DESC RS_SDIO_ABI_CURRENT_DESC
#define RS_SDIO_REG_BYTES_DONE RS_SDIO_ABI_BYTES_DONE
#define RS_SDIO_REG_DMA_ERROR_ADDR RS_SDIO_ABI_DMA_ERROR_ADDR
#define RS_SDIO_REG_DMA_ERROR RS_SDIO_ABI_DMA_ERROR
#define RS_SDIO_REG_IRQ_STATUS RS_SDIO_ABI_IRQ_STATUS
#define RS_SDIO_REG_IRQ_ENABLE RS_SDIO_ABI_IRQ_ENABLE
#define RS_SDIO_REG_IRQ_TEST RS_SDIO_ABI_IRQ_TEST
#define RS_SDIO_REG_ERROR_STATUS RS_SDIO_ABI_ERROR_STATUS
#define RS_SDIO_REG_LAST_CMD RS_SDIO_ABI_LAST_CMD
#define RS_SDIO_REG_CRC_ERROR_COUNT RS_SDIO_ABI_CRC_ERROR_COUNT
#define RS_SDIO_REG_TIMEOUT_COUNT RS_SDIO_ABI_TIMEOUT_COUNT
#define RS_SDIO_REG_AXI_ERROR_COUNT RS_SDIO_ABI_AXI_ERROR_COUNT
#define RS_SDIO_REG_STALL_COUNT RS_SDIO_ABI_STALL_COUNT
#define RS_SDIO_REG_DEBUG RS_SDIO_ABI_DEBUG
#define RS_SDIO_HOST_CTRL_ENABLE_BIT RS_SDIO_ABI_HOST_CTRL_ENABLE
#define RS_SDIO_HOST_CTRL_ABORT_BIT RS_SDIO_ABI_HOST_CTRL_ABORT
#define RS_SDIO_HOST_CTRL_IRQ_BIT RS_SDIO_ABI_HOST_CTRL_IRQ
#define RS_SDIO_CLOCK_CTRL_ENABLE_BIT RS_SDIO_ABI_CLOCK_CTRL_ENABLE
#define RS_SDIO_CLOCK_CTRL_HALF_PERIOD_LSB RS_SDIO_ABI_CLOCK_CTRL_HALF_PERIOD_LSB
#define RS_SDIO_BUS_CTRL_WIDTH_LSB RS_SDIO_ABI_BUS_CTRL_WIDTH_LSB
#define RS_SDIO_BUS_CTRL_WIDTH_1 RS_SDIO_ABI_BUS_CTRL_WIDTH_1
#define RS_SDIO_BUS_CTRL_WIDTH_4 RS_SDIO_ABI_BUS_CTRL_WIDTH_4
#define RS_SDIO_CMD_CFG_INDEX_LSB RS_SDIO_ABI_CMD_CFG_INDEX_LSB
#define RS_SDIO_CMD_CFG_RESP_LSB RS_SDIO_ABI_CMD_CFG_RESP_LSB
#define RS_SDIO_CMD_CFG_CRC_CHECK_BIT RS_SDIO_ABI_CMD_CFG_CRC_CHECK
#define RS_SDIO_CMD_CFG_INDEX_CHECK_BIT RS_SDIO_ABI_CMD_CFG_INDEX_CHECK
#define RS_SDIO_RESP_NONE RS_SDIO_ABI_RESP_NONE
#define RS_SDIO_RESP_R1 RS_SDIO_ABI_RESP_R1
#define RS_SDIO_RESP_R1B RS_SDIO_ABI_RESP_R1B
#define RS_SDIO_RESP_R2 RS_SDIO_ABI_RESP_R2
#define RS_SDIO_RESP_R3 RS_SDIO_ABI_RESP_R3
#define RS_SDIO_RESP_R4 RS_SDIO_ABI_RESP_R4
#define RS_SDIO_RESP_R5 RS_SDIO_ABI_RESP_R5
#define RS_SDIO_RESP_R6 RS_SDIO_ABI_RESP_R6
#define RS_SDIO_RESP_R7 RS_SDIO_ABI_RESP_R7
#define RS_SDIO_DATA_CFG_DIRECTION_BIT RS_SDIO_ABI_DATA_CFG_DIRECTION
#define RS_SDIO_DATA_CFG_WIDTH_LSB RS_SDIO_ABI_DATA_CFG_WIDTH_LSB
#define RS_SDIO_DATA_CFG_DMA_BIT RS_SDIO_ABI_DATA_CFG_DMA
#define RS_SDIO_DATA_CFG_BLOCK_BIT RS_SDIO_ABI_DATA_CFG_BLOCK
#define RS_SDIO_DATA_CFG_FIXED_BIT RS_SDIO_ABI_DATA_CFG_FIXED
#define RS_SDIO_DMA_CTRL_START_BIT RS_SDIO_ABI_DMA_CTRL_START
#define RS_SDIO_DMA_CTRL_ABORT_BIT RS_SDIO_ABI_DMA_CTRL_ABORT
#define RS_SDIO_DESC_OWN_BIT RS_SDIO_ABI_DESC_OWN
#define RS_SDIO_DESC_CHAIN_BIT RS_SDIO_ABI_DESC_CHAIN
#define RS_SDIO_DESC_END_BIT RS_SDIO_ABI_DESC_END
#define RS_SDIO_DESC_IRQ_BIT RS_SDIO_ABI_DESC_IRQ
#define RS_SDIO_DESC_DONE_BIT RS_SDIO_ABI_DESC_DONE
#define RS_SDIO_DESC_ERROR_BIT RS_SDIO_ABI_DESC_ERROR
#define RS_SDIO_IRQ_CMD_DONE (UINT32_C(1) << RS_SDIO_ABI_IRQ_CMD_DONE)
#define RS_SDIO_IRQ_DATA_DONE (UINT32_C(1) << RS_SDIO_ABI_IRQ_DATA_DONE)
#define RS_SDIO_IRQ_DMA_DONE (UINT32_C(1) << RS_SDIO_ABI_IRQ_DMA_DONE)
#define RS_SDIO_IRQ_CMD_ERROR (UINT32_C(1) << RS_SDIO_ABI_IRQ_CMD_ERROR)
#define RS_SDIO_IRQ_DATA_ERROR (UINT32_C(1) << RS_SDIO_ABI_IRQ_DATA_ERROR)
#define RS_SDIO_IRQ_DMA_ERROR (UINT32_C(1) << RS_SDIO_ABI_IRQ_DMA_ERROR)
#define RS_SDIO_IRQ_DAT1 (UINT32_C(1) << RS_SDIO_ABI_IRQ_DAT1)
#define RS_SDIO_IRQ_ABORT (UINT32_C(1) << RS_SDIO_ABI_IRQ_ABORT)
#define RS_SDIO_IRQ_ALL UINT32_C(0x000000FF)
#define RS_SDIO_CAP_SD_MEMORY_V2 (UINT32_C(1) << RS_SDIO_ABI_CAP_SD_MEMORY_V2)
#define RS_SDIO_CAP_SDIO_V2 (UINT32_C(1) << RS_SDIO_ABI_CAP_SDIO_V2)
#define RS_SDIO_CAP_BUS_1BIT (UINT32_C(1) << RS_SDIO_ABI_CAP_BUS_1BIT)
#define RS_SDIO_CAP_BUS_4BIT (UINT32_C(1) << RS_SDIO_ABI_CAP_BUS_4BIT)
#define RS_SDIO_CAP_SDR (UINT32_C(1) << RS_SDIO_ABI_CAP_SDR)
#define RS_SDIO_CAP_SG_DMA (UINT32_C(1) << RS_SDIO_ABI_CAP_SG_DMA)
#define RS_SDIO_CAP_PIO (UINT32_C(1) << RS_SDIO_ABI_CAP_PIO)
#define RS_SDIO_CAP_CRC (UINT32_C(1) << RS_SDIO_ABI_CAP_CRC)
#define RS_SDIO_CAP_MAX_BURST_16 (UINT32_C(1) << RS_SDIO_ABI_CAP_MAX_BURST_16)
#define RS_SDIO_IP_ID_VALUE UINT32_C(0x5344494F)
#define RS_SDIO_IP_VERSION_VALUE UINT32_C(0x00010000)
#define RS_SDIO_CAPABILITY_VALUE UINT32_C(0x000001FF)
#define RS_SDIO_PRESENT_CARD UINT32_C(0x00000001)
#define RS_SDIO_FIFO_WATERMARK_DEFAULT UINT32_C(0x00000001)
#define RS_SDIO_LAST_CMD_MASK UINT32_C(0x0000003F)
#define RS_SDIO_RESPONSE_R2_HIGH_MASK UINT32_C(0x000000FF)
#define RS_SDIO_DEBUG_PIO_VALID UINT32_C(0x00000001)
#define RS_SDIO_DEBUG_PIO_READY UINT32_C(0x00000002)
#define RS_SDIO_HOST_CTRL_ENABLE (UINT32_C(1) << RS_SDIO_HOST_CTRL_ENABLE_BIT)
#define RS_SDIO_HOST_CTRL_ABORT (UINT32_C(1) << RS_SDIO_HOST_CTRL_ABORT_BIT)
#define RS_SDIO_HOST_CTRL_IRQ (UINT32_C(1) << RS_SDIO_HOST_CTRL_IRQ_BIT)
#define RS_SDIO_CLOCK_CTRL_ENABLE (UINT32_C(1) << RS_SDIO_CLOCK_CTRL_ENABLE_BIT)
#define RS_SDIO_CLOCK_CTRL_HALF_PERIOD_MASK UINT32_C(0x00FFFF00)
#define RS_SDIO_BUS_CTRL_WIDTH_MASK UINT32_C(0x00000003)
#define RS_SDIO_CMD_CFG_INDEX_MASK UINT32_C(0x0000003F)
#define RS_SDIO_CMD_CFG_RESP_MASK UINT32_C(0x00000F00)
#define RS_SDIO_CMD_CFG_CRC_CHECK (UINT32_C(1) << RS_SDIO_CMD_CFG_CRC_CHECK_BIT)
#define RS_SDIO_CMD_CFG_INDEX_CHECK (UINT32_C(1) << RS_SDIO_CMD_CFG_INDEX_CHECK_BIT)
#define RS_SDIO_DATA_CFG_DIRECTION (UINT32_C(1) << RS_SDIO_DATA_CFG_DIRECTION_BIT)
#define RS_SDIO_DATA_CFG_WIDTH_MASK UINT32_C(0x00000006)
#define RS_SDIO_DATA_CFG_DMA (UINT32_C(1) << RS_SDIO_DATA_CFG_DMA_BIT)
#define RS_SDIO_DATA_CFG_BLOCK (UINT32_C(1) << RS_SDIO_DATA_CFG_BLOCK_BIT)
#define RS_SDIO_DATA_CFG_FIXED (UINT32_C(1) << RS_SDIO_DATA_CFG_FIXED_BIT)
#define RS_SDIO_DMA_CTRL_START (UINT32_C(1) << RS_SDIO_DMA_CTRL_START_BIT)
#define RS_SDIO_DMA_CTRL_ABORT (UINT32_C(1) << RS_SDIO_DMA_CTRL_ABORT_BIT)
#define RS_SDIO_STATUS_BUSY UINT32_C(0x00000001)
#define RS_SDIO_STATUS_CMD_BUSY UINT32_C(0x00000002)
#define RS_SDIO_STATUS_DATA_BUSY UINT32_C(0x00000004)
#define RS_SDIO_STATUS_DMA_BUSY UINT32_C(0x00000008)
#define RS_SDIO_STATUS_PIO_READY UINT32_C(0x00000010)
#define RS_SDIO_STATUS_DATA_FIXED UINT32_C(0x00000020)
#define RS_SDIO_STATUS_DATA_BLOCK UINT32_C(0x00000040)
#define RS_SDIO_STATUS_DAT1_HIGH UINT32_C(0x00000080)
#define RS_SDIO_STATUS_CLOCK_RUNNING UINT32_C(0x00000100)
#define RS_SDIO_CMD_STATUS_BUSY UINT32_C(0x00000001)
#define RS_SDIO_CMD_STATUS_ERROR UINT32_C(0x00000002)
#define RS_SDIO_CMD_STATUS_TIMEOUT UINT32_C(0x00000004)
#define RS_SDIO_CMD_STATUS_CRC_ERROR UINT32_C(0x00000008)
#define RS_SDIO_CMD_STATUS_INDEX_ERROR UINT32_C(0x00000010)
#define RS_SDIO_DATA_STATUS_BUSY UINT32_C(0x00000001)
#define RS_SDIO_DATA_STATUS_ERROR UINT32_C(0x00000002)
#define RS_SDIO_DATA_STATUS_TIMEOUT UINT32_C(0x00000004)
#define RS_SDIO_DATA_STATUS_CRC_ERROR UINT32_C(0x00000008)
#define RS_SDIO_DATA_STATUS_BUSY_TIMEOUT UINT32_C(0x00000010)
#define RS_SDIO_DMA_STATUS_BUSY UINT32_C(0x00000001)
#define RS_SDIO_DMA_STATUS_DONE UINT32_C(0x00000002)
#define RS_SDIO_DMA_STATUS_ERROR UINT32_C(0x00000004)
#define RS_SDIO_DMA_STATUS_ERROR_CODE_SHIFT 3U
#define RS_SDIO_DMA_STATUS_ERROR_CODE_MASK UINT32_C(0x000007F8)
#define RS_SDIO_ERROR_CMD_CRC UINT32_C(0x00000001)
#define RS_SDIO_ERROR_DATA_CRC UINT32_C(0x00000002)
#define RS_SDIO_ERROR_CMD_TIMEOUT UINT32_C(0x00000004)
#define RS_SDIO_ERROR_DATA_TIMEOUT UINT32_C(0x00000008)
#define RS_SDIO_ERROR_CMD UINT32_C(0x00000010)
#define RS_SDIO_ERROR_DATA UINT32_C(0x00000020)
#define RS_SDIO_ERROR_DMA UINT32_C(0x00000040)
#define RS_SDIO_ERROR_ABORT (UINT32_C(1) << RS_SDIO_ABI_ERROR_ABORT)
#define RS_SDIO_ERROR_ALL UINT32_C(0x000000FF)
#define RS_SDIO_DMA_ERROR_INVALID_START UINT32_C(0x01)
#define RS_SDIO_DMA_ERROR_DESCRIPTOR UINT32_C(0x02)
#define RS_SDIO_DMA_ERROR_AXI UINT32_C(0x04)
#define RS_SDIO_DMA_ERROR_ABORT UINT32_C(0x08)
#define RS_SDIO_DMA_ERROR_PAYLOAD UINT32_C(0x10)
#define RS_SDIO_DMA_ERROR_INTERNAL UINT32_C(0x80)
#define RS_SDIO_DESCRIPTOR_ALIGNMENT UINT32_C(16)
#define RS_SDIO_DESCRIPTOR_SIZE UINT32_C(16)
#define RS_SDIO_MAX_DESCRIPTOR_COUNT UINT32_C(16)
#define RS_SDIO_MAX_BURST_BEATS UINT32_C(16)
#define RS_SDIO_DESCRIPTOR_RESERVED_MASK UINT32_C(0xFFFC0000)
#define RS_SDIO_DESCRIPTOR_WRITEBACK_MASK UINT32_C(0x00030000)
typedef struct {
uint32_t buffer_addr;
uint32_t byte_count;
uint32_t next_addr;
uint32_t control_status;
} rs_sdio_descriptor_t;
_Static_assert(sizeof(rs_sdio_descriptor_t) == 16U, "SDIO descriptor ABI size");
#endif
+2
View File
@@ -15,6 +15,8 @@ typedef enum {
RS_SYSCTRL_PERF_SDRAM_WAIT = 5,
RS_SYSCTRL_PERF_PSRAM_WAIT = 6,
RS_SYSCTRL_PERF_FLASH_WAIT = 7,
RS_SYSCTRL_PERF_SDIO0_WAIT = 8,
RS_SYSCTRL_PERF_SDIO1_WAIT = 9,
} rs_sysctrl_perf_counter_t;
typedef struct {
+4
View File
@@ -24,6 +24,10 @@ rs_status_t rs_perf_snapshot(rs_perf_snapshot_t *snapshot) {
(rs_sysctrl_read_perf_counter(RS_SYSCTRL_PERF_MGMT_WAIT, &snapshot->mgmt_wait) != RS_OK) ||
(rs_sysctrl_read_perf_counter(RS_SYSCTRL_PERF_USER_WAIT, &snapshot->user_wait) != RS_OK) ||
(rs_sysctrl_read_perf_counter(RS_SYSCTRL_PERF_DMA_WAIT, &snapshot->dma_wait) != RS_OK) ||
(rs_sysctrl_read_perf_counter(RS_SYSCTRL_PERF_SDIO0_WAIT, &snapshot->sdio0_wait) !=
RS_OK) ||
(rs_sysctrl_read_perf_counter(RS_SYSCTRL_PERF_SDIO1_WAIT, &snapshot->sdio1_wait) !=
RS_OK) ||
(rs_sysctrl_read_perf_counter(RS_SYSCTRL_PERF_APB4_PERIPH_WAIT,
&snapshot->apb4_periph_wait) != RS_OK) ||
(rs_sysctrl_read_perf_counter(RS_SYSCTRL_PERF_APB4_SYSTEM_WAIT,
+1515
View File
File diff suppressed because it is too large. Load diff
+343
View File
@@ -0,0 +1,343 @@
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <retrosoc/hal/sdio.h>
static bool rs_sdio_response_range_valid(uint32_t high_bit, uint32_t low_bit) {
return (high_bit < 136U) && (low_bit <= high_bit) && ((high_bit - low_bit) < 32U);
}
static rs_status_t rs_sdio_response_bit(const rs_sdio_response_t *response, uint32_t bit,
uint32_t *value) {
uint32_t word;
uint32_t shift;
if ((response == NULL) || (value == NULL) || (bit >= 136U)) {
return RS_EINVAL;
}
word = bit / 32U;
shift = bit % 32U;
*value = (response->words[word] >> shift) & UINT32_C(1);
return RS_OK;
}
static bool rs_sdio_address_range_valid(uintptr_t address, size_t byte_count) {
uintptr_t last;
if ((address == (uintptr_t)0U) || (address > (uintptr_t)UINT32_MAX) || (byte_count == 0U) ||
(byte_count > (size_t)UINT32_MAX)) {
return false;
}
last = address + (uintptr_t)(byte_count - 1U);
return (last >= address) && (last <= (uintptr_t)UINT32_MAX);
}
static bool rs_sdio_descriptor_address_valid(uintptr_t address) {
return (address <= (uintptr_t)UINT32_MAX) &&
((address % (uintptr_t)RS_SDIO_DESCRIPTOR_ALIGNMENT) == (uintptr_t)0U) &&
((address & UINT32_C(0xFFF)) < UINT32_C(0xFF0));
}
rs_status_t rs_sdio_clock_calculate(uint32_t source_clock_hz, uint32_t target_clock_hz,
rs_sdio_clock_t *clock) {
uint64_t denominator;
uint64_t half_period;
if ((source_clock_hz == 0U) || (target_clock_hz == 0U) || (clock == NULL) ||
(target_clock_hz > (source_clock_hz / 2U))) {
return RS_EINVAL;
}
denominator = (uint64_t)target_clock_hz * UINT64_C(2);
half_period = ((uint64_t)source_clock_hz + denominator - UINT64_C(1)) / denominator;
if ((half_period == UINT64_C(0)) || (half_period > (uint64_t)UINT16_MAX)) {
return RS_EINVAL;
}
clock->half_period = (uint16_t)half_period;
clock->requested_hz = target_clock_hz;
clock->actual_hz = source_clock_hz / (2U * (uint32_t)half_period);
if (clock->actual_hz == 0U) {
return RS_EINVAL;
}
return RS_OK;
}
rs_status_t rs_sdio_response_field(const rs_sdio_response_t *response, uint32_t high_bit,
uint32_t low_bit, uint32_t *value) {
uint32_t result = 0U;
uint32_t bit;
if (!rs_sdio_response_range_valid(high_bit, low_bit) || (response == NULL) || (value == NULL)) {
return RS_EINVAL;
}
for (bit = low_bit; bit <= high_bit; bit++) {
uint32_t bit_value;
if (rs_sdio_response_bit(response, bit, &bit_value) != RS_OK) {
return RS_EINVAL;
}
result |= bit_value << (bit - low_bit);
if (bit == high_bit) {
break;
}
}
*value = result;
return RS_OK;
}
rs_status_t rs_sd_memory_address(rs_sd_memory_card_type_t card_type, uint32_t block,
uint32_t *argument) {
if (argument == NULL) {
return RS_EINVAL;
}
switch (card_type) {
case RS_SD_MEMORY_SDSC:
if (block > (UINT32_MAX / RS_SD_MEMORY_BLOCK_SIZE)) {
return RS_EINVAL;
}
*argument = block * RS_SD_MEMORY_BLOCK_SIZE;
break;
case RS_SD_MEMORY_SDHC:
*argument = block;
break;
default:
return RS_EINVAL;
}
return RS_OK;
}
rs_status_t rs_sd_memory_parse_csd(const rs_sdio_response_t *response, rs_sd_memory_info_t *info) {
uint32_t csd_structure;
uint32_t read_bl_len;
uint32_t c_size;
uint32_t c_size_mult;
uint32_t block_length;
uint64_t capacity_bytes;
uint64_t capacity_blocks;
if ((response == NULL) || (info == NULL) ||
(rs_sdio_response_field(response, 127U, 126U, &csd_structure) != RS_OK) ||
(rs_sdio_response_field(response, 83U, 80U, &read_bl_len) != RS_OK)) {
return RS_EINVAL;
}
if (csd_structure == 1U) {
if (rs_sdio_response_field(response, 69U, 48U, &c_size) != RS_OK) {
return RS_EINVAL;
}
capacity_blocks = ((uint64_t)c_size + UINT64_C(1)) << 10U;
capacity_bytes = capacity_blocks * (uint64_t)RS_SD_MEMORY_BLOCK_SIZE;
block_length = RS_SD_MEMORY_BLOCK_SIZE;
info->card_type = RS_SD_MEMORY_SDHC;
info->high_capacity = true;
} else if (csd_structure == 0U) {
if ((read_bl_len > 11U) || (rs_sdio_response_field(response, 73U, 62U, &c_size) != RS_OK) ||
(rs_sdio_response_field(response, 49U, 47U, &c_size_mult) != RS_OK)) {
return RS_EINVAL;
}
block_length = UINT32_C(1) << read_bl_len;
capacity_bytes = ((uint64_t)c_size + UINT64_C(1)) * (UINT64_C(1) << (c_size_mult + 2U)) *
(uint64_t)block_length;
capacity_blocks = capacity_bytes / (uint64_t)RS_SD_MEMORY_BLOCK_SIZE;
info->card_type = RS_SD_MEMORY_SDSC;
info->high_capacity = false;
} else {
return RS_EFORMAT;
}
if ((capacity_blocks == UINT64_C(0)) || (capacity_blocks > (uint64_t)UINT32_MAX) ||
(capacity_bytes == UINT64_C(0))) {
return RS_EINVAL;
}
info->capacity_blocks = (uint32_t)capacity_blocks;
info->block_length = block_length;
return RS_OK;
}
rs_status_t rs_sdio_cmd52_argument(const rs_sdio_cmd52_t *command, uint32_t *argument) {
uint32_t value;
if ((command == NULL) || (argument == NULL) || (command->function > 7U) ||
(command->address > UINT32_C(0x1FFFF))) {
return RS_EINVAL;
}
value =
((uint32_t)command->function << 28U) | (command->address << 9U) | (uint32_t)command->data;
if (command->write) {
value |= UINT32_C(0x80000000);
}
if (command->raw) {
value |= UINT32_C(0x08000000);
}
*argument = value;
return RS_OK;
}
rs_status_t rs_sdio_cmd53_argument(const rs_sdio_cmd53_t *command, uint32_t *argument) {
uint32_t encoded_count;
uint32_t value;
if ((command == NULL) || (argument == NULL) || (command->function > 7U) ||
(command->address > UINT32_C(0x1FFFF)) || (command->count == 0U) ||
(command->count > UINT16_C(512))) {
return RS_EINVAL;
}
encoded_count = (command->count == UINT16_C(512)) ? 0U : (uint32_t)command->count;
value = ((uint32_t)command->function << 28U) | (command->address << 9U) | encoded_count;
if (command->write) {
value |= UINT32_C(0x80000000);
}
if (command->block_mode) {
value |= UINT32_C(0x08000000);
}
if (!command->fixed_address) {
value |= UINT32_C(0x04000000);
}
*argument = value;
return RS_OK;
}
rs_status_t rs_sdio_validate_dma_buffer(const void *buffer, size_t byte_count) {
uintptr_t address;
if (buffer == NULL) {
return RS_EINVAL;
}
address = (uintptr_t)buffer;
if (((address % sizeof(uint32_t)) != 0U) || !rs_sdio_address_range_valid(address, byte_count)) {
return RS_EINVAL;
}
return RS_OK;
}
rs_status_t rs_sdio_descriptor_prepare(rs_sdio_descriptor_t *descriptor, uintptr_t buffer,
size_t byte_count, uintptr_t next, bool end, bool irq) {
uint32_t control;
uintptr_t descriptor_address;
if ((descriptor == NULL) || !rs_sdio_address_range_valid(buffer, byte_count) ||
((buffer % sizeof(uint32_t)) != 0U)) {
return RS_EINVAL;
}
descriptor_address = (uintptr_t)descriptor;
if ((descriptor_address <= (uintptr_t)UINT32_MAX) &&
!rs_sdio_descriptor_address_valid(descriptor_address)) {
return RS_EINVAL;
}
if (end) {
if (next != (uintptr_t)0U) {
return RS_EINVAL;
}
} else if ((next == (uintptr_t)0U) || (next > (uintptr_t)UINT32_MAX) ||
((next % RS_SDIO_DESCRIPTOR_ALIGNMENT) != 0U) ||
!rs_sdio_descriptor_address_valid(next)) {
return RS_EINVAL;
}
control = end ? (UINT32_C(1) << RS_SDIO_DESC_END_BIT) : (UINT32_C(1) << RS_SDIO_DESC_CHAIN_BIT);
if (irq) {
control |= UINT32_C(1) << RS_SDIO_DESC_IRQ_BIT;
}
descriptor->buffer_addr = (uint32_t)buffer;
descriptor->byte_count = (uint32_t)byte_count;
descriptor->next_addr = (uint32_t)next;
descriptor->control_status = control;
return RS_OK;
}
rs_status_t rs_sdio_descriptor_validate(const rs_sdio_descriptor_t *descriptor) {
bool end;
bool chain;
uintptr_t descriptor_address;
if ((descriptor == NULL) || ((descriptor->buffer_addr % sizeof(uint32_t)) != 0U) ||
(descriptor->byte_count == 0U) ||
((descriptor->control_status & RS_SDIO_DESCRIPTOR_RESERVED_MASK) != 0U) ||
((descriptor->control_status & RS_SDIO_DESCRIPTOR_WRITEBACK_MASK) != 0U)) {
return RS_EINVAL;
}
descriptor_address = (uintptr_t)descriptor;
if ((descriptor_address <= (uintptr_t)UINT32_MAX) &&
!rs_sdio_descriptor_address_valid(descriptor_address)) {
return RS_EINVAL;
}
end = (descriptor->control_status & (UINT32_C(1) << RS_SDIO_DESC_END_BIT)) != 0U;
chain = (descriptor->control_status & (UINT32_C(1) << RS_SDIO_DESC_CHAIN_BIT)) != 0U;
if (end) {
if (chain || (descriptor->next_addr != 0U)) {
return RS_EINVAL;
}
} else if (!chain || (descriptor->next_addr == 0U) ||
((descriptor->next_addr % RS_SDIO_DESCRIPTOR_ALIGNMENT) != 0U) ||
!rs_sdio_descriptor_address_valid((uintptr_t)descriptor->next_addr)) {
return RS_EINVAL;
}
return RS_OK;
}
rs_status_t rs_sdio_descriptor_chain_validate(const rs_sdio_descriptor_t *descriptors,
uint16_t count, uint32_t total_bytes) {
uint32_t total = 0U;
uint16_t index;
bool found_end = false;
if ((descriptors == NULL) || (count == 0U) ||
(count > (uint16_t)RS_SDIO_MAX_DESCRIPTOR_COUNT) ||
(((uintptr_t)descriptors % RS_SDIO_DESCRIPTOR_ALIGNMENT) != 0U) ||
((uintptr_t)descriptors > (uintptr_t)UINT32_MAX) ||
!rs_sdio_descriptor_address_valid((uintptr_t)descriptors)) {
return RS_EINVAL;
}
for (index = 0U; index < count; index++) {
const rs_sdio_descriptor_t *descriptor = &descriptors[index];
bool end;
if (rs_sdio_descriptor_validate(descriptor) != RS_OK) {
return RS_EINVAL;
}
if (descriptor->byte_count > (UINT32_MAX - total)) {
return RS_EINVAL;
}
total += descriptor->byte_count;
end = (descriptor->control_status & (UINT32_C(1) << RS_SDIO_DESC_END_BIT)) != 0U;
if (end) {
if (index + 1U != count) {
return RS_EINVAL;
}
found_end = true;
} else {
uintptr_t next = (uintptr_t)&descriptors[index + 1U];
if ((next > (uintptr_t)UINT32_MAX) || !rs_sdio_descriptor_address_valid(next) ||
(descriptor->next_addr != (uint32_t)next)) {
return RS_EINVAL;
}
}
}
if (!found_end || ((total_bytes != 0U) && (total != total_bytes))) {
return RS_EINVAL;
}
return RS_OK;
}
rs_status_t rs_sdio_descriptor_publish(rs_sdio_descriptor_t *descriptor) {
if (rs_sdio_descriptor_validate(descriptor) != RS_OK) {
return RS_EINVAL;
}
rs_sdio_memory_barrier();
descriptor->control_status |= UINT32_C(1) << RS_SDIO_DESC_OWN_BIT;
rs_sdio_memory_barrier();
return RS_OK;
}
rs_status_t rs_sdio_descriptor_publish_chain(rs_sdio_descriptor_t *descriptors, uint16_t count) {
uint16_t index;
if (rs_sdio_descriptor_chain_validate(descriptors, count, 0U) != RS_OK) {
return RS_EINVAL;
}
for (index = 0U; index < count; index++) {
if (rs_sdio_descriptor_publish(&descriptors[index]) != RS_OK) {
return RS_EINVAL;
}
}
return RS_OK;
}
+12
View File
@@ -22,6 +22,10 @@
#define RS_SYSCTRL_PERF_USER_WAIT_HI_OFFSET RS_SOC_SYSCTRL_PERF_USER_WAIT_HI_OFFSET
#define RS_SYSCTRL_PERF_DMA_WAIT_LO_OFFSET RS_SOC_SYSCTRL_PERF_DMA_WAIT_LO_OFFSET
#define RS_SYSCTRL_PERF_DMA_WAIT_HI_OFFSET RS_SOC_SYSCTRL_PERF_DMA_WAIT_HI_OFFSET
#define RS_SYSCTRL_PERF_SDIO0_WAIT_LO_OFFSET RS_SOC_SYSCTRL_PERF_SDIO0_WAIT_LO_OFFSET
#define RS_SYSCTRL_PERF_SDIO0_WAIT_HI_OFFSET RS_SOC_SYSCTRL_PERF_SDIO0_WAIT_HI_OFFSET
#define RS_SYSCTRL_PERF_SDIO1_WAIT_LO_OFFSET RS_SOC_SYSCTRL_PERF_SDIO1_WAIT_LO_OFFSET
#define RS_SYSCTRL_PERF_SDIO1_WAIT_HI_OFFSET RS_SOC_SYSCTRL_PERF_SDIO1_WAIT_HI_OFFSET
#define RS_SYSCTRL_PERF_APB4_PERIPH_WAIT_LO_OFFSET RS_SOC_SYSCTRL_PERF_APB4_PERIPH_WAIT_LO_OFFSET
#define RS_SYSCTRL_PERF_APB4_PERIPH_WAIT_HI_OFFSET RS_SOC_SYSCTRL_PERF_APB4_PERIPH_WAIT_HI_OFFSET
#define RS_SYSCTRL_PERF_APB4_SYSTEM_WAIT_LO_OFFSET RS_SOC_SYSCTRL_PERF_APB4_SYSTEM_WAIT_LO_OFFSET
@@ -84,6 +88,14 @@ static rs_status_t rs_sysctrl_perf_offsets(rs_sysctrl_perf_counter_t counter, ui
*low_offset = RS_SYSCTRL_PERF_DMA_WAIT_LO_OFFSET;
*high_offset = RS_SYSCTRL_PERF_DMA_WAIT_HI_OFFSET;
break;
case RS_SYSCTRL_PERF_SDIO0_WAIT:
*low_offset = RS_SYSCTRL_PERF_SDIO0_WAIT_LO_OFFSET;
*high_offset = RS_SYSCTRL_PERF_SDIO0_WAIT_HI_OFFSET;
break;
case RS_SYSCTRL_PERF_SDIO1_WAIT:
*low_offset = RS_SYSCTRL_PERF_SDIO1_WAIT_LO_OFFSET;
*high_offset = RS_SYSCTRL_PERF_SDIO1_WAIT_HI_OFFSET;
break;
case RS_SYSCTRL_PERF_APB4_PERIPH_WAIT:
*low_offset = RS_SYSCTRL_PERF_APB4_PERIPH_WAIT_LO_OFFSET;
*high_offset = RS_SYSCTRL_PERF_APB4_PERIPH_WAIT_HI_OFFSET;
+3
View File
@@ -54,6 +54,9 @@ the root README and subsystem guides.
- [ip/opipsram.md](ip/opipsram.md) defines the boot-selected OPI/xSPI and
single-clock HyperBus profiles, shared digital PHY, delay-cell boundary,
register ABI, central-DMA use, and commercial signoff limits.
- [ip/sdio-verification.md](ip/sdio-verification.md) records the standalone
SD/SDIO behavioral models, directed coverage, formal bound, and verification
blockers.
- [ip/ps2.md](ip/ps2.md) defines the Mini SoC PS/2 APB, GPIO pad,
interrupt, SDK, and managed-IP integration contract.
- The managed [RTC V2 datasheet](../rtl/managed/clusterip/rtc/doc/datasheet.md)
+127
View File
@@ -0,0 +1,127 @@
# SD/SDIO Host Verification
This document is the verification contract for the standalone
`apb4_sdio` foundation. The protocol contract remains in
[`sdio.md`](sdio.md); this file records evidence and coverage boundaries
without changing the IP ABI.
## Verification components
| Component | Purpose |
| --- | --- |
| `tests/rtl/sdio_native_model.sv` | Native SD memory v2 SDSC/SDHC and SDIO-only function model. |
| `tests/rtl/sdio_axi_memory_responder.sv` | Deterministic 32-bit AXI responder with backpressure, split, and response-error injection. |
| `tests/rtl/sdio_command_tb.sv` | Exact command-frame launch/sample and terminal-bit checks. |
| `tests/rtl/sdio_register_tb.sv` | APB register, PIO consume, empty-read, and W1C checks. |
| `tests/rtl/sdio_standalone_tb.sv` | APB-driven SDHC/SDSC 1-bit/4-bit PIO and DMA, 1/2/3/5-byte tails, 512-byte FIFO streaming, response-token errors, aborts, clock, response, busy, and CRC/timeout paths. |
| `tests/rtl/sdio_sdio_tb.sv` | CMD5, CCCR CMD52 including high-speed capability/enable, CMD53 byte/block fixed/increment, and DAT1 line behavior. |
| `tests/rtl/sdio_dual_tb.sv` | Two independent APB/AXI/pad contexts running concurrently. |
| `tests/rtl/sdio_dma_stress_tb.sv` | 16-beat bursts, 4 KiB splitting, backpressure, AXI errors, descriptor-fetch/read/write-response abort drain, and writeback observation. |
| `rtl/mini/formal/sdio_formal*.sv` | Reduced APB/clock/AXI/descriptor formal harness and properties. |
The models are verification-only and may use timing controls, tasks, and
backing arrays. They do not copy vendor register
tables or proprietary descriptor formats.
## Traceability
| Requirement | Evidence |
| --- | --- |
| 400 kHz command flow | `sdio_standalone_tb` programs half-period 90 at 72 MHz and checks `CLOCK_ACTUAL`. |
| SDSC byte vs SDHC block addressing | `sdio_sdsc_tb` and `sdio_standalone_tb` use the same command with distinct model geometry. |
| 1-bit and 4-bit SDR read/write | `sdio_onebit_tb` plus the 4-bit standalone path; strict token/CRC checks and PIO writes cover both widths. |
| Byte lanes and tails | `sdio_standalone_tb` checks lane-0-first packing and 1/2/3/5-byte tails through PIO and DMA in both directions. |
| PIO FIFO and remaining | `sdio_standalone_tb` streams a complete 512-byte write/read with consecutive APB accesses and only completes on the final payload word. |
| Command/data terminal edges | `sdio_command_tb` and the strict native model reject a missing command end bit or data token; standalone writes verify the final CRC. |
| Consecutive PIO reads | `sdio_register_tb` checks read-consume pulses; standalone tests consume two held words without repetition. |
| CMD12/R1b and DAT0 busy | Standalone command sequence and parameterized card busy interval. |
| CMD5/CMD52/CMD53 | `sdio_sdio_tb`, including CCCR backing and function memory. |
| DAT1 active-low IRQ | `sdio_sdio_tb` checks high-level no-IRQ, low-level sticky IRQ, W1C, stopped SDCLK persistence, and high-to-low re-arm. |
| Programmable delay and fault hooks | Card tasks provide response/data delay plus one-shot CRC and timeout injection; base CRC/error tests exercise the protocol engines. |
| SG chain/writeback | `sdio_dma_stress_tb` runs a bounded two-descriptor chain and checks DONE/OWN writeback. |
| AXI 16 beats/4 KiB split/backpressure/error | `sdio_dma_stress_tb` and the reusable responder. |
| Abort | `sdio_standalone_tb` aborts command, data, and descriptor fetch through HOST_CTRL; `sdio_dma_stress_tb` aborts after payload AR acceptance and during a stalled write response, checking drain and ERROR/OWN writeback. |
| Write response token | `sdio_standalone_tb` injects accepted, CRC-error, and write-error tokens and checks distinct status. |
| W1C/counters/descriptor IRQ | `sdio_register_tb` checks persistent ERROR_STATUS W1C, DEBUG semantic bits, IRQ W1C, and descriptor event behavior; standalone checks timeout event counting. |
| 100 MHz to 50 MHz SDCLK | `sdio_50mhz_tb` with `half_period=1`. |
| Dual-context isolation | `sdio_dual_tb` checks independent status, data, and DAT1 contexts. |
## Formal boundary
`make ... formal-sdio` runs a reduced register/DMA/clock proof through the
repository SymbiYosys/Yosys/Bitwuzla flow. It does **not** claim a complete
native SD host proof. Command/data serializer, card timing, token semantics,
PIO FIFO streaming, and full byte-count termination are simulation-covered
contracts; they are not certified by this reduced formal target.
The checked harness covers:
- APB held-access response reachability and registered response stability;
- AXI address-channel legality, 32-bit alignment, maximum 16 beats, and
no-cross-4-KiB arithmetic;
- conditional payload stability when a valid write beat remains backpressured;
- accepted-read abort drain: RREADY remains asserted until the terminal RLAST
handshake, with an abort-after-AR cover;
- descriptor-count error reachability;
- clock transition phase observation and abort/error event observation;
- sticky-IRQ event and terminal-event covers.
The shallow proof intentionally does not claim a complete native data-block
proof. Full command/data serializer coverage, arbitrary descriptor memory
contents, and two-context formal composition remain simulation/next-stage
targets. The current tractable CI invocation is:
```sh
make CONFIG=configs/ci/ihp130.mk SIMU=IVERILOG SYNTH=YOSYS STA=NONE \
FORMAL=YES FORMAL_SDIO_DEPTH=12 FORMAL_SDIO_COVER_DEPTH=12 formal-sdio
```
## Evidence status
**Simulated:** strict native command framing including the final command end
bit, R1/R4/R5 responses including the response terminal bit, 1-bit/4-bit
data token/payload/final CRC paths, SDSC/SDHC addressing, lane-0-first
1/2/3/5-byte tails, 512-byte PIO FIFO streaming, SDIO function memory,
accepted/CRC-error/write-error tokens, DAT0 busy, active-low DAT1 sticky IRQ
behavior with W1C and stopped SDCLK, AXI split/backpressure/error responses,
both DMA directions, descriptor IRQ/writeback, command/data/descriptor abort
draining, timeout/counter events, dual-instance concurrency, and 50 MHz phase
generation.
**Formally proven at the reduced bound:** APB response stability, AXI
legality/arithmetic, reachable backpressure payload checks, accepted-read abort
RREADY obligation/terminal cover, clock phase transition checks,
descriptor-count error reachability, and abort/error event observation.
Complete native command/data behavior, token decoding, PIO FIFO accounting,
and arbitrary block transfers remain simulation evidence only. The proof does
not certify complete block transfers or board/PHY behavior.
**Planned board/PHY coverage:** 3.3 V pad electrical behavior, package and
board delay, signal-integrity margins, real SDSC/SDHC vendor cards, SDIO
module interoperability, hot removal, UHS/1.8 V, tuning, and long-duration
stress. These require hardware, PHY constraints, or specifications not
present in this repository.
## Production RTL blocker closure
All four production blockers are closed in the current RTL. They have directed
simulation evidence; the reduced formal target covers the applicable APB,
clock, AXI, and abort obligations but is not a full-host proof:
1. **Command/data terminal edges:** command serialization advances after the
sampled edge, holds the final command bit through card observation, and
data write token/CRC terminal holds are phase-safe in both bus widths. The
native model now rejects repaired/shifted command frames and missing tokens.
2. **PIO read consume:** `PIO_DATA` reads require a valid word, return a
registered stable value, and emit one consume pulse per APB read. The
register and standalone tests exercise consecutive words.
3. **DMA abort drain:** an abort latch suppresses downstream payload delivery,
keeps RREADY asserted after AR acceptance, drains response/RLAST, records
response/last errors, and only then performs descriptor error writeback and
terminalizes. Simulation and formal cover the abort-after-AR case.
4. **DAT1 host IRQ:** the pad is double-synchronized in `clk_i`, qualified
only while DAT1 is released and the data path is idle, and active-low
assertion is latched into IRQ bit 6 with mask/W1C behavior independent of
SDCLK.
These tests are verification evidence, not waivers or certification claims.
+182
View File
@@ -0,0 +1,182 @@
# Standalone SD/SDIO Host Foundation
This document is the contract for the standalone `apb4_sdio` IP and its dual
Mini SoC integration. The two management windows are `APB4_SDIO0` at
`0x1000F000` and `APB4_SDIO1` at `0x10015000`; both are public to the
management core and denied to the user core.
## Scope and maturity
The host is a single-clock-domain, single-slot native SD host. `clk_i` is the
only logic clock. `sdio.sck_o` is a registered output and is never used as a
clock for state. Low/high phase clock-enables update outputs and sample inputs.
The wrapper has one APB4 management port, one 32-bit native AXI4 master port,
and one `sdio_if.dut` pad interface. Instances are independent when integrated.
The foundation contains generic command, data, CRC, phase, PIO, and bounded
scatter-gather DMA building blocks. Card enumeration and card-type policy
remain software driven.
## Supported behavior
- Native 1-bit and 4-bit SDR transfers.
- Generic six-bit command index and 32-bit argument.
- No response and R1, R1b, R2, R3, R4, R5, R6, and R7 response framing.
- Command index/end checks and CRC7 checks where the response type defines CRC.
R3 and R4 do not force a CRC check.
- Data token, payload, CRC16, write response, DAT0 busy, and command/data/busy
timeout state are present in the protocol engines.
- Write response tokens are checked in full: accepted continues to DAT0 busy,
CRC-error and write-error tokens terminate with distinct error semantics,
and only the accepted path uses `TIMEOUT_BUSY`.
- `TIMEOUT_CMD` covers command launch and response framing, `TIMEOUT_DATA`
covers data tokens, payload, CRC, and write-response tokens, and
`TIMEOUT_BUSY` covers only R1b/DAT0 busy phases.
- The 4-bit serializer keeps independent CRC16 state for each DAT lane; CRC
errors remain sticky until the next transaction or reset.
- PIO word transfers use a 32-bit word and four byte strobes. Lane 0
(`word[7:0]`/`WSTRB[0]`) is the first wire byte; each byte is serialized
MSB first. Descriptor DMA uses the same stream contract and supports a
byte-granular final word in both directions.
- The intended software protocols are SD Memory v2 SDSC/SDHC and SDIO v2
CMD5/CMD52/CMD53. SDSC byte addressing and SDHC block addressing are software
policy, not hard-coded card enumeration in the RTL.
- SDIO high-speed initialization is software sequenced: CCCR speed capability
is read, the high-speed enable bit is written and read back, and only a
verified enable permits the host clock/info flag to change.
- The capability word advertises SD Memory v2, SDIO v2, 1/4-bit SDR, CRC, PIO,
SG DMA, and 16-beat maximum bursts.
The implementation does not claim SD Association compliance or certification.
Normative specifications and any required licensing must be obtained and
reviewed separately before a compliance claim.
## Clock and I/O boundary
`CLOCK_CTRL[0]` enables the host clock and `CLOCK_CTRL[23:8]` supplies the
half-period in `clk_i` cycles. A value of zero is treated as one cycle. The
actual frequency is approximately:
```
sdclk_hz = clk_i_hz / (2 * half_period)
```
Clock stop completes a high phase and stops only low. Command/data output
changes occur on launch phase enables; command/data inputs are consumed on
sample phase enables. The current SoC profile is 72 MHz, so its evidence
boundary is 400 kHz, 24 MHz, and 36 MHz integer-divider modes. A 50 MHz
SDCLK requires a separately validated `clk_i >= 100 MHz` environment. No
statement about pad, package, board, voltage, or I/O timing signoff follows
from the RTL clock test.
The MVP is fixed-present and 3.3 V SDR. Card-detect, write-protect,
power-enable, hot-remove recovery, UHS, 1.8 V switching, DDR, eMMC, MMC,
SPI-mode operation, 8-bit mode, tuning/DLL, CQE, and combo-card policy are
unsupported.
## APB4 register contract
The window is 4 KiB and uses the offsets in `rtl/ip/storage/sdio_define.svh`.
An APB access is accepted in the enable phase and completes with a registered
one-cycle `PREADY`. Reads have no side effects. Unknown offsets, out-of-window
addresses, malformed start writes, and protected configuration writes while
active return `PSLVERR`. Command, data, and DMA starts are write pulses.
`IRQ_STATUS` and `ERROR_STATUS` are write-one-to-clear. `IRQ` is the masked
status reduction.
`ERROR_STATUS` is event-latched: command/data CRC and timeout, AXI, protocol,
and abort events set bits once and remain set until software writes one to the
corresponding bit. Level/status signals are not continuously re-ORed after a
clear. `DEBUG[0]` is PIO data valid and `DEBUG[1]` is PIO transmit ready.
`STALL_COUNT` counts cycles in which a valid PIO/DMA stream word is held
without a ready consumer.
| Offset | Register | Access |
| ---: | --- | --- |
| 0x000 | IP_ID | RO |
| 0x004 | IP_VERSION | RO |
| 0x008 | CAPABILITY | RO |
| 0x00c | HOST_CTRL | RW; enable, abort, IRQ enable |
| 0x010 | CLOCK_CTRL | RW; enable and half-period |
| 0x014 | CLOCK_ACTUAL | RO |
| 0x018 | BUS_CTRL | RW; 1-bit/4-bit |
| 0x01c-0x024 | TIMEOUT_CMD/DATA/BUSY | RW |
| 0x028-0x02c | STATUS/PRESENT | RO |
| 0x040-0x060 | CMD_ARG, CMD_CFG, CMD_START, CMD_STATUS, RESP0..RESP4 | mixed |
| 0x080-0x098 | block/data/PIO/FIFO controls | mixed |
| 0x0c0-0x0dc | descriptor and DMA status | mixed |
| 0x100-0x124 | IRQ, error, last-command, and counters | mixed |
R2 is 136 bits and is exposed in `RESP0..RESP4`; the upper register contains
the upper eight response bits in its low byte. Reads are side-effect free
except for `PIO_DATA`: a valid `PIO_DATA` read consumes exactly one held word,
and an empty `PIO_DATA` read completes with `PSLVERR`.
## Descriptor ABI
Each descriptor is 16-byte aligned and contains four little-endian 32-bit
words:
| Word | Name | Meaning |
| ---: | --- | --- |
| 0 | BUFFER_ADDR | 32-bit aligned memory buffer |
| 1 | BYTE_COUNT | positive byte count |
| 2 | NEXT_ADDR | 16-byte aligned next descriptor; zero at END |
| 3 | CONTROL_STATUS | OWN, CHAIN, END, IRQ; DONE and ERROR writeback |
`OWN` is bit 0, `CHAIN` bit 1, `END` bit 2, `IRQ` bit 3, `DONE` bit 16, and
`ERROR` bit 17. Software publishes the descriptor and buffer, executes its
platform memory barrier, and sets OWN last. Hardware clears OWN and writes
DONE or ERROR after payload completion or a precise failure. `DESC_COUNT` is
an inclusive hardware traversal bound; zero length, unaligned descriptor or
buffer, malformed chain, loop, early END, total-length mismatch, and an
unowned active descriptor are errors. A chain must terminate with END before
the bound.
The descriptor `IRQ` flag raises the visible `IRQ_STATUS.DMA_DONE` event when
that descriptor is written back; the same bit is also raised for terminal DMA
completion. Descriptor IRQ events obey the normal mask and W1C rules.
AXI payload bursts are INCR, 32-bit, at most 16 beats, and never cross 4 KiB.
Read response errors, write response errors, bad IDs, and RLAST mismatches are
reported. A descriptor fetch address with low 12-bit value `0xFF0` or above
is rejected so the 16-byte fetch remains within one 4 KiB region. A final
descriptor word may use any non-zero contiguous low-byte WSTRB pattern that
exactly matches its remaining byte count. The initial buffer address must be
aligned; software must use a bounce buffer for an unaligned head. Abort stops
new work, drains an accepted AXI transaction, clears OWN, and completes with
ERROR.
There are no multiple IDs or outstanding transactions.
## Software sequencing and integration contract
1. Reset the host, program clock/width/timeouts, and leave host disabled while
changing protected configuration.
2. Program command and response policy, issue `CMD_START`, and inspect
`CMD_STATUS` and response words.
3. Program block/data policy. For PIO, write/read `PIO_DATA`; for DMA, fill a
descriptor chain, publish OWN last, program `DESC_BASE/DESC_COUNT`, set
`DATA_CFG.DMA`, and issue one `DATA_START` operation. `DATA_START` starts
DMA and the card data engine atomically. `DMA_CTRL.START` is retained as a
compatibility alias for the same coordinated operation and must not be
used as an independent memory-transfer start.
4. Enable only the desired IRQ bits and clear sticky status with W1C.
5. On timeout, CRC, protocol, or AXI error, issue abort/reset, inspect the
first error snapshot, and re-enumerate in software.
Each wrapper's AXI interface is a native 32-bit master. The SoC assigns
`sdio0` and `sdio1` to AXI master IDs 3 and 4, respectively; management,
user, and central DMA retain IDs 0, 1, and 2. The per-master wait counters
are snapshotted by SystemCtrl at `0x08C/0x090` and `0x094/0x098`.
`sdio0` remains on GPIO15..20 ALT0. `sdio1` uses dedicated
`sdio1_clk_o_pad`, `sdio1_cmd_io_pad`, and
`sdio1_dat0_io_pad` through `sdio1_dat3_io_pad` pads.
The canonical pin map leaves these pads unbound in the FPGA profile until a
board package and 3.3 V bank assignment are approved; the existing LVCMOS18
constraints are not evidence for SDIO electrical compliance.
The standalone IP does not allocate memory, perform cache maintenance,
implement an IOMMU/firewall, or provide a hosted/runtime C API. The SoC
integration only supplies the two native AXI/APB/IRQ/pad paths; the SDK driver
is maintained by a separate workstream.
+6 -3
View File
@@ -12,7 +12,8 @@ the implementation is separated into `sysctrl_reg`, `sysctrl_core`, and
`sysctrl_if` is the SoC-facing contract. It drives the selected user core,
per-core reset mask, user-bus enable, selected user IP, performance
enable/clear, and sticky terminal test result. It receives user-bus idle,
fault metadata, eight wait counters, and asynchronous RTC wake. `sysctrl_core`
fault metadata, ten wait counters (including independent SDIO0 and SDIO1
master wait counters), and asynchronous RTC wake. `sysctrl_core`
synchronizes RTC wake with the Common `cdc_sync` primitive before storing its
sticky state.
@@ -51,10 +52,12 @@ offset macros.
| `0x01C` | `PLL_STATUS` | RO | Active profile, valid, busy, error/error reason, safe clock, lock, and capability. |
| `0x020` | `USER_CORE_RESET` | RW | Per-user-core active-high reset mask. All ones stops the user bus; a one-hot deassertion starts the selected core. |
| `0x024` | `USER_CORE_STATUS` | RW1C/RO | Selected core, user-bus enabled/idle/draining, and bit 11 configuration-error W1C. |
| `0x028` | `FAULT_MASTER` | RO | First fault master. |
| `0x028` | `FAULT_MASTER` | RO | First fault master, a three-bit AXI master ID (0..4). |
| `0x02C` | `FAULT_DETAIL` | RO | First raw RIB response code. |
| `0x040` | `PERF_CTRL` | RW | Bit 0 enable, bit 1 clear pulse, bit 2 snapshot pulse. |
| `0x044`-`0x080` | `PERF_*_WAIT_{LO,HI}` | RO | Snapshot of management, user, DMA, APB4, APB, SDRAM, PSRAM, and flash wait counters. |
| `0x044`-`0x098` | `PERF_*_WAIT_{LO,HI}` | RO | Snapshot of management, user, central DMA, SDIO0, SDIO1, APB4, SDRAM, PSRAM, and flash wait counters. |
| `0x08C`/`0x090` | `PERF_SDIO0_WAIT_LO/HI` | RO | Independent SDIO0 AXI master wait snapshot. |
| `0x094`/`0x098` | `PERF_SDIO1_WAIT_LO/HI` | RO | Independent SDIO1 AXI master wait snapshot. |
| `0x084` | `TEST_STATUS` | RW-once/RO | Full-word write with bit 31 set records sticky done, bit 0 pass, and bits `[15:8]` result code. |
| `0x088` | `RTC_WAKE_STATUS` | RW1C/RO | Bit 0 synchronized live wake; bit 1 sticky wake, cleared by writing one to bit 1. |
+3
View File
@@ -10,6 +10,9 @@ include. Before invoking an FPGA tool, generate the selected profile with
`build/<variant>/generated/pin_map/rtl` to the tool include path. Logical pad
names and wrapper bindings are owned by `rtl/mini/pin_map/pin_map.json`;
package locations remain in the board `.xdc` file.
The dedicated SDIO1 pads are intentionally left unbound by the FPGA profile
until a board package location and 3.3 V I/O-bank assignment are approved;
the existing LVCMOS18 constraints do not establish SDIO electrical support.
FPGA constraints are board-specific implementation inputs, not generic ASIC
RTL. Validate changes with the relevant FPGA tool flow and hardware target; do
+2 -1
View File
@@ -88,5 +88,6 @@ verification contract are documented in
The Mini SoC APB4 platform block is `apb4_system` in `rtl/mini/top`. It owns
archinfo, RTC, watchdog, PWM, PS/2, RNG, CRC, and the user-IP APB window.
`apb4_periph` remains the APB4 peripheral container. Topology generation
and the `soc_apb4_system_fabric.svh` include are documented in
includes both management-only native SDIO hosts (`sdio0` on GPIO15..20 ALT0
and `sdio1` on dedicated pads), and the `soc_apb4_system_fabric.svh` include is documented in
[Mini SoC Topology](mini/integration/README.md).
+24 -2
View File
@@ -46,6 +46,14 @@ module sysctrl_core (
localparam sysctrl_offset_t PerfUserWaitHi = sysctrl_offset_t'(`APB4_SYSCTRL__PERF_USER_WAIT_HI);
localparam sysctrl_offset_t PerfDmaWaitLo = sysctrl_offset_t'(`APB4_SYSCTRL__PERF_DMA_WAIT_LO);
localparam sysctrl_offset_t PerfDmaWaitHi = sysctrl_offset_t'(`APB4_SYSCTRL__PERF_DMA_WAIT_HI);
localparam sysctrl_offset_t PerfSdio0WaitLo =
sysctrl_offset_t'(`APB4_SYSCTRL__PERF_SDIO0_WAIT_LO);
localparam sysctrl_offset_t PerfSdio0WaitHi =
sysctrl_offset_t'(`APB4_SYSCTRL__PERF_SDIO0_WAIT_HI);
localparam sysctrl_offset_t PerfSdio1WaitLo =
sysctrl_offset_t'(`APB4_SYSCTRL__PERF_SDIO1_WAIT_LO);
localparam sysctrl_offset_t PerfSdio1WaitHi =
sysctrl_offset_t'(`APB4_SYSCTRL__PERF_SDIO1_WAIT_HI);
localparam sysctrl_offset_t PerfApb4PeriphWaitLo =
sysctrl_offset_t'(`APB4_SYSCTRL__PERF_APB4_PERIPH_WAIT_LO);
localparam sysctrl_offset_t PerfApb4PeriphWaitHi =
@@ -90,11 +98,13 @@ module sysctrl_core (
logic [2:0] s_fault_detail_d, s_fault_detail_q;
logic [31:0] s_fault_addr_d, s_fault_addr_q;
logic [31:0] s_fault_count_d, s_fault_count_q;
logic [1:0] s_fault_master_d, s_fault_master_q;
logic [2:0] s_fault_master_d, s_fault_master_q;
logic s_perf_en_d, s_perf_en_q;
logic [63:0] s_perf_mgmt_wait_d, s_perf_mgmt_wait_q;
logic [63:0] s_perf_user_wait_d, s_perf_user_wait_q;
logic [63:0] s_perf_dma_wait_d, s_perf_dma_wait_q;
logic [63:0] s_perf_sdio0_wait_d, s_perf_sdio0_wait_q;
logic [63:0] s_perf_sdio1_wait_d, s_perf_sdio1_wait_q;
logic [63:0] s_perf_apb4_periph_wait_d, s_perf_apb4_periph_wait_q;
logic [63:0] s_perf_apb4_system_wait_d, s_perf_apb4_system_wait_q;
logic [63:0] s_perf_sdram_wait_d, s_perf_sdram_wait_q;
@@ -298,6 +308,8 @@ module sysctrl_core (
s_perf_mgmt_wait_d = s_perf_mgmt_wait_q;
s_perf_user_wait_d = s_perf_user_wait_q;
s_perf_dma_wait_d = s_perf_dma_wait_q;
s_perf_sdio0_wait_d = s_perf_sdio0_wait_q;
s_perf_sdio1_wait_d = s_perf_sdio1_wait_q;
s_perf_apb4_periph_wait_d = s_perf_apb4_periph_wait_q;
s_perf_apb4_system_wait_d = s_perf_apb4_system_wait_q;
s_perf_sdram_wait_d = s_perf_sdram_wait_q;
@@ -310,6 +322,8 @@ module sysctrl_core (
s_perf_mgmt_wait_d = sysctrl.perf_mgmt_wait_i;
s_perf_user_wait_d = sysctrl.perf_user_wait_i;
s_perf_dma_wait_d = sysctrl.perf_dma_wait_i;
s_perf_sdio0_wait_d = sysctrl.perf_sdio0_wait_i;
s_perf_sdio1_wait_d = sysctrl.perf_sdio1_wait_i;
s_perf_apb4_periph_wait_d = sysctrl.perf_apb4_periph_wait_i;
s_perf_apb4_system_wait_d = sysctrl.perf_apb4_system_wait_i;
s_perf_sdram_wait_d = sysctrl.perf_sdram_wait_i;
@@ -370,6 +384,8 @@ module sysctrl_core (
s_perf_mgmt_wait_q <= '0;
s_perf_user_wait_q <= '0;
s_perf_dma_wait_q <= '0;
s_perf_sdio0_wait_q <= '0;
s_perf_sdio1_wait_q <= '0;
s_perf_apb4_periph_wait_q <= '0;
s_perf_apb4_system_wait_q <= '0;
s_perf_sdram_wait_q <= '0;
@@ -406,6 +422,8 @@ module sysctrl_core (
s_perf_mgmt_wait_q <= s_perf_mgmt_wait_d;
s_perf_user_wait_q <= s_perf_user_wait_d;
s_perf_dma_wait_q <= s_perf_dma_wait_d;
s_perf_sdio0_wait_q <= s_perf_sdio0_wait_d;
s_perf_sdio1_wait_q <= s_perf_sdio1_wait_d;
s_perf_apb4_periph_wait_q <= s_perf_apb4_periph_wait_d;
s_perf_apb4_system_wait_q <= s_perf_apb4_system_wait_d;
s_perf_sdram_wait_q <= s_perf_sdram_wait_d;
@@ -451,7 +469,7 @@ module sysctrl_core (
{(8 - `USER_CORESEL_WIDTH) {1'b0}},
s_sysctrl_coresel_q
};
FaultMaster: read_data_o = {30'd0, s_fault_master_q};
FaultMaster: read_data_o = {29'd0, s_fault_master_q};
FaultDetail: read_data_o = {29'd0, s_fault_detail_q};
PerfCtrl: read_data_o = {31'd0, s_perf_en_q};
PerfMgmtWaitLo: read_data_o = s_perf_mgmt_wait_q[31:0];
@@ -460,6 +478,10 @@ module sysctrl_core (
PerfUserWaitHi: read_data_o = s_perf_user_wait_q[63:32];
PerfDmaWaitLo: read_data_o = s_perf_dma_wait_q[31:0];
PerfDmaWaitHi: read_data_o = s_perf_dma_wait_q[63:32];
PerfSdio0WaitLo: read_data_o = s_perf_sdio0_wait_q[31:0];
PerfSdio0WaitHi: read_data_o = s_perf_sdio0_wait_q[63:32];
PerfSdio1WaitLo: read_data_o = s_perf_sdio1_wait_q[31:0];
PerfSdio1WaitHi: read_data_o = s_perf_sdio1_wait_q[63:32];
PerfApb4PeriphWaitLo: read_data_o = s_perf_apb4_periph_wait_q[31:0];
PerfApb4PeriphWaitHi: read_data_o = s_perf_apb4_periph_wait_q[63:32];
PerfApb4SystemWaitLo: read_data_o = s_perf_apb4_system_wait_q[31:0];
+4
View File
@@ -26,6 +26,10 @@
`define APB4_SYSCTRL__PERF_USER_WAIT_HI `SOC_SYSCTRL_PERF_USER_WAIT_HI_OFFSET
`define APB4_SYSCTRL__PERF_DMA_WAIT_LO `SOC_SYSCTRL_PERF_DMA_WAIT_LO_OFFSET
`define APB4_SYSCTRL__PERF_DMA_WAIT_HI `SOC_SYSCTRL_PERF_DMA_WAIT_HI_OFFSET
`define APB4_SYSCTRL__PERF_SDIO0_WAIT_LO `SOC_SYSCTRL_PERF_SDIO0_WAIT_LO_OFFSET
`define APB4_SYSCTRL__PERF_SDIO0_WAIT_HI `SOC_SYSCTRL_PERF_SDIO0_WAIT_HI_OFFSET
`define APB4_SYSCTRL__PERF_SDIO1_WAIT_LO `SOC_SYSCTRL_PERF_SDIO1_WAIT_LO_OFFSET
`define APB4_SYSCTRL__PERF_SDIO1_WAIT_HI `SOC_SYSCTRL_PERF_SDIO1_WAIT_HI_OFFSET
`define APB4_SYSCTRL__PERF_APB4_PERIPH_WAIT_LO `SOC_SYSCTRL_PERF_APB4_PERIPH_WAIT_LO_OFFSET
`define APB4_SYSCTRL__PERF_APB4_PERIPH_WAIT_HI `SOC_SYSCTRL_PERF_APB4_PERIPH_WAIT_HI_OFFSET
`define APB4_SYSCTRL__PERF_APB4_SYSTEM_WAIT_LO `SOC_SYSCTRL_PERF_APB4_SYSTEM_WAIT_LO_OFFSET
+5 -1
View File
@@ -9,13 +9,15 @@ interface sysctrl_if ();
logic user_bus_enable_o;
logic user_bus_idle_i;
logic fault_access_i;
logic [ 1:0] fault_master_i;
logic [ 2:0] fault_master_i;
logic [ 2:0] fault_code_i;
logic perf_enable_o;
logic perf_clear_o;
logic [ 63:0] perf_mgmt_wait_i;
logic [ 63:0] perf_user_wait_i;
logic [ 63:0] perf_dma_wait_i;
logic [ 63:0] perf_sdio0_wait_i;
logic [ 63:0] perf_sdio1_wait_i;
logic [ 63:0] perf_apb4_periph_wait_i;
logic [ 63:0] perf_apb4_system_wait_i;
logic [ 63:0] perf_sdram_wait_i;
@@ -35,6 +37,8 @@ interface sysctrl_if ();
input perf_mgmt_wait_i,
input perf_user_wait_i,
input perf_dma_wait_i,
input perf_sdio0_wait_i,
input perf_sdio1_wait_i,
input perf_apb4_periph_wait_i,
input perf_apb4_system_wait_i,
input perf_sdram_wait_i,
+203 -48
View File
@@ -1,62 +1,217 @@
// Copyright (c) 2023-2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// Copyright (c) 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:
// 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.
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
interface sdio_if ();
logic sck_o;
logic cmd_oe_o;
logic cmd_di_i;
logic cmd_do_o;
logic [3:0] dat_oe_o;
logic [3:0] dat_di_i;
logic [3:0] dat_do_o;
logic irq_o;
`timescale 1ns / 1ps
modport dut(
output sck_o,
output cmd_oe_o,
input cmd_di_i,
output cmd_do_o,
output dat_oe_o,
input dat_di_i,
output dat_do_o,
output irq_o
);
endinterface
module apb4_sdio (
// verilog_format: off -- preserve reviewed column alignment
input logic clk_i,
input logic rst_n_i,
apb4_if.slave apb4,
sdio_if.dut sdio
module apb4_sdio #(
parameter int InputClockHz = 72_000_000,
parameter int AddrWidth = 32,
parameter int DataWidth = 32,
parameter int DescCount = 16
) (
// verilog_format: off -- preserve the APB, AXI, and pad column alignment
input logic clk_i,
input logic rst_n_i,
apb4_if.slave apb4,
axi4_if.master dma_axi4,
sdio_if.dut sdio
// verilog_format: on
);
logic s_busy;
logic s_host_enable;
logic s_host_irq_enable;
logic s_clock_enable;
logic [ 15:0] s_half_period;
logic [ 1:0] s_bus_width;
logic [ 31:0] s_timeout_cmd;
logic [ 31:0] s_timeout_data;
logic [ 31:0] s_timeout_busy;
logic [ 5:0] s_cmd_index;
logic [ 31:0] s_cmd_arg;
sdio_pkg::sdio_resp_type_e s_cmd_resp_type;
logic s_cmd_crc_check;
logic s_cmd_index_check;
logic s_cmd_start;
logic [ 15:0] s_block_size;
logic [ 15:0] s_block_count;
logic s_data_direction;
logic s_data_dma_enable;
logic s_data_block_mode;
logic s_data_fixed_addr;
logic s_data_start;
logic s_pio_wvalid;
logic [ 31:0] s_pio_wdata;
logic [ 3:0] s_pio_wstrb;
logic [ 31:0] s_desc_base;
logic [ 15:0] s_desc_count;
logic s_dma_start;
logic s_dma_abort;
logic s_host_abort;
logic s_irq_dat1_enable;
logic s_irq;
logic [ 31:0] s_status;
logic [ 31:0] s_clock_actual;
logic [ 31:0] s_cmd_status;
logic [135:0] s_response;
logic [ 31:0] s_data_status;
logic [ 31:0] s_fifo_status;
logic [ 31:0] s_dma_status;
logic [ 31:0] s_current_desc;
logic [ 31:0] s_bytes_done;
logic [ 31:0] s_dma_err_addr;
logic [ 31:0] s_dma_err;
logic [ 31:0] s_err_stat;
logic [ 5:0] s_last_cmd;
logic [ 31:0] s_crc_err_count;
logic [ 31:0] s_timeout_count;
logic [ 31:0] s_axi_err_count;
logic [ 31:0] s_stall_count;
logic [ 7:0] s_irq_event;
logic [ 31:0] s_pio_rdata;
logic s_pio_valid;
logic s_pio_ready;
logic s_pio_read_consume;
sdio_if u_sdio_core_if ();
// This placeholder accepts no APB4 requests (ready remains low) and reports
// no errors; all SDIO outputs are deliberately held inactive.
logic s_dummy_clk;
logic s_dummy_rst_n;
assign u_sdio_core_if.cmd_di_i = sdio.cmd_di_i;
assign u_sdio_core_if.dat_di_i = sdio.dat_di_i;
assign sdio.sck_o = u_sdio_core_if.sck_o;
assign sdio.cmd_oe_o = u_sdio_core_if.cmd_oe_o;
assign sdio.cmd_do_o = u_sdio_core_if.cmd_do_o;
assign sdio.dat_oe_o = u_sdio_core_if.dat_oe_o;
assign sdio.dat_do_o = u_sdio_core_if.dat_do_o;
assign s_dummy_clk = clk_i;
assign s_dummy_rst_n = rst_n_i;
// apb4
assign apb4.prdata = '0;
assign apb4.pready = '0;
assign apb4.pslverr = 1'b0;
// sdio
assign sdio.sck_o = '0;
assign sdio.cmd_oe_o = '0;
assign sdio.cmd_do_o = '0;
assign sdio.dat_oe_o = '0;
assign sdio.dat_do_o = '0;
assign sdio.irq_o = '0;
sdio_reg u_sdio_reg (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.busy_i (s_busy),
.status_i (s_status),
.clock_actual_i (s_clock_actual),
.cmd_status_i (s_cmd_status),
.response_i (s_response),
.data_status_i (s_data_status),
.fifo_status_i (s_fifo_status),
.dma_status_i (s_dma_status),
.current_desc_i (s_current_desc),
.bytes_done_i (s_bytes_done),
.dma_error_addr_i (s_dma_err_addr),
.dma_error_i (s_dma_err),
.error_status_i (s_err_stat),
.last_cmd_i (s_last_cmd),
.crc_error_count_i (s_crc_err_count),
.timeout_count_i (s_timeout_count),
.axi_error_count_i (s_axi_err_count),
.stall_count_i (s_stall_count),
.irq_event_i (s_irq_event),
.pio_rdata_i (s_pio_rdata),
.pio_valid_i (s_pio_valid),
.pio_ready_i (s_pio_ready),
.pio_read_consume_o(s_pio_read_consume),
.host_enable_o (s_host_enable),
.host_irq_enable_o (s_host_irq_enable),
.clock_enable_o (s_clock_enable),
.half_period_o (s_half_period),
.bus_width_o (s_bus_width),
.timeout_cmd_o (s_timeout_cmd),
.timeout_data_o (s_timeout_data),
.timeout_busy_o (s_timeout_busy),
.cmd_index_o (s_cmd_index),
.cmd_arg_o (s_cmd_arg),
.cmd_resp_type_o (s_cmd_resp_type),
.cmd_crc_check_o (s_cmd_crc_check),
.cmd_index_check_o (s_cmd_index_check),
.cmd_start_o (s_cmd_start),
.block_size_o (s_block_size),
.block_count_o (s_block_count),
.data_direction_o (s_data_direction),
.data_dma_enable_o (s_data_dma_enable),
.data_block_mode_o (s_data_block_mode),
.data_fixed_addr_o (s_data_fixed_addr),
.data_start_o (s_data_start),
.pio_wvalid_o (s_pio_wvalid),
.pio_wdata_o (s_pio_wdata),
.pio_wstrb_o (s_pio_wstrb),
.desc_base_o (s_desc_base),
.desc_count_o (s_desc_count),
.dma_start_o (s_dma_start),
.dma_abort_o (s_dma_abort),
.host_abort_o (s_host_abort),
.irq_dat1_enable_o (s_irq_dat1_enable),
.irq_o (s_irq),
.apb4 (apb4)
);
sdio_core #(
.InputClockHz(InputClockHz),
.AddrWidth (AddrWidth),
.DataWidth (DataWidth),
.DescCount (DescCount)
) u_sdio_core (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.host_enable_i (s_host_enable),
.host_irq_enable_i (s_host_irq_enable),
.clock_enable_i (s_clock_enable),
.half_period_i (s_half_period),
.bus_width_i (s_bus_width),
.timeout_cmd_i (s_timeout_cmd),
.timeout_data_i (s_timeout_data),
.timeout_busy_i (s_timeout_busy),
.cmd_index_i (s_cmd_index),
.cmd_arg_i (s_cmd_arg),
.cmd_resp_type_i (s_cmd_resp_type),
.cmd_crc_check_i (s_cmd_crc_check),
.cmd_index_check_i (s_cmd_index_check),
.cmd_start_i (s_cmd_start),
.block_size_i (s_block_size),
.block_count_i (s_block_count),
.data_direction_i (s_data_direction),
.data_dma_enable_i (s_data_dma_enable),
.data_block_mode_i (s_data_block_mode),
.data_fixed_addr_i (s_data_fixed_addr),
.data_start_i (s_data_start),
.pio_wvalid_i (s_pio_wvalid),
.pio_wdata_i (s_pio_wdata),
.pio_wstrb_i (s_pio_wstrb),
.desc_base_i (s_desc_base),
.desc_count_i (s_desc_count),
.dma_start_i (s_dma_start),
.dma_abort_i (s_dma_abort),
.host_abort_i (s_host_abort),
.irq_dat1_enable_i (s_irq_dat1_enable),
.status_o (s_status),
.clock_actual_o (s_clock_actual),
.cmd_status_o (s_cmd_status),
.response_o (s_response),
.data_status_o (s_data_status),
.fifo_status_o (s_fifo_status),
.dma_status_o (s_dma_status),
.current_desc_o (s_current_desc),
.bytes_done_o (s_bytes_done),
.dma_error_addr_o (s_dma_err_addr),
.dma_error_o (s_dma_err),
.error_status_o (s_err_stat),
.last_cmd_o (s_last_cmd),
.crc_error_count_o (s_crc_err_count),
.timeout_count_o (s_timeout_count),
.axi_error_count_o (s_axi_err_count),
.stall_count_o (s_stall_count),
.irq_event_o (s_irq_event),
.pio_rdata_o (s_pio_rdata),
.pio_valid_o (s_pio_valid),
.pio_ready_o (s_pio_ready),
.pio_read_consume_i(s_pio_read_consume),
.busy_o (s_busy),
.sdio (u_sdio_core_if),
.dma_axi4 (dma_axi4)
);
assign sdio.irq_o = s_irq;
endmodule
+68
View File
@@ -0,0 +1,68 @@
// Copyright (c) 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:
// 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 FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
module sdio_clock #(
parameter int CounterWidth = 16
) (
input logic clk_i,
input logic rst_n_i,
input logic enable_i,
input logic [CounterWidth-1:0] half_period_i,
output logic sck_o,
output logic launch_tick_o,
output logic sample_tick_o,
output logic running_o
);
logic [CounterWidth-1:0] s_phase_count_d, s_phase_count_q;
logic s_sck_d, s_sck_q;
logic [CounterWidth-1:0] s_half_period;
logic s_phase_end;
always_comb begin
s_half_period = (half_period_i == '0) ? {{(CounterWidth - 1) {1'b0}}, 1'b1} : half_period_i;
s_phase_end = s_phase_count_q + 1'b1 >= s_half_period;
s_phase_count_d = s_phase_count_q;
s_sck_d = s_sck_q;
if (!enable_i && !s_sck_q) begin
s_phase_count_d = '0;
s_sck_d = 1'b0;
end else if (s_phase_end) begin
s_phase_count_d = '0;
s_sck_d = ~s_sck_q;
end else begin
s_phase_count_d = s_phase_count_q + 1'b1;
end
end
assign launch_tick_o = s_phase_end && enable_i && !s_sck_q;
assign sample_tick_o = s_phase_end && s_sck_q;
assign sck_o = s_sck_q;
assign running_o = s_sck_q || enable_i;
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_phase_count_q <= '0;
s_sck_q <= 1'b0;
end else begin
s_phase_count_q <= s_phase_count_d;
s_sck_q <= s_sck_d;
end
end
`ifndef SYNTHESIS
initial begin
if (CounterWidth < 1) begin
$fatal(1, "sdio_clock: CounterWidth must be positive");
end
end
`endif
endmodule
+314
View File
@@ -0,0 +1,314 @@
// Copyright (c) 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:
// 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 THE WARRANTIES OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
module sdio_command #(
parameter int TimeoutWidth = 32
) (
input logic clk_i,
input logic rst_n_i,
input logic launch_tick_i,
input logic sample_tick_i,
input logic start_i,
input logic abort_i,
input logic [ 5:0] cmd_index_i,
input logic [ 31:0] cmd_arg_i,
input sdio_pkg::sdio_resp_type_e resp_type_i,
input logic crc_check_i,
input logic index_check_i,
input logic [TimeoutWidth-1:0] timeout_cycles_i,
input logic [TimeoutWidth-1:0] busy_timeout_cycles_i,
input logic cmd_di_i,
input logic dat0_i,
output logic cmd_oe_o,
output logic cmd_do_o,
output logic busy_o,
output logic done_o,
output logic error_o,
output logic timeout_o,
output logic crc_error_o,
output logic index_error_o,
output logic busy_timeout_o,
output logic [ 135:0] response_o,
output logic [ 5:0] last_cmd_index_o
);
typedef enum logic [2:0] {
Idle,
Send,
WaitResponse,
CaptureResponse,
WaitBusy,
Complete
} command_state_e;
command_state_e s_state_d, s_state_q;
logic [47:0] s_tx_shift_d, s_tx_shift_q;
logic [135:0] s_resp_d, s_resp_q;
logic [5:0] s_cmd_index_d, s_cmd_index_q;
logic [3:0] s_resp_type_bits_d, s_resp_type_bits_q;
sdio_pkg::sdio_resp_type_e s_resp_type_q;
logic [7:0] s_resp_len_d, s_resp_len_q;
logic [7:0] s_bit_count_d, s_bit_count_q;
logic [5:0] s_tx_count_d, s_tx_count_q;
logic s_tx_started_d, s_tx_started_q;
logic [TimeoutWidth-1:0] s_timeout_count_d, s_timeout_count_q;
logic s_crc_check_d, s_crc_check_q;
logic s_index_check_d, s_index_check_q;
logic s_done_d, s_done_q;
logic s_err_d, s_err_q;
logic s_timeout_d, s_timeout_q;
logic s_crc_err_d, s_crc_err_q;
logic s_index_err_d, s_index_err_q;
logic s_busy_timeout_d, s_busy_timeout_q;
logic s_tx_final_hold_d, s_tx_final_hold_q;
logic [ 135:0] s_resp_shift_next;
logic s_timeout_expired;
logic s_frame_end_error;
logic s_frame_crc_error;
logic s_frame_index_error;
logic [ 6:0] s_frame_crc;
logic [TimeoutWidth-1:0] s_phase_timeout;
assign s_resp_shift_next = {s_resp_q[134:0], cmd_di_i};
assign s_phase_timeout = (s_state_q == WaitBusy) ? busy_timeout_cycles_i : timeout_cycles_i;
assign s_timeout_expired = (s_phase_timeout == '0) || (s_timeout_count_q >= s_phase_timeout);
assign s_frame_end_error = s_resp_shift_next[0] != 1'b1;
assign s_frame_crc = sdio_pkg::sdio_crc7_response(
s_resp_shift_next, s_resp_type_q == sdio_pkg::SdioRespR2
);
assign s_frame_crc_error = s_frame_crc != s_resp_shift_next[7:1];
assign s_frame_index_error = s_resp_shift_next[45:40] != s_cmd_index_q;
assign cmd_oe_o = (s_state_q == Send) || (s_tx_final_hold_q && !sample_tick_i);
assign cmd_do_o = ((s_state_q == Send) || s_tx_final_hold_q) ? s_tx_shift_q[47] : 1'b1;
assign busy_o = (s_state_q != Idle) && (s_state_q != Complete);
assign done_o = s_done_q;
assign error_o = s_err_q;
assign timeout_o = s_timeout_q;
assign crc_error_o = s_crc_err_q;
assign index_error_o = s_index_err_q;
assign busy_timeout_o = s_busy_timeout_q;
assign response_o = s_resp_q;
assign last_cmd_index_o = s_cmd_index_q;
always_comb begin
s_state_d = s_state_q;
s_tx_shift_d = s_tx_shift_q;
s_resp_d = s_resp_q;
s_cmd_index_d = s_cmd_index_q;
s_resp_type_bits_d = s_resp_type_q;
s_resp_len_d = s_resp_len_q;
s_bit_count_d = s_bit_count_q;
s_tx_count_d = s_tx_count_q;
s_tx_started_d = s_tx_started_q;
s_timeout_count_d = s_timeout_count_q;
s_crc_check_d = s_crc_check_q;
s_index_check_d = s_index_check_q;
s_done_d = 1'b0;
s_err_d = s_err_q;
s_timeout_d = s_timeout_q;
s_crc_err_d = s_crc_err_q;
s_index_err_d = s_index_err_q;
s_busy_timeout_d = s_busy_timeout_q;
s_tx_final_hold_d = s_tx_final_hold_q;
if (s_tx_final_hold_q && sample_tick_i) begin
s_tx_final_hold_d = 1'b0;
end
if (s_state_q != Idle && s_state_q != Complete) begin
if (s_timeout_expired) begin
s_state_d = Complete;
s_done_d = 1'b1;
s_err_d = 1'b1;
s_timeout_d = 1'b1;
if (s_state_q == WaitBusy) begin
s_busy_timeout_d = 1'b1;
end
end else begin
s_timeout_count_d = s_timeout_count_q + 1'b1;
end
end
if ((s_state_q != Idle) && (s_state_q != Complete) && abort_i) begin
s_state_d = Complete;
s_done_d = 1'b1;
s_err_d = 1'b1;
s_tx_final_hold_d = 1'b0;
end
if ((s_state_q == Send) && s_tx_started_q && sample_tick_i && (s_tx_count_q != 6'd47)) begin
s_tx_shift_d = {s_tx_shift_q[46:0], 1'b0};
s_tx_count_d = s_tx_count_q + 1'b1;
end
unique case (s_state_q)
Idle: begin
s_tx_final_hold_d = 1'b0;
s_tx_started_d = 1'b0;
if (start_i) begin
s_state_d = Send;
s_tx_shift_d = {
1'b0,
1'b1,
cmd_index_i,
cmd_arg_i,
sdio_pkg::sdio_crc7_calc({1'b0, 1'b1, cmd_index_i, cmd_arg_i}),
1'b1
};
s_resp_d = '0;
s_cmd_index_d = cmd_index_i;
s_resp_type_bits_d = resp_type_i;
s_resp_len_d = (resp_type_i == sdio_pkg::SdioRespR2) ? 8'd136 : 8'd48;
s_bit_count_d = '0;
s_tx_count_d = '0;
s_tx_started_d = 1'b0;
s_timeout_count_d = '0;
s_crc_check_d = crc_check_i;
s_index_check_d = index_check_i;
s_err_d = 1'b0;
s_timeout_d = 1'b0;
s_crc_err_d = 1'b0;
s_index_err_d = 1'b0;
s_busy_timeout_d = 1'b0;
end
end
Send: begin
if (launch_tick_i) begin
s_tx_started_d = 1'b1;
if (s_tx_count_q == 6'd47) begin
s_tx_final_hold_d = 1'b1;
if (s_resp_type_q == sdio_pkg::SdioRespNone) begin
s_state_d = Complete;
s_done_d = 1'b1;
end else begin
s_state_d = WaitResponse;
s_timeout_count_d = '0;
end
end
end
end
WaitResponse: begin
if (sample_tick_i && (cmd_di_i == 1'b0)) begin
s_resp_d = '0;
s_resp_d[0] = 1'b0;
s_bit_count_d = 8'd1;
s_timeout_count_d = '0;
s_state_d = CaptureResponse;
end
end
CaptureResponse: begin
if (sample_tick_i) begin
s_resp_d = s_resp_shift_next;
if (s_bit_count_q == (s_resp_len_q - 1'b1)) begin
if (s_frame_end_error) begin
s_err_d = 1'b1;
end
if (s_crc_check_q && sdio_pkg::sdio_response_has_crc(
s_resp_type_q
) && s_frame_crc_error) begin
s_err_d = 1'b1;
s_crc_err_d = 1'b1;
end
if (s_index_check_q && sdio_pkg::sdio_response_has_index(
s_resp_type_q
) && s_frame_index_error) begin
s_err_d = 1'b1;
s_index_err_d = 1'b1;
end
if ((s_resp_type_q == sdio_pkg::SdioRespR1b) && !s_frame_end_error &&
!(s_crc_check_q && s_frame_crc_error)) begin
s_state_d = WaitBusy;
s_timeout_count_d = '0;
end else begin
s_state_d = Complete;
s_done_d = 1'b1;
end
end else begin
s_bit_count_d = s_bit_count_q + 1'b1;
end
end
end
WaitBusy: begin
if (sample_tick_i && dat0_i) begin
s_state_d = Complete;
s_done_d = 1'b1;
end
end
Complete: begin
s_tx_final_hold_d = 1'b0;
s_tx_started_d = 1'b0;
s_state_d = Idle;
end
default: begin
s_state_d = Idle;
s_err_d = 1'b1;
end
endcase
end
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_state_q <= Idle;
s_tx_shift_q <= '0;
s_resp_q <= '0;
s_cmd_index_q <= '0;
s_resp_type_bits_q <= '0;
s_resp_len_q <= 8'd48;
s_bit_count_q <= '0;
s_tx_count_q <= '0;
s_tx_started_q <= 1'b0;
s_timeout_count_q <= '0;
s_crc_check_q <= 1'b0;
s_index_check_q <= 1'b0;
s_done_q <= 1'b0;
s_err_q <= 1'b0;
s_timeout_q <= 1'b0;
s_crc_err_q <= 1'b0;
s_index_err_q <= 1'b0;
s_busy_timeout_q <= 1'b0;
s_tx_final_hold_q <= 1'b0;
end else begin
s_state_q <= s_state_d;
s_tx_shift_q <= s_tx_shift_d;
s_resp_q <= s_resp_d;
s_cmd_index_q <= s_cmd_index_d;
s_resp_type_bits_q <= s_resp_type_bits_d;
s_resp_len_q <= s_resp_len_d;
s_bit_count_q <= s_bit_count_d;
s_tx_count_q <= s_tx_count_d;
s_tx_started_q <= s_tx_started_d;
s_timeout_count_q <= s_timeout_count_d;
s_crc_check_q <= s_crc_check_d;
s_index_check_q <= s_index_check_d;
s_done_q <= s_done_d;
s_err_q <= s_err_d;
s_timeout_q <= s_timeout_d;
s_crc_err_q <= s_crc_err_d;
s_index_err_q <= s_index_err_d;
s_busy_timeout_q <= s_busy_timeout_d;
s_tx_final_hold_q <= s_tx_final_hold_d;
end
end
always_comb begin
s_resp_type_q = sdio_pkg::sdio_resp_type_e'(s_resp_type_bits_q);
end
`ifndef SYNTHESIS
initial begin
if (TimeoutWidth < 1) begin
$fatal(1, "sdio_command: TimeoutWidth must be positive");
end
end
`endif
endmodule
+500
View File
@@ -0,0 +1,500 @@
// Copyright (c) 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:
// 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 THE WARRANTIES OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
module sdio_core #(
parameter int InputClockHz = 72_000_000,
parameter int AddrWidth = 32,
parameter int DataWidth = 32,
parameter int DescCount = 16
) (
// verilog_format: off -- preserve the host, status, and external bus groups
input logic clk_i,
input logic rst_n_i,
input logic host_enable_i,
input logic host_irq_enable_i,
input logic clock_enable_i,
input logic [15:0] half_period_i,
input logic [1:0] bus_width_i,
input logic [31:0] timeout_cmd_i,
input logic [31:0] timeout_data_i,
input logic [31:0] timeout_busy_i,
input logic [5:0] cmd_index_i,
input logic [31:0] cmd_arg_i,
input sdio_pkg::sdio_resp_type_e cmd_resp_type_i,
input logic cmd_crc_check_i,
input logic cmd_index_check_i,
input logic cmd_start_i,
input logic [15:0] block_size_i,
input logic [15:0] block_count_i,
input logic data_direction_i,
input logic data_dma_enable_i,
input logic data_block_mode_i,
input logic data_fixed_addr_i,
input logic data_start_i,
input logic pio_wvalid_i,
input logic [31:0] pio_wdata_i,
input logic [3:0] pio_wstrb_i,
input logic [31:0] desc_base_i,
input logic [15:0] desc_count_i,
input logic dma_start_i,
input logic dma_abort_i,
input logic host_abort_i,
input logic irq_dat1_enable_i,
output logic [31:0] status_o,
output logic [31:0] clock_actual_o,
output logic [31:0] cmd_status_o,
output logic [135:0] response_o,
output logic [31:0] data_status_o,
output logic [31:0] fifo_status_o,
output logic [31:0] dma_status_o,
output logic [31:0] current_desc_o,
output logic [31:0] bytes_done_o,
output logic [31:0] dma_error_addr_o,
output logic [31:0] dma_error_o,
output logic [31:0] error_status_o,
output logic [5:0] last_cmd_o,
output logic [31:0] crc_error_count_o,
output logic [31:0] timeout_count_o,
output logic [31:0] axi_error_count_o,
output logic [31:0] stall_count_o,
output logic [7:0] irq_event_o,
output logic [31:0] pio_rdata_o,
output logic pio_valid_o,
output logic pio_ready_o,
input logic pio_read_consume_i,
output logic busy_o,
sdio_if.core sdio,
axi4_if.master dma_axi4
// verilog_format: on
);
logic s_launch_tick;
logic s_sample_tick;
logic s_clock_sck;
logic s_clock_running;
logic s_clock_en;
logic s_cmd_oe;
logic s_cmd_do;
logic s_cmd_busy;
logic s_cmd_done;
logic s_cmd_err;
logic s_cmd_timeout;
logic s_cmd_crc_err;
logic s_cmd_index_err;
logic s_cmd_busy_timeout;
logic [135:0] s_cmd_response;
logic [ 5:0] s_last_cmd;
logic [ 3:0] s_data_oe;
logic [ 3:0] s_data_do;
logic s_data_tx_valid;
logic s_data_tx_ready;
logic s_data_tx_wait;
logic [ 31:0] s_data_tx_data;
logic [ 3:0] s_data_tx_strb;
logic s_data_tx_last;
logic s_data_rx_valid;
logic s_data_rx_ready;
logic s_data_rx_stall_q;
logic [ 31:0] s_data_rx_data;
logic [ 3:0] s_data_rx_strb;
logic s_data_rx_last;
logic s_data_busy;
logic s_data_done;
logic s_data_err;
logic s_data_timeout;
logic s_data_crc_err;
logic s_data_busy_timeout;
logic s_dma_start;
logic s_dma_direction;
logic s_dma_data_in_valid;
logic s_dma_data_in_ready;
logic [ 31:0] s_dma_data_in;
logic [ 3:0] s_dma_data_in_strb;
logic s_dma_data_in_last;
logic s_dma_data_out_valid;
logic s_dma_data_out_ready;
logic [ 31:0] s_dma_data_out;
logic [ 3:0] s_dma_data_out_strb;
logic s_dma_data_out_last;
logic s_dma_busy;
logic s_dma_done;
logic s_dma_err;
logic [ 7:0] s_dma_err_code;
logic [ 31:0] s_dma_current_desc;
logic [ 31:0] s_dma_bytes_done;
logic [ 31:0] s_dma_err_addr;
logic s_dma_descriptor_irq;
logic s_dma_abort_done;
logic s_dat1_meta_q, s_dat1_sync_q, s_dat1_seen_d, s_dat1_seen_q;
logic [31:0] s_pio_rdata_q;
logic s_pio_valid_q;
logic [31:0] s_pio_tx_data_q[0:3];
logic [ 3:0] s_pio_tx_strb_q[0:3];
logic [1:0] s_pio_tx_rd_q, s_pio_tx_wr_q;
logic [ 2:0] s_pio_tx_count_q;
logic [31:0] s_crc_err_count_q;
logic [31:0] s_timeout_count_q;
logic [31:0] s_axi_err_count_q;
logic [31:0] s_stall_count_q;
logic [ 7:0] s_irq_event;
logic [31:0] s_err_stat;
logic [31:0] s_total_bytes;
logic s_core_busy;
logic [31:0] s_cmd_timeout_cfg;
logic s_dat1_qualify;
logic s_abort_request;
logic s_abort_pending_q;
logic s_abort_event;
logic s_pio_tx_push, s_pio_tx_pop, s_pio_tx_full;
logic [ 2:0] s_pio_tx_head_bytes;
logic [31:0] s_pio_tx_bytes_q;
logic s_cmd_err_event, s_data_err_event, s_dma_err_event;
logic s_cmd_crc_event, s_data_crc_event;
logic s_cmd_timeout_event, s_data_timeout_event, s_busy_timeout_event;
function automatic logic [2:0] count_strobe(input logic [3:0] strb_i);
logic [2:0] count;
begin
count = 3'd0;
for (int index = 0; index < 4; index++) begin
if (strb_i[index]) count = count + 1'b1;
end
return count;
end
endfunction
assign s_clock_en = host_enable_i && clock_enable_i &&
(s_cmd_busy || s_data_busy || s_dma_busy || cmd_start_i ||
data_start_i) &&
!s_data_rx_stall_q &&
!(s_data_busy && s_data_tx_wait && (s_data_oe == 4'b0000));
assign s_total_bytes = {16'd0, block_size_i} * {16'd0, block_count_i};
assign s_abort_request = host_abort_i || dma_abort_i ||
(s_dma_done && s_dma_err) ||
(s_data_done && s_data_err);
assign s_dma_start = dma_start_i || (data_start_i && data_dma_enable_i);
assign s_dma_direction = data_direction_i;
assign s_cmd_timeout_cfg = timeout_cmd_i;
assign s_dat1_qualify = host_irq_enable_i && irq_dat1_enable_i && !s_data_busy &&
(!host_enable_i || !s_data_oe[1]) && !s_dat1_sync_q;
assign s_pio_tx_full = s_pio_tx_count_q == 3'd4;
assign s_pio_tx_head_bytes = count_strobe(s_pio_tx_strb_q[s_pio_tx_rd_q]);
assign s_pio_tx_push = pio_wvalid_i && pio_ready_o;
assign s_data_tx_valid = data_dma_enable_i ? s_dma_data_out_valid : (s_pio_tx_count_q != 3'd0);
assign s_data_tx_data = data_dma_enable_i ? s_dma_data_out : s_pio_tx_data_q[s_pio_tx_rd_q];
assign s_data_tx_strb = data_dma_enable_i ? s_dma_data_out_strb : s_pio_tx_strb_q[s_pio_tx_rd_q];
assign s_data_tx_last = data_dma_enable_i ? s_dma_data_out_last :
((s_pio_tx_bytes_q + {29'd0, s_pio_tx_head_bytes}) >=
s_total_bytes);
assign s_dma_data_out_ready = data_dma_enable_i && s_data_tx_ready;
assign s_dma_data_in_valid = data_dma_enable_i && s_data_rx_valid;
assign s_dma_data_in = s_data_rx_data;
assign s_dma_data_in_strb = s_data_rx_strb;
assign s_dma_data_in_last = s_data_rx_last;
assign s_data_rx_ready = data_dma_enable_i ? s_dma_data_in_ready : !s_pio_valid_q;
assign s_core_busy = s_cmd_busy || s_data_busy || s_dma_busy;
assign busy_o = s_core_busy;
assign s_pio_tx_pop = !data_dma_enable_i && s_data_tx_valid && s_data_tx_ready;
assign s_abort_event = s_abort_pending_q && !s_core_busy;
assign s_cmd_err_event = s_cmd_done && s_cmd_err;
assign s_data_err_event = s_data_done && s_data_err;
assign s_dma_err_event = s_dma_done && s_dma_err;
assign s_cmd_crc_event = s_cmd_done && s_cmd_crc_err;
assign s_data_crc_event = s_data_done && s_data_crc_err;
assign s_cmd_timeout_event = s_cmd_done && (s_cmd_timeout || s_cmd_busy_timeout);
assign s_data_timeout_event = s_data_done && s_data_timeout;
assign s_busy_timeout_event = s_data_done && s_data_busy_timeout;
assign sdio.sck_o = s_clock_sck;
assign sdio.cmd_oe_o = s_cmd_oe && host_enable_i;
assign sdio.cmd_do_o = s_cmd_do;
assign sdio.dat_oe_o = host_enable_i ? s_data_oe : 4'b0000;
assign sdio.dat_do_o = s_data_do;
assign pio_rdata_o = s_pio_rdata_q;
assign pio_valid_o = s_pio_valid_q;
assign pio_ready_o = s_data_busy && !data_dma_enable_i && !s_pio_tx_full &&
(data_direction_i == sdio_pkg::SdioDataToCard);
assign response_o = s_cmd_response;
assign last_cmd_o = s_last_cmd;
assign crc_error_count_o = s_crc_err_count_q;
assign timeout_count_o = s_timeout_count_q;
assign axi_error_count_o = s_axi_err_count_q;
assign stall_count_o = s_stall_count_q;
assign current_desc_o = s_dma_current_desc;
assign bytes_done_o = s_dma_bytes_done;
assign dma_error_addr_o = s_dma_err_addr;
assign dma_error_o = {24'd0, s_dma_err_code};
assign clock_actual_o = (half_period_i == 16'd0) ? 32'd0 : InputClockHz / (2 * half_period_i);
assign status_o = {
23'd0,
s_clock_running,
s_dat1_sync_q,
data_block_mode_i,
data_fixed_addr_i,
pio_ready_o,
s_dma_busy,
s_data_busy,
s_cmd_busy,
s_core_busy
};
assign cmd_status_o = {
27'd0, s_cmd_index_err, s_cmd_crc_err, s_cmd_timeout, s_cmd_err, s_cmd_busy
};
assign data_status_o = {
27'd0, s_data_busy_timeout, s_data_crc_err, s_data_timeout, s_data_err, s_data_busy
};
assign fifo_status_o = {28'd0, s_pio_tx_count_q, s_pio_valid_q};
assign dma_status_o = {21'd0, s_dma_err_code, s_dma_err, s_dma_done, s_dma_busy};
assign error_status_o = s_err_stat;
assign s_err_stat = {
24'd0,
s_abort_event || s_dma_abort_done,
s_dma_err_event,
s_data_err_event,
s_cmd_err_event,
s_data_timeout_event || s_busy_timeout_event,
s_cmd_timeout_event,
s_data_crc_event,
s_cmd_crc_event
};
assign s_irq_event = {
s_abort_event || s_dma_abort_done,
s_dat1_seen_d && !s_dat1_seen_q,
s_dma_err_event,
s_data_err_event,
s_cmd_err_event,
s_dma_done || s_dma_descriptor_irq,
s_data_done,
s_cmd_done
};
assign irq_event_o = s_irq_event;
sdio_clock #(
.CounterWidth(16)
) u_sdio_clock (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.enable_i (s_clock_en),
.half_period_i(half_period_i),
.sck_o (s_clock_sck),
.launch_tick_o(s_launch_tick),
.sample_tick_o(s_sample_tick),
.running_o (s_clock_running)
);
sdio_command #(
.TimeoutWidth(32)
) u_sdio_command (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.launch_tick_i (s_launch_tick),
.sample_tick_i (s_sample_tick),
.start_i (cmd_start_i),
.abort_i (s_abort_request),
.cmd_index_i (cmd_index_i),
.cmd_arg_i (cmd_arg_i),
.resp_type_i (cmd_resp_type_i),
.crc_check_i (cmd_crc_check_i),
.index_check_i (cmd_index_check_i),
.timeout_cycles_i (s_cmd_timeout_cfg),
.busy_timeout_cycles_i(timeout_busy_i),
.cmd_di_i (sdio.cmd_di_i),
.dat0_i (sdio.dat_di_i[0]),
.cmd_oe_o (s_cmd_oe),
.cmd_do_o (s_cmd_do),
.busy_o (s_cmd_busy),
.done_o (s_cmd_done),
.error_o (s_cmd_err),
.timeout_o (s_cmd_timeout),
.crc_error_o (s_cmd_crc_err),
.index_error_o (s_cmd_index_err),
.busy_timeout_o (s_cmd_busy_timeout),
.response_o (s_cmd_response),
.last_cmd_index_o (s_last_cmd)
);
sdio_data #(
.TimeoutWidth(32)
) u_sdio_data (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.launch_tick_i (s_launch_tick),
.sample_tick_i (s_sample_tick),
.start_i (data_start_i),
.abort_i (s_abort_request),
.direction_i (sdio_pkg::sdio_data_direction_e'(data_direction_i)),
.bus_width_i (bus_width_i),
.block_size_i (block_size_i),
.block_count_i (block_count_i),
.timeout_cycles_i (timeout_data_i),
.busy_timeout_cycles_i(timeout_busy_i),
.dat_di_i (sdio.dat_di_i),
.dat0_i (sdio.dat_di_i[0]),
.dat_oe_o (s_data_oe),
.dat_do_o (s_data_do),
.tx_valid_i (s_data_tx_valid),
.tx_ready_o (s_data_tx_ready),
.tx_wait_o (s_data_tx_wait),
.tx_data_i (s_data_tx_data),
.tx_strb_i (s_data_tx_strb),
.tx_last_i (s_data_tx_last),
.rx_valid_o (s_data_rx_valid),
.rx_ready_i (s_data_rx_ready),
.rx_data_o (s_data_rx_data),
.rx_strb_o (s_data_rx_strb),
.rx_last_o (s_data_rx_last),
.busy_o (s_data_busy),
.done_o (s_data_done),
.error_o (s_data_err),
.timeout_o (s_data_timeout),
.crc_error_o (s_data_crc_err),
.busy_timeout_o (s_data_busy_timeout)
);
sdio_dma #(
.AddrWidth(AddrWidth),
.DataWidth(DataWidth),
.DescCount(DescCount)
) u_sdio_dma (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_dma_start),
.abort_i (s_abort_request),
.direction_i (s_dma_direction),
.desc_base_i (desc_base_i),
.desc_count_i (desc_count_i),
.total_bytes_i (s_total_bytes),
.data_in_valid_i (s_dma_data_in_valid),
.data_in_ready_o (s_dma_data_in_ready),
.data_in_i (s_dma_data_in),
.data_in_strb_i (s_dma_data_in_strb),
.data_in_last_i (s_dma_data_in_last),
.data_out_valid_o(s_dma_data_out_valid),
.data_out_ready_i(s_dma_data_out_ready),
.data_out_o (s_dma_data_out),
.data_out_strb_o (s_dma_data_out_strb),
.data_out_last_o (s_dma_data_out_last),
.busy_o (s_dma_busy),
.done_o (s_dma_done),
.error_o (s_dma_err),
.error_code_o (s_dma_err_code),
.current_desc_o (s_dma_current_desc),
.bytes_done_o (s_dma_bytes_done),
.error_addr_o (s_dma_err_addr),
.descriptor_irq_o(s_dma_descriptor_irq),
.abort_done_o (s_dma_abort_done),
.dma_axi4 (dma_axi4)
);
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_dat1_meta_q <= 1'b1;
s_dat1_sync_q <= 1'b1;
s_dat1_seen_q <= 1'b0;
s_data_rx_stall_q <= 1'b0;
s_pio_rdata_q <= '0;
s_pio_valid_q <= 1'b0;
s_pio_tx_rd_q <= '0;
s_pio_tx_wr_q <= '0;
s_pio_tx_count_q <= '0;
s_pio_tx_bytes_q <= '0;
s_crc_err_count_q <= '0;
s_timeout_count_q <= '0;
s_axi_err_count_q <= '0;
s_stall_count_q <= '0;
s_abort_pending_q <= 1'b0;
for (int index = 0; index < 4; index++) begin
s_pio_tx_data_q[index] <= '0;
s_pio_tx_strb_q[index] <= '0;
end
end else begin
s_dat1_meta_q <= sdio.dat_di_i[1];
s_dat1_sync_q <= s_dat1_meta_q;
s_dat1_seen_q <= s_dat1_seen_d;
s_data_rx_stall_q <= s_data_rx_valid && !s_data_rx_ready;
if (data_start_i) begin
s_pio_tx_rd_q <= '0;
s_pio_tx_wr_q <= '0;
s_pio_tx_count_q <= '0;
s_pio_tx_bytes_q <= '0;
end else begin
if (s_pio_tx_push) begin
s_pio_tx_data_q[s_pio_tx_wr_q] <= pio_wdata_i;
s_pio_tx_strb_q[s_pio_tx_wr_q] <= pio_wstrb_i;
s_pio_tx_wr_q <= s_pio_tx_wr_q + 1'b1;
end
if (s_pio_tx_pop) begin
s_pio_tx_rd_q <= s_pio_tx_rd_q + 1'b1;
s_pio_tx_bytes_q <= s_pio_tx_bytes_q + {29'd0, s_pio_tx_head_bytes};
end
unique case ({
s_pio_tx_push, s_pio_tx_pop
})
2'b10: s_pio_tx_count_q <= s_pio_tx_count_q + 1'b1;
2'b01: s_pio_tx_count_q <= s_pio_tx_count_q - 1'b1;
default: begin
end
endcase
end
if (s_data_rx_valid && s_data_rx_ready && !data_dma_enable_i) begin
s_pio_rdata_q <= s_data_rx_data;
s_pio_valid_q <= 1'b1;
end
if (pio_read_consume_i) begin
s_pio_valid_q <= 1'b0;
end
unique case ({
s_cmd_crc_event, s_data_crc_event
})
2'b10, 2'b01: s_crc_err_count_q <= s_crc_err_count_q + 1'b1;
2'b11: s_crc_err_count_q <= s_crc_err_count_q + 32'd2;
default: begin
end
endcase
if (s_cmd_timeout_event || s_data_timeout_event || s_busy_timeout_event) begin
s_timeout_count_q <= s_timeout_count_q + 1'b1;
end
if (s_dma_err_event) begin
s_axi_err_count_q <= s_axi_err_count_q + 1'b1;
end
if ((s_data_tx_valid && !s_data_tx_ready) || (s_data_rx_valid && !s_data_rx_ready)) begin
s_stall_count_q <= s_stall_count_q + 1'b1;
end
if (s_abort_request && s_core_busy) begin
s_abort_pending_q <= 1'b1;
end else if (s_abort_pending_q && !s_core_busy) begin
s_abort_pending_q <= 1'b0;
end
end
end
always_comb begin
s_dat1_seen_d = s_dat1_seen_q;
if (s_dat1_sync_q) begin
s_dat1_seen_d = 1'b0;
end else if (s_dat1_qualify) begin
s_dat1_seen_d = 1'b1;
end
end
`ifndef SYNTHESIS
initial begin
if ((AddrWidth < 4) || (DataWidth != 32) || (DescCount < 1)) begin
$fatal(1, "sdio_core: unsupported geometry");
end
end
`endif
endmodule
+21
View File
@@ -0,0 +1,21 @@
// Copyright (c) 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:
// 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 THE WARRANTIES OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
module sdio_crc16 (
input logic [15:0] crc_i,
input logic [ 7:0] data_i,
output logic [15:0] crc_o
);
always_comb begin
crc_o = sdio_pkg::sdio_crc16_byte(crc_i, data_i);
end
endmodule
+20
View File
@@ -0,0 +1,20 @@
// Copyright (c) 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:
// 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 THE WARRANTIES OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
module sdio_crc7 (
input logic [39:0] data_i,
output logic [ 6:0] crc_o
);
always_comb begin
crc_o = sdio_pkg::sdio_crc7_calc(data_i);
end
endmodule
+657
View File
@@ -0,0 +1,657 @@
// Copyright (c) 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 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 THE WARRANTIES OF
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
module sdio_data #(
parameter int TimeoutWidth = 32
) (
input logic clk_i,
input logic rst_n_i,
input logic launch_tick_i,
input logic sample_tick_i,
input logic start_i,
input logic abort_i,
input sdio_pkg::sdio_data_direction_e direction_i,
input logic [ 1:0] bus_width_i,
input logic [ 15:0] block_size_i,
input logic [ 15:0] block_count_i,
input logic [TimeoutWidth-1:0] timeout_cycles_i,
input logic [TimeoutWidth-1:0] busy_timeout_cycles_i,
input logic [ 3:0] dat_di_i,
input logic dat0_i,
output logic [ 3:0] dat_oe_o,
output logic [ 3:0] dat_do_o,
input logic tx_valid_i,
output logic tx_ready_o,
output logic tx_wait_o,
input logic [ 31:0] tx_data_i,
input logic [ 3:0] tx_strb_i,
input logic tx_last_i,
output logic rx_valid_o,
input logic rx_ready_i,
output logic [ 31:0] rx_data_o,
output logic [ 3:0] rx_strb_o,
output logic rx_last_o,
output logic busy_o,
output logic done_o,
output logic error_o,
output logic timeout_o,
output logic crc_error_o,
output logic busy_timeout_o
);
typedef enum logic [3:0] {
Idle,
WriteWaitWord,
WriteNextWord,
WriteToken,
WritePayload,
WriteCrc,
WriteResponse,
WriteBusy,
ReadToken,
ReadPayload,
ReadEmit,
ReadCrc
} data_state_e;
data_state_e s_state_d, s_state_q;
logic s_width4_d, s_width4_q;
logic [31:0] s_target_bytes_d, s_target_bytes_q;
logic [31:0] s_tx_total_d, s_tx_total_q;
logic [31:0] s_rx_total_d, s_rx_total_q;
logic [31:0] s_tx_word_d, s_tx_word_q;
logic [3:0] s_tx_strb_d, s_tx_strb_q;
logic [2:0] s_tx_active_bytes_d, s_tx_active_bytes_q;
logic [2:0] s_tx_byte_d, s_tx_byte_q;
logic [3:0] s_tx_bit_d, s_tx_bit_q;
logic [15:0] s_tx_crc_d, s_tx_crc_q;
logic [63:0] s_tx_crc_lanes_d, s_tx_crc_lanes_q;
logic s_tx_token_launched_d, s_tx_token_launched_q;
logic [4:0] s_crc_bit_d, s_crc_bit_q;
logic s_tx_crc_final_hold_d, s_tx_crc_final_hold_q;
logic [31:0] s_rx_word_d, s_rx_word_q;
logic [7:0] s_rx_byte_d, s_rx_byte_q;
logic [2:0] s_rx_active_bytes_d, s_rx_active_bytes_q;
logic [2:0] s_rx_byte_didx_d, s_rx_byte_didx_q;
logic [3:0] s_rx_bit_d, s_rx_bit_q;
logic [15:0] s_rx_crc_d, s_rx_crc_q;
logic [63:0] s_rx_crc_lanes_d, s_rx_crc_lanes_q;
logic [15:0] s_rx_crc_received_d, s_rx_crc_received_q;
logic [63:0] s_rx_crc_received_lanes_d, s_rx_crc_received_lanes_q;
logic [31:0] s_rx_pending_data_d, s_rx_pending_data_q;
logic [3:0] s_rx_pending_strb_d, s_rx_pending_strb_q;
logic s_rx_pending_last_d, s_rx_pending_last_q;
logic [4:0] s_resp_token_d, s_resp_token_q;
logic [2:0] s_resp_bit_d, s_resp_bit_q;
logic [TimeoutWidth-1:0] s_timeout_count_d, s_timeout_count_q;
logic s_done_d, s_done_q;
logic s_err_d, s_err_q;
logic s_timeout_d, s_timeout_q;
logic s_crc_err_d, s_crc_err_q;
logic s_busy_timeout_d, s_busy_timeout_q;
logic [ 31:0] s_target_bytes_calc;
logic [ 7:0] s_rx_byte_next;
logic [ 31:0] s_rx_remaining;
logic [TimeoutWidth-1:0] s_phase_timeout;
logic s_timeout_expired;
logic s_tx_word_final;
function automatic logic [2:0] count_strobe(input logic [3:0] strb_i);
logic [2:0] count;
begin
count = 3'd0;
for (int index = 0; index < 4; index++) begin
if (strb_i[index]) begin
count = count + 1'b1;
end
end
return count;
end
endfunction
function automatic logic strobe_valid(input logic [3:0] strb_i);
return (strb_i != 4'd0) && ((strb_i & (strb_i + 4'd1)) == 4'd0);
endfunction
function automatic logic [63:0] update_lane_crc(
input logic [63:0] crc_i, input logic [3:0] bits_i, input logic [2:0] lane_count_i);
logic [63:0] crc;
begin
crc = crc_i;
for (int lane = 0; lane < 4; lane++) begin
if (lane < lane_count_i) begin
crc[lane*16+:16] = sdio_pkg::sdio_crc16_next(crc_i[lane*16+:16], bits_i[lane]);
end
end
return crc;
end
endfunction
function automatic logic [3:0] tx_lane_bits(input logic [31:0] word_i, input logic [3:0] bit_i);
logic [3:0] bits;
begin
bits = 4'b0000;
for (int lane = 0; lane < 4; lane++) begin
bits[lane] = word_i[lane*8+(int'(7)-int'(bit_i))];
end
return bits;
end
endfunction
assign s_target_bytes_calc = {16'd0, block_size_i} * {16'd0, block_count_i};
assign s_rx_remaining = s_target_bytes_q - s_rx_total_q;
assign s_rx_byte_next = {s_rx_byte_q[6:0], dat_di_i[0]};
assign s_phase_timeout = (s_state_q == WriteBusy) ? busy_timeout_cycles_i : timeout_cycles_i;
assign s_timeout_expired = (s_phase_timeout == '0) || (s_timeout_count_q >= s_phase_timeout);
assign s_tx_word_final = s_width4_q
? (s_tx_bit_q == 4'd7)
: ((s_tx_bit_q == 4'd7) &&
(s_tx_byte_q + 1'b1 >= s_tx_active_bytes_q));
assign tx_ready_o = (s_state_q == WriteWaitWord) || (s_state_q == WriteNextWord) ||
((s_state_q == WritePayload) && launch_tick_i && s_tx_word_final &&
(s_tx_total_q < s_target_bytes_q));
assign tx_wait_o = (s_state_q == WriteWaitWord) || (s_state_q == WriteNextWord);
assign rx_valid_o = (s_state_q == ReadEmit);
assign rx_data_o = s_rx_pending_data_q;
assign rx_strb_o = s_rx_pending_strb_q;
assign rx_last_o = s_rx_pending_last_q;
assign busy_o = s_state_q != Idle;
assign done_o = s_done_q;
assign error_o = s_err_q;
assign timeout_o = s_timeout_q;
assign crc_error_o = s_crc_err_q;
assign busy_timeout_o = s_busy_timeout_q;
`ifndef SYNTHESIS
always_ff @(posedge clk_i) begin
if (tx_valid_i && tx_ready_o && tx_last_i) begin
assert (s_tx_total_q + {29'd0, count_strobe(tx_strb_i)} >= s_target_bytes_q)
else $error("sdio_data: tx_last asserted before the target byte count");
end
end
`endif
always_comb begin
dat_oe_o = 4'b0000;
dat_do_o = 4'b1111;
if ((s_state_q == WriteToken) || (s_state_q == WritePayload) ||
(s_state_q == WriteCrc) || s_tx_crc_final_hold_q) begin
if (s_width4_q) begin
dat_oe_o = (s_tx_active_bytes_q >= 3'd4)
? 4'b1111
: ((4'b0001 << s_tx_active_bytes_q) - 1'b1);
end else begin
dat_oe_o = 4'b0001;
end
end
if (s_state_q == WriteToken) begin
dat_do_o = 4'b0000;
end else if ((s_state_q == WritePayload) && s_width4_q) begin
dat_do_o = 4'b1111;
for (int lane = 0; lane < 4; lane++) begin
if (lane < s_tx_active_bytes_q) begin
dat_do_o[lane] = s_tx_word_q[lane*8+(int'(7)-int'(s_tx_bit_q))];
end
end
end else if (s_state_q == WritePayload) begin
dat_do_o[0] = s_tx_word_q[s_tx_byte_q*8+(int'(7)-int'(s_tx_bit_q))];
end else if ((s_state_q == WriteCrc) || s_tx_crc_final_hold_q) begin
if (s_width4_q) begin
dat_do_o = 4'b1111;
for (int lane = 0; lane < 4; lane++) begin
if (lane < s_tx_active_bytes_q) begin
dat_do_o[lane] = s_tx_crc_lanes_q[lane*16+(int'(15)-int'(s_crc_bit_q))];
end
end
end else begin
dat_do_o[0] = s_tx_crc_q[int'(15)-int'(s_crc_bit_q)];
end
end
end
always_comb begin
s_state_d = s_state_q;
s_width4_d = s_width4_q;
s_target_bytes_d = s_target_bytes_q;
s_tx_total_d = s_tx_total_q;
s_rx_total_d = s_rx_total_q;
s_tx_word_d = s_tx_word_q;
s_tx_strb_d = s_tx_strb_q;
s_tx_active_bytes_d = s_tx_active_bytes_q;
s_tx_byte_d = s_tx_byte_q;
s_tx_bit_d = s_tx_bit_q;
s_tx_crc_d = s_tx_crc_q;
s_tx_crc_lanes_d = s_tx_crc_lanes_q;
s_tx_token_launched_d = s_tx_token_launched_q;
s_crc_bit_d = s_crc_bit_q;
s_tx_crc_final_hold_d = s_tx_crc_final_hold_q;
s_rx_word_d = s_rx_word_q;
s_rx_byte_d = s_rx_byte_q;
s_rx_active_bytes_d = s_rx_active_bytes_q;
s_rx_byte_didx_d = s_rx_byte_didx_q;
s_rx_bit_d = s_rx_bit_q;
s_rx_crc_d = s_rx_crc_q;
s_rx_crc_lanes_d = s_rx_crc_lanes_q;
s_rx_crc_received_d = s_rx_crc_received_q;
s_rx_crc_received_lanes_d = s_rx_crc_received_lanes_q;
s_rx_pending_data_d = s_rx_pending_data_q;
s_rx_pending_strb_d = s_rx_pending_strb_q;
s_rx_pending_last_d = s_rx_pending_last_q;
s_resp_token_d = s_resp_token_q;
s_resp_bit_d = s_resp_bit_q;
s_timeout_count_d = s_timeout_count_q;
s_done_d = 1'b0;
s_err_d = s_err_q;
s_timeout_d = s_timeout_q;
s_crc_err_d = s_crc_err_q;
s_busy_timeout_d = s_busy_timeout_q;
if (s_tx_crc_final_hold_q && sample_tick_i) begin
s_tx_crc_final_hold_d = 1'b0;
end
if ((s_state_q != Idle) && s_timeout_expired) begin
s_state_d = Idle;
s_done_d = 1'b1;
s_err_d = 1'b1;
s_timeout_d = 1'b1;
s_busy_timeout_d = (s_state_q == WriteBusy);
s_tx_crc_final_hold_d = 1'b0;
end else if (s_state_q != Idle) begin
s_timeout_count_d = s_timeout_count_q + 1'b1;
end
if (abort_i && (s_state_q != Idle)) begin
s_state_d = Idle;
s_done_d = 1'b1;
s_err_d = 1'b1;
s_tx_crc_final_hold_d = 1'b0;
end else if (s_state_q == Idle) begin
if (start_i) begin
s_width4_d = bus_width_i == 2'd1;
s_target_bytes_d = s_target_bytes_calc;
s_tx_total_d = '0;
s_rx_total_d = '0;
s_tx_word_d = '0;
s_tx_strb_d = '0;
s_tx_active_bytes_d = '0;
s_tx_byte_d = '0;
s_tx_bit_d = '0;
s_tx_crc_d = '0;
s_tx_crc_lanes_d = '0;
s_tx_token_launched_d = 1'b0;
s_crc_bit_d = '0;
s_tx_crc_final_hold_d = 1'b0;
s_rx_word_d = '0;
s_rx_byte_d = '0;
s_rx_active_bytes_d = (s_target_bytes_calc >= 32'd4) ? 3'd4 : s_target_bytes_calc[2:0];
s_rx_byte_didx_d = '0;
s_rx_bit_d = '0;
s_rx_crc_d = '0;
s_rx_crc_lanes_d = '0;
s_rx_crc_received_d = '0;
s_rx_crc_received_lanes_d = '0;
s_rx_pending_data_d = '0;
s_rx_pending_strb_d = '0;
s_rx_pending_last_d = 1'b0;
s_resp_token_d = '0;
s_resp_bit_d = '0;
s_timeout_count_d = '0;
s_err_d = s_target_bytes_calc == 32'd0;
s_timeout_d = 1'b0;
s_crc_err_d = 1'b0;
s_busy_timeout_d = 1'b0;
if (s_target_bytes_calc == 32'd0) begin
s_state_d = Idle;
s_done_d = 1'b1;
end else if (direction_i == sdio_pkg::SdioDataToCard) begin
s_state_d = WriteWaitWord;
end else begin
s_state_d = ReadToken;
end
end
end else begin
unique case (s_state_q)
WriteWaitWord, WriteNextWord: begin
if (tx_valid_i && tx_ready_o) begin
s_tx_active_bytes_d = count_strobe(tx_strb_i);
s_tx_word_d = tx_data_i;
s_tx_strb_d = tx_strb_i;
s_tx_byte_d = '0;
s_tx_bit_d = '0;
if (s_state_q == WriteWaitWord) begin
s_tx_crc_d = '0;
s_tx_crc_lanes_d = '0;
end
s_tx_token_launched_d = 1'b0;
s_tx_total_d = s_tx_total_q + {29'd0, count_strobe(tx_strb_i)};
s_tx_crc_final_hold_d = 1'b0;
s_timeout_count_d = '0;
if (!strobe_valid(
tx_strb_i
) || (s_tx_total_q + {29'd0, count_strobe(
tx_strb_i
)} > s_target_bytes_q)) begin
s_state_d = Idle;
s_done_d = 1'b1;
s_err_d = 1'b1;
end else begin
s_state_d = (s_state_q == WriteWaitWord) ? WriteToken : WritePayload;
end
end
end
WriteToken: begin
if (sample_tick_i) begin
s_tx_token_launched_d = 1'b1;
end
if (launch_tick_i && s_tx_token_launched_q) begin
s_state_d = WritePayload;
s_tx_byte_d = '0;
s_tx_bit_d = '0;
s_timeout_count_d = '0;
s_tx_token_launched_d = 1'b0;
end
end
WritePayload: begin
if (launch_tick_i) begin
if (s_width4_q) begin
s_tx_crc_lanes_d = update_lane_crc(
s_tx_crc_lanes_q, tx_lane_bits(s_tx_word_q, s_tx_bit_q), s_tx_active_bytes_q);
if (s_tx_bit_q == 4'd7) begin
if (s_tx_total_q >= s_target_bytes_q) begin
s_state_d = WriteCrc;
s_crc_bit_d = '0;
end else if (tx_valid_i && tx_ready_o) begin
s_tx_word_d = tx_data_i;
s_tx_strb_d = tx_strb_i;
s_tx_active_bytes_d = count_strobe(tx_strb_i);
s_tx_total_d = s_tx_total_q + {29'd0, count_strobe(tx_strb_i)};
s_tx_byte_d = '0;
s_tx_bit_d = '0;
end else begin
s_state_d = WriteNextWord;
end
end else begin
s_tx_bit_d = s_tx_bit_q + 1'b1;
end
end else if (s_tx_bit_q == 4'd7) begin
s_tx_crc_d = sdio_pkg::sdio_crc16_byte(s_tx_crc_q, s_tx_word_q[s_tx_byte_q*8+:8]);
if (s_tx_byte_q + 1'b1 >= s_tx_active_bytes_q) begin
if (s_tx_total_q >= s_target_bytes_q) begin
s_state_d = WriteCrc;
s_crc_bit_d = '0;
end else if (tx_valid_i && tx_ready_o) begin
s_tx_word_d = tx_data_i;
s_tx_strb_d = tx_strb_i;
s_tx_active_bytes_d = count_strobe(tx_strb_i);
s_tx_total_d = s_tx_total_q + {29'd0, count_strobe(tx_strb_i)};
s_tx_byte_d = '0;
s_tx_bit_d = '0;
end else begin
s_state_d = WriteNextWord;
end
end else begin
s_tx_byte_d = s_tx_byte_q + 1'b1;
s_tx_bit_d = '0;
end
end else begin
s_tx_bit_d = s_tx_bit_q + 1'b1;
end
end
end
WriteCrc: begin
if (launch_tick_i) begin
if (s_crc_bit_q == 5'd15) begin
s_tx_crc_final_hold_d = 1'b1;
s_state_d = WriteResponse;
s_resp_token_d = '0;
s_resp_bit_d = '0;
s_timeout_count_d = '0;
end else begin
s_crc_bit_d = s_crc_bit_q + 1'b1;
end
end
end
WriteResponse: begin
if (launch_tick_i && !s_tx_crc_final_hold_q) begin
s_resp_token_d = {s_resp_token_q[3:0], dat0_i};
if (s_resp_bit_q == 3'd4) begin
if ({s_resp_token_q[3:0], dat0_i} == 5'b00101) begin
s_state_d = WriteBusy;
s_timeout_count_d = '0;
end else begin
s_state_d = Idle;
s_done_d = 1'b1;
s_err_d = 1'b1;
if ({s_resp_token_q[3:0], dat0_i} == 5'b01011) begin
s_crc_err_d = 1'b1;
end
end
end else begin
s_resp_bit_d = s_resp_bit_q + 1'b1;
end
end
end
WriteBusy: begin
if (sample_tick_i && dat0_i) begin
if (s_tx_total_q >= s_target_bytes_q) begin
s_state_d = Idle;
s_done_d = 1'b1;
end else begin
s_state_d = WriteWaitWord;
s_timeout_count_d = '0;
end
end
end
ReadToken: begin
if (sample_tick_i && (dat_di_i == 4'b0000)) begin
s_state_d = ReadPayload;
s_rx_word_d = '0;
s_rx_byte_d = '0;
s_rx_byte_didx_d = '0;
s_rx_bit_d = '0;
s_rx_crc_d = '0;
s_rx_crc_lanes_d = '0;
s_timeout_count_d = '0;
end
end
ReadPayload: begin
if (sample_tick_i) begin
if (s_width4_q) begin
for (int lane = 0; lane < 4; lane++) begin
if (lane < s_rx_active_bytes_q) begin
s_rx_word_d[lane*8+(int'(7)-int'(s_rx_bit_q))] = dat_di_i[lane];
s_rx_crc_lanes_d[lane*16+:16] =
sdio_pkg::sdio_crc16_next(s_rx_crc_lanes_q[lane*16+:16], dat_di_i[lane]);
end
end
if (s_rx_bit_q == 4'd7) begin
s_rx_pending_data_d = s_rx_word_d;
s_rx_pending_strb_d = (4'b0001 << s_rx_active_bytes_q) - 1'b1;
s_rx_pending_last_d =
s_rx_total_q + {29'd0, s_rx_active_bytes_q} >= s_target_bytes_q;
s_rx_total_d = s_rx_total_q + {29'd0, s_rx_active_bytes_q};
s_state_d = ReadEmit;
end else begin
s_rx_bit_d = s_rx_bit_q + 1'b1;
end
end else begin
s_rx_byte_d = s_rx_byte_next;
if (s_rx_bit_q == 4'd7) begin
s_rx_word_d[s_rx_byte_didx_q*8+:8] = s_rx_byte_next;
s_rx_byte_d = '0;
if (s_rx_byte_didx_q + 1'b1 >= s_rx_active_bytes_q) begin
s_rx_pending_data_d = s_rx_word_d;
s_rx_pending_strb_d = (4'b0001 << s_rx_active_bytes_q) - 1'b1;
s_rx_pending_last_d =
s_rx_total_q + {29'd0, s_rx_active_bytes_q} >= s_target_bytes_q;
s_rx_total_d = s_rx_total_q + {29'd0, s_rx_active_bytes_q};
s_state_d = ReadEmit;
end else begin
s_rx_byte_didx_d = s_rx_byte_didx_q + 1'b1;
s_rx_bit_d = '0;
end
s_rx_crc_d = sdio_pkg::sdio_crc16_byte(s_rx_crc_q, s_rx_byte_next);
end else begin
s_rx_bit_d = s_rx_bit_q + 1'b1;
end
end
end
end
ReadEmit: begin
if (rx_valid_o && rx_ready_i) begin
if (s_rx_pending_last_q) begin
s_crc_bit_d = '0;
s_state_d = ReadCrc;
end else begin
s_rx_word_d = '0;
s_rx_byte_d = '0;
s_rx_active_bytes_d = (s_rx_remaining >= 32'd4) ? 3'd4 : s_rx_remaining[2:0];
s_rx_byte_didx_d = '0;
s_rx_bit_d = '0;
s_state_d = ReadPayload;
s_timeout_count_d = '0;
end
end
end
ReadCrc: begin
if (sample_tick_i) begin
if (s_width4_q) begin
for (int lane = 0; lane < 4; lane++) begin
if (lane < s_rx_active_bytes_q) begin
s_rx_crc_received_lanes_d[lane*16+:16] = {
s_rx_crc_received_lanes_q[lane*16+14-:15], dat_di_i[lane]
};
end
end
if (s_crc_bit_q == 5'd15) begin
for (int lane = 0; lane < 4; lane++) begin
if ((lane < s_rx_active_bytes_q) &&
(s_rx_crc_received_lanes_d[lane*16+:16] !=
s_rx_crc_lanes_q[lane*16+:16])) begin
s_err_d = 1'b1;
s_crc_err_d = 1'b1;
end
end
s_state_d = Idle;
s_done_d = 1'b1;
end else begin
s_crc_bit_d = s_crc_bit_q + 1'b1;
end
end else begin
s_rx_crc_received_d = {s_rx_crc_received_q[14:0], dat_di_i[0]};
if (s_crc_bit_q == 5'd15) begin
if ({s_rx_crc_received_q[14:0], dat_di_i[0]} != s_rx_crc_q) begin
s_err_d = 1'b1;
s_crc_err_d = 1'b1;
end
s_state_d = Idle;
s_done_d = 1'b1;
end else begin
s_crc_bit_d = s_crc_bit_q + 1'b1;
end
end
end
end
default: begin
s_state_d = Idle;
s_err_d = 1'b1;
end
endcase
end
end
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_state_q <= Idle;
s_width4_q <= 1'b0;
s_target_bytes_q <= '0;
s_tx_total_q <= '0;
s_rx_total_q <= '0;
s_tx_word_q <= '0;
s_tx_strb_q <= '0;
s_tx_active_bytes_q <= '0;
s_tx_byte_q <= '0;
s_tx_bit_q <= '0;
s_tx_crc_q <= '0;
s_tx_crc_lanes_q <= '0;
s_tx_token_launched_q <= 1'b0;
s_crc_bit_q <= '0;
s_tx_crc_final_hold_q <= 1'b0;
s_rx_word_q <= '0;
s_rx_byte_q <= '0;
s_rx_active_bytes_q <= '0;
s_rx_byte_didx_q <= '0;
s_rx_bit_q <= '0;
s_rx_crc_q <= '0;
s_rx_crc_lanes_q <= '0;
s_rx_crc_received_q <= '0;
s_rx_crc_received_lanes_q <= '0;
s_rx_pending_data_q <= '0;
s_rx_pending_strb_q <= '0;
s_rx_pending_last_q <= 1'b0;
s_resp_token_q <= '0;
s_resp_bit_q <= '0;
s_timeout_count_q <= '0;
s_done_q <= 1'b0;
s_err_q <= 1'b0;
s_timeout_q <= 1'b0;
s_crc_err_q <= 1'b0;
s_busy_timeout_q <= 1'b0;
end else begin
s_state_q <= s_state_d;
s_width4_q <= s_width4_d;
s_target_bytes_q <= s_target_bytes_d;
s_tx_total_q <= s_tx_total_d;
s_rx_total_q <= s_rx_total_d;
s_tx_word_q <= s_tx_word_d;
s_tx_strb_q <= s_tx_strb_d;
s_tx_active_bytes_q <= s_tx_active_bytes_d;
s_tx_byte_q <= s_tx_byte_d;
s_tx_bit_q <= s_tx_bit_d;
s_tx_crc_q <= s_tx_crc_d;
s_tx_crc_lanes_q <= s_tx_crc_lanes_d;
s_tx_token_launched_q <= s_tx_token_launched_d;
s_crc_bit_q <= s_crc_bit_d;
s_tx_crc_final_hold_q <= s_tx_crc_final_hold_d;
s_rx_word_q <= s_rx_word_d;
s_rx_byte_q <= s_rx_byte_d;
s_rx_active_bytes_q <= s_rx_active_bytes_d;
s_rx_byte_didx_q <= s_rx_byte_didx_d;
s_rx_bit_q <= s_rx_bit_d;
s_rx_crc_q <= s_rx_crc_d;
s_rx_crc_lanes_q <= s_rx_crc_lanes_d;
s_rx_crc_received_q <= s_rx_crc_received_d;
s_rx_crc_received_lanes_q <= s_rx_crc_received_lanes_d;
s_rx_pending_data_q <= s_rx_pending_data_d;
s_rx_pending_strb_q <= s_rx_pending_strb_d;
s_rx_pending_last_q <= s_rx_pending_last_d;
s_resp_token_q <= s_resp_token_d;
s_resp_bit_q <= s_resp_bit_d;
s_timeout_count_q <= s_timeout_count_d;
s_done_q <= s_done_d;
s_err_q <= s_err_d;
s_timeout_q <= s_timeout_d;
s_crc_err_q <= s_crc_err_d;
s_busy_timeout_q <= s_busy_timeout_d;
end
end
`ifndef SYNTHESIS
initial begin
if (TimeoutWidth < 1) begin
$fatal(1, "sdio_data: TimeoutWidth must be positive");
end
end
`endif
endmodule
+122
View File
@@ -0,0 +1,122 @@
// Copyright (c) 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:
// 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 RETROSOC_SDIO_DEFINE_SVH
`define RETROSOC_SDIO_DEFINE_SVH
// verilog_format: off -- preserve the reviewed APB ABI column alignment
`define APB4_SDIO__IP_ID 12'h000
`define APB4_SDIO__IP_VERSION 12'h004
`define APB4_SDIO__CAPABILITY 12'h008
`define APB4_SDIO__HOST_CTRL 12'h00C
`define APB4_SDIO__CLOCK_CTRL 12'h010
`define APB4_SDIO__CLOCK_ACTUAL 12'h014
`define APB4_SDIO__BUS_CTRL 12'h018
`define APB4_SDIO__TIMEOUT_CMD 12'h01C
`define APB4_SDIO__TIMEOUT_DATA 12'h020
`define APB4_SDIO__TIMEOUT_BUSY 12'h024
`define APB4_SDIO__STATUS 12'h028
`define APB4_SDIO__PRESENT 12'h02C
`define APB4_SDIO__CMD_ARG 12'h040
`define APB4_SDIO__CMD_CFG 12'h044
`define APB4_SDIO__CMD_START 12'h048
`define APB4_SDIO__CMD_STATUS 12'h04C
`define APB4_SDIO__RESP0 12'h050
`define APB4_SDIO__RESP1 12'h054
`define APB4_SDIO__RESP2 12'h058
`define APB4_SDIO__RESP3 12'h05C
`define APB4_SDIO__RESP4 12'h060
`define APB4_SDIO__BLOCK_SIZE 12'h080
`define APB4_SDIO__BLOCK_COUNT 12'h084
`define APB4_SDIO__DATA_CFG 12'h088
`define APB4_SDIO__DATA_START 12'h08C
`define APB4_SDIO__PIO_DATA 12'h090
`define APB4_SDIO__FIFO_STATUS 12'h094
`define APB4_SDIO__FIFO_WATERMARK 12'h098
`define APB4_SDIO__DESC_BASE 12'h0C0
`define APB4_SDIO__DESC_COUNT 12'h0C4
`define APB4_SDIO__DMA_CTRL 12'h0C8
`define APB4_SDIO__DMA_STATUS 12'h0CC
`define APB4_SDIO__CURRENT_DESC 12'h0D0
`define APB4_SDIO__BYTES_DONE 12'h0D4
`define APB4_SDIO__DMA_ERROR_ADDR 12'h0D8
`define APB4_SDIO__DMA_ERROR 12'h0DC
`define APB4_SDIO__IRQ_STATUS 12'h100
`define APB4_SDIO__IRQ_ENABLE 12'h104
`define APB4_SDIO__IRQ_TEST 12'h108
`define APB4_SDIO__ERROR_STATUS 12'h10C
`define APB4_SDIO__ERROR_ABORT 7
`define APB4_SDIO__LAST_CMD 12'h110
`define APB4_SDIO__CRC_ERROR_COUNT 12'h114
`define APB4_SDIO__TIMEOUT_COUNT 12'h118
`define APB4_SDIO__AXI_ERROR_COUNT 12'h11C
`define APB4_SDIO__STALL_COUNT 12'h120
`define APB4_SDIO__DEBUG 12'h124
`define APB4_SDIO__HOST_CTRL_ENABLE 0
`define APB4_SDIO__HOST_CTRL_ABORT 1
`define APB4_SDIO__HOST_CTRL_IRQ 2
`define APB4_SDIO__CLOCK_CTRL_ENABLE 0
`define APB4_SDIO__CLOCK_CTRL_HALF_PERIOD_LSB 8
`define APB4_SDIO__BUS_CTRL_WIDTH_LSB 0
`define APB4_SDIO__BUS_CTRL_WIDTH_1 0
`define APB4_SDIO__BUS_CTRL_WIDTH_4 1
`define APB4_SDIO__CMD_CFG_INDEX_LSB 0
`define APB4_SDIO__CMD_CFG_RESP_LSB 8
`define APB4_SDIO__CMD_CFG_CRC_CHECK 12
`define APB4_SDIO__CMD_CFG_INDEX_CHECK 13
`define APB4_SDIO__RESP_NONE 0
`define APB4_SDIO__RESP_R1 1
`define APB4_SDIO__RESP_R1B 2
`define APB4_SDIO__RESP_R2 3
`define APB4_SDIO__RESP_R3 4
`define APB4_SDIO__RESP_R4 5
`define APB4_SDIO__RESP_R5 6
`define APB4_SDIO__RESP_R6 7
`define APB4_SDIO__RESP_R7 8
`define APB4_SDIO__DATA_CFG_DIRECTION 0
`define APB4_SDIO__DATA_CFG_WIDTH_LSB 1
`define APB4_SDIO__DATA_CFG_DMA 4
`define APB4_SDIO__DATA_CFG_BLOCK 5
`define APB4_SDIO__DATA_CFG_FIXED 6
`define APB4_SDIO__DMA_CTRL_START 0
`define APB4_SDIO__DMA_CTRL_ABORT 1
`define APB4_SDIO__DESC_OWN 0
`define APB4_SDIO__DESC_CHAIN 1
`define APB4_SDIO__DESC_END 2
`define APB4_SDIO__DESC_IRQ 3
`define APB4_SDIO__DESC_DONE 16
`define APB4_SDIO__DESC_ERROR 17
`define APB4_SDIO__IRQ_CMD_DONE 0
`define APB4_SDIO__IRQ_DATA_DONE 1
`define APB4_SDIO__IRQ_DMA_DONE 2
`define APB4_SDIO__IRQ_CMD_ERROR 3
`define APB4_SDIO__IRQ_DATA_ERROR 4
`define APB4_SDIO__IRQ_DMA_ERROR 5
`define APB4_SDIO__IRQ_DAT1 6
`define APB4_SDIO__IRQ_ABORT 7
`define APB4_SDIO__CAP_SD_MEMORY_V2 0
`define APB4_SDIO__CAP_SDIO_V2 1
`define APB4_SDIO__CAP_BUS_1BIT 2
`define APB4_SDIO__CAP_BUS_4BIT 3
`define APB4_SDIO__CAP_SDR 4
`define APB4_SDIO__CAP_SG_DMA 5
`define APB4_SDIO__CAP_PIO 6
`define APB4_SDIO__CAP_CRC 7
`define APB4_SDIO__CAP_MAX_BURST_16 8
// verilog_format: on
`endif
+646
View File
@@ -0,0 +1,646 @@
// Copyright (c) 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:
// 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 THE WARRANTIES OF
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
`include "axi4_define.svh"
module sdio_dma #(
parameter int AddrWidth = 32,
parameter int DataWidth = 32,
parameter int DescCount = 16
) (
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic abort_i,
input logic direction_i,
input logic [AddrWidth-1:0] desc_base_i,
input logic [ 15:0] desc_count_i,
input logic [ 31:0] total_bytes_i,
input logic data_in_valid_i,
output logic data_in_ready_o,
input logic [ 31:0] data_in_i,
input logic [ 3:0] data_in_strb_i,
input logic data_in_last_i,
output logic data_out_valid_o,
input logic data_out_ready_i,
output logic [ 31:0] data_out_o,
output logic [ 3:0] data_out_strb_o,
output logic data_out_last_o,
output logic busy_o,
output logic done_o,
output logic error_o,
output logic [ 7:0] error_code_o,
output logic [AddrWidth-1:0] current_desc_o,
output logic [ 31:0] bytes_done_o,
output logic [AddrWidth-1:0] error_addr_o,
output logic descriptor_irq_o,
output logic abort_done_o,
axi4_if.master dma_axi4
);
localparam logic [15:0] DescCountLimit = 16'(DescCount);
typedef enum logic [3:0] {
Idle,
FetchStart,
FetchWait,
Validate,
PayloadReadStart,
PayloadReadWait,
PayloadWriteStart,
PayloadWriteWait,
WritebackStart,
WritebackWait,
Terminal
} dma_state_e;
dma_state_e s_state_d, s_state_q;
logic [AddrWidth-1:0] s_desc_addr_d, s_desc_addr_q;
logic [15:0] s_desc_limit_d, s_desc_limit_q;
logic [15:0] s_desc_index_d, s_desc_index_q;
logic s_direction_d, s_direction_q;
logic [31:0] s_total_bytes_d, s_total_bytes_q;
logic [31:0] s_total_seen_d, s_total_seen_q;
logic [31:0] s_total_done_d, s_total_done_q;
logic [31:0] s_desc_progress_d, s_desc_progress_q;
logic [4:0] s_burst_beats_d, s_burst_beats_q;
logic [4:0] s_burst_index_d, s_burst_index_q;
logic [31:0] s_desc_buffer_d, s_desc_buffer_q;
logic [31:0] s_desc_count_d, s_desc_count_q;
logic [31:0] s_desc_next_d, s_desc_next_q;
logic [31:0] s_desc_control_d, s_desc_control_q;
logic [31:0] s_writeback_control_d, s_writeback_control_q;
logic [31:0] s_fetch_word_d[0:3];
logic [31:0] s_fetch_word_q[0:3];
logic s_desc_end_d, s_desc_end_q;
logic s_desc_irq_d, s_desc_irq_q;
logic s_fault_d, s_fault_q;
logic [7:0] s_err_code_d, s_err_code_q;
logic [AddrWidth-1:0] s_err_addr_d, s_err_addr_q;
logic s_done_d, s_done_q;
logic s_err_sticky_d, s_err_sticky_q;
logic s_abort_pending_d, s_abort_pending_q;
logic s_write_final_sent_d, s_write_final_sent_q;
logic [4:0] s_write_beat_count_d, s_write_beat_count_q;
logic s_descriptor_irq_d, s_descriptor_irq_q;
logic s_abort_done_d, s_abort_done_q;
logic s_read_start_valid;
logic s_read_start_ready;
logic [AddrWidth-1:0] s_read_addr;
logic [ 4:0] s_read_beats;
logic s_read_beat_valid;
logic s_read_beat_ready;
logic [DataWidth-1:0] s_read_data;
logic [ 1:0] s_read_resp;
logic s_read_last;
logic s_read_expected_last;
logic s_read_id_error;
logic s_read_done;
logic s_write_start_valid;
logic s_write_start_ready;
logic [AddrWidth-1:0] s_write_addr;
logic [ 4:0] s_write_beats;
logic s_write_data_valid;
logic s_write_data_ready;
logic [DataWidth-1:0] s_write_data;
logic [ 3:0] s_write_strb;
logic s_write_done;
logic [ 1:0] s_write_resp;
logic s_write_id_error;
logic s_read_busy;
logic s_write_busy;
logic [ 31:0] s_payload_addr;
logic [ 31:0] s_payload_left;
logic [ 4:0] s_payload_beats;
logic [ 31:0] s_burst_offset;
logic [ 31:0] s_beat_left;
logic [ 2:0] s_beat_bytes;
logic [ 31:0] s_progress_after_beat;
logic [ 2:0] s_input_bytes;
logic s_axi_read_fault;
logic s_axi_write_fault;
function automatic logic [4:0] choose_burst(input logic [11:0] addr_low_i,
input logic [31:0] bytes_i);
logic [31:0] needed;
logic [31:0] boundary;
logic [31:0] selected;
begin
needed = (bytes_i + 32'd3) >> 2;
boundary = (32'd4096 - {20'd0, addr_low_i}) >> 2;
selected = needed;
if (selected > 32'd16) selected = 32'd16;
if (selected > boundary) selected = boundary;
if (selected == 32'd0) selected = 32'd1;
return selected[4:0];
end
endfunction
function automatic logic [2:0] beat_bytes(input logic [31:0] bytes_i);
return (bytes_i >= 32'd4) ? 3'd4 : bytes_i[2:0];
endfunction
function automatic logic strobe_valid(input logic [3:0] strb_i);
return (strb_i != 4'd0) && ((strb_i & (strb_i + 4'd1)) == 4'd0);
endfunction
assign s_payload_addr = s_desc_buffer_q + s_desc_progress_q;
assign s_payload_left = s_desc_count_q - s_desc_progress_q;
assign s_payload_beats = choose_burst(s_payload_addr[11:0], s_payload_left);
assign s_burst_offset = {27'd0, s_burst_index_q} << 2;
assign s_beat_left = s_payload_left;
assign s_beat_bytes = beat_bytes(s_beat_left);
assign s_progress_after_beat = s_desc_progress_q + {29'd0, s_beat_bytes};
assign s_input_bytes = count_strobe(data_in_strb_i);
assign s_axi_read_fault = (s_read_resp != `AXI4_RESP_OKAY) || s_read_id_error ||
(s_read_last != s_read_expected_last);
assign s_axi_write_fault = (s_write_resp != `AXI4_RESP_OKAY) || s_write_id_error;
function automatic logic [2:0] count_strobe(input logic [3:0] strb_i);
logic [2:0] count;
begin
count = 3'd0;
for (int index = 0; index < 4; index++) begin
if (strb_i[index]) count = count + 1'b1;
end
return count;
end
endfunction
assign s_read_start_valid = !abort_i &&
((s_state_q == FetchStart) || (s_state_q == PayloadReadStart));
assign s_read_addr = (s_state_q == FetchStart) ? s_desc_addr_q : s_payload_addr;
assign s_read_beats = (s_state_q == FetchStart) ? 5'd4 : s_payload_beats;
assign s_read_beat_ready = (s_state_q == FetchWait) ||
((s_state_q == PayloadReadWait) &&
(abort_i || s_abort_pending_q || data_out_ready_i));
assign s_write_start_valid = (s_state_q == PayloadWriteStart) || (s_state_q == WritebackStart);
assign s_write_addr = (s_state_q == PayloadWriteStart) ? s_payload_addr : s_desc_addr_q + 32'd12;
assign s_write_beats = (s_state_q == PayloadWriteStart) ? s_payload_beats : 5'd1;
assign s_write_data_valid = (s_state_q == PayloadWriteWait)
? (s_abort_pending_q || data_in_valid_i)
: (s_state_q == WritebackWait);
assign s_write_data = (s_state_q == PayloadWriteWait && s_abort_pending_q)
? '0
: (s_state_q == WritebackWait ? s_writeback_control_q : data_in_i);
assign s_write_strb = (s_state_q == PayloadWriteWait && s_abort_pending_q)
? 4'b0000
: (s_state_q == WritebackWait ? 4'b1111 : data_in_strb_i);
assign data_in_ready_o = (s_state_q == PayloadWriteWait) && !s_abort_pending_q &&
s_write_data_ready;
assign data_out_valid_o = (s_state_q == PayloadReadWait) && s_read_beat_valid &&
!s_abort_pending_q && !s_axi_read_fault;
assign data_out_o = s_read_data;
assign data_out_strb_o = (4'b0001 << s_beat_bytes) - 1'b1;
assign data_out_last_o = s_desc_end_q && (s_progress_after_beat >= s_desc_count_q);
assign busy_o = ((s_state_q != Idle) && (s_state_q != Terminal)) || s_read_busy || s_write_busy;
assign done_o = s_done_q;
assign error_o = s_err_sticky_q;
assign error_code_o = s_err_code_q;
assign current_desc_o = s_desc_addr_q;
assign bytes_done_o = s_total_done_q;
assign error_addr_o = s_err_addr_q;
assign descriptor_irq_o = s_descriptor_irq_q;
assign abort_done_o = s_abort_done_q;
dma_axi4_master #(
.AddrWidth (AddrWidth),
.DataWidth (DataWidth),
.MaxBurstBeats(16)
) u_dma_axi4_master (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.read_start_valid_i (s_read_start_valid),
.read_start_ready_o (s_read_start_ready),
.read_addr_i (s_read_addr),
.read_beats_i (s_read_beats),
.read_fixed_i (1'b0),
.read_busy_o (s_read_busy),
.read_beat_valid_o (s_read_beat_valid),
.read_beat_ready_i (s_read_beat_ready),
.read_data_o (s_read_data),
.read_resp_o (s_read_resp),
.read_last_o (s_read_last),
.read_expected_last_o(s_read_expected_last),
.read_id_error_o (s_read_id_error),
.read_done_o (s_read_done),
.write_start_valid_i (s_write_start_valid),
.write_start_ready_o (s_write_start_ready),
.write_addr_i (s_write_addr),
.write_beats_i (s_write_beats),
.write_fixed_i (1'b0),
.write_busy_o (s_write_busy),
.write_data_valid_i (s_write_data_valid),
.write_data_ready_o (s_write_data_ready),
.write_data_i (s_write_data),
.write_strb_i (s_write_strb),
.write_done_o (s_write_done),
.write_resp_o (s_write_resp),
.write_id_error_o (s_write_id_error),
.axi4 (dma_axi4)
);
always_comb begin
s_state_d = s_state_q;
s_desc_addr_d = s_desc_addr_q;
s_desc_limit_d = s_desc_limit_q;
s_desc_index_d = s_desc_index_q;
s_direction_d = s_direction_q;
s_total_bytes_d = s_total_bytes_q;
s_total_seen_d = s_total_seen_q;
s_total_done_d = s_total_done_q;
s_desc_progress_d = s_desc_progress_q;
s_burst_beats_d = s_burst_beats_q;
s_burst_index_d = s_burst_index_q;
s_desc_buffer_d = s_desc_buffer_q;
s_desc_count_d = s_desc_count_q;
s_desc_next_d = s_desc_next_q;
s_desc_control_d = s_desc_control_q;
s_writeback_control_d = s_writeback_control_q;
s_desc_end_d = s_desc_end_q;
s_desc_irq_d = s_desc_irq_q;
s_fault_d = s_fault_q;
s_err_code_d = s_err_code_q;
s_err_addr_d = s_err_addr_q;
s_done_d = 1'b0;
s_err_sticky_d = s_err_sticky_q;
s_abort_pending_d = s_abort_pending_q;
s_write_final_sent_d = s_write_final_sent_q;
s_write_beat_count_d = s_write_beat_count_q;
s_descriptor_irq_d = 1'b0;
s_abort_done_d = 1'b0;
for (int index = 0; index < 4; index++) begin
s_fetch_word_d[index] = s_fetch_word_q[index];
end
if (s_state_q == Idle) begin
if (start_i) begin
s_desc_addr_d = desc_base_i;
s_desc_limit_d = desc_count_i;
s_desc_index_d = '0;
s_direction_d = direction_i;
s_total_bytes_d = total_bytes_i;
s_total_seen_d = '0;
s_total_done_d = '0;
s_desc_progress_d = '0;
s_fault_d = 1'b0;
s_err_code_d = '0;
s_err_addr_d = '0;
s_err_sticky_d = 1'b0;
s_abort_pending_d = 1'b0;
if ((desc_base_i[3:0] != 4'b0000) || (desc_base_i[11:0] >= 12'hFF0) ||
(desc_count_i == 16'd0) || (desc_count_i > DescCountLimit)) begin
s_state_d = Terminal;
s_done_d = 1'b1;
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h02;
s_err_addr_d = desc_base_i;
end else begin
s_state_d = FetchStart;
end
end
end else if (s_state_q == FetchStart) begin
if (abort_i) begin
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h08;
s_err_addr_d = s_desc_addr_q;
s_state_d = Terminal;
s_done_d = 1'b1;
s_abort_done_d = 1'b1;
end else if (s_desc_addr_q[3:0] != 4'b0000 || s_desc_addr_q[11:0] >= 12'hFF0) begin
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h02;
s_err_addr_d = s_desc_addr_q;
s_state_d = Terminal;
s_done_d = 1'b1;
end else if (s_read_start_ready) begin
s_burst_index_d = '0;
s_state_d = FetchWait;
end
end else begin
unique case (s_state_q)
FetchWait: begin
if (abort_i) begin
s_abort_pending_d = 1'b1;
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h08;
s_err_addr_d = s_desc_addr_q;
end
if (s_read_beat_valid && s_read_beat_ready) begin
s_fetch_word_d[s_burst_index_q[1:0]] = s_read_data;
if (s_axi_read_fault) begin
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h04;
s_err_addr_d = s_desc_addr_q;
end
if (s_read_done) begin
s_abort_pending_d = 1'b0;
s_state_d = Validate;
end else begin
s_burst_index_d = s_burst_index_q + 1'b1;
end
end
end
Validate: begin
s_desc_buffer_d = s_fetch_word_q[0];
s_desc_count_d = s_fetch_word_q[1];
s_desc_next_d = s_fetch_word_q[2];
s_desc_control_d = s_fetch_word_q[3];
s_desc_end_d = s_fetch_word_q[3][sdio_pkg::SDIO_DESC_END];
s_desc_irq_d = s_fetch_word_q[3][sdio_pkg::SDIO_DESC_IRQ];
s_desc_progress_d = '0;
s_total_seen_d = s_total_seen_q + s_fetch_word_q[1];
if (!s_fetch_word_q[3][sdio_pkg::SDIO_DESC_OWN] ||
(s_fetch_word_q[0][1:0] != 2'b00) ||
(s_fetch_word_q[1] == 32'd0) ||
(s_fetch_word_q[3][31:18] != 14'd0) ||
(s_fetch_word_q[3][sdio_pkg::SDIO_DESC_END] &&
((s_fetch_word_q[2] != 32'd0) ||
s_fetch_word_q[3][sdio_pkg::SDIO_DESC_CHAIN])) ||
(!s_fetch_word_q[3][sdio_pkg::SDIO_DESC_END] &&
(!s_fetch_word_q[3][sdio_pkg::SDIO_DESC_CHAIN] ||
(s_fetch_word_q[2][3:0] != 4'b0000) ||
(s_fetch_word_q[2][11:0] >= 12'hFF0) ||
(s_fetch_word_q[2] == 32'd0) ||
(s_desc_index_q + 1'b1 >= s_desc_limit_q))) ||
((s_total_bytes_q != 32'd0) &&
((s_total_seen_q + s_fetch_word_q[1] > s_total_bytes_q) ||
(s_fetch_word_q[3][sdio_pkg::SDIO_DESC_END] &&
(s_total_seen_q + s_fetch_word_q[1] != s_total_bytes_q)))) ||
s_fault_q) begin
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
if (s_err_code_q == 8'd0) s_err_code_d = 8'h02;
end
s_writeback_control_d = (s_fetch_word_q[3] &
~(32'd1 << sdio_pkg::SDIO_DESC_OWN)) |
(s_fault_d ? (32'd1 << sdio_pkg::SDIO_DESC_ERROR) :
(32'd1 << sdio_pkg::SDIO_DESC_DONE));
if (!s_fetch_word_q[3][sdio_pkg::SDIO_DESC_OWN]) begin
s_state_d = Terminal;
s_done_d = 1'b1;
end else if (s_fault_d || s_abort_pending_q) begin
s_state_d = WritebackStart;
end else if (s_direction_q == sdio_pkg::SdioDataFromCard) begin
// Card-to-host consumes the stream and AXI-writes memory.
s_state_d = PayloadWriteStart;
end else begin
// Host-to-card AXI-reads memory and produces the stream.
s_state_d = PayloadReadStart;
end
end
PayloadReadStart: begin
if (abort_i) begin
s_abort_pending_d = 1'b1;
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h08;
s_err_addr_d = s_payload_addr;
s_state_d = WritebackStart;
end else if (s_read_start_ready) begin
s_burst_beats_d = s_payload_beats;
s_burst_index_d = '0;
s_abort_pending_d = 1'b0;
s_state_d = PayloadReadWait;
end
end
PayloadReadWait: begin
if (abort_i) begin
s_abort_pending_d = 1'b1;
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h08;
s_err_addr_d = s_payload_addr;
end
if (s_read_beat_valid && s_read_beat_ready) begin
if (s_axi_read_fault) begin
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h04;
s_err_addr_d = s_payload_addr + s_burst_offset;
end
if (!s_abort_pending_q && !abort_i && !s_axi_read_fault) begin
s_desc_progress_d = s_progress_after_beat;
s_total_done_d = s_total_done_q + {29'd0, s_beat_bytes};
end
if (s_read_done) begin
s_abort_pending_d = 1'b0;
if (s_fault_d || abort_i || s_abort_pending_q) begin
s_writeback_control_d = (s_desc_control_q &
~(32'd1 << sdio_pkg::SDIO_DESC_OWN)) |
(32'd1 << sdio_pkg::SDIO_DESC_ERROR);
s_state_d = WritebackStart;
end else if (s_progress_after_beat >= s_desc_count_q) begin
s_writeback_control_d = (s_desc_control_q &
~(32'd1 << sdio_pkg::SDIO_DESC_OWN)) |
(32'd1 << sdio_pkg::SDIO_DESC_DONE);
s_state_d = WritebackStart;
end else begin
s_state_d = PayloadReadStart;
end
end else begin
s_burst_index_d = s_burst_index_q + 1'b1;
end
end
end
PayloadWriteStart: begin
if (abort_i) begin
s_abort_pending_d = 1'b1;
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h08;
s_err_addr_d = s_payload_addr;
s_writeback_control_d = (s_desc_control_q &
~(32'd1 << sdio_pkg::SDIO_DESC_OWN)) |
(32'd1 << sdio_pkg::SDIO_DESC_ERROR);
s_state_d = WritebackStart;
end else if (s_write_start_ready) begin
s_burst_beats_d = s_payload_beats;
s_burst_index_d = '0;
s_write_beat_count_d = '0;
s_write_final_sent_d = 1'b0;
s_state_d = PayloadWriteWait;
end
end
PayloadWriteWait: begin
if (abort_i) begin
s_abort_pending_d = 1'b1;
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h08;
s_err_addr_d = s_payload_addr + s_burst_offset;
end
if (s_write_data_valid && s_write_data_ready) begin
if (!s_abort_pending_q && !abort_i) begin
if (!strobe_valid(
data_in_strb_i
) || (s_input_bytes != s_beat_bytes) ||
(data_in_last_i !=
(s_desc_end_q && (s_progress_after_beat >= s_desc_count_q)))) begin
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h10;
end
s_desc_progress_d = s_progress_after_beat;
s_total_done_d = s_total_done_q + {29'd0, s_beat_bytes};
end
s_write_beat_count_d = s_write_beat_count_q + 1'b1;
s_burst_index_d = s_burst_index_q + 1'b1;
if (s_write_beat_count_q + 1'b1 >= s_burst_beats_q) begin
s_write_final_sent_d = 1'b1;
end
end
if (s_write_done) begin
if (s_axi_write_fault) begin
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h04;
s_err_addr_d = s_payload_addr + s_burst_offset;
end
if (s_write_final_sent_q) begin
s_writeback_control_d = (s_desc_control_q &
~(32'd1 << sdio_pkg::SDIO_DESC_OWN)) |
(s_fault_d ? (32'd1 << sdio_pkg::SDIO_DESC_ERROR) :
(32'd1 << sdio_pkg::SDIO_DESC_DONE));
s_state_d = WritebackStart;
end
end
end
WritebackStart: begin
if (s_write_start_ready) begin
s_state_d = WritebackWait;
end
end
WritebackWait: begin
if (s_write_done) begin
if (s_axi_write_fault) begin
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h04;
s_err_addr_d = s_desc_addr_q + 32'd12;
end
if (s_fault_q || s_fault_d || s_axi_write_fault ||
s_writeback_control_q[sdio_pkg::SDIO_DESC_ERROR]) begin
s_abort_done_d = s_abort_pending_q ||
(s_writeback_control_q[sdio_pkg::SDIO_DESC_ERROR] &&
(s_err_code_q == 8'h08));
s_state_d = Terminal;
s_done_d = 1'b1;
end else if (s_desc_end_q) begin
s_descriptor_irq_d = s_desc_irq_q;
s_state_d = Terminal;
s_done_d = 1'b1;
end else begin
s_descriptor_irq_d = s_desc_irq_q;
s_desc_index_d = s_desc_index_q + 1'b1;
s_desc_addr_d = s_desc_next_q;
s_state_d = FetchStart;
end
end
end
Terminal: begin
s_state_d = Idle;
end
default: begin
s_state_d = Idle;
s_fault_d = 1'b1;
s_err_sticky_d = 1'b1;
s_err_code_d = 8'h80;
end
endcase
end
end
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_state_q <= Idle;
s_desc_addr_q <= '0;
s_desc_limit_q <= '0;
s_desc_index_q <= '0;
s_direction_q <= sdio_pkg::SdioDataFromCard;
s_total_bytes_q <= '0;
s_total_seen_q <= '0;
s_total_done_q <= '0;
s_desc_progress_q <= '0;
s_burst_beats_q <= '0;
s_burst_index_q <= '0;
s_desc_buffer_q <= '0;
s_desc_count_q <= '0;
s_desc_next_q <= '0;
s_desc_control_q <= '0;
s_writeback_control_q <= '0;
s_desc_end_q <= 1'b0;
s_desc_irq_q <= 1'b0;
s_fault_q <= 1'b0;
s_err_code_q <= '0;
s_err_addr_q <= '0;
s_done_q <= 1'b0;
s_err_sticky_q <= 1'b0;
s_abort_pending_q <= 1'b0;
s_write_final_sent_q <= 1'b0;
s_write_beat_count_q <= '0;
s_descriptor_irq_q <= 1'b0;
s_abort_done_q <= 1'b0;
for (int index = 0; index < 4; index++) begin
s_fetch_word_q[index] <= '0;
end
end else begin
s_state_q <= s_state_d;
s_desc_addr_q <= s_desc_addr_d;
s_desc_limit_q <= s_desc_limit_d;
s_desc_index_q <= s_desc_index_d;
s_direction_q <= s_direction_d;
s_total_bytes_q <= s_total_bytes_d;
s_total_seen_q <= s_total_seen_d;
s_total_done_q <= s_total_done_d;
s_desc_progress_q <= s_desc_progress_d;
s_burst_beats_q <= s_burst_beats_d;
s_burst_index_q <= s_burst_index_d;
s_desc_buffer_q <= s_desc_buffer_d;
s_desc_count_q <= s_desc_count_d;
s_desc_next_q <= s_desc_next_d;
s_desc_control_q <= s_desc_control_d;
s_writeback_control_q <= s_writeback_control_d;
s_desc_end_q <= s_desc_end_d;
s_desc_irq_q <= s_desc_irq_d;
s_fault_q <= s_fault_d;
s_err_code_q <= s_err_code_d;
s_err_addr_q <= s_err_addr_d;
s_done_q <= s_done_d;
s_err_sticky_q <= s_err_sticky_d;
s_abort_pending_q <= s_abort_pending_d;
s_write_final_sent_q <= s_write_final_sent_d;
s_write_beat_count_q <= s_write_beat_count_d;
s_descriptor_irq_q <= s_descriptor_irq_d;
s_abort_done_q <= s_abort_done_d;
for (int index = 0; index < 4; index++) begin
s_fetch_word_q[index] <= s_fetch_word_d[index];
end
end
end
`ifndef SYNTHESIS
initial begin
if ((AddrWidth < 4) || (DataWidth != 32) || (DescCount < 1) || (DescCount > 65535)) begin
$fatal(1, "sdio_dma: unsupported geometry");
end
end
`endif
endmodule
+56
View File
@@ -0,0 +1,56 @@
// Copyright (c) 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:
// 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 THE WARRANTIES OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
module sdio_dma_descriptor #(
parameter int DescCount = 16
) (
input logic [31:0] buffer_addr_i,
input logic [31:0] byte_count_i,
input logic [31:0] next_addr_i,
input logic [31:0] control_status_i,
input logic [15:0] desc_index_i,
output logic own_o,
output logic chain_o,
output logic end_o,
output logic irq_o,
output logic valid_o,
output logic error_o,
output logic address_error_o,
output logic length_error_o,
output logic chain_error_o
);
logic s_last_allowed;
logic s_control_valid;
assign own_o = control_status_i[sdio_pkg::SDIO_DESC_OWN];
assign chain_o = control_status_i[sdio_pkg::SDIO_DESC_CHAIN];
assign end_o = control_status_i[sdio_pkg::SDIO_DESC_END];
assign irq_o = control_status_i[sdio_pkg::SDIO_DESC_IRQ];
assign s_last_allowed = desc_index_i + 1'b1 < DescCount;
assign s_control_valid = own_o && !control_status_i[31:18];
assign address_error_o = (buffer_addr_i[1:0] != 2'b00) ||
((!end_o || chain_o) && ((next_addr_i[3:0] != 4'b0000) ||
(next_addr_i[11:0] >= 12'hFF0)));
assign length_error_o = (byte_count_i == 32'd0);
assign chain_error_o = end_o ? (next_addr_i != 32'd0) :
(!chain_o || (next_addr_i == 32'd0) || !s_last_allowed);
assign error_o = !s_control_valid || address_error_o || length_error_o || chain_error_o;
assign valid_o = !error_o;
`ifndef SYNTHESIS
initial begin
if (DescCount < 1) begin
$fatal(1, "sdio_dma_descriptor: DescCount must be positive");
end
end
`endif
endmodule
+43
View File
@@ -0,0 +1,43 @@
// Copyright (c) 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:
// 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.
`timescale 1ns / 1ps
interface sdio_if ();
logic sck_o;
logic cmd_oe_o;
logic cmd_di_i;
logic cmd_do_o;
logic [3:0] dat_oe_o;
logic [3:0] dat_di_i;
logic [3:0] dat_do_o;
logic irq_o;
modport dut(
output sck_o,
output cmd_oe_o,
input cmd_di_i,
output cmd_do_o,
output dat_oe_o,
input dat_di_i,
output dat_do_o,
output irq_o
);
modport core(
output sck_o,
output cmd_oe_o,
input cmd_di_i,
output cmd_do_o,
output dat_oe_o,
input dat_di_i,
output dat_do_o
);
endinterface
+133
View File
@@ -0,0 +1,133 @@
// Copyright (c) 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:
// 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.
`timescale 1ns / 1ps
package sdio_pkg;
typedef enum logic [3:0] {
SdioRespNone = 4'd0,
SdioRespR1 = 4'd1,
SdioRespR1b = 4'd2,
SdioRespR2 = 4'd3,
SdioRespR3 = 4'd4,
SdioRespR4 = 4'd5,
SdioRespR5 = 4'd6,
SdioRespR6 = 4'd7,
SdioRespR7 = 4'd8
} sdio_resp_type_e;
typedef enum logic {
SdioDataFromCard = 1'b0,
SdioDataToCard = 1'b1
} sdio_data_direction_e;
typedef enum logic [2:0] {
SdioClockStopped = 3'd0,
SdioClock400k = 3'd1,
SdioClock25m = 3'd2,
SdioClock50m = 3'd3
} sdio_clock_profile_e;
localparam logic [4:0] SDIO_DESC_OWN = 5'd0;
localparam logic [4:0] SDIO_DESC_CHAIN = 5'd1;
localparam logic [4:0] SDIO_DESC_END = 5'd2;
localparam logic [4:0] SDIO_DESC_IRQ = 5'd3;
localparam logic [4:0] SDIO_DESC_DONE = 5'd16;
localparam logic [4:0] SDIO_DESC_ERROR = 5'd17;
function automatic logic [6:0] sdio_crc7_next(input logic [6:0] crc_i, input logic data_i);
logic feedback;
begin
feedback = data_i ^ crc_i[6];
sdio_crc7_next = {crc_i[5:0], 1'b0};
if (feedback) begin
sdio_crc7_next = sdio_crc7_next ^ 7'h09;
end
end
endfunction
function automatic logic [6:0] sdio_crc7_calc(input logic [39:0] data_i);
logic [6:0] crc;
begin
crc = 7'd0;
for (int index = 39; index >= 0; index--) begin
crc = sdio_crc7_next(crc, data_i[index]);
end
return crc;
end
endfunction
function automatic logic [6:0] sdio_crc7_vector(input logic [135:0] data_i,
input int unsigned bit_count_i);
logic [6:0] crc;
begin
crc = 7'd0;
if ((bit_count_i >= 1) && (bit_count_i <= 136)) begin
for (int index = bit_count_i - 1; index >= 0; index--) begin
crc = sdio_crc7_next(crc, data_i[index]);
end
end
return crc;
end
endfunction
function automatic logic [6:0] sdio_crc7_response(input logic [135:0] data_i, input logic r2_i);
logic [6:0] crc;
begin
crc = 7'd0;
if (r2_i) begin
for (int index = 135; index >= 8; index--) begin
crc = sdio_crc7_next(crc, data_i[index]);
end
end else begin
for (int index = 47; index >= 8; index--) begin
crc = sdio_crc7_next(crc, data_i[index]);
end
end
return crc;
end
endfunction
function automatic logic [15:0] sdio_crc16_next(input logic [15:0] crc_i, input logic data_i);
logic feedback;
begin
feedback = data_i ^ crc_i[15];
sdio_crc16_next = {crc_i[14:0], 1'b0};
if (feedback) begin
sdio_crc16_next = sdio_crc16_next ^ 16'h1021;
end
end
endfunction
function automatic logic [15:0] sdio_crc16_byte(input logic [15:0] crc_i,
input logic [7:0] data_i);
logic [15:0] crc;
begin
crc = crc_i;
for (int index = 7; index >= 0; index--) begin
crc = sdio_crc16_next(crc, data_i[index]);
end
return crc;
end
endfunction
function automatic logic sdio_response_has_crc(input sdio_resp_type_e response_type_i);
return (response_type_i == SdioRespR1) || (response_type_i == SdioRespR1b) ||
(response_type_i == SdioRespR2) || (response_type_i == SdioRespR5) ||
(response_type_i == SdioRespR6) || (response_type_i == SdioRespR7);
endfunction
function automatic logic sdio_response_has_index(input sdio_resp_type_e response_type_i);
return (response_type_i != SdioRespNone) && (response_type_i != SdioRespR2) &&
(response_type_i != SdioRespR3) && (response_type_i != SdioRespR4);
endfunction
endpackage
+364
View File
@@ -0,0 +1,364 @@
// Copyright (c) 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:
// 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 THE WARRANTIES OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
`include "sdio_define.svh"
module sdio_reg (
// verilog_format: off -- preserve the APB and protocol port column groups
input logic clk_i,
input logic rst_n_i,
input logic busy_i,
input logic [31:0] status_i,
input logic [31:0] clock_actual_i,
input logic [31:0] cmd_status_i,
input logic [135:0] response_i,
input logic [31:0] data_status_i,
input logic [31:0] fifo_status_i,
input logic [31:0] dma_status_i,
input logic [31:0] current_desc_i,
input logic [31:0] bytes_done_i,
input logic [31:0] dma_error_addr_i,
input logic [31:0] dma_error_i,
input logic [31:0] error_status_i,
input logic [5:0] last_cmd_i,
input logic [31:0] crc_error_count_i,
input logic [31:0] timeout_count_i,
input logic [31:0] axi_error_count_i,
input logic [31:0] stall_count_i,
input logic [7:0] irq_event_i,
input logic [31:0] pio_rdata_i,
input logic pio_valid_i,
input logic pio_ready_i,
output logic host_enable_o,
output logic host_irq_enable_o,
output logic clock_enable_o,
output logic [15:0] half_period_o,
output logic [1:0] bus_width_o,
output logic [31:0] timeout_cmd_o,
output logic [31:0] timeout_data_o,
output logic [31:0] timeout_busy_o,
output logic [5:0] cmd_index_o,
output logic [31:0] cmd_arg_o,
output sdio_pkg::sdio_resp_type_e cmd_resp_type_o,
output logic cmd_crc_check_o,
output logic cmd_index_check_o,
output logic cmd_start_o,
output logic [15:0] block_size_o,
output logic [15:0] block_count_o,
output logic data_direction_o,
output logic data_dma_enable_o,
output logic data_block_mode_o,
output logic data_fixed_addr_o,
output logic data_start_o,
output logic pio_wvalid_o,
output logic [31:0] pio_wdata_o,
output logic [3:0] pio_wstrb_o,
output logic pio_read_consume_o,
output logic [31:0] desc_base_o,
output logic [15:0] desc_count_o,
output logic dma_start_o,
output logic dma_abort_o,
output logic host_abort_o,
output logic irq_dat1_enable_o,
output logic irq_o,
apb4_if.slave apb4
// verilog_format: on
);
import sdio_pkg::*;
logic s_apb4_ready_d, s_apb4_ready_q;
logic [31:0] s_apb4_rdata_d, s_apb4_rdata_q;
logic s_apb4_resp_err_d, s_apb4_resp_err_q;
logic s_req_accept;
logic s_write_access;
logic s_write_known;
logic s_write_error;
logic s_pio_write_wait;
logic s_pio_write_direction_error;
logic [31:0] s_read_data;
logic s_read_error;
logic [ 2:0] s_host_ctrl_q;
logic s_clock_en_q;
logic [15:0] s_half_period_q;
logic [ 1:0] s_bus_width_q;
logic [31:0] s_timeout_cmd_q;
logic [31:0] s_timeout_data_q;
logic [31:0] s_timeout_busy_q;
logic [ 5:0] s_cmd_index_q;
logic [31:0] s_cmd_arg_q;
logic [ 5:0] s_cmd_cfg_q;
logic [15:0] s_block_size_q;
logic [15:0] s_block_count_q;
logic [ 6:0] s_data_cfg_q;
logic [31:0] s_desc_base_q;
logic [15:0] s_desc_count_q;
logic [ 7:0] s_irq_stat_q;
logic [ 7:0] s_irq_en_q;
logic [31:0] s_err_stat_q;
assign s_pio_write_direction_error = (s_data_cfg_q[0] == 1'b0);
assign s_pio_write_wait = apb4.psel && apb4.penable && apb4.pwrite &&
(apb4.paddr[11:0] == `APB4_SDIO__PIO_DATA) && !pio_ready_i &&
!s_pio_write_direction_error;
assign s_req_accept = apb4.psel && apb4.penable && !s_apb4_ready_q && !s_pio_write_wait;
assign s_write_access = s_req_accept && apb4.pwrite;
assign apb4.pready = s_apb4_ready_q;
assign apb4.prdata = s_apb4_rdata_q;
assign apb4.pslverr = s_apb4_resp_err_q;
assign host_enable_o = s_host_ctrl_q[0];
assign host_irq_enable_o = s_host_ctrl_q[2];
assign clock_enable_o = s_clock_en_q;
assign half_period_o = s_half_period_q;
assign bus_width_o = s_bus_width_q;
assign timeout_cmd_o = s_timeout_cmd_q;
assign timeout_data_o = s_timeout_data_q;
assign timeout_busy_o = s_timeout_busy_q;
assign cmd_index_o = s_cmd_index_q;
assign cmd_arg_o = s_cmd_arg_q;
assign cmd_resp_type_o = sdio_resp_type_e'(s_cmd_cfg_q[3:0]);
assign cmd_crc_check_o = s_cmd_cfg_q[4];
assign cmd_index_check_o = s_cmd_cfg_q[5];
assign block_size_o = s_block_size_q;
assign block_count_o = s_block_count_q;
assign data_direction_o = s_data_cfg_q[0];
assign data_dma_enable_o = s_data_cfg_q[4];
assign data_block_mode_o = s_data_cfg_q[5];
assign data_fixed_addr_o = s_data_cfg_q[6];
assign desc_base_o = s_desc_base_q;
assign desc_count_o = s_desc_count_q;
assign irq_dat1_enable_o = s_irq_en_q[6];
assign irq_o = (s_irq_stat_q & s_irq_en_q) != 8'd0;
assign pio_read_consume_o = s_req_accept && !apb4.pwrite &&
(apb4.paddr[11:0] == `APB4_SDIO__PIO_DATA) && pio_valid_i;
always_comb begin
s_read_data = '0;
s_read_error = 1'b0;
unique case (apb4.paddr[11:0])
`APB4_SDIO__IP_ID: s_read_data = 32'h5344_494F;
`APB4_SDIO__IP_VERSION: s_read_data = 32'h0001_0000;
`APB4_SDIO__CAPABILITY: s_read_data = 32'h0000_01FF;
`APB4_SDIO__HOST_CTRL: s_read_data = {29'd0, s_host_ctrl_q};
`APB4_SDIO__CLOCK_CTRL: s_read_data = {8'd0, s_half_period_q, 7'd0, s_clock_en_q};
`APB4_SDIO__CLOCK_ACTUAL: s_read_data = clock_actual_i;
`APB4_SDIO__BUS_CTRL: s_read_data = {30'd0, s_bus_width_q};
`APB4_SDIO__TIMEOUT_CMD: s_read_data = s_timeout_cmd_q;
`APB4_SDIO__TIMEOUT_DATA: s_read_data = s_timeout_data_q;
`APB4_SDIO__TIMEOUT_BUSY: s_read_data = s_timeout_busy_q;
`APB4_SDIO__STATUS: s_read_data = status_i;
`APB4_SDIO__PRESENT: s_read_data = 32'h0000_0001;
`APB4_SDIO__CMD_ARG: s_read_data = s_cmd_arg_q;
`APB4_SDIO__CMD_CFG: s_read_data = {26'd0, s_cmd_cfg_q};
`APB4_SDIO__CMD_STATUS: s_read_data = cmd_status_i;
`APB4_SDIO__RESP0: s_read_data = response_i[31:0];
`APB4_SDIO__RESP1: s_read_data = response_i[63:32];
`APB4_SDIO__RESP2: s_read_data = response_i[95:64];
`APB4_SDIO__RESP3: s_read_data = response_i[127:96];
`APB4_SDIO__RESP4: s_read_data = {24'd0, response_i[135:128]};
`APB4_SDIO__BLOCK_SIZE: s_read_data = {16'd0, s_block_size_q};
`APB4_SDIO__BLOCK_COUNT: s_read_data = {16'd0, s_block_count_q};
`APB4_SDIO__DATA_CFG: s_read_data = {25'd0, s_data_cfg_q};
`APB4_SDIO__DATA_START: s_read_data = data_status_i;
`APB4_SDIO__PIO_DATA: begin
s_read_data = pio_rdata_i;
s_read_error = !pio_valid_i;
end
`APB4_SDIO__FIFO_STATUS: s_read_data = fifo_status_i;
`APB4_SDIO__FIFO_WATERMARK: s_read_data = 32'd1;
`APB4_SDIO__DESC_BASE: s_read_data = s_desc_base_q;
`APB4_SDIO__DESC_COUNT: s_read_data = {16'd0, s_desc_count_q};
`APB4_SDIO__DMA_CTRL: s_read_data = 32'd0;
`APB4_SDIO__DMA_STATUS: s_read_data = dma_status_i;
`APB4_SDIO__CURRENT_DESC: s_read_data = current_desc_i;
`APB4_SDIO__BYTES_DONE: s_read_data = bytes_done_i;
`APB4_SDIO__DMA_ERROR_ADDR: s_read_data = dma_error_addr_i;
`APB4_SDIO__DMA_ERROR: s_read_data = dma_error_i;
`APB4_SDIO__IRQ_STATUS: s_read_data = {24'd0, s_irq_stat_q};
`APB4_SDIO__IRQ_ENABLE: s_read_data = {24'd0, s_irq_en_q};
`APB4_SDIO__ERROR_STATUS: s_read_data = s_err_stat_q;
`APB4_SDIO__LAST_CMD: s_read_data = {26'd0, last_cmd_i};
`APB4_SDIO__CRC_ERROR_COUNT: s_read_data = crc_error_count_i;
`APB4_SDIO__TIMEOUT_COUNT: s_read_data = timeout_count_i;
`APB4_SDIO__AXI_ERROR_COUNT: s_read_data = axi_error_count_i;
`APB4_SDIO__STALL_COUNT: s_read_data = stall_count_i;
`APB4_SDIO__DEBUG: s_read_data = {30'd0, pio_ready_i, pio_valid_i};
default: s_read_error = 1'b1;
endcase
if (apb4.paddr[31:12] != 20'd0) begin
s_read_error = 1'b1;
end
end
always_comb begin
s_write_known = 1'b1;
s_write_error = 1'b0;
unique case (apb4.paddr[11:0])
`APB4_SDIO__HOST_CTRL,
`APB4_SDIO__CLOCK_CTRL,
`APB4_SDIO__BUS_CTRL,
`APB4_SDIO__TIMEOUT_CMD,
`APB4_SDIO__TIMEOUT_DATA,
`APB4_SDIO__TIMEOUT_BUSY,
`APB4_SDIO__CMD_ARG,
`APB4_SDIO__CMD_CFG,
`APB4_SDIO__BLOCK_SIZE,
`APB4_SDIO__BLOCK_COUNT,
`APB4_SDIO__DATA_CFG,
`APB4_SDIO__DESC_BASE,
`APB4_SDIO__DESC_COUNT,
`APB4_SDIO__IRQ_ENABLE: begin
if (busy_i && (apb4.paddr[11:0] != `APB4_SDIO__HOST_CTRL)) begin
s_write_error = 1'b1;
end
end
`APB4_SDIO__CMD_START: begin
s_write_error = (apb4.pstrb[0] == 1'b0) || (apb4.pwdata[31:1] != 31'd0) || busy_i;
end
`APB4_SDIO__DATA_START: begin
s_write_error = (apb4.pstrb[0] == 1'b0) || (apb4.pwdata[31:1] != 31'd0) || busy_i;
end
`APB4_SDIO__PIO_DATA: begin
s_write_error = (busy_i == 1'b0) || s_pio_write_direction_error;
end
`APB4_SDIO__DMA_CTRL: begin
s_write_error = (apb4.pwdata[31:2] != 30'd0) ||
(apb4.pwdata[0] && !s_data_cfg_q[4]) ||
(busy_i && apb4.pwdata[0]);
end
`APB4_SDIO__IRQ_STATUS, `APB4_SDIO__IRQ_TEST, `APB4_SDIO__ERROR_STATUS: begin
s_write_error = apb4.pstrb[0] == 1'b0;
end
default: begin
s_write_known = 1'b0;
s_write_error = 1'b1;
end
endcase
if (apb4.paddr[31:12] != 20'd0) begin
s_write_error = 1'b1;
end
end
always_comb begin
s_apb4_ready_d = s_req_accept;
s_apb4_rdata_d = s_apb4_rdata_q;
s_apb4_resp_err_d = s_apb4_resp_err_q;
if (s_req_accept) begin
s_apb4_rdata_d = s_read_data;
s_apb4_resp_err_d = s_read_error;
if (s_write_access) begin
s_apb4_resp_err_d = !s_write_known || s_write_error;
end
end
end
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_apb4_ready_q <= 1'b0;
s_apb4_rdata_q <= '0;
s_apb4_resp_err_q <= 1'b0;
s_host_ctrl_q <= '0;
s_clock_en_q <= 1'b0;
s_half_period_q <= 16'd90;
s_bus_width_q <= 2'd0;
s_timeout_cmd_q <= 32'd100000;
s_timeout_data_q <= 32'd1000000;
s_timeout_busy_q <= 32'd1000000;
s_cmd_index_q <= '0;
s_cmd_arg_q <= '0;
s_cmd_cfg_q <= 6'd0;
s_block_size_q <= 16'd512;
s_block_count_q <= 16'd1;
s_data_cfg_q <= '0;
s_desc_base_q <= '0;
s_desc_count_q <= 16'd1;
s_irq_stat_q <= '0;
s_irq_en_q <= '0;
s_err_stat_q <= '0;
cmd_start_o <= 1'b0;
data_start_o <= 1'b0;
pio_wvalid_o <= 1'b0;
pio_wdata_o <= '0;
pio_wstrb_o <= '0;
dma_start_o <= 1'b0;
dma_abort_o <= 1'b0;
host_abort_o <= 1'b0;
end else begin
s_apb4_ready_q <= s_apb4_ready_d;
s_apb4_rdata_q <= s_apb4_rdata_d;
s_apb4_resp_err_q <= s_apb4_resp_err_d;
cmd_start_o <= 1'b0;
data_start_o <= 1'b0;
pio_wvalid_o <= 1'b0;
dma_start_o <= 1'b0;
dma_abort_o <= 1'b0;
host_abort_o <= 1'b0;
s_irq_stat_q <= (s_irq_stat_q & ~irq_event_i) | irq_event_i;
s_err_stat_q <= s_err_stat_q | error_status_i;
if (s_req_accept && apb4.pwrite && !s_write_error) begin
unique case (apb4.paddr[11:0])
`APB4_SDIO__HOST_CTRL: begin
if (apb4.pstrb[0]) begin
s_host_ctrl_q[0] <= apb4.pwdata[0];
s_host_ctrl_q[2] <= apb4.pwdata[2];
if (apb4.pwdata[1]) begin
host_abort_o <= 1'b1;
end
end
end
`APB4_SDIO__CLOCK_CTRL: begin
if (apb4.pstrb[0]) s_clock_en_q <= apb4.pwdata[0];
if (apb4.pstrb[1]) s_half_period_q[7:0] <= apb4.pwdata[15:8];
if (apb4.pstrb[2]) s_half_period_q[15:8] <= apb4.pwdata[23:16];
end
`APB4_SDIO__BUS_CTRL: if (apb4.pstrb[0]) s_bus_width_q <= apb4.pwdata[1:0];
`APB4_SDIO__TIMEOUT_CMD: s_timeout_cmd_q <= apb4.pwdata;
`APB4_SDIO__TIMEOUT_DATA: s_timeout_data_q <= apb4.pwdata;
`APB4_SDIO__TIMEOUT_BUSY: s_timeout_busy_q <= apb4.pwdata;
`APB4_SDIO__CMD_ARG: s_cmd_arg_q <= apb4.pwdata;
`APB4_SDIO__CMD_CFG: begin
if (apb4.pstrb[0]) s_cmd_index_q <= apb4.pwdata[5:0];
if (apb4.pstrb[1]) s_cmd_cfg_q <= apb4.pwdata[13:8];
end
`APB4_SDIO__CMD_START: cmd_start_o <= 1'b1;
`APB4_SDIO__BLOCK_SIZE: s_block_size_q <= apb4.pwdata[15:0];
`APB4_SDIO__BLOCK_COUNT: s_block_count_q <= apb4.pwdata[15:0];
`APB4_SDIO__DATA_CFG: if (apb4.pstrb[0]) s_data_cfg_q <= apb4.pwdata[6:0];
`APB4_SDIO__DATA_START: data_start_o <= 1'b1;
`APB4_SDIO__PIO_DATA: begin
pio_wvalid_o <= 1'b1;
pio_wdata_o <= apb4.pwdata;
pio_wstrb_o <= apb4.pstrb;
end
`APB4_SDIO__DESC_BASE: s_desc_base_q <= apb4.pwdata;
`APB4_SDIO__DESC_COUNT: s_desc_count_q <= apb4.pwdata[15:0];
`APB4_SDIO__DMA_CTRL: begin
dma_start_o <= apb4.pwdata[0];
dma_abort_o <= apb4.pwdata[1];
if (apb4.pwdata[0]) begin
data_start_o <= 1'b1;
end
end
`APB4_SDIO__IRQ_ENABLE: if (apb4.pstrb[0]) s_irq_en_q <= apb4.pwdata[7:0];
`APB4_SDIO__IRQ_STATUS: s_irq_stat_q <= (s_irq_stat_q & ~apb4.pwdata[7:0]) | irq_event_i;
`APB4_SDIO__IRQ_TEST:
s_irq_stat_q <= ((s_irq_stat_q & ~irq_event_i) | irq_event_i) | apb4.pwdata[7:0];
`APB4_SDIO__ERROR_STATUS: s_err_stat_q <= (s_err_stat_q & ~apb4.pwdata) | error_status_i;
default: begin
end
endcase
end
end
end
endmodule
+29 -4
View File
@@ -125,6 +125,22 @@
{
"symbol": "RTC_WAKE_STATUS",
"offset": "0x88"
},
{
"symbol": "PERF_SDIO0_WAIT_LO",
"offset": "0x8C"
},
{
"symbol": "PERF_SDIO0_WAIT_HI",
"offset": "0x90"
},
{
"symbol": "PERF_SDIO1_WAIT_LO",
"offset": "0x94"
},
{
"symbol": "PERF_SDIO1_WAIT_HI",
"offset": "0x98"
}
],
"regions": [
@@ -275,12 +291,12 @@
"user_access": "rw"
},
{
"symbol": "APB4_SDIO",
"symbol": "APB4_SDIO0",
"base": "0x1000F000",
"size": "0x00001000",
"route": "reserved",
"kind": "reserved",
"public": false,
"route": "apb4_periph",
"kind": "active",
"public": true,
"user_access": "none"
},
{
@@ -328,6 +344,15 @@
"public": true,
"user_access": "none"
},
{
"symbol": "APB4_SDIO1",
"base": "0x10015000",
"size": "0x00001000",
"route": "apb4_periph",
"kind": "active",
"public": true,
"user_access": "none"
},
{
"symbol": "APB4_ARCHINFO",
"base": "0x20000000",
+2 -2
View File
@@ -10,8 +10,8 @@
/* verilator lint_off SYNCASYNCNET */
module axi4_interconnect_sva #(
parameter int NumMasters = 3,
parameter int NumTargets = 9,
parameter int NumMasters = 5,
parameter int NumTargets = 10,
parameter int MASTER_WIDTH = $clog2(NumMasters),
parameter int TARGET_WIDTH = $clog2(NumTargets)
) (
+3
View File
@@ -58,6 +58,9 @@ module retrosoc_tb;
wire s_i2s_sclk;
wire s_i2s_lrck;
wire s_i2s_adcdat;
wire s_sdio1_clk;
tri1 s_sdio1_cmd;
tri1 [ 3:0] s_sdio1_dat;
wire s_sdram_clk;
wire s_sdram_cke;
wire s_sdram_cs_n;
+7 -1
View File
@@ -30,7 +30,13 @@ module retrosoc_top (
output wire jtag_tdo_o,
output wire test_done_o,
output wire test_pass_o,
output wire [7:0] test_code_o
output wire [7:0] test_code_o,
output wire sdio1_clk_o,
inout wire sdio1_cmd_io,
inout wire sdio1_dat0_io,
inout wire sdio1_dat1_io,
inout wire sdio1_dat2_io,
inout wire sdio1_dat3_io
);
wire s_clk;
+13 -1
View File
@@ -22,7 +22,15 @@
/ip/storage/spisd_core.sv
/ip/storage/spisd_data.sv
/ip/storage/apb4_spisd.sv
/ip/storage/apb4_sdio.sv
/ip/storage/sdio_if.sv
/ip/storage/sdio_pkg.sv
/ip/storage/sdio_define.svh
/ip/storage/sdio_crc7.sv
/ip/storage/sdio_crc16.sv
/ip/storage/sdio_clock.sv
/ip/storage/sdio_command.sv
/ip/storage/sdio_data.sv
/ip/storage/sdio_dma_descriptor.sv
/ip/serial/i2c_filter.sv
/ip/serial/i2c_core.sv
/ip/serial/i2c_reg.sv
@@ -55,6 +63,10 @@
/ip/peripheral/dma_define.svh
/ip/peripheral/dma_req_if.sv
/ip/peripheral/dma_axi4_master.sv
/ip/storage/sdio_dma.sv
/ip/storage/sdio_reg.sv
/ip/storage/sdio_core.sv
/ip/storage/apb4_sdio.sv
/ip/peripheral/dma_reg.sv
/ip/peripheral/dma_core.sv
/ip/peripheral/apb4_dma.sv
@@ -21,6 +21,7 @@ INTERCONNECT = ROOT / "rtl/ip/interconnect"
PERIPHERAL = ROOT / "rtl/ip/peripheral"
SERIAL = ROOT / "rtl/ip/serial"
MEMORY = ROOT / "rtl/ip/memory"
STORAGE = ROOT / "rtl/ip/storage"
TOP = ROOT / "rtl/mini/top"
@@ -227,6 +228,19 @@ def source_files(target: str) -> list[Path]:
PERIPHERAL / "dma_core.sv",
SCRIPT_DIR / "dma_formal.sv",
]
if target == "sdio":
return [
COMMON_RTL / "interface/apb4_if.sv",
COMMON_RTL / "interface/axi4_if.sv",
PERIPHERAL / "dma_axi4_master.sv",
STORAGE / "sdio_pkg.sv",
STORAGE / "sdio_define.svh",
STORAGE / "sdio_clock.sv",
STORAGE / "sdio_dma_descriptor.sv",
STORAGE / "sdio_dma.sv",
STORAGE / "sdio_reg.sv",
SCRIPT_DIR / "sdio_formal.sv",
]
raise ValueError(f"unsupported formal target: {target}")
@@ -279,6 +293,7 @@ def parse_args() -> argparse.Namespace:
"psram",
"opipsram",
"dma",
"sdio",
),
required=True,
)
+320
View File
@@ -0,0 +1,320 @@
// Copyright (c) 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 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,
// INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A
// PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
`include "axi4_define.svh"
module sdio_formal_design (
// verilog_format: off -- scalar observations mirror the APB and AXI contracts
input logic clk_i,
input logic rst_n_i,
input logic apb_psel_i,
input logic apb_penable_i,
input logic apb_pwrite_i,
input logic [31:0] apb_paddr_i,
input logic [31:0] apb_pwdata_i,
input logic [ 3:0] apb_pstrb_i,
input logic [ 7:0] irq_event_i,
input logic axi_awready_i,
input logic axi_wready_i,
input logic axi_bvalid_i,
input logic axi_bid_i,
input logic [ 1:0] axi_bresp_i,
input logic axi_arready_i,
input logic axi_rvalid_i,
input logic axi_rid_i,
input logic [31:0] axi_rdata_i,
input logic [ 1:0] axi_rresp_i,
input logic axi_rlast_i,
input logic dma_data_out_ready_i,
output logic apb_pready_o,
output logic [31:0] apb_prdata_o,
output logic apb_pslverr_o,
output logic irq_o,
output logic sck_o,
output logic launch_tick_o,
output logic sample_tick_o,
output logic clock_running_o,
output logic dma_busy_o,
output logic dma_done_o,
output logic dma_error_o,
output logic [ 7:0] dma_error_code_o,
output logic [31:0] dma_current_desc_o,
output logic [31:0] dma_bytes_done_o,
output logic [31:0] dma_error_addr_o,
output logic dma_data_out_valid_o,
output logic [31:0] dma_data_out_o,
output logic [ 3:0] dma_data_out_strb_o,
output logic dma_data_out_last_o,
output logic axi_awvalid_o,
output logic [31:0] axi_awaddr_o,
output logic [ 7:0] axi_awlen_o,
output logic [ 2:0] axi_awsize_o,
output logic [ 1:0] axi_awburst_o,
output logic axi_wvalid_o,
output logic [31:0] axi_wdata_o,
output logic [ 3:0] axi_wstrb_o,
output logic axi_wlast_o,
output logic axi_bready_o,
output logic axi_arvalid_o,
output logic [31:0] axi_araddr_o,
output logic [ 7:0] axi_arlen_o,
output logic [ 2:0] axi_arsize_o,
output logic [ 1:0] axi_arburst_o,
output logic axi_rready_o,
output logic host_enable_o,
output logic clock_enable_o,
output logic [15:0] half_period_o,
output logic [ 1:0] bus_width_o,
output logic [15:0] desc_count_o,
output logic dma_start_o,
output logic dma_abort_o
// verilog_format: on
);
apb4_if apb4 (
.pclk (clk_i),
.presetn(rst_n_i)
);
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) dma_axi4 (
.aclk (clk_i),
.aresetn(rst_n_i)
);
logic s_dma_busy;
logic s_dma_done;
logic s_dma_error;
logic [ 7:0] s_dma_error_code;
logic [ 31:0] s_dma_current_desc;
logic [ 31:0] s_dma_bytes_done;
logic [ 31:0] s_dma_error_addr;
logic s_data_out_valid;
logic [ 31:0] s_data_out;
logic [ 3:0] s_data_out_strb;
logic s_data_out_last;
logic s_data_direction;
logic s_data_dma_enable;
logic [ 15:0] s_block_size;
logic [ 15:0] s_block_count;
logic [ 31:0] s_desc_base;
logic s_clock_enable;
logic [ 15:0] s_half_period;
logic s_host_enable;
logic s_dma_start;
logic s_dma_abort;
logic s_host_irq_enable;
logic s_irq_dat1_enable;
logic [ 31:0] s_status;
logic [ 31:0] s_clock_actual;
logic [ 31:0] s_cmd_status;
logic [135:0] s_response;
logic [ 31:0] s_data_status;
logic [ 31:0] s_fifo_status;
logic [ 31:0] s_dma_status;
logic [ 31:0] s_dma_error_addr_reg;
logic [ 31:0] s_dma_error_reg;
logic [ 31:0] s_error_status;
logic [ 5:0] s_last_cmd;
logic [ 31:0] s_crc_error_count;
logic [ 31:0] s_timeout_count;
logic [ 31:0] s_axi_error_count;
logic [ 31:0] s_stall_count;
logic [ 31:0] s_pio_rdata;
logic s_pio_valid;
logic s_pio_ready;
logic s_data_start;
logic s_cmd_start;
logic [ 5:0] s_cmd_index;
logic [ 31:0] s_cmd_arg;
sdio_pkg::sdio_resp_type_e s_cmd_resp_type;
logic s_cmd_crc_check;
logic s_cmd_index_check;
logic [ 15:0] s_desc_count_q;
assign apb4.psel = apb_psel_i;
assign apb4.penable = apb_penable_i;
assign apb4.pwrite = apb_pwrite_i;
assign apb4.paddr = apb_paddr_i;
assign apb4.pwdata = apb_pwdata_i;
assign apb4.pstrb = apb_pstrb_i;
assign apb_pready_o = apb4.pready;
assign apb_prdata_o = apb4.prdata;
assign apb_pslverr_o = apb4.pslverr;
assign dma_axi4.awready = axi_awready_i;
assign dma_axi4.wready = axi_wready_i;
assign dma_axi4.bvalid = axi_bvalid_i;
assign dma_axi4.bid = axi_bid_i;
assign dma_axi4.bresp = axi_bresp_i;
assign dma_axi4.buser = '0;
assign dma_axi4.arready = axi_arready_i;
assign dma_axi4.rvalid = axi_rvalid_i;
assign dma_axi4.rid = axi_rid_i;
assign dma_axi4.rdata = axi_rdata_i;
assign dma_axi4.rresp = axi_rresp_i;
assign dma_axi4.rlast = axi_rlast_i;
assign dma_axi4.ruser = '0;
assign axi_awvalid_o = dma_axi4.awvalid;
assign axi_awaddr_o = dma_axi4.awaddr;
assign axi_awlen_o = dma_axi4.awlen;
assign axi_awsize_o = dma_axi4.awsize;
assign axi_awburst_o = dma_axi4.awburst;
assign axi_wvalid_o = dma_axi4.wvalid;
assign axi_wdata_o = dma_axi4.wdata;
assign axi_wstrb_o = dma_axi4.wstrb;
assign axi_wlast_o = dma_axi4.wlast;
assign axi_bready_o = dma_axi4.bready;
assign axi_arvalid_o = dma_axi4.arvalid;
assign axi_araddr_o = dma_axi4.araddr;
assign axi_arlen_o = dma_axi4.arlen;
assign axi_arsize_o = dma_axi4.arsize;
assign axi_arburst_o = dma_axi4.arburst;
assign axi_rready_o = dma_axi4.rready;
sdio_reg u_sdio_reg (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.busy_i (s_dma_busy),
.status_i (s_status),
.clock_actual_i (s_clock_actual),
.cmd_status_i (s_cmd_status),
.response_i (s_response),
.data_status_i (s_data_status),
.fifo_status_i (s_fifo_status),
.dma_status_i (s_dma_status),
.current_desc_i (s_dma_current_desc),
.bytes_done_i (s_dma_bytes_done),
.dma_error_addr_i (s_dma_error_addr_reg),
.dma_error_i (s_dma_error_reg),
.error_status_i (s_error_status),
.last_cmd_i (s_last_cmd),
.crc_error_count_i(s_crc_error_count),
.timeout_count_i (s_timeout_count),
.axi_error_count_i(s_axi_error_count),
.stall_count_i (s_stall_count),
.irq_event_i (irq_event_i),
.pio_rdata_i (s_pio_rdata),
.pio_valid_i (s_pio_valid),
.pio_ready_i (s_pio_ready),
.host_enable_o (s_host_enable),
.host_irq_enable_o(s_host_irq_enable),
.clock_enable_o (s_clock_enable),
.half_period_o (s_half_period),
.bus_width_o (bus_width_o),
.timeout_cmd_o (),
.timeout_data_o (),
.timeout_busy_o (),
.cmd_index_o (s_cmd_index),
.cmd_arg_o (s_cmd_arg),
.cmd_resp_type_o (s_cmd_resp_type),
.cmd_crc_check_o (s_cmd_crc_check),
.cmd_index_check_o(s_cmd_index_check),
.cmd_start_o (s_cmd_start),
.block_size_o (s_block_size),
.block_count_o (s_block_count),
.data_direction_o (s_data_direction),
.data_dma_enable_o(s_data_dma_enable),
.data_block_mode_o(),
.data_fixed_addr_o(),
.data_start_o (s_data_start),
.pio_wvalid_o (),
.pio_wdata_o (),
.pio_wstrb_o (),
.desc_base_o (s_desc_base),
.desc_count_o (s_desc_count_q),
.dma_start_o (s_dma_start),
.dma_abort_o (s_dma_abort),
.irq_dat1_enable_o(s_irq_dat1_enable),
.irq_o (irq_o),
.apb4 (apb4)
);
assign host_enable_o = s_host_enable;
assign clock_enable_o = s_clock_enable;
assign half_period_o = s_half_period;
assign desc_count_o = s_desc_count_q;
assign dma_start_o = s_dma_start;
assign dma_abort_o = s_dma_abort;
assign s_clock_actual = 32'd0;
assign s_status = 32'd0;
assign s_cmd_status = 32'd0;
assign s_response = '0;
assign s_data_status = 32'd0;
assign s_fifo_status = 32'd0;
assign s_dma_status = 32'd0;
assign s_dma_error_addr_reg = s_dma_error_addr;
assign s_dma_error_reg = {24'd0, s_dma_error_code};
assign s_error_status = {24'd0, s_dma_error};
assign s_last_cmd = 6'd0;
assign s_crc_error_count = 32'd0;
assign s_timeout_count = 32'd0;
assign s_axi_error_count = 32'd0;
assign s_stall_count = 32'd0;
assign s_pio_rdata = 32'd0;
assign s_pio_valid = 1'b0;
assign s_pio_ready = 1'b1;
sdio_clock u_sdio_clock (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.enable_i (s_host_enable && s_clock_enable),
.half_period_i(s_half_period),
.sck_o (sck_o),
.launch_tick_o(launch_tick_o),
.sample_tick_o(sample_tick_o),
.running_o (clock_running_o)
);
sdio_dma u_sdio_dma (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_dma_start),
.abort_i (s_dma_abort),
.direction_i (s_data_direction),
.desc_base_i (s_desc_base),
.desc_count_i (s_desc_count_q),
.total_bytes_i ({16'd0, s_block_size} * {16'd0, s_block_count}),
.data_in_valid_i (1'b0),
.data_in_ready_o (),
.data_in_i ('0),
.data_in_strb_i ('0),
.data_in_last_i (1'b0),
.data_out_valid_o(s_data_out_valid),
.data_out_ready_i(dma_data_out_ready_i),
.data_out_o (s_data_out),
.data_out_strb_o (s_data_out_strb),
.data_out_last_o (s_data_out_last),
.busy_o (s_dma_busy),
.done_o (s_dma_done),
.error_o (s_dma_error),
.error_code_o (s_dma_error_code),
.current_desc_o (s_dma_current_desc),
.bytes_done_o (s_dma_bytes_done),
.error_addr_o (s_dma_error_addr),
.dma_axi4 (dma_axi4)
);
assign dma_busy_o = s_dma_busy;
assign dma_done_o = s_dma_done;
assign dma_error_o = s_dma_error;
assign dma_error_code_o = s_dma_error_code;
assign dma_current_desc_o = s_dma_current_desc;
assign dma_bytes_done_o = s_dma_bytes_done;
assign dma_error_addr_o = s_dma_error_addr;
assign dma_data_out_valid_o = s_data_out_valid;
assign dma_data_out_o = s_data_out;
assign dma_data_out_strb_o = s_data_out_strb;
assign dma_data_out_last_o = s_data_out_last;
endmodule
+312
View File
@@ -0,0 +1,312 @@
// Copyright (c) 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 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,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A
// PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
module sdio_formal;
(* anyseq *) (* gclk *)reg clk_i;
reg rst_n_i;
reg f_past_valid;
(* anyseq *)logic apb_psel_i;
(* anyseq *)logic apb_penable_i;
(* anyseq *)logic apb_pwrite_i;
(* anyseq *)logic [31:0] apb_paddr_i;
(* anyseq *)logic [31:0] apb_pwdata_i;
(* anyseq *)logic [ 3:0] apb_pstrb_i;
(* anyseq *)logic [ 7:0] irq_event_i;
(* anyseq *)logic axi_awready_i;
(* anyseq *)logic axi_wready_i;
(* anyseq *)logic axi_bvalid_i;
(* anyseq *)logic axi_bid_i;
(* anyseq *)logic [ 1:0] axi_bresp_i;
(* anyseq *)logic axi_arready_i;
(* anyseq *)logic axi_rvalid_i;
(* anyseq *)logic axi_rid_i;
(* anyseq *)logic [31:0] axi_rdata_i;
(* anyseq *)logic [ 1:0] axi_rresp_i;
(* anyseq *)logic axi_rlast_i;
(* anyseq *)logic dma_data_out_ready_i;
wire apb_pready_o;
wire [31:0] apb_prdata_o;
wire apb_pslverr_o;
wire irq_o;
wire sck_o;
wire launch_tick_o;
wire sample_tick_o;
wire clock_running_o;
wire dma_busy_o;
wire dma_done_o;
wire dma_error_o;
wire [ 7:0] dma_error_code_o;
wire [31:0] dma_current_desc_o;
wire [31:0] dma_bytes_done_o;
wire [31:0] dma_error_addr_o;
wire dma_data_out_valid_o;
wire [31:0] dma_data_out_o;
wire [ 3:0] dma_data_out_strb_o;
wire dma_data_out_last_o;
wire axi_awvalid_o;
wire [31:0] axi_awaddr_o;
wire [ 7:0] axi_awlen_o;
wire [ 2:0] axi_awsize_o;
wire [ 1:0] axi_awburst_o;
wire axi_wvalid_o;
wire [31:0] axi_wdata_o;
wire [ 3:0] axi_wstrb_o;
wire axi_wlast_o;
wire axi_bready_o;
wire axi_arvalid_o;
wire [31:0] axi_araddr_o;
wire [ 7:0] axi_arlen_o;
wire [ 2:0] axi_arsize_o;
wire [ 1:0] axi_arburst_o;
wire axi_rready_o;
wire host_enable_o;
wire clock_enable_o;
wire [15:0] half_period_o;
wire [ 1:0] bus_width_o;
wire [15:0] desc_count_o;
wire dma_start_o;
wire dma_abort_o;
logic [ 4:0] f_abort_age_q;
logic f_abort_pending_q;
logic f_irq_seen_q;
logic f_read_pending_q;
logic f_abort_read_pending_q;
sdio_formal_design u_design (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.apb_psel_i (apb_psel_i),
.apb_penable_i (apb_penable_i),
.apb_pwrite_i (apb_pwrite_i),
.apb_paddr_i (apb_paddr_i),
.apb_pwdata_i (apb_pwdata_i),
.apb_pstrb_i (apb_pstrb_i),
.irq_event_i (irq_event_i),
.axi_awready_i (axi_awready_i),
.axi_wready_i (axi_wready_i),
.axi_bvalid_i (axi_bvalid_i),
.axi_bid_i (axi_bid_i),
.axi_bresp_i (axi_bresp_i),
.axi_arready_i (axi_arready_i),
.axi_rvalid_i (axi_rvalid_i),
.axi_rid_i (axi_rid_i),
.axi_rdata_i (axi_rdata_i),
.axi_rresp_i (axi_rresp_i),
.axi_rlast_i (axi_rlast_i),
.dma_data_out_ready_i(dma_data_out_ready_i),
.apb_pready_o (apb_pready_o),
.apb_prdata_o (apb_prdata_o),
.apb_pslverr_o (apb_pslverr_o),
.irq_o (irq_o),
.sck_o (sck_o),
.launch_tick_o (launch_tick_o),
.sample_tick_o (sample_tick_o),
.clock_running_o (clock_running_o),
.dma_busy_o (dma_busy_o),
.dma_done_o (dma_done_o),
.dma_error_o (dma_error_o),
.dma_error_code_o (dma_error_code_o),
.dma_current_desc_o (dma_current_desc_o),
.dma_bytes_done_o (dma_bytes_done_o),
.dma_error_addr_o (dma_error_addr_o),
.dma_data_out_valid_o(dma_data_out_valid_o),
.dma_data_out_o (dma_data_out_o),
.dma_data_out_strb_o (dma_data_out_strb_o),
.dma_data_out_last_o (dma_data_out_last_o),
.axi_awvalid_o (axi_awvalid_o),
.axi_awaddr_o (axi_awaddr_o),
.axi_awlen_o (axi_awlen_o),
.axi_awsize_o (axi_awsize_o),
.axi_awburst_o (axi_awburst_o),
.axi_wvalid_o (axi_wvalid_o),
.axi_wdata_o (axi_wdata_o),
.axi_wstrb_o (axi_wstrb_o),
.axi_wlast_o (axi_wlast_o),
.axi_bready_o (axi_bready_o),
.axi_arvalid_o (axi_arvalid_o),
.axi_araddr_o (axi_araddr_o),
.axi_arlen_o (axi_arlen_o),
.axi_arsize_o (axi_arsize_o),
.axi_arburst_o (axi_arburst_o),
.axi_rready_o (axi_rready_o),
.host_enable_o (host_enable_o),
.clock_enable_o (clock_enable_o),
.half_period_o (half_period_o),
.bus_width_o (bus_width_o),
.desc_count_o (desc_count_o),
.dma_start_o (dma_start_o),
.dma_abort_o (dma_abort_o)
);
initial begin
rst_n_i = 1'b0;
f_past_valid = 1'b0;
f_abort_pending_q = 1'b0;
f_abort_age_q = 5'd0;
f_irq_seen_q = 1'b0;
f_read_pending_q = 1'b0;
f_abort_read_pending_q = 1'b0;
end
always @(posedge clk_i) begin
rst_n_i <= 1'b1;
f_past_valid <= 1'b1;
if (!rst_n_i) begin
assume (!apb_psel_i);
assume (!axi_bvalid_i);
assume (!axi_rvalid_i);
assume (!irq_event_i);
end
if (f_past_valid && $past(rst_n_i)) begin
if ($past(apb_psel_i && !apb_penable_i)) begin
assume (apb_psel_i);
assume (apb_paddr_i == $past(apb_paddr_i));
assume (apb_pwrite_i == $past(apb_pwrite_i));
assume (apb_pwdata_i == $past(apb_pwdata_i));
assume (apb_pstrb_i == $past(apb_pstrb_i));
end
if ($past(axi_rvalid_i && !axi_rready_o)) begin
assume (axi_rvalid_i);
assume (axi_rid_i == $past(axi_rid_i));
assume (axi_rdata_i == $past(axi_rdata_i));
assume (axi_rresp_i == $past(axi_rresp_i));
assume (axi_rlast_i == $past(axi_rlast_i));
end
if ($past(axi_bvalid_i && !axi_bready_o)) begin
assume (axi_bvalid_i);
assume (axi_bid_i == $past(axi_bid_i));
assume (axi_bresp_i == $past(axi_bresp_i));
end
end
end
always @(posedge clk_i) begin
if (!rst_n_i) begin
f_abort_pending_q <= 1'b0;
f_abort_age_q <= 5'd0;
f_irq_seen_q <= 1'b0;
f_read_pending_q <= 1'b0;
f_abort_read_pending_q <= 1'b0;
end
if (f_past_valid && $past(rst_n_i)) begin
if ($past(
apb_pready_o && apb_psel_i && apb_penable_i
) && apb_psel_i && apb_penable_i && (apb_paddr_i == $past(
apb_paddr_i
)) && (apb_pwrite_i == $past(
apb_pwrite_i
)) && (apb_pwdata_i == $past(
apb_pwdata_i
)) && (apb_pstrb_i == $past(
apb_pstrb_i
))) begin
assert (apb_prdata_o == $past(apb_prdata_o));
assert (apb_pslverr_o == $past(apb_pslverr_o));
end
if ($past(axi_awvalid_o && !axi_awready_i)) begin
assert (axi_awvalid_o);
assert (axi_awaddr_o == $past(axi_awaddr_o));
assert (axi_awlen_o == $past(axi_awlen_o));
assert (axi_awsize_o == $past(axi_awsize_o));
assert (axi_awburst_o == $past(axi_awburst_o));
end
if ($past(axi_wvalid_o && !axi_wready_i)) begin
if (axi_wvalid_o) begin
assert (axi_wdata_o == $past(axi_wdata_o));
assert (axi_wstrb_o == $past(axi_wstrb_o));
assert (axi_wlast_o == $past(axi_wlast_o));
end
end
if ($past(axi_arvalid_o && !axi_arready_i)) begin
assert (axi_arvalid_o);
assert (axi_araddr_o == $past(axi_araddr_o));
assert (axi_arlen_o == $past(axi_arlen_o));
assert (axi_arsize_o == $past(axi_arsize_o));
assert (axi_arburst_o == $past(axi_arburst_o));
end
if (axi_awvalid_o) begin
assert (axi_awlen_o <= 8'd15);
assert (axi_awsize_o == 3'd2);
assert ((axi_awburst_o == 2'b01) || (axi_awburst_o == 2'b00));
assert (axi_awaddr_o[1:0] == 2'b00);
if (axi_awburst_o == 2'b01) begin
assert ({1'b0, axi_awaddr_o[11:0]} + (({5'd0, axi_awlen_o} + 13'd1) << 2) <= 13'd4096);
end
end
if (axi_arvalid_o) begin
assert (axi_arlen_o <= 8'd15);
assert (axi_arsize_o == 3'd2);
assert ((axi_arburst_o == 2'b01) || (axi_arburst_o == 2'b00));
assert (axi_araddr_o[1:0] == 2'b00);
if (axi_arburst_o == 2'b01) begin
assert ({1'b0, axi_araddr_o[11:0]} + (({5'd0, axi_arlen_o} + 13'd1) << 2) <= 13'd4096);
end
end
if (axi_arvalid_o && axi_arready_i) begin
f_read_pending_q <= 1'b1;
end
if (f_read_pending_q && dma_abort_o) begin
f_abort_read_pending_q <= 1'b1;
end
if (f_read_pending_q && f_abort_read_pending_q) begin
assert (axi_rready_o);
cover (axi_rvalid_i && axi_rready_o && axi_rlast_i);
end
if (f_read_pending_q && axi_rvalid_i && axi_rready_o && axi_rlast_i) begin
f_read_pending_q <= 1'b0;
f_abort_read_pending_q <= 1'b0;
end
// The reduced DMA wrapper has no separate FIFO instance; stream
// ownership and byte accounting are the tractable FIFO-bound proxy.
assert (dma_bytes_done_o >= 32'd0);
if (dma_start_o && (desc_count_o > 16'd16)) begin
cover (dma_error_o);
end
if (sck_o != $past(sck_o)) begin
assert ($past(launch_tick_o) || $past(sample_tick_o));
end
if (dma_abort_o) begin
f_abort_pending_q <= 1'b1;
f_abort_age_q <= 5'd0;
end else if (f_abort_pending_q) begin
if (dma_done_o || dma_error_o || !dma_busy_o) begin
f_abort_pending_q <= 1'b0;
end else begin
if (f_abort_age_q != 5'd31) begin
f_abort_age_q <= f_abort_age_q + 1'b1;
end
end
end
if (irq_o) begin
f_irq_seen_q <= 1'b1;
end
if (apb_pwrite_i && apb_penable_i && apb_paddr_i[11:0] == 12'h100 &&
apb_pwdata_i[7:0] != 8'd0) begin
f_irq_seen_q <= 1'b0;
end
cover (apb_pready_o && apb_psel_i && apb_penable_i);
cover (dma_abort_o);
cover (irq_o);
end
end
endmodule
+2
View File
@@ -76,6 +76,8 @@ module sysctrl_formal_design (
assign sysctrl.perf_mgmt_wait_i = '0;
assign sysctrl.perf_user_wait_i = '0;
assign sysctrl.perf_dma_wait_i = '0;
assign sysctrl.perf_sdio0_wait_i = '0;
assign sysctrl.perf_sdio1_wait_i = '0;
assign sysctrl.perf_apb4_periph_wait_i = '0;
assign sysctrl.perf_apb4_system_wait_i = '0;
assign sysctrl.perf_sdram_wait_i = '0;
+10 -3
View File
@@ -9,7 +9,10 @@ files for `apb4_periph.sv`, `apb4_system.sv`, `axi42apb4_periph.sv`,
`axi42apb4_system.sv`, and `retrosoc.sv`. The two APB4 islands share one
decoder template. `apb4_system` is instantiated as `u_apb4_system`; its AXI4
configuration port is wired by generated `apb4_system_fabric.svh`.
`apb4_periph` remains the self-owned APB4 peripheral container. Generated fabric links are explicit 32-bit AXI4 interfaces.
`apb4_periph` remains the self-owned APB4 peripheral container. Generated
fabric links are explicit 32-bit AXI4 interfaces; `mgmt`, `user`, `dma`,
`sdio0`, and `sdio1` are the five native AXI4 masters, while `cfg` and
`system` are target-side links.
Interface arrays are confined to the AXI4 bus implementation and flattened by
the existing synthesis/export flow before FPGA or netlist simulation.
@@ -18,6 +21,8 @@ RIBP island at `0x1000_0000`. Every active `apb4_periph` memory-map region
must appear exactly once. Disabled targets keep their slot and interface
declaration but cannot own an address region. This list stays separate from
`apb4_system_targets`, which owns the `0x2000_0000` `apb4_system` island.
Slot 15 is the active SDIO0 management window; SDIO1 is appended at slot 18
so the existing slots 0..17 remain stable.
Each `gpio_alt_functions` entry defines both alternate modes for one GPIO pin.
`inputs` are driven from the selected GPIO input, and `do` and `oe` define the
@@ -37,8 +42,10 @@ exactly once, each core-vector bit must have one source, and sources are limited
to scalar signal references or one-bit constants. The generator emits the two
wrapper bindings, the core-vector wiring, and simulation-only assertions.
Existing allocated core IRQ bits retain their compatibility mapping; additions
must use currently unallocated bits and preserve the existing entries. Removing
a source leaves its core IRQ bit unallocated and driven low. Firmware does not
must use currently unallocated bits and preserve the existing entries. SDIO0
and SDIO1 use APB4 group bits 16 and 17 and core bits 10 and 21, and remain
management-only in the generated user IRQ mask. Removing a source leaves its
core IRQ bit unallocated and driven low. Firmware does not
expose a generic external interrupt API until the SoC includes a claim/complete
interrupt controller.
@@ -16,7 +16,7 @@ from typing import Any
SV_IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*$")
SV_REFERENCE_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*(?:\[[0-9]+\])?$")
SV_CONSTANT_RE = re.compile(r"(?:1'[bB][01]|'[01])$")
FABRIC_LINK_NAMES = ("mgmt", "user", "dma", "cfg", "system")
FABRIC_LINK_NAMES = ("mgmt", "user", "dma", "sdio0", "sdio1", "cfg", "system")
FABRIC_PROTOCOLS = {name: "axi4" for name in FABRIC_LINK_NAMES}
IRQ_GROUP_NAMES = ("apb4_periph", "apb4_system")
IRQ_VECTOR_WIDTH = 32
@@ -633,6 +633,8 @@ def render_bus_fabric_connections(links: list[FabricLink]) -> str:
"mgmt": "mgmt_axi4",
"user": "user_axi4",
"dma": "dma_axi4",
"sdio0": "sdio0_axi4",
"sdio1": "sdio1_axi4",
"cfg": "cfg_axi4",
"system": "system_axi4",
}
@@ -807,7 +809,9 @@ def generate(topology_path: Path, memory_map_path: Path, output_dir: Path) -> No
atomic_write(
rtl_dir / "apb4_periph_fabric.svh",
render_fabric_connection(fabric_links, "cfg", "cfg_axi4")
+ render_fabric_connection(fabric_links, "dma", "dma_axi4"),
+ render_fabric_connection(fabric_links, "dma", "dma_axi4")
+ render_fabric_connection(fabric_links, "sdio0", "sdio0_axi4")
+ render_fabric_connection(fabric_links, "sdio1", "sdio1_axi4"),
)
atomic_write(
rtl_dir / "apb4_system_fabric.svh",
+56 -21
View File
@@ -5,7 +5,7 @@
"irq_groups": [
{
"name": "apb4_periph",
"width": 16,
"width": 18,
"top_signal": "s_apb4_periph_irq"
},
{
@@ -97,6 +97,15 @@
"core_bit": 9,
"signal": "xpi.irq_o"
},
{
"name": "sdio0",
"description": "SDIO0 host interrupt",
"group": "apb4_periph",
"group_bit": 16,
"core_bit": 10,
"signal": "sdio0.irq_o",
"user_visible": false
},
{
"name": "pwm",
"description": "APB4 PWM interrupt",
@@ -186,6 +195,15 @@
"group_bit": 15,
"core_bit": 22,
"signal": "opipsram.irq_o"
},
{
"name": "sdio1",
"description": "SDIO1 host interrupt",
"group": "apb4_periph",
"group_bit": 17,
"core_bit": 21,
"signal": "sdio1.irq_o",
"user_visible": false
}
],
"wake_sources": [
@@ -212,6 +230,16 @@
"interface": "u_dma_axi4_if",
"protocol": "axi4"
},
{
"name": "sdio0",
"interface": "u_sdio0_axi4_if",
"protocol": "axi4"
},
{
"name": "sdio1",
"interface": "u_sdio1_axi4_if",
"protocol": "axi4"
},
{
"name": "cfg",
"interface": "u_cfg_axi4_if",
@@ -339,10 +367,10 @@
},
{
"slot": 15,
"name": "sdio",
"timed_interface": "u_sdio_apb4_if",
"pure_interface": "u_sdio_apb4_pure_if",
"disabled": true
"name": "sdio0",
"timed_interface": "u_sdio0_apb4_if",
"pure_interface": "u_sdio0_apb4_pure_if",
"region": "APB4_SDIO0"
},
{
"slot": 16,
@@ -357,6 +385,13 @@
"timed_interface": "u_i2c1_apb4_if",
"pure_interface": "u_i2c1_apb4_pure_if",
"region": "APB4_I2C1"
},
{
"slot": 18,
"name": "sdio1",
"timed_interface": "u_sdio1_apb4_if",
"pure_interface": "u_sdio1_apb4_pure_if",
"region": "APB4_SDIO1"
}
],
"apb4_system_targets": [
@@ -649,7 +684,7 @@
"pin": 15,
"alt0": {
"inputs": [],
"do": "u_sdio_if.sck_o",
"do": "u_sdio0_if.sck_o",
"oe": "1'b1"
},
"alt1": {
@@ -664,10 +699,10 @@
"pin": 16,
"alt0": {
"inputs": [
"u_sdio_if.cmd_di_i"
"u_sdio0_if.cmd_di_i"
],
"do": "u_sdio_if.cmd_do_o",
"oe": "u_sdio_if.cmd_oe_o"
"do": "u_sdio0_if.cmd_do_o",
"oe": "u_sdio0_if.cmd_oe_o"
},
"alt1": {
"inputs": [
@@ -681,10 +716,10 @@
"pin": 17,
"alt0": {
"inputs": [
"u_sdio_if.dat_di_i[0]"
"u_sdio0_if.dat_di_i[0]"
],
"do": "u_sdio_if.dat_do_o[0]",
"oe": "u_sdio_if.dat_oe_o[0]"
"do": "u_sdio0_if.dat_do_o[0]",
"oe": "u_sdio0_if.dat_oe_o[0]"
},
"alt1": {
"inputs": [
@@ -698,10 +733,10 @@
"pin": 18,
"alt0": {
"inputs": [
"u_sdio_if.dat_di_i[1]"
"u_sdio0_if.dat_di_i[1]"
],
"do": "u_sdio_if.dat_do_o[1]",
"oe": "u_sdio_if.dat_oe_o[1]"
"do": "u_sdio0_if.dat_do_o[1]",
"oe": "u_sdio0_if.dat_oe_o[1]"
},
"alt1": {
"inputs": [
@@ -715,10 +750,10 @@
"pin": 19,
"alt0": {
"inputs": [
"u_sdio_if.dat_di_i[2]"
"u_sdio0_if.dat_di_i[2]"
],
"do": "u_sdio_if.dat_do_o[2]",
"oe": "u_sdio_if.dat_oe_o[2]"
"do": "u_sdio0_if.dat_do_o[2]",
"oe": "u_sdio0_if.dat_oe_o[2]"
},
"alt1": {
"inputs": [
@@ -732,10 +767,10 @@
"pin": 20,
"alt0": {
"inputs": [
"u_sdio_if.dat_di_i[3]"
"u_sdio0_if.dat_di_i[3]"
],
"do": "u_sdio_if.dat_do_o[3]",
"oe": "u_sdio_if.dat_oe_o[3]"
"do": "u_sdio0_if.dat_do_o[3]",
"oe": "u_sdio0_if.dat_oe_o[3]"
},
"alt1": {
"inputs": [
+13 -6
View File
@@ -13,12 +13,15 @@ FORMAL_OPIPSRAM_DEPTH ?= 40
FORMAL_OPIPSRAM_BMC_DEPTH ?= 40
FORMAL_DMA_DEPTH ?= 24
FORMAL_DMA_COVER_DEPTH ?= 32
FORMAL_SDIO_DEPTH ?= 20
FORMAL_SDIO_COVER_DEPTH ?= 24
FORMAL_TIMEOUT ?= 60
FORMAL_WS2812_TIMEOUT ?= 120
FORMAL_I2C_TIMEOUT ?= 300
FORMAL_OPIPSRAM_TIMEOUT ?= 300
FORMAL_DMA_TIMEOUT ?= 120
FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp i2s psram opipsram dma
FORMAL_SDIO_TIMEOUT ?= 120
FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp i2s psram opipsram dma sdio
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
@@ -54,6 +57,8 @@ FORMAL_SOURCE_FILES := $(RTL_PATH)/formal/bus_formal.sv \
$(RTL_PATH)/formal/opipsram_formal_props.sv \
$(RTL_PATH)/formal/dma_formal.sv \
$(RTL_PATH)/formal/dma_formal_props.sv \
$(RTL_PATH)/formal/sdio_formal.sv \
$(RTL_PATH)/formal/sdio_formal_props.sv \
$(RTL_PATH)/top/rib_bus.sv \
$(RTL_PATH)/top/rib_error_slave.sv \
$(RTL_PATH)/top/rib_if.sv \
@@ -173,14 +178,14 @@ $(FORMAL_DIR)/%/prove.sby: $(FORMAL_DIR)/%/design.v $(RTL_PATH)/formal/%_formal_
$(FORMAL_SBY_GENERATOR)
python3 $(FORMAL_SBY_GENERATOR) --top $*_formal --input $< \
--properties $(RTL_PATH)/formal/$*_formal_props.sv --solver $(FORMAL_SOLVER) \
--mode $(if $(filter dma,$*),bmc,prove) --depth $(if $(filter dma,$*),$(FORMAL_DMA_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))))) --output $@
--mode $(if $(filter dma sdio,$*),bmc,prove) --depth $(if $(filter dma,$*),$(FORMAL_DMA_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH)))))))) --output $@
@if [ "$*" = opipsram ]; then sed -i '/^async2sync/i clk2fflogic' $@; fi
$(FORMAL_DIR)/%/cover.sby: $(FORMAL_DIR)/%/design.v $(RTL_PATH)/formal/%_formal_props.sv \
$(FORMAL_SBY_GENERATOR)
python3 $(FORMAL_SBY_GENERATOR) --top $*_formal --input $< \
--properties $(RTL_PATH)/formal/$*_formal_props.sv --solver $(FORMAL_SOLVER) \
--mode cover --depth $(if $(filter dma,$*),$(FORMAL_DMA_COVER_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))))) \
--mode cover --depth $(if $(filter dma,$*),$(FORMAL_DMA_COVER_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_COVER_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH)))))))) \
$(if $(filter i2c,$*),--no-vcd) --output $@
@if [ "$*" = opipsram ]; then sed -i '/^async2sync/i clk2fflogic' $@; fi
@@ -204,7 +209,7 @@ $(FORMAL_DIR)/%/prove.stamp: $(FORMAL_DIR)/%/prove.sby $(FORMAL_SOLVER_WRAPPER)
--log $(@D)/prove.log --result $(@D)/result-prove.json \
--env RETROSOC_BITWUZLA=$(FORMAL_BITWUZLA) \
--env PATH=$(FORMAL_SOLVER_DIR):$(PATH) \
-- timeout --foreground --kill-after=5s $(if $(filter dma,$*),$(FORMAL_DMA_TIMEOUT),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_TIMEOUT),$(if $(filter ws2812,$*),$(FORMAL_WS2812_TIMEOUT),$(if $(filter i2c,$*),$(FORMAL_I2C_TIMEOUT),$(FORMAL_TIMEOUT)))))s $(FORMAL_SBY) \
-- timeout --foreground --kill-after=5s $(if $(filter dma,$*),$(FORMAL_DMA_TIMEOUT),$(if $(filter sdio,$*),$(FORMAL_SDIO_TIMEOUT),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_TIMEOUT),$(if $(filter ws2812,$*),$(FORMAL_WS2812_TIMEOUT),$(if $(filter i2c,$*),$(FORMAL_I2C_TIMEOUT),$(FORMAL_TIMEOUT))))))s $(FORMAL_SBY) \
-f -d $(@D)/prove $<
@test "$$(awk '{print $$1}' $(@D)/prove/status)" = PASS
@touch $@
@@ -214,7 +219,7 @@ $(FORMAL_DIR)/%/cover.stamp: $(FORMAL_DIR)/%/cover.sby $(FORMAL_SOLVER_WRAPPER)
--log $(@D)/cover.log --result $(@D)/result-cover.json \
--env RETROSOC_BITWUZLA=$(FORMAL_BITWUZLA) \
--env PATH=$(FORMAL_SOLVER_DIR):$(PATH) \
-- timeout --foreground --kill-after=5s $(if $(filter dma,$*),$(FORMAL_DMA_TIMEOUT),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_TIMEOUT),$(if $(filter ws2812,$*),$(FORMAL_WS2812_TIMEOUT),$(if $(filter i2c,$*),$(FORMAL_I2C_TIMEOUT),$(FORMAL_TIMEOUT)))))s $(FORMAL_SBY) \
-- timeout --foreground --kill-after=5s $(if $(filter dma,$*),$(FORMAL_DMA_TIMEOUT),$(if $(filter sdio,$*),$(FORMAL_SDIO_TIMEOUT),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_TIMEOUT),$(if $(filter ws2812,$*),$(FORMAL_WS2812_TIMEOUT),$(if $(filter i2c,$*),$(FORMAL_I2C_TIMEOUT),$(FORMAL_TIMEOUT))))))s $(FORMAL_SBY) \
-f -d $(@D)/cover $<
@test "$$(awk '{print $$1}' $(@D)/cover/status)" = PASS
@touch $@
@@ -275,10 +280,12 @@ formal-opipsram: $(FORMAL_DIR)/opipsram/.stamp | manifest
formal-dma: $(FORMAL_DIR)/dma/.stamp | manifest
formal-sdio: $(FORMAL_DIR)/sdio/.stamp | manifest
formal-doctor:
$(MAKE) FORMAL=YES SIMU=IVERILOG SYNTH=YOSYS STA=NONE 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-i2s formal-psram formal-opipsram formal-dma 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-opipsram formal-dma formal-sdio formal-doctor formal-clean
+2
View File
@@ -107,6 +107,8 @@ CRT_SRCS := $(ROOT_PATH)/crt/arch/riscv/startup.S \
$(ROOT_PATH)/crt/src/hal/opipsram.c \
$(ROOT_PATH)/crt/src/hal/sdram_math.c \
$(ROOT_PATH)/crt/src/hal/sdram.c \
$(ROOT_PATH)/crt/src/hal/sdio_math.c \
$(ROOT_PATH)/crt/src/hal/sdio.c \
$(ROOT_PATH)/crt/src/hal/spisd.c \
$(ROOT_PATH)/crt/src/hal/qspi.c \
$(ROOT_PATH)/crt/src/hal/user_core.c
+6 -2
View File
@@ -6,5 +6,9 @@ temporary SystemVerilog include files below the selected build variant; do not
edit generated files.
The map intentionally does not contain FPGA package locations. Board-level
locations remain owned by the FPGA constraint file. Validate changes with
`make check-pin-map` and the pin-map tests before running a regression.
locations remain owned by the FPGA constraint file. Dedicated SDIO1 pads are
therefore emitted but intentionally unbound in the FPGA profile until a
package pin and 3.3 V I/O-bank contract exists. The generic
`peripheral_bidir` kind requires explicit input, output, and output-enable
signals and is reusable for any peripheral, not just SDIO. Validate changes
with `make check-pin-map` and the pin-map tests before running a regression.
+39 -2
View File
@@ -16,6 +16,8 @@ from typing import Any
VALID_FEATURES = {"HAVE_PLL"}
VALID_KINDS = {
"full",
"peripheral_output",
"peripheral_bidir",
"schmitt_input",
"sdram_dq",
"tri_input",
@@ -36,6 +38,9 @@ class Pad:
kind: str
index: int | None
signal: str | None
input_signal: str | None
output_signal: str | None
output_enable_signal: str | None
interface: str | None
peer: str | None
bind: bool
@@ -93,12 +98,20 @@ def parse_pad(entry: dict[str, Any], defaults: dict[str, Any], index: int | None
raise ValueError(f"{name}.kind is not supported")
feature = parse_feature(data.get("feature"), f"{name}.feature")
signal = format_template(data.get("signal"), index, f"{name}.signal")
input_signal = format_template(data.get("input"), index, f"{name}.input")
output_signal = format_template(data.get("output"), index, f"{name}.output")
output_enable_signal = format_template(
data.get("output_enable"), index, f"{name}.output_enable"
)
interface = format_template(data.get("interface"), index, f"{name}.interface")
peer = format_template(data.get("peer"), index, f"{name}.peer")
bind = data.get("bind", True)
if not isinstance(bind, bool):
raise ValueError(f"{name}.bind must be boolean")
if kind in {"tri_input", "schmitt_input", "tri_output"} and signal is None:
if (
kind in {"tri_input", "schmitt_input", "tri_output", "peripheral_output"}
and signal is None
):
raise ValueError(f"{name}.signal is required for {kind}")
if kind == "full" and (interface is None or index is None):
raise ValueError(f"{name} requires an interface and index")
@@ -106,7 +119,24 @@ def parse_pad(entry: dict[str, Any], defaults: dict[str, Any], index: int | None
raise ValueError(f"{name} requires an index")
if kind == "xtal" and bind and (signal is None or peer is None):
raise ValueError(f"{name} requires signal and peer")
return Pad(name, direction, feature, kind, index, signal, interface, peer, bind)
if kind == "peripheral_bidir" and (
input_signal is None or output_signal is None or output_enable_signal is None
):
raise ValueError(f"{name} requires input, output, and output_enable")
return Pad(
name,
direction,
feature,
kind,
index,
signal,
input_signal,
output_signal,
output_enable_signal,
interface,
peer,
bind,
)
def expand_group(group: Any, index: int) -> list[Pad]:
@@ -215,6 +245,13 @@ def render_pad_instance(pad: Pad) -> str | None:
f" tc_io_tri_pad {instance} (.pad({pad.name}), .c2p({pad.signal}), .c2p_en(1'b1), "
".p2c());"
)
if pad.kind == "peripheral_output":
return f" tc_io_out_pad {instance} (.pad({pad.name}), .c2p({pad.signal}));"
if pad.kind == "peripheral_bidir":
return (
f" tc_io_tri_pad {instance} (.pad({pad.name}), .c2p({pad.output_signal}), "
f".c2p_en({pad.output_enable_signal}), .p2c({pad.input_signal}));"
)
if pad.kind == "full":
return (
f" tc_io_tri_full_pad {instance} (.pad({pad.name}), .c2p({pad.interface}.do_o[{pad.index}]), "
+61
View File
@@ -55,6 +55,49 @@
"count": 32,
"interface": "u_gpio_if"
},
{
"kind": "peripheral_output",
"direction": "output",
"ports": [
{"name": "sdio1_clk_o_pad", "signal": "u_sdio1_if.sck_o"}
]
},
{
"kind": "peripheral_bidir",
"direction": "inout",
"ports": [
{
"name": "sdio1_cmd_io_pad",
"input": "u_sdio1_if.cmd_di_i",
"output": "u_sdio1_if.cmd_do_o",
"output_enable": "u_sdio1_if.cmd_oe_o"
},
{
"name": "sdio1_dat0_io_pad",
"input": "u_sdio1_if.dat_di_i[0]",
"output": "u_sdio1_if.dat_do_o[0]",
"output_enable": "u_sdio1_if.dat_oe_o[0]"
},
{
"name": "sdio1_dat1_io_pad",
"input": "u_sdio1_if.dat_di_i[1]",
"output": "u_sdio1_if.dat_do_o[1]",
"output_enable": "u_sdio1_if.dat_oe_o[1]"
},
{
"name": "sdio1_dat2_io_pad",
"input": "u_sdio1_if.dat_di_i[2]",
"output": "u_sdio1_if.dat_do_o[2]",
"output_enable": "u_sdio1_if.dat_oe_o[2]"
},
{
"name": "sdio1_dat3_io_pad",
"input": "u_sdio1_if.dat_di_i[3]",
"output": "u_sdio1_if.dat_do_o[3]",
"output_enable": "u_sdio1_if.dat_oe_o[3]"
}
]
},
{
"kind": "tri_output",
"direction": "output",
@@ -169,6 +212,12 @@
"gpio_25_io_pad": "psram_dat2_io",
"gpio_26_io_pad": "psram_dat3_io",
"gpio_30_io_pad": "gpio_io30",
"sdio1_clk_o_pad": null,
"sdio1_cmd_io_pad": null,
"sdio1_dat0_io_pad": null,
"sdio1_dat1_io_pad": null,
"sdio1_dat2_io_pad": null,
"sdio1_dat3_io_pad": null,
"uart0_tx_o_pad": "uart0_tx_o",
"uart0_rx_i_pad": "uart0_rx_i",
"xpi_sck_o_pad": "xpi_sck_o",
@@ -236,6 +285,12 @@
"gpio_27_io_pad": "s_psram_nss1",
"gpio_28_io_pad": "s_psram_nss2",
"gpio_29_io_pad": "s_psram_nss3",
"sdio1_clk_o_pad": "sdio1_clk_o",
"sdio1_cmd_io_pad": "sdio1_cmd_io",
"sdio1_dat0_io_pad": "sdio1_dat0_io",
"sdio1_dat1_io_pad": "sdio1_dat1_io",
"sdio1_dat2_io_pad": "sdio1_dat2_io",
"sdio1_dat3_io_pad": "sdio1_dat3_io",
"xpi_sck_o_pad": "s_xpi_sck_o",
"xpi_nss0_o_pad": "s_xpi_nss0_o",
"xpi_dat0_io_pad": "s_xpi_dat0_io",
@@ -307,6 +362,12 @@
"gpio_27_io_pad": "s_psram_nss1",
"gpio_28_io_pad": "s_psram_nss2",
"gpio_29_io_pad": "s_psram_nss3",
"sdio1_clk_o_pad": "s_sdio1_clk",
"sdio1_cmd_io_pad": "s_sdio1_cmd",
"sdio1_dat0_io_pad": "s_sdio1_dat[0]",
"sdio1_dat1_io_pad": "s_sdio1_dat[1]",
"sdio1_dat2_io_pad": "s_sdio1_dat[2]",
"sdio1_dat3_io_pad": "s_sdio1_dat[3]",
"uart0_tx_o_pad": "s_uart0_tx",
"uart0_rx_i_pad": "s_uart0_rx",
"xpi_sck_o_pad": "s_xpi_sck_o",
+18 -6
View File
@@ -34,11 +34,14 @@ module apb4_periph (
ws2812_if.dut ws2812,
xpi_if.dut xpi,
axi4_if.master dma_axi4,
axi4_if.master sdio0_axi4,
axi4_if.master sdio1_axi4,
sysctrl_if.dut sysctrl,
pll_ctrl_if.sysctrl pll_ctrl,
apb4_if.master sdram_cfg,
dvp_if.dut dvp,
sdio_if.dut sdio,
sdio_if.dut sdio0,
sdio_if.dut sdio1,
opipsram_if.dut opipsram,
i2c_if.dut i2c1,
input logic fault_valid_i,
@@ -271,11 +274,20 @@ axi4_stream_if #(
.irq_o (s_dvp_irq)
);
apb4_sdio u_apb4_sdio (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.apb4 (u_sdio_apb4_if),
.sdio (sdio)
apb4_sdio u_apb4_sdio0 (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.apb4 (u_sdio0_apb4_if),
.dma_axi4(sdio0_axi4),
.sdio (sdio0)
);
apb4_sdio u_apb4_sdio1 (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.apb4 (u_sdio1_apb4_if),
.dma_axi4(sdio1_axi4),
.sdio (sdio1)
);
apb4_opipsram u_apb4_opipsram (
+18 -2
View File
@@ -15,6 +15,8 @@ module axi4_bus (
axi4_if.slave mgmt_axi4,
axi4_if.slave user_axi4,
axi4_if.slave dma_axi4,
axi4_if.slave sdio0_axi4,
axi4_if.slave sdio1_axi4,
axi4_if.master cfg_axi4,
axi4_if.master system_axi4,
`ifdef HAVE_SRAM_IF
@@ -34,11 +36,13 @@ module axi4_bus (
output logic [3:0] fault_wstrb_o,
output logic fault_reserved_o,
output logic fault_access_o,
output logic [1:0] fault_master_o,
output logic [2:0] fault_master_o,
output logic [2:0] fault_code_o,
output logic [63:0] perf_mgmt_wait_o,
output logic [63:0] perf_user_wait_o,
output logic [63:0] perf_dma_wait_o,
output logic [63:0] perf_sdio0_wait_o,
output logic [63:0] perf_sdio1_wait_o,
output logic [63:0] perf_apb4_periph_wait_o,
output logic [63:0] perf_apb4_system_wait_o,
output logic [63:0] perf_sdram_wait_o,
@@ -47,7 +51,7 @@ module axi4_bus (
output logic [63:0] perf_opipsram_wait_o
// verilog_format: on
);
localparam int NumMasters = 3;
localparam int NumMasters = 5;
localparam int NumTargets = 10;
axi4_if #(
@@ -95,6 +99,16 @@ module axi4_bus (
.sink (u_master_axi4_if[2])
);
axi4_connector u_sdio0_connector (
.source(sdio0_axi4),
.sink (u_master_axi4_if[3])
);
axi4_connector u_sdio1_connector (
.source(sdio1_axi4),
.sink (u_master_axi4_if[4])
);
axi4_connector u_cfg_connector (
.source(u_target_axi4_if[0]),
.sink (cfg_axi4)
@@ -157,6 +171,8 @@ module axi4_bus (
.perf_mgmt_wait_o (perf_mgmt_wait_o),
.perf_user_wait_o (perf_user_wait_o),
.perf_dma_wait_o (perf_dma_wait_o),
.perf_sdio0_wait_o (perf_sdio0_wait_o),
.perf_sdio1_wait_o (perf_sdio1_wait_o),
.perf_apb4_periph_wait_o(perf_apb4_periph_wait_o),
.perf_apb4_system_wait_o(perf_apb4_system_wait_o),
.perf_sdram_wait_o (perf_sdram_wait_o),
+8 -4
View File
@@ -7,7 +7,7 @@
`include "rib_defs.svh"
module axi4_interconnect #(
parameter int NumMasters = 3,
parameter int NumMasters = 5,
parameter int NumTargets = 10
) (
input logic clk_i,
@@ -23,11 +23,13 @@ module axi4_interconnect #(
output logic [ 3:0] fault_wstrb_o,
output logic fault_reserved_o,
output logic fault_access_o,
output logic [ 1:0] fault_master_o,
output logic [ 2:0] fault_master_o,
output logic [ 2:0] fault_code_o,
output logic [63:0] perf_mgmt_wait_o,
output logic [63:0] perf_user_wait_o,
output logic [63:0] perf_dma_wait_o,
output logic [63:0] perf_sdio0_wait_o,
output logic [63:0] perf_sdio1_wait_o,
output logic [63:0] perf_apb4_periph_wait_o,
output logic [63:0] perf_apb4_system_wait_o,
output logic [63:0] perf_sdram_wait_o,
@@ -471,7 +473,7 @@ module axi4_interconnect #(
fault_wstrb_o = s_master_first_wstrb[master];
fault_reserved_o = `SOC_ADDR_IS_RESERVED(s_master_addr[master]);
fault_access_o = s_master_access_err[master];
fault_master_o = 2'(master);
fault_master_o = 3'(master);
fault_code_o = s_master_fault_code[master];
end
end
@@ -575,6 +577,8 @@ module axi4_interconnect #(
assign perf_mgmt_wait_o = s_perf_master_wait[0];
assign perf_user_wait_o = s_perf_master_wait[1];
assign perf_dma_wait_o = s_perf_master_wait[2];
assign perf_sdio0_wait_o = s_perf_master_wait[3];
assign perf_sdio1_wait_o = s_perf_master_wait[4];
assign perf_apb4_periph_wait_o = s_perf_target_wait[TARGET_CFG];
assign perf_apb4_system_wait_o = s_perf_target_wait[TARGET_APB4_SYSTEM];
assign perf_sdram_wait_o = s_perf_target_wait[TARGET_SDRAM];
@@ -588,7 +592,7 @@ module axi4_interconnect #(
`ifndef SYNTHESIS
initial begin
if (NumMasters != 3 || ((NumTargets != 9) && (NumTargets != 10))) begin
if (NumMasters != 5 || ((NumTargets != 9) && (NumTargets != 10))) begin
$fatal(1, "axi4_interconnect: invalid topology dimensions");
end
end
+11 -3
View File
@@ -29,6 +29,7 @@ module retrosoc (
output logic uart_tx_o,
xpi_if.dut xpi,
sdram_if.dut sdram,
sdio_if.dut sdio1,
input logic jtag_tck_i,
input logic jtag_tms_i,
input logic jtag_tdi_i,
@@ -66,7 +67,7 @@ module retrosoc (
ws2812_if u_ws2812_if ();
sysctrl_if u_sysctrl_if ();
dvp_if u_dvp_if ();
sdio_if u_sdio_if ();
sdio_if u_sdio0_if ();
opipsram_if u_opipsram_if ();
i2c_if u_i2c1_if ();
pwm_if u_pwm_if ();
@@ -82,13 +83,15 @@ module retrosoc (
logic [ 3:0] s_bus_fault_wstrb;
logic s_bus_fault_reserved;
logic s_bus_fault_access;
logic [ 1:0] s_bus_fault_master;
logic [ 2:0] s_bus_fault_master;
logic [ 2:0] s_bus_fault_code;
logic s_perf_en;
logic s_perf_clear;
logic [ 63:0] s_perf_mgmt_wait;
logic [ 63:0] s_perf_user_wait;
logic [ 63:0] s_perf_dma_wait;
logic [ 63:0] s_perf_sdio0_wait;
logic [ 63:0] s_perf_sdio1_wait;
logic [ 63:0] s_perf_apb4_periph_wait;
logic [ 63:0] s_perf_apb4_system_wait;
logic [ 63:0] s_perf_sdram_wait;
@@ -109,6 +112,8 @@ module retrosoc (
assign u_sysctrl_if.perf_mgmt_wait_i = s_perf_mgmt_wait;
assign u_sysctrl_if.perf_user_wait_i = s_perf_user_wait;
assign u_sysctrl_if.perf_dma_wait_i = s_perf_dma_wait;
assign u_sysctrl_if.perf_sdio0_wait_i = s_perf_sdio0_wait;
assign u_sysctrl_if.perf_sdio1_wait_i = s_perf_sdio1_wait;
assign u_sysctrl_if.perf_apb4_periph_wait_i = s_perf_apb4_periph_wait;
assign u_sysctrl_if.perf_apb4_system_wait_i = s_perf_apb4_system_wait;
assign u_sysctrl_if.perf_sdram_wait_i = s_perf_sdram_wait;
@@ -179,6 +184,8 @@ core_wrapper u_core_wrapper (
.perf_mgmt_wait_o (s_perf_mgmt_wait),
.perf_user_wait_o (s_perf_user_wait),
.perf_dma_wait_o (s_perf_dma_wait),
.perf_sdio0_wait_o (s_perf_sdio0_wait),
.perf_sdio1_wait_o (s_perf_sdio1_wait),
.perf_apb4_periph_wait_o(s_perf_apb4_periph_wait),
.perf_apb4_system_wait_o(s_perf_apb4_system_wait),
.perf_sdram_wait_o (s_perf_sdram_wait),
@@ -212,7 +219,8 @@ core_wrapper u_core_wrapper (
.pll_ctrl (pll_ctrl),
.sdram_cfg (u_sdram_cfg_if),
.dvp (u_dvp_if),
.sdio (u_sdio_if),
.sdio0 (u_sdio0_if),
.sdio1 (sdio1),
.opipsram (u_opipsram_if),
.i2c1 (u_i2c1_if),
.fault_valid_i (s_bus_fault_valid),
+4
View File
@@ -50,6 +50,8 @@
// GPIO29 OPI_PSRAM_IO6 BIDI QSPI_PSRAM_NSS3 OUT
// GPIO30 OPI_PSRAM_IO7 BIDI PWM_CAPTURE0 IN
// GPIO31 OPI_PSRAM_DQS BIDI PWM_CAPTURE1 IN
//
// Dedicated SDIO1 pads: SDIO1_CLK, SDIO1_CMD, SDIO1_DAT[3:0].
module retrosoc_asic (
`include "retrosoc_asic_ports.svh"
@@ -84,6 +86,7 @@ module retrosoc_asic (
gpio_if u_gpio_if ();
xpi_if u_xpi_if ();
sdram_if u_sdram_if ();
sdio_if u_sdio1_if ();
pll_ctrl_if u_pll_ctrl_if ();
`include "retrosoc_asic_pad_bindings.svh"
@@ -130,6 +133,7 @@ rcu #(
.uart_tx_o (s_uart0_tx),
.xpi (u_xpi_if),
.sdram (u_sdram_if),
.sdio1 (u_sdio1_if),
.jtag_tck_i (s_jtag_tck),
.jtag_tms_i (s_jtag_tms),
.jtag_tdi_i (s_jtag_tdi),
+12
View File
@@ -202,6 +202,18 @@
"apb4_sdio.sv",
"apb4_spisd.sv",
"apb4_xpi.sv",
"sdio_clock.sv",
"sdio_command.sv",
"sdio_core.sv",
"sdio_crc16.sv",
"sdio_crc7.sv",
"sdio_data.sv",
"sdio_define.svh",
"sdio_dma.sv",
"sdio_dma_descriptor.sv",
"sdio_if.sv",
"sdio_pkg.sv",
"sdio_reg.sv",
"spisd_cache.sv",
"spisd_core.sv",
"spisd_data.sv",
+1
View File
@@ -27,6 +27,7 @@ TEST_SOURCES = (
"crt/src/hal/timer_math.c",
"crt/src/hal/psram_math.c",
"crt/src/hal/sdram_math.c",
"crt/src/hal/sdio_math.c",
"crt/src/hal/i2s_math.c",
"crt/src/hal/dma_math.c",
"crt/src/hal/sysctrl.c",
+85 -9
View File
@@ -13,6 +13,7 @@
#include <retrosoc/hal/i2s.h>
#include <retrosoc/hal/psram.h>
#include <retrosoc/hal/sdram.h>
#include <retrosoc/hal/sdio.h>
#include <retrosoc/hal/spisd.h>
#include <retrosoc/hal/timer.h>
#include <retrosoc/hal/uart.h>
@@ -503,6 +504,81 @@ static int test_i2c_helpers(void) {
return 0;
}
static int test_sdio_helpers(void) {
rs_sdio_clock_t clock;
rs_sdio_response_t csd = {{0U, 0U, 0U, 0U, 0U}};
rs_sdio_cmd52_t cmd52 = {
.function = 2U,
.address = UINT32_C(0x123),
.write = true,
.raw = false,
.data = UINT8_C(0xA5),
};
rs_sdio_cmd53_t cmd53 = {
.function = 1U,
.address = UINT32_C(0x100),
.count = UINT16_C(512),
.write = true,
.block_mode = true,
.fixed_address = false,
};
rs_sdio_descriptor_t descriptor;
rs_sd_memory_info_t info = {0};
uint32_t value;
if ((rs_sdio_clock_calculate(72000000U, 400000U, &clock) != RS_OK) ||
(clock.half_period != 90U) || (clock.actual_hz != 400000U) ||
(rs_sdio_clock_calculate(100000000U, 50000000U, &clock) != RS_OK) ||
(clock.half_period != 1U) || (clock.actual_hz != 50000000U)) {
return 1;
}
if ((rs_sdio_clock_calculate(0U, 400000U, &clock) != RS_EINVAL) ||
(rs_sdio_clock_calculate(72000000U, 0U, &clock) != RS_EINVAL) ||
(rs_sdio_clock_calculate(72000000U, 40000001U, &clock) != RS_EINVAL) ||
(rs_sdio_clock_calculate(72000000U, 400000U, NULL) != RS_EINVAL)) {
return 2;
}
if ((rs_sd_memory_address(RS_SD_MEMORY_SDSC, 2U, &value) != RS_OK) || (value != 1024U) ||
(rs_sd_memory_address(RS_SD_MEMORY_SDHC, 2U, &value) != RS_OK) || (value != 2U) ||
(rs_sdio_cmd52_argument(&cmd52, &value) != RS_OK) || (value != UINT32_C(0xA00246A5)) ||
(rs_sdio_cmd53_argument(&cmd53, &value) != RS_OK) || (value != UINT32_C(0x9C020000))) {
return 3;
}
if ((rs_sdio_validate_dma_buffer((const void *)(uintptr_t)UINT32_C(0x40000000), 4U) != RS_OK) ||
(rs_sdio_validate_dma_buffer((const void *)(uintptr_t)UINT32_C(0x40000002), 4U) !=
RS_EINVAL) ||
(rs_sdio_descriptor_prepare(&descriptor, (uintptr_t)UINT32_C(0x40000000), 64U,
(uintptr_t)0U, true, true) != RS_OK) ||
(rs_sdio_descriptor_validate(&descriptor) != RS_OK) ||
(rs_sdio_descriptor_publish(&descriptor) != RS_OK) ||
((descriptor.control_status & (UINT32_C(1) << RS_SDIO_DESC_OWN_BIT)) == 0U)) {
return 4;
}
if (rs_sdio_descriptor_prepare(&descriptor, (uintptr_t)UINT32_C(0x40000000), 64U,
(uintptr_t)UINT32_C(0x40000FF0), false, false) != RS_EINVAL) {
return 5;
}
descriptor.buffer_addr = UINT32_C(0x40000000);
descriptor.byte_count = 64U;
descriptor.next_addr = UINT32_C(0x40000FF0);
descriptor.control_status = UINT32_C(0x00000003);
if (rs_sdio_descriptor_validate(&descriptor) != RS_EINVAL) {
return 6;
}
for (uint32_t bit = 48U; bit <= 69U; bit++) {
if (((UINT32_C(0x3FF) >> (bit - 48U)) & 1U) != 0U) {
csd.words[bit / 32U] |= UINT32_C(1) << (bit % 32U);
}
}
csd.words[3] |= UINT32_C(1) << (126U - 96U);
if ((rs_sd_memory_parse_csd(&csd, &info) != RS_OK) || (info.card_type != RS_SD_MEMORY_SDHC) ||
(info.capacity_blocks != UINT32_C(0x100000)) ||
(info.block_length != RS_SD_MEMORY_BLOCK_SIZE)) {
return 5;
}
return 0;
}
static int test_gpio_helpers(void) {
rs_gpio_filter_timing_t timing;
@@ -634,15 +710,15 @@ static int test_video_parser(void) {
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_sdram_helpers(),
test_uart_helpers(), test_i2s_helpers(),
test_i2c_helpers(), test_gpio_helpers(),
test_dma_config_validation(), test_opipsram_helpers(),
test_ps2_decoders(), test_wav_parser(),
test_video_parser(),
test_string_helpers(), test_formatter(),
test_compiler_helpers(), test_wait_helper(),
test_ws2812_helpers(), test_timer_helpers(),
test_psram_helpers(), test_sdram_helpers(),
test_uart_helpers(), test_i2s_helpers(),
test_i2c_helpers(), test_sdio_helpers(),
test_gpio_helpers(), test_dma_config_validation(),
test_opipsram_helpers(), test_ps2_decoders(),
test_wav_parser(), test_video_parser(),
};
for (size_t index = 0U; index < (sizeof(results) / sizeof(results[0])); ++index) {
+5 -2
View File
@@ -16,7 +16,7 @@ module apb4_topology_tb;
begin
s_decode_addr = address;
#1;
if (s_psel_comb !== (18'd1 << slot)) begin
if (s_psel_comb !== (19'd1 << slot)) begin
$fatal(1, "unexpected APB4 target slot for %h: got %b", address, s_psel_comb);
end
end
@@ -40,7 +40,10 @@ module apb4_topology_tb;
expect_select(`SOC_ADDR_APB4_SDRAM_BASE, 13);
expect_select(`SOC_ADDR_APB4_OPIPSRAM_BASE, 16);
expect_select(`SOC_ADDR_APB4_I2C1_BASE, 17);
expect_disabled(`SOC_ADDR_APB4_SDIO_BASE);
expect_select(`SOC_ADDR_APB4_SDIO0_BASE, 15);
expect_select(`SOC_ADDR_APB4_SDIO1_BASE, 18);
expect_disabled(`SOC_ADDR_APB4_GA_BASE);
expect_disabled(`SOC_ADDR_APB4_APU_BASE);
$display("SoC topology APB4 routing test passed");
$finish;
end
+87 -5
View File
@@ -4,7 +4,7 @@
`include "rib_defs.svh"
module axi4_interconnect_tb;
localparam int NumMasters = 3;
localparam int NumMasters = 5;
localparam int NumTargets = 9;
logic clk_i = 1'b0;
@@ -14,7 +14,7 @@ module axi4_interconnect_tb;
logic [ 3:0] fault_wstrb_o;
logic fault_reserved_o;
logic fault_access_o;
logic [ 1:0] fault_master_o;
logic [ 2:0] fault_master_o;
logic [ 2:0] fault_code_o;
logic user_bus_idle_o;
@@ -62,11 +62,14 @@ module axi4_interconnect_tb;
.perf_mgmt_wait_o (),
.perf_user_wait_o (),
.perf_dma_wait_o (),
.perf_sdio0_wait_o (),
.perf_sdio1_wait_o (),
.perf_apb4_periph_wait_o(),
.perf_apb4_system_wait_o(),
.perf_sdram_wait_o (),
.perf_psram_wait_o (),
.perf_flash_wait_o ()
.perf_flash_wait_o (),
.perf_opipsram_wait_o ()
);
for (genvar target = 0; target < NumTargets; target++) begin : GEN_TARGETS
@@ -136,7 +139,7 @@ module axi4_interconnect_tb;
end
if ((beat == int'(length)) &&
(!fault_valid_o || (fault_addr_o != address) || fault_access_o ||
(fault_master_o != 2'd0) || (fault_code_o != fault_code))) begin
(fault_master_o != 3'd0) || (fault_code_o != fault_code))) begin
$fatal(1, "management fault classification mismatch");
end
beat = beat + 1;
@@ -168,7 +171,7 @@ module axi4_interconnect_tb;
#1;
if ((masters[1].bresp != `AXI4_RESP_SLAVE_ERROR) || !fault_valid_o ||
!fault_access_o || (fault_addr_o != address) || (fault_wstrb_o != 4'hF) ||
(fault_master_o != 2'd1) || (fault_code_o != `RIB_RESP_PROTERR)) begin
(fault_master_o != 3'd1) || (fault_code_o != `RIB_RESP_PROTERR)) begin
$fatal(1, "user access fault classification mismatch");
end
@(negedge clk_i);
@@ -176,16 +179,95 @@ module axi4_interconnect_tb;
end
endtask
task automatic issue_contending_sdio_faults;
integer cycles;
begin
@(negedge clk_i);
masters[3].araddr = 32'hA000_1000;
masters[3].arlen = 8'd0;
masters[3].arvalid = 1'b1;
masters[4].araddr = 32'hA000_2000;
masters[4].arlen = 8'd0;
masters[4].arvalid = 1'b1;
do @(posedge clk_i); while (!(masters[3].arready && masters[4].arready));
@(negedge clk_i);
masters[3].arvalid = 1'b0;
masters[4].arvalid = 1'b0;
cycles = 0;
while (!masters[3].rvalid && !masters[4].rvalid && cycles < 100) begin
@(negedge clk_i);
cycles = cycles + 1;
end
if (cycles >= 100) $fatal(1, "SDIO master contention did not produce a response");
if (masters[3].rvalid) begin
masters[3].rready = 1'b1;
#1;
if ((masters[3].rresp != `AXI4_RESP_DECODE_ERROR) || !fault_valid_o ||
(fault_master_o != 3'd3) || (fault_addr_o != 32'hA000_1000) ||
(fault_code_o != `RIB_RESP_DECERR)) begin
$fatal(1, "SDIO0 fault attribution mismatch");
end
@(posedge clk_i);
@(negedge clk_i);
masters[3].rready = 1'b0;
end else begin
masters[4].rready = 1'b1;
#1;
if ((masters[4].rresp != `AXI4_RESP_DECODE_ERROR) || !fault_valid_o ||
(fault_master_o != 3'd4) || (fault_addr_o != 32'hA000_2000) ||
(fault_code_o != `RIB_RESP_DECERR)) begin
$fatal(1, "SDIO1 fault attribution mismatch");
end
@(posedge clk_i);
@(negedge clk_i);
masters[4].rready = 1'b0;
end
cycles = 0;
while (!masters[3].rvalid && !masters[4].rvalid && cycles < 100) begin
@(negedge clk_i);
cycles = cycles + 1;
end
if (cycles >= 100) $fatal(1, "second SDIO master response was lost");
if (masters[3].rvalid) begin
masters[3].rready = 1'b1;
#1;
if ((masters[3].rresp != `AXI4_RESP_DECODE_ERROR) || !fault_valid_o ||
(fault_master_o != 3'd3) || (fault_addr_o != 32'hA000_1000) ||
(fault_code_o != `RIB_RESP_DECERR)) begin
$fatal(1, "second SDIO0 fault attribution mismatch");
end
@(posedge clk_i);
@(negedge clk_i);
masters[3].rready = 1'b0;
end else begin
masters[4].rready = 1'b1;
#1;
if ((masters[4].rresp != `AXI4_RESP_DECODE_ERROR) || !fault_valid_o ||
(fault_master_o != 3'd4) || (fault_addr_o != 32'hA000_2000) ||
(fault_code_o != `RIB_RESP_DECERR)) begin
$fatal(1, "second SDIO1 fault attribution mismatch");
end
@(posedge clk_i);
@(negedge clk_i);
masters[4].rready = 1'b0;
end
end
endtask
initial begin
`INIT_MASTER(0)
`INIT_MASTER(1)
`INIT_MASTER(2)
`INIT_MASTER(3)
`INIT_MASTER(4)
repeat (4) @(posedge clk_i);
rst_n_i = 1'b1;
issue_mgmt_error_read(32'hA000_0000, 8'd0, `AXI4_RESP_DECODE_ERROR, `RIB_RESP_DECERR);
issue_mgmt_error_read(32'h3000_0000, 8'd16, `AXI4_RESP_SLAVE_ERROR, `RIB_RESP_BURSTERR);
issue_denied_user_write(32'h1000_B000);
issue_contending_sdio_faults();
$display("AXI4 interconnect fault classification test passed");
$finish;
+1 -1
View File
@@ -154,7 +154,7 @@ module bus_fault_tb;
repeat (2) @(posedge clk_i);
rst_n_i = 1'b1;
expect_fault(32'h1000_F000, 4'hF, 1'b1, `RIB_RESP_RESERVED);
expect_fault(32'h1001_2000, 4'hF, 1'b1, `RIB_RESP_RESERVED);
expect_fault(32'hA000_0000, 4'h0, 1'b0, `RIB_RESP_DECERR);
expect_user_denied(32'h1000_B000, 4'hF);
$display("bus fault responder test passed");
+72
View File
@@ -0,0 +1,72 @@
// Copyright (c) 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 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 THE WARRANTIES OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
module sdio_50mhz_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic enable_i = 1'b0;
logic [15:0] half_period_i = 16'd1;
logic sck_o;
logic launch_tick_o;
logic sample_tick_o;
logic running_o;
integer rising_count;
integer falling_count;
logic previous_sck;
always #5 clk_i = ~clk_i;
sdio_clock u_sdio_clock (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.enable_i (enable_i),
.half_period_i(half_period_i),
.sck_o (sck_o),
.launch_tick_o(launch_tick_o),
.sample_tick_o(sample_tick_o),
.running_o (running_o)
);
always @(posedge clk_i) begin
if (rst_n_i && (sck_o != previous_sck)) begin
if (sck_o) begin
rising_count = rising_count + 1;
end else begin
falling_count = falling_count + 1;
end
end
previous_sck = sck_o;
end
initial begin
rising_count = 0;
falling_count = 0;
previous_sck = 1'b0;
repeat (2) @(posedge clk_i);
rst_n_i = 1'b1;
enable_i = 1'b1;
repeat (12) @(posedge clk_i);
if ((rising_count < 5) || (falling_count < 5) ||
(rising_count > falling_count + 1) ||
(falling_count > rising_count + 1) || !running_o) begin
$fatal(1, "50 MHz phase count mismatch rise=%0d fall=%0d running=%b", rising_count,
falling_count, running_o);
end
enable_i = 1'b0;
repeat (3) @(posedge clk_i);
if (sck_o || running_o) begin
$fatal(1, "50 MHz clock did not stop low");
end
$display("SDIO 100 MHz to 50 MHz SDCLK test passed");
$finish;
end
endmodule
+267
View File
@@ -0,0 +1,267 @@
// Copyright (c) 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 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 THE WARRANTIES OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
`include "axi4_define.svh"
// Deterministic AXI4 memory target for SDIO standalone verification.
// It models the actual Mini-fabric contract: one transaction per direction,
// 32-bit data, INCR/FIXED bursts, and no outstanding IDs.
module sdio_axi_memory_responder #(
parameter int DepthWords = 16384
) (
input logic clk_i,
input logic rst_n_i,
axi4_if.slave axi4
);
logic [31:0] s_memory [0:DepthWords-1];
logic s_read_active_q;
logic [31:0] s_read_addr_q;
logic [ 7:0] s_read_len_q;
logic [ 7:0] s_read_index_q;
logic s_read_error_q;
logic s_read_bad_id_q;
logic s_read_bad_last_q;
logic s_write_active_q;
logic [31:0] s_write_addr_q;
logic [ 7:0] s_write_len_q;
logic [ 7:0] s_write_index_q;
logic s_write_error_q;
logic s_write_bad_id_q;
logic s_aw_stall_q;
logic s_ar_stall_q;
logic s_w_stall_q;
logic s_b_stall_q;
integer s_read_error_address;
integer s_write_error_address;
integer s_aw_stall_cycles;
integer s_ar_stall_cycles;
integer s_w_stall_cycles;
integer s_b_stall_cycles;
integer read_burst_count;
integer write_burst_count;
integer max_read_beats;
integer max_write_beats;
function automatic integer word_index(input logic [31:0] address_i);
integer index;
begin
index = address_i >> 2;
if ((index < 0) || (index >= DepthWords)) begin
index = 0;
end
return index;
end
endfunction
task automatic clear_memory;
begin
for (integer index = 0; index < DepthWords; index++) begin
s_memory[index] = 32'd0;
end
end
endtask
task automatic write_word(input logic [31:0] address_i, input logic [31:0] data_i);
begin
s_memory[word_index(address_i)] = data_i;
end
endtask
function automatic logic [31:0] read_word(input logic [31:0] address_i);
begin
read_word = s_memory[word_index(address_i)];
end
endfunction
task automatic set_backpressure(input integer aw_cycles_i, input integer ar_cycles_i,
input integer w_cycles_i, input integer b_cycles_i);
begin
s_aw_stall_cycles = (aw_cycles_i < 0) ? 0 : aw_cycles_i;
s_ar_stall_cycles = (ar_cycles_i < 0) ? 0 : ar_cycles_i;
s_w_stall_cycles = (w_cycles_i < 0) ? 0 : w_cycles_i;
s_b_stall_cycles = (b_cycles_i < 0) ? 0 : b_cycles_i;
end
endtask
task automatic inject_read_error(input integer address_i);
begin
s_read_error_address = address_i;
end
endtask
task automatic inject_write_error(input integer address_i);
begin
s_write_error_address = address_i;
end
endtask
task automatic inject_read_bad_id;
begin
s_read_bad_id_q = 1'b1;
end
endtask
task automatic inject_read_bad_last;
begin
s_read_bad_last_q = 1'b1;
end
endtask
task automatic inject_write_bad_id;
begin
s_write_bad_id_q = 1'b1;
end
endtask
task automatic clear_errors;
begin
s_read_error_address = -1;
s_write_error_address = -1;
s_read_bad_id_q = 1'b0;
s_write_bad_id_q = 1'b0;
s_read_bad_last_q = 1'b0;
end
endtask
assign axi4.arready = !s_read_active_q && !s_ar_stall_q;
assign axi4.rid = s_read_bad_id_q ? '1 : '0;
assign axi4.rdata = s_memory[word_index(
s_read_addr_q+((axi4.arburst==`AXI4_BURST_TYPE_FIXED)?32'd0 : ({24'd0, s_read_index_q}<<2))
)];
assign axi4.rresp = (s_read_error_q) ? `AXI4_RESP_SLAVE_ERROR : `AXI4_RESP_OKAY;
assign axi4.rlast = s_read_bad_last_q || ((s_read_index_q + 1'b1) >= (s_read_len_q + 1'b1));
assign axi4.ruser = '0;
assign axi4.rvalid = s_read_active_q;
assign axi4.awready = !s_write_active_q && !axi4.bvalid && !s_aw_stall_q;
assign axi4.wready = s_write_active_q && !s_w_stall_q;
assign axi4.bid = s_write_bad_id_q ? '1 : '0;
assign axi4.bresp = s_write_error_q ? `AXI4_RESP_SLAVE_ERROR : `AXI4_RESP_OKAY;
assign axi4.buser = '0;
assign axi4.bvalid = s_write_active_q && !s_b_stall_q &&
(s_write_index_q >= s_write_len_q + 1'b1);
always @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_read_active_q <= 1'b0;
s_read_addr_q <= 32'd0;
s_read_len_q <= 8'd0;
s_read_index_q <= 8'd0;
s_read_error_q <= 1'b0;
s_read_bad_id_q <= 1'b0;
s_read_bad_last_q <= 1'b0;
s_write_active_q <= 1'b0;
s_write_addr_q <= 32'd0;
s_write_len_q <= 8'd0;
s_write_index_q <= 8'd0;
s_write_error_q <= 1'b0;
s_write_bad_id_q <= 1'b0;
s_aw_stall_q <= 1'b0;
s_ar_stall_q <= 1'b0;
s_w_stall_q <= 1'b0;
s_b_stall_q <= 1'b0;
s_read_error_address = -1;
s_write_error_address = -1;
s_aw_stall_cycles = 0;
s_ar_stall_cycles = 0;
s_w_stall_cycles = 0;
s_b_stall_cycles = 0;
read_burst_count = 0;
write_burst_count = 0;
max_read_beats = 0;
max_write_beats = 0;
end else begin
if (s_aw_stall_cycles > 0) begin
s_aw_stall_cycles = s_aw_stall_cycles - 1;
s_aw_stall_q = 1'b1;
end else begin
s_aw_stall_q = 1'b0;
end
if (s_ar_stall_cycles > 0) begin
s_ar_stall_cycles = s_ar_stall_cycles - 1;
s_ar_stall_q = 1'b1;
end else begin
s_ar_stall_q = 1'b0;
end
if (s_w_stall_cycles > 0) begin
s_w_stall_cycles = s_w_stall_cycles - 1;
s_w_stall_q = 1'b1;
end else begin
s_w_stall_q = 1'b0;
end
if (s_b_stall_cycles > 0) begin
s_b_stall_cycles = s_b_stall_cycles - 1;
s_b_stall_q = 1'b1;
end else begin
s_b_stall_q = 1'b0;
end
if (axi4.arvalid && axi4.arready) begin
s_read_active_q = 1'b1;
s_read_addr_q = axi4.araddr;
s_read_len_q = axi4.arlen;
s_read_index_q = 8'd0;
s_read_error_q = (s_read_error_address >= 0) && (axi4.araddr == s_read_error_address);
s_read_bad_last_q = 1'b0;
read_burst_count = read_burst_count + 1;
if (({24'd0, axi4.arlen} + 32'd1) > max_read_beats) begin
max_read_beats = {24'd0, axi4.arlen} + 32'd1;
end
end
if (axi4.rvalid && axi4.rready) begin
if (axi4.rlast) begin
s_read_active_q = 1'b0;
if (s_read_error_q) begin
s_read_error_address = -1;
end
end else begin
s_read_index_q = s_read_index_q + 1'b1;
end
end
if (axi4.awvalid && axi4.awready) begin
s_write_active_q = 1'b1;
s_write_addr_q = axi4.awaddr;
s_write_len_q = axi4.awlen;
s_write_index_q = 8'd0;
s_write_error_q = (s_write_error_address >= 0) && (axi4.awaddr == s_write_error_address);
write_burst_count = write_burst_count + 1;
if (({24'd0, axi4.awlen} + 32'd1) > max_write_beats) begin
max_write_beats = {24'd0, axi4.awlen} + 32'd1;
end
end
if (axi4.wvalid && axi4.wready) begin
for (integer byte_index = 0; byte_index < 4; byte_index++) begin
if (axi4.wstrb[byte_index]) begin
s_memory[word_index(s_write_addr_q + ((axi4.awburst == `AXI4_BURST_TYPE_FIXED)
? 32'd0
: ({24'd0, s_write_index_q} << 2)))][byte_index*8+:8] <=
axi4.wdata[byte_index*8+:8];
end
end
s_write_index_q = s_write_index_q + 1'b1;
end
if (axi4.bvalid && axi4.bready) begin
s_write_active_q = 1'b0;
if (s_write_error_q) begin
s_write_error_address = -1;
end
end
end
end
initial begin
clear_memory();
clear_errors();
end
endmodule
+52
View File
@@ -0,0 +1,52 @@
`timescale 1ns / 1ps
module sdio_clock_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic enable_i = 1'b0;
logic [15:0] half_period_i = 16'd2;
logic sck_o;
logic launch_tick_o;
logic sample_tick_o;
logic running_o;
integer launch_count;
integer sample_count;
always #1 clk_i = ~clk_i;
sdio_clock u_sdio_clock (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.enable_i (enable_i),
.half_period_i(half_period_i),
.sck_o (sck_o),
.launch_tick_o(launch_tick_o),
.sample_tick_o(sample_tick_o),
.running_o (running_o)
);
always @(posedge clk_i) begin
if (launch_tick_o) launch_count = launch_count + 1;
if (sample_tick_o) sample_count = sample_count + 1;
end
initial begin
launch_count = 0;
sample_count = 0;
repeat (2) @(posedge clk_i);
rst_n_i = 1'b1;
enable_i = 1'b1;
repeat (8) @(posedge clk_i);
if ((launch_count != 2) || (sample_count != 2) || !running_o) begin
$fatal(1, "phase counts mismatch: launch=%0d sample=%0d running=%b", launch_count,
sample_count, running_o);
end
enable_i = 1'b0;
repeat (4) @(posedge clk_i);
if (sck_o || running_o) begin
$fatal(1, "clock did not stop on a complete low phase");
end
$display("SDIO phase clock test passed");
$finish;
end
endmodule
+121
View File
@@ -0,0 +1,121 @@
`timescale 1ns / 1ps
module sdio_command_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic clock_enable_i = 1'b0;
logic [ 15:0] half_period_i = 16'd1;
logic launch_tick;
logic sample_tick;
logic sck;
logic running;
logic start_i = 1'b0;
logic [ 5:0] cmd_index_i = 6'd0;
logic [ 31:0] cmd_arg_i = '0;
sdio_pkg::sdio_resp_type_e resp_type_i = sdio_pkg::SdioRespNone;
logic crc_check_i = 1'b0;
logic index_check_i = 1'b0;
logic [ 31:0] timeout_cycles_i = 32'd1000;
logic cmd_di_i = 1'b1;
logic dat0_i = 1'b1;
logic cmd_oe_o;
logic cmd_do_o;
logic busy_o;
logic done_o;
logic error_o;
logic timeout_o;
logic crc_error_o;
logic index_error_o;
logic busy_timeout_o;
logic [135:0] response_o;
logic [ 5:0] last_cmd_index_o;
integer launch_count;
integer captured_bits;
logic [ 47:0] captured_command;
always #1 clk_i = ~clk_i;
sdio_clock u_sdio_clock (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.enable_i (clock_enable_i),
.half_period_i(half_period_i),
.sck_o (sck),
.launch_tick_o(launch_tick),
.sample_tick_o(sample_tick),
.running_o (running)
);
sdio_command u_sdio_command (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.launch_tick_i (launch_tick),
.sample_tick_i (sample_tick),
.start_i (start_i),
.cmd_index_i (cmd_index_i),
.cmd_arg_i (cmd_arg_i),
.resp_type_i (resp_type_i),
.crc_check_i (crc_check_i),
.index_check_i (index_check_i),
.timeout_cycles_i(timeout_cycles_i),
.cmd_di_i (cmd_di_i),
.dat0_i (dat0_i),
.cmd_oe_o (cmd_oe_o),
.cmd_do_o (cmd_do_o),
.busy_o (busy_o),
.done_o (done_o),
.error_o (error_o),
.timeout_o (timeout_o),
.crc_error_o (crc_error_o),
.index_error_o (index_error_o),
.busy_timeout_o (busy_timeout_o),
.response_o (response_o),
.last_cmd_index_o(last_cmd_index_o)
);
always @(posedge clk_i) begin
if (launch_tick) launch_count = launch_count + 1;
end
always @(posedge sck) begin
if (cmd_oe_o) begin
captured_command = {captured_command[46:0], cmd_do_o};
captured_bits = captured_bits + 1;
end
end
initial begin
launch_count = 0;
captured_bits = 0;
captured_command = '0;
repeat (2) @(posedge clk_i);
rst_n_i = 1'b1;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
clock_enable_i = 1'b1;
wait (done_o);
if (error_o || (launch_count != 48) || (captured_bits != 48) ||
(captured_command[47:46] != 2'b01) || (captured_command[0] != 1'b1) ||
(captured_command[7:1] != sdio_pkg::sdio_crc7_calc(
captured_command[47:8]
)) || (last_cmd_index_o != 6'd0) || cmd_oe_o) begin
$fatal(1, "no-response command frame failed: err=%b launches=%0d captured=%0d frame=%h oe=%b",
error_o, launch_count, captured_bits, captured_command, cmd_oe_o);
end
@(posedge clk_i);
resp_type_i = sdio_pkg::SdioRespR1;
timeout_cycles_i = 32'd4;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
wait (done_o);
if (!error_o || !timeout_o) begin
$fatal(1, "response timeout was not reported");
end
$display("SDIO command engine test passed");
$finish;
end
endmodule
+66
View File
@@ -0,0 +1,66 @@
`timescale 1ns / 1ps
module sdio_crc_tb;
logic [39:0] crc7_data;
logic [ 6:0] crc7_value;
logic [15:0] crc16_value;
logic [15:0] crc16_seed;
logic [ 7:0] crc16_data;
sdio_crc7 u_sdio_crc7 (
.data_i(crc7_data),
.crc_o (crc7_value)
);
sdio_crc16 u_sdio_crc16 (
.crc_i (crc16_seed),
.data_i(crc16_data),
.crc_o (crc16_value)
);
initial begin
crc7_data = {1'b0, 1'b1, 6'd0, 32'd0};
#1;
if (crc7_value != 7'h4A) begin
$fatal(1, "CMD0 CRC7 mismatch: %h", crc7_value);
end
crc7_data = {1'b0, 1'b1, 6'd8, 32'h0000_01AA};
#1;
if (crc7_value != 7'h43) begin
$fatal(1, "CMD8 CRC7 mismatch: %h", crc7_value);
end
crc16_seed = 16'h0000;
crc16_data = "1";
#1;
crc16_seed = crc16_value;
crc16_data = "2";
#1;
crc16_seed = crc16_value;
crc16_data = "3";
#1;
crc16_seed = crc16_value;
crc16_data = "4";
#1;
crc16_seed = crc16_value;
crc16_data = "5";
#1;
crc16_seed = crc16_value;
crc16_data = "6";
#1;
crc16_seed = crc16_value;
crc16_data = "7";
#1;
crc16_seed = crc16_value;
crc16_data = "8";
#1;
crc16_seed = crc16_value;
crc16_data = "9";
#1;
if (crc16_value != 16'h31C3) begin
$fatal(1, "CRC16 mismatch: %h", crc16_value);
end
$display("SDIO CRC vectors passed");
$finish;
end
endmodule
+68
View File
@@ -0,0 +1,68 @@
`timescale 1ns / 1ps
module sdio_descriptor_tb;
logic [31:0] buffer_addr_i = 32'h4000_0000;
logic [31:0] byte_count_i = 32'd16;
logic [31:0] next_addr_i = '0;
logic [31:0] control_status_i = 32'h0001_0005;
logic [15:0] desc_index_i = 16'd0;
logic own_o;
logic chain_o;
logic end_o;
logic irq_o;
logic valid_o;
logic error_o;
logic address_error_o;
logic length_error_o;
logic chain_error_o;
sdio_dma_descriptor #(
.DescCount(4)
) u_sdio_dma_descriptor (
.buffer_addr_i (buffer_addr_i),
.byte_count_i (byte_count_i),
.next_addr_i (next_addr_i),
.control_status_i(control_status_i),
.desc_index_i (desc_index_i),
.own_o (own_o),
.chain_o (chain_o),
.end_o (end_o),
.irq_o (irq_o),
.valid_o (valid_o),
.error_o (error_o),
.address_error_o (address_error_o),
.length_error_o (length_error_o),
.chain_error_o (chain_error_o)
);
initial begin
#1;
if (!valid_o || error_o || !own_o || !end_o) begin
$fatal(1, "valid terminal descriptor rejected");
end
control_status_i = 32'h0000_0003;
next_addr_i = 32'h4000_0100;
#1;
if (!valid_o || error_o || !chain_o || end_o) begin
$fatal(1, "valid chained descriptor rejected");
end
next_addr_i = 32'h4000_0FF0;
#1;
if (!error_o || !address_error_o) $fatal(1, "crossing descriptor chain not rejected");
buffer_addr_i = 32'h4000_0002;
#1;
if (!error_o || !address_error_o) $fatal(1, "unaligned buffer not rejected");
buffer_addr_i = 32'h4000_0000;
byte_count_i = 32'd0;
#1;
if (!error_o || !length_error_o) $fatal(1, "zero length not rejected");
byte_count_i = 32'd16;
control_status_i = 32'h0000_0001;
next_addr_i = 32'h4000_0100;
desc_index_i = 16'd3;
#1;
if (!error_o || !chain_error_o) $fatal(1, "chain bound not rejected");
$display("SDIO descriptor validation test passed");
$finish;
end
endmodule
+314
View File
@@ -0,0 +1,314 @@
// Copyright (c) 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 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,
// INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A
// PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
`include "axi4_define.svh"
module sdio_dma_stress_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic start_i = 1'b0;
logic abort_i = 1'b0;
logic direction_i = 1'b1;
logic [31:0] desc_base_i = 32'd0;
logic [15:0] desc_count_i = 16'd1;
logic [31:0] total_bytes_i = 32'd0;
logic data_in_valid_i = 1'b0;
logic data_in_ready_o;
logic [31:0] data_in_i = 32'd0;
logic [ 3:0] data_in_strb_i = 4'hF;
logic data_in_last_i = 1'b0;
logic data_out_valid_o;
logic data_out_ready_i = 1'b1;
logic [31:0] data_out_o;
logic [ 3:0] data_out_strb_o;
logic data_out_last_o;
logic busy_o;
logic done_o;
logic error_o;
logic [ 7:0] error_code_o;
logic [31:0] current_desc_o;
logic [31:0] bytes_done_o;
logic [31:0] error_addr_o;
integer output_count;
integer guard;
logic done_seen;
integer abort_read_bursts;
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) dma_axi4 (
.aclk (clk_i),
.aresetn(rst_n_i)
);
always #5 clk_i = ~clk_i;
sdio_axi_memory_responder #(
.DepthWords(16384)
) u_memory (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.axi4 (dma_axi4)
);
sdio_dma u_sdio_dma (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (start_i),
.abort_i (abort_i),
.direction_i (direction_i),
.desc_base_i (desc_base_i),
.desc_count_i (desc_count_i),
.total_bytes_i (total_bytes_i),
.data_in_valid_i (data_in_valid_i),
.data_in_ready_o (data_in_ready_o),
.data_in_i (data_in_i),
.data_in_strb_i (data_in_strb_i),
.data_in_last_i (data_in_last_i),
.data_out_valid_o(data_out_valid_o),
.data_out_ready_i(data_out_ready_i),
.data_out_o (data_out_o),
.data_out_strb_o (data_out_strb_o),
.data_out_last_o (data_out_last_o),
.busy_o (busy_o),
.done_o (done_o),
.error_o (error_o),
.error_code_o (error_code_o),
.current_desc_o (current_desc_o),
.bytes_done_o (bytes_done_o),
.error_addr_o (error_addr_o),
.dma_axi4 (dma_axi4)
);
always @(posedge clk_i) begin
if (data_out_valid_o && data_out_ready_i) begin
output_count = output_count + 1;
end
if (done_o) begin
done_seen = 1'b1;
end
end
task automatic pulse_start;
begin
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
end
endtask
task automatic wait_done;
logic seen_busy;
begin
guard = 0;
seen_busy = busy_o;
while ((!seen_busy || !done_o) && (guard < 200000)) begin
@(posedge clk_i);
guard = guard + 1;
seen_busy = seen_busy || busy_o;
end
if (guard >= 200000) begin
$fatal(1, "SDIO DMA stress transfer did not complete");
end
end
endtask
task automatic clear_run_state;
begin
rst_n_i = 1'b0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
start_i = 1'b0;
abort_i = 1'b0;
direction_i = 1'b1;
data_out_ready_i = 1'b1;
data_in_valid_i = 1'b0;
output_count = 0;
done_seen = 1'b0;
u_memory.clear_errors();
u_memory.set_backpressure(0, 0, 0, 0);
end
endtask
initial begin
u_memory.clear_memory();
u_memory.write_word(32'h0000_0000, 32'h0000_0100);
u_memory.write_word(32'h0000_0004, 32'd64);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_0005);
for (integer index = 0; index < 16; index++) begin
u_memory.write_word(32'h0000_0100 + (index * 4), 32'h1000_0000 + index);
end
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
done_seen = 1'b0;
// A full 16-beat payload burst under independent channel backpressure.
total_bytes_i = 32'd64;
$display("DMA STRESS full burst");
u_memory.set_backpressure(2, 3, 2, 2);
pulse_start();
wait_done();
if (error_o || (output_count != 16) || (u_memory.max_read_beats != 16)) begin
$fatal(1, "16-beat DMA contract failed err=%b outputs=%0d max=%0d", error_o, output_count,
u_memory.max_read_beats);
end
// The same payload geometry split at the 4 KiB boundary.
clear_run_state();
u_memory.write_word(32'h0000_0000, 32'h0000_0FFC);
u_memory.write_word(32'h0000_0004, 32'd8);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_0005);
u_memory.write_word(32'h0000_0FFC, 32'hAAAA_0001);
u_memory.write_word(32'h0000_1000, 32'hAAAA_0002);
total_bytes_i = 32'd8;
$display("DMA STRESS split");
pulse_start();
wait_done();
if (error_o || (u_memory.read_burst_count < 3)) begin
$fatal(1, "4 KiB split DMA contract failed err=%b bursts=%0d", error_o,
u_memory.read_burst_count);
end
// A bounded two-descriptor chain must write DONE/clear OWN on both
// descriptors before the terminal event.
clear_run_state();
u_memory.write_word(32'h0000_0000, 32'h0000_0100);
u_memory.write_word(32'h0000_0004, 32'd4);
u_memory.write_word(32'h0000_0008, 32'h0000_0020);
u_memory.write_word(32'h0000_000C, 32'h0000_0003);
u_memory.write_word(32'h0000_0020, 32'h0000_0110);
u_memory.write_word(32'h0000_0024, 32'd4);
u_memory.write_word(32'h0000_0028, 32'd0);
u_memory.write_word(32'h0000_002C, 32'h0000_0005);
u_memory.write_word(32'h0000_0100, 32'h1111_0001);
u_memory.write_word(32'h0000_0110, 32'h2222_0002);
desc_count_i = 16'd2;
total_bytes_i = 32'd8;
pulse_start();
wait_done();
if (error_o || (output_count != 2) || (u_memory.read_word(
32'h0000_000C
) != 32'h0001_0002) || (u_memory.read_word(
32'h0000_002C
) != 32'h0001_0004)) begin
$fatal(1, "descriptor chain/writeback contract failed");
end
// A target read error is converted into descriptor ERROR writeback.
clear_run_state();
u_memory.write_word(32'h0000_0000, 32'h0000_0200);
u_memory.write_word(32'h0000_0004, 32'd4);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_0005);
u_memory.inject_read_error(32'h0000_0200);
total_bytes_i = 32'd4;
$display("DMA STRESS error");
pulse_start();
wait_done();
if (!error_o || (u_memory.read_word(32'h0000_000C) != 32'h0002_0004)) begin
$fatal(1, "AXI read error did not reach descriptor writeback");
end
// Abort with a stalled stream drains the accepted AXI response and
// terminates through the same bounded writeback path.
clear_run_state();
u_memory.clear_memory();
u_memory.write_word(32'h0000_0000, 32'h0000_0300);
u_memory.write_word(32'h0000_0004, 32'd128);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_0005);
total_bytes_i = 32'd128;
$display("DMA STRESS abort");
data_out_ready_i = 1'b0;
pulse_start();
wait (u_sdio_dma.s_state_q == 4'd5);
wait (u_sdio_dma.s_read_busy);
@(posedge clk_i);
abort_read_bursts = u_memory.read_burst_count;
repeat (5) @(posedge clk_i);
@(negedge clk_i);
abort_i = 1'b1;
repeat (4) @(posedge clk_i);
abort_i = 1'b0;
if (!done_seen) begin
wait_done();
end
if (!error_o) begin
$fatal(1, "DMA abort did not drain/write back error");
end
if (busy_o || (u_memory.read_burst_count != abort_read_bursts) || (u_memory.read_word(
32'h0000_000C
) != 32'h0002_0004)) begin
$fatal(1, "DMA abort left an accepted AXI read active or issued a new request");
end
// Abort during descriptor fetch still drains the accepted four-beat read
// and writes ERROR/clears OWN on the fetched active descriptor.
clear_run_state();
u_memory.write_word(32'h0000_0000, 32'h0000_0400);
u_memory.write_word(32'h0000_0004, 32'd4);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_0005);
total_bytes_i = 32'd4;
u_memory.set_backpressure(0, 3, 0, 0);
pulse_start();
wait (u_sdio_dma.s_state_q == 4'd2);
wait (u_sdio_dma.s_read_busy);
@(negedge clk_i);
abort_i = 1'b1;
@(negedge clk_i);
abort_i = 1'b0;
wait_done();
if (!error_o || (u_memory.read_word(
32'h0000_000C
) != 32'h0002_0004) || u_memory.s_read_active_q) begin
$fatal(1, "descriptor-fetch abort did not drain/write back correctly");
end
// Abort while an AXI write response is stalled must wait for B before
// terminalizing the descriptor.
clear_run_state();
direction_i = 1'b0;
u_memory.write_word(32'h0000_0000, 32'h0000_0500);
u_memory.write_word(32'h0000_0004, 32'd4);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_0005);
total_bytes_i = 32'd4;
u_memory.set_backpressure(0, 0, 0, 5);
pulse_start();
wait (u_sdio_dma.s_state_q == 4'd7);
data_in_i = 32'hDDCC_BBAA;
data_in_strb_i = 4'hF;
data_in_last_i = 1'b1;
data_in_valid_i = 1'b1;
@(posedge clk_i);
data_in_valid_i = 1'b0;
@(negedge clk_i);
abort_i = 1'b1;
@(negedge clk_i);
abort_i = 1'b0;
wait_done();
if (!error_o || (u_memory.read_word(
32'h0000_000C
) != 32'h0002_0004) || u_memory.s_write_active_q) begin
$fatal(1, "write-response abort did not drain/write back correctly");
end
$display("SDIO AXI 16-beat, 4 KiB split, error, and abort test passed");
$finish;
end
endmodule
+324
View File
@@ -0,0 +1,324 @@
`timescale 1ns / 1ps
`include "axi4_define.svh"
module sdio_dma_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic start_i = 1'b0;
logic abort_i = 1'b0;
logic direction_i = 1'b0;
logic [31:0] desc_base_i = 32'd0;
logic [15:0] desc_count_i = 16'd1;
logic [31:0] total_bytes_i = 32'd5;
logic data_in_valid_i = 1'b0;
logic data_in_ready_o;
logic [31:0] data_in_i = '0;
logic [ 3:0] data_in_strb_i = '0;
logic data_in_last_i = 1'b0;
logic data_out_valid_o;
logic data_out_ready_i = 1'b1;
logic [31:0] data_out_o;
logic [ 3:0] data_out_strb_o;
logic data_out_last_o;
logic busy_o;
logic done_o;
logic error_o;
logic [ 7:0] error_code_o;
logic [31:0] current_desc_o;
logic [31:0] bytes_done_o;
logic [31:0] error_addr_o;
logic [31:0] memory [0:2047];
logic read_active_q = 1'b0;
logic [31:0] read_addr_q = '0;
logic [ 4:0] read_beats_q = '0;
logic [ 4:0] read_index_q = '0;
logic write_active_q = 1'b0;
logic [31:0] write_addr_q = '0;
logic write_response_q = 1'b0;
integer read_bursts;
integer max_read_beats;
integer output_count;
logic descriptor_irq_seen;
logic [31:0] output_data [ 0:3];
logic [ 3:0] output_strb [ 0:3];
logic output_last [ 0:3];
logic error_mode = 1'b0;
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) dma_axi4 (
.aclk (clk_i),
.aresetn(rst_n_i)
);
always #5 clk_i = ~clk_i;
assign dma_axi4.arready = !read_active_q;
assign dma_axi4.rid = 1'b0;
assign dma_axi4.rdata = memory[(read_addr_q>>2)+read_index_q];
assign dma_axi4.rresp =
error_mode && (read_addr_q == 32'h0000_0200) ? `AXI4_RESP_SLAVE_ERROR :
`AXI4_RESP_OKAY;
assign dma_axi4.rlast = (read_index_q + 1'b1) >= read_beats_q;
assign dma_axi4.ruser = '0;
assign dma_axi4.rvalid = read_active_q;
assign dma_axi4.awready = !write_active_q && !write_response_q;
assign dma_axi4.wready = write_active_q;
assign dma_axi4.bid = 1'b0;
assign dma_axi4.bresp = `AXI4_RESP_OKAY;
assign dma_axi4.buser = '0;
assign dma_axi4.bvalid = write_response_q;
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
read_active_q <= 1'b0;
read_addr_q <= '0;
read_beats_q <= '0;
read_index_q <= '0;
write_active_q <= 1'b0;
write_addr_q <= '0;
write_response_q <= 1'b0;
read_bursts <= 0;
max_read_beats <= 0;
end else begin
if (dma_axi4.arvalid && dma_axi4.arready) begin
read_active_q <= 1'b1;
read_addr_q <= dma_axi4.araddr;
read_beats_q <= dma_axi4.arlen + 1'b1;
read_index_q <= '0;
read_bursts <= read_bursts + 1;
if ((dma_axi4.arlen + 1'b1) > max_read_beats) begin
max_read_beats <= dma_axi4.arlen + 1'b1;
end
end
if (dma_axi4.rvalid && dma_axi4.rready) begin
if (dma_axi4.rlast) begin
read_active_q <= 1'b0;
end else begin
read_index_q <= read_index_q + 1'b1;
end
end
if (dma_axi4.awvalid && dma_axi4.awready) begin
write_active_q <= 1'b1;
write_addr_q <= dma_axi4.awaddr;
end
if (dma_axi4.wvalid && dma_axi4.wready) begin
for (integer byte_index = 0; byte_index < 4; byte_index++) begin
if (dma_axi4.wstrb[byte_index]) begin
memory[(write_addr_q>>2)][byte_index*8+:8] <= dma_axi4.wdata[byte_index*8+:8];
end
end
if (dma_axi4.wlast) begin
write_active_q <= 1'b0;
write_response_q <= 1'b1;
end else begin
write_addr_q <= write_addr_q + 4;
end
end
if (dma_axi4.bvalid && dma_axi4.bready) begin
write_response_q <= 1'b0;
end
end
end
always_ff @(posedge clk_i) begin
if (data_out_valid_o && data_out_ready_i) begin
output_data[output_count] <= data_out_o;
output_strb[output_count] <= data_out_strb_o;
output_last[output_count] <= data_out_last_o;
output_count <= output_count + 1;
end
if (u_sdio_dma.descriptor_irq_o) begin
descriptor_irq_seen <= 1'b1;
end
end
sdio_dma #(
.AddrWidth(32),
.DataWidth(32),
.DescCount(16)
) u_sdio_dma (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (start_i),
.abort_i (abort_i),
.direction_i (direction_i),
.desc_base_i (desc_base_i),
.desc_count_i (desc_count_i),
.total_bytes_i (total_bytes_i),
.data_in_valid_i (data_in_valid_i),
.data_in_ready_o (data_in_ready_o),
.data_in_i (data_in_i),
.data_in_strb_i (data_in_strb_i),
.data_in_last_i (data_in_last_i),
.data_out_valid_o(data_out_valid_o),
.data_out_ready_i(data_out_ready_i),
.data_out_o (data_out_o),
.data_out_strb_o (data_out_strb_o),
.data_out_last_o (data_out_last_o),
.busy_o (busy_o),
.done_o (done_o),
.error_o (error_o),
.error_code_o (error_code_o),
.current_desc_o (current_desc_o),
.bytes_done_o (bytes_done_o),
.error_addr_o (error_addr_o),
.dma_axi4 (dma_axi4)
);
initial begin
for (integer index = 0; index < 2048; index++) memory[index] = '0;
output_count = 0;
descriptor_irq_seen = 1'b0;
direction_i = 1'b1;
memory[0] = 32'h0000_0FFC;
memory[1] = 32'd5;
memory[2] = 32'd0;
memory[3] = 32'h0000_000D;
memory[32'h0FFC>>2] = 32'h4433_2211;
memory[32'h1000>>2] = 32'h0000_0055;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
wait (done_o);
if (error_o || (output_count != 2) || (bytes_done_o != 32'd5) ||
(read_bursts < 3) || (max_read_beats > 16) ||
(output_strb[0] != 4'hF) || (output_strb[1] != 4'h1) ||
(output_data[0] != 32'h4433_2211) || (output_data[1] != 32'h0000_0055) ||
!output_last[1] || (memory[3] != 32'h0001_000C)) begin
$fatal(1, "DMA test failed err=%b code=%h outputs=%0d bytes=%0d bursts=%0d max=%0d wb=%h",
error_o, error_code_o, output_count, bytes_done_o, read_bursts, max_read_beats,
memory[3]);
end
@(posedge clk_i);
#1;
if (!descriptor_irq_seen) $fatal(1, "descriptor IRQ event was not visible");
// Card-to-host is the opposite stream direction: input stream bytes are
// AXI-written with low lane zero as the first byte.
rst_n_i = 1'b0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
output_count = 0;
direction_i = 1'b0;
desc_base_i = 32'd0;
total_bytes_i = 32'd5;
error_mode = 1'b0;
memory[0] = 32'h0000_0200;
memory[1] = 32'd5;
memory[2] = 32'd0;
memory[3] = 32'h0000_000D;
memory[32'h0200>>2] = '0;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
@(negedge clk_i);
data_in_i = 32'h8877_6655;
data_in_strb_i = 4'hF;
data_in_last_i = 1'b0;
data_in_valid_i = 1'b1;
while (u_sdio_dma.s_state_q != 4'd7) @(posedge clk_i);
@(posedge clk_i);
@(negedge clk_i);
data_in_i = 32'h0000_0099;
data_in_strb_i = 4'h1;
data_in_last_i = 1'b1;
while (u_sdio_dma.s_state_q != 4'd7) @(posedge clk_i);
@(posedge clk_i);
@(negedge clk_i);
data_in_valid_i = 1'b0;
wait (done_o);
if (error_o || (bytes_done_o != 32'd5) ||
(memory[32'h0200>>2] != 32'h8877_6655) ||
(memory[32'h0204>>2][7:0] != 8'h99) ||
(memory[3] != 32'h0001_000C) || !descriptor_irq_seen) begin
$fatal(1, "card-to-host DMA failed err=%b code=%h bytes=%0d data=%h tail=%h wb=%h", error_o,
error_code_o, bytes_done_o, memory[32'h0200>>2], memory[32'h0204>>2], memory[3]);
end
for (integer tail_bytes = 1; tail_bytes <= 3; tail_bytes++) begin
rst_n_i = 1'b0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
data_in_valid_i = 1'b0;
direction_i = 1'b0;
desc_base_i = 32'd0;
total_bytes_i = tail_bytes;
memory[0] = 32'h0000_0300 + (tail_bytes * 32);
memory[1] = tail_bytes;
memory[2] = 32'd0;
memory[3] = 32'h0000_0005;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
while (u_sdio_dma.s_state_q != 4'd7) @(posedge clk_i);
data_in_i = 32'h0403_0201;
data_in_strb_i = (4'b0001 << tail_bytes) - 1'b1;
data_in_last_i = 1'b1;
data_in_valid_i = 1'b1;
@(posedge clk_i);
@(negedge clk_i);
data_in_valid_i = 1'b0;
wait (done_o);
for (integer byte_index = 0; byte_index < tail_bytes; byte_index++) begin
if (memory[(32'h0000_0300 + (tail_bytes * 32) >> 2) + (byte_index >> 2)]
[(byte_index % 4)*8+:8] != byte_index + 1) begin
$fatal(1, "DMA tail readback failed bytes=%0d byte=%0d", tail_bytes, byte_index);
end
end
if (error_o || (bytes_done_o != tail_bytes) || (memory[3] != 32'h0001_0004)) begin
$fatal(1, "DMA tail transfer failed bytes=%0d err=%b wb=%h", tail_bytes, error_o,
memory[3]);
end
end
// A descriptor at ...FF0 is rejected before an AXI fetch.
rst_n_i = 1'b0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
desc_base_i = 32'h0000_0FF0;
direction_i = 1'b1;
total_bytes_i = 32'd4;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
wait (done_o);
if (!error_o || (read_bursts != 0)) begin
$fatal(1, "descriptor boundary rejection failed err=%b bursts=%0d", error_o, read_bursts);
end
// Restore a clean responder state for the directed AXI error check.
rst_n_i = 1'b0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
memory[0] = 32'h0000_0200;
memory[1] = 32'd4;
memory[2] = 32'd0;
memory[3] = 32'h0000_0005;
memory[32'h0200>>2] = 32'hAABB_CCDD;
desc_base_i = 32'd0;
total_bytes_i = 32'd4;
direction_i = 1'b1;
error_mode = 1'b1;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
wait (done_o);
if (!error_o || (memory[3] != 32'h0002_0004)) begin
$fatal(1, "AXI error was not written back: err=%b code=%h wb=%h", error_o, error_code_o,
memory[3]);
end
$display("SDIO DMA descriptor, tail, and 4 KiB split test passed");
$finish;
end
endmodule
+365
View File
@@ -0,0 +1,365 @@
// Copyright (c) 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 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,
// INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A
// PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
module sdio_dual_tb;
localparam logic [31:0] HostCtrl = 32'h00C;
localparam logic [31:0] ClockCtrl = 32'h010;
localparam logic [31:0] BusCtrl = 32'h018;
localparam logic [31:0] CmdArg = 32'h040;
localparam logic [31:0] CmdCfg = 32'h044;
localparam logic [31:0] CmdStart = 32'h048;
localparam logic [31:0] CmdStatus = 32'h04C;
localparam logic [31:0] Resp0 = 32'h050;
localparam logic [31:0] BlockSize = 32'h080;
localparam logic [31:0] BlockCount = 32'h084;
localparam logic [31:0] DataCfg = 32'h088;
localparam logic [31:0] DataStart = 32'h08C;
localparam logic [31:0] PioData = 32'h090;
localparam logic [31:0] Status = 32'h028;
localparam logic [31:0] ErrorStatus = 32'h10C;
logic clk_i = 1'b0;
logic rst0_n_i = 1'b0;
logic rst1_n_i = 1'b0;
apb4_if apb0 (
.pclk (clk_i),
.presetn(rst0_n_i)
);
apb4_if apb1 (
.pclk (clk_i),
.presetn(rst1_n_i)
);
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) axi0 (
.aclk (clk_i),
.aresetn(rst0_n_i)
);
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) axi1 (
.aclk (clk_i),
.aresetn(rst1_n_i)
);
sdio_if sdio0 ();
sdio_if sdio1 ();
logic card0_cmd_do;
logic [ 3:0] card0_dat_do;
logic card0_irq;
logic card1_cmd_do;
logic [ 3:0] card1_dat_do;
logic card1_irq;
logic [31:0] value0;
logic [31:0] value1;
integer guard0;
integer guard1;
assign sdio0.cmd_di_i = sdio0.cmd_oe_o ? sdio0.cmd_do_o : card0_cmd_do;
assign sdio0.dat_di_i = (sdio0.dat_oe_o & sdio0.dat_do_o) | (~sdio0.dat_oe_o & card0_dat_do);
assign sdio1.cmd_di_i = sdio1.cmd_oe_o ? sdio1.cmd_do_o : card1_cmd_do;
assign sdio1.dat_di_i = (sdio1.dat_oe_o & sdio1.dat_do_o) | (~sdio1.dat_oe_o & card1_dat_do);
always #5 clk_i = ~clk_i;
apb4_sdio u_sdio0 (
.clk_i (clk_i),
.rst_n_i (rst0_n_i),
.apb4 (apb0),
.dma_axi4(axi0),
.sdio (sdio0)
);
apb4_sdio u_sdio1 (
.clk_i (clk_i),
.rst_n_i (rst1_n_i),
.apb4 (apb1),
.dma_axi4(axi1),
.sdio (sdio1)
);
sdio_sd_memory_model #(
.HighCapacity(1'b1),
.BusyCycles (8)
) u_card0 (
.rst_n_i (rst0_n_i),
.sck_i (sdio0.sck_o),
.cmd_oe_i(sdio0.cmd_oe_o),
.cmd_do_i(sdio0.cmd_do_o),
.cmd_do_o(card0_cmd_do),
.dat_oe_i(sdio0.dat_oe_o),
.dat_do_i(sdio0.dat_do_o),
.dat_do_o(card0_dat_do),
.irq_o (card0_irq)
);
sdio_sdio_model #(
.FunctionCount(2),
.FunctionBytes(4096),
.BusyCycles (8)
) u_card1 (
.rst_n_i (rst1_n_i),
.sck_i (sdio1.sck_o),
.cmd_oe_i(sdio1.cmd_oe_o),
.cmd_do_i(sdio1.cmd_do_o),
.cmd_do_o(card1_cmd_do),
.dat_oe_i(sdio1.dat_oe_o),
.dat_do_i(sdio1.dat_do_o),
.dat_do_o(card1_dat_do),
.irq_o (card1_irq)
);
sdio_axi_memory_responder u_mem0 (
.clk_i (clk_i),
.rst_n_i(rst0_n_i),
.axi4 (axi0)
);
sdio_axi_memory_responder u_mem1 (
.clk_i (clk_i),
.rst_n_i(rst1_n_i),
.axi4 (axi1)
);
task automatic apb0_idle;
begin
apb0.psel = 1'b0;
apb0.penable = 1'b0;
apb0.pwrite = 1'b0;
apb0.paddr = '0;
apb0.pwdata = '0;
apb0.pstrb = '0;
end
endtask
task automatic apb1_idle;
begin
apb1.psel = 1'b0;
apb1.penable = 1'b0;
apb1.pwrite = 1'b0;
apb1.paddr = '0;
apb1.pwdata = '0;
apb1.pstrb = '0;
end
endtask
task automatic apb0_write(input logic [31:0] address_i, input logic [31:0] data_i);
begin
@(negedge clk_i);
apb0.paddr = address_i;
apb0.pwdata = data_i;
apb0.pstrb = 4'hF;
apb0.pwrite = 1'b1;
apb0.psel = 1'b1;
apb0.penable = 1'b0;
@(negedge clk_i);
apb0.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb0.pready || apb0.pslverr) $fatal(1, "dual APB0 write failed %h", address_i);
apb0_idle();
end
endtask
task automatic apb1_write(input logic [31:0] address_i, input logic [31:0] data_i);
begin
@(negedge clk_i);
apb1.paddr = address_i;
apb1.pwdata = data_i;
apb1.pstrb = 4'hF;
apb1.pwrite = 1'b1;
apb1.psel = 1'b1;
apb1.penable = 1'b0;
@(negedge clk_i);
apb1.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb1.pready || apb1.pslverr) $fatal(1, "dual APB1 write failed %h", address_i);
apb1_idle();
end
endtask
task automatic apb0_read(input logic [31:0] address_i, output logic [31:0] data_o);
begin
@(negedge clk_i);
apb0.paddr = address_i;
apb0.pwrite = 1'b0;
apb0.psel = 1'b1;
apb0.penable = 1'b0;
apb0.pstrb = '0;
@(negedge clk_i);
apb0.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb0.pready || apb0.pslverr) $fatal(1, "dual APB0 read failed %h", address_i);
data_o = apb0.prdata;
apb0_idle();
end
endtask
task automatic apb1_read(input logic [31:0] address_i, output logic [31:0] data_o);
begin
@(negedge clk_i);
apb1.paddr = address_i;
apb1.pwrite = 1'b0;
apb1.psel = 1'b1;
apb1.penable = 1'b0;
apb1.pstrb = '0;
@(negedge clk_i);
apb1.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb1.pready || apb1.pslverr) $fatal(1, "dual APB1 read failed %h", address_i);
data_o = apb1.prdata;
apb1_idle();
end
endtask
task automatic wait0_command;
logic seen_busy;
begin
guard0 = 0;
seen_busy = 1'b0;
while (guard0 < 20000) begin
@(posedge clk_i);
guard0 = guard0 + 1;
if (u_sdio0.u_sdio_core.s_cmd_busy) seen_busy = 1'b1;
if (seen_busy && (u_sdio0.u_sdio_core.u_sdio_command.s_state_q == 3'd0)) begin
break;
end
end
if (guard0 >= 20000) $fatal(1, "dual SDIO0 command did not drain");
end
endtask
task automatic wait1_command;
logic seen_busy;
begin
guard1 = 0;
seen_busy = 1'b0;
while (guard1 < 20000) begin
@(posedge clk_i);
guard1 = guard1 + 1;
if (u_sdio1.u_sdio_core.s_cmd_busy) seen_busy = 1'b1;
if (seen_busy && (u_sdio1.u_sdio_core.u_sdio_command.s_state_q == 3'd0)) begin
break;
end
end
if (guard1 >= 20000) $fatal(1, "dual SDIO1 command did not drain");
end
endtask
task automatic wait0_data;
logic seen_busy;
begin
guard0 = 0;
seen_busy = 1'b0;
while (guard0 < 200000) begin
@(posedge clk_i);
guard0 = guard0 + 1;
if (u_sdio0.u_sdio_core.s_data_busy || u_sdio0.u_sdio_core.s_dma_busy) begin
seen_busy = 1'b1;
end
if (seen_busy && !u_sdio0.u_sdio_core.s_data_busy && !u_sdio0.u_sdio_core.s_dma_busy) begin
break;
end
end
if (guard0 >= 200000) $fatal(1, "dual SDIO0 data did not drain");
end
endtask
task automatic wait1_data;
logic seen_busy;
begin
guard1 = 0;
seen_busy = 1'b0;
while (guard1 < 200000) begin
@(posedge clk_i);
guard1 = guard1 + 1;
if (u_sdio1.u_sdio_core.s_data_busy || u_sdio1.u_sdio_core.s_dma_busy) begin
seen_busy = 1'b1;
end
if (seen_busy && !u_sdio1.u_sdio_core.s_data_busy && !u_sdio1.u_sdio_core.s_dma_busy) begin
break;
end
end
if (guard1 >= 200000) $fatal(1, "dual SDIO1 data did not drain");
end
endtask
task automatic run_memory_context;
begin
apb0_write(ClockCtrl, 32'h0000_0101);
apb0_write(HostCtrl, 32'h0000_0005);
apb0_write(CmdArg, 32'd2);
apb0_write(CmdCfg, {18'd0, 1'b1, 1'b1, 4'd1, 2'd0, 6'd17});
apb0_write(CmdStart, 32'd1);
wait0_command();
apb0_read(CmdStatus, value0);
if ((value0 & 32'h1E) != 0) $fatal(1, "dual SDIO0 CMD17 failed");
apb0_write(BusCtrl, 32'd0);
apb0_write(BlockSize, 32'd4);
apb0_write(BlockCount, 32'd1);
apb0_write(DataCfg, 32'd0);
u_card0.arm_read(1024, 4, 1'b0, 1'b0);
apb0_write(DataStart, 32'd1);
wait0_data();
apb0_read(PioData, value0);
if (value0 != 32'h0000_0000) $fatal(1, "dual SDIO0 data mismatch");
end
endtask
task automatic run_sdio_context;
begin
apb1_write(ClockCtrl, 32'h0000_0101);
apb1_write(HostCtrl, 32'h0000_0005);
apb1_write(CmdArg, 32'd0);
apb1_write(CmdCfg, {18'd0, 1'b1, 1'b1, 4'd5, 2'd0, 6'd5});
apb1_write(CmdStart, 32'd1);
wait1_command();
apb1_write(CmdArg, {1'b0, 3'd0, 1'b0, 1'b0, 17'h00000, 1'b0, 8'h00});
apb1_write(CmdCfg, {18'd0, 1'b1, 1'b1, 4'd6, 2'd0, 6'd52});
apb1_write(CmdStart, 32'd1);
wait1_command();
apb1_write(CmdArg, {1'b0, 3'd1, 1'b0, 1'b1, 17'h00020, 9'd4});
apb1_write(CmdCfg, {18'd0, 1'b0, 1'b0, 4'd0, 2'd0, 6'd53});
apb1_write(CmdStart, 32'd1);
wait1_command();
apb1_write(BusCtrl, 32'd1);
apb1_write(BlockSize, 32'd4);
apb1_write(BlockCount, 32'd1);
apb1_write(DataCfg, 32'h0000_0002);
u_card1.arm_read(4096 + 32, 4, 1'b1, 1'b0);
apb1_write(DataStart, 32'd1);
wait1_data();
apb1_read(PioData, value1);
if (value1 != 32'd0) $fatal(1, "dual SDIO1 data mismatch");
u_card1.set_irq();
end
endtask
initial begin
apb0_idle();
apb1_idle();
repeat (4) @(posedge clk_i);
rst0_n_i = 1'b1;
rst1_n_i = 1'b1;
fork
run_memory_context();
run_sdio_context();
join
apb0_read(Status, value0);
apb1_read(Status, value1);
if (value0 == value1) $fatal(1, "dual status contexts were aliased");
if (sdio1.dat_di_i[1] != 1'b0) begin
$fatal(1, "dual SDIO1 DAT1 context is not isolated");
end
apb0_read(ErrorStatus, value0);
if (value0 != 0) $fatal(1, "SDIO0 inherited SDIO1 error state: %h", value0);
$display("SDIO dual-instance isolation and concurrent traffic test passed");
$finish;
end
endmodule
File diff suppressed because it is too large. Load diff
+294
View File
@@ -0,0 +1,294 @@
`timescale 1ns / 1ps
module sdio_register_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic busy_i = 1'b0;
logic [ 31:0] status_i = 32'h0000_0011;
logic [ 31:0] clock_actual_i = 32'd400000;
logic [ 31:0] cmd_status_i = '0;
logic [135:0] response_i = '0;
logic [ 31:0] data_status_i = '0;
logic [ 31:0] fifo_status_i = '0;
logic [ 31:0] dma_status_i = '0;
logic [ 31:0] current_desc_i = '0;
logic [ 31:0] bytes_done_i = '0;
logic [ 31:0] dma_error_addr_i = '0;
logic [ 31:0] dma_error_i = '0;
logic [ 31:0] error_status_i = '0;
logic [ 5:0] last_cmd_i = '0;
logic [ 31:0] crc_error_count_i = '0;
logic [ 31:0] timeout_count_i = '0;
logic [ 31:0] axi_error_count_i = '0;
logic [ 31:0] stall_count_i = '0;
logic [ 7:0] irq_event_i = '0;
logic [ 31:0] pio_rdata_i = '0;
logic pio_valid_i = 1'b0;
logic pio_ready_i = 1'b0;
logic pio_read_consume_o;
logic host_enable_o;
logic host_irq_enable_o;
logic clock_enable_o;
logic [ 15:0] half_period_o;
logic [ 1:0] bus_width_o;
logic [ 31:0] timeout_cmd_o;
logic [ 31:0] timeout_data_o;
logic [ 31:0] timeout_busy_o;
logic [ 5:0] cmd_index_o;
logic [ 31:0] cmd_arg_o;
sdio_pkg::sdio_resp_type_e cmd_resp_type_o;
logic cmd_crc_check_o;
logic cmd_index_check_o;
logic cmd_start_o;
logic [ 15:0] block_size_o;
logic [ 15:0] block_count_o;
logic data_direction_o;
logic data_dma_enable_o;
logic data_block_mode_o;
logic data_fixed_addr_o;
logic data_start_o;
logic pio_wvalid_o;
logic [ 31:0] pio_wdata_o;
logic [ 3:0] pio_wstrb_o;
logic [ 31:0] desc_base_o;
logic [ 15:0] desc_count_o;
logic dma_start_o;
logic dma_abort_o;
logic irq_dat1_enable_o;
logic irq_o;
integer pio_consume_count;
apb4_if apb4 (
.pclk (clk_i),
.presetn(rst_n_i)
);
always #5 clk_i = ~clk_i;
always @(posedge clk_i) begin
if (pio_read_consume_o) begin
pio_consume_count = pio_consume_count + 1;
end
end
sdio_reg u_sdio_reg (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.busy_i (busy_i),
.status_i (status_i),
.clock_actual_i (clock_actual_i),
.cmd_status_i (cmd_status_i),
.response_i (response_i),
.data_status_i (data_status_i),
.fifo_status_i (fifo_status_i),
.dma_status_i (dma_status_i),
.current_desc_i (current_desc_i),
.bytes_done_i (bytes_done_i),
.dma_error_addr_i (dma_error_addr_i),
.dma_error_i (dma_error_i),
.error_status_i (error_status_i),
.last_cmd_i (last_cmd_i),
.crc_error_count_i (crc_error_count_i),
.timeout_count_i (timeout_count_i),
.axi_error_count_i (axi_error_count_i),
.stall_count_i (stall_count_i),
.irq_event_i (irq_event_i),
.pio_rdata_i (pio_rdata_i),
.pio_valid_i (pio_valid_i),
.pio_ready_i (pio_ready_i),
.pio_read_consume_o(pio_read_consume_o),
.host_enable_o (host_enable_o),
.host_irq_enable_o (host_irq_enable_o),
.clock_enable_o (clock_enable_o),
.half_period_o (half_period_o),
.bus_width_o (bus_width_o),
.timeout_cmd_o (timeout_cmd_o),
.timeout_data_o (timeout_data_o),
.timeout_busy_o (timeout_busy_o),
.cmd_index_o (cmd_index_o),
.cmd_arg_o (cmd_arg_o),
.cmd_resp_type_o (cmd_resp_type_o),
.cmd_crc_check_o (cmd_crc_check_o),
.cmd_index_check_o (cmd_index_check_o),
.cmd_start_o (cmd_start_o),
.block_size_o (block_size_o),
.block_count_o (block_count_o),
.data_direction_o (data_direction_o),
.data_dma_enable_o (data_dma_enable_o),
.data_block_mode_o (data_block_mode_o),
.data_fixed_addr_o (data_fixed_addr_o),
.data_start_o (data_start_o),
.pio_wvalid_o (pio_wvalid_o),
.pio_wdata_o (pio_wdata_o),
.pio_wstrb_o (pio_wstrb_o),
.desc_base_o (desc_base_o),
.desc_count_o (desc_count_o),
.dma_start_o (dma_start_o),
.dma_abort_o (dma_abort_o),
.irq_dat1_enable_o (irq_dat1_enable_o),
.irq_o (irq_o),
.apb4 (apb4)
);
task automatic apb_write(input logic [31:0] address, input logic [31:0] data,
input logic [3:0] strobe, input logic expected_error);
begin
@(negedge clk_i);
apb4.paddr = address;
apb4.pwdata = data;
apb4.pstrb = strobe;
apb4.pwrite = 1'b1;
apb4.psel = 1'b1;
apb4.penable = 1'b0;
@(negedge clk_i);
apb4.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb4.pready || (apb4.pslverr != expected_error)) begin
$fatal(1, "APB write %h ready=%b err=%b expected=%b", address, apb4.pready, apb4.pslverr,
expected_error);
end
@(negedge clk_i);
apb4.psel = 1'b0;
apb4.penable = 1'b0;
end
endtask
task automatic apb_read_error(input logic [31:0] address);
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;
@(posedge clk_i);
#1;
if (!apb4.pready || !apb4.pslverr) begin
$fatal(1, "APB read %h did not report empty PIO error", address);
end
@(negedge clk_i);
apb4.psel = 1'b0;
apb4.penable = 1'b0;
end
endtask
task automatic apb_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;
@(posedge clk_i);
#1;
if (!apb4.pready || apb4.pslverr) begin
$fatal(1, "APB read %h ready=%b err=%b", address, apb4.pready, apb4.pslverr);
end
data = apb4.prdata;
@(negedge clk_i);
apb4.psel = 1'b0;
apb4.penable = 1'b0;
end
endtask
initial begin
logic [31:0] value;
apb4.paddr = '0;
apb4.pprot = '0;
apb4.psel = 1'b0;
apb4.penable = 1'b0;
apb4.pwrite = 1'b0;
apb4.pwdata = '0;
apb4.pstrb = '0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
apb_read(32'h000, value);
if (value != 32'h5344_494F) $fatal(1, "IP ID mismatch");
apb_read(32'h008, value);
if (value != 32'h0000_01FF) $fatal(1, "capability mismatch");
apb_write(32'h010, 32'h0000_0501, 4'hF, 1'b0);
apb_read(32'h010, value);
if (value != 32'h0000_0501) $fatal(1, "clock control mismatch: %h", value);
apb_write(32'h040, 32'hCAFE_BABE, 4'hF, 1'b0);
apb_write(32'h044, 32'h0000_3108, 4'hF, 1'b0);
apb_write(32'h048, 32'h0000_0001, 4'h1, 1'b0);
#1;
if (!cmd_start_o || (cmd_index_o != 6'd8) || (cmd_arg_o != 32'hCAFE_BABE)) begin
$fatal(1, "command pulse/configuration mismatch");
end
apb_write(32'h3FC, 32'h0, 4'hF, 1'b1);
busy_i = 1'b1;
apb_write(32'h040, 32'h0, 4'hF, 1'b1);
busy_i = 1'b0;
apb_write(32'h104, 32'h0000_0001, 4'h1, 1'b0);
apb_write(32'h108, 32'h0000_0001, 4'h1, 1'b0);
if (!irq_o) $fatal(1, "IRQ test did not assert IRQ");
apb_read(32'h100, value);
if ((value & 32'h1) == 0) $fatal(1, "IRQ status did not latch");
apb_write(32'h100, 32'h0000_0001, 4'h1, 1'b0);
if (irq_o) $fatal(1, "IRQ W1C did not clear IRQ");
apb_write(32'h104, 32'h0000_0004, 4'h1, 1'b0);
irq_event_i = 8'h04;
@(posedge clk_i);
irq_event_i = 8'h00;
apb_read(32'h100, value);
if ((value & 32'h04) == 0 || !irq_o) $fatal(1, "descriptor IRQ event did not latch");
apb_write(32'h100, 32'h0000_0004, 4'h1, 1'b0);
apb_read(32'h100, value);
if ((value & 32'h04) != 0 || irq_o) $fatal(1, "descriptor IRQ W1C failed");
error_status_i = 32'h0000_0001;
@(posedge clk_i);
error_status_i = 32'h0000_0000;
apb_read(32'h10C, value);
if ((value & 32'h01) == 0) $fatal(1, "error event did not latch");
apb_write(32'h10C, 32'h0000_0001, 4'h1, 1'b0);
apb_read(32'h10C, value);
if ((value & 32'h01) != 0) $fatal(1, "ERROR_STATUS W1C did not clear");
apb_read(32'h10C, value);
if ((value & 32'h01) != 0) $fatal(1, "ERROR_STATUS clear was not persistent");
error_status_i = 32'h0000_0001;
@(posedge clk_i);
error_status_i = 32'h0000_0000;
apb_read(32'h10C, value);
if ((value & 32'h01) == 0) $fatal(1, "new error event did not re-latch");
pio_rdata_i = 32'h1122_3344;
pio_valid_i = 1'b1;
pio_ready_i = 1'b0;
apb_read(32'h124, value);
if (value != 32'h0000_0001) $fatal(1, "DEBUG PIO valid bit mismatch: %h", value);
pio_valid_i = 1'b0;
pio_ready_i = 1'b1;
apb_read(32'h124, value);
if (value != 32'h0000_0002) $fatal(1, "DEBUG PIO ready bit mismatch: %h", value);
pio_rdata_i = 32'h1122_3344;
pio_valid_i = 1'b1;
pio_consume_count = 0;
apb_read(32'h090, value);
if ((value != 32'h1122_3344) || (pio_consume_count != 1)) begin
$fatal(1, "PIO read did not return and consume one word: %h count=%0d", value,
pio_consume_count);
end
pio_valid_i = 1'b0;
apb_read_error(32'h090);
pio_rdata_i = 32'h5566_7788;
pio_valid_i = 1'b1;
apb_read(32'h090, value);
if ((value != 32'h5566_7788) || (pio_consume_count != 2)) begin
$fatal(1, "consecutive PIO read contract failed: %h count=%0d", value, pio_consume_count);
end
$display("SDIO APB register test passed");
$finish;
end
endmodule
+342
View File
@@ -0,0 +1,342 @@
// Copyright (c) 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 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,
// INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A
// PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
module sdio_sdio_tb;
localparam logic [31:0] HostCtrl = 32'h00C;
localparam logic [31:0] ClockCtrl = 32'h010;
localparam logic [31:0] BusCtrl = 32'h018;
localparam logic [31:0] CmdArg = 32'h040;
localparam logic [31:0] CmdCfg = 32'h044;
localparam logic [31:0] CmdStart = 32'h048;
localparam logic [31:0] CmdStatus = 32'h04C;
localparam logic [31:0] Resp0 = 32'h050;
localparam logic [31:0] BlockSize = 32'h080;
localparam logic [31:0] BlockCount = 32'h084;
localparam logic [31:0] DataCfg = 32'h088;
localparam logic [31:0] DataStart = 32'h08C;
localparam logic [31:0] PioData = 32'h090;
localparam logic [31:0] IrqStatus = 32'h100;
localparam logic [31:0] IrqEnable = 32'h104;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
apb4_if apb4 (
.pclk (clk_i),
.presetn(rst_n_i)
);
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) dma_axi4 (
.aclk (clk_i),
.aresetn(rst_n_i)
);
sdio_if sdio ();
logic card_cmd_do;
logic [ 3:0] card_dat_do;
logic card_irq;
logic [31:0] apb_value;
integer timeout_guard;
assign sdio.cmd_di_i = sdio.cmd_oe_o ? sdio.cmd_do_o : card_cmd_do;
assign sdio.dat_di_i = (sdio.dat_oe_o & sdio.dat_do_o) | (~sdio.dat_oe_o & card_dat_do);
always #5 clk_i = ~clk_i;
apb4_sdio #(
.InputClockHz(72_000_000),
.AddrWidth (32),
.DataWidth (32),
.DescCount (16)
) u_apb4_sdio (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.apb4 (apb4),
.dma_axi4(dma_axi4),
.sdio (sdio)
);
sdio_sdio_model #(
.FunctionCount(2),
.FunctionBytes(4096),
.BusyCycles (8)
) u_card (
.rst_n_i (rst_n_i),
.sck_i (sdio.sck_o),
.cmd_oe_i(sdio.cmd_oe_o),
.cmd_do_i(sdio.cmd_do_o),
.cmd_do_o(card_cmd_do),
.dat_oe_i(sdio.dat_oe_o),
.dat_do_i(sdio.dat_do_o),
.dat_do_o(card_dat_do),
.irq_o (card_irq)
);
sdio_axi_memory_responder u_axi_memory (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.axi4 (dma_axi4)
);
task automatic apb_idle;
begin
apb4.psel = 1'b0;
apb4.penable = 1'b0;
apb4.pwrite = 1'b0;
apb4.paddr = '0;
apb4.pwdata = '0;
apb4.pstrb = '0;
end
endtask
task automatic issue_command_unchecked(input logic [5:0] index_i, input logic [31:0] argument_i,
input logic [3:0] response_i);
begin
apb_write(CmdArg, argument_i, 4'hF);
apb_write(CmdCfg, {18'd0, 1'b0, 1'b0, response_i, 2'd0, index_i}, 4'hF);
apb_write(CmdStart, 32'd1, 4'h1);
wait_command();
end
endtask
task automatic apb_write(input logic [31:0] address_i, input logic [31:0] data_i,
input logic [3:0] strobe_i);
begin
@(negedge clk_i);
apb4.paddr = address_i;
apb4.pwdata = data_i;
apb4.pstrb = strobe_i;
apb4.pwrite = 1'b1;
apb4.psel = 1'b1;
apb4.penable = 1'b0;
@(negedge clk_i);
apb4.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb4.pready || apb4.pslverr) begin
$fatal(1, "SDIO APB write failed addr=%h data=%h", address_i, data_i);
end
apb_idle();
end
endtask
task automatic apb_read(input logic [31:0] address_i, output logic [31:0] data_o);
begin
@(negedge clk_i);
apb4.paddr = address_i;
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;
@(posedge clk_i);
#1;
if (!apb4.pready || apb4.pslverr) begin
$fatal(1, "SDIO APB read failed addr=%h", address_i);
end
data_o = apb4.prdata;
apb_idle();
end
endtask
task automatic wait_command;
logic seen_busy;
begin
timeout_guard = 0;
seen_busy = 1'b0;
while (timeout_guard < 20000) begin
@(posedge clk_i);
timeout_guard = timeout_guard + 1;
if (u_apb4_sdio.u_sdio_core.s_cmd_busy) begin
seen_busy = 1'b1;
end
if (seen_busy && (u_apb4_sdio.u_sdio_core.u_sdio_command.s_state_q == 3'd0)) begin
break;
end
end
if (timeout_guard >= 20000) begin
$fatal(1, "SDIO command did not drain");
end
end
endtask
task automatic wait_data;
logic seen_busy;
begin
timeout_guard = 0;
seen_busy = 1'b0;
while (timeout_guard < 200000) begin
@(posedge clk_i);
timeout_guard = timeout_guard + 1;
if (u_apb4_sdio.u_sdio_core.s_data_busy || u_apb4_sdio.u_sdio_core.s_dma_busy) begin
seen_busy = 1'b1;
end
if (seen_busy && !u_apb4_sdio.u_sdio_core.s_data_busy &&
!u_apb4_sdio.u_sdio_core.s_dma_busy) begin
break;
end
end
if (timeout_guard >= 200000) begin
$fatal(1, "SDIO data path did not drain");
end
end
endtask
task automatic wait_write_ready;
begin
timeout_guard = 0;
while ((u_apb4_sdio.u_sdio_core.u_sdio_data.s_state_q != 4'd1) &&
(timeout_guard < 1000)) begin
@(posedge clk_i);
timeout_guard = timeout_guard + 1;
end
if (timeout_guard >= 1000) begin
$fatal(1, "SDIO write engine did not become ready");
end
end
endtask
task automatic issue_command(input logic [5:0] index_i, input logic [31:0] argument_i,
input logic [3:0] response_i);
logic [31:0] status_value;
begin
apb_write(CmdArg, argument_i, 4'hF);
apb_write(CmdCfg, {18'd0, 1'b1, 1'b1, response_i, 2'd0, index_i}, 4'hF);
apb_write(CmdStart, 32'd1, 4'h1);
wait_command();
apb_read(CmdStatus, status_value);
if ((status_value & 32'h0000_001E) != 0) begin
$fatal(1, "SDIO command %0d failed status=%h", index_i, status_value);
end
end
endtask
task automatic configure_data(input logic width4_i, input logic direction_i,
input logic block_mode_i, input logic fixed_address_i,
input logic [15:0] bytes_i, input logic [15:0] count_i);
begin
apb_write(BusCtrl, width4_i ? 32'd1 : 32'd0, 4'h1);
apb_write(BlockSize, {16'd0, bytes_i}, 4'hF);
apb_write(BlockCount, {16'd0, count_i}, 4'hF);
apb_write(DataCfg, {
25'd0, fixed_address_i, block_mode_i, 1'b0, 1'b0, 1'b0, width4_i, direction_i},
4'h1);
end
endtask
initial begin
apb_idle();
repeat (4) @(posedge clk_i);
rst_n_i = 1'b1;
apb_write(ClockCtrl, 32'h0000_0101, 4'hF);
apb_write(HostCtrl, 32'h0000_0005, 4'h1);
// SDIO enumeration and CCCR/FBR/CIS-backed direct accesses.
issue_command(6'd5, 32'h0000_0000, 4'd5);
repeat (8) @(posedge clk_i);
issue_command(6'd52, 32'h0000_0000, 4'd6);
apb_read(Resp0, apb_value);
if (apb_value[15:8] != 8'h03) begin
$fatal(1, "CCCR version mismatch: %h", apb_value);
end
issue_command(6'd52, {1'b0, 3'd0, 1'b0, 1'b0, 17'h00013, 1'b0, 8'h00}, 4'd6);
apb_read(Resp0, apb_value);
if (apb_value[15:8] != 8'h01) $fatal(1, "CCCR high-speed capability missing");
issue_command(6'd52, {1'b1, 3'd0, 1'b0, 1'b0, 17'h00013, 1'b0, 8'h03}, 4'd6);
issue_command(6'd52, {1'b0, 3'd0, 1'b0, 1'b0, 17'h00013, 1'b0, 8'h00}, 4'd6);
apb_read(Resp0, apb_value);
if (apb_value[15:8] != 8'h03) $fatal(1, "CCCR high-speed enable did not stick");
issue_command(6'd52, {1'b1, 3'd0, 1'b0, 1'b0, 17'h00004, 1'b0, 8'h5A}, 4'd6);
apb_read(Resp0, apb_value);
if (apb_value[15:8] != 8'h5A) begin
$fatal(1, "CCCR write response mismatch: %h", apb_value);
end
// CMD53 byte mode, incrementing address, 4-bit SDR.
issue_command_unchecked(6'd53, {1'b0, 3'd1, 1'b0, 1'b1, 17'h00020, 9'd4}, 4'd0);
configure_data(1'b1, 1'b0, 1'b0, 1'b0, 16'd4, 16'd1);
u_card.arm_read(4096 + 32, 4, 1'b1, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
wait_data();
apb_read(PioData, apb_value);
if (apb_value != 32'h0000_0000) begin
$fatal(1, "unexpected initial function memory: %h", apb_value);
end
// CMD53 block mode, fixed address, 1-bit SDR write.
issue_command_unchecked(6'd53, {1'b1, 3'd1, 1'b1, 1'b0, 17'h00030, 9'd1}, 4'd0);
configure_data(1'b0, 1'b1, 1'b1, 1'b1, 16'd4, 16'd1);
u_card.arm_write(4096 + 48, 4, 1'b0, 1'b1);
apb_write(DataStart, 32'd1, 4'h1);
wait_write_ready();
apb_write(PioData, 32'h1122_3344, 4'hF);
wait_data();
if (u_card.read_backing(4096 + 48) != 8'h11) begin
$fatal(1, "fixed-address CMD53 write did not land: %h", u_card.read_backing(4096 + 48));
end
apb_write(IrqEnable, 32'h0000_0040, 4'h1);
apb_write(IrqStatus, 32'h0000_00FF, 4'h1);
u_card.clear_irq();
repeat (4) @(posedge clk_i);
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0040) != 0 || sdio.irq_o) begin
$fatal(1, "DAT1 high level caused a false host IRQ");
end
// DAT1 is an active-low function interrupt. It is synchronized in clk_i,
// qualified only while the host releases the data path, and remains sticky
// while SDCLK is stopped.
u_card.set_irq();
repeat (4) @(posedge clk_i);
if (sdio.dat_di_i[1] != 1'b0 || !card_irq) begin
$fatal(1, "SDIO DAT1 interrupt was not asserted low");
end
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0040) == 0 || !sdio.irq_o) begin
$fatal(1, "active-low DAT1 did not latch host IRQ");
end
apb_write(IrqStatus, 32'h0000_0040, 4'h1);
repeat (8) @(posedge clk_i);
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0040) != 0 || sdio.irq_o) begin
$fatal(1, "DAT1 W1C did not clear sticky host IRQ");
end
if (sdio.sck_o != 1'b0) begin
$fatal(1, "SDCLK was not stopped for DAT1 persistence check");
end
repeat (8) @(posedge clk_i);
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0040) != 0) begin
$fatal(1, "cleared DAT1 IRQ reasserted while line remained low");
end
u_card.clear_irq();
repeat (4) @(posedge clk_i);
if (sdio.dat_di_i[1] != 1'b1) begin
$fatal(1, "DAT1 interrupt did not release");
end
u_card.set_irq();
repeat (4) @(posedge clk_i);
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0040) == 0) begin
$fatal(1, "DAT1 did not re-arm after returning high");
end
$display("SDIO-only CMD5/CMD52/CMD53/DAT1 test passed");
$finish;
end
endmodule
+677
View File
@@ -0,0 +1,677 @@
// Copyright (c) 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 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 THE WARRANTIES OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
module sdio_standalone_tb #(
parameter bit CardHighCapacity = 1'b1,
parameter bit RunFourBit = 1'b1
);
localparam logic [31:0] HostCtrl = 32'h00C;
localparam logic [31:0] ClockCtrl = 32'h010;
localparam logic [31:0] BusCtrl = 32'h018;
localparam logic [31:0] CmdArg = 32'h040;
localparam logic [31:0] CmdCfg = 32'h044;
localparam logic [31:0] CmdStart = 32'h048;
localparam logic [31:0] CmdStatus = 32'h04C;
localparam logic [31:0] Resp0 = 32'h050;
localparam logic [31:0] BlockSize = 32'h080;
localparam logic [31:0] BlockCount = 32'h084;
localparam logic [31:0] DataCfg = 32'h088;
localparam logic [31:0] DataStart = 32'h08C;
localparam logic [31:0] DataStatus = 32'h08C;
localparam logic [31:0] IrqStatus = 32'h100;
localparam logic [31:0] IrqEnable = 32'h104;
localparam logic [31:0] TimeoutData = 32'h020;
localparam logic [31:0] TimeoutBusy = 32'h024;
localparam logic [31:0] TimeoutCount = 32'h118;
localparam logic [31:0] CrcCount = 32'h114;
localparam logic [31:0] AxiCount = 32'h11C;
localparam logic [31:0] PioData = 32'h090;
localparam logic [31:0] DescBase = 32'h0C0;
localparam logic [31:0] DescCount = 32'h0C4;
localparam logic [31:0] Status = 32'h028;
localparam logic [31:0] ClockActual = 32'h014;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
apb4_if apb4 (
.pclk (clk_i),
.presetn(rst_n_i)
);
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) dma_axi4 (
.aclk (clk_i),
.aresetn(rst_n_i)
);
sdio_if sdio ();
logic card_cmd_do;
logic [ 3:0] card_dat_do;
logic card_irq;
logic [31:0] apb_value;
integer timeout_guard;
integer timeout_count_before;
integer crc_count_before;
assign sdio.cmd_di_i = sdio.cmd_oe_o ? sdio.cmd_do_o : card_cmd_do;
assign sdio.dat_di_i = (sdio.dat_oe_o & sdio.dat_do_o) | (~sdio.dat_oe_o & card_dat_do);
always #5 clk_i = ~clk_i;
apb4_sdio #(
.InputClockHz(72_000_000),
.AddrWidth (32),
.DataWidth (32),
.DescCount (16)
) u_apb4_sdio (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.apb4 (apb4),
.dma_axi4(dma_axi4),
.sdio (sdio)
);
sdio_sd_memory_model #(
.HighCapacity(CardHighCapacity),
.BlockBytes (512),
.BlockCount (128),
.BusyCycles (8)
) u_card (
.rst_n_i (rst_n_i),
.sck_i (sdio.sck_o),
.cmd_oe_i(sdio.cmd_oe_o),
.cmd_do_i(sdio.cmd_do_o),
.cmd_do_o(card_cmd_do),
.dat_oe_i(sdio.dat_oe_o),
.dat_do_i(sdio.dat_do_o),
.dat_do_o(card_dat_do),
.irq_o (card_irq)
);
sdio_axi_memory_responder #(
.DepthWords(16384)
) u_axi_memory (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.axi4 (dma_axi4)
);
task automatic apb_idle;
begin
apb4.psel = 1'b0;
apb4.penable = 1'b0;
apb4.pwrite = 1'b0;
apb4.paddr = '0;
apb4.pwdata = '0;
apb4.pstrb = '0;
end
endtask
task automatic apb_write(input logic [31:0] address_i, input logic [31:0] data_i,
input logic [3:0] strobe_i);
begin
@(negedge clk_i);
apb4.paddr = address_i;
apb4.pwdata = data_i;
apb4.pstrb = strobe_i;
apb4.pwrite = 1'b1;
apb4.psel = 1'b1;
apb4.penable = 1'b0;
@(negedge clk_i);
apb4.penable = 1'b1;
for (integer wait_count = 0; wait_count < 100000; wait_count++) begin
@(posedge clk_i);
#1;
if (apb4.pready) begin
break;
end
end
if (!apb4.pready || apb4.pslverr) begin
$fatal(1, "SDIO APB write failed addr=%h data=%h err=%b", address_i, data_i, apb4.pslverr);
end
apb_idle();
end
endtask
task automatic apb_read(input logic [31:0] address_i, output logic [31:0] data_o);
begin
@(negedge clk_i);
apb4.paddr = address_i;
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;
@(posedge clk_i);
#1;
if (!apb4.pready || apb4.pslverr) begin
$fatal(1, "SDIO APB read failed addr=%h err=%b", address_i, apb4.pslverr);
end
data_o = apb4.prdata;
apb_idle();
end
endtask
task automatic wait_command;
logic seen_busy;
begin
timeout_guard = 0;
seen_busy = 1'b0;
while (timeout_guard < 20000) begin
@(posedge clk_i);
timeout_guard = timeout_guard + 1;
if (u_apb4_sdio.u_sdio_core.s_cmd_busy) begin
seen_busy = 1'b1;
end
if (seen_busy && (u_apb4_sdio.u_sdio_core.u_sdio_command.s_state_q == 3'd0)) begin
break;
end
end
if (timeout_guard >= 20000) begin
$fatal(1, "command did not drain");
end
end
endtask
task automatic wait_data;
logic seen_busy;
begin
timeout_guard = 0;
seen_busy = 1'b0;
while (timeout_guard < 200000) begin
@(posedge clk_i);
timeout_guard = timeout_guard + 1;
if (u_apb4_sdio.u_sdio_core.s_data_busy || u_apb4_sdio.u_sdio_core.s_dma_busy) begin
seen_busy = 1'b1;
end
if (seen_busy && !u_apb4_sdio.u_sdio_core.s_data_busy &&
!u_apb4_sdio.u_sdio_core.s_dma_busy) begin
break;
end
end
if (timeout_guard >= 200000) begin
$fatal(1, "data path did not drain");
end
end
endtask
task automatic issue_command(input logic [5:0] index_i, input logic [31:0] argument_i,
input logic [3:0] response_i, input logic crc_check_i,
input logic index_check_i);
logic [31:0] status_value;
begin
apb_write(CmdArg, argument_i, 4'hF);
apb_write(CmdCfg, {18'd0, index_check_i, crc_check_i, response_i, 2'd0, index_i}, 4'hF);
apb_write(CmdStart, 32'd1, 4'h1);
wait_command();
apb_read(CmdStatus, status_value);
if ((status_value & 32'h0000_001E) != 0) begin
$fatal(1, "command %0d failed status=%h", index_i, status_value);
end
end
endtask
task automatic configure_data(input logic width4_i, input logic direction_i,
input logic [15:0] bytes_i, input logic [15:0] count_i);
begin
apb_write(BusCtrl, width4_i ? 32'd1 : 32'd0, 4'h1);
apb_write(BlockSize, {16'd0, bytes_i}, 4'hF);
apb_write(BlockCount, {16'd0, count_i}, 4'hF);
apb_write(DataCfg, {25'd0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, width4_i, direction_i}, 4'h1);
end
endtask
task automatic configure_dma_data(input logic width4_i, input logic direction_i,
input logic [15:0] bytes_i);
begin
apb_write(BusCtrl, width4_i ? 32'd1 : 32'd0, 4'h1);
apb_write(BlockSize, {16'd0, bytes_i}, 4'hF);
apb_write(BlockCount, 32'd1, 4'hF);
apb_write(DataCfg, {25'd0, 1'b0, 1'b0, 1'b1, 1'b0, 1'b0, width4_i, direction_i}, 4'h1);
apb_write(DescBase, 32'd0, 4'hF);
apb_write(DescCount, 32'd1, 4'hF);
end
endtask
task automatic read_pio(input integer address_i, input logic width4_i,
output logic [31:0] data_o);
begin
u_card.arm_read(address_i, 4, width4_i, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
wait_data();
apb_read(PioData, data_o);
end
endtask
task automatic wait_pio_valid;
begin
timeout_guard = 0;
while (!u_apb4_sdio.u_sdio_core.s_pio_valid_q && (timeout_guard < 200000)) begin
@(posedge clk_i);
timeout_guard = timeout_guard + 1;
end
if (timeout_guard >= 200000) begin
$fatal(1, "PIO read word did not become valid");
end
end
endtask
task automatic write_pio(input integer address_i, input logic width4_i,
input logic [31:0] data_i);
begin
u_card.arm_write(address_i, 4, width4_i, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
apb_write(PioData, data_i, 4'hF);
wait_data();
if (u_card.read_backing(
address_i
) != data_i[7:0] || u_card.read_backing(
address_i + 1
) != data_i[15:8] || u_card.read_backing(
address_i + 2
) != data_i[23:16] || u_card.read_backing(
address_i + 3
) != data_i[31:24]) begin
$fatal(1, "PIO write mismatch at %0d", address_i);
end
end
endtask
task automatic tail_read_test(input integer address_i, input logic width4_i,
input integer byte_count_i);
logic [31:0] word0;
logic [31:0] word1;
integer word_count;
integer expected_words;
begin
configure_data(width4_i, 1'b0, byte_count_i, 16'd1);
u_card.arm_read(address_i, byte_count_i, width4_i, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
expected_words = (byte_count_i + 3) / 4;
word_count = 0;
while (word_count < expected_words) begin
wait_pio_valid();
if (word_count == 0) begin
apb_read(PioData, word0);
end else begin
apb_read(PioData, word1);
end
word_count = word_count + 1;
end
wait_data();
for (integer byte_index = 0; byte_index < byte_count_i; byte_index++) begin
if ((byte_index < 4 ? word0[(byte_index % 4)*8+:8] :
word1[(byte_index % 4)*8+:8]) !=
u_card.read_backing(
address_i + byte_index
)) begin
$fatal(1, "tail read mismatch width4=%b bytes=%0d byte=%0d", width4_i, byte_count_i,
byte_index);
end
end
end
endtask
task automatic tail_write_test(input integer address_i, input logic width4_i,
input integer byte_count_i);
integer expected_words;
logic [31:0] word;
logic [ 3:0] strb;
integer remaining;
begin
configure_data(width4_i, 1'b1, byte_count_i, 16'd1);
u_card.arm_write(address_i, byte_count_i, width4_i, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
expected_words = (byte_count_i + 3) / 4;
for (integer word_index = 0; word_index < expected_words; word_index++) begin
word = '0;
remaining = byte_count_i - (word_index * 4);
for (integer byte_index = 0; byte_index < 4; byte_index++) begin
if (byte_index < remaining) begin
word[byte_index*8+:8] = 8'hA0 + (word_index * 4) + byte_index;
end
end
strb = (remaining >= 4) ? 4'hF : ((4'b0001 << remaining) - 1'b1);
apb_write(PioData, word, strb);
end
wait_data();
for (integer byte_index = 0; byte_index < byte_count_i; byte_index++) begin
if (u_card.read_backing(address_i + byte_index) != (8'hA0 + byte_index)) begin
$fatal(1, "tail write mismatch width4=%b bytes=%0d byte=%0d data=%h", width4_i,
byte_count_i, byte_index, u_card.read_backing(address_i + byte_index));
end
end
end
endtask
initial begin
apb_idle();
repeat (4) @(posedge clk_i);
rst_n_i = 1'b1;
u_card.fill_memory(8'h20);
u_card.set_response_delay(1);
u_card.set_data_delay(1);
// 400 kHz command flow at the documented 72 MHz integration clock.
apb_write(ClockCtrl, 32'h0000_5A01, 4'hF);
apb_write(HostCtrl, 32'h0000_0005, 4'h1);
apb_read(ClockActual, apb_value);
if (apb_value != 32'd400000) begin
$fatal(1, "400 kHz clock calculation mismatch: %0d", apb_value);
end
issue_command(6'd8, 32'h0000_01AA, 4'd8, 1'b1, 1'b1);
apb_read(Resp0, apb_value);
if (apb_value[31:8] != 24'h0001AA) begin
$fatal(1, "CMD8 response echo mismatch: %h", apb_value);
end
apb_write(TimeoutBusy, 32'd4, 4'hF);
apb_write(CmdArg, 32'd0, 4'hF);
apb_write(CmdCfg, {18'd0, 1'b1, 1'b1, 4'd2, 2'd0, 6'd12}, 4'hF);
apb_write(CmdStart, 32'd1, 4'h1);
wait_command();
apb_read(CmdStatus, apb_value);
if ((apb_value & 32'h0000_0004) == 0) begin
$fatal(1, "R1b command did not use TIMEOUT_BUSY");
end
apb_write(TimeoutBusy, 32'd1000000, 4'hF);
apb_write(IrqEnable, 32'h0000_0080, 4'h1);
u_card.set_response_delay(1000);
apb_write(CmdArg, 32'd0, 4'hF);
apb_write(CmdCfg, {18'd0, 1'b1, 1'b1, 4'd1, 2'd0, 6'd17}, 4'hF);
apb_write(CmdStart, 32'd1, 4'h1);
repeat (8) @(posedge clk_i);
apb_write(HostCtrl, 32'h0000_0007, 4'h1);
wait_command();
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0080) == 0) $fatal(1, "command abort IRQ missing");
apb_write(IrqStatus, 32'h0000_0080, 4'h1);
u_card.set_response_delay(1);
configure_data(1'b0, 1'b0, 16'd4, 16'd1);
u_card.set_data_delay(1000);
u_card.arm_read(3000, 4, 1'b0, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
repeat (8) @(posedge clk_i);
apb_write(HostCtrl, 32'h0000_0007, 4'h1);
wait_data();
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0080) == 0) $fatal(1, "data abort IRQ missing");
apb_write(IrqStatus, 32'h0000_0080, 4'h1);
u_card.set_data_delay(1);
u_axi_memory.clear_memory();
u_axi_memory.write_word(32'h0000_0000, 32'h0000_0200);
u_axi_memory.write_word(32'h0000_0004, 32'd4);
u_axi_memory.write_word(32'h0000_0008, 32'd0);
u_axi_memory.write_word(32'h0000_000C, 32'h0000_0005);
u_axi_memory.set_backpressure(0, 3, 0, 0);
configure_dma_data(1'b0, 1'b0, 16'd4);
u_card.arm_read(3004, 4, 1'b0, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
wait (u_apb4_sdio.u_sdio_core.u_sdio_dma.s_state_q == 4'd2);
apb_write(HostCtrl, 32'h0000_0007, 4'h1);
wait_data();
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0080) == 0 || u_axi_memory.read_word(
32'h0000_000C
) != 32'h0002_0004) begin
$fatal(1, "descriptor-fetch host abort did not drain/write back");
end
apb_write(IrqStatus, 32'h0000_0080, 4'h1);
u_axi_memory.set_backpressure(0, 0, 0, 0);
apb_write(TimeoutData, 32'd4, 4'hF);
apb_read(TimeoutCount, apb_value);
timeout_count_before = apb_value;
u_card.set_data_delay(1000);
u_card.arm_read(3200, 4, 1'b0, 1'b0);
configure_data(1'b0, 1'b0, 16'd4, 16'd1);
apb_write(DataStart, 32'd1, 4'h1);
wait_data();
apb_read(DataStatus, apb_value);
if ((apb_value & 32'h0000_0004) == 0) $fatal(1, "data timeout phase not reported");
apb_read(TimeoutCount, apb_value);
if (apb_value != timeout_count_before + 1) begin
$fatal(1, "timeout counter incremented incorrectly: before=%0d after=%0d",
timeout_count_before, apb_value);
end
apb_write(TimeoutData, 32'd1000000, 4'hF);
u_card.set_data_delay(1);
// SDSC and SDHC use the same native command path but different address
// interpretation. The parameterized wrapper is run once for each card
// geometry by the pytest entry points.
issue_command(
6'd17, CardHighCapacity ? (RunFourBit ? 32'd4 : 32'd2) : (RunFourBit ? 32'd2048 : 32'd1024),
4'd1, 1'b1, 1'b1);
issue_command(6'd12, 32'd0, 4'd2, 1'b1, 1'b1);
configure_data(RunFourBit, 1'b0, 16'd4, 16'd1);
read_pio(RunFourBit ? 2048 : 1024, RunFourBit, apb_value);
if (apb_value != 32'h2322_2120) begin
$fatal(1, "4-bit read mismatch: %h", apb_value);
end
// Each APB PIO read consumes one held word. The second word must not
// repeat the first word or require a reset between reads.
configure_data(RunFourBit, 1'b0, 16'd8, 16'd1);
u_card.arm_read(RunFourBit ? 2052 : 1028, 8, RunFourBit, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
wait_pio_valid();
apb_read(PioData, apb_value);
if (apb_value != 32'h2726_2524) begin
$fatal(1, "first back-to-back PIO word mismatch: %h", apb_value);
end
wait_pio_valid();
apb_read(PioData, apb_value);
if (apb_value != 32'h2B2A_2928) begin
$fatal(1, "second back-to-back PIO word mismatch: %h", apb_value);
end
wait_data();
configure_data(1'b0, 1'b1, 16'd4, 16'd1);
write_pio(3072, 1'b0, 32'hA1B2_C3D4);
configure_data(1'b1, 1'b1, 16'd4, 16'd1);
write_pio(4096, 1'b1, 32'h1020_3040);
// A complete PIO block uses a real ready/consume FIFO. The APB writes
// below are intentionally consecutive; PREADY supplies the required
// wait-states while the serializer drains the queue.
apb_write(ClockCtrl, 32'h0000_0101, 4'hF);
// End-to-end DMA direction checks use the same card model and AXI
// responder as the PIO path.
u_axi_memory.clear_memory();
u_axi_memory.write_word(32'h0000_0000, 32'h0000_0200);
u_axi_memory.write_word(32'h0000_0004, 32'd5);
u_axi_memory.write_word(32'h0000_0008, 32'd0);
u_axi_memory.write_word(32'h0000_000C, 32'h0000_000D);
configure_dma_data(1'b1, 1'b0, 16'd5);
u_card.arm_read(20000, 5, 1'b1, 1'b0);
for (integer byte_index = 0; byte_index < 5; byte_index++) begin
u_card.write_backing(20000 + byte_index, 8'h60 + byte_index);
end
apb_write(DataStart, 32'd1, 4'h1);
wait_data();
apb_write(IrqEnable, 32'h0000_0084, 4'h1);
repeat (2) @(posedge clk_i);
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0004) == 0) begin
$fatal(1, "descriptor IRQ was not visible through the core");
end
apb_write(IrqStatus, 32'h0000_0004, 4'h1);
if (u_axi_memory.read_word(
32'h0000_0200
) != 32'h6362_6160 || (u_axi_memory.read_word(
32'h0000_0204
) & 32'hFF) != 32'h64 || u_axi_memory.read_word(
32'h0000_000C
) != 32'h0001_000C) begin
$fatal(1, "card-to-memory DMA end-to-end direction failed");
end
u_axi_memory.clear_memory();
u_axi_memory.write_word(32'h0000_0000, 32'h0000_0300);
u_axi_memory.write_word(32'h0000_0004, 32'd5);
u_axi_memory.write_word(32'h0000_0008, 32'd0);
u_axi_memory.write_word(32'h0000_000C, 32'h0000_0005);
u_axi_memory.write_word(32'h0000_0300, 32'hD4C3_B2A1);
u_axi_memory.write_word(32'h0000_0304, 32'h0000_00E5);
configure_dma_data(1'b0, 1'b1, 16'd5);
u_card.arm_write(21000, 5, 1'b0, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
wait_data();
if (u_card.read_backing(
21000
) != 8'hA1 || u_card.read_backing(
21001
) != 8'hB2 || u_card.read_backing(
21002
) != 8'hC3 || u_card.read_backing(
21003
) != 8'hD4 || u_card.read_backing(
21004
) != 8'hE5 || u_axi_memory.read_word(
32'h0000_000C
) != 32'h0001_0004) begin
$fatal(1, "memory-to-card DMA end-to-end direction failed");
end
apb_read(AxiCount, apb_value);
timeout_count_before = apb_value;
u_axi_memory.clear_memory();
u_axi_memory.write_word(32'h0000_0000, 32'h0000_0400);
u_axi_memory.write_word(32'h0000_0004, 32'd4);
u_axi_memory.write_word(32'h0000_0008, 32'd0);
u_axi_memory.write_word(32'h0000_000C, 32'h0000_0005);
u_axi_memory.write_word(32'h0000_0400, 32'h1122_3344);
u_axi_memory.inject_read_error(32'h0000_0400);
configure_dma_data(1'b0, 1'b1, 16'd4);
u_card.arm_write(22000, 4, 1'b0, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
wait_data();
apb_read(AxiCount, apb_value);
if (apb_value != timeout_count_before + 1 || u_axi_memory.read_word(
32'h0000_000C
) != 32'h0002_0004) begin
$fatal(1, "AXI error counter/writeback contract failed: before=%0d after=%0d",
timeout_count_before, apb_value);
end
u_axi_memory.clear_errors();
apb_write(ClockCtrl, 32'h0000_0100, 4'hF);
repeat (4) @(posedge clk_i);
apb_write(ClockCtrl, 32'h0000_0101, 4'hF);
configure_data(1'b0, 1'b1, 16'd512, 16'd1);
u_card.arm_write(8192, 512, 1'b0, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
for (integer word_index = 0; word_index < 128; word_index++) begin
logic [31:0] word;
word = '0;
for (integer byte_index = 0; byte_index < 4; byte_index++) begin
word[byte_index*8+:8] = (word_index * 4) + byte_index;
end
apb_write(PioData, word, 4'hF);
end
wait_data();
for (integer byte_index = 0; byte_index < 512; byte_index++) begin
if (u_card.read_backing(8192 + byte_index) != byte_index[7:0]) begin
$fatal(1, "512-byte PIO write mismatch at %0d: %h", byte_index, u_card.read_backing(
8192 + byte_index));
end
end
configure_data(1'b0, 1'b0, 16'd512, 16'd1);
u_card.arm_read(8192, 512, 1'b0, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
for (integer word_index = 0; word_index < 128; word_index++) begin
wait_pio_valid();
apb_read(PioData, apb_value);
for (integer byte_index = 0; byte_index < 4; byte_index++) begin
if (apb_value[byte_index*8+:8] != ((word_index * 4) + byte_index) % 256) begin
$fatal(1, "512-byte PIO read mismatch word=%0d byte=%0d: %h", word_index, byte_index,
apb_value);
end
end
end
wait_data();
for (integer tail_bytes = 1; tail_bytes <= 5; tail_bytes++) begin
if (tail_bytes != 4) begin
tail_read_test(12000 + (tail_bytes * 16), 1'b0, tail_bytes);
tail_write_test(13000 + (tail_bytes * 16), 1'b0, tail_bytes);
tail_read_test(14000 + (tail_bytes * 16), 1'b1, tail_bytes);
tail_write_test(15000 + (tail_bytes * 16), 1'b1, tail_bytes);
end
end
apb_write(TimeoutBusy, 32'd4, 4'hF);
configure_data(1'b0, 1'b1, 16'd4, 16'd1);
u_card.arm_write(15996, 4, 1'b0, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
apb_write(PioData, 32'h4433_2211, 4'hF);
wait_data();
apb_read(DataStatus, apb_value);
if ((apb_value & 32'h0000_0010) == 0) begin
$fatal(1, "DAT0 busy timeout did not use TIMEOUT_BUSY");
end
apb_write(TimeoutBusy, 32'd1000000, 4'hF);
configure_data(1'b0, 1'b1, 16'd4, 16'd1);
u_card.inject_next_crc_error();
u_card.arm_write(16000, 4, 1'b0, 1'b0);
apb_read(CrcCount, apb_value);
crc_count_before = apb_value;
apb_write(DataStart, 32'd1, 4'h1);
apb_write(PioData, 32'h4433_2211, 4'hF);
wait_data();
apb_read(DataStatus, apb_value);
if ((apb_value & 32'h0000_0008) == 0) begin
$fatal(1, "CRC-error write response token was not reported: status=%h dataerr=%b crc=%b",
apb_value, u_apb4_sdio.u_sdio_core.s_data_err, u_apb4_sdio.u_sdio_core.s_data_crc_err);
end
apb_read(CrcCount, apb_value);
if (apb_value != crc_count_before + 1) begin
$fatal(1, "CRC counter incremented incorrectly: before=%0d after=%0d", crc_count_before,
apb_value);
end
configure_data(1'b0, 1'b1, 16'd4, 16'd1);
u_card.inject_next_write_error();
u_card.arm_write(16004, 4, 1'b0, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
apb_write(PioData, 32'h8877_6655, 4'hF);
wait_data();
apb_read(DataStatus, apb_value);
if (((apb_value & 32'h0000_0002) == 0) || ((apb_value & 32'h0000_0008) != 0)) begin
$fatal(1, "write-error response token was not distinguished: %h", apb_value);
end
$display("SDIO standalone native SD model test passed (%s)",
CardHighCapacity ? "SDHC" : "SDSC");
$finish;
end
endmodule
module sdio_sdsc_tb;
sdio_standalone_tb #(.CardHighCapacity(1'b0)) u_tb ();
endmodule
module sdio_onebit_tb;
sdio_standalone_tb #(.RunFourBit(1'b0)) u_tb ();
endmodule
+74
View File
@@ -0,0 +1,74 @@
`timescale 1ns / 1ps
`include "axi4_define.svh"
module sdio_wrapper_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
apb4_if apb4 (
.pclk (clk_i),
.presetn(rst_n_i)
);
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) dma_axi4 (
.aclk (clk_i),
.aresetn(rst_n_i)
);
sdio_if sdio ();
always #5 clk_i = ~clk_i;
assign dma_axi4.awready = 1'b1;
assign dma_axi4.wready = 1'b1;
assign dma_axi4.bid = '0;
assign dma_axi4.bresp = `AXI4_RESP_OKAY;
assign dma_axi4.buser = '0;
assign dma_axi4.bvalid = 1'b0;
assign dma_axi4.arready = 1'b1;
assign dma_axi4.rid = '0;
assign dma_axi4.rdata = '0;
assign dma_axi4.rresp = `AXI4_RESP_OKAY;
assign dma_axi4.rlast = 1'b1;
assign dma_axi4.ruser = '0;
assign dma_axi4.rvalid = 1'b0;
apb4_sdio #(
.InputClockHz(72_000_000),
.AddrWidth (32),
.DataWidth (32),
.DescCount (16)
) u_apb4_sdio (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.apb4 (apb4),
.dma_axi4(dma_axi4),
.sdio (sdio)
);
initial begin
apb4.paddr = '0;
apb4.pprot = '0;
apb4.psel = 1'b0;
apb4.penable = 1'b0;
apb4.pwrite = 1'b0;
apb4.pwdata = '0;
apb4.pstrb = '0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
@(negedge clk_i);
apb4.paddr = 32'h0000_0000;
apb4.psel = 1'b1;
@(negedge clk_i);
apb4.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb4.pready || apb4.pslverr || (apb4.prdata != 32'h5344_494F)) begin
$fatal(1, "wrapper APB identification failed");
end
$display("SDIO wrapper elaboration and APB smoke test passed");
$finish;
end
endmodule
+5 -2
View File
@@ -36,6 +36,8 @@ module soc_irq_topology_tb;
case (bit_index)
13: expect_irq(32'd1 << 15);
14: expect_irq(32'd1 << 20);
16: expect_irq(32'd1 << 10);
17: expect_irq(32'd1 << 21);
default: expect_irq(32'd1 << (bit_index + 7));
endcase
end
@@ -56,8 +58,9 @@ module soc_irq_topology_tb;
s_apb4_system_irq = '0;
expect_irq('0);
if (s_irq[10] !== 1'b0) begin
$fatal(1, "removed core IRQ bit is not low: IRQ10=%b", s_irq[10]);
if ((s_irq[10] !== 1'b0) || (s_irq[21] !== 1'b0)) begin
$fatal(1, "SDIO core IRQ bits are not low when sources are low: IRQ10=%b IRQ21=%b",
s_irq[10], s_irq[21]);
end
if ((s_irq[31:21] !== '0) || (s_irq[19:18] !== '0)) begin
$fatal(1, "unallocated core IRQ bits are not low: %h", s_irq);
+21 -11
View File
@@ -84,6 +84,8 @@ module sysctrl_tb;
sysctrl.perf_mgmt_wait_i = 64'd11;
sysctrl.perf_user_wait_i = 64'd12;
sysctrl.perf_dma_wait_i = 64'd13;
sysctrl.perf_sdio0_wait_i = 64'd131;
sysctrl.perf_sdio1_wait_i = 64'd141;
sysctrl.perf_apb4_periph_wait_i = 64'd14;
sysctrl.perf_apb4_system_wait_i = 64'd15;
sysctrl.perf_sdram_wait_i = 64'd16;
@@ -102,32 +104,36 @@ module sysctrl_tb;
rst_n_i = 1'b1;
@(negedge clk_i);
fault_valid_i = 1'b1;
fault_addr_i = 32'h1000_F000;
fault_wstrb_i = 4'hF;
fault_reserved_i = 1'b1;
sysctrl.fault_master_i = 3'd3;
fault_valid_i = 1'b1;
fault_addr_i = 32'h1001_2000;
fault_wstrb_i = 4'hF;
fault_reserved_i = 1'b1;
@(negedge clk_i);
fault_valid_i = 1'b0;
read_register(32'h1000_B010, read_data);
if (read_data !== 32'h0000_000B) $fatal(1, "fault status was not recorded");
read_register(32'h1000_B014, read_data);
if (read_data !== 32'h1000_F000) $fatal(1, "fault address was not recorded");
if (read_data !== 32'h1001_2000) $fatal(1, "fault address was not recorded");
read_register(32'h1000_B018, read_data);
if (read_data !== 32'h0000_0001) $fatal(1, "fault count was not incremented");
read_register(32'h1000_B028, read_data);
if (read_data !== 32'h0000_0003) $fatal(1, "three-bit fault master was not recorded");
read_register(32'h1000_B02C, read_data);
if (read_data !== `RIB_RESP_RESERVED) $fatal(1, "fault detail was not recorded");
@(negedge clk_i);
sysctrl.fault_code_i = `RIB_RESP_DECERR;
fault_valid_i = 1'b1;
fault_addr_i = 32'hA000_0000;
fault_wstrb_i = 4'h0;
fault_reserved_i = 1'b0;
sysctrl.fault_master_i = 3'd4;
sysctrl.fault_code_i = `RIB_RESP_DECERR;
fault_valid_i = 1'b1;
fault_addr_i = 32'hA000_0000;
fault_wstrb_i = 4'h0;
fault_reserved_i = 1'b0;
@(negedge clk_i);
fault_valid_i = 1'b0;
read_register(32'h1000_B014, read_data);
if (read_data !== 32'h1000_F000) $fatal(1, "later fault overwrote first fault address");
if (read_data !== 32'h1001_2000) $fatal(1, "later fault overwrote first fault address");
read_register(32'h1000_B018, read_data);
if (read_data !== 32'h0000_0002) $fatal(1, "later fault did not increment count");
read_register(32'h1000_B02C, read_data);
@@ -140,6 +146,10 @@ module sysctrl_tb;
write_register(32'h1000_B040, 32'h0000_0005);
read_register(32'h1000_B044, read_data);
if (read_data !== 32'd11) $fatal(1, "performance snapshot was not recorded");
read_register(32'h1000_B08C, read_data);
if (read_data !== 32'd131) $fatal(1, "SDIO0 performance snapshot was not recorded");
read_register(32'h1000_B094, read_data);
if (read_data !== 32'd141) $fatal(1, "SDIO1 performance snapshot was not recorded");
read_register(32'h1000_B020, read_data);
if (read_data !== 32'h0000_003F) $fatal(1, "user cores were not held in reset");
+12
View File
@@ -63,6 +63,8 @@ def test_generated_artifacts_share_the_capacity_baseline(tmp_path: Path) -> None
assert "#ifdef __ASSEMBLER__" in header
assert "#define UINT32_C(value) value" in header
assert "RS_SOC_APB4_SDIO_BASE" not in header
assert "RS_SOC_APB4_SDIO0_BASE UINT32_C(0x1000F000)" in header
assert "RS_SOC_APB4_SDIO1_BASE UINT32_C(0x10015000)" in header
assert "RS_SOC_NMI_" not in header
assert "RS_SOC_OPIPSRAM_BASE UINT32_C(0x48000000)" in header
assert "RS_SOC_HAS_SRAM 1U" in header
@@ -70,6 +72,10 @@ def test_generated_artifacts_share_the_capacity_baseline(tmp_path: Path) -> None
assert "SOC_SYSCTRL_PLL_STATUS_OFFSET 32'h0000001C" in rtl
assert "SOC_SYSCTRL_TEST_STATUS_OFFSET 32'h00000084" in rtl
assert "SOC_SYSCTRL_RTC_WAKE_STATUS_OFFSET 32'h00000088" in rtl
assert "SOC_SYSCTRL_PERF_SDIO0_WAIT_LO_OFFSET 32'h0000008C" in rtl
assert "SOC_SYSCTRL_PERF_SDIO0_WAIT_HI_OFFSET 32'h00000090" in rtl
assert "SOC_SYSCTRL_PERF_SDIO1_WAIT_LO_OFFSET 32'h00000094" in rtl
assert "SOC_SYSCTRL_PERF_SDIO1_WAIT_HI_OFFSET 32'h00000098" in rtl
assert "SOC_ADDR_IS_APB4_PERIPH(addr)" in rtl
assert "SOC_ADDR_IS_NMI" not in rtl
user_policy = rtl.split("`define SOC_USER_ADDR_READABLE", 1)[1].split(
@@ -82,6 +88,10 @@ def test_generated_artifacts_share_the_capacity_baseline(tmp_path: Path) -> None
assert "RS_SOC_SYSCTRL_PLL_STATUS_OFFSET UINT32_C(0x0000001C)" in header
assert "RS_SOC_SYSCTRL_TEST_STATUS_OFFSET UINT32_C(0x00000084)" in header
assert "RS_SOC_SYSCTRL_RTC_WAKE_STATUS_OFFSET UINT32_C(0x00000088)" in header
assert "RS_SOC_SYSCTRL_PERF_SDIO0_WAIT_LO_OFFSET UINT32_C(0x0000008C)" in header
assert "RS_SOC_SYSCTRL_PERF_SDIO0_WAIT_HI_OFFSET UINT32_C(0x00000090)" in header
assert "RS_SOC_SYSCTRL_PERF_SDIO1_WAIT_LO_OFFSET UINT32_C(0x00000094)" in header
assert "RS_SOC_SYSCTRL_PERF_SDIO1_WAIT_HI_OFFSET UINT32_C(0x00000098)" in header
assert "PSRAM (wxa!ri) : ORIGIN = 0x40000000, LENGTH = 0x02000000" in linker
assert "OPIPSRAM (wxa!ri) : ORIGIN = 0x48000000, LENGTH = 0x08000000" in linker
@@ -112,6 +122,8 @@ def test_user_ip_is_always_emitted_for_the_fixed_platform(tmp_path: Path) -> Non
assert "`define SOC_ADDR_APB4_OPIPSRAM_BASE 32'h10010000" in rtl
assert "`define SOC_ADDR_IS_OPIPSRAM(addr)" in rtl
assert "#define RS_SOC_APB4_OPIPSRAM_BASE UINT32_C(0x10010000)" in header
assert "`define SOC_ADDR_APB4_SDIO0_BASE 32'h1000F000" in rtl
assert "`define SOC_ADDR_APB4_SDIO1_BASE 32'h10015000" in rtl
def test_bootstrap_assembly_uses_the_generated_gpio_admin_base() -> None:
+29
View File
@@ -54,10 +54,19 @@ def test_pin_map_generates_asic_and_platform_bindings(tmp_path: Path) -> None:
assert "inout jtag_tck_i_pad" in ports
assert "inout jtag_trst_n_i_pad" in ports
assert "output jtag_tdo_o_pad" in ports
assert "output sdio1_clk_o_pad," in ports
assert "inout sdio1_cmd_io_pad," in ports
assert "inout sdio1_dat3_io_pad," in ports
assert "HAVE_DEBUG" not in ports
assert "HAVE_DEBUG" not in pads
assert "u_gpio_31_io_pad" in pads
assert "u_sdram_dq15_io_pad" in pads
assert "u_sdio1_clk_o_pad" in pads
assert "tc_io_out_pad u_sdio1_clk_o_pad" in pads
assert "u_sdio1_cmd_io_pad" in pads
assert ".c2p(u_sdio1_if.cmd_do_o)" in pads
assert ".c2p_en(u_sdio1_if.cmd_oe_o)" in pads
assert ".p2c(u_sdio1_if.cmd_di_i)" in pads
assert ".gpio_30_io_pad(gpio_io30)" in fpga
assert ".gpio_15_io_pad()" in fpga
assert ".gpio_24_io_pad(s_psram_dat1)" in testbench
@@ -66,6 +75,14 @@ def test_pin_map_generates_asic_and_platform_bindings(tmp_path: Path) -> None:
assert ".sdram_dq15_io_pad(s_sdram_dq[15])" in verilator
assert ".jtag_tck_i_pad(s_jtag_tck)" in verilator
assert ".jtag_tdo_o_pad(s_jtag_tdo)" in verilator
assert ".sdio1_clk_o_pad(sdio1_clk_o)" in verilator
assert ".sdio1_cmd_io_pad(sdio1_cmd_io)" in verilator
assert ".sdio1_dat3_io_pad(sdio1_dat3_io)" in verilator
assert ".sdio1_clk_o_pad(s_sdio1_clk)" in testbench
assert ".sdio1_cmd_io_pad(s_sdio1_cmd)" in testbench
assert ".sdio1_dat0_io_pad(s_sdio1_dat[0])" in testbench
assert ".sdio1_clk_o_pad()" in fpga
assert ".sdio1_cmd_io_pad()" in fpga
assert ".jtag_tck_i_pad(s_jtag_tck)" in testbench
assert "user_gpio_" not in ports
assert "user_gpio_" not in pads
@@ -108,6 +125,18 @@ def test_pin_map_rejects_unknown_profile_pad(tmp_path: Path) -> None:
assert "unknown pad" in result.stderr
def test_pin_map_requires_explicit_bidirectional_peripheral_signals(tmp_path: Path) -> None:
document = json.loads(PIN_MAP.read_text(encoding="utf-8"))
document["pads"][8]["ports"][0].pop("output_enable")
invalid_map = tmp_path / "missing-bidir-signal.json"
invalid_map.write_text(json.dumps(document), encoding="utf-8")
result = validate(invalid_map)
assert result.returncode != 0
assert "requires input, output, and output_enable" in result.stderr
def test_fpga_constraints_reference_declared_top_ports() -> None:
top_ports = set(
re.findall(
+322
View File
@@ -0,0 +1,322 @@
"""Focused standalone SDIO RTL regressions."""
from __future__ import annotations
import shutil
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
CONVERTER = ROOT / "rtl/mini/script/convt_sv2v.py"
BUILD = ROOT / "build/test-sdio"
SDIO_FULL_SOURCES = [
"rtl/managed/clusterip/common/rtl/interface/apb4_if.sv",
"rtl/managed/clusterip/common/rtl/interface/axi4_if.sv",
"rtl/ip/storage/sdio_if.sv",
"rtl/ip/storage/sdio_pkg.sv",
"rtl/ip/storage/sdio_define.svh",
"rtl/ip/storage/sdio_crc7.sv",
"rtl/ip/storage/sdio_crc16.sv",
"rtl/ip/storage/sdio_clock.sv",
"rtl/ip/storage/sdio_command.sv",
"rtl/ip/storage/sdio_data.sv",
"rtl/ip/storage/sdio_dma_descriptor.sv",
"rtl/ip/peripheral/dma_axi4_master.sv",
"rtl/ip/storage/sdio_dma.sv",
"rtl/ip/storage/sdio_reg.sv",
"rtl/ip/storage/sdio_core.sv",
"rtl/ip/storage/apb4_sdio.sv",
"tests/rtl/sdio_native_model.sv",
"tests/rtl/sdio_axi_memory_responder.sv",
]
SDIO_FULL_INCDIRS = [
"rtl/managed/clusterip/common/rtl/interface",
"rtl/ip/peripheral",
"rtl/ip/storage",
]
def _run_tb(name: str, top: str, sources: list[str], incdirs: list[str], marker: str) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
BUILD.mkdir(parents=True, exist_ok=True)
filelist = BUILD / f"{name}.fl"
filelist.write_text(
"\n".join([*(f"+incdir+{ROOT / path}" for path in incdirs), *(str(ROOT / path) for path in sources)])
+ "\n",
encoding="utf-8",
)
converted = BUILD / f"{name}.v"
simulation = BUILD / f"{name}.vvp"
subprocess.run(
["python3", str(CONVERTER), "-f", str(filelist), "--output", str(converted)],
check=True,
)
subprocess.run([iverilog, "-g2012", "-s", top, "-o", str(simulation), str(converted)], check=True)
result = subprocess.run([vvp, str(simulation)], check=True, text=True, capture_output=True)
assert marker in result.stdout
def _run_verilator_tb(
name: str, top: str, sources: list[str], incdirs: list[str], marker: str
) -> None:
verilator = shutil.which("verilator")
if verilator is None:
return
build_dir = BUILD / f"{name}-verilator"
obj_dir = build_dir / "obj"
obj_dir.mkdir(parents=True, exist_ok=True)
source_paths = [str(ROOT / path) for path in sources]
include_args = [f"-I{ROOT / path}" for path in incdirs]
subprocess.run(
[
verilator,
"--binary",
"--timing",
"--top-module",
top,
"-Mdir",
str(obj_dir),
"-o",
"simv",
*include_args,
*source_paths,
],
check=True,
)
result = subprocess.run(
[str(obj_dir / "simv")], check=True, text=True, capture_output=True
)
assert marker in result.stdout
def test_sdio_crc_vectors() -> None:
_run_tb(
"crc",
"sdio_crc_tb",
[
"rtl/ip/storage/sdio_pkg.sv",
"rtl/ip/storage/sdio_crc7.sv",
"rtl/ip/storage/sdio_crc16.sv",
"tests/rtl/sdio_crc_tb.sv",
],
["rtl/ip/storage"],
"SDIO CRC vectors passed",
)
def test_sdio_phase_clock() -> None:
_run_tb(
"clock",
"sdio_clock_tb",
["rtl/ip/storage/sdio_clock.sv", "tests/rtl/sdio_clock_tb.sv"],
[],
"SDIO phase clock test passed",
)
def test_sdio_command_engine() -> None:
_run_tb(
"command",
"sdio_command_tb",
[
"rtl/ip/storage/sdio_pkg.sv",
"rtl/ip/storage/sdio_clock.sv",
"rtl/ip/storage/sdio_command.sv",
"tests/rtl/sdio_command_tb.sv",
],
["rtl/ip/storage"],
"SDIO command engine test passed",
)
def test_sdio_registers() -> None:
_run_tb(
"register",
"sdio_register_tb",
[
"rtl/managed/clusterip/common/rtl/interface/apb4_if.sv",
"rtl/ip/storage/sdio_pkg.sv",
"rtl/ip/storage/sdio_define.svh",
"rtl/ip/storage/sdio_reg.sv",
"tests/rtl/sdio_register_tb.sv",
],
["rtl/managed/clusterip/common/rtl/interface", "rtl/ip/storage"],
"SDIO APB register test passed",
)
def test_sdio_descriptors() -> None:
_run_tb(
"descriptor",
"sdio_descriptor_tb",
[
"rtl/ip/storage/sdio_pkg.sv",
"rtl/ip/storage/sdio_dma_descriptor.sv",
"tests/rtl/sdio_descriptor_tb.sv",
],
["rtl/ip/storage"],
"SDIO descriptor validation test passed",
)
def test_sdio_dma() -> None:
_run_tb(
"dma",
"sdio_dma_tb",
[
"rtl/managed/clusterip/common/rtl/interface/axi4_if.sv",
"rtl/ip/peripheral/dma_axi4_master.sv",
"rtl/ip/storage/sdio_pkg.sv",
"rtl/ip/storage/sdio_dma.sv",
"tests/rtl/sdio_dma_tb.sv",
],
[
"rtl/managed/clusterip/common/rtl/interface",
"rtl/ip/peripheral",
"rtl/ip/storage",
],
"SDIO DMA descriptor, tail, and 4 KiB split test passed",
)
def test_sdio_wrapper() -> None:
_run_tb(
"wrapper",
"sdio_wrapper_tb",
[
"rtl/managed/clusterip/common/rtl/interface/apb4_if.sv",
"rtl/managed/clusterip/common/rtl/interface/axi4_if.sv",
"rtl/ip/storage/sdio_if.sv",
"rtl/ip/storage/sdio_pkg.sv",
"rtl/ip/storage/sdio_define.svh",
"rtl/ip/storage/sdio_crc7.sv",
"rtl/ip/storage/sdio_crc16.sv",
"rtl/ip/storage/sdio_clock.sv",
"rtl/ip/storage/sdio_command.sv",
"rtl/ip/storage/sdio_data.sv",
"rtl/ip/storage/sdio_dma_descriptor.sv",
"rtl/ip/peripheral/dma_axi4_master.sv",
"rtl/ip/storage/sdio_dma.sv",
"rtl/ip/storage/sdio_reg.sv",
"rtl/ip/storage/sdio_core.sv",
"rtl/ip/storage/apb4_sdio.sv",
"tests/rtl/sdio_wrapper_tb.sv",
],
[
"rtl/managed/clusterip/common/rtl/interface",
"rtl/ip/peripheral",
"rtl/ip/storage",
],
"SDIO wrapper elaboration and APB smoke test passed",
)
def test_sdio_standalone_sdhc() -> None:
_run_tb(
"standalone-sdhc",
"sdio_standalone_tb",
[*SDIO_FULL_SOURCES, "tests/rtl/sdio_standalone_tb.sv"],
SDIO_FULL_INCDIRS,
"SDIO standalone native SD model test passed (SDHC)",
)
def test_sdio_standalone_sdsc() -> None:
_run_tb(
"standalone-sdsc",
"sdio_sdsc_tb",
[*SDIO_FULL_SOURCES, "tests/rtl/sdio_standalone_tb.sv"],
SDIO_FULL_INCDIRS,
"SDIO standalone native SD model test passed (SDSC)",
)
def test_sdio_standalone_onebit() -> None:
_run_tb(
"standalone-onebit",
"sdio_onebit_tb",
[*SDIO_FULL_SOURCES, "tests/rtl/sdio_standalone_tb.sv"],
SDIO_FULL_INCDIRS,
"SDIO standalone native SD model test passed (SDHC)",
)
def test_sdio_only_device() -> None:
_run_tb(
"sdio-only",
"sdio_sdio_tb",
[*SDIO_FULL_SOURCES, "tests/rtl/sdio_sdio_tb.sv"],
SDIO_FULL_INCDIRS,
"SDIO-only CMD5/CMD52/CMD53/DAT1 test passed",
)
def test_sdio_dual_instance() -> None:
_run_tb(
"dual",
"sdio_dual_tb",
[*SDIO_FULL_SOURCES, "tests/rtl/sdio_dual_tb.sv"],
SDIO_FULL_INCDIRS,
"SDIO dual-instance isolation and concurrent traffic test passed",
)
def test_sdio_dma_stress_contract() -> None:
_run_tb(
"dma-stress",
"sdio_dma_stress_tb",
[
"rtl/managed/clusterip/common/rtl/interface/axi4_if.sv",
"rtl/ip/peripheral/dma_axi4_master.sv",
"rtl/ip/storage/sdio_pkg.sv",
"rtl/ip/storage/sdio_dma.sv",
"tests/rtl/sdio_axi_memory_responder.sv",
"tests/rtl/sdio_dma_stress_tb.sv",
],
["rtl/managed/clusterip/common/rtl/interface", "rtl/ip/peripheral", "rtl/ip/storage"],
"SDIO AXI 16-beat, 4 KiB split, error, and abort test passed",
)
def test_sdio_50mhz_clock() -> None:
_run_tb(
"50mhz",
"sdio_50mhz_tb",
["rtl/ip/storage/sdio_clock.sv", "tests/rtl/sdio_50mhz_tb.sv"],
[],
"SDIO 100 MHz to 50 MHz SDCLK test passed",
)
def test_sdio_50mhz_clock_verilator() -> None:
_run_verilator_tb(
"50mhz",
"sdio_50mhz_tb",
["rtl/ip/storage/sdio_clock.sv", "tests/rtl/sdio_50mhz_tb.sv"],
[],
"SDIO 100 MHz to 50 MHz SDCLK test passed",
)
def test_sdio_sdk_high_speed_enable_sequence() -> None:
"""Keep the SDK high-speed capability/enable/verify/clock order explicit."""
source = (ROOT / "crt/src/hal/sdio.c").read_text(encoding="utf-8")
start = source.index("rs_sdio_function_initialize")
end = source.index("rs_sdio_function_enable", start)
initialize = source[start:end]
capability_read = initialize.index('rs_sdio_function_cmd52_read(instance, 0U, 0x13U')
enable_write = initialize.index("rs_sdio_function_cmd52_write", capability_read)
verify_read = initialize.index('rs_sdio_function_cmd52_read(instance, 0U, 0x13U', enable_write)
clock_change = initialize.index("rs_sdio_clock_set", verify_read)
assert capability_read < enable_write < verify_read < clock_change
assert "value | UINT8_C(0x02)" in initialize
assert "value & UINT8_C(0x02)" in initialize
high_speed_info = initialize.index("info->high_speed = high_speed_enabled")
assert clock_change < high_speed_info
+56
View File
@@ -0,0 +1,56 @@
"""Check the handwritten SDIO ABI against the RTL register contract."""
from __future__ import annotations
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
RTL_DEFINE = ROOT / "rtl/ip/storage/sdio_define.svh"
C_HEADER = ROOT / "crt/include/retrosoc/hal/sdio_regs.h"
def _rtl_values() -> dict[str, int]:
values: dict[str, int] = {}
pattern = re.compile(
r"^`define\s+(APB4_SDIO__\w+)\s+"
r"(?:(\d+)'([hHdDbB])([0-9a-fA-F_xXzZ]+)|(\d+))\s*$"
)
for line in RTL_DEFINE.read_text(encoding="utf-8").splitlines():
match = pattern.match(line)
if match is None:
continue
name, width, base, digits, decimal = match.groups()
del width
if decimal is not None:
values[name] = int(decimal, 10)
else:
radix = {"h": 16, "d": 10, "b": 2}[base.lower()]
values[name] = int(digits.replace("_", ""), radix)
return values
def _c_values() -> dict[str, int]:
values: dict[str, int] = {}
pattern = re.compile(
r"^#define\s+(RS_SDIO_ABI_\w+)\s+"
r"(?:UINT32_C\((0[xX][0-9a-fA-F]+)\)|(\d+)[uU]?)\s*$"
)
for line in C_HEADER.read_text(encoding="utf-8").splitlines():
match = pattern.match(line)
if match is None:
continue
name, hexadecimal, decimal = match.groups()
values[name.removeprefix("RS_SDIO_ABI_")] = int(
hexadecimal, 16
) if hexadecimal is not None else int(decimal, 10)
return values
def test_sdio_register_constants_match_expected_abi() -> None:
rtl = _rtl_values()
c_header = _c_values()
assert c_header == {
name.removeprefix("APB4_SDIO__"): value for name, value in rtl.items()
}
+19 -7
View File
@@ -74,14 +74,15 @@ def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -
irq_sva = (tmp_path / "rtl/soc_irq_sva.svh").read_text(encoding="utf-8")
filelist = (tmp_path / "soc_topology.fl").read_text(encoding="utf-8")
assert interfaces.count("apb4_if u_") == 17
assert interfaces.count("apb4_if u_") == 18
assert "nmi_if" not in interfaces
assert "soc_nmi" not in interfaces
assert "assign s_psel_comb[17] = `SOC_ADDR_IS_APB4_I2C1(s_decode_addr);" in routes
assert "assign s_psel_comb[1] = `SOC_ADDR_IS_APB4_GPIO(s_decode_addr) || `SOC_ADDR_IS_APB4_GPIO_ADMIN(s_decode_addr);" in routes
assert "SOC_ADDR_FLASH" not in routes
assert "assign s_psel_comb[15] = 1'b0;" in routes
assert "assign s_psel_comb[15] = `SOC_ADDR_IS_APB4_SDIO0(s_decode_addr);" in routes
assert "assign s_psel_comb[16] = `SOC_ADDR_IS_APB4_OPIPSRAM(s_decode_addr);" in routes
assert "assign s_psel_comb[18] = `SOC_ADDR_IS_APB4_SDIO1(s_decode_addr);" in routes
assert gpio.count("// GPIO") == 64
assert "u_uart1_if" not in gpio
assert "assign u_uart0_if.cts_n_i = u_gpio_if.di_i[0];" in gpio
@@ -89,6 +90,8 @@ def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -
assert "assign u_gpio_if.alt0_oe_i[0] = 1'b0;" in gpio
assert "assign u_gpio_if.alt0_do_i[1] = u_uart0_if.rts_n_o;" in gpio
assert "assign u_gpio_if.alt0_oe_i[1] = 1'b1;" in gpio
assert "assign u_gpio_if.alt0_do_i[15] = u_sdio0_if.sck_o;" in gpio
assert "assign u_sdio0_if.cmd_di_i = u_gpio_if.di_i[16];" in gpio
assert "assign u_gpio_if.alt1_do_i[0] = u_ps2_if.ps2_clk_o;" in gpio
assert "assign u_gpio_if.alt1_oe_i[0] = u_ps2_if.ps2_clk_oe_o;" in gpio
assert "assign u_gpio_if.alt1_do_i[1] = u_ps2_if.ps2_dat_o;" in gpio
@@ -113,16 +116,18 @@ def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -
assert "assign user_ip.paddr = s_addr_q;" in apb_routes
assert "({32{s_psel_q[7]}} & user_ip.prdata)" in apb_response
assert "localparam int NSLV = 8;" in apb_declarations
assert fabric.count("axi4_if #(") == 5
assert fabric.count("axi4_if #(") == 7
assert ".mgmt_axi4(u_mgmt_axi4_if)" in bus_fabric
assert ".user_axi4(u_user_axi4_if)" in bus_fabric
assert ".dma_axi4(u_dma_axi4_if)" in bus_fabric
assert ".sdio0_axi4(u_sdio0_axi4_if)" in bus_fabric
assert ".sdio1_axi4(u_sdio1_axi4_if)" in bus_fabric
assert ".cfg_axi4(u_cfg_axi4_if)" in bus_fabric
assert ".system_axi4(u_system_axi4_if)" in bus_fabric
assert ".axi4(u_system_axi4_if)" in apb_system_fabric
assert "`define SOC_IRQ_VECTOR_WIDTH 32" in irq_config
assert "`define SOC_USER_IRQ_MASK 32'h004EFBFC" in irq_config
assert "`define SOC_IRQ_APB4_PERIPH_WIDTH 16" in irq_config
assert "`define SOC_IRQ_APB4_PERIPH_WIDTH 18" in irq_config
assert "`define SOC_IRQ_APB4_SYSTEM_WIDTH 5" in irq_config
assert "assign irq_o[0] = u_clint_if.software_irq_o[0];" in rib_irq
assert "assign irq_o[10] = ws2812.irq_o;" in rib_irq
@@ -131,17 +136,21 @@ def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -
assert "assign irq_o[13] = s_dvp_irq;" in rib_irq
assert "assign irq_o[14] = s_dma_irq;" in rib_irq
assert "assign irq_o[15] = opipsram.irq_o;" in rib_irq
assert "assign irq_o[16] = sdio0.irq_o;" in rib_irq
assert "assign irq_o[17] = sdio1.irq_o;" in rib_irq
assert "assign irq_o[0] = pwm.irq_o;" in apb_irq
assert "assign irq_o[4] = s_rng_irq;" in apb_irq
assert "s_irq[10]" not in irq_wiring
assert "s_irq[10] = s_apb4_periph_irq[16];" in irq_wiring
assert "s_irq[15] = s_apb4_periph_irq[13];" in irq_wiring
assert "s_irq[16] = s_apb4_system_irq[4];" in irq_wiring
assert "s_irq[20] = s_apb4_periph_irq[14];" in irq_wiring
assert "s_irq[22] = s_apb4_periph_irq[15];" in irq_wiring
assert "irq_i[10] == 1'b0" in irq_sva
assert "s_irq[21] = s_apb4_periph_irq[17];" in irq_wiring
assert "irq_i[10] == apb4_periph_irq_i[16]" in irq_sva
assert "irq_i[15] == apb4_periph_irq_i[13]" in irq_sva
assert "irq_i[20] == apb4_periph_irq_i[14]" in irq_sva
assert "irq_i[22] == apb4_periph_irq_i[15]" in irq_sva
assert "irq_i[21] == apb4_periph_irq_i[17]" in irq_sva
assert "irq_i[31] == 1'b0" in irq_sva
assert "bind retrosoc soc_irq_topology_sva" in irq_sva
assert filelist.startswith("+incdir+")
@@ -171,6 +180,7 @@ def test_topology_preserves_default_irq_compatibility_mapping() -> None:
("i2c0", "apb4_periph", 7, 7, "i2c0.irq_o"),
("i2s", "apb4_periph", 8, 8, "i2s.irq_o"),
("xpi", "apb4_periph", 9, 9, "xpi.irq_o"),
("sdio0", "apb4_periph", 16, 10, "sdio0.irq_o"),
("pwm", "apb4_system", 0, 11, "pwm.irq_o"),
("ps2", "apb4_system", 1, 12, "ps2.irq_o"),
("rtc", "apb4_system", 2, 13, "u_rtc_if.irq_o"),
@@ -182,6 +192,7 @@ def test_topology_preserves_default_irq_compatibility_mapping() -> None:
("dvp", "apb4_periph", 13, 15, "s_dvp_irq"),
("dma", "apb4_periph", 14, 20, "s_dma_irq"),
("opipsram", "apb4_periph", 15, 22, "opipsram.irq_o"),
("sdio1", "apb4_periph", 17, 21, "sdio1.irq_o"),
]
@@ -227,7 +238,8 @@ def test_topology_rejects_duplicate_region_and_disabled_owner(tmp_path: Path) ->
assert "multiple targets" in result.stderr
document = json.loads(TOPOLOGY.read_text(encoding="utf-8"))
document["apb4_periph_targets"][15]["regions"] = ["APB4_SDIO"]
document["apb4_periph_targets"][15]["disabled"] = True
document["apb4_periph_targets"][15]["regions"] = ["APB4_SDIO0"]
result = validate(write_invalid_topology(tmp_path, document))
assert result.returncode != 0
assert "disabled but declares regions" in result.stderr