module n64_cic (
    input clk,
    input reset,

    n64_scb.cic n64_scb,

    input n64_reset,
    input n64_cic_clk,
    inout n64_cic_dq,
    input n64_si_clk
);

    // Input/output synchronization

    logic [1:0] n64_reset_ff;
    logic [1:0] n64_cic_clk_ff;
    logic [1:0] n64_cic_dq_ff;
    logic [1:0] n64_si_clk_ff;

    always_ff @(posedge clk) begin
        n64_reset_ff <= {n64_reset_ff[0], n64_reset};
        n64_cic_clk_ff <= {n64_cic_clk_ff[0], n64_cic_clk};
        n64_cic_dq_ff <= {n64_cic_dq_ff[0], n64_cic_dq};
        n64_si_clk_ff <= {n64_si_clk_ff[0], n64_si_clk};
    end

    logic cic_reset;
    logic cic_clk;
    logic cic_dq;
    logic si_clk;

    always_comb begin
        cic_reset = n64_reset_ff[1];
        cic_clk = n64_cic_clk_ff[1];
        cic_dq = n64_cic_dq_ff[1];
        si_clk = n64_si_clk_ff[1];
    end

    logic cic_dq_out;
    logic cic_dq_oe;

    always_ff @(posedge clk) begin
        cic_dq_oe <= ~cic_dq_out;
    end

    assign n64_cic_dq = cic_dq_oe ? 1'b0 : 1'bZ;


    // Timer (divider and counter)

    logic last_si_clk;

    always_ff @(posedge clk) begin
        last_si_clk <= si_clk;
    end

    logic si_clk_rising_edge;

    always_comb begin
        si_clk_rising_edge = cic_reset && !last_si_clk && si_clk;
    end

    logic [7:0] timer_divider;
    logic [11:0] timer_counter;
    logic timer_start;
    logic timer_clear;
    logic timer_elapsed;

    const bit [11:0] TIMEOUT_500MS = 12'd3815; // (62_500_000 / 32 / 256 / 2) = ~500 ms

    always_ff @(posedge clk) begin
        if (si_clk_rising_edge) begin
            timer_divider <= timer_divider + 1'd1;
        end

        if (si_clk_rising_edge && (&timer_divider) && (timer_counter > 12'd0)) begin
            timer_counter <= timer_counter - 1'd1;
            if (timer_counter == 12'd1) begin
                timer_elapsed <= 1'b1;
            end
        end

        if (timer_start) begin
            timer_divider <= 8'd0;
            timer_counter <= TIMEOUT_500MS;
            timer_elapsed <= 1'b0;
        end

        if (timer_clear) begin
            timer_divider <= 8'd0;
            timer_counter <= 12'd0;
            timer_elapsed <= 1'b0;
        end
    end


    // SERV RISC-V CPU

    logic [31:0] ibus_addr;
    logic ibus_cycle;
    logic [31:0] ibus_rdata;
    logic ibus_ack;

    logic [31:0] dbus_addr;
    logic [31:0] dbus_wdata;
    logic [3:0] dbus_wmask;
    logic dbus_write;
    logic dbus_cycle;
    logic [31:0] dbus_rdata;
    logic dbus_ack;

    logic [31:0] ext_rs1;
    logic [31:0] ext_rs2;
    logic [2:0] ext_funct3;
    logic mdu_valid;

    serv_rf_top #(
        .RESET_PC(32'h8000_0000),
        .PRE_REGISTER(0),
        .WITH_CSR(0)
    ) serv_rf_top_inst (
        .clk(clk),
        .i_rst(reset),
        .i_timer_irq(1'b0),

        .o_ibus_adr(ibus_addr),
        .o_ibus_cyc(ibus_cycle),
        .i_ibus_rdt(ibus_rdata),
        .i_ibus_ack(ibus_ack),

        .o_dbus_adr(dbus_addr),
        .o_dbus_dat(dbus_wdata),
        .o_dbus_sel(dbus_wmask),
        .o_dbus_we(dbus_write) ,
        .o_dbus_cyc(dbus_cycle),
        .i_dbus_rdt(dbus_rdata),
        .i_dbus_ack(dbus_ack),

        .o_ext_rs1(ext_rs1),
        .o_ext_rs2(ext_rs2),
        .o_ext_funct3(ext_funct3),
        .i_ext_rd(32'd0),
        .i_ext_ready(1'b0),
        .o_mdu_valid(mdu_valid)
    );


    // CPU memory

    logic [8:0] ram_addr;
    logic [31:0] ram [0:511];
    logic [31:0] ram_output;

    assign ram_addr = ibus_cycle ? ibus_addr[10:2] : dbus_addr[10:2];
    assign ibus_rdata = ram_output;

    always_ff @(posedge clk) begin
        ram_output <= ram[ram_addr];
        ibus_ack <= ibus_cycle && !ibus_ack;
    end

    initial begin
        $readmemh("../../../sw/cic/build/cic.mem", ram);
    end


    // Bus controller

    always_ff @(posedge clk) begin
        timer_start <= 1'b0;
        timer_clear <= 1'b0;
        n64_scb.cic_invalid_region <= 1'b0;

        dbus_ack <= dbus_cycle && !dbus_ack;

        if (dbus_cycle && dbus_write) begin
            case (dbus_addr[31:30])
                2'b10: begin
                    if (dbus_wmask[0]) ram[ram_addr][7:0] <= dbus_wdata[7:0];
                    if (dbus_wmask[1]) ram[ram_addr][15:8] <= dbus_wdata[15:8];
                    if (dbus_wmask[2]) ram[ram_addr][23:16] <= dbus_wdata[23:16];
                    if (dbus_wmask[3]) ram[ram_addr][31:24] <= dbus_wdata[31:24];
                end

                2'b11: begin
                    case (dbus_addr[3:2])
                        2'b10: begin
                            n64_scb.cic_invalid_region <= dbus_wdata[6];
                            timer_clear <= dbus_wdata[5];
                            timer_start <= dbus_wdata[4];
                            cic_dq_out <= dbus_wdata[0];
                        end

                        2'b11: begin
                            n64_scb.cic_debug <= dbus_wdata[3:0];
                        end
                    endcase
                end
            endcase
        end

        if (reset) begin
            n64_scb.cic_debug <= 3'd0;
        end

        if (reset || !cic_reset) begin
            cic_dq_out <= 1'b1;
        end
    end

    always_comb begin
        dbus_rdata = 32'd0;

        case (dbus_addr[31:30])
            2'b10: begin
                dbus_rdata = ram_output;
            end

            2'b11: begin
                case (dbus_addr[3:2])
                    2'b00: dbus_rdata = {
                        n64_scb.cic_disabled,
                        n64_scb.cic_64dd_mode,
                        n64_scb.cic_region,
                        n64_scb.cic_seed,
                        n64_scb.cic_checksum[47:32]
                    };

                    2'b01: dbus_rdata = n64_scb.cic_checksum[31:0];

                    2'b10: dbus_rdata = {
                        28'd0,
                        timer_elapsed,
                        cic_reset,
                        cic_clk,
                        cic_dq
                    };

                    2'b11: dbus_rdata = {28'd0, n64_scb.cic_debug};
                endcase
            end
        endcase
    end

endmodule
