Search code examples
verilogramxilinxhdl

Xilinx:Reading from BRAM


I have attemted to write a Minimal, Complete, and Verifiable example below.

I want to write 10 values to the first 10 addresses of the BRAM (single port Block RAM) and then read the values. After inspecting the results I find that

  • there are no changes in the first 10 addresses while perfoming the write operation.
  • While reading, the output changes after 3 clock cycles and stays constant when the 'address' signal stops changing.

Can you give an explanation to this behaviour and how to get the desired result (write 10 values in the 10 addresses). I'm more interested in solving the second problem (reading the values from the first 10 addresses).

Below is my verilog testbench and snapshot of the waveguide.

module BRAM_tb;
    // Inputs
    reg clk;
    reg [3:0] wea;                  // write enable signal
    reg [31:0] addra;               // address
    reg signed [31:0] dina;     // data in

    // Outputs
    wire [31:0] douta;          // data out

    // Instantiate the Unit Under Test (UUT)
    BLOCK_MEM uut (
        .clka(clk), 
        .wea(wea), 
        .addra(addra), 
        .dina(dina), 
        .douta(douta)
    );

    always begin
        #15 clk =~clk;
    end

    task writeStuff;    //write to address
        begin
            addra <= addra + 1;
            dina <= dina+1;
        end
    endtask

    task readStuff; // read the at address
        begin
            addra <= addra + 1;
        end
    endtask

    reg [1:0] writing;
    integer counter;
    initial begin
        // Initialize Inputs
        clk = 0;
        addra = 0;
        dina = 16;
        counter = 0;
        writing = 2'b10; //idle state
        // Wait 100 ns for global reset to finish
        #100;
        wea <= 1;
        writing <=1;
    end

    always @(posedge clk)begin
        case(writing)
            1: if(counter<10) begin
                    writeStuff;
                    counter <=counter+1;
                end else begin
                    writing <=0;    // change state to reading
                    counter <=0;
                    addra <= 0;
                    wea <=0;    // stop writing
                end
            0:  if(counter<10) begin
                    readStuff;
                    counter <=counter+1;
                end else begin // change addra to zero and do nothing
                    addra <= 0;
                    writing <=2'b10; //goto idle state
                end
            2: if(1) begin
                    //do nothing
                end
        endcase
    end

BRAM - The gray line is where the write operation begins. The blue line is where the read operation begins.

BRAM_2

BLOCK_MEM is an IP-CORE that is generated by Xilinx.


Solution

  • I'm going to take some guesses as to how you've configured your BRAM (I'm using Vivado 2015.4 and http://www.xilinx.com/support/documentation/ip_documentation/blk_mem_gen/v8_3/pg058-blk-mem-gen.pdf as references). Looks like you've opted for the always-enable (since the ENA signal isn't present), 32-bit data, and the 32-bit address interface. Note that if you use the 32-bit address interface, WEA changes from a 1-bit signal to a 4-bit signal. This is to allow for byte-addressable writes.

    Given this, we know that for a wea of 0b0001, only the least-significant byte will be written. Also, from the timing diagram on pg 46 of the above guide, we know we can expect the written data to be available after the write on douta. We can verify this on your first image - for example, at 200ns, douta = 0xfff75c13, with the 0x13 byte having come from dina on the previous clock edge (the other bytes are what was previously in the memory). So, this confirms the writes are working as expected.

    As for the reads in image two, if you count again, you'll see that douta changes every 4 clock cycles. Again, remember that the memory is addressed per byte, but you're returning 4 bytes, so the lowest two bits of the address are ignored (address 0x07 == 0x06 == 0x05 == 0x04).

    In short, the BRAM is working as intended, which might not be the way you expected. To move to the next 32-bit/4-byte word in memory, you need to increment the address by 4, not by 1. To write the whole word, (not just the lowest byte) set wea='b1111.