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.
iCE40 UltraPlus FPGAs include four 256 Kb SPRAM blocks organized as 16K x 16-bit (16,384 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 ( .ADDRESS(addr), .DATAIN(data_in), .MASKWREN(we), .WREN(|we), .CHIPSELECT(1'b1), .CLOCK(clk), .STANDBY(1'b0), .SLEEP(1'b0), .POWEROFF(1'b1), .DATAOUT(data_out) ); endmodule
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.
The SB_SPRAM256KA primitive has the following ports:
|ADDRESS [13:0]||Memory address|
|DATAIN [15:0]||Value to write|
|MASKWREN [3:0]||Select nibbles for writing|
|WREN||Write enable (active high)|
|CHIPSELECT||Enable block (active high)|
|CLOCK||Clock for memory|
|STANDBY||Light sleep (active high)|
|SLEEP||Deep sleep (active high)|
|POWEROFF||Turn off memory (active low)|
|DATAOUT [15:0]||Output 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.
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.
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:
|Feature||SPRAM||Block RAM (EBR)|
|Ports||One (Shared by Read & Write)||Separate Read & Write or one R/W|
|Data Width||Fixed 16 bit (4-bit write mask)||2, 4, 8, or 16 bit|
|Block Size||256 Kb (32 KiB)||4 Kb (512 bytes)|
|Block Count||4||30 (UP5K) or 20 (UP3K)|
|Total Size||1024 Kb||120 Kb (UP5K) or 80 Kb (UP3K)|
NB. There are two models of UltraPlus, UP3K and UP5K, but both include the full 1 Mb of SPRAM.
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.
The following boards include the iCE40UP5K FPGA:
Check out my FPGA demos or the FPGA graphics tutorials.
Have a question or suggestion? Contact @WillFlux or join me on Project F Discussions or 1BitSquared Discord. If you like what I do, consider sponsoring me on GitHub. Thank you.