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
859 lines
30 KiB
Verilog
859 lines
30 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;
|
|
|
|
// ============================================================
|
|
// Early-request latch (bug found + fixed 2026-09-04, see
|
|
// WORKLOG.md flash-subsystem F2 entry for the full writeup)
|
|
//
|
|
// STATE_INIT (150us power-up wait) and STATE_CR_INIT (the 4-step
|
|
// software-access sequence) do not check `mem_req` at all -- an
|
|
// external request arriving during that window was previously
|
|
// silently LOST (mem_req is a one-cycle pulse from
|
|
// int8_memory_access.v with no retry), while the caller sat in
|
|
// its own WAIT state watching for `mem_ready`. Meanwhile
|
|
// STATE_READ/STATE_WRITE_WAIT's completion unconditionally
|
|
// pulsed the SAME external `mem_ready` for CR_INIT's own 4
|
|
// internal dummy-read/write steps too -- so the waiting caller
|
|
// would see one of THOSE stray pulses, believe its own (never
|
|
// actually issued) request had completed, and move on with
|
|
// garbage/no data. Two independent effects of the same root
|
|
// cause (CR_INIT reusing the external-facing datapath for
|
|
// internal housekeeping): fixed together below (this latch) and
|
|
// at both `mem_ready <= 1'b1` sites (guarded on `!cr_init_active`).
|
|
// ============================================================
|
|
|
|
reg req_pending;
|
|
reg [ADDR_WIDTH-1:0] pending_addr;
|
|
reg [DATA_WIDTH-1:0] pending_wdata;
|
|
reg pending_wr;
|
|
reg pending_lb_n;
|
|
reg pending_ub_n;
|
|
|
|
// ============================================================
|
|
// 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;
|
|
|
|
req_pending <= 1'b0;
|
|
pending_addr <= {ADDR_WIDTH{1'b0}};
|
|
pending_wdata <= {DATA_WIDTH{1'b0}};
|
|
pending_wr <= 1'b0;
|
|
pending_lb_n <= 1'b1;
|
|
pending_ub_n <= 1'b1;
|
|
|
|
// ----------------------------------------------------
|
|
// 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;
|
|
|
|
// Latch (don't drop) a request that arrives while the
|
|
// controller is still busy with its own power-up/CR-init
|
|
// sequence -- see the req_pending declaration above for
|
|
// why this is needed. Only ever latches ONE request (the
|
|
// arbiter + int8_memory_access contract guarantees no
|
|
// caller issues a second req before its first is
|
|
// acknowledged, so this window can only ever have at
|
|
// most one outstanding request to remember).
|
|
if ((state == STATE_INIT || state == STATE_CR_INIT) &&
|
|
mem_req && !req_pending) begin
|
|
req_pending <= 1'b1;
|
|
pending_addr <= mem_addr;
|
|
pending_wdata <= mem_wdata;
|
|
pending_wr <= mem_wr;
|
|
pending_lb_n <= mem_lb_n;
|
|
pending_ub_n <= mem_ub_n;
|
|
end
|
|
|
|
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 || req_pending) begin
|
|
|
|
// ------------------------------------------------
|
|
// Latch transaction -- from the live port if a
|
|
// fresh request arrived this cycle, otherwise
|
|
// from the early-request latch (see above)
|
|
// captured while STATE_INIT/STATE_CR_INIT was
|
|
// still running. `mem_req` takes priority
|
|
// (cannot both be true from a real caller given
|
|
// the one-outstanding-request contract, but if
|
|
// they ever were, the live request is the more
|
|
// recent one).
|
|
// ------------------------------------------------
|
|
|
|
address_reg <= mem_req ? mem_addr : pending_addr;
|
|
wdata_reg <= mem_req ? mem_wdata : pending_wdata;
|
|
wr_reg <= mem_req ? mem_wr : pending_wr;
|
|
|
|
// ------------------------------------------------
|
|
// Latch byte enables
|
|
// ------------------------------------------------
|
|
|
|
lb_reg <= mem_req ? mem_lb_n : pending_lb_n;
|
|
ub_reg <= mem_req ? mem_ub_n : pending_ub_n;
|
|
|
|
req_pending <= 1'b0;
|
|
|
|
// ------------------------------------------------
|
|
// 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;
|
|
|
|
// Only a REAL external transaction's
|
|
// completion may pulse the external
|
|
// mem_ready -- CR_INIT's own 2 dummy-read
|
|
// steps reuse this same state but must never
|
|
// be visible to whatever caller happens to
|
|
// be waiting (see req_pending's declaration
|
|
// above for the full incident writeup).
|
|
if (!cr_init_active)
|
|
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 -- same cr_init_active
|
|
// guard as the STATE_READ completion above (this
|
|
// is CR_INIT's own 2 write steps reusing this
|
|
// state too).
|
|
// ------------------------------------------------
|
|
|
|
if (!cr_init_active)
|
|
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
|