-----------------------------------------------------------------------------
--! @file
--! @copyright Copyright 2016 GNSS Sensor Ltd. All right reserved.
--! @author    Sergey Khabarov - sergeykhbr@gmail.com
--! @brief     Debug port must be connected to DSU.
------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
library commonlib;
use commonlib.types_common.all;
--! RIVER CPU specific library.
library riverlib;
--! RIVER CPU configuration constants.
use riverlib.river_cfg.all;


entity DbgPort is
  port (
    i_clk : in std_logic;                                     -- CPU clock
    i_nrst : in std_logic;                                    -- Reset. Active LOW.
    -- "RIVER" Debug interface
    i_dport_valid : in std_logic;                             -- Debug access from DSU is valid
    i_dport_write : in std_logic;                             -- Write command flag
    i_dport_region : in std_logic_vector(1 downto 0);         -- Registers region ID: 0=CSR; 1=IREGS; 2=Control
    i_dport_addr : in std_logic_vector(11 downto 0);          -- Register idx
    i_dport_wdata : in std_logic_vector(RISCV_ARCH-1 downto 0);-- Write value
    o_dport_ready : out std_logic;                            -- Response is ready
    o_dport_rdata : out std_logic_vector(RISCV_ARCH-1 downto 0);-- Response value
    -- CPU debugging signals:
    o_core_addr : out std_logic_vector(11 downto 0);          -- Address of the sub-region register
    o_core_wdata : out std_logic_vector(RISCV_ARCH-1 downto 0);-- Write data
    o_csr_ena : out std_logic;                                -- Region 0: Access to CSR bank is enabled.
    o_csr_write : out std_logic;                              -- Region 0: CSR write enable
    i_csr_rdata : in std_logic_vector(RISCV_ARCH-1 downto 0); -- Region 0: CSR read value
    o_ireg_ena : out std_logic;                               -- Region 1: Access to integer register bank is enabled
    o_ireg_write : out std_logic;                             -- Region 1: Integer registers bank write pulse
    o_npc_write : out std_logic;                              -- Region 1: npc write enable
    i_ireg_rdata : in std_logic_vector(RISCV_ARCH-1 downto 0);-- Region 1: Integer register read value
    i_pc : in std_logic_vector(BUS_ADDR_WIDTH-1 downto 0);    -- Region 1: Instruction pointer
    i_npc : in std_logic_vector(BUS_ADDR_WIDTH-1 downto 0);   -- Region 1: Next Instruction pointer
    i_e_valid : in std_logic;                                 -- Stepping control signal
    i_e_call : in std_logic;                                  -- pseudo-instruction CALL
    i_e_ret : in std_logic;                                   -- pseudo-instruction RET
    i_m_valid : in std_logic;                                 -- To compute number of valid executed instruction
    o_clock_cnt : out std_logic_vector(63 downto 0);          -- Number of clocks excluding halt state
    o_executed_cnt : out std_logic_vector(63 downto 0);       -- Number of executed instructions
    o_halt : out std_logic;                                   -- Halt signal is equal to hold pipeline
    i_ebreak : in std_logic;                                  -- ebreak instruction decoded
    o_break_mode : out std_logic;                             -- Behaviour on EBREAK instruction: 0 = halt; 1 = generate trap
    o_br_fetch_valid : out std_logic;                         -- Fetch injection address/instr are valid
    o_br_address_fetch : out std_logic_vector(BUS_ADDR_WIDTH-1 downto 0); -- Fetch injection address to skip ebreak instruciton only once
    o_br_instr_fetch : out std_logic_vector(31 downto 0);     -- Real instruction value that was replaced by ebreak
    -- Debug signals:
    i_istate : in std_logic_vector(1 downto 0);               -- ICache state machine value
    i_dstate : in std_logic_vector(1 downto 0);               -- DCache state machine value
    i_cstate : in std_logic_vector(1 downto 0);               -- CacheTop state machine value
    i_instr_buf : in std_logic_vector(DBG_FETCH_TRACE_SIZE*64-1 downto 0) -- trace last fetched instructions
  );
end; 
 
architecture arch_DbgPort of DbgPort is

  constant zero64 : std_logic_vector(63 downto 0) := (others => '0');
  constant one64 : std_logic_vector(63 downto 0) := X"0000000000000001";
  constant STACKTR_ADRSZ : integer := log2(CFG_STACK_TRACE_BUF_SIZE);

  type RegistersType is record
      ready : std_logic;
      halt : std_logic;
      breakpoint : std_logic;
      stepping_mode : std_logic;
      stepping_mode_cnt : std_logic_vector(RISCV_ARCH-1 downto 0);
      trap_on_break : std_logic;
      br_address_fetch : std_logic_vector(BUS_ADDR_WIDTH-1 downto 0);
      br_instr_fetch : std_logic_vector(31 downto 0);
      br_fetch_valid : std_logic;

      rdata : std_logic_vector(RISCV_ARCH-1 downto 0);
      stepping_mode_steps : std_logic_vector(RISCV_ARCH-1 downto 0); -- Number of steps before halt in stepping mode
      clock_cnt : std_logic_vector(63 downto 0);               -- Timer in clocks.
      executed_cnt : std_logic_vector(63 downto 0);            -- Number of valid executed instructions
      stack_trace_cnt : integer range 0 to CFG_STACK_TRACE_BUF_SIZE-1; -- Stack trace buffer counter
      rd_trbuf_ena : std_logic;
      rd_trbuf_addr0 : std_logic;
  end record;

  signal r, rin : RegistersType;
  signal wb_stack_raddr : std_logic_vector(STACKTR_ADRSZ-1 downto 0);
  signal wb_stack_rdata : std_logic_vector(2*BUS_ADDR_WIDTH-1 downto 0);
  signal w_stack_we : std_logic;
  signal wb_stack_waddr : std_logic_vector(STACKTR_ADRSZ-1 downto 0);
  signal wb_stack_wdata : std_logic_vector(2*BUS_ADDR_WIDTH-1 downto 0);

  component StackTraceBuffer is
  generic (
    abits : integer := 5;
    dbits : integer := 64
  );
  port (
    i_clk   : in std_logic;
    i_raddr : in std_logic_vector(abits-1 downto 0);
    o_rdata : out std_logic_vector(dbits-1 downto 0);
    i_we    : in std_logic;
    i_waddr : in std_logic_vector(abits-1 downto 0);
    i_wdata : in std_logic_vector(dbits-1 downto 0)
  );
  end component;

begin


  stacktr_ena : if CFG_STACK_TRACE_BUF_SIZE /= 0 generate 
    stacktr0 : StackTraceBuffer generic map (
      abits => STACKTR_ADRSZ,
      dbits => 2*BUS_ADDR_WIDTH
    ) port map (
      i_clk   => i_clk,
      i_raddr => wb_stack_raddr,
      o_rdata => wb_stack_rdata,
      i_we    => w_stack_we,
      i_waddr => wb_stack_waddr,
      i_wdata => wb_stack_wdata
    );
  end generate;

  comb : process(i_nrst, i_dport_valid, i_dport_write, i_dport_region, 
                 i_dport_addr, i_dport_wdata, i_ireg_rdata, i_csr_rdata,
                 i_pc, i_npc, i_e_valid, i_m_valid, i_ebreak, r,
                 wb_stack_rdata, i_e_call, i_e_ret, i_istate, i_dstate,
                 i_cstate, i_instr_buf)
    variable v : RegistersType;
    variable wb_o_core_addr : std_logic_vector(11 downto 0);
    variable wb_o_core_wdata : std_logic_vector(RISCV_ARCH-1 downto 0);
    variable wb_rdata : std_logic_vector(63 downto 0);
    variable wb_o_rdata : std_logic_vector(63 downto 0);
    variable wb_idx : integer range 0 to 4095;
    variable w_o_csr_ena : std_logic;
    variable w_o_csr_write : std_logic;
    variable w_o_ireg_ena : std_logic;
    variable w_o_ireg_write : std_logic;
    variable w_o_npc_write : std_logic;
    variable w_cur_halt : std_logic;
  begin

    v := r;

    wb_o_core_addr := (others => '0');
    wb_o_core_wdata := (others => '0');
    wb_rdata := (others => '0');
    wb_o_rdata := (others => '0');
    wb_idx := conv_integer(i_dport_addr);
    w_o_csr_ena := '0';
    w_o_csr_write := '0';
    w_o_ireg_ena := '0';
    w_o_ireg_write := '0';
    w_o_npc_write := '0';
    v.br_fetch_valid := '0';
    v.rd_trbuf_ena := '0';
    wb_stack_raddr <= (others => '0');
    w_stack_we <= '0';
    wb_stack_waddr <= (others => '0');
    wb_stack_wdata <= (others => '0');

    v.ready := i_dport_valid;

    w_cur_halt := '0';
    if i_e_valid = '1' then
        if r.stepping_mode_cnt /= zero64(RISCV_ARCH-1 downto 0) then
            v.stepping_mode_cnt := r.stepping_mode_cnt - 1;
            if r.stepping_mode_cnt = one64 then
                v.halt := '1';
                w_cur_halt := '1';
                v.stepping_mode := '0';
            end if;
        end if;
    end if;

    if r.halt = '0' then
        v.clock_cnt := r.clock_cnt + 1;
    end if;
    if i_m_valid = '1' then
        v.executed_cnt := r.executed_cnt + 1;
    end if;
    if i_ebreak = '1' then
        v.breakpoint := '1';
        if r.trap_on_break = '0' then
            v.halt := '1';
        end if;
    end if;

    if CFG_STACK_TRACE_BUF_SIZE /= 0 then
        if i_e_call = '1' and r.stack_trace_cnt /= (CFG_STACK_TRACE_BUF_SIZE - 1) then
            w_stack_we <= '1';
            wb_stack_waddr <= conv_std_logic_vector(r.stack_trace_cnt, STACKTR_ADRSZ);
            wb_stack_wdata <= i_npc & i_pc;
            v.stack_trace_cnt := r.stack_trace_cnt + 1;
        elsif i_e_ret = '1' and r.stack_trace_cnt /= 0 then
            v.stack_trace_cnt := r.stack_trace_cnt - 1;
        end if;
    end if;

    if i_dport_valid = '1' then
        case i_dport_region is
        when "00" =>
            w_o_csr_ena := '1';
            wb_o_core_addr := i_dport_addr;
            wb_rdata := i_csr_rdata;
            if i_dport_write = '1' then
                w_o_csr_write := '1';
                wb_o_core_wdata := i_dport_wdata;
            end if;
        when "01" =>
            if wb_idx < 32 then
                w_o_ireg_ena := '1';
                wb_o_core_addr := i_dport_addr;
                wb_rdata := i_ireg_rdata;
                if i_dport_write = '1' then
                    w_o_ireg_write := '1';
                    wb_o_core_wdata := i_dport_wdata;
                end if;
            elsif wb_idx = 32 then
                --! Read only register
                wb_rdata(BUS_ADDR_WIDTH-1 downto 0) := i_pc;
            elsif wb_idx = 33 then
                wb_rdata(BUS_ADDR_WIDTH-1 downto 0) := i_npc;
                if i_dport_write = '1' then
                    w_o_npc_write := '1';
                    wb_o_core_wdata := i_dport_wdata;
                end if;
            elsif wb_idx = 34 then
                wb_rdata(STACKTR_ADRSZ-1 downto 0) := 
							conv_std_logic_vector(r.stack_trace_cnt, STACKTR_ADRSZ);
                if i_dport_write = '1' then
                    v.stack_trace_cnt := conv_integer(i_dport_wdata);
                end if;
            elsif (wb_idx >= 128) and (wb_idx < (128 + 2 * CFG_STACK_TRACE_BUF_SIZE)) then
                    v.rd_trbuf_ena := '1';
                    v.rd_trbuf_addr0 := conv_std_logic_vector(wb_idx, 1)(0);
                    wb_stack_raddr <= conv_std_logic_vector((wb_idx - 128) / 2, STACKTR_ADRSZ);
            elsif wb_idx = 256 then
                wb_rdata := i_instr_buf(63 downto 0);
            elsif wb_idx = 257 then
                wb_rdata := i_instr_buf(127 downto 64);
            elsif wb_idx = 258 then
                wb_rdata := i_instr_buf(191 downto 128);
            elsif wb_idx = 259 then
                wb_rdata := i_instr_buf(255 downto 192);
            end if;
        when "10" =>
            case wb_idx is
            when 0 =>
                wb_rdata(0) := r.halt;
                wb_rdata(2) := r.breakpoint;
                wb_rdata(33 downto 32) := i_istate;
                wb_rdata(37 downto 36) := i_dstate;
                wb_rdata(41 downto 40) := i_cstate;
                if i_dport_write = '1' then
                    v.halt := i_dport_wdata(0);
                    v.stepping_mode := i_dport_wdata(1);
                    if i_dport_wdata(1) = '1' then
                        v.stepping_mode_cnt := r.stepping_mode_steps;
                    end if;
                end if;
            when 1 =>
                wb_rdata := r.stepping_mode_steps;
                if i_dport_write = '1' then
                    v.stepping_mode_steps := i_dport_wdata;
                end if;
            when 2 =>
                wb_rdata := r.clock_cnt;
            when 3 =>
                wb_rdata := r.executed_cnt;
            when 4 =>
                --! Trap on instruction:
                --!      0 = Halt pipeline on ECALL instruction
                --!      1 = Generate trap on ECALL instruction
                wb_rdata(0) := r.trap_on_break;
                if i_dport_write = '1' then
                    v.trap_on_break := i_dport_wdata(0);
                end if;
            when 5 =>
                -- todo: add hardware breakpoint
            when 6 =>
                -- todo: remove hardware breakpoint
            when 7 =>
                wb_rdata(BUS_ADDR_WIDTH-1 downto 0) := r.br_address_fetch;
                if i_dport_write = '1' then
                    v.br_address_fetch := i_dport_wdata(BUS_ADDR_WIDTH-1 downto 0);
                end if;
            when 8 =>
                wb_rdata(31 downto 0) := r.br_instr_fetch;
                if i_dport_write = '1' then
                    v.br_fetch_valid := '1';
                    v.breakpoint := '0';
                    v.br_instr_fetch := i_dport_wdata(31 downto 0);
                end if;
            when others =>
            end case;
        when others =>
        end case;
    end if;
    v.rdata := wb_rdata;
    if r.rd_trbuf_ena = '1' then
        if r.rd_trbuf_addr0 = '0' then
            wb_o_rdata(BUS_ADDR_WIDTH-1 downto 0) :=
					wb_stack_rdata(BUS_ADDR_WIDTH-1 downto 0);
        else
            wb_o_rdata(BUS_ADDR_WIDTH-1 downto 0) :=
					wb_stack_rdata(2*BUS_ADDR_WIDTH-1 downto BUS_ADDR_WIDTH);
        end if;
    else
        wb_o_rdata := r.rdata;
    end if;


    if i_nrst = '0' then
        v.ready := '0';
        v.halt := '0';
        v.breakpoint := '0';
        v.stepping_mode := '0';
        v.rdata := (others => '0');
        v.stepping_mode_cnt := (others => '0');
        v.stepping_mode_steps := (others => '0');
        v.clock_cnt := (others => '0');
        v.executed_cnt := (others => '0');
        v.trap_on_break := '0';
        v.br_address_fetch := (others => '0');
        v.br_instr_fetch := (others => '0');
        v.br_fetch_valid := '0';
        v.stack_trace_cnt := 0;
        v.rd_trbuf_ena := '0';
        v.rd_trbuf_addr0 := '0';
    end if;

    rin <= v;

    o_core_addr <= wb_o_core_addr;
    o_core_wdata <= wb_o_core_wdata;
    o_csr_ena <= w_o_csr_ena;
    o_csr_write <= w_o_csr_write;
    o_ireg_ena <= w_o_ireg_ena;
    o_ireg_write <= w_o_ireg_write;
    o_npc_write <= w_o_npc_write;
    o_clock_cnt <= r.clock_cnt;
    o_executed_cnt <= r.executed_cnt;
    o_halt <= r.halt or w_cur_halt;
    o_break_mode <= r.trap_on_break;
    o_br_fetch_valid <= r.br_fetch_valid;
    o_br_address_fetch <= r.br_address_fetch;
    o_br_instr_fetch <= r.br_instr_fetch;

    o_dport_ready <= r.ready;
    o_dport_rdata <= wb_o_rdata;
  end process;


  -- registers:
  regs : process(i_clk)
  begin 
     if rising_edge(i_clk) then 
        r <= rin;
     end if; 
  end process;

end;
