Skip to content

nd_ddr2_storage

Source: Verilog/fpga/nexys4ddr/ddr2/nd_ddr2_storage.v

Where it sits (Nexys): nd120_nexys4ddr_top > nd_ddr2_storage - instance path: u_region

Used in: nd120_nexys4ddr_top (Nexys)

Contains: no other modules.

Module hierarchy - All modules

nd_ddr2_storage symbol

Schematic

Drawn from the Verilog: the yosys netlist of the Nexys 4 DDR build, instance u_region. 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_ddr2_storage schematic

Description

nd_ddr2_storage - the nd_storage region, held in DDR2 nd_storage keeps a REGION of block storage that its Phase-4 tag directory uses as a CACHE of the disc images on the SD card (Verilog/SD-FAT/circuit/nd_storage_cache.v): the disc classes are cached, tape and floppy go direct to the card, and writes go through to the card. On the Tang Nano 20K that region lives in the upper half of the single SDRAM chip, reached through MEM_RAM_49_SDRAM's ND_STORAGE_PORT because the CPU's main memory shares the same chip. This board has DDR2 that nothing else uses yet, so the region connects to it DIRECTLY through ../ddr2/nd_ddr2_port.v - no detour through the CPU memory path. When ND-120 main memory later moves into DDR2 as well, nd_ddr2_port is the place that arbitrates the two clients. THE PORT CONTRACT (from nd_storage_engine.v, matched exactly): mem_start 1-cycle pulse, only legal while mem_busy = 0 mem_we / mem_addr / mem_wdata stable from mem_start until mem_done mem_rdata valid at mem_done and held afterwards mem_busy level, high for the whole operation mem_done 1-cycle pulse ADDRESS MAPPING mem_addr[19:0] indexes 32-bit words: 1M words = 4 MB of region. One DDR2 transfer is 128 bits = four of those words, and app_addr counts 16-bit units with 8 units per transfer, so: transfer index = mem_addr[19:2] word in transfer = mem_addr[1:0] req_addr = REGION_BASE_UNITS + {mem_addr[19:2], 3'b000} A write updates ONE 32-bit word, so it uses the byte mask rather than a read-modify-write: MIG's mask is active low, so the four bytes of the selected lane are 0 and the other twelve are 1. CLOCK DOMAINS: the storage stack runs on stor_clk (27 MHz here), the controller on ui_clk (75 MHz). The request crosses as a toggle with the payload held stable behind it, and completion comes back as a second toggle - the same shape MEM_RAM_49_SDRAM uses on the Tang. Last reviewed: 20-AUG-2026 Ronny Hansen

Parameters

Parameter Default
REGION_BASE_UNITS 27'h2000000

Ports

Direction Width Name Description
input 1 stor_clk
input 1 stor_rst_n (active low)
input 1 mem_start
input 1 mem_we
input [19:0] mem_addr
input [31:0] mem_wdata
output [31:0] mem_rdata
output 1 mem_busy
output 1 mem_done
input 1 ui_clk 75 MHz - the domain of every port signal below (from nd_ddr2_port.ui_clk)
input 1 ui_rst
output 1 req_valid
output 1 req_we
output [26:0] req_addr
output [127:0] req_wdata
output [15:0] req_wmask
input 1 req_ready
input 1 rsp_valid
input [127:0] rsp_rdata

Verilog source

Verilog/fpga/nexys4ddr/ddr2/nd_ddr2_storage.v on GitHub.

Show the Verilog of nd_ddr2_storage (192 lines)
/****************************************************************************
** nd_ddr2_storage - the nd_storage region, held in DDR2                    **
**                                                                         **
** nd_storage keeps a REGION of block storage that its Phase-4 tag          **
** directory uses as a CACHE of the disc images on the SD card              **
** (Verilog/SD-FAT/circuit/nd_storage_cache.v): the disc classes are        **
** cached, tape and floppy go direct to the card, and writes go through to  **
** the card. On the Tang Nano 20K that region lives in the upper half of    **
** the single SDRAM chip, reached through MEM_RAM_49_SDRAM's                **
** ND_STORAGE_PORT because the CPU's main memory shares the same chip.      **
**                                                                         **
** This board has DDR2 that nothing else uses yet, so the region connects   **
** to it DIRECTLY through ../ddr2/nd_ddr2_port.v - no detour through the    **
** CPU memory path. When ND-120 main memory later moves into DDR2 as well,  **
** nd_ddr2_port is the place that arbitrates the two clients.               **
**                                                                         **
** THE PORT CONTRACT (from nd_storage_engine.v, matched exactly):           **
**   mem_start  1-cycle pulse, only legal while mem_busy = 0                **
**   mem_we / mem_addr / mem_wdata  stable from mem_start until mem_done    **
**   mem_rdata  valid at mem_done and held afterwards                       **
**   mem_busy   level, high for the whole operation                         **
**   mem_done   1-cycle pulse                                               **
**                                                                         **
** ADDRESS MAPPING                                                          **
**   mem_addr[19:0] indexes 32-bit words: 1M words = 4 MB of region.        **
**   One DDR2 transfer is 128 bits = four of those words, and app_addr      **
**   counts 16-bit units with 8 units per transfer, so:                     **
**       transfer index = mem_addr[19:2]                                    **
**       word in transfer = mem_addr[1:0]                                   **
**       req_addr = REGION_BASE_UNITS + {mem_addr[19:2], 3'b000}            **
**   A write updates ONE 32-bit word, so it uses the byte mask rather than  **
**   a read-modify-write: MIG's mask is active low, so the four bytes of    **
**   the selected lane are 0 and the other twelve are 1.                    **
**                                                                         **
** CLOCK DOMAINS: the storage stack runs on stor_clk (27 MHz here), the     **
** controller on ui_clk (75 MHz). The request crosses as a toggle with the  **
** payload held stable behind it, and completion comes back as a second     **
** toggle - the same shape MEM_RAM_49_SDRAM uses on the Tang.               **
**                                                                         **
** Last reviewed: 20-AUG-2026                                              **
** Ronny Hansen                                                            **
*****************************************************************************/
`default_nettype none

module nd_ddr2_storage #(
    // Where the region sits in DDR2, in 16-bit units. Default 64 MiB in, so
    // the bottom half of the device stays free for ND-120 main memory later.
    parameter [26:0] REGION_BASE_UNITS = 27'h2000000
) (
    // ---- storage side (stor_clk) ----
    input  wire        stor_clk,
    input  wire        stor_rst_n,
    input  wire        mem_start,
    input  wire        mem_we,
    input  wire [19:0] mem_addr,
    input  wire [31:0] mem_wdata,
    output reg  [31:0] mem_rdata,
    output wire        mem_busy,
    output reg         mem_done,

    // ---- controller side (ui_clk), wired to nd_ddr2_port ----
    input  wire          ui_clk,  //! 75 MHz - the domain of every port signal below (from nd_ddr2_port.ui_clk)
    input  wire          ui_rst,
    output reg           req_valid,
    output reg           req_we,
    output reg  [ 26:0]  req_addr,
    output reg  [127:0]  req_wdata,
    output reg  [ 15:0]  req_wmask,
    input  wire          req_ready,
    input  wire          rsp_valid,
    input  wire [127:0]  rsp_rdata
);

  /*******************************************************************
   *  stor_clk side: latch the request, raise busy, flip the toggle
   *******************************************************************/
  reg        busy_r;
  reg        req_tgl;
  reg        r_we;
  reg [19:0] r_addr;
  reg [31:0] r_wdata;

  assign mem_busy = busy_r;

  // completion toggle coming back from ui_clk
  reg dn_s0, dn_s1, dn_s2;
  always @(posedge stor_clk) begin
    if (!stor_rst_n) begin dn_s0 <= 1'b0; dn_s1 <= 1'b0; dn_s2 <= 1'b0; end
    else begin dn_s0 <= done_tgl; dn_s1 <= dn_s0; dn_s2 <= dn_s1; end
  end
  wire done_edge = dn_s1 ^ dn_s2;

  always @(posedge stor_clk) begin
    if (!stor_rst_n) begin
      busy_r    <= 1'b0;
      req_tgl   <= 1'b0;
      mem_done  <= 1'b0;
      mem_rdata <= 32'd0;
      r_we      <= 1'b0;
      r_addr    <= 20'd0;
      r_wdata   <= 32'd0;
    end else begin
      mem_done <= 1'b0;

      if (mem_start && !busy_r) begin
        r_we    <= mem_we;
        r_addr  <= mem_addr;
        r_wdata <= mem_wdata;
        busy_r  <= 1'b1;
        req_tgl <= ~req_tgl;
      end else if (busy_r && done_edge) begin
        // ui_rdata was captured several ui_clk cycles before the toggle
        // flipped, so it is stable to sample here
        mem_rdata <= ui_rdata;
        mem_done  <= 1'b1;
        busy_r    <= 1'b0;
      end
    end
  end

  /*******************************************************************
   *  ui_clk side: run one DDR2 transfer per request toggle
   *******************************************************************/
  reg        done_tgl;
  reg [31:0] ui_rdata;

  reg rq_s0, rq_s1, rq_s2;
  always @(posedge ui_clk) begin
    if (ui_rst) begin rq_s0 <= 1'b0; rq_s1 <= 1'b0; rq_s2 <= 1'b0; end
    else begin rq_s0 <= req_tgl; rq_s1 <= rq_s0; rq_s2 <= rq_s1; end
  end
  wire rq_edge = rq_s1 ^ rq_s2;

  // r_we / r_addr / r_wdata are held stable by the contract from the start
  // pulse until done, so they are safe to sample here once the toggle lands
  wire [1:0]  lane = r_addr[1:0];
  wire [15:0] lane_mask = ~(16'h000F << {lane, 2'b00});

  localparam U_IDLE = 2'd0;
  localparam U_REQ  = 2'd1;
  localparam U_WAIT = 2'd2;

  reg [1:0] ustate;

  always @(posedge ui_clk) begin
    if (ui_rst) begin
      ustate    <= U_IDLE;
      req_valid <= 1'b0;
      req_we    <= 1'b0;
      req_addr  <= 27'd0;
      req_wdata <= 128'd0;
      req_wmask <= 16'hFFFF;
      done_tgl  <= 1'b0;
      ui_rdata  <= 32'd0;
    end else begin
      case (ustate)
        U_IDLE:
        if (rq_edge) begin
          req_we    <= r_we;
          req_addr  <= REGION_BASE_UNITS + {7'd0, r_addr[19:2], 3'b000};
          req_wdata <= {4{r_wdata}};        // the mask picks the live lane
          req_wmask <= r_we ? lane_mask : 16'h0000;
          req_valid <= 1'b1;
          ustate    <= U_REQ;
        end

        U_REQ:
        if (req_valid && req_ready) begin
          req_valid <= 1'b0;   // dropped in the cycle it is accepted
          ustate    <= U_WAIT;
        end

        U_WAIT:
        if (rsp_valid) begin
          case (lane)
            2'd0: ui_rdata <= rsp_rdata[31:0];
            2'd1: ui_rdata <= rsp_rdata[63:32];
            2'd2: ui_rdata <= rsp_rdata[95:64];
            2'd3: ui_rdata <= rsp_rdata[127:96];
          endcase
          done_tgl <= ~done_tgl;
          ustate   <= U_IDLE;
        end

        default: ustate <= U_IDLE;
      endcase
    end
  end

endmodule

`default_nettype wire