Inferring block RAM - Page 1
Forum section: electronics / general

author: kpow8050
author_rank: Regular Contributor
timestamp: April 01, 2022, 11:40:35 am
content: I'm attempting to use the block RAM available in my FPGA (Altera Cyclone II) to store a relatively large array of data. Based on my readings about block RAM, the compiler is supposed to infer whether the design requires it or not automatically. However upon compilation of my code it does not appear that is the case for me and I end up with a design that has 2.5x more gates than the entire FPGA can handle (clearly they are all being used to store the memory). The lines I am using to declare my memory arrays are shown below (System verilog)Code:// Video memory (can store up to 4095 pixels based on the current 12 bit address size)reg [N_DATA_BITS-1:0] bufferRAM [MAX_RAM:0]; // Stores the buffer data to be loaded into VRAMreg [N_DATA_BITS:0] bufferAddress = 0; // Keeps track of the current buffer memory addressreg [N_DATA_BITS-1:0] activeRAM [MAX_RAM:0]; // Current VRAM for outputing pixel datareg [N_DATA_BITS:0] vramAddress = 0; // Keeps track of the current VRAM memory addressIs there anything missed here? The memory is interfaced over a serial port and updated in a loop synchronous to the main FPGA clock as followsCode:STORE_PIXEL_PACKET : begin // Store the pixels in the buffer RAMbufferRAM <= data_reg_reversed[N_DATA_BITS-1:0];if ( bufferAddress < MAX_RAM ) bufferAddress <= bufferAddress + 1'b1; // Increment buffer addresselse bufferAddress <= 1'b0; // reset buffer addressendRESET_ADDRESS : begin // reset the buffer address for buffer memory accessif (data_reg_reversed < MAX_RAM) bufferAddress <= data_reg_reversed; // 0 will reset it to 0, otherwise the buffer address can be set to an arbitrary address locationelse bufferAddress <= 1'b0;endUPDATE_VRAM : begin // Copy buffer ram into VRAMactiveRAM <= bufferRAM;endAnd the memory is accessed as shown in the following snippet of codeCode:if( debug_mode ) laser_out <= pixel_count; // Laser is toggled based on the LSB of pixel counterelse begin// Use VRAM for pixel outputlaser_out <= activeRAM;if(vramColCount < N_DATA_BITS-1) vramColCount <= vramColCount + 1'b1;else beginvramColCount <= 1'b0; // reset col counter and increment row counterif(vramRowCount < MAX_RAM) vramRowCount <= vramRowCount + 1'b1;else vramRowCount <= 1'b0;endendI can include the full code if required, however the snippets I posted above include all the instances where the RAM is used.Is there a way I can let the compiler know that I want those registers to be block RAM?

author: AndyC_772
author_rank: Super Contributor
timestamp: April 01, 2022, 02:31:41 pm
content: What are the read and write clocks for the RAM?Inferring RAM blocks is all well and good provided the function of your logic is exactly compatible, under all conditions, with the hard RAM blocks in the device. However, if there's some way (however small) in which your code describes something that doesn't exactly match the hardware, then it ends up all getting turned into logic cells instead.IMHO the easiest and most reliable way to do this is use the Megawizard Plug-in Manager to create an instance of RAM which matches your needs, then incorporate that into your project as a separate component.

author: Bassman59
author_rank: Super Contributor
timestamp: April 01, 2022, 03:46:07 pm
content: You should probably spend a few minutes reading the synthesis guide to see exactly what you need to do so the tools infer block RAMs.Pro tip: BRAMs are synchronous devices and need read and write clocks. The code snippets you posted don't have clocks.

author: BrianHG
author_rank: Super Contributor
timestamp: April 01, 2022, 04:25:14 pm
content: There is a mistake in your logic's approach to a dual buffer.For now, please just launch the LPM_DP_RAM megafunction generator and place in the parameters your want. (IE: Dual port memory, SC or DC)IE: 4096 bits (2 line buffer), or 8192 bits (4 line buffer).8 bit write port, 1 bit read port.Or, if your MCU interface is serial and you can stream 2048 bits in 1 go:1 bit write port and 1 bit read port. (IE write each bit as it comes in.)Then, look for the generated source .v, take it out and just copy & paste into your code.Remember to use your main single 25MHz clock for everything.Also remember that having a registered address in and registered data out grants the best performance, though 25MHz wont need it, it is still best to learn now how to deal with the 2 clock delay on the read side as this would be needed if you want to bump your source clock up to 200MHz.Inferring ram in verilog can be done, however, there are some rules to follow and you might not get what you want when the compiler tries compile it.I hope you didn't make a complete mess of thing since the last bit of sim code.

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 01, 2022, 05:33:11 pm
content: Quote from: Bassman59 on April 01, 2022, 03:46:07 pmYep. Note that the culprit here is that writes are asynchronous. Most FPGA tools can infer block RAM with synchronous writes and asynchronous reads (as long as reads are done only at one or two addresses concurrently. In the case of two reads, that will infer dual-port memory, but properly having tools infer dual-port memory can be tricky, so definitely read the docs.)Async reads will infer non-registered block RAM outputs (lower latency but reaches lower Fmax.)Of course code style heavily matters, so once again make sure to read the vendor docs about writing HDL for inferring memory. All vendors that I know of have such docs.

author: BrianHG
author_rank: Super Contributor
timestamp: April 01, 2022, 06:09:18 pm
content: Also, the OT intent with this line is impossible with block ram:Quote from: kpow8050 on April 01, 2022, 11:40:35 amHe just needs to rethink things through a little better.Such a setup cannot function as inferred ram blocks. It can only be done in logic cells or shift-taps which is not available on Cyclone II.Generating the LPM_DP_RAM will force him away from that line's intent and to visualize his code correctly as addressing a 'ram'.Then, 1 day if he so chooses to, maybe by chance he may attempt to remove the DP_RAM and create the equivalent inferred ram version.

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 01, 2022, 06:20:55 pm
content: Quote from: BrianHG on April 01, 2022, 06:09:18 pmUh yeah. It just looks like some software approach.It tries to assign whole memory blocks, and... asynchronously.

author: BrianHG
author_rank: Super Contributor
timestamp: April 01, 2022, 06:28:43 pm
content: It's ok, I do not want to give too much away as kpow is just beginning to learn HDL and I know from a previous thread where I helped him where his skill is at.If he gets stuck, then I'll point him in the right direction.As for now, its the ram he wants to use for his line buffer should be seen as a dual port ram chip with a master clock, a write address with write data and write enable, and a read address with valid read data coming out 2 clock cycles later. Work with this limitation of the DP_RAM megafunction and I hope he sees the non-utilitarian, and non-necessity error in his approach.Quote from: SiliconWizard on April 01, 2022, 06:20:55 pmI'm sure we are looking at a center snipit of code which is sitting inside a larger section of code with an always@(posedge clk). Otherwise, he has ignored my teachings.

author: kpow8050
author_rank: Regular Contributor
timestamp: April 02, 2022, 05:03:52 am
content: Thanks guys. It looks like I made a lot of mistakes. I will attempt to use the LPM_DP_RAM megafunction instead and see if I can get that working. Because it has read and write ports, does this mean that I can write to the RAM while another process reads from it? What if I want to write all the data to RAM first and then only once that is complete update the RAM. Essentially I need 2 RAMs, one is the buffer because the data being written will be slow and the other for the fast output, which is only updated once the buffer RAM is filled. Is there an easy way to implement this with the megafunction?

author: BrianHG
author_rank: Super Contributor
timestamp: April 02, 2022, 05:17:54 am
content: Quote from: kpow8050 on April 02, 2022, 05:03:52 amYou can read and write to the same memory at different addresses simultaneously, all at your system clock speed. That's why it is called a dual-port ram.(Now I hope your MCU interface is operating on your 25MHz clock as well....)All you need is 1 ram which is large enough.Write in one area while showing another.You have your laser program sequencer which can swap the MSB read address to you MCU controlled setting once at the trigger reset point.I hope you didn't engineer a gigantic mess of your sequencer. The 'said' line playback should be a single binary up counter added to the countdown timer while adding a start read address in the reset timer section.

author: kpow8050
author_rank: Regular Contributor
timestamp: April 02, 2022, 08:35:15 am
content: Ahh, good point. I'll just use a single RAM divided into 2 sections (lower and upper). I'm working on the code now, will post soon - probably won't be a perfect implementation but still learning...

author: kpow8050
author_rank: Regular Contributor
timestamp: April 05, 2022, 07:45:08 am
content: It seems that ModelSim cannot run when I attempt to simulate my design after adding a RAM MegaFunction to my project. The project compiles fine in Quartus II however after finding it won't run on my FPGA I decided to simulate it and found that ModelSim also fails. Any ideas why this might be? I have posted the error message below

author: BrianHG
author_rank: Super Contributor
timestamp: April 05, 2022, 02:25:42 pm
content: Quote from: kpow8050 on April 05, 2022, 07:45:08 amOk, did you copy and paste the right function into your code?Note that there were 2 .v files made when you generated your megafunction. One only calls the other .v file. If you went this route, then you need to add those .v files into you simulator .do files to include them.If is easier to just copy the 'altsyncram' (this is what altera now calls the dual port ram and should work in quartus 13.0) instantiation into your source file and wire the ports.EG:Code:// megafunction wizard: %RAM: 2-PORT%// GENERATION: STANDARD// VERSION: WM1.0// MODULE: altsyncram// ============================================================// File Name: LineBuffer.v// Megafunction Name(s)://altsyncram//// Simulation Library Files(s)://altera_mf// ============================================================// ************************************************************// THIS IS A WIZARD-GENERATED FILE. DO NOT EDIT THIS FILE!//// 13.0.1 Build 232 06/12/2013 SP 1 SJ Web Edition// ************************************************************//Copyright (C) 1991-2013 Altera Corporation//Your use of Altera Corporation's design tools, logic functions//and other software and tools, and its AMPP partner logic//functions, and any output files from any of the foregoing//(including device programming or simulation files), and any//associated documentation or information are expressly subject//to the terms and conditions of the Altera Program License//Subscription Agreement, Altera MegaCore Function License//Agreement, or other applicable license agreement, including,//without limitation, that your use is for the sole purpose of//programming logic devices manufactured by Altera and sold by//Altera or its authorized distributors. Please refer to the//applicable agreement for further details.// synopsys translate_off`timescale 1 ps / 1 ps// synopsys translate_onmodule LineBuffer (data,rdaddress,rdclock,wraddress,wrclock,wren,q);input[127:0] data;input[11:0] rdaddress;inputrdclock;input[9:0] wraddress;inputwrclock;inputwren;output[31:0] q;`ifndef ALTERA_RESERVED_QIS// synopsys translate_off`endiftri1wrclock;tri0wren;`ifndef ALTERA_RESERVED_QIS// synopsys translate_on`endifwire [31:0] sub_wire0;wire [31:0] q = sub_wire0[31:0];altsyncramaltsyncram_component (.address_a (wraddress),.clock0 (wrclock),.data_a (data),.wren_a (wren),.address_b (rdaddress),.clock1 (rdclock),.q_b (sub_wire0),.aclr0 (1'b0),.aclr1 (1'b0),.addressstall_a (1'b0),.addressstall_b (1'b0),.byteena_a (1'b1),.byteena_b (1'b1),.clocken0 (1'b1),.clocken1 (1'b1),.clocken2 (1'b1),.clocken3 (1'b1),.data_b ({32{1'b1}}),.eccstatus (),.q_a (),.rden_a (1'b1),.rden_b (1'b1),.wren_b (1'b0));defparamaltsyncram_component.address_aclr_b = "NONE",altsyncram_component.address_reg_b = "CLOCK1",altsyncram_component.clock_enable_input_a = "BYPASS",altsyncram_component.clock_enable_input_b = "BYPASS",altsyncram_component.clock_enable_output_b = "BYPASS",altsyncram_component.init_file = "",altsyncram_component.init_file_layout = "PORT_B",altsyncram_component.intended_device_family = "Cyclone III",altsyncram_component.lpm_type = "altsyncram",altsyncram_component.numwords_a = 1024,altsyncram_component.numwords_b = 4096,altsyncram_component.operation_mode = "DUAL_PORT",altsyncram_component.outdata_aclr_b = "NONE",altsyncram_component.outdata_reg_b = "CLOCK1",altsyncram_component.power_up_uninitialized = "FALSE",altsyncram_component.widthad_a = 10,altsyncram_component.widthad_b = 12,altsyncram_component.width_a = 128,altsyncram_component.width_b = 32,altsyncram_component.width_byteena_a = 1;endmodule// ============================================================// CNX file retrieval info// ============================================================// Retrieval info: PRIVATE: ADDRESSSTALL_A NUMERIC "0"// Retrieval info: PRIVATE: ADDRESSSTALL_B NUMERIC "0"// Retrieval info: PRIVATE: BYTEENA_ACLR_A NUMERIC "0"// Retrieval info: PRIVATE: BYTEENA_ACLR_B NUMERIC "0"// Retrieval info: PRIVATE: BYTE_ENABLE_A NUMERIC "0"// Retrieval info: PRIVATE: BYTE_ENABLE_B NUMERIC "0"// Retrieval info: PRIVATE: BYTE_SIZE NUMERIC "8"// Retrieval info: PRIVATE: BlankMemory NUMERIC "0"// Retrieval info: PRIVATE: CLOCK_ENABLE_INPUT_A NUMERIC "0"// Retrieval info: PRIVATE: CLOCK_ENABLE_INPUT_B NUMERIC "0"// Retrieval info: PRIVATE: CLOCK_ENABLE_OUTPUT_A NUMERIC "0"// Retrieval info: PRIVATE: CLOCK_ENABLE_OUTPUT_B NUMERIC "0"// Retrieval info: PRIVATE: CLRdata NUMERIC "0"// Retrieval info: PRIVATE: CLRq NUMERIC "0"// Retrieval info: PRIVATE: CLRrdaddress NUMERIC "0"// Retrieval info: PRIVATE: CLRrren NUMERIC "0"// Retrieval info: PRIVATE: CLRwraddress NUMERIC "0"// Retrieval info: PRIVATE: CLRwren NUMERIC "0"// Retrieval info: PRIVATE: Clock NUMERIC "1"// Retrieval info: PRIVATE: Clock_A NUMERIC "0"// Retrieval info: PRIVATE: Clock_B NUMERIC "0"// Retrieval info: PRIVATE: IMPLEMENT_IN_LES NUMERIC "0"// Retrieval info: PRIVATE: INDATA_ACLR_B NUMERIC "0"// Retrieval info: PRIVATE: INDATA_REG_B NUMERIC "0"// Retrieval info: PRIVATE: INIT_FILE_LAYOUT STRING "PORT_B"// Retrieval info: PRIVATE: INIT_TO_SIM_X NUMERIC "0"// Retrieval info: PRIVATE: INTENDED_DEVICE_FAMILY STRING "Cyclone IV GX"// Retrieval info: PRIVATE: JTAG_ENABLED NUMERIC "0"// Retrieval info: PRIVATE: JTAG_ID STRING "NONE"// Retrieval info: PRIVATE: MAXIMUM_DEPTH NUMERIC "0"// Retrieval info: PRIVATE: MEMSIZE NUMERIC "131072"// Retrieval info: PRIVATE: MEM_IN_BITS NUMERIC "0"// Retrieval info: PRIVATE: MIFfilename STRING "line_buf_init.mif"// Retrieval info: PRIVATE: OPERATION_MODE NUMERIC "2"// Retrieval info: PRIVATE: OUTDATA_ACLR_B NUMERIC "0"// Retrieval info: PRIVATE: OUTDATA_REG_B NUMERIC "1"// Retrieval info: PRIVATE: RAM_BLOCK_TYPE NUMERIC "0"// Retrieval info: PRIVATE: READ_DURING_WRITE_MODE_MIXED_PORTS NUMERIC "2"// Retrieval info: PRIVATE: READ_DURING_WRITE_MODE_PORT_A NUMERIC "3"// Retrieval info: PRIVATE: READ_DURING_WRITE_MODE_PORT_B NUMERIC "3"// Retrieval info: PRIVATE: REGdata NUMERIC "1"// Retrieval info: PRIVATE: REGq NUMERIC "0"// Retrieval info: PRIVATE: REGrdaddress NUMERIC "1"// Retrieval info: PRIVATE: REGrren NUMERIC "1"// Retrieval info: PRIVATE: REGwraddress NUMERIC "1"// Retrieval info: PRIVATE: REGwren NUMERIC "1"// Retrieval info: PRIVATE: SYNTH_WRAPPER_GEN_POSTFIX STRING "0"// Retrieval info: PRIVATE: USE_DIFF_CLKEN NUMERIC "0"// Retrieval info: PRIVATE: UseDPRAM NUMERIC "1"// Retrieval info: PRIVATE: VarWidth NUMERIC "1"// Retrieval info: PRIVATE: WIDTH_READ_A NUMERIC "128"// Retrieval info: PRIVATE: WIDTH_READ_B NUMERIC "32"// Retrieval info: PRIVATE: WIDTH_WRITE_A NUMERIC "128"// Retrieval info: PRIVATE: WIDTH_WRITE_B NUMERIC "32"// Retrieval info: PRIVATE: WRADDR_ACLR_B NUMERIC "0"// Retrieval info: PRIVATE: WRADDR_REG_B NUMERIC "0"// Retrieval info: PRIVATE: WRCTRL_ACLR_B NUMERIC "0"// Retrieval info: PRIVATE: enable NUMERIC "0"// Retrieval info: PRIVATE: rden NUMERIC "0"// Retrieval info: LIBRARY: altera_mf altera_mf.altera_mf_components.all// Retrieval info: CONSTANT: ADDRESS_ACLR_B STRING "NONE"// Retrieval info: CONSTANT: ADDRESS_REG_B STRING "CLOCK1"// Retrieval info: CONSTANT: CLOCK_ENABLE_INPUT_A STRING "BYPASS"// Retrieval info: CONSTANT: CLOCK_ENABLE_INPUT_B STRING "BYPASS"// Retrieval info: CONSTANT: CLOCK_ENABLE_OUTPUT_B STRING "BYPASS"// Retrieval info: CONSTANT: INIT_FILE STRING "line_buf_init.mif"// Retrieval info: CONSTANT: INIT_FILE_LAYOUT STRING "PORT_B"// Retrieval info: CONSTANT: INTENDED_DEVICE_FAMILY STRING "Cyclone IV GX"// Retrieval info: CONSTANT: LPM_TYPE STRING "altsyncram"// Retrieval info: CONSTANT: NUMWORDS_A NUMERIC "1024"// Retrieval info: CONSTANT: NUMWORDS_B NUMERIC "4096"// Retrieval info: CONSTANT: OPERATION_MODE STRING "DUAL_PORT"// Retrieval info: CONSTANT: OUTDATA_ACLR_B STRING "NONE"// Retrieval info: CONSTANT: OUTDATA_REG_B STRING "CLOCK1"// Retrieval info: CONSTANT: POWER_UP_UNINITIALIZED STRING "FALSE"// Retrieval info: CONSTANT: WIDTHAD_A NUMERIC "10"// Retrieval info: CONSTANT: WIDTHAD_B NUMERIC "12"// Retrieval info: CONSTANT: WIDTH_A NUMERIC "128"// Retrieval info: CONSTANT: WIDTH_B NUMERIC "32"// Retrieval info: CONSTANT: WIDTH_BYTEENA_A NUMERIC "1"// Retrieval info: USED_PORT: data 0 0 128 0 INPUT NODEFVAL "data[127..0]"// Retrieval info: USED_PORT: q 0 0 32 0 OUTPUT NODEFVAL "q[31..0]"// Retrieval info: USED_PORT: rdaddress 0 0 12 0 INPUT NODEFVAL "rdaddress[11..0]"// Retrieval info: USED_PORT: rdclock 0 0 0 0 INPUT NODEFVAL "rdclock"// Retrieval info: USED_PORT: wraddress 0 0 10 0 INPUT NODEFVAL "wraddress[9..0]"// Retrieval info: USED_PORT: wrclock 0 0 0 0 INPUT VCC "wrclock"// Retrieval info: USED_PORT: wren 0 0 0 0 INPUT GND "wren"// Retrieval info: CONNECT: @address_a 0 0 10 0 wraddress 0 0 10 0// Retrieval info: CONNECT: @address_b 0 0 12 0 rdaddress 0 0 12 0// Retrieval info: CONNECT: @clock0 0 0 0 0 wrclock 0 0 0 0// Retrieval info: CONNECT: @clock1 0 0 0 0 rdclock 0 0 0 0// Retrieval info: CONNECT: @data_a 0 0 128 0 data 0 0 128 0// Retrieval info: CONNECT: @wren_a 0 0 0 0 wren 0 0 0 0// Retrieval info: CONNECT: q 0 0 32 0 @q_b 0 0 32 0// Retrieval info: GEN_FILE: TYPE_NORMAL LineBuffer.v TRUE// Retrieval info: GEN_FILE: TYPE_NORMAL LineBuffer.inc FALSE// Retrieval info: GEN_FILE: TYPE_NORMAL LineBuffer.cmp FALSE// Retrieval info: GEN_FILE: TYPE_NORMAL LineBuffer.bsf TRUE// Retrieval info: GEN_FILE: TYPE_NORMAL LineBuffer_inst.v FALSE// Retrieval info: GEN_FILE: TYPE_NORMAL LineBuffer_bb.v TRUE// Retrieval info: LIB_FILE: altera_mfYou only need lines 67 to 111 in the above example from one of my projects. You should see this inside the xxxxx.v generated when you ran your megafunction wizard. and it should have been filled out with the correct parameters for your application.EG, the only lines you should copy and paste into your code:Code:altsyncramaltsyncram_component (.address_a (wraddress),.clock0 (wrclock),.data_a (data),.wren_a (wren),.address_b (rdaddress),.clock1 (rdclock),.q_b (sub_wire0),.aclr0 (1'b0),.aclr1 (1'b0),.addressstall_a (1'b0),.addressstall_b (1'b0),.byteena_a (1'b1),.byteena_b (1'b1),.clocken0 (1'b1),.clocken1 (1'b1),.clocken2 (1'b1),.clocken3 (1'b1),.data_b ({32{1'b1}}),.eccstatus (),.q_a (),.rden_a (1'b1),.rden_b (1'b1),.wren_b (1'b0));defparamaltsyncram_component.address_aclr_b = "NONE",altsyncram_component.address_reg_b = "CLOCK1",altsyncram_component.clock_enable_input_a = "BYPASS",altsyncram_component.clock_enable_input_b = "BYPASS",altsyncram_component.clock_enable_output_b = "BYPASS",altsyncram_component.init_file = "",altsyncram_component.init_file_layout = "PORT_B",altsyncram_component.intended_device_family = "Cyclone III",altsyncram_component.lpm_type = "altsyncram",altsyncram_component.numwords_a = 1024,altsyncram_component.numwords_b = 4096,altsyncram_component.operation_mode = "DUAL_PORT",altsyncram_component.outdata_aclr_b = "NONE",altsyncram_component.outdata_reg_b = "CLOCK1",altsyncram_component.power_up_uninitialized = "FALSE",altsyncram_component.widthad_a = 10,altsyncram_component.widthad_b = 12,altsyncram_component.width_a = 128,altsyncram_component.width_b = 32,altsyncram_component.width_byteena_a = 1;