Project F


Published · Updated

The iCE40 UltraPlus distinguishes itself from the rest of the iCE40 FPGA family by including a relatively generous 1 Mb (128 KiB) of single port synchronous SRAM known as SPRAM. SPRAM blocks are much larger than BRAM but are limited to a single port and 16-bit data bus.

In this quick how to, we learn how to use SPRAM with Yosys and contrast it with Block RAM. If you want to learn more about other FPGA memory, see FPGA Memory Types.

Share your thoughts with @WillFlux on Mastodon or Twitter. If you like what I do, sponsor me. 🙏


iCE40 UltraPlus FPGAs include four 256 Kb SPRAM blocks organized as 16K x 16-bit (16384 locations of 16 bits each). You can combine these blocks to create wider or deeper memories. For example, you can create a 32-bit memory by combining two SPRAM blocks to form a 16K x 32-bit memory (64 KiB).


SPRAM is easy to use, but you need to explicitly request SPRAM: Yosys won’t infer it for you.
The SPRAM primitive is named SB_SPRAM256KA.

See the comparison with Block RAM for more details on which memory is best for your design.

Example Verilog Module

Here’s a simple Verilog wrapper around a single 256 Kb SPRAM block that works with Yosys:

module spram #(
    localparam WIDTH=16,     // fixed data width: 16-bits
    localparam DEPTH=16384,  // fixed depth: 16K 
    localparam ADDRW=$clog2(DEPTH)
    ) (
    input wire logic clk,
    input wire logic [3:0] we,
    input wire logic [ADDRW-1:0] addr,
    input wire logic [WIDTH-1:0] data_in,
    output     logic [WIDTH-1:0] data_out

    SB_SPRAM256KA spram_inst (

This example uses a 4-bit write enable signal, we, to select individual nibbles for write. If any of the bits of we are set, then WREN is enabled for writing. All the ports are described in the next section.

Port Summary

The SB_SPRAM256KA primitive has the following ports:

ADDRESS [13:0]14'h0Memory address
DATAIN [15:0]16'h0Value to write
MASKWREN [3:0]4'b1111Select nibbles for writing
WREN1'b0Write enable (active high)
CHIPSELECT1'b0Enable block (active high)
CLOCKN/AClock for memory
STANDBY1'b0Light sleep (active high)
SLEEP1'b0Deep sleep (active high)
POWEROFF1'b1Turn off memory (active low)
DATAOUT [15:0]N/AOutput value from memory read

All ports are inputs apart from DATAOUT.
For details on the sleep and power pins see the iCE40 SPRAM Usage Guide.

Write Mask

SPRAM has a fixed 16-bit data bus, but you can select individual nibbles (4 bits) for writing with MASKWREN. CPUs usually expect memory to be byte writeable; the write mask allows you to trivially support byte writes. 4-bit writes are more unusual but come in handy when working with SPI devices or 16 colour (4 bit) graphics.

For example, with MASKWREN(4'b1100), the upper byte of DATAIN is written to memory.

Initializing SPRAM

Unlike Block RAM, SPRAM cannot be initialized by device configuration, so you can’t use $readmemh. SPRAM starts out with undefined values, which you need to explicitly initialize in logic if required.

Compared with Block RAM

The following comparison can help you decide between SPRAM and block RAM:

FeatureSPRAMBlock RAM (EBR)
PortsOne (Shared by Read & Write)Separate Read & Write or one R/W
Data WidthFixed 16 bit (4-bit write mask)2, 4, 8, or 16 bit
Block Size256 Kb (32 KiB)4 Kb (512 bytes)
Block Count430 (UP5K) or 20 (UP3K)
Total Size1024 Kb120 Kb (UP5K) or 80 Kb (UP3K)
Init ValuesNoYes

NB. There are two models of UltraPlus, UP3K and UP5K, but both include the full 1 Mb of SPRAM.

Project Ideas

If you’re doing CPU development, try combining two SPRAM blocks to make a 32-bit wide, 64 KiB memory. I’ll be adding examples of SPRAM as a framebuffer in future.

iCE40UP Boards

The following boards include the iCE40UP5K FPGA:


What’s Next?

If you enjoyed this post, please sponsor me. Sponsors help me create more FPGA and RISC-V projects for everyone, and they get early access to blog posts and source code. 🙏

Check out my FPGA demos or the FPGA graphics tutorials.