SD Card
The FPGC has a single microSD card slot wired to the FPGA as a
fifth SPI peripheral (SPI5). The slot accepts standard SDHC and
SDXC cards; legacy SDSC cards are not supported. The card is
optional — nothing in the boot path or in BDOS depends on a card
being inserted — but it is the second mass-storage option on the
board, alongside the on-board SPI flash.
Bus shape
The slot is wired in SPI mode, the simplest of the three electrical modes the SD card specification defines. The other two (1-bit SD bus and 4-bit SD bus) require a multi-line bidirectional controller and a clock that the FPGC's existing SPI cores cannot produce, so the SPI mode trade-off — slower but trivial to drive — is the right fit.
| Pin | SPI-mode role |
|---|---|
CS |
chip select (active low) |
CLK |
serial clock |
DI |
host → card (MOSI) |
DO |
card → host (MISO) |
VDD/VSS |
3.3 V / GND |
The card-detect switch in the slot is not routed in the current PCB revision, so software cannot tell whether a card is inserted other than by attempting to initialise it.
The SPI clock runs at the same divider as the BRFS flash on
SPI1 — fast enough to be useful but well within the SD spec for
SPI mode. The SD specification mandates that the initial clock
phase (before the card has been put into SPI mode) be no faster
than 400 kHz; in practice every card the project has been tested
against tolerates the higher clock from cycle one, and the FPGC does
not implement a slow-init phase.
Logical layout
Every SD card presents itself as a flat array of 512-byte logical blocks. Block 0 is the master boot record / boot sector if the card has been formatted with a partition table; the FPGC does not currently parse partition tables and treats the card as raw block storage.
Capacities below 2 GiB use byte addressing (SDSC). Capacities of 2 GiB and above use block addressing (SDHC up to 32 GiB, SDXC above that). The FPGC's driver hard-rejects byte-addressed cards during init, so every supported card sees the same address space: pass an LBA in, get a 512-byte block back.
The card has its own internal flash translation layer, wear levelling, and erase scheduling. The host never sees the underlying NAND geometry and never has to issue an erase before overwriting a block — an in-place rewrite is always sufficient.
Initialisation
Bringing a card from cold power-up to ready takes a small choreographed
sequence. The card boots in 1-bit SD-bus mode and only switches to
SPI mode once it has seen CMD0 (GO_IDLE_STATE) with CS held
low. Before that, CS must stay deasserted while at least 74 clock
pulses are sent — the spec calls these the "initial dummy clocks"
and they give the card's internal regulator time to reach a stable
state.
The full sequence is:
- Hold
CShigh, send 80 dummy clocks (10 bytes of0xFF). - Pull
CSlow, sendCMD0(GO_IDLE_STATE) with the canonical precomputed CRC. The card responds withR1 = 0x01to confirm it has entered SPI mode. - Send
CMD8(SEND_IF_COND) with the voltage and check-pattern arguments specified by the SD spec. Cards that don't respond, or that flag CMD8 as illegal, are pre-spec-2.0 (SDSC) and are rejected at this point. - Loop on
ACMD41(SEND_OP_COND) with the High Capacity Support bit set, until the card returnsR1 = 0x00(ready) or the retry budget runs out. - Issue
CMD58(READ_OCR) and inspect the CCS bit in the returned 32-bit OCR. CCS = 0 means SDSC byte-addressing — also rejected. CCS = 1 means SDHC/SDXC block-addressing, which is what we want.
Once init is complete the driver pulls capacity (in 512-byte
blocks) out of the CSD register via CMD9, leaves the card
selected only for the duration of each later command, and is ready
to serve block reads and writes.
Command set
A small subset of the full SD command set is enough for a block device:
| CMD | Name | Direction | Purpose |
|---|---|---|---|
CMD0 |
GO_IDLE_STATE |
host → card | Reset; required first-ever command |
CMD8 |
SEND_IF_COND |
host → card | Probe for ≥ spec 2.0 compliance |
CMD9 |
SEND_CSD |
card → host | Read card-specific data (capacity) |
CMD12 |
STOP_TRANSMISSION |
host → card | Stop a multi-block stream |
CMD16 |
SET_BLOCKLEN |
host → card | Defensive; ignored on SDHC/SDXC |
CMD17 |
READ_SINGLE_BLOCK |
card → host | Read one 512-byte block |
CMD18 |
READ_MULTIPLE_BLOCK |
card → host | Stream consecutive 512-byte blocks |
CMD24 |
WRITE_BLOCK |
host → card | Write one 512-byte block |
CMD25 |
WRITE_MULTIPLE_BLOCK |
host → card | Stream consecutive blocks |
CMD55 |
APP_CMD |
host → card | Marks the next command as ACMDxx |
ACMD41 |
SD_SEND_OP_COND |
host → card | Init "ready?" loop |
CMD58 |
READ_OCR |
card → host | Read operating conditions register |
CMD59 |
CRC_ON_OFF |
host → card | Toggle bus CRC checking |
Every command frame is six bytes: 0x40 | cmd_index, then a 32-bit
big-endian argument, then a CRC7 byte with the LSB always set to 1.
After a command the host clocks 0xFF bytes until it sees an R1
response (top bit clear). Commands that return more than R1
(CMD8, CMD58) are followed by a fixed number of trailing data
bytes; commands that move a block of data are followed by a start
token (0xFE for reads, 0xFE/0xFC for writes) and the 512-byte
payload plus a 16-bit CRC.
Read / write protocol
A single-block read (CMD17) flows like this:
- Host sends
CMD17with the LBA as argument. - Card returns
R1 = 0x00. - Card streams
0xFFbytes (busy filler) until it has the data ready, then sends the start token0xFE. - Card streams 512 data bytes followed by a 16-bit CRC.
- Host deselects
CSand sends one more dummy byte to release the bus.
A single-block write (CMD24) is the mirror image: host sends
the command, gets R1, sends one gap byte and the start token,
then 512 payload bytes and a CRC. The card replies with a 1-byte
data response token (0x05 = accepted, 0x0B = CRC error,
0x0D = write error). While the card programs the block it holds
DO low; the host clocks 0xFF until DO floats high again.
Multi-block transfers (CMD18 / CMD25) follow the same per-block
shape but stay open until the host explicitly stops the stream
(with CMD12 for reads, or with the 0xFD "stop tran" token for
writes).
DMA path
The SPI5 controller is wired into the DMA engine's
SPI burst port, with the same MEM2SPI and SPI2MEM modes used by
the BRFS flash. Software issues the command, waits for the start
token, and then hands the 512-byte payload to the DMA. The CPU is
free during the transfer and only re-engages to drain the trailing
CRC bytes and to poll the busy line on writes.
This matters more for writes than for reads: a 512-byte write is followed by a card-internal program operation that can take several milliseconds, and being able to do unrelated work during the program phase keeps the rest of the system responsive.
Reliability and recovery
SD cards can recover from a power loss between the host's last write and the card's internal commit, but not always cleanly. The SD specification mandates an internal write journal but leaves recovery semantics card-specific. Software that uses the SD card for file storage should treat any in-flight write as non-persistent until the next read confirms it.
The card's internal controller can also reorder erases and remaps; the host has no insight into when this is happening other than through occasional longer-than-normal busy periods on a write.
If the host issues a malformed command or ends a transfer at the wrong place, the card can become wedged in a transitional state where subsequent commands fail with a "protocol" response code. Power-cycling the slot (in practice, power-cycling the whole board) is the only reliable way to reset the card from this state — the SD spec has no software-only "go back to idle" command that is guaranteed to work after a multi-block stream has gone sideways.
Future use
The SD card driver and its block-oriented vtable wrapper are in place but the BRFS filesystem currently only mounts the on-board SPI flash. Wiring the card up as a second BRFS volume — with the much larger capacity available on a microSD — is a future filesystem-layer change; nothing in the hardware or driver layers needs to move first.