/// Copyright by Syntacore LLC © 2016-2021. See LICENSE for details
/// @file       <scr1_pipe_hdu.sv>
/// @brief      HART Debug Unit (HDU)
///

//------------------------------------------------------------------------------
 //
 //
 // Functionality:
 // - Controls HART state (RUN, Debug RUN, Debug HALTED)
 // - Setups Debug Mode execution
 // - Provides status information about Debug Mode execution
 // - Provides Program Buffer functionality (a few instructions execution while
 //   in Debug Mode)
 // - Provides access to Debug CSRs
 //
 // Structure:
 // - Debug state FSM
 // - HART Control logic
 // - HART Status logic
 // - Program Buffer
 // - Debug CSRs
 // - HDU <-> DM interface
 // - HDU <-> EXU interface
 // - HDU <-> IFU interface
 // - HDU <-> CSR interface
 // - HDU <-> TDU interface
 //
//------------------------------------------------------------------------------

`include "scr1_arch_description.svh"

`ifdef SCR1_DBG_EN
`include "scr1_arch_types.svh"
`include "scr1_riscv_isa_decoding.svh"
`include "scr1_hdu.svh"

module scr1_pipe_hdu #(parameter HART_PBUF_INSTR_REGOUT_EN = 1'b1) (
    // Common signals
    input  logic                                        rst_n,                      // HDU reset
    input  logic                                        clk,                        // HDU clock
    input  logic                                        clk_en,                     // HDU clock enable
`ifdef SCR1_CLKCTRL_EN
    input   logic                                       clk_pipe_en,                // Pipeline clock enable
`endif // SCR1_CLKCTRL_EN
    input  logic                                        pipe2hdu_rdc_qlfy_i,        // Pipeline RDC qualifier

    // HDU <-> CSR i/f
    input  logic                                        csr2hdu_req_i,              // CSR i/f request
    input  type_scr1_csr_cmd_sel_e                      csr2hdu_cmd_i,              // CSR i/f command
    input  logic [SCR1_HDU_DEBUGCSR_ADDR_WIDTH-1:0]     csr2hdu_addr_i,             // CSR i/f address
    input  logic [`SCR1_XLEN-1:0]                       csr2hdu_wdata_i,            // CSR i/f write data
    output type_scr1_csr_resp_e                         hdu2csr_resp_o,             // CSR i/f response
    output logic [`SCR1_XLEN-1:0]                       hdu2csr_rdata_o,            // CSR i/f read data

    // HDU <-> DM i/f
    // HART Run Control i/f
    input  logic                                        dm2hdu_cmd_req_i,           // DM-HART Command request
    input  type_scr1_hdu_dbgstates_e                    dm2hdu_cmd_i,               // DM-HART Command
    output logic                                        hdu2dm_cmd_resp_o,          // DM-HART Command response
    output logic                                        hdu2dm_cmd_rcode_o,         // DM-HART Command return code: 0 - Ok; 1 - Error
    output logic                                        hdu2dm_hart_event_o,        // DM-HART Event: 1 if HART debug state changed
    output type_scr1_hdu_hartstatus_s                   hdu2dm_hart_status_o,       // DM-HART Status

    // Program Buffer i/f
    output logic [SCR1_HDU_PBUF_ADDR_WIDTH-1:0]         hdu2dm_pbuf_addr_o,         // Program Buffer address - so far request only for 1 instruction
    input  logic [SCR1_HDU_CORE_INSTR_WIDTH-1:0]        dm2hdu_pbuf_instr_i,        // Program Buffer instruction

    // HART Abstract Data regs i/f
    output logic                                        hdu2dm_dreg_req_o,          // Abstract Data Register request
    output logic                                        hdu2dm_dreg_wr_o,           // Abstract Data Register write
    output logic [`SCR1_XLEN-1:0]                       hdu2dm_dreg_wdata_o,        // Abstract Data Register write data
    input  logic                                        dm2hdu_dreg_resp_i,         // Abstract Data Register response
    input  logic                                        dm2hdu_dreg_fail_i,         // Abstract Data Register fail
    input  logic [`SCR1_XLEN-1:0]                       dm2hdu_dreg_rdata_i,        // Abstract Data Register read data

`ifdef SCR1_TDU_EN
    // HDU <-> TDU interface
    output  logic                                       hdu2tdu_hwbrk_dsbl_o,       // Disables BRKM
    input   logic                                       tdu2hdu_dmode_req_i,        // Trigger Module requests transition to debug mode
    input   logic                                       exu2hdu_ibrkpt_hw_i,        // Hardware breakpoint on current instruction
`endif // SCR1_TDU_EN

    // HART Run Status
    input   logic                                       pipe2hdu_exu_busy_i,        // EXU busy
    input   logic                                       pipe2hdu_instret_i,         // Instruction retired (with or without exception)
    input   logic                                       pipe2hdu_init_pc_i,         // Reset exit

    // HART Halt Status
    input   logic                                       pipe2hdu_exu_exc_req_i,     // Exception request
    input   logic                                       pipe2hdu_brkpt_i,           // Software Breakpoint (EBREAK)

    // HDU <-> EXU i/f
    // HART Run Control
    output  logic                                       hdu2exu_pbuf_fetch_o,       // Fetch instruction from Program Buffer
    output  logic                                       hdu2exu_no_commit_o,        // Forbid instruction commitment
    output  logic                                       hdu2exu_irq_dsbl_o,         // Disable IRQ
    output  logic                                       hdu2exu_pc_advmt_dsbl_o,    // Forbid PC advancement
    output  logic                                       hdu2exu_dmode_sstep_en_o,   // Enable single-step

    // HART state
    output  logic                                       hdu2exu_dbg_halted_o,       // Debug halted state
    output  logic                                       hdu2exu_dbg_run2halt_o,     // Transition to debug halted state
    output  logic                                       hdu2exu_dbg_halt2run_o,     // Transition to run state
    output  logic                                       hdu2exu_dbg_run_start_o,    // First cycle of run state

    // PC interface
    input  logic [`SCR1_XLEN-1:0]                       pipe2hdu_pc_curr_i,         // Current PC
    output logic [`SCR1_XLEN-1:0]                       hdu2exu_dbg_new_pc_o,       // New PC for resume

    // HDU <-> IFU i/f
    // Program Buffer Instruction interface
    input   logic                                       ifu2hdu_pbuf_instr_rdy_i,   // Program Buffer Instruction i/f ready
    output  logic                                       hdu2ifu_pbuf_instr_vd_o,    // Program Buffer Instruction valid
    output  logic                                       hdu2ifu_pbuf_instr_err_o,   // Program Buffer Instruction i/f error
    output  logic [SCR1_HDU_CORE_INSTR_WIDTH-1:0]       hdu2ifu_pbuf_instr_o        // Program Buffer Instruction itself
);

//------------------------------------------------------------------------------
// Local Parameters
//------------------------------------------------------------------------------

localparam int unsigned SCR1_HDU_TIMEOUT       = 64;       // must be power of 2
localparam int unsigned SCR1_HDU_TIMEOUT_WIDTH = $clog2(SCR1_HDU_TIMEOUT);

//------------------------------------------------------------------------------
// Local Signals
//------------------------------------------------------------------------------

// Debug FSM
//------------------------------------------------------------------------------

// FSM control signals
logic                                               dm_dhalt_req;
logic                                               dm_run_req;

logic                                               dm_cmd_run;
logic                                               dm_cmd_dhalted;
logic                                               dm_cmd_drun;

// Debug state FSM signals
type_scr1_hdu_dbgstates_e                           dbg_state;
type_scr1_hdu_dbgstates_e                           dbg_state_next;
logic                                               dbg_state_dhalted;
logic                                               dbg_state_drun;
logic                                               dbg_state_run;
logic                                               dbg_state_reset;

// FSM transition, update and event registers
logic                                               dfsm_trans;
logic                                               dfsm_trans_next;
logic                                               dfsm_update;
logic                                               dfsm_update_next;
logic                                               dfsm_event;
logic                                               dfsm_event_next;

// HART Control signals
//------------------------------------------------------------------------------

logic                                               hart_resume_req;
logic                                               hart_halt_req;
logic                                               hart_cmd_req;

// HART Run Control register
logic                                               hart_runctrl_upd;
logic                                               hart_runctrl_clr;
type_scr1_hdu_runctrl_s                             hart_runctrl;

// HART halt request timeout counter signals
logic [SCR1_HDU_TIMEOUT_WIDTH-1:0]                  halt_req_timeout_cnt;
logic [SCR1_HDU_TIMEOUT_WIDTH-1:0]                  halt_req_timeout_cnt_next;
logic                                               halt_req_timeout_cnt_en;
logic                                               halt_req_timeout_flag;

// HART Status signals
//------------------------------------------------------------------------------

type_scr1_hdu_haltstatus_s                          hart_haltstatus;
type_scr1_hdu_haltcause_e                           hart_haltcause;

logic                                               hart_halt_pnd;
logic                                               hart_halt_ack;

// Debug mode cause decoder signals
logic                                               dmode_cause_sstep;
logic                                               dmode_cause_except;
logic                                               dmode_cause_ebreak;
logic                                               dmode_cause_any;
`ifdef SCR1_TDU_EN
logic                                               dmode_cause_tmreq;
`endif // SCR1_TDU_EN

// Program Buffer FSM
//------------------------------------------------------------------------------

// PBUF FSM control signals
logic                                               ifu_handshake_done;
logic                                               pbuf_exc_inj_req;
logic                                               pbuf_exc_inj_end;
logic                                               pbuf_start_fetch;

// PBUF FSM signals
type_scr1_hdu_pbufstates_e                          pbuf_fsm_curr;
type_scr1_hdu_pbufstates_e                          pbuf_fsm_next;
logic                                               pbuf_fsm_idle;
logic                                               pbuf_fsm_fetch;
logic                                               pbuf_fsm_excinj;

// PBUF address signals
logic [SCR1_HDU_PBUF_ADDR_WIDTH-1:0]                pbuf_addr_ff;
logic [SCR1_HDU_PBUF_ADDR_WIDTH-1:0]                pbuf_addr_next;
logic                                               pbuf_addr_end;
logic                                               pbuf_addr_next_vd;

logic                                               pbuf_instr_wait_latching;

// Debugs CSRs
//------------------------------------------------------------------------------

// CSRs write/read interface signals
logic                                               csr_upd_on_halt;
logic                                               csr_wr;
logic [`SCR1_XLEN-1:0]                              csr_wr_data;
logic [`SCR1_XLEN-1:0]                              csr_rd_data;

// Debug Control and Status register (DCSR)
logic                                               csr_dcsr_sel;
logic                                               csr_dcsr_wr;
type_scr1_hdu_dcsr_s                                csr_dcsr_in;
type_scr1_hdu_dcsr_s                                csr_dcsr_out;
logic                                               csr_dcsr_ebreakm;
logic                                               csr_dcsr_stepie;
logic                                               csr_dcsr_step;
logic [SCR1_HDU_DCSR_CAUSE_BIT_L-
       SCR1_HDU_DCSR_CAUSE_BIT_R:0]                 csr_dcsr_cause;

// Debug Program Counter register (DPC)
logic                                               csr_dpc_sel;
logic                                               csr_dpc_wr;
logic [`SCR1_XLEN-1:0]                              csr_dpc_ff;
logic [`SCR1_XLEN-1:0]                              csr_dpc_next;
logic [`SCR1_XLEN-1:0]                              csr_dpc_out;

// Debug Scratch register 0 (DSCRATCH0)
logic                                               csr_addr_dscratch0;
logic                                               csr_dscratch0_sel;
logic                                               csr_dscratch0_wr;
logic [`SCR1_XLEN-1:0]                              csr_dscratch0_out;
type_scr1_csr_resp_e                                csr_dscratch0_resp;

//------------------------------------------------------------------------------
// Debug state FSM logic
//------------------------------------------------------------------------------
//
 // Debug state FSM logic consists of the following functional units:
 // - FSM control logic
 // - Debug state FSM
 // - FSM transition, update and event registers
//

// FSM control logic
//------------------------------------------------------------------------------

assign dm_cmd_dhalted = (dm2hdu_cmd_i == SCR1_HDU_DBGSTATE_DHALTED);
assign dm_cmd_run     = (dm2hdu_cmd_i == SCR1_HDU_DBGSTATE_RUN);
assign dm_cmd_drun    = (dm2hdu_cmd_i == SCR1_HDU_DBGSTATE_DRUN);

assign dm_dhalt_req   = dm2hdu_cmd_req_i & dm_cmd_dhalted;
assign dm_run_req     = dm2hdu_cmd_req_i & (dm_cmd_run | dm_cmd_drun);

// Debug state FSM
//------------------------------------------------------------------------------

always_ff @(negedge rst_n, posedge clk) begin
    if (~rst_n) begin
        dbg_state <= SCR1_HDU_DBGSTATE_RESET;
    end else begin
        dbg_state <= dbg_state_next;
    end
end

always_comb begin
    if (~pipe2hdu_rdc_qlfy_i) begin
        dbg_state_next = SCR1_HDU_DBGSTATE_RESET;
    end else begin
        case (dbg_state)
            SCR1_HDU_DBGSTATE_RESET: begin
                dbg_state_next = ~pipe2hdu_init_pc_i ? SCR1_HDU_DBGSTATE_RESET
                               : dm_dhalt_req        ? SCR1_HDU_DBGSTATE_DHALTED
                                                     : SCR1_HDU_DBGSTATE_RUN;
            end
            SCR1_HDU_DBGSTATE_RUN: begin
                dbg_state_next = dfsm_update         ? SCR1_HDU_DBGSTATE_DHALTED
                                                     : SCR1_HDU_DBGSTATE_RUN;
            end
            SCR1_HDU_DBGSTATE_DHALTED: begin
                dbg_state_next = ~dfsm_update        ? SCR1_HDU_DBGSTATE_DHALTED
                               : dm_cmd_drun         ? SCR1_HDU_DBGSTATE_DRUN
                                                     : SCR1_HDU_DBGSTATE_RUN;
            end
            SCR1_HDU_DBGSTATE_DRUN: begin
                dbg_state_next = dfsm_update         ? SCR1_HDU_DBGSTATE_DHALTED
                                                     : SCR1_HDU_DBGSTATE_DRUN;
            end
            default: begin
`ifdef SCR1_XPROP_EN
                dbg_state_next = SCR1_HDU_DBGSTATE_XXX;
`else // SCR1_XPROP_EN
                dbg_state_next = dbg_state;
`endif // SCR1_XPROP_EN
            end
        endcase
    end
end

assign dbg_state_dhalted = (dbg_state == SCR1_HDU_DBGSTATE_DHALTED);
assign dbg_state_drun    = (dbg_state == SCR1_HDU_DBGSTATE_DRUN);
assign dbg_state_run     = (dbg_state == SCR1_HDU_DBGSTATE_RUN);
assign dbg_state_reset   = (dbg_state == SCR1_HDU_DBGSTATE_RESET);

// FSM transition, update and event registers
//------------------------------------------------------------------------------

always_ff @(negedge rst_n, posedge clk) begin
    if (~rst_n) begin
        dfsm_trans  <= 1'b0;
        dfsm_update <= 1'b0;
        dfsm_event  <= 1'b0;
    end else begin
        dfsm_trans  <= dfsm_trans_next;
        dfsm_update <= dfsm_update_next;
        dfsm_event  <= dfsm_event_next;
    end
end

always_comb begin
    dfsm_trans_next  = 1'b0;
    dfsm_update_next = 1'b0;
    dfsm_event_next  = 1'b0;

    if (~pipe2hdu_rdc_qlfy_i) begin
        dfsm_trans_next  = 1'b0;
        dfsm_update_next = 1'b0;
        dfsm_event_next  = 1'b1;
    end else begin
        case (dbg_state)
            SCR1_HDU_DBGSTATE_RESET: begin
                dfsm_trans_next  = 1'b0;
                dfsm_update_next = 1'b0;
                dfsm_event_next  = pipe2hdu_init_pc_i & ~dm2hdu_cmd_req_i;
            end

            SCR1_HDU_DBGSTATE_RUN,
            SCR1_HDU_DBGSTATE_DRUN: begin
                dfsm_trans_next  = ~dfsm_update ? hart_halt_pnd : dfsm_trans;
                dfsm_update_next = ~dfsm_update & hart_halt_ack;
                dfsm_event_next  = dfsm_update;
            end

            SCR1_HDU_DBGSTATE_DHALTED: begin
                dfsm_trans_next  = ~dfsm_update ? ~dfsm_trans & dm_run_req
                                                : dfsm_trans;
                dfsm_update_next = ~dfsm_update & dfsm_trans;
                dfsm_event_next  = dfsm_update;
            end

            default : begin
                dfsm_trans_next  = 'X;
                dfsm_update_next = 'X;
                dfsm_event_next  = 'X;
            end
        endcase
    end
end

//------------------------------------------------------------------------------
// HART Control logic
//------------------------------------------------------------------------------
//
 // HART Control logic consists of the following functional units:
 // - Control signals
 // - HART Run Control register
 // - HART Halt Request Time-Out counter
//

// Control logic
always_comb begin
    hart_cmd_req = 1'b0;

    if (~pipe2hdu_rdc_qlfy_i) begin
        hart_cmd_req = 1'b0;
    end else begin
        case (dbg_state)
            SCR1_HDU_DBGSTATE_RESET  : hart_cmd_req = dm2hdu_cmd_req_i;
            SCR1_HDU_DBGSTATE_DHALTED: hart_cmd_req = (dfsm_update | dfsm_trans);
            SCR1_HDU_DBGSTATE_RUN,
            SCR1_HDU_DBGSTATE_DRUN   : hart_cmd_req = ~dfsm_update & dfsm_trans;
            default                  : hart_cmd_req = 'X;
        endcase
    end
end

assign hart_halt_req   = dm_cmd_dhalted & hart_cmd_req;
assign hart_resume_req = (dm_cmd_run | dm_cmd_drun) & hart_cmd_req;

// HART Run Control register
//------------------------------------------------------------------------------

assign hart_runctrl_clr = (dbg_state_run | dbg_state_drun)
                        & (dbg_state_next == SCR1_HDU_DBGSTATE_DHALTED);
assign hart_runctrl_upd = dbg_state_dhalted & dfsm_trans_next;

always_ff @(negedge rst_n, posedge clk) begin
    if (~rst_n) begin
        hart_runctrl.irq_dsbl      <= 1'b0;
        hart_runctrl.fetch_src     <= SCR1_HDU_FETCH_SRC_NORMAL;
        hart_runctrl.pc_advmt_dsbl <= 1'b0;
        hart_runctrl.hwbrkpt_dsbl  <= 1'b0;
        hart_runctrl.redirect      <= '0;
    end else if(clk_en) begin
        if (hart_runctrl_clr) begin
            hart_runctrl           <= '0;
        end else begin
            if (hart_runctrl_upd) begin
                if (~dm_cmd_drun) begin
                    // Case : resume to RUN state
                    hart_runctrl.irq_dsbl        <= csr_dcsr_step ? ~csr_dcsr_stepie : 1'b0;
                    hart_runctrl.fetch_src       <= SCR1_HDU_FETCH_SRC_NORMAL;
                    hart_runctrl.pc_advmt_dsbl   <= 1'b0;
                    hart_runctrl.hwbrkpt_dsbl    <= 1'b0;
                    hart_runctrl.redirect.sstep  <= csr_dcsr_step;
                    hart_runctrl.redirect.ebreak <= csr_dcsr_ebreakm;
                end else begin
                    // Case : resume to DRUN state
                    hart_runctrl.irq_dsbl        <= 1'b1;
                    hart_runctrl.fetch_src       <= SCR1_HDU_FETCH_SRC_PBUF;
                    hart_runctrl.pc_advmt_dsbl   <= 1'b1;
                    hart_runctrl.hwbrkpt_dsbl    <= 1'b1;
                    hart_runctrl.redirect.sstep  <= 1'b0;
                    hart_runctrl.redirect.ebreak <= 1'b1;
                end
            end
        end
    end
end

// HART Halt Request Time-Out counter
//------------------------------------------------------------------------------
// HART goes into halt state only if the halt request is present for timeout period
// of time

assign halt_req_timeout_cnt_en = hdu2exu_dbg_halt2run_o
                               | (hart_halt_req & ~hdu2exu_dbg_run2halt_o);

always_ff @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
        halt_req_timeout_cnt <= '1;
    end else if (halt_req_timeout_cnt_en) begin
        halt_req_timeout_cnt <= halt_req_timeout_cnt_next;
    end
end

assign halt_req_timeout_cnt_next = hdu2exu_dbg_halt2run_o                    ? '1
                                 : (hart_halt_req & ~hdu2exu_dbg_run2halt_o) ? halt_req_timeout_cnt - 1'b1
                                                                             : halt_req_timeout_cnt;

assign halt_req_timeout_flag = ~|halt_req_timeout_cnt;

//------------------------------------------------------------------------------
// HART Status logic
//------------------------------------------------------------------------------
//
 // HART Status logic consists of the following functional units:
 // - Debug mode cause decoder
 // - Hart halt status cause encoder
 // - Hart halt status register
//

// Debug mode cause decoder
//------------------------------------------------------------------------------

assign dmode_cause_sstep  = hart_runctrl.redirect.sstep & pipe2hdu_instret_i;
assign dmode_cause_except = dbg_state_drun & pipe2hdu_exu_exc_req_i
                          & ~pipe2hdu_brkpt_i
`ifdef SCR1_TDU_EN
                          & ~exu2hdu_ibrkpt_hw_i
`endif // SCR1_TDU_EN
                          ;
assign dmode_cause_ebreak = hart_runctrl.redirect.ebreak & pipe2hdu_brkpt_i;
`ifdef SCR1_TDU_EN
assign dmode_cause_tmreq  = tdu2hdu_dmode_req_i & exu2hdu_ibrkpt_hw_i;
`endif // SCR1_TDU_EN

assign dmode_cause_any = dmode_cause_sstep | dmode_cause_ebreak | dmode_cause_except
                       | hart_halt_req
`ifdef SCR1_TDU_EN
                       | dmode_cause_tmreq
`endif // SCR1_TDU_EN
                       ;

// HART halt cause encoder
//------------------------------------------------------------------------------

always_comb begin
    case (1'b1)
`ifdef SCR1_TDU_EN
        dmode_cause_tmreq   : hart_haltcause = SCR1_HDU_HALTCAUSE_TMREQ;
`endif // SCR1_TDU_EN
        dmode_cause_ebreak  : hart_haltcause = SCR1_HDU_HALTCAUSE_EBREAK;
        hart_halt_req       : hart_haltcause = SCR1_HDU_HALTCAUSE_DMREQ;
        dmode_cause_sstep   : hart_haltcause = SCR1_HDU_HALTCAUSE_SSTEP;
        default             : hart_haltcause = SCR1_HDU_HALTCAUSE_NONE;
    endcase
end

// HART halt status register
//------------------------------------------------------------------------------

always_ff @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
        hart_haltstatus        <= '0;
    end else if (hart_halt_ack) begin
        hart_haltstatus.except <= dmode_cause_except;
        hart_haltstatus.cause  <= hart_haltcause;
    end
end

assign hart_halt_pnd = (dfsm_trans | dm_dhalt_req) & ~hart_halt_ack;
assign hart_halt_ack = ~hdu2exu_dbg_halted_o
                     & (halt_req_timeout_flag | (~pipe2hdu_exu_busy_i & dmode_cause_any));

//------------------------------------------------------------------------------
// Program Buffer (PBUF) logic
//------------------------------------------------------------------------------
//
 // Program Buffer allows to execute small programs in debug mode
//

// To terminate Program Buffer execution exception should be raised. There are 2
// cases:
// - One of PBUF instructions raise an exception
// - No PBUF instruction raise an exception before the last PBUF instruction has
// been issued. In this case FSM goes into EXCINJECT state and an "Instruction
// fetch access fault" exception is injected

// PBUF FSM
//------------------------------------------------------------------------------

assign ifu_handshake_done = hdu2ifu_pbuf_instr_vd_o & ifu2hdu_pbuf_instr_rdy_i;
assign pbuf_addr_end      = (pbuf_addr_ff == (SCR1_HDU_PBUF_ADDR_SPAN-1));

assign pbuf_start_fetch = dbg_state_dhalted      & (dbg_state_next == SCR1_HDU_DBGSTATE_DRUN);
assign pbuf_exc_inj_req = ifu_handshake_done     & pbuf_addr_end;
assign pbuf_exc_inj_end = pipe2hdu_exu_exc_req_i | ifu_handshake_done;

always_ff @(negedge rst_n, posedge clk) begin
    if (~rst_n) begin
        pbuf_fsm_curr <= SCR1_HDU_PBUFSTATE_IDLE;
    end else if(clk_en) begin
        pbuf_fsm_curr <= pbuf_fsm_next;
    end
end

always_comb begin
    case (pbuf_fsm_curr)
        SCR1_HDU_PBUFSTATE_IDLE: begin
            pbuf_fsm_next = pbuf_start_fetch       ? SCR1_HDU_PBUFSTATE_FETCH
                                                   : SCR1_HDU_PBUFSTATE_IDLE;
        end
        SCR1_HDU_PBUFSTATE_FETCH: begin
            pbuf_fsm_next = pipe2hdu_exu_exc_req_i ? SCR1_HDU_PBUFSTATE_WAIT4END
                          : pbuf_exc_inj_req       ? SCR1_HDU_PBUFSTATE_EXCINJECT
                                                   : SCR1_HDU_PBUFSTATE_FETCH;
        end
        SCR1_HDU_PBUFSTATE_EXCINJECT: begin
            pbuf_fsm_next = pbuf_exc_inj_end       ? SCR1_HDU_PBUFSTATE_WAIT4END
                                                   : SCR1_HDU_PBUFSTATE_EXCINJECT;
        end
        SCR1_HDU_PBUFSTATE_WAIT4END: begin
            pbuf_fsm_next = hdu2exu_dbg_halted_o   ? SCR1_HDU_PBUFSTATE_IDLE
                                                   : SCR1_HDU_PBUFSTATE_WAIT4END;
        end
    endcase
end

assign pbuf_fsm_idle   = (pbuf_fsm_curr == SCR1_HDU_PBUFSTATE_IDLE);
assign pbuf_fsm_fetch  = (pbuf_fsm_curr == SCR1_HDU_PBUFSTATE_FETCH);
assign pbuf_fsm_excinj = (pbuf_fsm_curr == SCR1_HDU_PBUFSTATE_EXCINJECT);

// Program Buffer address register
//------------------------------------------------------------------------------

assign pbuf_addr_next_vd = pbuf_fsm_fetch          & ifu_handshake_done
                         & ~pipe2hdu_exu_exc_req_i & ~pbuf_addr_end;

always_ff @(negedge rst_n, posedge clk) begin
    if (~rst_n) begin
        pbuf_addr_ff <= '0;
    end else if(clk_en) begin
        pbuf_addr_ff <= pbuf_addr_next;
    end
end

assign pbuf_addr_next = pbuf_fsm_idle     ? '0
                      : pbuf_addr_next_vd ? pbuf_addr_ff + 1'b1
                                          : pbuf_addr_ff;

// Pass instruction from debug program buffer to cpu pipeline with two options:
// - through register, better for frequency
// - through wires, better for area
generate if (HART_PBUF_INSTR_REGOUT_EN) begin
    always_ff @(posedge clk, negedge rst_n) begin
        if (~rst_n) begin
            pbuf_instr_wait_latching <= 1'b0;
        end else begin
            pbuf_instr_wait_latching <= ifu_handshake_done;
        end
    end
end else begin
    assign pbuf_instr_wait_latching = 1'b0;
end endgenerate

//------------------------------------------------------------------------------
// Debug CSRs
//------------------------------------------------------------------------------

assign csr_upd_on_halt = (dbg_state_reset | dbg_state_run)
                       & (dbg_state_next == SCR1_HDU_DBGSTATE_DHALTED);

// CSRs select logic
//------------------------------------------------------------------------------

always_comb begin : csr_if_regsel
    csr_dcsr_sel        = 1'b0;
    csr_dpc_sel         = 1'b0;
    csr_dscratch0_sel   = 1'b0;
    //csr_dscratch1_sel   = 1'b0;

    if (csr2hdu_req_i) begin
        case (csr2hdu_addr_i)
            SCR1_HDU_DBGCSR_OFFS_DCSR     : csr_dcsr_sel      = 1'b1;
            SCR1_HDU_DBGCSR_OFFS_DPC      : csr_dpc_sel       = 1'b1;
            SCR1_HDU_DBGCSR_OFFS_DSCRATCH0: csr_dscratch0_sel = 1'b1;
            default : begin
                                            csr_dcsr_sel      = 1'bX;
                                            csr_dpc_sel       = 1'bX;
                                            csr_dscratch0_sel = 1'bX;
            end
        endcase
    end
end : csr_if_regsel

// CSRs read interface
//------------------------------------------------------------------------------

assign csr_rd_data = csr_dcsr_out | csr_dpc_out | csr_dscratch0_out;

// CSRs write interface
//------------------------------------------------------------------------------

assign csr_wr = csr2hdu_req_i;

always_comb begin : csr_if_write
    csr_wr_data     = '0;

    if (csr2hdu_req_i) begin
        case (csr2hdu_cmd_i)
            SCR1_CSR_CMD_WRITE : csr_wr_data = csr2hdu_wdata_i;
            SCR1_CSR_CMD_SET   : csr_wr_data = csr_rd_data | csr2hdu_wdata_i;
            SCR1_CSR_CMD_CLEAR : csr_wr_data = csr_rd_data & (~csr2hdu_wdata_i);
            default            : csr_wr_data = 'X;
        endcase
    end
end : csr_if_write

// Debug Control and Status register
//------------------------------------------------------------------------------
// Setups the HART behaviour in Debug Mode and holds Debug status information

always_comb begin
    csr_dcsr_in                 = csr_wr_data;
    csr_dcsr_wr                 = csr_wr & csr_dcsr_sel;

    csr_dcsr_out                = '0;
    if (csr_dcsr_sel) begin
        csr_dcsr_out.xdebugver  = SCR1_HDU_DEBUGCSR_DCSR_XDEBUGVER;
        csr_dcsr_out.ebreakm    = csr_dcsr_ebreakm;
        csr_dcsr_out.stepie     = csr_dcsr_stepie;
        csr_dcsr_out.step       = csr_dcsr_step;
        csr_dcsr_out.prv        = 2'b11;
        csr_dcsr_out.cause      = csr_dcsr_cause;
    end
end

always_ff @(negedge rst_n, posedge clk) begin
    if (~rst_n) begin
        csr_dcsr_ebreakm        <= 1'b0;
        csr_dcsr_stepie         <= 1'b0;
        csr_dcsr_step           <= 1'b0;
    end else if(clk_en) begin
        if (csr_dcsr_wr) begin
            csr_dcsr_ebreakm    <= csr_dcsr_in.ebreakm;
            csr_dcsr_stepie     <= csr_dcsr_in.stepie;
            csr_dcsr_step       <= csr_dcsr_in.step;
        end
    end
end

always_ff @(negedge rst_n, posedge clk) begin
    if (~rst_n) begin
        csr_dcsr_cause <= 1'b0;
    end else if(clk_en) begin
        if(csr_upd_on_halt) begin
            csr_dcsr_cause <= hart_haltstatus.cause;
        end
    end
end

// Debug PC register
//------------------------------------------------------------------------------
// Saves the virtual address of the next instruction to be executed when Debug
// Mode is entered. Could be changed by debugger

assign csr_dpc_wr   = csr_wr & csr_dpc_sel;

always_ff @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
        csr_dpc_ff <= '0;
    end else if(clk_en) begin
        csr_dpc_ff <= csr_dpc_next;
    end
end

assign csr_dpc_next = csr_upd_on_halt ? pipe2hdu_pc_curr_i
                    : csr_dpc_wr      ? csr_wr_data
                                      : csr_dpc_ff;
assign csr_dpc_out  = csr_dpc_sel     ? csr_dpc_ff : '0;

// Debug Scratch 0 register
//------------------------------------------------------------------------------

assign csr_dscratch0_resp = (~dm2hdu_dreg_resp_i | dm2hdu_dreg_fail_i)
                          ? SCR1_CSR_RESP_ER
                          : SCR1_CSR_RESP_OK;
assign csr_dscratch0_out  = csr_dscratch0_sel ? dm2hdu_dreg_rdata_i : '0;

//------------------------------------------------------------------------------
// HDU <-> DM interface
//------------------------------------------------------------------------------

assign hdu2dm_hart_event_o = dfsm_event;

// HART status
always_comb begin
    hdu2dm_hart_status_o           = '0;
    hdu2dm_hart_status_o.dbg_state = dbg_state;
    hdu2dm_hart_status_o.except    = dbg_state_dhalted & hart_haltstatus.except;
    hdu2dm_hart_status_o.ebreak    = dbg_state_dhalted & (hart_haltstatus.cause == SCR1_HDU_HALTCAUSE_EBREAK);
end

assign hdu2dm_cmd_rcode_o = dbg_state_reset
                          ? ~pipe2hdu_rdc_qlfy_i | ~pipe2hdu_init_pc_i | ~dm2hdu_cmd_req_i
                          : ~pipe2hdu_rdc_qlfy_i | ~dfsm_update;

always_comb begin
    hdu2dm_cmd_resp_o   = 1'b0;

    case (dbg_state)
        SCR1_HDU_DBGSTATE_RESET: begin
            hdu2dm_cmd_resp_o  = pipe2hdu_rdc_qlfy_i & pipe2hdu_init_pc_i & dm2hdu_cmd_req_i;
        end

        SCR1_HDU_DBGSTATE_RUN: begin
            hdu2dm_cmd_resp_o  = pipe2hdu_rdc_qlfy_i & dfsm_update & dm2hdu_cmd_req_i;
        end

        SCR1_HDU_DBGSTATE_DHALTED: begin
            hdu2dm_cmd_resp_o  = pipe2hdu_rdc_qlfy_i ? dfsm_update : dm2hdu_cmd_req_i;
        end

        SCR1_HDU_DBGSTATE_DRUN: begin
            hdu2dm_cmd_resp_o  = (~pipe2hdu_rdc_qlfy_i | dfsm_update) & dm2hdu_cmd_req_i;
        end

        default: begin
            hdu2dm_cmd_resp_o  = 'X;
        end
    endcase
end

assign hdu2dm_pbuf_addr_o  = pbuf_addr_ff;
assign hdu2dm_dreg_req_o   = csr_dscratch0_sel;
assign hdu2dm_dreg_wr_o    = csr_wr & csr_dscratch0_sel;
assign hdu2dm_dreg_wdata_o = csr_wr_data;

//------------------------------------------------------------------------------
// HDU <-> EXU interface
//------------------------------------------------------------------------------

assign hdu2exu_dbg_halted_o    = (dbg_state_next == SCR1_HDU_DBGSTATE_DHALTED)
                               | (~pipe2hdu_rdc_qlfy_i & ~dbg_state_run);
assign hdu2exu_dbg_run_start_o = dbg_state_dhalted & pipe2hdu_rdc_qlfy_i & dfsm_update;
assign hdu2exu_dbg_halt2run_o  = hdu2exu_dbg_halted_o & hart_resume_req
`ifdef SCR1_CLKCTRL_EN
                               & clk_pipe_en
`endif // SCR1_CLKCTRL_EN
                               ;
assign hdu2exu_dbg_run2halt_o  = hart_halt_ack;

assign hdu2exu_pbuf_fetch_o    = hart_runctrl.fetch_src;
assign hdu2exu_irq_dsbl_o      = hart_runctrl.irq_dsbl;
assign hdu2exu_pc_advmt_dsbl_o = hart_runctrl.pc_advmt_dsbl;
// No change in arch. state if dmode caused by breakpoint
assign hdu2exu_no_commit_o     = dmode_cause_ebreak
`ifdef SCR1_TDU_EN
                               | dmode_cause_tmreq
`endif // SCR1_TDU_EN
                               ;
assign hdu2exu_dmode_sstep_en_o = hart_runctrl.redirect.sstep;
assign hdu2exu_dbg_new_pc_o     = csr_dpc_ff;

//------------------------------------------------------------------------------
// HDU <-> IFU interface
//------------------------------------------------------------------------------

assign hdu2ifu_pbuf_instr_vd_o  = (pbuf_fsm_fetch | pbuf_fsm_excinj)
                                & ~pbuf_instr_wait_latching;
assign hdu2ifu_pbuf_instr_err_o = pbuf_fsm_excinj;

generate if (HART_PBUF_INSTR_REGOUT_EN) begin
    always_ff @(posedge clk) begin
        hdu2ifu_pbuf_instr_o <= dm2hdu_pbuf_instr_i;
    end
end else begin
    assign hdu2ifu_pbuf_instr_o = dm2hdu_pbuf_instr_i;
end endgenerate

//------------------------------------------------------------------------------
// HDU <-> CSR interface
//------------------------------------------------------------------------------

assign csr_addr_dscratch0 = (csr2hdu_addr_i == SCR1_HDU_DBGCSR_OFFS_DSCRATCH0);

assign hdu2csr_resp_o  = ~dbg_state_drun    ? SCR1_CSR_RESP_ER
                       : csr_addr_dscratch0 ? csr_dscratch0_resp
                       : csr2hdu_req_i      ? SCR1_CSR_RESP_OK
                                            : SCR1_CSR_RESP_ER;
assign hdu2csr_rdata_o = csr_rd_data;

`ifdef SCR1_TDU_EN
//------------------------------------------------------------------------------
// HDU <-> TDU interface
//------------------------------------------------------------------------------

assign hdu2tdu_hwbrk_dsbl_o = hart_runctrl.hwbrkpt_dsbl;
`endif // SCR1_TDU_EN

`ifdef SCR1_TRGT_SIMULATION
//-------------------------------------------------------------------------------
// Assertion
//-------------------------------------------------------------------------------

SVA_HDU_XCHECK_COMMON :
    assert property (
        @(negedge clk) disable iff (~rst_n)
        !$isunknown( {rst_n,clk,clk_en,csr2hdu_req_i,pipe2hdu_rdc_qlfy_i} )
    )
    else $error("HDU Error: common signals are in X state");

SVA_HDU_XCHECK_CSR_INTF :
    assert property (
        @(negedge clk) disable iff (~rst_n)
        csr2hdu_req_i |-> !$isunknown( {csr2hdu_cmd_i,csr2hdu_addr_i,csr2hdu_wdata_i} )
    )
    else $error("HDU Error: CSR i/f is in X state");

SVA_HDU_XCHECK_DM_INTF :
    assert property (
        @(negedge clk) disable iff (~rst_n)
        !$isunknown( {dm2hdu_cmd_req_i,dm2hdu_cmd_i,dm2hdu_dreg_resp_i,
        dm2hdu_dreg_fail_i} )
    )
    else $error("HDU Error: DM i/f is in X state");

SVA_HDU_XCHECK_TDU_INTF :
    assert property (
        @(negedge clk) disable iff (~rst_n)
        !$isunknown( {tdu2hdu_dmode_req_i,exu2hdu_ibrkpt_hw_i} )
    )
    else $error("HDU Error: TDU i/f is in X state");

SVA_HDU_XCHECK_HART_INTF :
    assert property (
        @(negedge clk) disable iff (~rst_n)
        !$isunknown( {pipe2hdu_exu_busy_i,pipe2hdu_instret_i,pipe2hdu_init_pc_i,pipe2hdu_exu_exc_req_i,pipe2hdu_brkpt_i,
        pipe2hdu_pc_curr_i,ifu2hdu_pbuf_instr_rdy_i} )
    )
    else $error("HDU Error: HART i/f is in X state");

`endif // SCR1_TRGT_SIMULATION

endmodule : scr1_pipe_hdu

`endif // SCR1_DBG_EN
