It’s been about 6 months since I started my FPGA journey with iCE40LP1K-CM36. I’ve been working on MN15439A controller based on this FPGA. One part of it using BRAM (Block RAM) as a Framebuffer memory. However, I encountered the problem with BRAM. First time of synthesize the code. Yosys refused to infer BRAM for me. And after I corrected that part (It’s nothing to do with Yosys at all). Even though I filled entire BRAM with 1s. Sometime It would read back as 0. It happened so often that I saw pattern of missing pixels on the screen.
After some researching and testing. I came with guides of how to use iCE40 BRAM in a proper way.
BRAM needs output
When I first wrote the controller code of the MN15439A. I haven’t had much experience with Verilog and FPGA and I left a TON of mistakes in the code. The result was some part of the code wasn’t reachable. Whether the Logic state is stay true/false and/or I totally forgot to use the enable signal.
I’ve learnt that when I successfully infer the BRAM. I noticed that when It’s still un-inferable. BRAM wasn’t read by other thing at all. Meaning that when BRAM output doesn’t connect to something. Because of that Yosys won’t infer the BRAM. Though, It’s not necessary to write to BRAM. Because you can use BRAM as ROM. So synthesizer kind of really care more about BRAM output.
So here is an example code of 8KBytes BRAM module. That you can easily instantiate this module. Just copy and paste in your Verilog design.
module BRAM(
input RWCLK,// Read/Write clock. This BRAM is RW synchronized
input [7:0]BRAM_IN,// Data input to BRAM (Write)
output reg [7:0]BRAM_OUT,// Data output from BRAM (Read)
input [12:0] BRAM_ADDR_R,// Read address
input [12:0] BRAM_ADDR_W,// Write address
input B_CE_W,// Write Enable
input B_CE_R);// Read enable
parameter BRAM_size = 8096 - 1;
reg [7:0] mem1 [BRAM_size:0];// This reg will be inferred as BRAM by Yosys. THis one uses 8096 Bytes (Entire BRAM space of LP1K).
integer i;
initial begin
for(i = 0; i < 8096; i++)// start with blank memory with 0 instead of x so that we can infer Yosys for BRAM.
mem1[i] <= 8'd0;
end
always@(posedge RWCLK) begin
if(B_CE_R)
BRAM_OUT <= mem1[BRAM_ADDR_R];
if(B_CE_W)
mem1[BRAM_ADDR_W] <= BRAM_IN;
end
endmodule
And here is example code of ROM module I used in E paper project. As you can see, there’s only output from the module. Yosys is happy that this module has output and inferred it to BRAM.
// Character ROM.
module CharROM(
input ROMCLK,// Read clock
output reg [7:0]ROM_OUT,// Data output
input [9:0] ROM_ADDR,// Read address
input ROM_CE);// Read enable
// E paper fonts.
reg [7:0] fonts [1023:0];// Font bitmap, Inferring to BRAM.
initial begin
// Write font data to BRAM rom.
$readmemh("epp_font_bmp.bin", fonts);// read file data from same location of this Verilog file. I used this binary data to initialize the BRAM as a character bitmap ROM.
end
always@(posedge ROMCLK) begin
if(ROM_CE)
ROM_OUT <= fonts[ROM_ADDR];
end
endmodule
Reading from BRAM needs State Machine (Dammit, I really love state machine UwU).
According to the MemoryUsageGuideforiCE40Devices (TN1250). This is the Timing diagram of iCE40 BRAM from page 4 :

Just ignore the data writing part (Will explain later after this). At around the bottom right area. There’s BRAM reading waveform. I won’t let you figure that out on your own but It’s good practice to read the timing/waveform diagram. Instead I will explain what really happen when with BRAM reading.

To read the data from BRAM. First, you need to set ReadEnable (RE) signal high (First clock cycle). Then write the address to read from BRAM to the BRAM module (2nd clock cycle). Then the data comes out from BRAM (on 3rd and 4th clock cycle).
But in real situation. After We write the address to BRAM. The data that comes out on the next clock cycle is kind of Unpredictable. It’s probably something to do with the actual BRAM timing or something that I literally have no idea about them. But one thing that I did know is that next clock cycle after that Unpredictable data, we now have the Reliable data read from BRAM!
From that BRAM behavior. the state machine can we written in different way. Sequential read and Non-sequential read.
With Sequential read. The code looks like this
reg [1:0]BRAM_FSM = 0;// first start to stay at default waiting state.
reg want_to_write = 0;// want to write ? set this to 1 to kick star the BRAM state machine.
reg [n:0]last_addr = 0;
.
.
.
last_addr <= 6969;// last address we want to read from.
case(BRAM_FSM)
0: begin
if(want_to_write)begin// check if we want to read from BRAM again
BRAM_FSM <= 1;// If yes back to the starting point of the loop
bram_re <= 1;// read enable == 1.
end
else begin
BRAM_FSM <= BRAM_FSM;// stay here until we got signal to read.
bram_ce <= 0;// read disable.
end
end
1: begin// this part is to wait 1 clock cycle to get reliable data on next clock cycle.
BRAM_FSM <= 2;// move to next step.
end
2: begin// This is where we read the data from BRAM and do the address increment.
read_addr <= read_addr + 1;// Address increment.
read_buffer <= bram_read;// read data from BRAM and put it into the buffer.
if(read_addr == last_addr)begin// if we reached the last address we want to read
read_addr <= 0;// reset back to address 0 (default address).
BRAM_FSM <= 0;// exit from BRAM read state machine loop.
end
else
BRAM_FSM <= 1;// back to stage 1 to spend 1 clock cycle to wait for reliable data.
end
endcase// end BRAM state machine case.
The sequential code start from some address. Then keep increase the address until the last address that we want then stop. The reading sequence is something like this :
Steps | BRAM_FSM | read_addr | Descriptions |
1 | 0 | 0 | Wait for want_to_write. if want_to_write == 1, BRAM_FSM will goes back to 1. |
2 | 1 | 0 | at the beginning of the BRAM read. The very first read address is already written to the BRAM. |
3 | 2 | 1 | BRAM address increment by 1. read data from address 0. |
4 | 1 | 1 | wait 1 clock cycle for reliable data on next cycle. |
5 | 2 | 2 | BRAM address increment by 1. read data from address 1. |
6 | 1 | 2 | wait 1 clock cycle for reliable data on next cycle. |
7 | 2 | 3 | BRAM address increment by 1. read data from address 2. |
n-2 | 1 | last_addr | wait 1 clock cycle for reliable data on next cycle. |
n-1 | 2 | 0 | BRAM address increment by 1. But since it already reached the last_addr. BRAM address reset back to 0. read data from address last_addr. Exit from BRAM read loop. |
n | 2 | 0 | Wait for want_to_write. if want_to_write == 1, BRAM_FSM will goes back to 1. |
Now it’s the time of Non-sequential read. This state machine is a little bit different from sequential read.
reg [1:0]BRAM_FSM = 0;
reg want_to_read = 0;
reg read_done = 0;
.
.
.
case(BRAM_FSM)
0: begin// wait for read thingy to begin.
if(want_to_read)begin
bram_re <= 1;// read enable
BRAM_FSM <= 1;
read_done <= 0;
end
else begin
bram_re <= 0;// read disable
read_addr <= 0;
BRAM_FSM <= BRAM_FSM;
read_done <= 1;
end
end
1: begin// Write address to the BRAM
read_addr <= <your desire address to read from>;
BRAM_FSM <= 2;
end
2: begin// Reliable data comes on next clock cycle.
BRAM_FSM <= 3;
end
3: begin// read the data and write it to wherever you want.
read_buffer <= bram_read;// read data from BRAM to buffer.
BRAM_FSM <= 0;// then goes back to stage 0.
end
as always. The sequence table is here : (Let say that the address to read from is ‘h69)
Steps | BRAM_FSM | read_addr | Descriptions |
1 | 0 | 0 | Wait for want_to_write. if want_to_write == 1, BRAM_FSM will goes back to 1. |
2 | 1 | ‘h69 | write address to BRAM. |
3 | 2 | ‘h69 | wait 1 clock cycle for reliable data on next cycle. |
4 | 3 | ‘h69 | read data from BRAM at ‘h69 and write to buffer. Exit BRAM reading sequence. |
5 | 0 | 0 | Wait for want_to_write. if want_to_write == 1, BRAM_FSM will goes back to 1. |
Always manage Read Write
I personally don’t read and write from/to BRAM simultaneously. All of my code involving BRAM will write first then read or vise versa but not at the same time. IIRC, iCE40 BRAM allows to Read/Write at the same time. But that require some management too. For now I don’t have example of that but soon I might test the simul RW.
Summary
- BRAM always need to be read by something in order to get inferred.
- Read from BRAM (Whether it’s BRAM as RAM or ROM) require a proper state machine and sequence.
- Always manage Read Write. If read, don’t write and vise versa.
I hope this sh1tty 101 helps you solve problem related to BRAM usage in your iCE40 FPGA design ;D.