Back to Home

SD card as ROM for RISC-V YRV FPGA

The article describes using an SD card as storage for executable code for the RISC-V YRV core on the Tang Nano 9K board. Implemented FSM for reading 512-byte sectors with caching in RAM. Testing with HELO output confirms functionality.

SD instead of RAM: RISC-V YRV on FPGA with memory card
Advertisement 728x90

Using an SD Card as Code Storage for RISC-V YRV on Tang Nano 9K FPGA

On budget-friendly FPGA boards like the Tang Nano 9K, limited synthesis resources constrain on-chip RAM—making it impractical to store full program code in block RAM. Only 16–20 KB is available for stack and data, sufficient for basic tests but not for complete firmware. This article proposes using an SD card as external read-only memory (ROM), with sector-based 512-byte reads—functionally similar to eXecute-In-Place (XIP) from QSPI Flash, but adapted to the constraints of SPI protocol.

The YRV core interfaces with memory via a 16-bit bus. Read/write logic is implemented in Verilog:

always @ (posedge clk) begin
  if (mem_trans[0]) begin
    mem_rdata[31:24] <= mcu_mem_bank3 [mem_addr[13:2]];
    mem_rdata[23:16] <= mcu_mem_bank2 [mem_addr[13:2]];
    mem_rdata[15:8]  <= mcu_mem_bank1 [mem_addr[13:2]];
    mem_rdata[7:0]   <= mcu_mem_bank0 [mem_addr[13:2]];
  end
  if (mem_wr_byte[3]) mcu_mem_bank3 [mem_addr_reg[13:2]] <= mem_wdata[31:24];
  if (mem_wr_byte[2]) mcu_mem_bank2 [mem_addr_reg[13:2]] <= mem_wdata[23:16];
  if (mem_wr_byte[1]) mcu_mem_bank1 [mem_addr_reg[13:2]] <= mem_wdata[15:8];
  if (mem_wr_byte[0]) mcu_mem_bank0 [mem_addr_reg[13:2]] <= mem_wdata[7:0];
end

State Machine for SD Sector Caching

RAM is reduced to just 512 bytes—one sector. When the CPU accesses an address within the currently cached sector, execution continues immediately with mem_ready asserted (analogous to HREADY in AHB-Lite). On a cache miss—i.e., when accessing outside the current sector—the finite state machine (FSM) triggers a new sector read from the SD card. A byte_cnt counter tracks read progress.

Google AdInline article slot

The FSM includes three states:

  • IDLE: Waits for a memory access request.
  • READ: Reads the requested 512-byte sector from the SD card.
  • START: Initiates the read command (sends rd).

The SD controller is derived from open-source FPGA-SDcard-Reader and MIT 6.111 projects, then adapted for the Tang Nano 9K’s SPI interface (DAT1/DAT2 lines left unconnected). Initialization runs at 400 kHz for robustness—no power-cycle reset required.

Integration into YRV Plus Project

Implemented in labs/99_experimental/99_03_yrv_sd, built atop the basics-graphics-music base. The configuration tang_nano_9k_tm1638_sd sets SD pins to 3.3V. Output uses TM1638: port0 drives BCD segments; port1 selects digit position.

Google AdInline article slot

Example assembly code for displaying "HELO" (each character stored in its own sector):

li s0, 0xFFFF0000    # port0 — segment data
li s1, 0xFFFF0002    # port1 — digit position

main_loop:
  # 'H' (digit 3)
  li t1, 0x8
  sh t1, 0(s1)
  .rept 100
  nop
  .endr
  li t2, 0b00110111
  sh t2, 0(s0)
  .rept 1000
  nop
  .endr
  # Repeat similarly for E, L, O

Firmware is written sector-by-sector using HxD.

Test Results:

Google AdInline article slot
  • ~1,000 NOPs executed between characters (representing real workload).
  • "HELO" displays clearly across four 7-segment digits.
  • Reliable reset and initialization—no power cycling needed.
  • Removing the SD card halts execution cleanly.

Key Takeaways

  • SD cards in SPI mode support XIP-like execution when paired with a 512-byte sector cache.
  • The FSM minimizes latency; mem_ready keeps the CPU synchronized during reads.
  • Ideal for low-bandwidth applications where signal timing is in the millisecond range.
  • For smoother performance, consider increasing the cache to 1–2 KB.
  • Fully compatible with YRV cores on Gowin FPGAs—even without external SRAM.

Optimization Opportunities

Current implementation takes 6+ clock cycles per sector read (vs. 6 for QSPI). Expanding the cache to 2–4 sectors would reduce cache misses significantly. Multi-bank buffering—mirroring YRV’s original memory logic—is also feasible. For advanced developers: integrate with Wishbone or AXI adapters to scale this solution across larger SoC designs.

— Editorial Team

Advertisement 728x90

Read Next