iCE40 BRAM 101 – (Probably) The correct way to use iCE40 Block RAM

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 :

Look quite confusing to me xD

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 :

StepsBRAM_FSMread_addrDescriptions
100Wait for want_to_write. if want_to_write == 1, BRAM_FSM will goes back to 1.
210at the beginning of the BRAM read. The very first read address is already written to the BRAM.
321BRAM address increment by 1.
read data from address 0.
411 wait 1 clock cycle for reliable data on next cycle.
522BRAM address increment by 1.
read data from address 1.
612wait 1 clock cycle for reliable data on next cycle.
723BRAM address increment by 1.
read data from address 2.
n-21last_addrwait 1 clock cycle for reliable data on next cycle.
n-120BRAM 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.
n20Wait for want_to_write. if want_to_write == 1, BRAM_FSM will goes back to 1.
BRAM reading sequence.

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)

StepsBRAM_FSMread_addrDescriptions
100Wait for want_to_write. if want_to_write == 1, BRAM_FSM will goes back to 1.
21‘h69write address to BRAM.
32‘h69wait 1 clock cycle for reliable data on next cycle.
43‘h69read data from BRAM at ‘h69 and write to buffer.
Exit BRAM reading sequence.
500Wait 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

  1. BRAM always need to be read by something in order to get inferred.
  2. Read from BRAM (Whether it’s BRAM as RAM or ROM) require a proper state machine and sequence.
  3. 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.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s