MEM_RAM_49_SDRAM¶
Source: Verilog/fpga/tang-nano-20k/sdram-bridge/MEM_RAM_49_SDRAM.v
Where it sits (Tang): ND120_TANG20K_TOP > ND120_CORE > ND3202D > MEM_43 > MEM_RAM_49_SDRAM
- instance path: CORE.CPU_BOARD.MEM.RAM
Used in: MEM_43 (Tang, MiSTer, MEGA65 R6, QMTECH)
Contains: sdram18
Module hierarchy - All modules

Schematic¶
Drawn from the Verilog: the yosys netlist of the Tang Nano 20K build, instance CORE.CPU_BOARD.MEM.RAM. 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).
Description¶
ND120 CPU, MM&M MEM/RAM - SDRAM backend (Tang Nano 20K) Drop-in replacement for the sheet-49 RAM (MEM_RAM_49) that maps the ND-120 DRAM protocol onto the board's 8 MB embedded SDRAM through the 18-bit-word controller (sdram18.v). Protocol contract (measured, docs/nd120-dram-memory.md section 4; N = OSC cycle of the RAS rising edge, fast = 2x OSC, edge-aligned): N : row on AA_9_0, MWRITE50_n and BANKx valid N+1 : column on AA_9_0 N+2 : CAS rises, write data DD_17_0_IN valid N+4 : read data must be on DD_17_0_OUT (held while CAS high) N+5 : RAS falls, CAS tail one more cycle Next access no earlier than N+11. Capacity: 2M x 18-bit words = BANK0 + BANK2 (1M words each) = 4 MB. BANK1 is not populated: never written, reads as 0, so the ND-120's boot-time memory sizing simply detects two banks. (The board PAL decodes phys banks in the order BANK0,BANK2,BANK1 - so the CONTIGUOUS second 2 MB is BANK2, and BANK0+BANK2 is the real 4 MB; see line 373.) ND_SDRAM_PACK16 (docs/build-defines.md section 1, semantics pinned by docs/nd120-parity-analysis.md): only the 16 DATA bits are stored, two adjacent ND words per 32-bit SDRAM location, so BANK0+BANK2 (still the full 4 MB, boot sizing unchanged) fold into the LOWER half of the chip (location bit 20 = 0); the upper half is RESERVED for the nd_storage disk-image cache (nd-storage-design.md section 5.2). Parity is COMPUTED on the read path (DD[8]/DD[17] regenerated as odd parity, CORR_n always "correct") - licensed by the parity analysis: no self-test or runtime path reads stored parity. The CPU/storage split is parameterized at ND-row granularity (CPU_PART_ROWS) so a future build can trade CPU memory for cache. ND_STORAGE_PORT (requires ND_SDRAM_PACK16): adds the nd_storage device port of nd-storage-design.md section 5.2 - a start/busy/done mem port (stor_clk domain, toggle-CDC into clk2x) that reads/writes whole 32-bit locations at {1'b1, mem_addr[19:0]}, i.e. ONLY the upper-half storage region: the leading 1 is forced HERE, so device traffic physically cannot reach the CPU's half of the chip. Device ops are granted exactly like refresh - in the guaranteed-idle B_POST slot after each CPU access (>= 14 free fast cycles before the earliest next access under the N+11 rule; one op is 5 cycles), in B_TAIL (absent-row accesses leave the controller idle), and in B_IDLE behind the same idle_cnt watchdog guard - so CPU accesses always win and the measured protocol timing is untouched. Without the define the module is bit-identical to the plain pack16 build. Refresh is generated HERE (the board logic's refresh chain is inactive - see docs/nd120-dram-memory.md section 4): primarily in the guaranteed-idle slot right after each access, plus an idle watchdog when the CPU leaves memory alone. Last reviewed: 11-JUL-2026 Ronny Hansen
Parameters¶
| Parameter | Default |
|---|---|
CLK2X_FREQ |
2 *BOARD_CLK_FREQ` |
CLK2X_FREQ |
54_000_000 |
CPU_PART_ROWS |
2048 |
Ports¶
| Direction | Width | Name | Description |
|---|---|---|---|
| input | 1 |
sysclk |
OSC-domain clock (unused internally; kept for symmetry) |
| input | 1 |
sys_rst_n (active low) |
System reset |
| input | [9:0] |
AA_9_0 |
10 bits of LBD (including parity in bit 10)- 10 bit input to MEM/RAM (from MEM_ADDR_44.AA_9_0) |
| input | 1 |
BANK0 |
|
| input | 1 |
BANK1 |
absent third 1M bank (phys 2M-3M |
Verilog source¶
Verilog/fpga/tang-nano-20k/sdram-bridge/MEM_RAM_49_SDRAM.v on GitHub.
Show the Verilog of MEM_RAM_49_SDRAM (645 lines)
/**************************************************************************
** ND120 CPU, MM&M **
** MEM/RAM - SDRAM backend (Tang Nano 20K) **
** Drop-in replacement for the sheet-49 RAM (MEM_RAM_49) that maps the **
** ND-120 DRAM protocol onto the board's 8 MB embedded SDRAM through **
** the 18-bit-word controller (sdram18.v). **
** **
** Protocol contract (measured, docs/nd120-dram-memory.md section 4; **
** N = OSC cycle of the RAS rising edge, fast = 2x OSC, edge-aligned): **
** N : row on AA_9_0, MWRITE50_n and BANKx valid **
** N+1 : column on AA_9_0 **
** N+2 : CAS rises, write data DD_17_0_IN valid **
** N+4 : read data must be on DD_17_0_OUT (held while CAS high) **
** N+5 : RAS falls, CAS tail one more cycle **
** Next access no earlier than N+11. **
** **
** Capacity: 2M x 18-bit words = BANK0 + BANK2 (1M words each) = 4 MB. **
** BANK1 is not populated: never written, reads as 0, so the ND-120's **
** boot-time memory sizing simply detects two banks. (The board PAL **
** decodes phys banks in the order BANK0,BANK2,BANK1 - so the CONTIGUOUS **
** second 2 MB is BANK2, and BANK0+BANK2 is the real 4 MB; see line 373.) **
** **
** ND_SDRAM_PACK16 (docs/build-defines.md section 1, semantics **
** pinned by docs/nd120-parity-analysis.md): only the 16 DATA bits are **
** stored, two adjacent ND words per 32-bit SDRAM location, so **
** BANK0+BANK2 (still the full 4 MB, boot sizing unchanged) fold into **
** the LOWER half of the chip (location bit 20 = 0); the upper half is **
** RESERVED for the nd_storage disk-image cache (nd-storage-design.md **
** section 5.2). Parity is COMPUTED on the read path (DD[8]/DD[17] **
** regenerated as odd parity, CORR_n always "correct") - licensed by the **
** parity analysis: no self-test or runtime path reads stored parity. **
** The CPU/storage split is parameterized at ND-row granularity **
** (CPU_PART_ROWS) so a future build can trade CPU memory for cache. **
** **
** ND_STORAGE_PORT (requires ND_SDRAM_PACK16): adds the nd_storage **
** device port of nd-storage-design.md section 5.2 - a start/busy/done **
** mem port (stor_clk domain, toggle-CDC into clk2x) that reads/writes **
** whole 32-bit locations at {1'b1, mem_addr[19:0]}, i.e. ONLY the **
** upper-half storage region: the leading 1 is forced HERE, so device **
** traffic physically cannot reach the CPU's half of the chip. Device **
** ops are granted exactly like refresh - in the guaranteed-idle B_POST **
** slot after each CPU access (>= 14 free fast cycles before the **
** earliest next access under the N+11 rule; one op is 5 cycles), in **
** B_TAIL (absent-row accesses leave the controller idle), and in **
** B_IDLE behind the same idle_cnt watchdog guard - so CPU accesses **
** always win and the measured protocol timing is untouched. Without **
** the define the module is bit-identical to the plain pack16 build. **
** **
** Refresh is generated HERE (the board logic's refresh chain is **
** inactive - see docs/nd120-dram-memory.md section 4): primarily in **
** the guaranteed-idle slot right after each access, plus an idle **
** watchdog when the CPU leaves memory alone. **
** **
** Last reviewed: 11-JUL-2026 **
** Ronny Hansen **
***************************************************************************/
module MEM_RAM_49_SDRAM #(
// Frequency of clk2x (= 2x OSC). MEM_43 instantiates this module without
// parameters, so the default must track the board clock: when the build
// provides BOARD_CLK_FREQ (tang20k_defines.v), derive 2x from it - the
// refresh interval and the controller's init counts depend on it.
`ifdef BOARD_CLK_FREQ
parameter CLK2X_FREQ = 2 * `BOARD_CLK_FREQ,
`else
parameter CLK2X_FREQ = 54_000_000,
`endif
// ND_SDRAM_PACK16 only: how many 1K-ND-word rows ({bank, row[9:0]} out of
// 2048) belong to the CPU. Default 2048 = full 4 MB main memory (both ND
// banks). Rows at/above this count report ABSENT (read 0, writes dropped),
// so boot-time sizing shrinks accordingly - e.g. 1024 = 2 MB CPU, freeing
// 6 MB of the chip for the storage cache. Keep it a multiple of 1024 so
// whole ND banks appear/disappear (the sizing probe works per bank).
parameter CPU_PART_ROWS = 2048
) (
// Input signals (sheet-49 interface, same as MEM_RAM_49)
input sysclk, // OSC-domain clock (unused internally; kept for symmetry)
input sys_rst_n, // System reset
input [9:0] AA_9_0, //! 10 bits of LBD (including parity in bit 10)- 10 bit input to MEM/RAM (from MEM_ADDR_44.AA_9_0)
input BANK0,
/* verilator lint_off UNUSEDSIGNAL */
input BANK1, // absent third 1M bank (phys 2M-3M); not populated here
/* verilator lint_on UNUSEDSIGNAL */
input BANK2, // 2nd populated 1M bank: PAL decodes phys 1M-2M here
input CAS,
input RAS,
input MWRITE50_n,
input [17:0] DD_17_0_IN,
output [17:0] DD_17_0_OUT,
output CORR_n,
// Fast clock domain (2x OSC from the same rPLL, edge-aligned)
input clk2x,
input clk2x_sdram, // 180 degrees from clk2x, for the SDRAM chip
// SDRAM device pins ("magic" names on Gowin EDA; pinned in the OSS cst)
output O_sdram_clk,
output O_sdram_cke,
output O_sdram_cs_n,
output O_sdram_cas_n,
output O_sdram_ras_n,
output O_sdram_wen_n,
inout [31:0] IO_sdram_dq,
output [10:0] O_sdram_addr,
output [ 1:0] O_sdram_ba,
output [ 3:0] O_sdram_dqm,
// Raw bridge state for the on-chip analyzer (see TRACE-CAPTURE-GUIDE.md)
output [ 7:0] DBG_BRIDGE,
//! ---- PAGE-WRITE WATCH (24-AUG-2026, zero-page campaign) ---------------
//! Run 15 measured that the page the CPU fetches zeros from is PPN 0o3770
//! = physical page 2040 = {bank_q=1 (BANK2), row_q=1016}, which is REAL
//! populated memory. So the disc data was never stored there. This bus
//! reports every access to the 8-page window rows 1016..1023 in BOTH
//! banks, AT THE BRIDGE - the last point before the SDRAM itself, so a
//! transfer that never arrives cannot be argued away.
//! word A: [15:14]=10 [13]=bank [12:3]=row [2:0]=data[15:13]
//! word B: [15:14]=11 [13:1]=data[12:0] [0]=0
//! read : [15:14]=01 [13]=bank [12:3]=row [2:0]=0
//! idle = 16'h0000
output [15:0] DBG_PGW
`ifdef ND_SDRAM_PACK16
`ifdef ND_STORAGE_PORT
// nd_storage device port (nd-storage-design.md section 5.2): 32-bit
// locations at {1'b1, mem_addr} = the upper-half storage region only
,
input wire stor_clk, // = clk_stor (nd_storage's memory side)
input wire stor_rst_n,
input wire mem_start, // 1-cycle pulse, only legal when mem_busy=0
input wire mem_we,
input wire [19:0] mem_addr, // 32-bit-location address inside the region
input wire [31:0] mem_wdata,
output reg [31:0] mem_rdata, // valid at mem_done, then held
output wire mem_busy,
output reg mem_done // 1-cycle pulse
`endif
`endif
);
/*******************************************************************************
** Write-data capture - OR-accumulation across the access **
** **
** Hardware evidence (Tang, 8-JUL-2026, four builds): the write data on **
** DD_17_0_IN appears only as a SUB-CYCLE PULSE somewhere inside the access **
** window - the AM29833A transceivers in MEM_DATA_46 drive it **
** combinationally, gated by PAL-generated OET_n/OER_n whose FF-mode phase **
** is narrow; every fixed sample point tried (2N+5 / 2N+9 / OSC N+2 / N+3) **
** read a dead or half-risen bus. The saving property: when NOT driving, **
** the transceiver outputs hard ZEROS (T_OUT = mode ? data : 0). So **
** OR-accumulate DD on every clk2x edge across the access: idle samples **
** contribute nothing, and with the pulse ~an OSC cycle wide, at least one **
** 74 ns-spaced sample lands inside it with all bits driven. **
*******************************************************************************/
reg [17:0] dd_acc;
// Second sampler on the falling clk2x edge: 37 ns effective granularity,
// in case the drive pulse is narrower than one clk2x period
reg [17:0] dd_acc_n;
reg ras_dn;
always @(negedge clk2x) begin
if (!sys_rst_n) begin
dd_acc_n <= 0;
ras_dn <= 0;
end else begin
ras_dn <= RAS;
if (RAS && !ras_dn) dd_acc_n <= DD_17_0_IN;
else if (RAS || CAS) dd_acc_n <= dd_acc_n | DD_17_0_IN;
end
end
/*******************************************************************************
** SDRAM controller (18-bit word variant, runs entirely on clk2x) **
*******************************************************************************/
reg s_rd, s_wr, s_refresh;
`ifdef ND_SDRAM_PACK16
reg [21:0] s_addr; // half-word address: [21]=0 pins the CPU to the low half
reg [15:0] s_din;
wire [15:0] s_dout;
`ifdef ND_STORAGE_PORT
// stor_clk-side request latches (stable from mem_start until mem_done, so
// safe to sample in clk2x once the synced start toggle arrives)
reg s_dev_busy;
reg s_dev_we_l;
reg [19:0] s_dev_addr_l;
reg [31:0] s_dev_wdata_l;
reg s_dev_start_tgl; // stor_clk -> clk2x request
reg s_dev_dn_s0, s_dev_dn_s1, s_dev_dn_d; // done toggle, synced back
// clk2x-side device engine
reg s_dev_st_s0, s_dev_st_s1, s_dev_st_d; // start toggle, synced in
reg s_dev_pend; // request waiting for a grant slot
reg s_dev_run; // op issued to the controller
reg s_dev_seen; // controller busy observed since issue
reg s_dev_done_tgl; // clk2x -> stor_clk completion
reg [31:0] s_dev_rdata; // dout32 captured at data_ready, held for stor_clk
reg s_acc32; // full-location command qualifier to sdram18
wire [31:0] s_dout32;
`endif
`else
reg [20:0] s_addr;
reg [17:0] s_din;
wire [17:0] s_dout;
`endif
wire s_data_ready, s_busy;
sdram18 #(
.FREQ(CLK2X_FREQ)
) u_sdram (
.clk(clk2x),
.clk_sdram(clk2x_sdram),
.resetn(sys_rst_n),
.rd(s_rd),
.wr(s_wr),
.refresh(s_refresh),
.addr(s_addr),
.din(s_din),
.dout(s_dout),
`ifdef ND_SDRAM_PACK16
`ifdef ND_STORAGE_PORT
.acc32(s_acc32),
.din32(s_dev_wdata_l),
.dout32(s_dout32),
`else
.acc32(1'b0),
.din32(32'b0),
.dout32(),
`endif
`endif
.data_ready(s_data_ready),
.busy(s_busy),
`ifdef ND_SDRAM_DQ16
// 16-bit module: only the low half of the 32-bit port pair is a pin;
// IO_sdram_dq[31:16] is left unconnected at the board top
.SDRAM_DQ(IO_sdram_dq[15:0]),
`else
.SDRAM_DQ(IO_sdram_dq),
`endif
.SDRAM_A(O_sdram_addr),
.SDRAM_BA(O_sdram_ba),
.SDRAM_nCS(O_sdram_cs_n),
.SDRAM_nWE(O_sdram_wen_n),
.SDRAM_nRAS(O_sdram_ras_n),
.SDRAM_nCAS(O_sdram_cas_n),
.SDRAM_CLK(O_sdram_clk),
.SDRAM_CKE(O_sdram_cke),
.SDRAM_DQM(O_sdram_dqm)
);
/*******************************************************************************
** Refresh timer: one auto-refresh per ND_SDRAM_REFRESH_US (default 15 us) **
** **
** 15 us suits the Tang's 2K-row die (4096 refreshes per 64 ms). A 16-bit **
** DE10-Nano module (ND_SDRAM_DQ16) has 8192 rows and needs one auto-refresh **
** every 7.8 us at most; the MiSTer build sets ND_SDRAM_REFRESH_US=7. Auto- **
** refresh walks the chip's OWN row counter, so this cadence covers the **
** whole die however many rows the CPU actually uses. **
*******************************************************************************/
`ifndef ND_SDRAM_REFRESH_US
`define ND_SDRAM_REFRESH_US 15
`endif
localparam REFRESH_INTERVAL = CLK2X_FREQ / 1_000_000 * `ND_SDRAM_REFRESH_US;
reg [10:0] ref_cnt;
reg refresh_needed;
always @(posedge clk2x) begin
if (!sys_rst_n) begin
ref_cnt <= 0;
refresh_needed <= 0;
end else begin
ref_cnt <= ref_cnt + 1;
if (ref_cnt >= REFRESH_INTERVAL[10:0]) refresh_needed <= 1;
if (s_refresh) begin
ref_cnt <= 0;
refresh_needed <= 0;
end
end
end
/*******************************************************************************
** Protocol bridge state machine (clk2x domain) **
** **
** RAS/CAS/AA/BANKx/MWRITE50_n are OSC-domain registered-PAL outputs; clk2x **
** is the same PLL at exactly 2x, so they change only on every other clk2x **
** edge and are sampled here as synchronous signals (fast edge 2N+1 sees the **
** value OSC edge N produced). **
*******************************************************************************/
localparam B_IDLE = 3'd0;
localparam B_COLWAIT = 3'd1; // fast edge 2N+2: AA is switching to the column
localparam B_COL = 3'd2; // fast edge 2N+3: column valid -> issue read
localparam B_WRDATA = 3'd3; // fast edge 2N+5: DD_IN valid -> issue write
localparam B_RDWAIT = 3'd4; // wait for data_ready, capture and hold
localparam B_POST = 3'd5; // op done: run a refresh if one is due
localparam B_TAIL = 3'd6; // unpopulated bank: just wait out the access
reg [2:0] bstate;
reg ras_d;
reg [9:0] row_q;
// ---- page-write watch (see the DBG_PGW port comment) ----------------------
// The window is rows 1016..1023 (row_q[9:3] == 7'd127) in both banks, so a
// transfer that lands a few pages off the target is visible as a near miss
// instead of looking like "no write at all".
localparam [6:0] PGW_ROW_HI = 7'd127;
reg [1:0] pgw_phase = 2'd0;
reg pgw_bank = 1'b0;
reg [9:0] pgw_row = 10'd0;
reg [15:0] pgw_data = 16'd0;
wire pgw_match = (row_q[9:3] == PGW_ROW_HI);
assign DBG_PGW = (pgw_phase == 2'd1) ? {2'b10, pgw_bank, pgw_row, pgw_data[15:13]}
: (pgw_phase == 2'd2) ? {2'b11, pgw_data[12:0], 1'b0}
: (pgw_phase == 2'd3) ? {2'b01, pgw_bank, pgw_row, 3'b000}
: 16'h0000;
// Rising-edge detect on the controller's own issue pulses, so the record is
// taken at the moment the access is handed to the SDRAM - not at the board
// interface, where a dropped transfer would still look present. s_din is
// already updated by the time s_wr's edge is seen one cycle later.
reg s_wr_d = 1'b0;
reg s_rd_d = 1'b0;
always @(posedge clk2x) begin
s_wr_d <= s_wr;
s_rd_d <= s_rd;
if (pgw_phase != 2'd0) pgw_phase <= (pgw_phase == 2'd1) ? 2'd2 : 2'd0;
if (s_wr && !s_wr_d && pgw_match) begin
pgw_bank <= bank_q;
pgw_row <= row_q;
pgw_data <= s_din[15:0];
pgw_phase <= 2'd1;
end else if (s_rd && !s_rd_d && pgw_match) begin
pgw_bank <= bank_q;
pgw_row <= row_q;
pgw_phase <= 2'd3;
end
end
reg wn_q; // 1 = read (MWRITE50_n high)
reg bsel_q; // access hits a populated bank (BANK0/BANK2)
reg bank_q; // 0 = BANK0, 1 = BANK2 (the 2nd populated 1M bank)
reg [2:0] wcnt_q; // write-data settle counter (capture late in the window)
reg [17:0] dd_hold;
reg have_data;
assign DBG_BRIDGE = {bstate[2:0], // [7:5] FSM state (B_IDLE..B_TAIL)
s_wr, // [4] write command issued
s_rd, // [3] read command issued
s_data_ready, // [2] controller returned read data
have_data, // [1] read data held for DD_OUT
s_busy}; // [0] controller busy
// Idle watchdog: if the CPU leaves memory alone, refresh anyway
reg [6:0] idle_cnt;
localparam IDLE_REFRESH_AFTER = 7'd64; // fast cycles (~1.2 us at 54 MHz)
always @(posedge clk2x) begin
if (!sys_rst_n) begin
bstate <= B_IDLE;
ras_d <= 0;
s_rd <= 0;
s_wr <= 0;
s_refresh <= 0;
s_addr <= 0;
s_din <= 0;
row_q <= 0;
wn_q <= 1;
bsel_q <= 0;
bank_q <= 0;
wcnt_q <= 0;
dd_hold <= 0;
have_data <= 0;
idle_cnt <= 0;
`ifdef ND_SDRAM_PACK16
`ifdef ND_STORAGE_PORT
s_dev_st_s0 <= 0;
s_dev_st_s1 <= 0;
s_dev_st_d <= 0;
s_dev_pend <= 0;
s_dev_run <= 0;
s_dev_seen <= 0;
s_dev_done_tgl <= 0;
s_dev_rdata <= 0;
s_acc32 <= 0;
`endif
`endif
end else begin
// command outputs are 1-cycle pulses
s_rd <= 0;
s_wr <= 0;
s_refresh <= 0;
ras_d <= RAS;
`ifdef ND_SDRAM_PACK16
`ifdef ND_STORAGE_PORT
// ---- storage device port, clk2x side ----
s_acc32 <= 0; // command qualifier: pulses with s_rd/s_wr on device grants
// request toggle from stor_clk: 2-flop sync + edge detect -> pending
s_dev_st_s0 <= s_dev_start_tgl;
s_dev_st_s1 <= s_dev_st_s0;
s_dev_st_d <= s_dev_st_s1;
if (s_dev_st_s1 != s_dev_st_d) s_dev_pend <= 1;
// completion tracking: after a grant, watch the controller go busy and
// idle again; reads capture the full location at data_ready
if (s_dev_run) begin
if (s_busy) s_dev_seen <= 1;
if (s_data_ready) s_dev_rdata <= s_dout32;
if (s_dev_seen && !s_busy) begin
s_dev_run <= 0;
s_dev_seen <= 0;
s_dev_done_tgl <= ~s_dev_done_tgl; // rdata already stable
end
end
`endif
`endif
// OR-accumulate the write data across the access (see comment above):
// reset at RAS rise, collect every clk2x edge while the access runs
if (RAS && !ras_d) dd_acc <= DD_17_0_IN;
else if (RAS || CAS) dd_acc <= dd_acc | DD_17_0_IN;
// have_data lifecycle: set when a read completes, cleared when the next
// access starts; the CAS term in read_active closes the output window.
case (bstate)
B_IDLE: begin
if (RAS && !ras_d) begin // fast edge 2N+1: RAS rise seen, AA = row
row_q <= AA_9_0;
wn_q <= MWRITE50_n;
`ifdef ND_SDRAM_PACK16
// partition check at row granularity: rows beyond the CPU's share
// behave exactly like an unpopulated bank (B_TAIL path)
// NOTE: the board decode PAL (PAL_44445B) wires the three 1M-word
// banks in physical-address order BANK0, BANK2, BANK1 - so the
// CONTIGUOUS second 2 MB (phys words 1M-2M) is decoded as BANK2,
// not BANK1. The two populated SDRAM regions must therefore be
// BANK0 + BANK2 (BANK1 = the absent third bank at 2M-3M).
bsel_q <= (BANK0 | BANK2) && ({BANK2, AA_9_0} < CPU_PART_ROWS[11:0]);
`else
bsel_q <= BANK0 | BANK2;
`endif
bank_q <= BANK2;
have_data <= 0;
idle_cnt <= 0;
bstate <= B_COLWAIT;
end else begin
// idle watchdog refresh (only when no access is starting)
idle_cnt <= (idle_cnt == IDLE_REFRESH_AFTER) ? idle_cnt : idle_cnt + 1;
if (refresh_needed && !s_busy && idle_cnt == IDLE_REFRESH_AFTER) s_refresh <= 1;
`ifdef ND_SDRAM_PACK16
`ifdef ND_STORAGE_PORT
// device grant, behind the same long-idle guard as the watchdog
// refresh (refresh keeps priority; !s_refresh blocks the cycle a
// refresh pulse is still being presented to the controller)
else if (s_dev_pend && !s_dev_run && !s_busy && !s_refresh
&& idle_cnt == IDLE_REFRESH_AFTER) begin
s_addr <= {1'b1, s_dev_addr_l, 1'b0}; // storage half ONLY
s_acc32 <= 1;
if (s_dev_we_l) s_wr <= 1;
else s_rd <= 1;
s_dev_pend <= 0;
s_dev_run <= 1;
end
`endif
`endif
end
end
B_COLWAIT: bstate <= B_COL; // 2N+2: skip the AA row->column switch edge
B_COL: begin // 2N+3: AA carries the column
if (!bsel_q) begin
bstate <= B_TAIL; // BANK1 / no bank: not populated, do nothing
end else if (!s_busy) begin
`ifdef ND_SDRAM_PACK16
// ND word address as a HALF-WORD index into the low half of the
// chip: adjacent ND words share one 32-bit location ([0] = half)
s_addr <= {1'b0, bank_q, row_q, AA_9_0};
`else
s_addr <= {bank_q, row_q, AA_9_0}; // {bank, row[9:0], col[9:0]} = 21 bits
`endif
if (wn_q) begin
s_rd <= 1; // read: issue now -> data_ready by fast 2N+8 = OSC N+4
bstate <= B_RDWAIT;
end else begin
wcnt_q <= 0; // write: DD_IN settles DURING the both-low window
bstate <= B_WRDATA;
end
end
// if s_busy (watchdog refresh collided with this access) hold here;
// the refresh frees the controller within 5 fast cycles
end
B_WRDATA: begin
// Wait until the accumulated data has covered the whole possible
// drive window (through OSC N+4 = fast 2N+8), then issue with it
wcnt_q <= wcnt_q + 1'b1;
if (wcnt_q >= 3'd5 && !s_busy) begin
`ifdef ND_SDRAM_PACK16
// store the 16 DATA bits only; DD[8]/DD[17] (parity) are dropped
// and recomputed on read (docs/nd120-parity-analysis.md section 6)
s_din <= {dd_acc[16:9] | dd_acc_n[16:9], dd_acc[7:0] | dd_acc_n[7:0]};
`else
s_din <= dd_acc | dd_acc_n;
`endif
s_wr <= 1;
bstate <= B_POST;
end else if (wcnt_q >= 3'd5) begin
wcnt_q <= wcnt_q; // controller busy (colliding refresh): hold
end
end
B_RDWAIT:
if (s_data_ready) begin
`ifdef ND_SDRAM_PACK16
// regenerate ODD parity (AM29833A convention: PAR = ~^byte) so the
// downstream checkers and CORR_n always see a correct word
dd_hold <= {~(^s_dout[15:8]), s_dout[15:8], ~(^s_dout[7:0]), s_dout[7:0]};
`else
dd_hold <= s_dout;
`endif
have_data <= 1;
bstate <= B_POST;
end
B_POST:
if (!s_busy && !s_rd && !s_wr) begin
// guaranteed-idle slot: earliest next access is N+11 (22 fast cycles
// after RAS rise), a refresh takes 5 - always safe here
if (refresh_needed) s_refresh <= 1;
`ifdef ND_SDRAM_PACK16
`ifdef ND_STORAGE_PORT
// device grant in the same guaranteed-idle slot, refresh first: a
// 5-cycle device op issued here is long done before the earliest
// next access reaches B_COL (fast 2N+25)
else if (s_dev_pend && !s_dev_run && !s_refresh) begin
s_addr <= {1'b1, s_dev_addr_l, 1'b0}; // storage half ONLY
s_acc32 <= 1;
if (s_dev_we_l) s_wr <= 1;
else s_rd <= 1;
s_dev_pend <= 0;
s_dev_run <= 1;
end
`endif
`endif
bstate <= B_IDLE;
end
B_TAIL: begin
// absent bank/row: the controller is idle for this whole access, so
// it is a free refresh slot - without this, a run of absent-row
// accesses (each resetting the idle watchdog, none reaching the
// B_POST slot) starves refresh past its 15 us cadence
if (refresh_needed && !s_busy) s_refresh <= 1;
`ifdef ND_SDRAM_PACK16
`ifdef ND_STORAGE_PORT
// device grant in the absent-row free slot, refresh first: the op
// ends at latest 6 cycles after RAS fall, the next access is >= 12
else if (s_dev_pend && !s_dev_run && !s_busy && !s_refresh) begin
s_addr <= {1'b1, s_dev_addr_l, 1'b0}; // storage half ONLY
s_acc32 <= 1;
if (s_dev_we_l) s_wr <= 1;
else s_rd <= 1;
s_dev_pend <= 0;
s_dev_run <= 1;
end
`endif
`endif
if (!RAS) bstate <= B_IDLE;
end
default: bstate <= B_IDLE;
endcase
end
end
/*******************************************************************************
** Sheet-49 outputs: same gating as the SIP1M9 chips (drive 0 / parity 1 **
** when not selected or not reading, so downstream OR-combining still works) **
*******************************************************************************/
wire read_active = CAS & wn_q & bsel_q & have_data;
assign DD_17_0_OUT = read_active ? dd_hold : 18'b0;
// Two virtual SIP1M9 parity outputs (low chip = DD[8:0], high chip = DD[17:9]),
// remaining four (unpopulated banks) contribute constant 1 - same formula as
// SIP1M9's PRD_n, same AND-combination as MEM_RAM_49's CORR_n.
assign CORR_n = read_active ? ((^dd_hold[8:0]) & (^dd_hold[17:9])) : 1'b1;
`ifdef ND_SDRAM_PACK16
`ifdef ND_STORAGE_PORT
/*******************************************************************************
** Storage device port, stor_clk side (nd-storage-design.md section 5.2) **
** **
** start/we/addr/wdata are latched at the mem_start pulse and held stable **
** for the whole op, so the clk2x side samples them as quasi-static data **
** behind the 2-flop start-toggle sync. Completion comes back as a done **
** toggle; s_dev_rdata was captured at data_ready, several clk2x cycles **
** before the toggle flip, so it is stable when sampled here. **
*******************************************************************************/
assign mem_busy = s_dev_busy;
always @(posedge stor_clk) begin
if (!stor_rst_n) begin
s_dev_busy <= 0;
s_dev_we_l <= 0;
s_dev_addr_l <= 0;
s_dev_wdata_l <= 0;
s_dev_start_tgl <= 0;
s_dev_dn_s0 <= 0;
s_dev_dn_s1 <= 0;
s_dev_dn_d <= 0;
mem_rdata <= 0;
mem_done <= 0;
end else begin
mem_done <= 0;
s_dev_dn_s0 <= s_dev_done_tgl;
s_dev_dn_s1 <= s_dev_dn_s0;
s_dev_dn_d <= s_dev_dn_s1;
if (!s_dev_busy) begin
if (mem_start) begin
s_dev_we_l <= mem_we;
s_dev_addr_l <= mem_addr;
s_dev_wdata_l <= mem_wdata;
s_dev_busy <= 1;
s_dev_start_tgl <= ~s_dev_start_tgl;
end
end else if (s_dev_dn_s1 != s_dev_dn_d) begin
mem_rdata <= s_dev_rdata;
mem_done <= 1;
s_dev_busy <= 0;
end
end
end
`endif
`endif
endmodule