Skip to content

DOOMSoC SD Card Interface

Back to project overview

The SD card interface is a memory mapped peripheral which consists of N control and status registers and a 512 byte block buffer. The interface consists of logic that facilitates initializing the card and moving data between the card and the block buffer.

The interface operates solely in SPI mode. It initializes the card and the1 n increases the SPI frequency to 50MHz.

Overview

The SD card interface logic consists of the following components: * An SPI PHY with programmable bus frequency * An automated block transfer FSM * Read and Write word registers * CSR registers to allow the CPU to interface with the aforementioned components

The SD initialization is performed via software by writing the necessary bytes

Register definitions:

0x000 : ctrl \ 0x004 : status \ 0x008 : tx_word \ 0x00C : rx_word \ 0x010 : block_addr \ 0x200 : block data start\ 0x2FF : block data end

ctrl: \ | 1 | 0 | \ | block_write | block_read |

status: \ | 1 | 0 | \ block_rd_in_prog | rx_rdy | tx_rdy |

Memory Map Overview

All registers are 32-bit wide and aligned to 4-byte boundaries unless otherwise noted. The block buffer occupies a contiguous 512-byte memory region mapped from 0x200 to 0x3FF.

Base Offset Register Name Width Access Reset Value Description
0x000 CTRL 32 RW 0x0 Control Register
0x004 STATUS 32 RO 0x0 Status Register
0x008 TX_WORD 8 WO 0x0 Transmit Single Word
0x00C RX_WORD 8 RO 0x0 Receive Single Word
0x010 BLOCK_ADDR 32 RW 0x0 External SD card Target Address
0x200–0x3FF BLOCK_DATA 32 RW 0x0 Internal Block Transfer Buffer (512 Bytes)

Register Descriptions

CTRL (Control Register)

Bits Field Name Access Reset Description
5 BLOCK_WRITE WO (Strobe) 0 Block Write Trigger: Writing 1 initiates an autonomous 512-byte transmission from BLOCK_DATA to the SD card starting at BLOCK_ADDR. Self-clearing.
4 BLOCK_READ WO (Strobe) 0 Block Read Trigger: Writing 1 initiates an autonomous 512-byte fetch from the SD card at BLOCK_ADDR into BLOCK_DATA. Self-clearing.
3 CLR_CS WO (Strobe) 0 Clear Chip Select: Writing 1 drives the active-low Chip Select (CS_N) high (deasserts slave select). Self-clearing.
2 SET_CS WO (Strobe) 0 Set Chip Select: Writing 1 drives the active-low Chip Select (CS_N) low (asserts slave select). Self-clearing.
1 CLR_INIT_CLK WO (Strobe) 0 Clear Init Clock Mode: Writing 1 switches the SPI clock generator back to high-speed operational mode (e.g., normal system transfer clock). Self-clearing.
0 SET_INIT_CLK WO (Strobe) 0 Set Init Clock Mode: Writing 1 gates/prescales the SPI clock generator to slow identification speed (100–400 kHz) for SD card initialization sequences. Self-clearing.

STATUS (Status Register)

Bits Field Name Access Reset Description
3 BLOCK_WR_BUSY RO 0 Block Write Active:
0: Idle.
1: Block FSM currently shifting out data.
2 BLOCK_RD_BUSY RO 0 Block Read Active:
0: Idle.
1: Block FSM currently shifting in data.
1 RX_RDY RO 0 Receive Data Ready:
0: RX_WORD empty or transfer in progress.
1: Valid word latched into RX_WORD and ready to read. Cleared by reading RX_WORD.
0 TX_RDY RO 0 Transmit Buffer Ready:
0: Shift register / TX buffer busy.
1: Ready to accept the next word written to TX_WORD.

TX_WORD (Transmit Word Register)

Bits Field Name Access Reset Description
7:0 TX_DATA WO 0x0 Single word to shift onto MOSI. Software must verify STATUS.TX_RDY == 1 before writing.

RX_WORD (Receive Word Register)

Bits Field Name Access Reset Description
7:0 RX_DATA RO 0x0 Data captured from MISO. Reading this register deasserts STATUS.RX_RDY.

BLOCK_ADDR (Block Target Address)

Bits Field Name Access Reset Description
31:0 ADDR RW 0x0 Holds the target byte address sent across SPI prior to streaming payload data during a block read or write command.

BLOCK_DATA (Internal Block RAM Buffer)

Internal block data buffer: * Pre-Write: CPU/DMA fills 0x200–0x3FF with payload data before setting CTRL.BLOCK_WRITE = 1. * Post-Read: Once STATUS.BLOCK_RD_BUSY transitions from 1 to 0, the received 256 bytes are available in this address range.

SPI PHY interface:

Block copy FSM

flowchart TD
    IDLE["IDLE"]

    %% Branch from IDLE
    IDLE -->|read_block| SEND_READ_CMD["SEND_READ_CMD<br/>(CMD17)"]
    IDLE -->|write_block| SEND_WRITE_CMD["SEND_WRITE_CMD<br/>(CMD24)"]

    %% Converge to Address & Checksum
    SEND_READ_CMD --> SEND_ADDR["SEND_ADDR<br/>(4 bytes: block_addr)"]
    SEND_WRITE_CMD --> SEND_ADDR
    SEND_ADDR --> SEND_CHKSUM["SEND_CHKSUM<br/>(Fixed END_BIT = 0x01)"]
    SEND_CHKSUM --> WAIT_R1["WAIT_R1<br/>(Poll until bit 7 == 0)"]

    %% Fork to Read/Write paths
    WAIT_R1 -->|read_active| READ_GAP["READ_GAP<br/>(Wait for 0xFE)"]
    WAIT_R1 -->|write_active| WRITE_GAP["WRITE_GAP<br/>(1 dummy byte)"]

    %% Read Path
    READ_GAP --> READ_BLOCK["READ_BLOCK<br/>(512 bytes)"]
    READ_BLOCK --> READ_CRC["READ_CRC<br/>(2 dummy bytes)"]
    READ_CRC --> IDLE

    %% Write Path
    WRITE_GAP --> WRITE_START["WRITE_START<br/>(Send 0xFE)"]
    WRITE_START --> WRITE_BLOCK["WRITE_BLOCK<br/>(512 bytes)"]
    WRITE_BLOCK --> WRITE_CRC["WRITE_CRC<br/>(2 dummy bytes)"]
    WRITE_CRC --> WAIT_WR_RESP["WAIT_WR_RESP<br/>(Wait data response token)"]
    WAIT_WR_RESP --> WAIT_WR_COMMIT["WAIT_WR_COMMIT<br/>(Wait busy MISO high)"]
    WAIT_WR_COMMIT --> IDLE