0
\$\begingroup\$

From the 7 Series Memory Resources User Guide (page 11):

The block RAM in Xilinx® 7 series FPGAs stores up to 36 Kbits of data and can be configured as either two independent 18 Kb RAMs, or one 36 Kb RAM. Each 36 Kb block RAM can be configured as a 64K x 1 (when cascaded with an adjacent 36 Kb block RAM), 32K x 1, 16K x 2, 8K x 4, 4K x 9, 2K x 18, 1K x 36, or 512 x 72 in simple dual-port mode. Each 18 Kb block RAM can be configured as a 16K x 1, 8K x2 , 4K x 4, 2K x 9, 1K x 18 or 512 x 36 in simple dual-port mode.

I have some confusion about inferring the dual port block RAM using HDL. From my understanding if I want to use dual port mode, there are two 18 Kb RAMs to do so. If my data width is 16-bits, I need to use one of the 1K x 18 or 512 x 36 modes.

What happens if I create a Verilog module who has an output port that is larger than 36-bits? For example, I'd ideally like to read as big as possible of a chunk of data from BRAM at a time (same with writes), as quickly as possible. If I created a port that was 16-bits x 16 data wide (256 bits), what hardware would be inferred (Verilog example below)? Would it just happen over multiple clock cycles?

Also is it good design practice to wrap the reads around the end (back to the beginning of the BRAM) when the address + offset is greater than the depth of the BRAM?

module BRAM #(parameter DATA_WIDTH=16, DEPTH=8, N_READ_ELS=16) (
  input                             Clk,
                                    Reset,
                                    WriteEn,
  input [DATA_WIDTH-1:0]            DataIn, 
  input [DEPTH-1:0]                 AddressIn,          // 2^DEPTH = number of elements
  output logic [DATA_WIDTH*N_READ_ELS-1:0]     DataOut);// [N_READ_ELS]);
  
  // Main storage
  logic [DATA_WIDTH-1:0] Data[2**DEPTH];

  // Combinational reads
  integer i, SHAMT;
  always_comb begin
    for(i = 0; i < N_READ_ELS; i = i + 1) begin
      // if address + offset is outside address range, wrap around to beginning
      if (AddressIn + i >= 2**DEPTH)
        SHAMT = 2**DEPTH;
      else
        SHAMT = 0;
      DataOut[(i+1)*DATA_WIDTH-1 -: DATA_WIDTH] = Data[AddressIn+i-SHAMT];
    end
  end

  // Clocked writes
  always_ff @ (posedge Clk)
  if (Reset) begin
    Data = '{default:'b0};
  end else if (WriteEn) 
      Data[AddressIn] <= DataIn;

endmodule
\$\endgroup\$
4
  • \$\begingroup\$ Why don't you try it and find out? \$\endgroup\$ – user253751 Dec 29 '20 at 18:43
  • \$\begingroup\$ Synth your module alone and check the synthesis reports. It may use both ports of a dual port module in parallel to increase width, or it may instantiate multiple BRAMs. \$\endgroup\$ – Brian Drummond Dec 29 '20 at 18:44
  • \$\begingroup\$ My assumption is that if the tool can't infer what you want, it will default to distributed RAM. For this reason, I hate inferring block RAM. Just add a block RAM primitive and design the circuit around its limitations, that way you're guaranteed to get a block RAM and be aware of its limitations. \$\endgroup\$ – user253751 Dec 29 '20 at 18:47
  • \$\begingroup\$ You'll end up inferring 8 BRAMs each 32-bit wide, connected in parallel (address lines connected together, data lines concatenated). \$\endgroup\$ – Tom Carpenter Dec 29 '20 at 20:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.