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