Files
retroDE_ps2/rtl/platform/ps2_hps_bridge_null.sv
thejayman77 ec82764bef 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>
2026-06-29 20:10:50 -04:00

223 lines
8.9 KiB
Systemverilog

// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2025-2026 retroDE contributors
// ============================================================================
// ps2_hps_bridge_null — minimal AXI4 slave for the PS2 core's Ch170 shell
// ============================================================================
//
// Purpose: present an AXI4 slave endpoint to the HPS hps2fpga bridge that
// (a) does proper AXI handshake so HPS transactions can't stall the bus,
// and (b) exposes a minimal "core identity" register window at 0x000-0x00F
// so retrodesd / probing utilities can read back who loaded.
//
// This is the Ch170 placeholder — when a real ps2_hps_bridge.sv lands (with
// HPS-driven core_reset, status mirrors, ROM staging, etc.), it should keep
// the same AXI4 port signature so the top-wrapper instantiation doesn't
// need to change.
//
// AXI4 subset (matches splash_hps_bridge.sv):
// - 128-bit data bus with byte-lane selection via {awaddr[3:2] / araddr[3:2]}
// - Single-beat only (awlen=0, arlen=0)
// - 4-bit ID echo
// - 38-bit address
//
// Identity register map (ABI v1.0 — read-only):
// 0x000 CORE_ID = 32'h70533200 ("pS2\0" — placeholder, refine later)
// 0x004 ABI_VERSION = 32'h00000100 (v1.0)
// 0x008 CORE_STATUS = 32'h00000001 (bit 0 = loaded)
// 0x00C CORE_CAPS = 32'h00000000 (no caps advertised)
//
// Everything else: reads return 0, writes ACK'd and discarded.
// ============================================================================
`timescale 1ns/1ps
module ps2_hps_bridge_null (
input logic clk, // qsys clk_100_clk domain
input logic reset_n,
input logic h2f_reset, // HPS-driven fabric reset (active high) — unused; reserved
// AXI4 slave — write address channel
input logic [3:0] s_axi_awid,
input logic [37:0] s_axi_awaddr,
input logic [7:0] s_axi_awlen,
input logic [2:0] s_axi_awsize,
input logic [1:0] s_axi_awburst,
input logic s_axi_awlock,
input logic [3:0] s_axi_awcache,
input logic [2:0] s_axi_awprot,
input logic s_axi_awvalid,
output logic s_axi_awready,
// AXI4 slave — write data channel
input logic [127:0] s_axi_wdata,
input logic [15:0] s_axi_wstrb,
input logic s_axi_wlast,
input logic s_axi_wvalid,
output logic s_axi_wready,
// AXI4 slave — write response channel
output logic [3:0] s_axi_bid,
output logic [1:0] s_axi_bresp,
output logic s_axi_bvalid,
input logic s_axi_bready,
// AXI4 slave — read address channel
input logic [3:0] s_axi_arid,
input logic [37:0] s_axi_araddr,
input logic [7:0] s_axi_arlen,
input logic [2:0] s_axi_arsize,
input logic [1:0] s_axi_arburst,
input logic s_axi_arlock,
input logic [3:0] s_axi_arcache,
input logic [2:0] s_axi_arprot,
input logic s_axi_arvalid,
output logic s_axi_arready,
// AXI4 slave — read data channel
output logic [3:0] s_axi_rid,
output logic [127:0] s_axi_rdata,
output logic [1:0] s_axi_rresp,
output logic s_axi_rlast,
output logic s_axi_rvalid,
input logic s_axi_rready
);
// ----------------------------------------------------------------
// Identity register window (Ch170 ABI v1.0).
// ----------------------------------------------------------------
localparam logic [31:0] CORE_ID = 32'h70533200;
localparam logic [31:0] ABI_VERSION = 32'h00000100;
localparam logic [31:0] CORE_STATUS = 32'h00000001;
localparam logic [31:0] CORE_CAPS = 32'h00000000;
function automatic logic [31:0] identity_lookup(input logic [37:0] addr);
// Identity registers live in the first 16 bytes of the bridge map.
// Anything else returns 0. addr[3:2] picks one of four 32-bit slots.
if (addr[37:4] != '0)
return 32'd0;
case (addr[3:2])
2'b00: identity_lookup = CORE_ID;
2'b01: identity_lookup = ABI_VERSION;
2'b10: identity_lookup = CORE_STATUS;
default: identity_lookup = CORE_CAPS;
endcase
endfunction
// ----------------------------------------------------------------
// Write FSM. Single-beat: accept awvalid + wvalid together, hold
// them ready for one cycle each, then emit bvalid. Stays in the
// BRESP state until bready, so multi-cycle bready timing from
// qsys still completes cleanly.
// ----------------------------------------------------------------
typedef enum logic [1:0] { W_IDLE, W_DATA, W_RESP } w_state_t;
w_state_t w_state;
logic [3:0] aw_id_q;
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
w_state <= W_IDLE;
aw_id_q <= '0;
s_axi_bvalid <= 1'b0;
end else begin
case (w_state)
W_IDLE: begin
s_axi_bvalid <= 1'b0;
if (s_axi_awvalid && s_axi_awready) begin
aw_id_q <= s_axi_awid;
w_state <= W_DATA;
end
end
W_DATA: begin
if (s_axi_wvalid && s_axi_wready) begin
s_axi_bvalid <= 1'b1;
w_state <= W_RESP;
end
end
W_RESP: begin
if (s_axi_bready) begin
s_axi_bvalid <= 1'b0;
w_state <= W_IDLE;
end
end
default: w_state <= W_IDLE;
endcase
end
end
assign s_axi_awready = (w_state == W_IDLE);
assign s_axi_wready = (w_state == W_DATA);
assign s_axi_bid = aw_id_q;
assign s_axi_bresp = 2'b00; // OKAY
// ----------------------------------------------------------------
// Read FSM. Same shape — accept arvalid, drive rdata + rvalid,
// hold until rready.
// ----------------------------------------------------------------
typedef enum logic [0:0] { R_IDLE, R_RESP } r_state_t;
r_state_t r_state;
logic [3:0] ar_id_q;
logic [37:0] ar_addr_q;
logic [127:0] rdata_q;
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
r_state <= R_IDLE;
ar_id_q <= '0;
ar_addr_q <= '0;
rdata_q <= '0;
s_axi_rvalid <= 1'b0;
end else begin
case (r_state)
R_IDLE: begin
s_axi_rvalid <= 1'b0;
if (s_axi_arvalid && s_axi_arready) begin
ar_id_q <= s_axi_arid;
ar_addr_q <= s_axi_araddr;
// Replicate the 32-bit identity word into the
// matching 32-bit lane of the 128-bit response,
// mirroring splash_hps_bridge's lane semantics.
case (s_axi_araddr[3:2])
2'b00: rdata_q <= {96'd0, identity_lookup(s_axi_araddr)};
2'b01: rdata_q <= {64'd0, identity_lookup(s_axi_araddr), 32'd0};
2'b10: rdata_q <= {32'd0, identity_lookup(s_axi_araddr), 64'd0};
default: rdata_q <= {identity_lookup(s_axi_araddr), 96'd0};
endcase
s_axi_rvalid <= 1'b1;
r_state <= R_RESP;
end
end
R_RESP: begin
if (s_axi_rready) begin
s_axi_rvalid <= 1'b0;
r_state <= R_IDLE;
end
end
default: r_state <= R_IDLE;
endcase
end
end
assign s_axi_arready = (r_state == R_IDLE);
assign s_axi_rid = ar_id_q;
assign s_axi_rdata = rdata_q;
assign s_axi_rresp = 2'b00; // OKAY
assign s_axi_rlast = 1'b1; // single-beat
// ----------------------------------------------------------------
// Tie off the AXI4 fields we don't consume so Quartus doesn't
// emit lint warnings: awlen/awsize/awburst/awlock/awcache/awprot,
// wstrb/wlast, arlen/arsize/arburst/arlock/arcache/arprot, h2f_reset.
// ----------------------------------------------------------------
// verilator lint_off UNUSED
wire _unused_ok = &{ 1'b0,
s_axi_awlen, s_axi_awsize, s_axi_awburst,
s_axi_awlock, s_axi_awcache, s_axi_awprot,
s_axi_wdata, s_axi_wstrb, s_axi_wlast,
s_axi_arlen, s_axi_arsize, s_axi_arburst,
s_axi_arlock, s_axi_arcache, s_axi_arprot,
h2f_reset,
1'b0 };
// verilator lint_on UNUSED
endmodule : ps2_hps_bridge_null