-- #################################################################################################
-- # << NEORV32 - RISC-V-Compatible Debug Module (DM) >>                                           #
-- # ********************************************************************************************* #
-- # Compatible to the "Minimal RISC-V External Debug Spec. Version 1.0 [Sdext]" (or v0.13) using  #
-- # "execution-based" debugging scheme (via the program buffer). FOR A SINGLE HART ONLY!          #
-- # ********************************************************************************************* #
-- # Key features:                                                                                 #
-- # * register access commands only                                                               #
-- # * auto-execution commands                                                                     #
-- # * for a single hart only                                                                      #
-- # * 2 general purpose program buffer entries                                                    #
-- # * 1 general purpose data buffer entry                                                         #
-- # ********************************************************************************************* #
-- # BSD 3-Clause License                                                                          #
-- #                                                                                               #
-- # The NEORV32 RISC-V Processor, https://github.com/stnolting/neorv32                            #
-- # Copyright (c) 2024, Stephan Nolting. All rights reserved.                                     #
-- #                                                                                               #
-- # Redistribution and use in source and binary forms, with or without modification, are          #
-- # permitted provided that the following conditions are met:                                     #
-- #                                                                                               #
-- # 1. Redistributions of source code must retain the above copyright notice, this list of        #
-- #    conditions and the following disclaimer.                                                   #
-- #                                                                                               #
-- # 2. Redistributions in binary form must reproduce the above copyright notice, this list of     #
-- #    conditions and the following disclaimer in the documentation and/or other materials        #
-- #    provided with the distribution.                                                            #
-- #                                                                                               #
-- # 3. Neither the name of the copyright holder nor the names of its contributors may be used to  #
-- #    endorse or promote products derived from this software without specific prior written      #
-- #    permission.                                                                                #
-- #                                                                                               #
-- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS   #
-- # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF               #
-- # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE    #
-- # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,     #
-- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE #
-- # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED    #
-- # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING     #
-- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED  #
-- # OF THE POSSIBILITY OF SUCH DAMAGE.                                                            #
-- #################################################################################################

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library neorv32;
use neorv32.neorv32_package.all;

entity neorv32_debug_dm is
  generic (
    CPU_BASE_ADDR : std_ulogic_vector(31 downto 0);
    LEGACY_MODE   : boolean -- false = spec. v1.0, true = spec. v0.13
  );
  port (
    -- global control --
    clk_i          : in  std_ulogic; -- global clock line
    rstn_i         : in  std_ulogic; -- global reset line, low-active
    cpu_debug_i    : in  std_ulogic; -- CPU is in debug mode
    -- debug module interface (DMI) --
    dmi_req_i      : in  dmi_req_t; -- request
    dmi_rsp_o      : out dmi_rsp_t; -- response
    -- CPU bus access --
    bus_req_i      : in  bus_req_t; -- bus request
    bus_rsp_o      : out bus_rsp_t; -- bus response
    -- CPU control --
    cpu_ndmrstn_o  : out std_ulogic; -- soc reset
    cpu_halt_req_o : out std_ulogic  -- request hart to halt (enter debug mode)
  );
end neorv32_debug_dm;

architecture neorv32_debug_dm_rtl of neorv32_debug_dm is

  -- **********************************************************
  -- DM Memory Layout
  -- **********************************************************
  constant dm_code_base_c : std_ulogic_vector(31 downto 0) := std_ulogic_vector(unsigned(CPU_BASE_ADDR) + x"00"); -- base address of code ROM (park loop)
  constant dm_pbuf_base_c : std_ulogic_vector(31 downto 0) := std_ulogic_vector(unsigned(CPU_BASE_ADDR) + x"40"); -- base address of program buffer (PBUF)
  constant dm_data_base_c : std_ulogic_vector(31 downto 0) := std_ulogic_vector(unsigned(CPU_BASE_ADDR) + x"80"); -- base address of abstract data buffer (DATA)
  constant dm_sreg_base_c : std_ulogic_vector(31 downto 0) := std_ulogic_vector(unsigned(CPU_BASE_ADDR) + x"C0"); -- base address of status register (SREG)

  -- **********************************************************
  -- DMI Access
  -- **********************************************************

  -- available DMI registers --
  constant addr_data0_c        : std_ulogic_vector(6 downto 0) := "0000100";
  constant addr_dmcontrol_c    : std_ulogic_vector(6 downto 0) := "0010000";
  constant addr_dmstatus_c     : std_ulogic_vector(6 downto 0) := "0010001";
  constant addr_hartinfo_c     : std_ulogic_vector(6 downto 0) := "0010010";
  constant addr_abstractcs_c   : std_ulogic_vector(6 downto 0) := "0010110";
  constant addr_command_c      : std_ulogic_vector(6 downto 0) := "0010111";
  constant addr_abstractauto_c : std_ulogic_vector(6 downto 0) := "0011000";
  constant addr_nextdm_c       : std_ulogic_vector(6 downto 0) := "0011101";
  constant addr_progbuf0_c     : std_ulogic_vector(6 downto 0) := "0100000";
  constant addr_progbuf1_c     : std_ulogic_vector(6 downto 0) := "0100001";
  constant addr_sbcs_c         : std_ulogic_vector(6 downto 0) := "0111000";
  constant addr_haltsum0_c     : std_ulogic_vector(6 downto 0) := "1000000";

  -- RISC-V 32-bit instruction prototypes --
  constant instr_nop_c    : std_ulogic_vector(31 downto 0) := x"00000013"; -- nop
  constant instr_lw_c     : std_ulogic_vector(31 downto 0) := x"00002003"; -- lw zero, 0(zero)
  constant instr_sw_c     : std_ulogic_vector(31 downto 0) := x"00002023"; -- sw zero, 0(zero)
  constant instr_ebreak_c : std_ulogic_vector(31 downto 0) := x"00100073"; -- ebreak

  -- DMI access --
  signal dmi_wren : std_ulogic;
  signal dmi_rden : std_ulogic;

  -- debug module DMI registers / access --
  type progbuf_t is array (0 to 1) of std_ulogic_vector(31 downto 0);
  type dm_reg_t is record
    dmcontrol_ndmreset           : std_ulogic;
    dmcontrol_dmactive           : std_ulogic;
    abstractauto_autoexecdata    : std_ulogic;
    abstractauto_autoexecprogbuf : std_ulogic_vector(01 downto 0);
    progbuf                      : progbuf_t;
    command                      : std_ulogic_vector(31 downto 0);
    --
    halt_req    : std_ulogic;
    resume_req  : std_ulogic;
    reset_ack   : std_ulogic;
    wr_acc_err  : std_ulogic;
    rd_acc_err  : std_ulogic;
    clr_acc_err : std_ulogic;
    autoexec_wr : std_ulogic;
    autoexec_rd : std_ulogic;
  end record;
  signal dm_reg : dm_reg_t;

  -- cpu program buffer --
  type cpu_progbuf_t is array (0 to 3) of std_ulogic_vector(31 downto 0);
  signal cpu_progbuf : cpu_progbuf_t;

  -- **********************************************************
  -- DM Control
  -- **********************************************************

  -- DM configuration --
  constant nscratch_c   : std_ulogic_vector(03 downto 0) := "0001"; -- number of dscratch registers in CPU (=1)
  constant datasize_c   : std_ulogic_vector(03 downto 0) := "0001"; -- number of data registers in memory/CSR space (=1)
  constant dataaddr_c   : std_ulogic_vector(11 downto 0) := dm_data_base_c(11 downto 0); -- signed base address of data registers in memory/CSR space
  constant dataaccess_c : std_ulogic                     := '1';    -- 1: abstract data is memory-mapped, 0: abstract data is CSR-mapped
  constant dm_version_c : std_ulogic_vector(03 downto 0) := cond_sel_suv_f(LEGACY_MODE, "0010", "0011"); -- version: v0.13 / v1.0

  -- debug module controller --
  type dm_ctrl_state_t is (CMD_IDLE, CMD_CHECK, CMD_PREPARE, CMD_TRIGGER, CMD_BUSY, CMD_ERROR);
  type dm_ctrl_t is record
    -- fsm --
    state           : dm_ctrl_state_t;
    busy            : std_ulogic;
    ldsw_progbuf    : std_ulogic_vector(31 downto 0);
    pbuf_en         : std_ulogic;
    -- error flags --
    illegal_state   : std_ulogic;
    illegal_cmd     : std_ulogic;
    cmderr          : std_ulogic_vector(02 downto 0);
    -- hart status --
    hart_halted     : std_ulogic;
    hart_resume_req : std_ulogic;
    hart_resume_ack : std_ulogic;
    hart_reset      : std_ulogic;
  end record;
  signal dm_ctrl : dm_ctrl_t;

  -- program buffer access --
  type prog_buf_t is array (0 to 3) of std_ulogic_vector(31 downto 0);
  signal prog_buf : prog_buf_t;

  -- **********************************************************
  -- CPU Bus and Debug Interfaces
  -- **********************************************************

  -- status and control register - bits --
  -- for write access we only care about the actual BYTE WRITE ACCESSES! --
  constant sreg_halt_ack_c      : natural :=  0; -- -/w: CPU is halted in debug mode and waits in park loop
  constant sreg_resume_req_c    : natural :=  8; -- r/-: DM requests CPU to resume
  constant sreg_resume_ack_c    : natural :=  8; -- -/w: CPU starts resuming
  constant sreg_execute_req_c   : natural := 16; -- r/-: DM requests to execute program buffer
  constant sreg_execute_ack_c   : natural := 16; -- -/w: CPU starts to execute program buffer
  constant sreg_exception_ack_c : natural := 24; -- -/w: CPU has detected an exception

  -- code ROM containing "park loop" --
  -- copied manually from 'sw/ocd-firmware/neorv32_debug_mem_code.vhd' --
  type code_rom_t is array (0 to 15) of std_ulogic_vector(31 downto 0);
  constant code_rom : code_rom_t := (
    00 => x"fc0001a3",
    01 => x"00100073",
    02 => x"7b241073",
    03 => x"fc000023",
    04 => x"fc204403",
    05 => x"00041c63",
    06 => x"fc104403",
    07 => x"fe0408e3",
    08 => x"fc0000a3",
    09 => x"7b202473",
    10 => x"7b200073",
    11 => x"fc000123",
    12 => x"7b202473",
    13 => x"0000100f",
    14 => x"f4000067",
    15 => x"00000073"
  );

  -- CPU access helpers --
  signal accen, rden, wren : std_ulogic;

  -- Debug Core Interface --
  type dci_t is record
    halt_ack      : std_ulogic; -- CPU (re-)entered HALT state (single-shot)
    resume_req    : std_ulogic; -- DM wants the CPU to resume when set
    resume_ack    : std_ulogic; -- CPU starts resuming when set (single-shot)
    execute_req   : std_ulogic; -- DM wants CPU to execute program buffer when set
    execute_ack   : std_ulogic; -- CPU starts executing program buffer when set (single-shot)
    exception_ack : std_ulogic; -- CPU has detected an exception (single-shot)
    data_we       : std_ulogic; -- write abstract data
    data_reg      : std_ulogic_vector(31 downto 0); -- memory-mapped data exchange register
  end record;
  signal dci : dci_t;

begin

  -- Info -----------------------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  assert not (LEGACY_MODE = true)  report "[NEORV32] OCD DM compatible to debug spec. version 0.13" severity note;
  assert not (LEGACY_MODE = false) report "[NEORV32] OCD DM compatible to debug spec. version 1.0"  severity note;


  -- DMI Access -----------------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  dmi_wren <= '1' when (dmi_req_i.op = dmi_req_wr_c) else '0';
  dmi_rden <= '1' when (dmi_req_i.op = dmi_req_rd_c) else '0';


  -- Debug Module Command Controller --------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  dm_controller: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      dm_ctrl.state         <= CMD_IDLE;
      dm_ctrl.ldsw_progbuf  <= (others => '0');
      dci.execute_req       <= '0';
      dm_ctrl.pbuf_en       <= '0';
      dm_ctrl.illegal_cmd   <= '0';
      dm_ctrl.illegal_state <= '0';
      dm_ctrl.cmderr        <= "000";
    elsif rising_edge(clk_i) then
      if (dm_reg.dmcontrol_dmactive = '0') then -- DM reset / DM disabled
        dm_ctrl.state         <= CMD_IDLE;
        dm_ctrl.ldsw_progbuf  <= instr_sw_c;
        dci.execute_req       <= '0';
        dm_ctrl.pbuf_en       <= '0';
        --
        dm_ctrl.illegal_cmd   <= '0';
        dm_ctrl.illegal_state <= '0';
        dm_ctrl.cmderr        <= "000";
      else -- DM active

        -- defaults --
        dci.execute_req       <= '0';
        dm_ctrl.illegal_cmd   <= '0';
        dm_ctrl.illegal_state <= '0';

        -- command execution engine --
        case dm_ctrl.state is

          when CMD_IDLE => -- wait for new abstract command
          -- ------------------------------------------------------------
            if (dmi_wren = '1') then -- valid DM write access
              if (dmi_req_i.addr = addr_command_c) then
                if (dm_ctrl.cmderr = "000") then -- only execute if no error
                  dm_ctrl.state <= CMD_CHECK;
                end if;
              end if;
            elsif (dm_reg.autoexec_rd = '1') or (dm_reg.autoexec_wr = '1') then -- auto execution trigger
              dm_ctrl.state <= CMD_CHECK;
            end if;

          when CMD_CHECK => -- check if command is valid / supported
          -- ------------------------------------------------------------
            if (dm_reg.command(31 downto 24) = x"00") and -- cmdtype: register access
               (dm_reg.command(23) = '0') and -- reserved
               (dm_reg.command(22 downto 20) = "010") and -- aarsize: has to be 32-bit
               (dm_reg.command(19) = '0') and -- aarpostincrement: not supported
               ((dm_reg.command(17) = '0') or (dm_reg.command(15 downto 05) = "00010000000")) then -- regno: only GPRs are supported: 0x1000..0x101f if transfer is set
              if (dm_ctrl.hart_halted = '1') then -- CPU is halted
                dm_ctrl.state <= CMD_PREPARE;
              else -- error! CPU is still running
                dm_ctrl.illegal_state <= '1';
                dm_ctrl.state         <= CMD_ERROR;
              end if;
            else -- error! invalid command
              dm_ctrl.illegal_cmd <= '1';
              dm_ctrl.state       <= CMD_ERROR;
            end if;

          when CMD_PREPARE => -- setup program buffer
          -- ------------------------------------------------------------
            if (dm_reg.command(17) = '1') then -- "transfer"
              if (dm_reg.command(16) = '0') then -- "write" = 0 -> read from GPR
                dm_ctrl.ldsw_progbuf <= instr_sw_c;
                dm_ctrl.ldsw_progbuf(31 downto 25) <= dataaddr_c(11 downto 5); -- destination address
                dm_ctrl.ldsw_progbuf(24 downto 20) <= dm_reg.command(4 downto 0); -- "regno" = source register
                dm_ctrl.ldsw_progbuf(11 downto 07) <= dataaddr_c(4 downto 0); -- destination address
              else -- "write" = 1 -> write to GPR
                dm_ctrl.ldsw_progbuf <= instr_lw_c;
                dm_ctrl.ldsw_progbuf(31 downto 20) <= dataaddr_c(11 downto 0); -- source address
                dm_ctrl.ldsw_progbuf(11 downto 07) <= dm_reg.command(4 downto 0); -- "regno" = destination register
              end if;
            else
              dm_ctrl.ldsw_progbuf <= instr_nop_c; -- NOP - do nothing
            end if;
            dm_ctrl.pbuf_en <= dm_reg.command(18); -- "postexec" - execute program buffer when set (execute as NOPs if not)
            dm_ctrl.state   <= CMD_TRIGGER;

          when CMD_TRIGGER => -- request CPU to execute command
          -- ------------------------------------------------------------
            dci.execute_req <= '1'; -- request execution
            if (dci.execute_ack = '1') then -- CPU starts execution
              dm_ctrl.state <= CMD_BUSY;
            end if;

          when CMD_BUSY => -- wait for CPU to finish
          -- ------------------------------------------------------------
            if (dci.halt_ack = '1') then -- CPU is parked (halted) again -> execution done
              dm_ctrl.state <= CMD_IDLE;
            end if;

          when CMD_ERROR => -- delay cycle for error to arrive abstracts.cmderr
          -- ------------------------------------------------------------
            dm_ctrl.state <= CMD_IDLE;

          when others => -- undefined
          -- ------------------------------------------------------------
            dm_ctrl.state <= CMD_IDLE;

        end case;

        -- error code --
        -- ------------------------------------------------------------
        if (dm_ctrl.cmderr = "000") then -- ready to set new error
          if (dm_ctrl.illegal_state = '1') then -- cannot execute since hart is not in expected state
            dm_ctrl.cmderr <= "100";
          elsif (dci.exception_ack = '1') then -- exception during execution
            dm_ctrl.cmderr <= "011";
          elsif (dm_ctrl.illegal_cmd = '1') then -- unsupported command
            dm_ctrl.cmderr <= "010";
          elsif (dm_reg.rd_acc_err = '1') or (dm_reg.wr_acc_err = '1') then -- invalid read/write while command is executing
            dm_ctrl.cmderr <= "001";
          end if;
        elsif (dm_reg.clr_acc_err = '1') then -- acknowledge/clear error flags
          dm_ctrl.cmderr <= "000";
        end if;

      end if;
    end if;
  end process dm_controller;

  -- controller busy flag --
  dm_ctrl.busy <= '0' when (dm_ctrl.state = CMD_IDLE) else '1';


  -- Hart Status ----------------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  hart_status: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      dm_ctrl.hart_halted     <= '0';
      dm_ctrl.hart_resume_req <= '0';
      dm_ctrl.hart_resume_ack <= '0';
      dm_ctrl.hart_reset      <= '0';
    elsif rising_edge(clk_i) then

      -- HALTED ACK --
      if (dm_reg.dmcontrol_ndmreset = '1') then
        dm_ctrl.hart_halted <= '0';
      elsif (dci.halt_ack = '1') then
        dm_ctrl.hart_halted <= '1';
      elsif (dci.resume_ack = '1') then
        dm_ctrl.hart_halted <= '0';
      end if;

      -- RESUME REQ --
      if (dm_reg.dmcontrol_ndmreset = '1') then
        dm_ctrl.hart_resume_req <= '0';
      elsif (dm_reg.resume_req = '1') then
        dm_ctrl.hart_resume_req <= '1';
      elsif (dci.resume_ack = '1') then
        dm_ctrl.hart_resume_req <= '0';
      end if;

      -- RESUME ACK --
      if (dm_reg.dmcontrol_ndmreset = '1') then
        dm_ctrl.hart_resume_ack <= '0';
      elsif (dci.resume_ack = '1') then
        dm_ctrl.hart_resume_ack <= '1';
      elsif (dm_reg.resume_req = '1') then
        dm_ctrl.hart_resume_ack <= '0';
      end if;

      -- hart has been RESET --
      if (dm_reg.dmcontrol_ndmreset = '1') then -- explicit RESET triggered by DM
        dm_ctrl.hart_reset <= '1';
      elsif (dm_reg.reset_ack = '1') then
        dm_ctrl.hart_reset <= '0';
      end if;

    end if;
  end process hart_status;


  -- Debug Module Interface - Write Access --------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  dmi_write_access: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      dm_reg.dmcontrol_ndmreset <= '0'; -- no system SoC reset
      dm_reg.dmcontrol_dmactive <= '0'; -- DM is in reset state after hardware reset
      --
      dm_reg.abstractauto_autoexecdata    <= '0';
      dm_reg.abstractauto_autoexecprogbuf <= "00";
      --
      dm_reg.command <= (others => '0');
      dm_reg.progbuf <= (others => instr_nop_c);
      --
      dm_reg.halt_req    <= '0';
      dm_reg.resume_req  <= '0';
      dm_reg.reset_ack   <= '0';
      dm_reg.wr_acc_err  <= '0';
      dm_reg.clr_acc_err <= '0';
      dm_reg.autoexec_wr <= '0';
    elsif rising_edge(clk_i) then

      -- default --
      dm_reg.resume_req  <= '0';
      dm_reg.reset_ack   <= '0';
      dm_reg.wr_acc_err  <= '0';
      dm_reg.clr_acc_err <= '0';
      dm_reg.autoexec_wr <= '0';

      -- DMI access --
      if (dmi_wren = '1') then -- valid DMI write request

        -- debug module control --
        if (dmi_req_i.addr = addr_dmcontrol_c) then
          dm_reg.halt_req           <= dmi_req_i.data(31); -- haltreq (-/w): write 1 to request halt; has to be cleared again by debugger
          dm_reg.resume_req         <= dmi_req_i.data(30); -- resumereq (-/w1): write 1 to request resume; auto-clears
          dm_reg.reset_ack          <= dmi_req_i.data(28); -- ackhavereset (-/w1): write 1 to ACK reset; auto-clears
          dm_reg.dmcontrol_ndmreset <= dmi_req_i.data(01); -- ndmreset (r/w): soc reset
          dm_reg.dmcontrol_dmactive <= dmi_req_i.data(00); -- dmactive (r/w): DM reset
        end if;

        -- write abstract command --
        if (dmi_req_i.addr = addr_command_c) then
          if (dm_ctrl.busy = '0') and (dm_ctrl.cmderr = "000") then -- idle and no errors yet
            dm_reg.command <= dmi_req_i.data;
          end if;
        end if;

        -- write abstract command autoexec --
        if (dmi_req_i.addr = addr_abstractauto_c) then
          if (dm_ctrl.busy = '0') then -- idle and no errors yet
            dm_reg.abstractauto_autoexecdata       <= dmi_req_i.data(00);
            dm_reg.abstractauto_autoexecprogbuf(0) <= dmi_req_i.data(16);
            dm_reg.abstractauto_autoexecprogbuf(1) <= dmi_req_i.data(17);
          end if;
        end if;

        -- auto execution trigger --
        if ((dmi_req_i.addr = addr_data0_c)    and (dm_reg.abstractauto_autoexecdata = '1')) or
           ((dmi_req_i.addr = addr_progbuf0_c) and (dm_reg.abstractauto_autoexecprogbuf(0) = '1')) or
           ((dmi_req_i.addr = addr_progbuf1_c) and (dm_reg.abstractauto_autoexecprogbuf(1) = '1')) then
          dm_reg.autoexec_wr <= '1';
        end if;

        -- acknowledge command error --
        if (dmi_req_i.addr = addr_abstractcs_c) then
          if (dmi_req_i.data(10 downto 8) = "111") then
            dm_reg.clr_acc_err <= '1';
          end if;
        end if;

        -- write program buffer --
        if (dmi_req_i.addr(dmi_req_i.addr'left downto 1) = addr_progbuf0_c(dmi_req_i.addr'left downto 1)) then
          if (dm_ctrl.busy = '0') then -- idle
            if (dmi_req_i.addr(0) = addr_progbuf0_c(0)) then
              dm_reg.progbuf(0) <= dmi_req_i.data;
            else
              dm_reg.progbuf(1) <= dmi_req_i.data;
            end if;
          end if;
        end if;

        -- invalid access while command is executing --
        if (dm_ctrl.busy = '1') then -- busy
          if (dmi_req_i.addr = addr_abstractcs_c) or
             (dmi_req_i.addr = addr_command_c) or
             (dmi_req_i.addr = addr_abstractauto_c) or
             (dmi_req_i.addr = addr_data0_c) or
             (dmi_req_i.addr = addr_progbuf0_c) or
             (dmi_req_i.addr = addr_progbuf1_c) then
            dm_reg.wr_acc_err <= '1';
          end if;
        end if;

      end if;
    end if;
  end process dmi_write_access;


  -- Direct Control -------------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  -- write to abstract data register --
  dci.data_we <= '1' when (dmi_wren = '1') and (dmi_req_i.addr = addr_data0_c) and (dm_ctrl.busy = '0') else '0';

  -- CPU halt/resume request --
  cpu_halt_req_o <= dm_reg.halt_req and dm_reg.dmcontrol_dmactive; -- single-shot
  dci.resume_req <= dm_ctrl.hart_resume_req; -- active until explicitly cleared

  -- SoC reset --
  cpu_ndmrstn_o <= '0' when (dm_reg.dmcontrol_ndmreset = '1') and (dm_reg.dmcontrol_dmactive = '1') else '1'; -- to processor's reset generator

  -- construct program buffer array for CPU access --
  cpu_progbuf(0) <= dm_ctrl.ldsw_progbuf; -- pseudo program buffer for GPR access
  cpu_progbuf(1) <= instr_nop_c when (dm_ctrl.pbuf_en = '0') else dm_reg.progbuf(0);
  cpu_progbuf(2) <= instr_nop_c when (dm_ctrl.pbuf_en = '0') else dm_reg.progbuf(1);
  cpu_progbuf(3) <= instr_ebreak_c; -- implicit ebreak instruction


  -- Debug Module Interface - Read Access ---------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  dmi_read_access: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      dmi_rsp_o.ack      <= '0';
      dmi_rsp_o.data     <= (others => '0');
      dm_reg.rd_acc_err  <= '0';
      dm_reg.autoexec_rd <= '0';
    elsif rising_edge(clk_i) then
      dmi_rsp_o.ack      <= dmi_wren or dmi_rden; -- always ACK any request
      dmi_rsp_o.data     <= (others => '0'); -- default
      dm_reg.rd_acc_err  <= '0';
      dm_reg.autoexec_rd <= '0';

      case dmi_req_i.addr is

        -- debug module status register --
        when addr_dmstatus_c =>
          dmi_rsp_o.data(31 downto 23) <= (others => '0');           -- reserved (r/-)
          dmi_rsp_o.data(22)           <= '1';                       -- impebreak (r/-): there is an implicit ebreak instruction after the visible program buffer
          dmi_rsp_o.data(21 downto 20) <= (others => '0');           -- reserved (r/-)
          dmi_rsp_o.data(19)           <= dm_ctrl.hart_reset;        -- allhavereset (r/-): there is only one hart that can be reset
          dmi_rsp_o.data(18)           <= dm_ctrl.hart_reset;        -- anyhavereset (r/-): there is only one hart that can be reset
          dmi_rsp_o.data(17)           <= dm_ctrl.hart_resume_ack;   -- allresumeack (r/-): there is only one hart that can acknowledge resume request
          dmi_rsp_o.data(16)           <= dm_ctrl.hart_resume_ack;   -- anyresumeack (r/-): there is only one hart that can acknowledge resume request
          dmi_rsp_o.data(15)           <= '0';                       -- allnonexistent (r/-): there is only one hart that is always existent
          dmi_rsp_o.data(14)           <= '0';                       -- anynonexistent (r/-): there is only one hart that is always existent
          dmi_rsp_o.data(13)           <= dm_reg.dmcontrol_ndmreset; -- allunavail (r/-): there is only one hart that is unavailable during reset
          dmi_rsp_o.data(12)           <= dm_reg.dmcontrol_ndmreset; -- anyunavail (r/-): there is only one hart that is unavailable during reset
          dmi_rsp_o.data(11)           <= not dm_ctrl.hart_halted;   -- allrunning (r/-): there is only one hart that can be RUNNING or HALTED
          dmi_rsp_o.data(10)           <= not dm_ctrl.hart_halted;   -- anyrunning (r/-): there is only one hart that can be RUNNING or HALTED
          dmi_rsp_o.data(09)           <= dm_ctrl.hart_halted;       -- allhalted (r/-): there is only one hart that can be RUNNING or HALTED
          dmi_rsp_o.data(08)           <= dm_ctrl.hart_halted;       -- anyhalted (r/-): there is only one hart that can be RUNNING or HALTED
          dmi_rsp_o.data(07)           <= '1';                       -- authenticated (r/-): authentication passed since there is no authentication
          dmi_rsp_o.data(06)           <= '0';                       -- authbusy (r/-): always ready since there is no authentication
          dmi_rsp_o.data(05)           <= '0';                       -- hasresethaltreq (r/-): halt-on-reset not implemented
          dmi_rsp_o.data(04)           <= '0';                       -- confstrptrvalid (r/-): no configuration string available
          dmi_rsp_o.data(03 downto 00) <= dm_version_c;              -- version (r/-): DM spec. version

        -- debug module control --
        when addr_dmcontrol_c =>
          dmi_rsp_o.data(31)           <= '0';                       -- haltreq (-/w): write-only
          dmi_rsp_o.data(30)           <= '0';                       -- resumereq (-/w1): write-only
          dmi_rsp_o.data(29)           <= '0';                       -- hartreset (r/w): not supported
          dmi_rsp_o.data(28)           <= '0';                       -- ackhavereset (-/w1): write-only
          dmi_rsp_o.data(27)           <= '0';                       -- reserved (r/-)
          dmi_rsp_o.data(26)           <= '0';                       -- hasel (r/-) - only a single hart can be selected at once
          dmi_rsp_o.data(25 downto 16) <= (others => '0');           -- hartsello (r/-) - there is only one hart
          dmi_rsp_o.data(15 downto 06) <= (others => '0');           -- hartselhi (r/-) - there is only one hart
          dmi_rsp_o.data(05 downto 04) <= (others => '0');           -- reserved (r/-)
          dmi_rsp_o.data(03)           <= '0';                       -- setresethaltreq (-/w1): halt-on-reset request - halt-on-reset not implemented
          dmi_rsp_o.data(02)           <= '0';                       -- clrresethaltreq (-/w1): halt-on-reset ack - halt-on-reset not implemented
          dmi_rsp_o.data(01)           <= dm_reg.dmcontrol_ndmreset; -- ndmreset (r/w): soc reset
          dmi_rsp_o.data(00)           <= dm_reg.dmcontrol_dmactive; -- dmactive (r/w): DM reset

        -- hart info --
        when addr_hartinfo_c =>
          dmi_rsp_o.data(31 downto 24) <= (others => '0');         -- reserved (r/-)
          dmi_rsp_o.data(23 downto 20) <= nscratch_c;              -- nscratch (r/-): number of dscratch CSRs
          dmi_rsp_o.data(19 downto 17) <= (others => '0');         -- reserved (r/-)
          dmi_rsp_o.data(16)           <= dataaccess_c;            -- dataaccess (r/-): 1: data registers are memory-mapped, 0: data registers are CSR-mapped
          dmi_rsp_o.data(15 downto 12) <= datasize_c;              -- datasize (r/-): number data registers in memory/CSR space
          dmi_rsp_o.data(11 downto 00) <= dataaddr_c(11 downto 0); -- dataaddr (r/-): data registers base address (memory/CSR)

        -- abstract control and status --
        when addr_abstractcs_c =>
          dmi_rsp_o.data(31 downto 24) <= (others => '0'); -- reserved (r/-)
          dmi_rsp_o.data(28 downto 24) <= "00010";         -- progbufsize (r/-): number of words in program buffer = 2
          dmi_rsp_o.data(12)           <= dm_ctrl.busy;    -- busy (r/-): abstract command in progress (1) / idle (0)
          dmi_rsp_o.data(11)           <= '1';             -- relaxedpriv (r/-): PMP rules are ignored when in debug-mode
          dmi_rsp_o.data(10 downto 08) <= dm_ctrl.cmderr;  -- cmderr (r/w1c): any error during execution?
          dmi_rsp_o.data(07 downto 04) <= (others => '0'); -- reserved (r/-)
          dmi_rsp_o.data(03 downto 00) <= "0001";          -- datacount (r/-): number of implemented data registers = 1

        -- abstract command (-/w) --
        when addr_command_c =>
          dmi_rsp_o.data <= (others => '0'); -- register is write-only

        -- abstract command autoexec (r/w) --
        when addr_abstractauto_c =>
          dmi_rsp_o.data(00) <= dm_reg.abstractauto_autoexecdata;       -- autoexecdata(0):    read/write access to data0 triggers execution of program buffer
          dmi_rsp_o.data(16) <= dm_reg.abstractauto_autoexecprogbuf(0); -- autoexecprogbuf(0): read/write access to progbuf0 triggers execution of program buffer
          dmi_rsp_o.data(17) <= dm_reg.abstractauto_autoexecprogbuf(1); -- autoexecprogbuf(1): read/write access to progbuf1 triggers execution of program buffer

        -- next debug module (r/-) --
        when addr_nextdm_c =>
          dmi_rsp_o.data <= (others => '0'); -- this is the only DM

        -- abstract data 0 (r/w) --
        when addr_data0_c =>
          dmi_rsp_o.data <= dci.data_reg;

        -- program buffer (r/w) --
        when addr_progbuf0_c =>
          if (LEGACY_MODE = true) then dmi_rsp_o.data <= dm_reg.progbuf(0); else dmi_rsp_o.data <= (others => '0'); end if; -- program buffer 0
        when addr_progbuf1_c =>
          if (LEGACY_MODE = true) then dmi_rsp_o.data <= dm_reg.progbuf(1); else dmi_rsp_o.data <= (others => '0'); end if; -- program buffer 1

        -- system bus access control and status (r/-) --
        when addr_sbcs_c =>
          dmi_rsp_o.data <= (others => '0'); -- system bus access not implemented

        -- halt summary 0 (r/-) --
        when addr_haltsum0_c =>
          dmi_rsp_o.data(0) <= dm_ctrl.hart_halted; -- hart 0 is halted

        -- not implemented (r/-) --
        when others =>
          dmi_rsp_o.data <= (others => '0');

      end case;

      -- invalid read access while command is executing --
      -- ------------------------------------------------------------
      if (dmi_rden = '1') then -- valid DMI read request
        if (dm_ctrl.busy = '1') then -- busy
          if (dmi_req_i.addr = addr_data0_c) or
             (dmi_req_i.addr = addr_progbuf0_c) or
             (dmi_req_i.addr = addr_progbuf1_c) then
            dm_reg.rd_acc_err <= '1';
          end if;
        end if;
      end if;

      -- auto execution trigger --
      -- ------------------------------------------------------------
      if (dmi_rden = '1') then -- valid DMI read request
        if ((dmi_req_i.addr = addr_data0_c)    and (dm_reg.abstractauto_autoexecdata = '1')) or
           ((dmi_req_i.addr = addr_progbuf0_c) and (dm_reg.abstractauto_autoexecprogbuf(0) = '1')) or
           ((dmi_req_i.addr = addr_progbuf1_c) and (dm_reg.abstractauto_autoexecprogbuf(1) = '1')) then
          dm_reg.autoexec_rd <= '1';
        end if;
      end if;

    end if;
  end process dmi_read_access;


  -- Bus Access ----------------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  bus_access: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      bus_rsp_o.ack     <= '0';
      bus_rsp_o.err     <= '0';
      bus_rsp_o.data    <= (others => '0');
      dci.data_reg      <= (others => '0');
      dci.halt_ack      <= '0';
      dci.resume_ack    <= '0';
      dci.execute_ack   <= '0';
      dci.exception_ack <= '0';
    elsif rising_edge(clk_i) then
      -- bus handshake --
      bus_rsp_o.ack  <= accen;
      bus_rsp_o.err  <= '0';
      bus_rsp_o.data <= (others => '0');

      -- data buffer --
      if (dci.data_we = '1') then -- DM write access
        dci.data_reg <= dmi_req_i.data;
      elsif (bus_req_i.addr(7 downto 6) = dm_data_base_c(7 downto 6)) and (wren = '1') then -- CPU write access
        dci.data_reg <= bus_req_i.data;
      end if;

      -- control and status register CPU write access --
      dci.halt_ack      <= '0'; -- all writable flags auto-clear
      dci.resume_ack    <= '0';
      dci.execute_ack   <= '0';
      dci.exception_ack <= '0';
      if (bus_req_i.addr(7 downto 6) = dm_sreg_base_c(7 downto 6)) and (wren = '1') then
        dci.halt_ack      <= bus_req_i.ben(sreg_halt_ack_c/8); -- [NOTE] use the individual BYTE ENABLES and not the actual write data
        dci.resume_ack    <= bus_req_i.ben(sreg_resume_ack_c/8);
        dci.execute_ack   <= bus_req_i.ben(sreg_execute_ack_c/8);
        dci.exception_ack <= bus_req_i.ben(sreg_exception_ack_c/8);
      end if;

      -- control and status register CPU read access --
      if (rden = '1') then -- output enable
        case bus_req_i.addr(7 downto 6) is -- module select
          when "00" => -- dm_code_base_c: code ROM
            bus_rsp_o.data <= code_rom(to_integer(unsigned(bus_req_i.addr(5 downto 2))));
          when "01" => -- dm_pbuf_base_c: program buffer
            bus_rsp_o.data <= cpu_progbuf(to_integer(unsigned(bus_req_i.addr(3 downto 2))));
          when "10" => -- dm_data_base_c: data buffer
            bus_rsp_o.data <= dci.data_reg;
          when others => -- dm_sreg_base_c: control and status register
            bus_rsp_o.data(sreg_resume_req_c)  <= dci.resume_req;
            bus_rsp_o.data(sreg_execute_req_c) <= dci.execute_req;
        end case;
      end if;
    end if;
  end process bus_access;

  -- access helpers --
  accen <= cpu_debug_i and bus_req_i.stb; -- allow access only when in debug-mode
  rden  <= accen and (not bus_req_i.rw);
  wren  <= accen and (    bus_req_i.rw);


end neorv32_debug_dm_rtl;
