9

I am having trouble initializing the contents of an inferred ram in Verilog. The code for the ram is as below:

module ram(
        input clock, // System clock
        input we, // When high RAM sets data in input lines to given address
        input [13:0] data_in, // Data lines to write to memory
        input [10:0] addr_in, // Address lines for saving data to memory
        input [10:0] addr_out, // Address for reading from ram
        output reg data_out // Data out
);

reg [13:0] ram[2047:0];

// Initialize RAM from file
// WHAT SHOULD GO HERE?

always @(posedge clock) begin
    // Save data to RAM
    if (we) begin
        ram[addr_in] <= data_in;
    end

    // Place data from RAM
    data_out <= ram[addr_out];
end        
endmodule

I have run into the command $readmemh. However, documentation for it seems sparse. How should I format the file that contains the data? Also, how can I pass the file as argument when instantiating this module so that I can have different instances of this module load from different files?

I want the initialized content to be available for both simulation and actual implementation. So that the FPGA already boots with this content in RAM.

I am using Vivado 2015.4 to program a Kintex xc7k70 FPGA.

Paulo C
  • 374
  • 1
  • 5
  • 11

4 Answers4

13

You are correct that you should use $readmemh inside an initial block. In order to make it so different instances of the module can have different initialization files, you should use a parameter like so:

parameter MEM_INIT_FILE = "";
...
initial begin
  if (MEM_INIT_FILE != "") begin
    $readmemh(MEM_INIT_FILE, ram);
  end
end

The format is described in Section 21.4 of the IEEE1800-2012 specification; typically the file is just a bunch of lines containing hex numbers of the correct bit-length, like so:

0001
1234
3FFF
1B34
...

Note that there is no "0x" prefix and each line represents an adjacent address (or any separating whitespace). In the example above, $readmemh would put 14'h0001 into ram[0], 14'h1234 into ram[1], 14'h3FFF into ram[2] and so on. You can also include comments in the hex file using // or /* */. Finally, you can use the @ symbol to designate an address for the following numbers to be located at, like so:

@0002
0101
0A0A
...

In the above file, ram[0] and ram[1] would be uninitialized and ram[2] would get 14'h0101. Those are all the major constructs of the hex file format, though you can also use _, x and z as you would in other Verilog numbers and theres a few more rules you can read in the section sited above.

Unn
  • 4,775
  • 18
  • 30
  • any way to confirm the same after synthesis or implementation? – tollin jose Apr 23 '18 at 08:56
  • @tollinjose Not sure I know of a good way to make sure the contents of the HEX file actually made it into the RAM without testing it directly in post-synthesis simulator or running it on an FPGA or something like that. Depending on the synthesis tool, you can try to look through its resources (ie, any RTL display tools or output files) to see if that data in your HEX file made it into the appreciate place. – Unn Apr 23 '18 at 18:57
6

Apart from @Unn's excellent ans, I want to add that, If you just want to initialize your memory with either all bits to 1'b1 or 1'b0, then you can just put following code,

integer j;
initial 
  for(j = 0; j < DEPTH; j = j+1) 
    ram[j] = {WIDTH{MEM_INIT_VAL}};

For your case, WIDTH=14, and MEM_INIT_VAL may be 1'b1 or 1'b0.

Prakash Darji
  • 990
  • 6
  • 13
  • will this initialise the inferred bram in an implemented design or only on simulation? – Mah35h Apr 03 '21 at 05:28
  • @Mah35h mentioned initial block is synthesizable so it will work for both inferred implemented ram as well as for simulation. – Prakash Darji Apr 03 '21 at 05:39
0

Since your question cited the #xilinx and #vivado tags, I wanted to suggest that you can also use the xpm_memory family of primitives to instantiate a parameterized memory. The advantages of this approach:

  1. Exports exactly the hardware capabilities of the memory resources on the FPGA (ie, makes you think clearly about limitations such as memory ports).

  2. Guarantees correct identical behavior in simulation and benchtop for memory primitives.

  3. You can allow Vivado to choose the most efficient memory implementation (BRAM, UltraRAM, distributed RAM, flops) at synthesis time, according to your design constraints.

  4. Easy to fine tune (enable or disable internal pipeline stages, etc.).

With that said, purely inferred memories are often easier to code. But, it's still worth getting familiar with the Xilinx-provided memory primitives so that you'll have a clearer idea of what Vivado can easily synthesize, and what it can't.

For more information, see UG573, the Vivado Memory Resources User Guide:

https://www.xilinx.com/support/documentation/user_guides/ug573-ultrascale-memory-resources.pdf

Jonathan Mayer
  • 1,432
  • 13
  • 17
  • For vivado look at the language templates, specifically, the Examples Modules -> RAM to see exampl verilog or VHDL code that infers the desired hardware with all the advantages of avoiding xilinx specific macros – Edward Kigwana Jan 11 '21 at 04:48
-3
integer j;
initial 
  for(j = 0; j < DEPTH; j = j+1) 
    ram[j] = j;

This might be easy in case of debug, where the value of a location is its location number.

Also, I would suggest you to not initialize the RAMs. It will help you in catching bugs, if any, in simulation as the data driven will be 'x if RAM is un-intialized and can be caught easily.

Sourabh
  • 634
  • 5
  • 12