Begins the V2 Neural Multiprocessor / Dataflow architecture per docs/v2-description.md, per explicit user request to freeze V1 and start V2 development, copying from V1 what's needed. Scaffold: - hardware/v1/: byte-exact, read-only copy of the current V1 codebase (rtl, testbenches, tools, constraints, a representative subset of synthesis results, and reference docs) -- verified identical via diff/cmp against the live top-level tree before being made filesystem-read-only. The live top-level tree is untouched and remains the project's "production" V1 (see hardware/v1/README.md and hardware/v2/logs/decisions.log DEC-0001 for why copy-not-move). - hardware/v2/: mandatory structure (rtl/sim/constraints/synthesis/ reports/scripts/logs/docs) plus the full logging system required by the spec (development/architecture/simulation/synthesis/timing/ benchmark/decisions/experiments/errors.log). M1 -- Neural Processor (hardware/v2/rtl/neural_processor.v): - 8-stage pipelined perceptron unit (P_IN=8): input align, 8 multipliers, 3-level adder tree, accumulator, bias+activation, INT8 saturation. Genuine 1-tile/cycle throughput, not just a wider combinational datapath. - 7-state FSM (NP_IDLE..NP_ERROR per docs/v2-description.md §6, with 4 baseline states merged into NP_WAIT_OPERANDS -- see decisions.log DEC-0002); valid/ready/data/last stream interfaces per §7. - Bit-exact vs the frozen hardware/v1/rtl/neuron_parallel.v + mac8.v + mac_unit.v: 7/7 tests pass (hardware/v2/sim/tb_neural_processor.v), covering regular/mixed-sign/extreme-INT8 vectors, both activations, a zero-idle-gap back-to-back-tiles throughput check, and an 8-tile job -- verified with Verilator (see below for why). - Real synthesis + place&route (Yosys + nextpnr-ecp5): 0 CHECK problems, Fmax 183.12 MHz at ACC_WIDTH=32 (PASS at 80MHz, ~3x V1's isolated PARALLEL=8 Fmax of 61.71 MHz) and 176.21 MHz at ACC_WIDTH=24 (a user-requested comparison experiment, also bit-exact-verified; see experiments.log EXP-0001/EXP-0002 and benchmark.log). Three real bugs found and resolved during M1 development (full diagnostic record in errors.log): - Two independent, reproducible Icarus Verilog v13.0 scheduling defects (ERR-0001, ERR-0002) that silently produced wrong simulation results for standard sequential Verilog -- confirmed via Verilator 5.050 giving correct results on the same minimal repros. Verilator is now the trusted simulator for hardware/v2/ (decisions.log DEC-0004); Icarus's affected protocol-violation check was removed from the RTL and deferred architecturally to the Neural Director (DEC-0003) rather than chased further. - One real RTL bug (ERR-0003): last0 wasn't gated like valid0, letting a "last tile" tag leak into the pipeline ahead of its actual valid tile on back-to-back jobs. Fixed and verified. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
544 lines
19 KiB
Verilog
544 lines
19 KiB
Verilog
`timescale 1ns/1ps
|
|
|
|
// ================================================================
|
|
// PSRAM PAGE MODE TEST
|
|
//
|
|
// Exercises the page-mode burst-read path added to
|
|
// rtl/psram_controller.v: the configuration-register load at
|
|
// power-up, fast same-page read continuations (tAPA instead of
|
|
// tAA) including byte-enable changes between words (the pattern
|
|
// int8_memory_access.v actually produces -- LB#/UB# alternate on
|
|
// nearly every access), page-boundary crossing within an open
|
|
// session, and the one condition that closes the page (a WRITE).
|
|
//
|
|
// sim/psram_model.v enforces real datasheet timing ($fatal on any
|
|
// violation), including the new page-mode tAPA/tAA continuation
|
|
// check -- so a passing run here is a real proof that the RTL
|
|
// waits the correct number of cycles, not just that data compares
|
|
// equal.
|
|
// ================================================================
|
|
|
|
module tb;
|
|
|
|
localparam ADDR_WIDTH = 23;
|
|
localparam DATA_WIDTH = 16;
|
|
localparam CLK_FREQ_MHZ = 80;
|
|
localparam CLK_PERIOD = 12.5; // 80 MHz
|
|
|
|
reg clk;
|
|
reg rst;
|
|
|
|
reg mem_req;
|
|
reg mem_wr;
|
|
reg [ADDR_WIDTH-1:0] mem_addr;
|
|
reg [DATA_WIDTH-1:0] mem_wdata;
|
|
reg mem_lb_n;
|
|
reg mem_ub_n;
|
|
|
|
wire [DATA_WIDTH-1:0] mem_rdata;
|
|
wire mem_ready;
|
|
|
|
wire [ADDR_WIDTH-1:0] psram_a;
|
|
wire [DATA_WIDTH-1:0] psram_dq;
|
|
|
|
wire psram_ce_n;
|
|
wire psram_oe_n;
|
|
wire psram_we_n;
|
|
wire psram_lb_n;
|
|
wire psram_ub_n;
|
|
wire psram_zz_n;
|
|
|
|
// ============================================================
|
|
// Expected cycle counts (must match the RTL's own formulas)
|
|
// ============================================================
|
|
|
|
localparam integer ACCESS_CYCLES = ((70 * CLK_FREQ_MHZ) + 999) / 1000;
|
|
localparam integer PAGE_CYCLES = ((20 * CLK_FREQ_MHZ) + 999) / 1000;
|
|
|
|
// ============================================================
|
|
// DUT
|
|
// ============================================================
|
|
|
|
psram_controller #(
|
|
.ADDR_WIDTH (ADDR_WIDTH),
|
|
.DATA_WIDTH (DATA_WIDTH),
|
|
.CLK_FREQ_MHZ (CLK_FREQ_MHZ)
|
|
) dut (
|
|
.clk (clk),
|
|
.rst (rst),
|
|
|
|
.mem_req (mem_req),
|
|
.mem_wr (mem_wr),
|
|
.mem_addr (mem_addr),
|
|
.mem_wdata (mem_wdata),
|
|
.mem_lb_n (mem_lb_n),
|
|
.mem_ub_n (mem_ub_n),
|
|
|
|
.mem_rdata (mem_rdata),
|
|
.mem_ready (mem_ready),
|
|
|
|
.psram_a (psram_a),
|
|
.psram_dq (psram_dq),
|
|
|
|
.psram_ce_n(psram_ce_n),
|
|
.psram_oe_n(psram_oe_n),
|
|
.psram_we_n(psram_we_n),
|
|
.psram_lb_n(psram_lb_n),
|
|
.psram_ub_n(psram_ub_n),
|
|
.psram_zz_n(psram_zz_n)
|
|
);
|
|
|
|
psram_model #(
|
|
.ADDR_WIDTH(ADDR_WIDTH),
|
|
.DATA_WIDTH(DATA_WIDTH),
|
|
.DEPTH(4096)
|
|
) memory (
|
|
.clk (clk),
|
|
.a (psram_a),
|
|
.dq (psram_dq),
|
|
.ce_n (psram_ce_n),
|
|
.oe_n (psram_oe_n),
|
|
.we_n (psram_we_n),
|
|
.lb_n (psram_lb_n),
|
|
.ub_n (psram_ub_n),
|
|
.zz_n (psram_zz_n)
|
|
);
|
|
|
|
// ============================================================
|
|
// Clock
|
|
// ============================================================
|
|
|
|
initial begin
|
|
clk = 1'b0;
|
|
forever #(CLK_PERIOD / 2.0) clk = ~clk;
|
|
end
|
|
|
|
initial begin
|
|
$dumpfile("sim/psram_page_mode.vcd");
|
|
$dumpvars(0, tb);
|
|
end
|
|
|
|
// ============================================================
|
|
// CE# pulse counter -- counts how many times psram_ce_n rises
|
|
// (i.e. how many times a session actually closed), so tests
|
|
// can check that a page really stayed open (or really closed)
|
|
// without hand-parsing the DUT's internal state.
|
|
// ============================================================
|
|
|
|
integer ce_close_count;
|
|
integer ce_close_before_burst;
|
|
|
|
always @(posedge psram_ce_n)
|
|
ce_close_count = ce_close_count + 1;
|
|
|
|
// ============================================================
|
|
// Helpers
|
|
// ============================================================
|
|
|
|
real req_time;
|
|
real latency_ns;
|
|
|
|
task write_word;
|
|
input [ADDR_WIDTH-1:0] addr;
|
|
input [DATA_WIDTH-1:0] data;
|
|
begin
|
|
@(posedge clk);
|
|
mem_addr <= addr;
|
|
mem_wdata <= data;
|
|
mem_wr <= 1'b1;
|
|
mem_lb_n <= 1'b0;
|
|
mem_ub_n <= 1'b0;
|
|
mem_req <= 1'b1;
|
|
|
|
@(posedge clk);
|
|
mem_req <= 1'b0;
|
|
|
|
wait (mem_ready);
|
|
@(posedge clk);
|
|
end
|
|
endtask
|
|
|
|
// Full-word read, records latency (mem_req assertion -> mem_ready)
|
|
// in latency_ns for the caller to inspect.
|
|
task read_word;
|
|
input [ADDR_WIDTH-1:0] addr;
|
|
input [DATA_WIDTH-1:0] expected;
|
|
begin
|
|
@(posedge clk);
|
|
mem_addr <= addr;
|
|
mem_wr <= 1'b0;
|
|
mem_lb_n <= 1'b0;
|
|
mem_ub_n <= 1'b0;
|
|
mem_req <= 1'b1;
|
|
|
|
req_time = $realtime;
|
|
|
|
@(posedge clk);
|
|
mem_req <= 1'b0;
|
|
|
|
wait (mem_ready);
|
|
latency_ns = $realtime - req_time;
|
|
|
|
if (mem_rdata !== expected) begin
|
|
$display("READ addr=0x%06x FAIL got=0x%04x expected=0x%04x",
|
|
addr, mem_rdata, expected);
|
|
$fatal;
|
|
end
|
|
|
|
$display("READ addr=0x%06x data=0x%04x latency=%0.1fns PASS",
|
|
addr, mem_rdata, latency_ns);
|
|
|
|
@(posedge clk);
|
|
end
|
|
endtask
|
|
|
|
task read_word_be;
|
|
input [ADDR_WIDTH-1:0] addr;
|
|
input lb;
|
|
input ub;
|
|
input [DATA_WIDTH-1:0] expected;
|
|
begin
|
|
@(posedge clk);
|
|
mem_addr <= addr;
|
|
mem_wr <= 1'b0;
|
|
mem_lb_n <= lb;
|
|
mem_ub_n <= ub;
|
|
mem_req <= 1'b1;
|
|
|
|
req_time = $realtime;
|
|
|
|
@(posedge clk);
|
|
mem_req <= 1'b0;
|
|
|
|
wait (mem_ready);
|
|
latency_ns = $realtime - req_time;
|
|
|
|
if (mem_rdata !== expected) begin
|
|
$display("READ(BE) addr=0x%06x FAIL got=0x%04x expected=0x%04x",
|
|
addr, mem_rdata, expected);
|
|
$fatal;
|
|
end
|
|
|
|
$display("READ(BE) addr=0x%06x LB#=%b UB#=%b data=0x%04x latency=%0.1fns PASS",
|
|
addr, lb, ub, mem_rdata, latency_ns);
|
|
|
|
@(posedge clk);
|
|
end
|
|
endtask
|
|
|
|
task expect_ce_closes;
|
|
input integer expected_count;
|
|
input [8*48-1:0] label;
|
|
begin
|
|
if (ce_close_count !== expected_count) begin
|
|
$display("CE# close-count FAIL (%0s): got=%0d expected=%0d",
|
|
label, ce_close_count, expected_count);
|
|
$fatal;
|
|
end else begin
|
|
$display("CE# close-count OK (%0s): %0d", label, ce_close_count);
|
|
end
|
|
end
|
|
endtask
|
|
|
|
// ============================================================
|
|
// Test
|
|
// ============================================================
|
|
|
|
integer i;
|
|
reg [DATA_WIDTH-1:0] page_data [0:15];
|
|
|
|
initial begin
|
|
|
|
mem_req = 1'b0;
|
|
mem_wr = 1'b0;
|
|
mem_addr = 0;
|
|
mem_wdata = 0;
|
|
mem_lb_n = 1'b1;
|
|
mem_ub_n = 1'b1;
|
|
|
|
ce_close_count = 0;
|
|
|
|
rst = 1'b1;
|
|
repeat (5) @(posedge clk);
|
|
rst = 1'b0;
|
|
|
|
$display("");
|
|
$display("========================================");
|
|
$display("PSRAM PAGE MODE TEST");
|
|
$display("%0d MHz -- ACCESS_CYCLES=%0d PAGE_CYCLES=%0d",
|
|
CLK_FREQ_MHZ, ACCESS_CYCLES, PAGE_CYCLES);
|
|
$display("========================================");
|
|
$display("");
|
|
|
|
// Wait through STATE_INIT + the CR software-access-sequence
|
|
// (2 dummy reads + 2 writes at the top address) -- if the
|
|
// sequence violates any read/write timing the strict
|
|
// psram_model will $fatal before we ever get here.
|
|
|
|
wait (dut.state == dut.STATE_IDLE);
|
|
$display("PSRAM init + CR page-mode enable sequence complete");
|
|
$display("");
|
|
|
|
// Reset the close-counter here: boot (reset release + the
|
|
// 4-step CR sequence) legitimately toggles CE# several
|
|
// times and that's not what the test below is checking.
|
|
ce_close_count = 0;
|
|
|
|
// ========================================================
|
|
// Fill one 16-word page (addresses share bits above A[3])
|
|
// plus one word in the next page, for boundary testing.
|
|
// ========================================================
|
|
|
|
for (i = 0; i < 16; i = i + 1) begin
|
|
page_data[i] = 16'hA000 + i[15:0];
|
|
write_word(23'h000100 + i, page_data[i]);
|
|
end
|
|
|
|
write_word(23'h000110, 16'hB000); // first word of the NEXT page
|
|
|
|
expect_ce_closes(17, "after 17 writes");
|
|
|
|
// ========================================================
|
|
// Same-page sequential reads: first word pays full tAA,
|
|
// every following word in the same page must be a fast
|
|
// PAGE_CYCLES continuation with CE# never toggling.
|
|
// ========================================================
|
|
|
|
$display("");
|
|
$display("---- same-page sequential read burst ----");
|
|
|
|
read_word(23'h000100, page_data[0]);
|
|
|
|
if (latency_ns < ACCESS_CYCLES * CLK_PERIOD) begin
|
|
$display("FAIL: first word of a fresh page was faster than tAA (%0.1fns < %0.1fns)",
|
|
latency_ns, ACCESS_CYCLES * CLK_PERIOD);
|
|
$fatal;
|
|
end
|
|
|
|
// Opening a page (a completed read) does NOT close CE# --
|
|
// that's the whole point, the session stays open.
|
|
expect_ce_closes(17, "page opened, CE# still low");
|
|
|
|
for (i = 1; i < 16; i = i + 1) begin
|
|
read_word(23'h000100 + i, page_data[i]);
|
|
|
|
if (latency_ns >= ACCESS_CYCLES * CLK_PERIOD) begin
|
|
$display("FAIL: same-page word %0d did not use the fast path (%0.1fns >= tAA %0.1fns)",
|
|
i, latency_ns, ACCESS_CYCLES * CLK_PERIOD);
|
|
$fatal;
|
|
end
|
|
|
|
if (latency_ns > PAGE_CYCLES * CLK_PERIOD + CLK_PERIOD) begin
|
|
$display("FAIL: same-page word %0d slower than expected (%0.1fns)",
|
|
i, latency_ns);
|
|
$fatal;
|
|
end
|
|
end
|
|
|
|
// CE# must NOT have toggled again across the whole 16-word
|
|
// burst -- it should still be exactly one open session.
|
|
expect_ce_closes(17, "still one open session after 16-word burst");
|
|
|
|
$display("PAGE MODE BURST SPEEDUP CONFIRMED");
|
|
$display("");
|
|
|
|
// ========================================================
|
|
// Page-boundary crossing while CE# stays open: a READ to a
|
|
// different page still doesn't need to close CE# (only a
|
|
// WRITE does) -- but that one word pays the full tAA per
|
|
// the datasheet rule ("any change in addresses A[4] or
|
|
// higher initiates a new tAA access time"), then
|
|
// continuations in the new page are fast again.
|
|
// ========================================================
|
|
|
|
$display("---- page-boundary crossing (still open) ----");
|
|
|
|
read_word(23'h000110, 16'hB000);
|
|
|
|
if (latency_ns < ACCESS_CYCLES * CLK_PERIOD) begin
|
|
$display("FAIL: page-crossing word was not full tAA (%0.1fns)", latency_ns);
|
|
$fatal;
|
|
end
|
|
|
|
expect_ce_closes(17, "page crossing did not toggle CE#");
|
|
|
|
// A WRITE, unlike a READ, always closes the page -- and it
|
|
// costs two CE# pulses: one to close the read session that
|
|
// was open, one for the write's own transaction.
|
|
write_word(23'h000111, 16'hB001);
|
|
expect_ce_closes(19, "write after page crossing closes the session");
|
|
|
|
read_word(23'h000111, 16'hB001);
|
|
expect_ce_closes(19, "fresh read reopens its own session (no close)");
|
|
|
|
read_word(23'h000112, 16'h0000); // untouched location -> reset value
|
|
if (latency_ns >= ACCESS_CYCLES * CLK_PERIOD) begin
|
|
$display("FAIL: second word of new page-crossing session should be fast");
|
|
$fatal;
|
|
end
|
|
expect_ce_closes(19, "same-page continuation after the crossing");
|
|
|
|
$display("");
|
|
|
|
// ========================================================
|
|
// A WRITE mid-session must close the page.
|
|
// ========================================================
|
|
|
|
$display("---- write closes an open page ----");
|
|
|
|
// Still the SAME open session as above (page 0x11) -- a
|
|
// READ to a different page (0x10) is just another
|
|
// page-miss continuation, not a close.
|
|
read_word(23'h000100, page_data[0]);
|
|
expect_ce_closes(19, "page-miss read continuation (still open)");
|
|
|
|
read_word(23'h000101, page_data[1]); // fast continuation
|
|
if (latency_ns >= ACCESS_CYCLES * CLK_PERIOD) begin
|
|
$display("FAIL: continuation before the write should be fast");
|
|
$fatal;
|
|
end
|
|
expect_ce_closes(19, "still open before the write");
|
|
|
|
write_word(23'h000200, 16'hC0DE);
|
|
expect_ce_closes(21, "write forces a close (2 pulses: close + write)");
|
|
|
|
read_word(23'h000200, 16'hC0DE);
|
|
expect_ce_closes(21, "post-write read opens a fresh session (no close)");
|
|
|
|
$display("");
|
|
|
|
// ========================================================
|
|
// Byte-enable changes must NOT close the page.
|
|
//
|
|
// int8_memory_access.v alternates LB#/UB# on essentially
|
|
// every access (byte-granular reads over the 16-bit PSRAM
|
|
// bus, addr[0] selects the byte) -- this is the actual
|
|
// real-world access pattern (e.g. graph_engine's edge-list
|
|
// gather), so it must stay on the fast page-hit path, not
|
|
// force a close on every single byte.
|
|
// ========================================================
|
|
|
|
$display("---- byte-enable changes stay on the fast path ----");
|
|
|
|
read_word(23'h000102, page_data[2]);
|
|
expect_ce_closes(21, "page-miss read continuation, still open");
|
|
|
|
read_word_be(23'h000102, 1'b0, 1'b1, {8'h00, page_data[2][7:0]});
|
|
expect_ce_closes(21, "LB# only -- still open, still fast");
|
|
if (latency_ns >= ACCESS_CYCLES * CLK_PERIOD) begin
|
|
$display("FAIL: byte-enable-only change should stay on the fast path");
|
|
$fatal;
|
|
end
|
|
|
|
read_word_be(23'h000102, 1'b1, 1'b0, {page_data[2][15:8], 8'h00});
|
|
expect_ce_closes(21, "UB# only -- still open, still fast");
|
|
if (latency_ns >= ACCESS_CYCLES * CLK_PERIOD) begin
|
|
$display("FAIL: byte-enable-only change should stay on the fast path");
|
|
$fatal;
|
|
end
|
|
|
|
read_word_be(23'h000103, 1'b0, 1'b1, {8'h00, page_data[3][7:0]});
|
|
expect_ce_closes(21, "new address + byte-enable change together -- still fast");
|
|
if (latency_ns >= ACCESS_CYCLES * CLK_PERIOD) begin
|
|
$display("FAIL: address+byte-enable change together should stay fast");
|
|
$fatal;
|
|
end
|
|
|
|
$display("");
|
|
|
|
// ========================================================
|
|
// tCEM safety timeout: an open page with no further
|
|
// requests must close itself well before the 8us CE#-low
|
|
// refresh limit (PAGE_TIMEOUT_CYCLES, ~6us of margin) --
|
|
// not just "eventually", but on its own, unprompted.
|
|
// ========================================================
|
|
|
|
$display("---- tCEM idle timeout closes an unattended open page ----");
|
|
|
|
read_word(23'h000104, page_data[4]); // still page 0x10, still open
|
|
expect_ce_closes(21, "same page continuation, waiting idle now");
|
|
|
|
repeat (dut.PAGE_TIMEOUT_CYCLES + 4) @(posedge clk);
|
|
|
|
expect_ce_closes(22, "idle page auto-closed by the tCEM timeout");
|
|
|
|
read_word(23'h000104, page_data[4]); // must still work correctly
|
|
expect_ce_closes(22, "fresh session opened, not yet closed again");
|
|
|
|
$display("");
|
|
|
|
// ========================================================
|
|
// tCEM budget mid-burst: a long run of back-to-back
|
|
// same-page HITS (never idle, never a write) must still be
|
|
// split before the limit -- not just the idle-timeout case
|
|
// above. sim/psram_model.v independently enforces the real
|
|
// 8us tCEM hard limit ($fatal on violation); this is a real
|
|
// safety net, not a rubber stamp, so a passing run here is
|
|
// genuine proof the RTL splits the burst in time, with
|
|
// margin, not just "in simulation it happened to work".
|
|
// ========================================================
|
|
|
|
$display("---- tCEM budget forces a split mid-burst (never idle) ----");
|
|
|
|
for (i = 0; i < 16; i = i + 1) begin
|
|
page_data[i] = 16'hC000 + i[15:0];
|
|
write_word(23'h000500 + i, page_data[i]);
|
|
end
|
|
|
|
read_word(23'h000500, page_data[0]); // opens page 0x50
|
|
ce_close_before_burst = ce_close_count;
|
|
|
|
// 2*PAGE_TIMEOUT_CYCLES/16 round trips through this 16-word
|
|
// page comfortably crosses PAGE_TIMEOUT_CYCLES worth of
|
|
// STATE_READ time (each hit costs PAGE_CYCLES inside
|
|
// STATE_READ, plus this task's own idle cycles between
|
|
// requests, both counted by hold_cycles) while never once
|
|
// idling long enough on its own to hit the separate
|
|
// idle-timeout path above -- this is the "busy" case.
|
|
for (i = 0; i < 2 * dut.PAGE_TIMEOUT_CYCLES; i = i + 1)
|
|
read_word(23'h000500 + (i % 16), page_data[i % 16]);
|
|
|
|
if (ce_close_count <= ce_close_before_burst) begin
|
|
$display("FAIL: expected at least one mid-burst split, CE# never toggled (before=%0d after=%0d)",
|
|
ce_close_before_burst, ce_close_count);
|
|
$fatal;
|
|
end
|
|
|
|
$display("CE# split during long burst confirmed: %0d -> %0d closes",
|
|
ce_close_before_burst, ce_close_count);
|
|
$display("(no tCEM $fatal from psram_model.v -- split happened with margin)");
|
|
$display("");
|
|
|
|
// ========================================================
|
|
// Data-integrity stress: random-ish scattered pages,
|
|
// mixing writes and page-local read bursts.
|
|
// ========================================================
|
|
|
|
$display("---- mixed stress: scattered pages + local bursts ----");
|
|
|
|
for (i = 0; i < 64; i = i + 1) begin
|
|
reg [ADDR_WIDTH-1:0] base;
|
|
reg [DATA_WIDTH-1:0] val;
|
|
integer j;
|
|
|
|
base = ((i * 8191) ^ (i << 6)) & 23'h000FF0; // page-aligned
|
|
val = (i * 733) ^ 16'h5A5A;
|
|
|
|
for (j = 0; j < 4; j = j + 1)
|
|
write_word(base + j, val + j[15:0]);
|
|
|
|
for (j = 0; j < 4; j = j + 1)
|
|
read_word(base + j, val + j[15:0]);
|
|
end
|
|
|
|
$display("");
|
|
$display("========================================");
|
|
$display("PSRAM PAGE MODE TEST PASSED");
|
|
$display("========================================");
|
|
$display("");
|
|
|
|
$finish;
|
|
end
|
|
|
|
endmodule
|