// 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