PSRAM page-mode read burst support in psram_controller.v: enables the ISSI IS66WVE4M16EBLL-70BLI's page mode via its configuration-register software-access sequence at boot (disabled by default on the real chip), then keeps CE#/OE# asserted after a read so a same-page continuation only pays tAPA (20ns) instead of a full tAA (70ns) random access, with automatic tCEM-safe session closing. Only a WRITE closes the page -- byte-enable changes do not, since int8_memory_access.v alternates them on nearly every access and an early implementation attempt that treated them as a close condition measured a real regression (53.25->61.25 cycles/edge) before being corrected (53.25->37.53 cycles/edge, +42% gather bandwidth). sim/psram_model.v gained independent tAPA/tAA and tCEM enforcement (with a real Verilog same-timestep event-ordering race found and fixed via a #0 sync) so the regression proves real timing compliance, not just data correctness. New sim/psram_page_mode_tb.v; full 26-file regression suite re-run clean. Real nextpnr-ecp5 Fmax re-measured on the full spi_neuron_top system: 75.73MHz (P2, up from 55.59MHz) and 65.13MHz (P8) -- still under the 80MHz target but not regressed, with the critical path confirmed (not assumed) to remain entirely inside neuron_parallel's accumulate chain, never psram_controller. Also includes this session's other already-validated work: the graph engine (Type #2 sparse-graph network: act_buffer, graph_engine, netasm host assembler), real CABGA381 pinout (.lpf, place&route verified) and physical IRQ_N/DATA_READY_N pins, and Phase 7 timing closure logs -- all previously uncommitted, documented in WORKLOG.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LH3jPeJ3eFMfF2v8SQhpkk
783 lines
26 KiB
Verilog
783 lines
26 KiB
Verilog
module psram_controller #(
|
|
parameter ADDR_WIDTH = 23,
|
|
parameter DATA_WIDTH = 16,
|
|
parameter CLK_FREQ_MHZ = 80
|
|
)(
|
|
input wire clk,
|
|
input wire rst,
|
|
|
|
// ============================================================
|
|
// Memory Interface side
|
|
// ============================================================
|
|
|
|
input wire mem_req,
|
|
input wire mem_wr,
|
|
input wire [ADDR_WIDTH-1:0] mem_addr,
|
|
input wire [DATA_WIDTH-1:0] mem_wdata,
|
|
input wire mem_lb_n,
|
|
input wire mem_ub_n,
|
|
|
|
output reg [DATA_WIDTH-1:0] mem_rdata,
|
|
output reg mem_ready,
|
|
|
|
// ============================================================
|
|
// PSRAM physical interface
|
|
// ============================================================
|
|
|
|
output reg [ADDR_WIDTH-1:0] psram_a,
|
|
|
|
inout wire [DATA_WIDTH-1:0] psram_dq,
|
|
|
|
output reg psram_ce_n,
|
|
output reg psram_oe_n,
|
|
output reg psram_we_n,
|
|
output reg psram_lb_n,
|
|
output reg psram_ub_n,
|
|
output reg psram_zz_n
|
|
);
|
|
|
|
// ============================================================
|
|
// Timing
|
|
// ============================================================
|
|
//
|
|
// ISSI IS66WVE4M16EBLL-70BLI (-70 speed grade): async random
|
|
// access is 70ns (tAA/tRC). The chip also supports PAGE MODE
|
|
// reads: once an initial tAA access has been done, further
|
|
// reads to the same 16-word page (address bits above A[3])
|
|
// only need to wait tAPA/tPC = 20ns before the next word is
|
|
// valid, because CE#/OE# stay asserted and only the low
|
|
// address bits change (datasheet Fig. 4). Page mode only
|
|
// applies to reads; writes always pay the full random-access
|
|
// time.
|
|
//
|
|
// Page mode read access is DISABLED at power-up (CR[7] = 0)
|
|
// and must be turned on with a configuration-register write
|
|
// before it can be relied on -- see STATE_CR_INIT below.
|
|
// ============================================================
|
|
|
|
localparam integer ACCESS_CYCLES =
|
|
((70 * CLK_FREQ_MHZ) + 999) / 1000;
|
|
|
|
localparam integer PAGE_CYCLES =
|
|
((20 * CLK_FREQ_MHZ) + 999) / 1000;
|
|
|
|
localparam integer INIT_CYCLES =
|
|
150 * CLK_FREQ_MHZ;
|
|
|
|
localparam integer COUNTER_WIDTH =
|
|
(INIT_CYCLES <= 1) ? 1 : $clog2(INIT_CYCLES + 1);
|
|
|
|
// A page is kept open (CE#/OE# held low between transactions)
|
|
// only up to a safety margin under tCEM (8us max CE# low
|
|
// pulse, refresh-related). 6us leaves comfortable headroom.
|
|
|
|
localparam integer PAGE_HOLD_NS = 6000;
|
|
|
|
localparam integer PAGE_TIMEOUT_CYCLES =
|
|
((PAGE_HOLD_NS * CLK_FREQ_MHZ) + 999) / 1000;
|
|
|
|
localparam integer HOLD_WIDTH =
|
|
(PAGE_TIMEOUT_CYCLES <= 1) ? 1 : $clog2(PAGE_TIMEOUT_CYCLES + 1);
|
|
|
|
// ============================================================
|
|
// Configuration register value
|
|
//
|
|
// Loaded once at power-up via the software-access sequence
|
|
// (datasheet Fig. 6/7 -- 2 dummy reads + 2 writes at the
|
|
// highest chip address; the first write is a required 0x0000
|
|
// "unlock", the second carries the real value). Bit layout is
|
|
// the standard ISSI CellularRAM CR (verified against the
|
|
// sibling IS66WVE1M16BLL datasheet -- same CR layout is used
|
|
// across the whole BLL family; re-check against the exact
|
|
// -EBLL datasheet at hardware bring-up):
|
|
//
|
|
// bit 7 Page 1 = page-mode reads enabled
|
|
// bits6:5 TCR 11 = +85C refresh (matches power-on default)
|
|
// bit 4 Sleep 1 = PAR on ZZ# (matches power-on default)
|
|
// bits2:0 PAR 000 = full-array refresh (default)
|
|
//
|
|
// i.e. power-on default (0x0070) with only the Page bit set.
|
|
// ============================================================
|
|
|
|
localparam [DATA_WIDTH-1:0] CR_VALUE = 16'h00F0;
|
|
|
|
// ============================================================
|
|
// State machine
|
|
// ============================================================
|
|
|
|
localparam [3:0]
|
|
STATE_INIT = 4'd0,
|
|
STATE_IDLE = 4'd1,
|
|
STATE_READ = 4'd2,
|
|
STATE_WRITE = 4'd3,
|
|
STATE_WRITE_WAIT = 4'd4,
|
|
STATE_CR_INIT = 4'd5,
|
|
STATE_PAGE_OPEN = 4'd6,
|
|
STATE_PAGE_CLOSE = 4'd7,
|
|
STATE_PAGE_REOPEN = 4'd8;
|
|
|
|
reg [3:0] state;
|
|
reg [COUNTER_WIDTH-1:0] counter;
|
|
|
|
// ============================================================
|
|
// Latched transaction
|
|
// ============================================================
|
|
|
|
reg [ADDR_WIDTH-1:0] address_reg;
|
|
reg [DATA_WIDTH-1:0] wdata_reg;
|
|
reg wr_reg;
|
|
|
|
// ============================================================
|
|
// Latched byte enables
|
|
//
|
|
// Active LOW:
|
|
// 0 = byte enabled
|
|
// 1 = byte disabled
|
|
// ============================================================
|
|
|
|
reg lb_reg;
|
|
reg ub_reg;
|
|
|
|
// ============================================================
|
|
// Page-mode bookkeeping
|
|
// ============================================================
|
|
|
|
reg page_hit_reg; // current READ: fast (page) vs slow (tAA)
|
|
reg [HOLD_WIDTH-1:0] hold_cycles; // cycles CE# has been held low this session
|
|
|
|
// ============================================================
|
|
// Configuration-register load sequence
|
|
// ============================================================
|
|
|
|
reg cr_init_active;
|
|
reg [2:0] cr_step;
|
|
|
|
// ============================================================
|
|
// PSRAM data bus control
|
|
// ============================================================
|
|
|
|
reg [DATA_WIDTH-1:0] dq_out;
|
|
reg dq_oe;
|
|
|
|
assign psram_dq =
|
|
dq_oe ? dq_out : {DATA_WIDTH{1'bz}};
|
|
|
|
// ============================================================
|
|
// Main state machine
|
|
// ============================================================
|
|
|
|
always @(posedge clk) begin
|
|
|
|
if (rst) begin
|
|
|
|
// ----------------------------------------------------
|
|
// State
|
|
// ----------------------------------------------------
|
|
|
|
state <= STATE_INIT;
|
|
counter <= 0;
|
|
|
|
// ----------------------------------------------------
|
|
// Transaction registers
|
|
// ----------------------------------------------------
|
|
|
|
address_reg <= {ADDR_WIDTH{1'b0}};
|
|
wdata_reg <= {DATA_WIDTH{1'b0}};
|
|
wr_reg <= 1'b0;
|
|
|
|
// Byte enables disabled during reset
|
|
lb_reg <= 1'b1;
|
|
ub_reg <= 1'b1;
|
|
|
|
// ----------------------------------------------------
|
|
// Page-mode bookkeeping
|
|
// ----------------------------------------------------
|
|
|
|
page_hit_reg <= 1'b0;
|
|
hold_cycles <= 0;
|
|
|
|
cr_init_active <= 1'b0;
|
|
cr_step <= 0;
|
|
|
|
// ----------------------------------------------------
|
|
// Memory interface
|
|
// ----------------------------------------------------
|
|
|
|
mem_rdata <= {DATA_WIDTH{1'b0}};
|
|
mem_ready <= 1'b0;
|
|
|
|
// ----------------------------------------------------
|
|
// PSRAM address
|
|
// ----------------------------------------------------
|
|
|
|
psram_a <= {ADDR_WIDTH{1'b0}};
|
|
|
|
// ----------------------------------------------------
|
|
// PSRAM control
|
|
// ----------------------------------------------------
|
|
|
|
psram_ce_n <= 1'b1;
|
|
psram_oe_n <= 1'b1;
|
|
psram_we_n <= 1'b1;
|
|
|
|
psram_lb_n <= 1'b1;
|
|
psram_ub_n <= 1'b1;
|
|
|
|
psram_zz_n <= 1'b1;
|
|
|
|
// ----------------------------------------------------
|
|
// Data bus
|
|
// ----------------------------------------------------
|
|
|
|
dq_out <= {DATA_WIDTH{1'b0}};
|
|
dq_oe <= 1'b0;
|
|
|
|
end else begin
|
|
|
|
// mem_ready is a one-cycle pulse
|
|
mem_ready <= 1'b0;
|
|
|
|
case (state)
|
|
|
|
// =================================================
|
|
// PSRAM power-up initialization
|
|
// =================================================
|
|
|
|
STATE_INIT: begin
|
|
|
|
psram_ce_n <= 1'b1;
|
|
psram_oe_n <= 1'b1;
|
|
psram_we_n <= 1'b1;
|
|
|
|
psram_lb_n <= 1'b1;
|
|
psram_ub_n <= 1'b1;
|
|
|
|
psram_zz_n <= 1'b1;
|
|
|
|
dq_oe <= 1'b0;
|
|
|
|
if (counter == INIT_CYCLES - 1) begin
|
|
|
|
counter <= 0;
|
|
cr_step <= 0;
|
|
state <= STATE_CR_INIT;
|
|
|
|
end else begin
|
|
|
|
counter <= counter + 1'b1;
|
|
|
|
end
|
|
end
|
|
|
|
// =================================================
|
|
// Configuration-register load
|
|
//
|
|
// Software-access sequence (datasheet Fig. 6):
|
|
// 2 dummy reads + 2 writes at the highest chip
|
|
// address, each a fully separate CE# pulse. The
|
|
// first write clocks in 0x0000 (unlock), the
|
|
// second clocks in the real CR value. Reuses the
|
|
// ordinary STATE_READ/STATE_WRITE datapath so it
|
|
// is checked by the exact same timing as every
|
|
// other transaction.
|
|
// =================================================
|
|
|
|
STATE_CR_INIT: begin
|
|
|
|
if (cr_step == 3'd4) begin
|
|
|
|
cr_init_active <= 1'b0;
|
|
state <= STATE_IDLE;
|
|
|
|
end else begin
|
|
|
|
cr_init_active <= 1'b1;
|
|
|
|
address_reg <= {ADDR_WIDTH{1'b1}};
|
|
lb_reg <= 1'b0;
|
|
ub_reg <= 1'b0;
|
|
|
|
psram_a <= {ADDR_WIDTH{1'b1}};
|
|
psram_lb_n <= 1'b0;
|
|
psram_ub_n <= 1'b0;
|
|
psram_ce_n <= 1'b0;
|
|
psram_zz_n <= 1'b1;
|
|
|
|
counter <= 0;
|
|
page_hit_reg <= 1'b0;
|
|
|
|
if (cr_step < 3'd2) begin
|
|
|
|
// Dummy READ steps
|
|
wr_reg <= 1'b0;
|
|
dq_oe <= 1'b0;
|
|
|
|
psram_we_n <= 1'b1;
|
|
psram_oe_n <= 1'b0;
|
|
|
|
state <= STATE_READ;
|
|
|
|
end else begin
|
|
|
|
// WRITE steps: 0x0000 unlock, then real CR value
|
|
wr_reg <= 1'b1;
|
|
wdata_reg <= (cr_step == 3'd2) ?
|
|
{DATA_WIDTH{1'b0}} : CR_VALUE;
|
|
dq_out <= (cr_step == 3'd2) ?
|
|
{DATA_WIDTH{1'b0}} : CR_VALUE;
|
|
dq_oe <= 1'b1;
|
|
|
|
psram_we_n <= 1'b0;
|
|
psram_oe_n <= 1'b1;
|
|
|
|
state <= STATE_WRITE;
|
|
|
|
end
|
|
|
|
cr_step <= cr_step + 1'b1;
|
|
|
|
end
|
|
end
|
|
|
|
// =================================================
|
|
// Idle
|
|
// =================================================
|
|
|
|
STATE_IDLE: begin
|
|
|
|
psram_ce_n <= 1'b1;
|
|
psram_oe_n <= 1'b1;
|
|
psram_we_n <= 1'b1;
|
|
|
|
psram_lb_n <= 1'b1;
|
|
psram_ub_n <= 1'b1;
|
|
|
|
psram_zz_n <= 1'b1;
|
|
|
|
dq_oe <= 1'b0;
|
|
|
|
if (mem_req) begin
|
|
|
|
// ------------------------------------------------
|
|
// Latch transaction
|
|
// ------------------------------------------------
|
|
|
|
address_reg <= mem_addr;
|
|
wdata_reg <= mem_wdata;
|
|
wr_reg <= mem_wr;
|
|
|
|
// ------------------------------------------------
|
|
// Latch byte enables
|
|
// ------------------------------------------------
|
|
|
|
lb_reg <= mem_lb_n;
|
|
ub_reg <= mem_ub_n;
|
|
|
|
// ------------------------------------------------
|
|
// Address
|
|
// ------------------------------------------------
|
|
|
|
psram_a <= mem_addr;
|
|
|
|
// ------------------------------------------------
|
|
// Apply byte enables immediately
|
|
// ------------------------------------------------
|
|
|
|
psram_lb_n <= mem_lb_n;
|
|
psram_ub_n <= mem_ub_n;
|
|
|
|
psram_ce_n <= 1'b0;
|
|
|
|
counter <= 0;
|
|
|
|
// =================================================
|
|
// WRITE
|
|
// =================================================
|
|
|
|
if (mem_wr) begin
|
|
|
|
dq_out <= mem_wdata;
|
|
dq_oe <= 1'b1;
|
|
|
|
psram_we_n <= 1'b0;
|
|
psram_oe_n <= 1'b1;
|
|
|
|
state <= STATE_WRITE;
|
|
|
|
end
|
|
|
|
// =================================================
|
|
// READ (fresh session -- always full tAA)
|
|
// =================================================
|
|
|
|
else begin
|
|
|
|
dq_oe <= 1'b0;
|
|
|
|
psram_we_n <= 1'b1;
|
|
psram_oe_n <= 1'b0;
|
|
|
|
page_hit_reg <= 1'b0;
|
|
hold_cycles <= 0;
|
|
|
|
state <= STATE_READ;
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
// =================================================
|
|
// READ
|
|
//
|
|
// Wait ACCESS_CYCLES (tAA, fresh/random access) or
|
|
// PAGE_CYCLES (tAPA, same-page continuation) as
|
|
// selected by page_hit_reg.
|
|
// =================================================
|
|
|
|
STATE_READ: begin
|
|
|
|
psram_ce_n <= 1'b0;
|
|
psram_oe_n <= 1'b0;
|
|
psram_we_n <= 1'b1;
|
|
|
|
psram_lb_n <= lb_reg;
|
|
psram_ub_n <= ub_reg;
|
|
|
|
psram_zz_n <= 1'b1;
|
|
|
|
dq_oe <= 1'b0;
|
|
|
|
hold_cycles <= hold_cycles + 1'b1;
|
|
|
|
if (counter ==
|
|
(page_hit_reg ? PAGE_CYCLES : ACCESS_CYCLES) - 1) begin
|
|
|
|
// ------------------------------------------------
|
|
// Capture PSRAM data
|
|
// ------------------------------------------------
|
|
|
|
mem_rdata <= psram_dq;
|
|
mem_ready <= 1'b1;
|
|
|
|
counter <= 0;
|
|
|
|
if (cr_init_active) begin
|
|
|
|
// Close between CR software-access-sequence
|
|
// steps (datasheet Fig. 6 -- 4 separate CE#
|
|
// pulses).
|
|
|
|
psram_ce_n <= 1'b1;
|
|
psram_oe_n <= 1'b1;
|
|
psram_lb_n <= 1'b1;
|
|
psram_ub_n <= 1'b1;
|
|
|
|
state <= STATE_CR_INIT;
|
|
|
|
end else begin
|
|
|
|
// Keep the page open: CE#/OE# stay
|
|
// asserted so a following same-page read
|
|
// can skip straight to a fast PAGE_CYCLES
|
|
// access instead of a full tAA.
|
|
|
|
state <= STATE_PAGE_OPEN;
|
|
|
|
end
|
|
|
|
end else begin
|
|
|
|
counter <= counter + 1'b1;
|
|
|
|
end
|
|
end
|
|
|
|
// =================================================
|
|
// PAGE OPEN
|
|
//
|
|
// A read just completed and CE#/OE# were left
|
|
// asserted. From here:
|
|
// - a same-page READ continues immediately with
|
|
// only the address/byte-enable lines changing
|
|
// (fast PAGE_CYCLES access) -- byte enables are
|
|
// free to change here too, since
|
|
// int8_memory_access.v alternates LB#/UB# on
|
|
// nearly every byte-granular access and the
|
|
// datasheet's page timing (Fig. 4) is defined
|
|
// purely on the address bus and CE#/OE#;
|
|
// - a different-page READ can also continue
|
|
// without a CE# toggle, but pays the full
|
|
// ACCESS_CYCLES for that one word (real chip
|
|
// behaviour: any change at A[4] or above needs
|
|
// a fresh tAA);
|
|
// - a WRITE, or exceeding the tCEM safety margin,
|
|
// closes the page first.
|
|
// =================================================
|
|
|
|
STATE_PAGE_OPEN: begin
|
|
|
|
psram_ce_n <= 1'b0;
|
|
psram_oe_n <= 1'b0;
|
|
psram_we_n <= 1'b1;
|
|
|
|
psram_lb_n <= lb_reg;
|
|
psram_ub_n <= ub_reg;
|
|
|
|
psram_zz_n <= 1'b1;
|
|
|
|
dq_oe <= 1'b0;
|
|
|
|
if (mem_req) begin
|
|
|
|
if (mem_wr ||
|
|
(hold_cycles >= PAGE_TIMEOUT_CYCLES)) begin
|
|
|
|
// Latch the new transaction, then close
|
|
// the page before servicing it.
|
|
|
|
address_reg <= mem_addr;
|
|
wdata_reg <= mem_wdata;
|
|
wr_reg <= mem_wr;
|
|
lb_reg <= mem_lb_n;
|
|
ub_reg <= mem_ub_n;
|
|
|
|
psram_ce_n <= 1'b1;
|
|
psram_oe_n <= 1'b1;
|
|
psram_lb_n <= 1'b1;
|
|
psram_ub_n <= 1'b1;
|
|
|
|
state <= STATE_PAGE_CLOSE;
|
|
|
|
end else begin
|
|
|
|
// READ continuation: address and byte
|
|
// enables change freely, CE#/OE# stay
|
|
// low. int8_memory_access.v alternates
|
|
// LB#/UB# on essentially every access
|
|
// (byte-granular reads over the 16-bit
|
|
// bus) so byte-enable changes are the
|
|
// common case, not an exception -- the
|
|
// datasheet's page-mode timing (Fig. 4)
|
|
// is defined purely on the address bus
|
|
// and CE#/OE#, and says nothing that
|
|
// requires LB#/UB# to stay fixed.
|
|
|
|
page_hit_reg <=
|
|
(mem_addr[ADDR_WIDTH-1:4] ==
|
|
address_reg[ADDR_WIDTH-1:4]);
|
|
|
|
address_reg <= mem_addr;
|
|
psram_a <= mem_addr;
|
|
|
|
lb_reg <= mem_lb_n;
|
|
ub_reg <= mem_ub_n;
|
|
psram_lb_n <= mem_lb_n;
|
|
psram_ub_n <= mem_ub_n;
|
|
|
|
counter <= 0;
|
|
|
|
state <= STATE_READ;
|
|
|
|
end
|
|
|
|
end else begin
|
|
|
|
// Idle inside an open page -- respect tCEM.
|
|
|
|
if (hold_cycles >= PAGE_TIMEOUT_CYCLES) begin
|
|
|
|
psram_ce_n <= 1'b1;
|
|
psram_oe_n <= 1'b1;
|
|
psram_lb_n <= 1'b1;
|
|
psram_ub_n <= 1'b1;
|
|
|
|
state <= STATE_IDLE;
|
|
|
|
end else begin
|
|
|
|
hold_cycles <= hold_cycles + 1'b1;
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
// =================================================
|
|
// PAGE CLOSE
|
|
//
|
|
// One fully-deasserted cycle before reopening for a
|
|
// WRITE (or a timed-out page): guarantees OE# has
|
|
// been high for a full cycle (>= tHZ) before the
|
|
// controller starts driving DQ, avoiding bus
|
|
// contention with the PSRAM's own output buffer.
|
|
// =================================================
|
|
|
|
STATE_PAGE_CLOSE: begin
|
|
|
|
psram_ce_n <= 1'b1;
|
|
psram_oe_n <= 1'b1;
|
|
psram_we_n <= 1'b1;
|
|
|
|
psram_lb_n <= 1'b1;
|
|
psram_ub_n <= 1'b1;
|
|
|
|
psram_zz_n <= 1'b1;
|
|
|
|
dq_oe <= 1'b0;
|
|
|
|
state <= STATE_PAGE_REOPEN;
|
|
|
|
end
|
|
|
|
// =================================================
|
|
// PAGE REOPEN
|
|
//
|
|
// Dispatches the transaction latched just before
|
|
// STATE_PAGE_CLOSE, exactly like STATE_IDLE would.
|
|
// =================================================
|
|
|
|
STATE_PAGE_REOPEN: begin
|
|
|
|
psram_a <= address_reg;
|
|
|
|
psram_lb_n <= lb_reg;
|
|
psram_ub_n <= ub_reg;
|
|
|
|
psram_zz_n <= 1'b1;
|
|
|
|
counter <= 0;
|
|
|
|
if (wr_reg) begin
|
|
|
|
dq_out <= wdata_reg;
|
|
dq_oe <= 1'b1;
|
|
|
|
psram_ce_n <= 1'b0;
|
|
psram_we_n <= 1'b0;
|
|
psram_oe_n <= 1'b1;
|
|
|
|
state <= STATE_WRITE;
|
|
|
|
end else begin
|
|
|
|
dq_oe <= 1'b0;
|
|
|
|
psram_ce_n <= 1'b0;
|
|
psram_we_n <= 1'b1;
|
|
psram_oe_n <= 1'b0;
|
|
|
|
page_hit_reg <= 1'b0;
|
|
hold_cycles <= 0;
|
|
|
|
state <= STATE_READ;
|
|
|
|
end
|
|
end
|
|
|
|
// =================================================
|
|
// WRITE
|
|
// =================================================
|
|
|
|
STATE_WRITE: begin
|
|
|
|
psram_ce_n <= 1'b0;
|
|
psram_oe_n <= 1'b1;
|
|
psram_we_n <= 1'b0;
|
|
|
|
psram_lb_n <= lb_reg;
|
|
psram_ub_n <= ub_reg;
|
|
|
|
psram_zz_n <= 1'b1;
|
|
|
|
dq_oe <= 1'b1;
|
|
|
|
if (counter == ACCESS_CYCLES - 1) begin
|
|
|
|
// ------------------------------------------------
|
|
// End WE# pulse
|
|
// ------------------------------------------------
|
|
|
|
psram_we_n <= 1'b1;
|
|
|
|
counter <= 0;
|
|
state <= STATE_WRITE_WAIT;
|
|
|
|
end else begin
|
|
|
|
counter <= counter + 1'b1;
|
|
|
|
end
|
|
end
|
|
|
|
// =================================================
|
|
// WRITE WAIT
|
|
//
|
|
// Keep CE#/LB#/UB# active for the final write hold
|
|
// interval before releasing the transaction.
|
|
// =================================================
|
|
|
|
STATE_WRITE_WAIT: begin
|
|
|
|
psram_ce_n <= 1'b0;
|
|
psram_oe_n <= 1'b1;
|
|
psram_we_n <= 1'b1;
|
|
|
|
psram_lb_n <= lb_reg;
|
|
psram_ub_n <= ub_reg;
|
|
|
|
psram_zz_n <= 1'b1;
|
|
|
|
dq_oe <= 1'b0;
|
|
|
|
// ------------------------------------------------
|
|
// Release PSRAM
|
|
// ------------------------------------------------
|
|
|
|
psram_ce_n <= 1'b1;
|
|
psram_lb_n <= 1'b1;
|
|
psram_ub_n <= 1'b1;
|
|
|
|
// ------------------------------------------------
|
|
// Transaction complete
|
|
// ------------------------------------------------
|
|
|
|
mem_ready <= 1'b1;
|
|
|
|
state <= cr_init_active ? STATE_CR_INIT : STATE_IDLE;
|
|
end
|
|
|
|
// =================================================
|
|
// Default recovery
|
|
// =================================================
|
|
|
|
default: begin
|
|
|
|
state <= STATE_INIT;
|
|
counter <= 0;
|
|
|
|
psram_ce_n <= 1'b1;
|
|
psram_oe_n <= 1'b1;
|
|
psram_we_n <= 1'b1;
|
|
|
|
psram_lb_n <= 1'b1;
|
|
psram_ub_n <= 1'b1;
|
|
|
|
psram_zz_n <= 1'b1;
|
|
|
|
dq_oe <= 1'b0;
|
|
|
|
lb_reg <= 1'b1;
|
|
ub_reg <= 1'b1;
|
|
|
|
page_hit_reg <= 1'b0;
|
|
hold_cycles <= 0;
|
|
cr_init_active <= 1'b0;
|
|
cr_step <= 0;
|
|
|
|
end
|
|
|
|
endcase
|
|
end
|
|
end
|
|
|
|
endmodule
|