Skip to content

nd_memtest_bram

Source: Verilog/fpga/nexys4ddr/sd-fat-test/nd_memtest_bram.v

Hierarchy: not instantiated by any of the 9 build tops (elaborated by yosys).

Module hierarchy - All modules

nd_memtest_bram symbol

Schematic

Drawn from the Verilog: no build top uses this module, so it was elaborated from its own file with no defines and default parameters. Sub-modules are boxes (click the picture to open it full size; there every sub-module box links to its page, and every wire shows its Verilog name).

nd_memtest_bram schematic

Description

ND-120 memory-path test as a character stream Same test as fpga/basys3/mem-test/basys3_mem_test_top.v - it isolates MEM_RAM_49 (-> SIP1M9 sync BRAM, ramSize=3) from the whole CPU and drives the EXACT DRAM RAS/CAS/AA protocol the real controller uses (measured via DBG_MEM: row valid at RAS fall, AA -> column while CAS is still high, both strobes low a few cycles, read data captured while RAS is deasserted and CAS still low), then writes, reads back and verifies a set of addresses. The difference from the Basys3 version is only the OUTPUT: that one owns a UART, this one emits bytes through a tx_data/tx_valid/tx_busy handshake so it can be a command inside the SD-FAT test menu and share that design's UART. Same vectors as the Basys3 run, so the two boards are directly comparable: if this PASSES the ND-120 BRAM memory path is sound and a memory fault lives in the CPU/MAC integration; if it FAILS the fault is here, where it can be iterated on without the CPU in the way. Report (one line per vector, then a verdict): NDMEM START ND 00000 W A5 R A5 OK ... NDMEM PASS (or NDMEM FAIL n) Last reviewed: 20-AUG-2026 Ronny Hansen

Ports

Direction Width Name Description
input 1 clk
input 1 rst_n (active low)
input 1 start 1-cycle pulse
output 1 busy
output [7:0] tx_data
output 1 tx_valid
input 1 tx_busy
output 1 fail

Verilog source

Verilog/fpga/nexys4ddr/sd-fat-test/nd_memtest_bram.v on GitHub.

Show the Verilog of nd_memtest_bram (292 lines)
/****************************************************************************
** ND-120 memory-path test as a character stream                           **
**                                                                         **
** Same test as fpga/basys3/mem-test/basys3_mem_test_top.v - it isolates   **
** MEM_RAM_49 (-> SIP1M9 sync BRAM, ramSize=3) from the whole CPU and      **
** drives the EXACT DRAM RAS/CAS/AA protocol the real controller uses      **
** (measured via DBG_MEM: row valid at RAS fall, AA -> column while CAS is **
** still high, both strobes low a few cycles, read data captured while RAS **
** is deasserted and CAS still low), then writes, reads back and verifies  **
** a set of addresses.                                                     **
**                                                                         **
** The difference from the Basys3 version is only the OUTPUT: that one     **
** owns a UART, this one emits bytes through a tx_data/tx_valid/tx_busy    **
** handshake so it can be a command inside the SD-FAT test menu and share  **
** that design's UART.                                                     **
**                                                                         **
** Same vectors as the Basys3 run, so the two boards are directly          **
** comparable: if this PASSES the ND-120 BRAM memory path is sound and a   **
** memory fault lives in the CPU/MAC integration; if it FAILS the fault is **
** here, where it can be iterated on without the CPU in the way.           **
**                                                                         **
** Report (one line per vector, then a verdict):                           **
**   NDMEM START                                                           **
**   ND 00000 W A5 R A5 OK                                                 **
**   ...                                                                   **
**   NDMEM PASS   (or NDMEM FAIL n)                                        **
**                                                                         **
** Last reviewed: 20-AUG-2026                                              **
** Ronny Hansen                                                            **
*****************************************************************************/
`default_nettype none

module nd_memtest_bram (
    input wire clk,
    input wire rst_n,

    input  wire start,  // 1-cycle pulse
    output wire busy,

    // character output, arbitrated by the caller
    output reg [7:0] tx_data,
    output reg       tx_valid,
    input  wire      tx_busy,

    output reg fail
);

  /*******************************************************************
   *  The sheet-49 RAM under test
   *******************************************************************/
  reg  [ 9:0] aa;
  reg         ras, cas, bank0, mwrite_n;
  reg  [17:0] dd_in;
  wire [17:0] dd_out;
  wire        corr_n;

  MEM_RAM_49 ram (
      .sysclk(clk),
      .sys_rst_n(rst_n),
      .AA_9_0(aa),
      .BANK0(bank0),
      .BANK1(1'b0),
      .BANK2(1'b0),
      .CAS(cas),
      .RAS(ras),
      .MWRITE50_n(mwrite_n),
      .DD_17_0_IN(dd_in),
      .DD_17_0_OUT(dd_out),
      .CORR_n(corr_n)
  );

  /*******************************************************************
   *  Test vectors - identical to the Basys3 mem-test
   *******************************************************************/
  reg [19:0] t_addr;
  reg [ 7:0] t_data;
  reg [ 2:0] tidx;
  always @(*) begin
    case (tidx)
      3'd0: begin t_addr = 20'h00000; t_data = 8'hA5; end
      3'd1: begin t_addr = 20'h00001; t_data = 8'h5A; end
      3'd2: begin t_addr = 20'h00002; t_data = 8'h3C; end
      3'd3: begin t_addr = 20'h00004; t_data = 8'hC3; end  // old model aliased 0<->4
      3'd4: begin t_addr = 20'h00010; t_data = 8'hFF; end
      3'd5: begin t_addr = 20'h00100; t_data = 8'h11; end
      3'd6: begin t_addr = 20'h000FF; t_data = 8'h77; end
      3'd7: begin t_addr = 20'h003FF; t_data = 8'h42; end
    endcase
  end
  wire [9:0] row = t_addr[9:0];
  wire [9:0] col = t_addr[19:10];

  /*******************************************************************
   *  Line assembler: build up to 32 characters, then shift them out
   *******************************************************************/
  reg [7:0] line[0:31];
  reg [5:0] line_len;
  reg [5:0] line_ptr;
  reg       emitting;

  function [7:0] hexch;
    input [3:0] v;
    begin
      hexch = (v < 4'd10) ? (8'h30 + {4'b0, v}) : (8'h41 + {4'b0, (v - 4'd10)});
    end
  endfunction

  /*******************************************************************
   *  Main FSM
   *******************************************************************/
  localparam S_IDLE   = 4'd0;
  localparam S_BANNER = 4'd1;
  localparam S_WR     = 4'd2;
  localparam S_RD     = 4'd3;
  localparam S_LINE   = 4'd4;
  localparam S_EMIT   = 4'd5;
  localparam S_NEXT   = 4'd6;
  localparam S_RESULT = 4'd7;
  localparam S_END    = 4'd8;

  reg [3:0] state;
  reg [3:0] d;  // DRAM sub-cycle 0..6
  reg [7:0] rd_data;
  reg [2:0] nfail;
  reg [3:0] after_emit;

  assign busy = (state != S_IDLE);

  // DRAM protocol driven by sub-cycle d (each = one clk), exactly as measured.
  // Non-blocking throughout (the Basys3 original mixes blocking and
  // non-blocking, which Verilator rejects outright): each arm writes the
  // defaults first and then overrides, and the last non-blocking write to a
  // signal in a timestep is the one that lands, so the behaviour is the same.
  task drive_dram(input is_write);
    begin
      ras <= 1'b0; cas <= 1'b0; bank0 <= 1'b0; mwrite_n <= 1'b1;
      aa <= row; dd_in <= {10'b0, t_data};
      case (d)
        4'd0: begin ras <= 1; bank0 <= 1; aa <= row; mwrite_n <= is_write?1'b0:1'b1; end  // RAS fall, row
        4'd1: begin ras <= 1; bank0 <= 1; aa <= col; mwrite_n <= is_write?1'b0:1'b1; end  // AA -> column
        4'd2: begin ras <= 1; bank0 <= 1; cas <= 1; aa <= col; mwrite_n <= is_write?1'b0:1'b1; end  // CAS fall
        4'd3: begin ras <= 1; bank0 <= 1; cas <= 1; aa <= col; mwrite_n <= is_write?1'b0:1'b1; end  // both low
        4'd4: begin ras <= 1; bank0 <= 1; cas <= 1; aa <= col; mwrite_n <= is_write?1'b0:1'b1; end  // both low
        4'd5: begin ras <= 0; bank0 <= 1; cas <= 1; aa <= col; mwrite_n <= 1'b1; end          // read window
        default: begin ras <= 0; cas <= 0; bank0 <= 0; end                             // precharge
      endcase
    end
  endtask

  integer i;

  always @(posedge clk) begin
    if (!rst_n) begin
      state    <= S_IDLE;
      d        <= 4'd0;
      tidx     <= 3'd0;
      fail     <= 1'b0;
      nfail    <= 3'd0;
      tx_valid <= 1'b0;
      emitting <= 1'b0;
      line_len <= 6'd0;
      line_ptr <= 6'd0;
      rd_data  <= 8'd0;
      ras      <= 1'b0;
      cas      <= 1'b0;
      bank0    <= 1'b0;
      mwrite_n <= 1'b1;
      aa       <= 10'd0;
      dd_in    <= 18'd0;
    end else begin
      tx_valid <= 1'b0;

      case (state)
        S_IDLE:
        if (start) begin
          fail  <= 1'b0;
          nfail <= 3'd0;
          tidx  <= 3'd0;
          d     <= 4'd0;
          state <= S_BANNER;
        end

        // "NDMEM START" CR LF
        S_BANNER: begin
          line[0]  <= "N"; line[1] <= "D"; line[2] <= "M"; line[3] <= "E";
          line[4]  <= "M"; line[5] <= " "; line[6] <= "S"; line[7] <= "T";
          line[8]  <= "A"; line[9] <= "R"; line[10] <= "T";
          line[11] <= 8'h0D; line[12] <= 8'h0A;
          line_len <= 6'd13;
          line_ptr <= 6'd0;
          after_emit <= S_WR;
          d <= 4'd0;
          state <= S_EMIT;
        end

        S_WR: begin
          drive_dram(1'b1);
          if (d == 4'd6) begin d <= 4'd0; state <= S_RD; end
          else d <= d + 4'd1;
        end

        S_RD: begin
          drive_dram(1'b0);
          if (d == 4'd5) rd_data <= dd_out[7:0];  // capture in the read window
          if (d == 4'd6) begin d <= 4'd0; state <= S_LINE; end
          else d <= d + 4'd1;
        end

        // "ND aaaaa W dd R dd OK" CR LF
        S_LINE: begin
          line[0]  <= "N"; line[1] <= "D"; line[2] <= " ";
          line[3]  <= hexch(t_addr[19:16]);
          line[4]  <= hexch(t_addr[15:12]);
          line[5]  <= hexch(t_addr[11:8]);
          line[6]  <= hexch(t_addr[7:4]);
          line[7]  <= hexch(t_addr[3:0]);
          line[8]  <= " "; line[9] <= "W"; line[10] <= " ";
          line[11] <= hexch(t_data[7:4]);
          line[12] <= hexch(t_data[3:0]);
          line[13] <= " "; line[14] <= "R"; line[15] <= " ";
          line[16] <= hexch(rd_data[7:4]);
          line[17] <= hexch(rd_data[3:0]);
          line[18] <= " ";
          if (rd_data == t_data) begin
            line[19] <= "O"; line[20] <= "K";
          end else begin
            line[19] <= "E"; line[20] <= "R";
            fail  <= 1'b1;
            nfail <= nfail + 3'd1;
          end
          line[21] <= 8'h0D; line[22] <= 8'h0A;
          line_len <= 6'd23;
          line_ptr <= 6'd0;
          after_emit <= S_NEXT;
          state <= S_EMIT;
        end

        // shift the assembled line out through the tx handshake
        S_EMIT:
        if (!tx_busy && !tx_valid) begin
          if (line_ptr == line_len) begin
            state <= after_emit;
          end else begin
            tx_data  <= line[line_ptr[4:0]];
            tx_valid <= 1'b1;
            line_ptr <= line_ptr + 6'd1;
          end
        end

        S_NEXT:
        if (tidx == 3'd7) state <= S_RESULT;
        else begin
          tidx  <= tidx + 3'd1;
          d     <= 4'd0;
          state <= S_WR;
        end

        // "NDMEM PASS" / "NDMEM FAIL n"
        S_RESULT: begin
          line[0] <= "N"; line[1] <= "D"; line[2] <= "M"; line[3] <= "E";
          line[4] <= "M"; line[5] <= " ";
          if (!fail) begin
            line[6]  <= "P"; line[7] <= "A"; line[8] <= "S"; line[9] <= "S";
            line[10] <= 8'h0D; line[11] <= 8'h0A;
            line_len <= 6'd12;
          end else begin
            line[6]  <= "F"; line[7] <= "A"; line[8] <= "I"; line[9] <= "L";
            line[10] <= " ";
            line[11] <= 8'h30 + {5'b0, nfail};
            line[12] <= 8'h0D; line[13] <= 8'h0A;
            line_len <= 6'd14;
          end
          line_ptr   <= 6'd0;
          after_emit <= S_END;
          state      <= S_EMIT;
        end

        S_END: begin
          ras   <= 1'b0;
          cas   <= 1'b0;
          bank0 <= 1'b0;
          state <= S_IDLE;
        end

        default: state <= S_IDLE;
      endcase
    end
  end

endmodule

`default_nettype wire