Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression (272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps, and all dump-derived textures/traces) is excluded via .gitignore and stays local. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
// retroDE_ps2 — ee_dmac_ctrl_stub
|
||||
//
|
||||
// Ch287 — EE DMAC global control/status registers at
|
||||
// 0x1000_E000..0x1000_E0FF (256 bytes). NOT the per-channel registers
|
||||
// (those live in dmac_reg_stub at 0x1000_A000+ for channel 2; per-
|
||||
// channel registers for other channels are not modelled yet).
|
||||
//
|
||||
// Surface modelled here (R5900 DMAC global):
|
||||
// offset 0x00 D_CTRL — DMAC enable / cycle-stealing / RELE / etc.
|
||||
// Latched write, read returns last-written.
|
||||
// offset 0x10 D_STAT — Per-channel interrupt status (CIS) + per-
|
||||
// channel interrupt mask (CIM) + stall / MEIS.
|
||||
// Read returns current latch (reset = 0 = no
|
||||
// pending interrupts). Writes are W1C against
|
||||
// the CIS/MEIS half (bits where write_data has
|
||||
// a 1 are cleared); CIM half is NOT W1C — bits
|
||||
// are unconditionally written. Real R5900
|
||||
// splits the word: bits[15:0] = CIS (W1C), bits
|
||||
// [31:16] = CIM (write). With nothing in the
|
||||
// stub yet setting bits, qbert sees "no
|
||||
// interrupts pending" on every read, which is
|
||||
// exactly the wait-for-quiet pattern its init
|
||||
// loop polls for.
|
||||
// offset 0x20 D_PCR — Per-channel priority + W1C enables. Latched
|
||||
// write, read returns last-written.
|
||||
// offset 0x30 D_SQWC — Stall/skip cycles. Latched.
|
||||
// offset 0x40 D_RBSR — Ring-buffer size. Latched.
|
||||
// offset 0x50 D_RBOR — Ring-buffer base. Latched.
|
||||
// any other offset — write traced + dropped; read returns 0.
|
||||
//
|
||||
// Codex framing: "If the hot PC is truly a D_STAT poll, read-as-zero
|
||||
// may or may not be the right 'ready' value. Let the next run tell us.
|
||||
// If it still loops, the next chapter should decode the branch
|
||||
// condition and choose the exact D_STAT bit semantics, not guess the
|
||||
// whole region." The implementation honors that — every offset has
|
||||
// minimal-sufficient behavior; future chapters can refine specific
|
||||
// bits once a real ELF surfaces a divergence.
|
||||
//
|
||||
// Port interface mirrors the dmac_reg_stub / intc_stub conventions:
|
||||
// reg_wr_en / reg_offset / reg_wr_data : write port
|
||||
// reg_rd_en / reg_offset / reg_rd_data / reg_rd_valid : read port,
|
||||
// 1-cycle latency
|
||||
// trace_pkg::* : ev_* events tagged SUBSYS_DMAC + EV_READ/EV_WRITE
|
||||
// with arg0 = offset, arg1 = data.
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module ee_dmac_ctrl_stub
|
||||
import trace_pkg::*;
|
||||
(
|
||||
input logic clk,
|
||||
input logic rst_n,
|
||||
|
||||
// Write port (single-cycle, shared offset with read).
|
||||
input logic reg_wr_en,
|
||||
input logic [7:0] reg_offset,
|
||||
input logic [31:0] reg_wr_data,
|
||||
|
||||
// Read port (1-cycle latency).
|
||||
input logic reg_rd_en,
|
||||
output logic [31:0] reg_rd_data,
|
||||
output logic reg_rd_valid,
|
||||
|
||||
// Trace
|
||||
output logic ev_valid,
|
||||
output subsys_e ev_subsys,
|
||||
output event_e ev_event,
|
||||
output logic [63:0] ev_arg0,
|
||||
output logic [63:0] ev_arg1,
|
||||
output logic [63:0] ev_arg2,
|
||||
output logic [63:0] ev_arg3,
|
||||
output logic [31:0] ev_flags
|
||||
);
|
||||
|
||||
localparam logic [7:0] D_CTRL_OFFSET = 8'h00;
|
||||
localparam logic [7:0] D_STAT_OFFSET = 8'h10;
|
||||
localparam logic [7:0] D_PCR_OFFSET = 8'h20;
|
||||
localparam logic [7:0] D_SQWC_OFFSET = 8'h30;
|
||||
localparam logic [7:0] D_RBSR_OFFSET = 8'h40;
|
||||
localparam logic [7:0] D_RBOR_OFFSET = 8'h50;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Register file
|
||||
// ------------------------------------------------------------------
|
||||
logic [31:0] d_ctrl;
|
||||
logic [31:0] d_stat; // CIS in low half (W1C), CIM in high half (W)
|
||||
logic [31:0] d_pcr;
|
||||
logic [31:0] d_sqwc;
|
||||
logic [31:0] d_rbsr;
|
||||
logic [31:0] d_rbor;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
d_ctrl <= 32'd0;
|
||||
d_stat <= 32'd0;
|
||||
d_pcr <= 32'd0;
|
||||
d_sqwc <= 32'd0;
|
||||
d_rbsr <= 32'd0;
|
||||
d_rbor <= 32'd0;
|
||||
end else if (reg_wr_en) begin
|
||||
unique case (reg_offset)
|
||||
D_CTRL_OFFSET: d_ctrl <= reg_wr_data;
|
||||
D_STAT_OFFSET: begin
|
||||
// W1C on the low half (interrupt-status bits): a 1
|
||||
// in reg_wr_data clears that bit; a 0 leaves it.
|
||||
// Direct-write on the high half (mask bits).
|
||||
d_stat[15:0] <= d_stat[15:0] & ~reg_wr_data[15:0];
|
||||
d_stat[31:16] <= reg_wr_data[31:16];
|
||||
end
|
||||
D_PCR_OFFSET: d_pcr <= reg_wr_data;
|
||||
D_SQWC_OFFSET: d_sqwc <= reg_wr_data;
|
||||
D_RBSR_OFFSET: d_rbsr <= reg_wr_data;
|
||||
D_RBOR_OFFSET: d_rbor <= reg_wr_data;
|
||||
default: ; // unknown offsets: write dropped (traced)
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// Read mux (1-cycle latency to match the stub ecosystem).
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
reg_rd_data <= 32'd0;
|
||||
reg_rd_valid <= 1'b0;
|
||||
end else begin
|
||||
reg_rd_valid <= reg_rd_en;
|
||||
if (reg_rd_en) begin
|
||||
unique case (reg_offset)
|
||||
D_CTRL_OFFSET: reg_rd_data <= d_ctrl;
|
||||
D_STAT_OFFSET: reg_rd_data <= d_stat;
|
||||
D_PCR_OFFSET: reg_rd_data <= d_pcr;
|
||||
D_SQWC_OFFSET: reg_rd_data <= d_sqwc;
|
||||
D_RBSR_OFFSET: reg_rd_data <= d_rbsr;
|
||||
D_RBOR_OFFSET: reg_rd_data <= d_rbor;
|
||||
default: reg_rd_data <= 32'd0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Trace — one event per cycle, write priority over read (consistent
|
||||
// with the rest of the stub ecosystem).
|
||||
// ------------------------------------------------------------------
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
ev_valid <= 1'b0;
|
||||
ev_subsys <= SUBSYS_DMAC;
|
||||
ev_event <= EV_WRITE;
|
||||
ev_arg0 <= 64'd0;
|
||||
ev_arg1 <= 64'd0;
|
||||
ev_arg2 <= 64'd0;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'd0;
|
||||
end else if (reg_wr_en) begin
|
||||
ev_valid <= 1'b1;
|
||||
ev_subsys <= SUBSYS_DMAC;
|
||||
ev_event <= EV_WRITE;
|
||||
ev_arg0 <= {56'd0, reg_offset};
|
||||
ev_arg1 <= {32'd0, reg_wr_data};
|
||||
ev_arg2 <= 64'd0;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'h0000_0001; // write
|
||||
end else if (reg_rd_en) begin
|
||||
ev_valid <= 1'b1;
|
||||
ev_subsys <= SUBSYS_DMAC;
|
||||
ev_event <= EV_READ;
|
||||
ev_arg0 <= {56'd0, reg_offset};
|
||||
ev_arg1 <= 64'd0;
|
||||
ev_arg2 <= 64'd0;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'd0;
|
||||
end else begin
|
||||
ev_valid <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule : ee_dmac_ctrl_stub
|
||||
Reference in New Issue
Block a user