/* Copyright 2018 ETH Zurich and University of Bologna.
 * Copyright and related rights are licensed under the Solderpad Hardware
 * License, Version 0.51 (the “License”); you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 * http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
 * or agreed to in writing, software, hardware and materials distributed under
 * this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 *
 * File:  dm_csrs.sv
 * Author: Florian Zaruba <zarubaf@iis.ee.ethz.ch>
 * Date:   30.6.2018
 *
 * Description: Debug CSRs. Communication over Debug Transport Module (DTM)
 */

module dm_csrs #(
  parameter int unsigned        NrHarts          = 1,
  parameter int unsigned        BusWidth         = 32,
  parameter logic [NrHarts-1:0] SelectableHarts  = {NrHarts{1'b1}}
) (
  input  logic                              clk_i,           // Clock
  input  logic                              rst_ni,          // Asynchronous reset active low
  input  logic                              testmode_i,
  input  logic                              dmi_rst_ni,      // Debug Module Intf reset active-low
  input  logic                              dmi_req_valid_i,
  output logic                              dmi_req_ready_o,
  input  dm::dmi_req_t                      dmi_req_i,
  // every request needs a response one cycle later
  output logic                              dmi_resp_valid_o,
  input  logic                              dmi_resp_ready_i,
  output dm::dmi_resp_t                     dmi_resp_o,
  // global ctrl
  output logic                              ndmreset_o,      // non-debug module reset active-high
  output logic                              dmactive_o,      // 1 -> debug-module is active,
                                                             // 0 -> synchronous re-set
  // hart status
  input  dm::hartinfo_t [NrHarts-1:0]       hartinfo_i,      // static hartinfo
  input  logic [NrHarts-1:0]                halted_i,        // hart is halted
  input  logic [NrHarts-1:0]                unavailable_i,   // e.g.: powered down
  input  logic [NrHarts-1:0]                resumeack_i,     // hart acknowledged resume request
  // hart control
  output logic [19:0]                       hartsel_o,       // hartselect to ctrl module
  output logic [NrHarts-1:0]                haltreq_o,       // request to halt a hart
  output logic [NrHarts-1:0]                resumereq_o,     // request hart to resume
  output logic                              clear_resumeack_o,

  output logic                              cmd_valid_o,       // debugger writing to cmd field
  output dm::command_t                      cmd_o,             // abstract command
  input  logic                              cmderror_valid_i,  // an error occurred
  input  dm::cmderr_e                       cmderror_i,        // this error occurred
  input  logic                              cmdbusy_i,         // cmd is currently busy executing

  output logic [dm::ProgBufSize-1:0][31:0]  progbuf_o, // to system bus
  output logic [dm::DataCount-1:0][31:0]    data_o,

  input  logic [dm::DataCount-1:0][31:0]    data_i,
  input  logic                              data_valid_i,
  // system bus access module (SBA)
  output logic [BusWidth-1:0]               sbaddress_o,
  input  logic [BusWidth-1:0]               sbaddress_i,
  output logic                              sbaddress_write_valid_o,
  // control signals in
  output logic                              sbreadonaddr_o,
  output logic                              sbautoincrement_o,
  output logic [2:0]                        sbaccess_o,
  // data out
  output logic                              sbreadondata_o,
  output logic [BusWidth-1:0]               sbdata_o,
  output logic                              sbdata_read_valid_o,
  output logic                              sbdata_write_valid_o,
  // read data in
  input  logic [BusWidth-1:0]               sbdata_i,
  input  logic                              sbdata_valid_i,
  // control signals
  input  logic                              sbbusy_i,
  input  logic                              sberror_valid_i, // bus error occurred
  input  logic [2:0]                        sberror_i // bus error occurred
);

  // the amount of bits we need to represent all harts
  localparam int unsigned HartSelLen = (NrHarts == 1) ? 1 : $clog2(NrHarts);
  localparam int unsigned NrHartsAligned = 2**HartSelLen;

  dm::dtm_op_e dtm_op;
  assign dtm_op = dm::dtm_op_e'(dmi_req_i.op);

  logic [31:0] resp_queue_data;

  localparam dm::dm_csr_e DataEnd = dm::dm_csr_e'(dm::Data0 + {4'b0, dm::DataCount} - 8'h1);
  localparam dm::dm_csr_e ProgBufEnd = dm::dm_csr_e'(dm::ProgBuf0 + {4'b0, dm::ProgBufSize} - 8'h1);

  logic [31:0] haltsum0, haltsum1, haltsum2, haltsum3;
  logic [((NrHarts-1)/2**5 + 1) * 32 - 1 : 0] halted;
  logic [(NrHarts-1)/2**5:0][31:0] halted_reshaped0;
  logic [(NrHarts-1)/2**10:0][31:0] halted_reshaped1;
  logic [(NrHarts-1)/2**15:0][31:0] halted_reshaped2;
  logic [((NrHarts-1)/2**10+1)*32-1:0] halted_flat1;
  logic [((NrHarts-1)/2**15+1)*32-1:0] halted_flat2;
  logic [31:0] halted_flat3;

  // haltsum0
  logic [14:0] hartsel_idx0;
  always_comb begin : p_haltsum0
    halted              = '0;
    haltsum0            = '0;
    hartsel_idx0        = hartsel_o[19:5];
    halted[NrHarts-1:0] = halted_i;
    halted_reshaped0    = halted;
    if (hartsel_idx0 < 15'((NrHarts-1)/2**5+1)) begin
      haltsum0 = halted_reshaped0[hartsel_idx0];
    end
  end

  // haltsum1
  logic [9:0] hartsel_idx1;
  always_comb begin : p_reduction1
    halted_flat1 = '0;
    haltsum1     = '0;
    hartsel_idx1 = hartsel_o[19:10];

    for (int unsigned k = 0; k < (NrHarts-1)/2**5+1; k++) begin
      halted_flat1[k] = |halted_reshaped0[k];
    end
    halted_reshaped1 = halted_flat1;

    if (hartsel_idx1 < 10'(((NrHarts-1)/2**10+1))) begin
      haltsum1 = halted_reshaped1[hartsel_idx1];
    end
  end

  // haltsum2
  logic [4:0] hartsel_idx2;
  always_comb begin : p_reduction2
    halted_flat2 = '0;
    haltsum2     = '0;
    hartsel_idx2 = hartsel_o[19:15];

    for (int unsigned k = 0; k < (NrHarts-1)/2**10+1; k++) begin
      halted_flat2[k] = |halted_reshaped1[k];
    end
    halted_reshaped2 = halted_flat2;

    if (hartsel_idx2 < 5'(((NrHarts-1)/2**15+1))) begin
      haltsum2         = halted_reshaped2[hartsel_idx2];
    end
  end

  // haltsum3
  always_comb begin : p_reduction3
    halted_flat3 = '0;
    for (int unsigned k = 0; k < NrHarts/2**15+1; k++) begin
      halted_flat3[k] = |halted_reshaped2[k];
    end
    haltsum3 = halted_flat3;
  end


  dm::dmstatus_t      dmstatus;
  dm::dmcontrol_t     dmcontrol_d, dmcontrol_q;
  dm::abstractcs_t    abstractcs;
  dm::cmderr_e        cmderr_d, cmderr_q;
  dm::command_t       command_d, command_q;
  logic               cmd_valid_d, cmd_valid_q;
  dm::abstractauto_t  abstractauto_d, abstractauto_q;
  dm::sbcs_t          sbcs_d, sbcs_q;
  logic [63:0]        sbaddr_d, sbaddr_q;
  logic [63:0]        sbdata_d, sbdata_q;

  logic [NrHarts-1:0] havereset_d, havereset_q;
  // program buffer
  logic [dm::ProgBufSize-1:0][31:0] progbuf_d, progbuf_q;
  logic [dm::DataCount-1:0][31:0] data_d, data_q;

  logic [HartSelLen-1:0] selected_hart;

  // a successful response returns zero
  assign dmi_resp_o.resp = dm::DTM_SUCCESS;
  // SBA
  assign sbautoincrement_o = sbcs_q.sbautoincrement;
  assign sbreadonaddr_o    = sbcs_q.sbreadonaddr;
  assign sbreadondata_o    = sbcs_q.sbreadondata;
  assign sbaccess_o        = sbcs_q.sbaccess;
  assign sbdata_o          = sbdata_q[BusWidth-1:0];
  assign sbaddress_o       = sbaddr_q[BusWidth-1:0];

  assign hartsel_o         = {dmcontrol_q.hartselhi, dmcontrol_q.hartsello};

  // needed to avoid lint warnings
  logic [NrHartsAligned-1:0] havereset_d_aligned, havereset_q_aligned,
                             resumeack_aligned, unavailable_aligned,
                             halted_aligned;
  assign resumeack_aligned   = NrHartsAligned'(resumeack_i);
  assign unavailable_aligned = NrHartsAligned'(unavailable_i);
  assign halted_aligned      = NrHartsAligned'(halted_i);

  assign havereset_d         = NrHarts'(havereset_d_aligned);
  assign havereset_q_aligned = NrHartsAligned'(havereset_q);

  dm::hartinfo_t [NrHartsAligned-1:0] hartinfo_aligned;
  always_comb begin : p_hartinfo_align
    hartinfo_aligned = '0;
    hartinfo_aligned[NrHarts-1:0] = hartinfo_i;
  end

  // helper variables
  dm::sbcs_t sbcs;
  dm::dmcontrol_t dmcontrol;
  dm::abstractcs_t a_abstractcs;
  logic [4:0] autoexecdata_idx;
  always_comb begin : csr_read_write
    // --------------------
    // Static Values (R/O)
    // --------------------
    // dmstatus
    dmstatus    = '0;
    dmstatus.version = dm::DbgVersion013;
    // no authentication implemented
    dmstatus.authenticated = 1'b1;
    // we do not support halt-on-reset sequence
    dmstatus.hasresethaltreq = 1'b0;
    // TODO(zarubaf) things need to change here if we implement the array mask
    dmstatus.allhavereset = havereset_q_aligned[selected_hart];
    dmstatus.anyhavereset = havereset_q_aligned[selected_hart];

    dmstatus.allresumeack = resumeack_aligned[selected_hart];
    dmstatus.anyresumeack = resumeack_aligned[selected_hart];

    dmstatus.allunavail   = unavailable_aligned[selected_hart];
    dmstatus.anyunavail   = unavailable_aligned[selected_hart];

    // as soon as we are out of the legal Hart region tell the debugger
    // that there are only non-existent harts
    dmstatus.allnonexistent = logic'(32'(hartsel_o) > (NrHarts - 1));
    dmstatus.anynonexistent = logic'(32'(hartsel_o) > (NrHarts - 1));

    // We are not allowed to be in multiple states at once. This is a to
    // make the running/halted and unavailable states exclusive.
    dmstatus.allhalted    = halted_aligned[selected_hart] & ~unavailable_aligned[selected_hart];
    dmstatus.anyhalted    = halted_aligned[selected_hart] & ~unavailable_aligned[selected_hart];

    dmstatus.allrunning   = ~halted_aligned[selected_hart] & ~unavailable_aligned[selected_hart];
    dmstatus.anyrunning   = ~halted_aligned[selected_hart] & ~unavailable_aligned[selected_hart];

    // abstractcs
    abstractcs = '0;
    abstractcs.datacount = dm::DataCount;
    abstractcs.progbufsize = dm::ProgBufSize;
    abstractcs.busy = cmdbusy_i;
    abstractcs.cmderr = cmderr_q;

    // abstractautoexec
    abstractauto_d = abstractauto_q;
    abstractauto_d.zero0 = '0;

    // default assignments
    havereset_d_aligned = NrHartsAligned'(havereset_q);
    dmcontrol_d         = dmcontrol_q;
    cmderr_d            = cmderr_q;
    command_d           = command_q;
    progbuf_d           = progbuf_q;
    data_d              = data_q;
    sbcs_d              = sbcs_q;
    sbaddr_d            = 64'(sbaddress_i);
    sbdata_d            = sbdata_q;

    resp_queue_data         = 32'b0;
    cmd_valid_d             = 1'b0;
    sbaddress_write_valid_o = 1'b0;
    sbdata_read_valid_o     = 1'b0;
    sbdata_write_valid_o    = 1'b0;
    clear_resumeack_o       = 1'b0;

    // helper variables
    sbcs         = '0;
    dmcontrol    = '0;
    a_abstractcs = '0;

    autoexecdata_idx    = dmi_req_i.addr[4:0] - 5'(dm::Data0);

    // localparam int unsigned DataCountAlign = $clog2(dm::DataCount);
    // reads
    if (dmi_req_ready_o && dmi_req_valid_i && dtm_op == dm::DTM_READ) begin
      unique case ({1'b0, dmi_req_i.addr}) inside
        [(dm::Data0):DataEnd]: begin
          // logic [$clog2(dm::DataCount)-1:0] resp_queue_idx;
          // resp_queue_idx = dmi_req_i.addr[4:0] - int'(dm::Data0);
          resp_queue_data = data_q[$clog2(dm::DataCount)'(autoexecdata_idx)];
          if (!cmdbusy_i) begin
            // check whether we need to re-execute the command (just give a cmd_valid)
            if (autoexecdata_idx < $bits(abstractauto_q.autoexecdata)) begin
              cmd_valid_d = abstractauto_q.autoexecdata[autoexecdata_idx];
            end
          end
        end
        dm::DMControl:    resp_queue_data = dmcontrol_q;
        dm::DMStatus:     resp_queue_data = dmstatus;
        dm::Hartinfo:     resp_queue_data = hartinfo_aligned[selected_hart];
        dm::AbstractCS:   resp_queue_data = abstractcs;
        dm::AbstractAuto: resp_queue_data = abstractauto_q;
        // command is read-only
        dm::Command:    resp_queue_data = '0;
        [(dm::ProgBuf0):ProgBufEnd]: begin
          resp_queue_data = progbuf_q[dmi_req_i.addr[$clog2(dm::ProgBufSize)-1:0]];
          if (!cmdbusy_i) begin
            // check whether we need to re-execute the command (just give a cmd_valid)
            // range of autoexecprogbuf is 31:16
            cmd_valid_d = abstractauto_q.autoexecprogbuf[{1'b1, dmi_req_i.addr[3:0]}];
          end
        end
        dm::HaltSum0: resp_queue_data = haltsum0;
        dm::HaltSum1: resp_queue_data = haltsum1;
        dm::HaltSum2: resp_queue_data = haltsum2;
        dm::HaltSum3: resp_queue_data = haltsum3;
        dm::SBCS: begin
          resp_queue_data = sbcs_q;
        end
        dm::SBAddress0: begin
          // access while the SBA was busy
          if (sbbusy_i) begin
            sbcs_d.sbbusyerror = 1'b1;
          end else begin
            resp_queue_data = sbaddr_q[31:0];
          end
        end
        dm::SBAddress1: begin
          // access while the SBA was busy
          if (sbbusy_i) begin
            sbcs_d.sbbusyerror = 1'b1;
          end else begin
            resp_queue_data = sbaddr_q[63:32];
          end
        end
        dm::SBData0: begin
          // access while the SBA was busy
          if (sbbusy_i) begin
            sbcs_d.sbbusyerror = 1'b1;
          end else begin
            sbdata_read_valid_o = (sbcs_q.sberror == '0);
            resp_queue_data = sbdata_q[31:0];
          end
        end
        dm::SBData1: begin
          // access while the SBA was busy
          if (sbbusy_i) begin
            sbcs_d.sbbusyerror = 1'b1;
          end else begin
            resp_queue_data = sbdata_q[63:32];
          end
        end
        default:;
      endcase
    end

    // write
    if (dmi_req_ready_o && dmi_req_valid_i && dtm_op == dm::DTM_WRITE) begin
      unique case (dm::dm_csr_e'({1'b0, dmi_req_i.addr})) inside
        [(dm::Data0):DataEnd]: begin
          // attempts to write them while busy is set does not change their value
          if (!cmdbusy_i && dm::DataCount > 0) begin
            data_d[dmi_req_i.addr[$clog2(dm::DataCount)-1:0]] = dmi_req_i.data;
            // check whether we need to re-execute the command (just give a cmd_valid)
            if (autoexecdata_idx < $bits(abstractauto_q.autoexecdata)) begin
              cmd_valid_d = abstractauto_q.autoexecdata[autoexecdata_idx];
            end
          end
        end
        dm::DMControl: begin
          dmcontrol = dm::dmcontrol_t'(dmi_req_i.data);
          // clear the havreset of the selected hart
          if (dmcontrol.ackhavereset) begin
            havereset_d_aligned[selected_hart] = 1'b0;
          end
          dmcontrol_d = dmi_req_i.data;
        end
        dm::DMStatus:; // write are ignored to R/O register
        dm::Hartinfo:; // hartinfo is R/O
        // only command error is write-able
        dm::AbstractCS: begin // W1C
          // Gets set if an abstract command fails. The bits in this
          // field remain set until they are cleared by writing 1 to
          // them. No abstract command is started until the value is
          // reset to 0.
          a_abstractcs = dm::abstractcs_t'(dmi_req_i.data);
          // reads during abstract command execution are not allowed
          if (!cmdbusy_i) begin
            cmderr_d = dm::cmderr_e'(~a_abstractcs.cmderr & cmderr_q);
          end else if (cmderr_q == dm::CmdErrNone) begin
            cmderr_d = dm::CmdErrBusy;
          end
        end
        dm::Command: begin
          // writes are ignored if a command is already busy
          if (!cmdbusy_i) begin
            cmd_valid_d = 1'b1;
            command_d = dm::command_t'(dmi_req_i.data);
          // if there was an attempted to write during a busy execution
          // and the cmderror field is zero set the busy error
          end else if (cmderr_q == dm::CmdErrNone) begin
            cmderr_d = dm::CmdErrBusy;
          end
        end
        dm::AbstractAuto: begin
          // this field can only be written legally when there is no command executing
          if (!cmdbusy_i) begin
            abstractauto_d                 = 32'b0;
            abstractauto_d.autoexecdata    = 12'(dmi_req_i.data[dm::DataCount-1:0]);
            abstractauto_d.autoexecprogbuf = 16'(dmi_req_i.data[dm::ProgBufSize-1+16:16]);
          end else if (cmderr_q == dm::CmdErrNone) begin
            cmderr_d = dm::CmdErrBusy;
          end
        end
        [(dm::ProgBuf0):ProgBufEnd]: begin
          // attempts to write them while busy is set does not change their value
          if (!cmdbusy_i) begin
            progbuf_d[dmi_req_i.addr[$clog2(dm::ProgBufSize)-1:0]] = dmi_req_i.data;
            // check whether we need to re-execute the command (just give a cmd_valid)
            // this should probably throw an error if executed during another command
            // was busy
            // range of autoexecprogbuf is 31:16
            cmd_valid_d = abstractauto_q.autoexecprogbuf[{1'b1, dmi_req_i.addr[3:0]}];
          end
        end
        dm::SBCS: begin
          // access while the SBA was busy
          if (sbbusy_i) begin
            sbcs_d.sbbusyerror = 1'b1;
          end else begin
            sbcs = dm::sbcs_t'(dmi_req_i.data);
            sbcs_d = sbcs;
            // R/W1C
            sbcs_d.sbbusyerror = sbcs_q.sbbusyerror & (~sbcs.sbbusyerror);
            sbcs_d.sberror     = sbcs_q.sberror     & (~sbcs.sberror);
          end
        end
        dm::SBAddress0: begin
          // access while the SBA was busy
          if (sbbusy_i) begin
            sbcs_d.sbbusyerror = 1'b1;
          end else begin
            sbaddr_d[31:0] = dmi_req_i.data;
            sbaddress_write_valid_o = (sbcs_q.sberror == '0);
          end
        end
        dm::SBAddress1: begin
          // access while the SBA was busy
          if (sbbusy_i) begin
            sbcs_d.sbbusyerror = 1'b1;
          end else begin
            sbaddr_d[63:32] = dmi_req_i.data;
          end
        end
        dm::SBData0: begin
          // access while the SBA was busy
          if (sbbusy_i) begin
           sbcs_d.sbbusyerror = 1'b1;
          end else begin
            sbdata_d[31:0] = dmi_req_i.data;
            sbdata_write_valid_o = (sbcs_q.sberror == '0);
          end
        end
        dm::SBData1: begin
          // access while the SBA was busy
          if (sbbusy_i) begin
           sbcs_d.sbbusyerror = 1'b1;
          end else begin
            sbdata_d[63:32] = dmi_req_i.data;
          end
        end
        default:;
      endcase
    end
    // hart threw a command error and has precedence over bus writes
    if (cmderror_valid_i) begin
      cmderr_d = cmderror_i;
    end

    // update data registers
    if (data_valid_i) begin
      data_d = data_i;
    end

    // set the havereset flag when we did a ndmreset
    if (ndmreset_o) begin
      havereset_d_aligned[NrHarts-1:0] = '1;
    end
    // -------------
    // System Bus
    // -------------
    // set bus error
    if (sberror_valid_i) begin
      sbcs_d.sberror = sberror_i;
    end
    // update read data
    if (sbdata_valid_i) begin
      sbdata_d = 64'(sbdata_i);
    end

    // dmcontrol
    // TODO(zarubaf) we currently do not implement the hartarry mask
    dmcontrol_d.hasel           = 1'b0;
    // we do not support resetting an individual hart
    dmcontrol_d.hartreset       = 1'b0;
    dmcontrol_d.setresethaltreq = 1'b0;
    dmcontrol_d.clrresethaltreq = 1'b0;
    dmcontrol_d.zero1           = '0;
    dmcontrol_d.zero0           = '0;
    // Non-writeable, clear only
    dmcontrol_d.ackhavereset    = 1'b0;
    if (!dmcontrol_q.resumereq && dmcontrol_d.resumereq) begin
      clear_resumeack_o = 1'b1;
    end
    if (dmcontrol_q.resumereq && resumeack_i) begin
      dmcontrol_d.resumereq = 1'b0;
    end
    // static values for dcsr
    sbcs_d.sbversion            = 3'd1;
    sbcs_d.sbbusy               = sbbusy_i;
    sbcs_d.sbasize              = $bits(sbcs_d.sbasize)'(BusWidth);
    sbcs_d.sbaccess128          = 1'b0;
    sbcs_d.sbaccess64           = logic'(BusWidth == 32'd64);
    sbcs_d.sbaccess32           = logic'(BusWidth == 32'd32);
    sbcs_d.sbaccess16           = 1'b0;
    sbcs_d.sbaccess8            = 1'b0;
    sbcs_d.sbaccess             = (BusWidth == 32'd64) ? 3'd3 : 3'd2;
  end

  // output multiplexer
  always_comb begin : p_outmux
    selected_hart = hartsel_o[HartSelLen-1:0];
    // default assignment
    haltreq_o = '0;
    resumereq_o = '0;
    if (selected_hart < (HartSelLen+1)'(NrHarts)) begin
      haltreq_o[selected_hart]   = dmcontrol_q.haltreq;
      resumereq_o[selected_hart] = dmcontrol_q.resumereq;
    end
  end

  assign dmactive_o  = dmcontrol_q.dmactive;
  assign cmd_o       = command_q;
  assign cmd_valid_o = cmd_valid_q;
  assign progbuf_o   = progbuf_q;
  assign data_o      = data_q;

  assign ndmreset_o = dmcontrol_q.ndmreset;

  logic unused_testmode;
  assign unused_testmode = testmode_i;

  // response FIFO
  prim_fifo_sync #(
    .Width   (32),
    .Pass    (1'b0),
    .Depth   (2)
  ) i_fifo (
    .clk_i   ( clk_i                ),
    .rst_ni  ( dmi_rst_ni           ), // reset only when system is re-set
    .clr_i   ( 1'b0                 ),
    .wdata_i ( resp_queue_data      ),
    .wvalid_i( dmi_req_valid_i      ),
    .wready_o( dmi_req_ready_o      ),
    .rdata_o ( dmi_resp_o.data      ),
    .rvalid_o( dmi_resp_valid_o     ),
    .rready_i( dmi_resp_ready_i     ),
    .depth_o (                      )  // Doesn't use
  );

  always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
    // PoR
    if (!rst_ni) begin
      dmcontrol_q    <= '0;
      // this is the only write-able bit during reset
      cmderr_q       <= dm::CmdErrNone;
      command_q      <= '0;
      cmd_valid_q    <= '0;
      abstractauto_q <= '0;
      progbuf_q      <= '0;
      data_q         <= '0;
      sbcs_q         <= '0;
      sbaddr_q       <= '0;
      sbdata_q       <= '0;
      havereset_q    <= '1;
    end else begin
      havereset_q    <= SelectableHarts & havereset_d;
      // synchronous re-set of debug module, active-low, except for dmactive
      if (!dmcontrol_q.dmactive) begin
        dmcontrol_q.haltreq          <= '0;
        dmcontrol_q.resumereq        <= '0;
        dmcontrol_q.hartreset        <= '0;
        dmcontrol_q.ackhavereset     <= '0;
        dmcontrol_q.zero1            <= '0;
        dmcontrol_q.hasel            <= '0;
        dmcontrol_q.hartsello        <= '0;
        dmcontrol_q.hartselhi        <= '0;
        dmcontrol_q.zero0            <= '0;
        dmcontrol_q.setresethaltreq  <= '0;
        dmcontrol_q.clrresethaltreq  <= '0;
        dmcontrol_q.ndmreset         <= '0;
        // this is the only write-able bit during reset
        dmcontrol_q.dmactive         <= dmcontrol_d.dmactive;
        cmderr_q                     <= dm::CmdErrNone;
        command_q                    <= '0;
        cmd_valid_q                  <= '0;
        abstractauto_q               <= '0;
        progbuf_q                    <= '0;
        data_q                       <= '0;
        sbcs_q                       <= '0;
        sbaddr_q                     <= '0;
        sbdata_q                     <= '0;
      end else begin
        dmcontrol_q                  <= dmcontrol_d;
        cmderr_q                     <= cmderr_d;
        command_q                    <= command_d;
        cmd_valid_q                  <= cmd_valid_d;
        abstractauto_q               <= abstractauto_d;
        progbuf_q                    <= progbuf_d;
        data_q                       <= data_d;
        sbcs_q                       <= sbcs_d;
        sbaddr_q                     <= sbaddr_d;
        sbdata_q                     <= sbdata_d;
      end
    end
  end

  ///////////////////////////////////////////////////////
  // assertions
  ///////////////////////////////////////////////////////

  //pragma translate_off
  `ifndef VERILATOR
  haltsum: assert property (
      @(posedge clk_i) disable iff (!rst_ni)
          (dmi_req_ready_o && dmi_req_valid_i && dtm_op == dm::DTM_READ) |->
              !({1'b0, dmi_req_i.addr} inside
                  {dm::HaltSum0, dm::HaltSum1, dm::HaltSum2, dm::HaltSum3}))
      else $warning("Haltsums have not been properly tested yet.");
  `endif
  //pragma translate_on

endmodule : dm_csrs
