/*****************************************************************************\
|                      Copyright (C) 2021-2022 Luke Wren                      |
|                     SPDX-License-Identifier: Apache-2.0                     |
\*****************************************************************************/

// RISC-V Debug Module for Hazard3. Supports up to 32 cores (1 hart per core).

`default_nettype none

module hazard3_dm #(
	// Where there are multiple harts per DM, the least-indexed hart is the
	// least-significant on each concatenated hart access bus.
	parameter N_HARTS      = 1,
	// Where there are multiple DMs, the address of each DM should be a
	// multiple of 'h200, so that bits[8:2] decode correctly.
	parameter NEXT_DM_ADDR = 32'h0000_0000,
	// Implement support for system bus access:
	parameter HAVE_SBA     = 0,

	// Do not modify:
	parameter XLEN         = 32,                               // Do not modify
	parameter W_HARTSEL    = N_HARTS > 1 ? $clog2(N_HARTS) : 1 // Do not modify
) (
	// DM is assumed to be in same clock domain as core; clock crossing
	// (if any) is inside DTM, or between DTM and DM.
	input  wire                      clk,
	input  wire                      rst_n,

	// APB access from Debug Transport Module
	input  wire                      dmi_psel,
	input  wire                      dmi_penable,
	input  wire                      dmi_pwrite,
	input  wire [8:0]                dmi_paddr,
	input  wire [31:0]               dmi_pwdata,
	output reg  [31:0]               dmi_prdata,
	output wire                      dmi_pready,
	output wire                      dmi_pslverr,

	// Reset request/acknowledge. "req" is a pulse >= 1 cycle wide. "done" is
	// level-sensitive, goes high once component is out of reset.
	//
	// The "sys" reset (ndmreset) is conventionally everything apart from DM +
	// DTM, but, as per section 3.2 in 0.13.2 debug spec: "Exactly what is
	// affected by this reset is implementation dependent, as long as it is
	// possible to debug programs from the first instruction executed." So
	// this could simply be an all-hart reset.
	output wire                      sys_reset_req,
	input  wire                      sys_reset_done,
	output wire [N_HARTS-1:0]        hart_reset_req,
	input  wire [N_HARTS-1:0]        hart_reset_done,

	// Hart run/halt control
	output wire [N_HARTS-1:0]        hart_req_halt,
	output wire [N_HARTS-1:0]        hart_req_halt_on_reset,
	output wire [N_HARTS-1:0]        hart_req_resume,
	input  wire [N_HARTS-1:0]        hart_halted,
	input  wire [N_HARTS-1:0]        hart_running,

	// Hart access to data0 CSR (assumed to be core-internal but per-hart)
	output wire [N_HARTS*XLEN-1:0]   hart_data0_rdata,
	input  wire [N_HARTS*XLEN-1:0]   hart_data0_wdata,
	input  wire [N_HARTS-1:0]        hart_data0_wen,

	// Hart instruction injection
	output wire [N_HARTS*32-1:0]     hart_instr_data,
	output reg  [N_HARTS-1:0]        hart_instr_data_vld,
	input  wire [N_HARTS-1:0]        hart_instr_data_rdy,
	input  wire [N_HARTS-1:0]        hart_instr_caught_exception,
	input  wire [N_HARTS-1:0]        hart_instr_caught_ebreak,

	// System bus access (optional) -- can be hooked up to the standalone AHB
	// shim (hazard3_sbus_to_ahb.v) or the SBA input port on the processor
	// wrapper, which muxes SBA into the processor's load/store bus access
	// port. SBA does not increase debugger bus throughput, but supports
	// minimally intrusive debug bus access for e.g. Segger RTT.
	output wire [31:0]               sbus_addr,
	output wire                      sbus_write,
	output wire [1:0]                sbus_size,
	output wire                      sbus_vld,
	input  wire                      sbus_rdy,
	input  wire                      sbus_err,
	output wire [31:0]               sbus_wdata,
	input  wire [31:0]               sbus_rdata
);

wire dmi_write = dmi_psel && dmi_penable && dmi_pready && dmi_pwrite;
wire dmi_read = dmi_psel && dmi_penable && dmi_pready && !dmi_pwrite;
assign dmi_pready = 1'b1;
assign dmi_pslverr = 1'b0;

// Program buffer is fixed at 2 words plus impebreak. The main thing we care
// about is support for efficient memory block transfers using abstractauto;
// in this case 2 words + impebreak is sufficient for RV32I, and 1 word +
// impebreak is sufficient for RV32IC.
localparam PROGBUF_SIZE = 2;

// ----------------------------------------------------------------------------
// Address constants

localparam ADDR_DATA0        = 7'h04;
// Other data registers not present.
localparam ADDR_DMCONTROL    = 7'h10;
localparam ADDR_DMSTATUS     = 7'h11;
localparam ADDR_HARTINFO     = 7'h12;
localparam ADDR_HALTSUM1     = 7'h13;
localparam ADDR_HALTSUM0     = 7'h40;
// No HALTSUM2+ registers (we don't support >32 harts anyway)
localparam ADDR_HAWINDOWSEL  = 7'h14;
localparam ADDR_HAWINDOW     = 7'h15;
localparam ADDR_ABSTRACTCS   = 7'h16;
localparam ADDR_COMMAND      = 7'h17;
localparam ADDR_ABSTRACTAUTO = 7'h18;
localparam ADDR_CONFSTRPTR0  = 7'h19;
localparam ADDR_CONFSTRPTR1  = 7'h1a;
localparam ADDR_CONFSTRPTR2  = 7'h1b;
localparam ADDR_CONFSTRPTR3  = 7'h1c;
localparam ADDR_NEXTDM       = 7'h1d;
localparam ADDR_PROGBUF0     = 7'h20;
localparam ADDR_PROGBUF1     = 7'h21;
// No authentication
localparam ADDR_SBCS         = 7'h38;
localparam ADDR_SBADDRESS0   = 7'h39;
localparam ADDR_SBDATA0      = 7'h3c;

// APB is byte-addressed, DM registers are word-addressed.
wire [6:0] dmi_regaddr = dmi_paddr[8:2];

// ----------------------------------------------------------------------------
// Hart selection

reg dmactive;

// Some fiddliness to make sure we get a single-wide zero-valued signal when
// N_HARTS == 1 (so we can use this for indexing of per-hart signals)
reg  [W_HARTSEL-1:0] hartsel;
wire [W_HARTSEL-1:0] hartsel_next;

generate
if (N_HARTS > 1) begin: has_hartsel

	// Only the lower 10 bits of hartsel are supported
	assign hartsel_next = dmi_write && dmi_regaddr == ADDR_DMCONTROL ?
		dmi_pwdata[16 +: W_HARTSEL] : hartsel;

end else begin: has_no_hartsel

	assign hartsel_next = 1'b0;

end
endgenerate

always @ (posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		hartsel <= {W_HARTSEL{1'b0}};
	end else if (!dmactive) begin
		hartsel <= {W_HARTSEL{1'b0}};
	end else begin
		hartsel <= hartsel_next;
	end
end

// Also implement the hart array mask if there is more than one hart.
reg  [N_HARTS-1:0] hart_array_mask;
reg                hasel;
wire [N_HARTS-1:0] hart_array_mask_next;
wire               hasel_next;

generate
if (N_HARTS > 1) begin: has_array_mask

	assign hart_array_mask_next = dmi_write && dmi_regaddr == ADDR_HAWINDOW ?
		dmi_pwdata[N_HARTS-1:0] : hart_array_mask;
	assign hasel_next = dmi_write && dmi_regaddr == ADDR_DMCONTROL ?
		dmi_pwdata[26] : hasel;

end else begin: has_no_array_mask

	assign hart_array_mask_next = 1'b0;
	assign hasel_next = 1'b0;

end
endgenerate

always @ (posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		hart_array_mask <= {N_HARTS{1'b0}};
		hasel <= 1'b0;
	end else if (!dmactive) begin
		hart_array_mask <= {N_HARTS{1'b0}};
		hasel <= 1'b0;
	end else begin
		hart_array_mask <= hart_array_mask_next;
		hasel <= hasel_next;
	end
end

// ----------------------------------------------------------------------------
// Run/halt/reset control

// Normal read/write fields for dmcontrol (note some of these are per-hart
// fields that get rotated into dmcontrol based on the current/next hartsel).
reg  [N_HARTS-1:0] dmcontrol_haltreq;
reg  [N_HARTS-1:0] dmcontrol_hartreset;
reg  [N_HARTS-1:0] dmcontrol_resethaltreq;
reg                dmcontrol_ndmreset;

wire [N_HARTS-1:0] dmcontrol_op_mask;

generate
if (N_HARTS > 1) begin: dmcontrol_multiple_harts

	// Selection is the hart selected by hartsel, *plus* the hart array mask
	// if hasel is set. Note we don't need to use the "next" version of
	// hart_array_mask since it can't change simultaneously with dmcontrol.
	assign dmcontrol_op_mask =
		(hartsel_next >= N_HARTS ? {N_HARTS{1'b0}} : {{N_HARTS-1{1'b0}}, 1'b1} << hartsel_next)
		| ({N_HARTS{hasel_next}} & hart_array_mask);

end else begin: dmcontrol_single_hart

	assign dmcontrol_op_mask = 1'b1;

end
endgenerate

always @ (posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		dmactive <= 1'b0;
		dmcontrol_ndmreset <= 1'b0;
		dmcontrol_haltreq <= {N_HARTS{1'b0}};
		dmcontrol_hartreset <= {N_HARTS{1'b0}};
		dmcontrol_resethaltreq <= {N_HARTS{1'b0}};
	end else if (!dmactive) begin
		// Only dmactive is writable when !dmactive
		if (dmi_write && dmi_regaddr == ADDR_DMCONTROL)
			dmactive <= dmi_pwdata[0];
		dmcontrol_ndmreset <= 1'b0;
		dmcontrol_haltreq <= {N_HARTS{1'b0}};
		dmcontrol_hartreset <= {N_HARTS{1'b0}};
		dmcontrol_resethaltreq <= {N_HARTS{1'b0}};
	end else if (dmi_write && dmi_regaddr == ADDR_DMCONTROL) begin
		dmactive <= dmi_pwdata[0];
		dmcontrol_ndmreset <= dmi_pwdata[1];

		dmcontrol_haltreq <= (dmcontrol_haltreq & ~dmcontrol_op_mask) |
			({N_HARTS{dmi_pwdata[31]}} & dmcontrol_op_mask);

		dmcontrol_hartreset <= (dmcontrol_hartreset & ~dmcontrol_op_mask) |
			({N_HARTS{dmi_pwdata[29]}} & dmcontrol_op_mask);

		dmcontrol_resethaltreq <= (dmcontrol_resethaltreq
			& ~({N_HARTS{dmi_pwdata[2]}} & dmcontrol_op_mask))
			|  ({N_HARTS{dmi_pwdata[3]}} & dmcontrol_op_mask);
	end
end

assign sys_reset_req = dmcontrol_ndmreset;
assign hart_reset_req = dmcontrol_hartreset;
assign hart_req_halt = dmcontrol_haltreq;
assign hart_req_halt_on_reset = dmcontrol_resethaltreq;

reg  [N_HARTS-1:0] hart_reset_done_prev;
reg  [N_HARTS-1:0] dmstatus_havereset;
wire [N_HARTS-1:0] hart_available = hart_reset_done & {N_HARTS{sys_reset_done}};

always @ (posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		hart_reset_done_prev <= {N_HARTS{1'b0}};
	end else begin
		hart_reset_done_prev <= hart_reset_done;
	end
end

wire dmcontrol_ackhavereset = dmi_write && dmi_regaddr == ADDR_DMCONTROL && dmi_pwdata[28];

always @ (posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		dmstatus_havereset <= {N_HARTS{1'b0}};
	end else if (!dmactive) begin
		dmstatus_havereset <= {N_HARTS{1'b0}};
	end else begin
		dmstatus_havereset <= (dmstatus_havereset | (hart_reset_done & ~hart_reset_done_prev))
			& ~({N_HARTS{dmcontrol_ackhavereset}} & dmcontrol_op_mask);
	end
end

reg [N_HARTS-1:0] dmstatus_resumeack;
reg [N_HARTS-1:0] dmcontrol_resumereq_sticky;

// Note: we are required to ignore resumereq when haltreq is also set, as per
// spec (odd since the host is forbidden from writing both at once anyway).
// The wording is odd, it refers only to `haltreq` which is specifically the
// write-only `dmcontrol` field, not the underlying halt request state bits.
wire dmcontrol_resumereq = dmi_write && dmi_regaddr == ADDR_DMCONTROL &&
	dmi_pwdata[30] && !dmi_pwdata[31];

always @ (posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		dmstatus_resumeack <= {N_HARTS{1'b0}};
		dmcontrol_resumereq_sticky <= {N_HARTS{1'b0}};
	end else if (!dmactive) begin
		dmstatus_resumeack <= {N_HARTS{1'b0}};
		dmcontrol_resumereq_sticky <= {N_HARTS{1'b0}};
	end else begin
		dmstatus_resumeack <= (dmstatus_resumeack
			| (dmcontrol_resumereq_sticky & hart_running & hart_available))
			& ~({N_HARTS{dmcontrol_resumereq}} & dmcontrol_op_mask);

		dmcontrol_resumereq_sticky <= (dmcontrol_resumereq_sticky
			& ~(hart_running & hart_available))
			| ({N_HARTS{dmcontrol_resumereq}} & dmcontrol_op_mask);
	end
end

assign hart_req_resume = dmcontrol_resumereq_sticky;

// ----------------------------------------------------------------------------
// System bus access

reg [31:0] sbaddress;
reg [31:0] sbdata;

// Update logic for address/data registers:

reg        sbbusy;
reg        sbautoincrement;
reg [2:0]  sbaccess; // Size of the transfer

wire sbdata_write_blocked;

always @ (posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		sbaddress <= {32{1'b0}};
		sbdata <= {32{1'b0}};
	end else if (!dmactive) begin
		sbaddress <= {32{1'b0}};
		sbdata <= {32{1'b0}};
	end else if (HAVE_SBA) begin
		if (dmi_write && dmi_regaddr == ADDR_SBDATA0 && !sbdata_write_blocked) begin
			// Note sbbusyerror and sberror block writes to sbdata0, as the
			// write is required to have no side effects when they are set.
			sbdata <= dmi_pwdata;
		end else if (sbus_vld && sbus_rdy && !sbus_write && !sbus_err) begin
			// Make sure the lower byte lanes see appropriately shifted data as
			// long as the transfer is naturally aligned
			sbdata <= sbaddress[1:0] == 2'b01 ? {sbus_rdata[31:8],  sbus_rdata[15:8]}  :
			          sbaddress[1:0] == 2'b10 ? {sbus_rdata[31:16], sbus_rdata[31:16]} :
			          sbaddress[1:0] == 2'b11 ? {sbus_rdata[31:8],  sbus_rdata[31:24]} : sbus_rdata;
		end
		if (dmi_write && dmi_regaddr == ADDR_SBADDRESS0 && !sbbusy) begin
			// Note sbaddress can't be written when busy, but
			// sberror/sbbusyerror do not prevent writes.
			sbaddress <= dmi_pwdata;
		end else if (sbus_vld && sbus_rdy && !sbus_err && sbautoincrement) begin
			// Note: address increments only following a successful transfer.
			// Spec 0.13.2 weirdly implies address should increment following
			// a sbdata0 read with sbautoincrement=1 and sbreadondata=0, but
			// this seems to be a typo, fixed in later versions.
			sbaddress <= sbaddress + (
				sbaccess[1:0] == 2'b00 ? 32'd1 :
				sbaccess[1:0] == 2'b01 ? 32'd2 : 32'd4
			);
		end
	end
end

// Control logic:

reg        sbbusyerror;
reg        sbreadonaddr;
reg        sbreadondata;
reg [2:0]  sberror;
reg        sb_current_is_write;

localparam SBERROR_OK       = 3'h0;
localparam SBERROR_BADADDR  = 3'h2;
localparam SBERROR_BADALIGN = 3'h3;
localparam SBERROR_BADSIZE  = 3'h4;

assign sbdata_write_blocked = sbbusy || sbbusyerror || |sberror;

// Notes on behaviour of sbbusyerror: the sbbusyerror description says:
//
//  "Set when the debugger attempts to read data while a read is in progress,
//   or when the debugger initiates a new access while one is already in
//   progress (while sbbusy is set)."
//
// However, sbaddress0 description says:
//
//  "When the system bus master is busy, writes to this register will set
//   sbbusyerror and don’t do anything else."
//
// ...not conditioned on sbreadonaddr. Likewise the sbdata0 description says:
//
//   "If the bus master is busy then accesses set sbbusyerror, and don’t do
//    anything else."
//
// ...not conditioned on sbreadondata. We are going to take the union of all
// the cases where the spec says we should raise an error:

wire sb_access_illegal_when_busy =
	dmi_regaddr == ADDR_SBDATA0 && (dmi_read || dmi_write) ||
	dmi_regaddr == ADDR_SBADDRESS0 && dmi_write;

wire sb_want_start_write = dmi_write && dmi_regaddr == ADDR_SBDATA0;

wire sb_want_start_read =
	(sbreadonaddr && dmi_write && dmi_regaddr == ADDR_SBADDRESS0) ||
	(sbreadondata && dmi_read && dmi_regaddr == ADDR_SBDATA0);

wire [1:0] sb_next_align = sbreadonaddr && dmi_write && dmi_regaddr == ADDR_SBADDRESS0 ?
	dmi_pwdata[1:0] : sbaddress[1:0];

wire sb_badalign =
	(sbaccess == 3'h1 && sb_next_align[0]) ||
	(sbaccess == 3'h2 && |sb_next_align[1:0]);

wire sb_badsize = sbaccess > 3'h2;

always @ (posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		sbbusy              <= 1'b0;
		sbbusyerror         <= 1'b0;
		sbreadonaddr        <= 1'b0;
		sbreadondata        <= 1'b0;
		sbaccess            <= 3'h0;
		sbautoincrement     <= 1'b0;
		sberror             <= 3'h0;
		sb_current_is_write <= 1'b0;
	end else if (!dmactive) begin
		sbbusy              <= 1'b0;
		sbbusyerror         <= 1'b0;
		sbreadonaddr        <= 1'b0;
		sbreadondata        <= 1'b0;
		sbaccess            <= 3'h0;
		sbautoincrement     <= 1'b0;
		sberror             <= 3'h0;
		sb_current_is_write <= 1'b0;
	end else if (HAVE_SBA) begin
		if (dmi_write && dmi_regaddr == ADDR_SBCS) begin
			// Assume a transfer is not in progress when written (per spec)
			sbbusyerror     <= sbbusyerror && !dmi_pwdata[22];
			sbreadonaddr    <= dmi_pwdata[20];
			sbaccess        <= dmi_pwdata[19:17];
			sbautoincrement <= dmi_pwdata[16];
			sbreadondata    <= dmi_pwdata[15];
			sberror         <= sberror & ~dmi_pwdata[14:12];
		end
		if (sbbusy) begin
			if (sb_access_illegal_when_busy) begin
				sbbusyerror <= 1'b1;
			end
			if (sbus_vld && sbus_rdy) begin
				sbbusy <= 1'b0;
				if (sbus_err) begin
					sberror <= SBERROR_BADADDR;
				end
			end
		end else if ((sb_want_start_read || sb_want_start_write) && ~|sberror && !sbbusyerror) begin
			if (sb_badsize) begin
				sberror <= SBERROR_BADSIZE;
			end else if (sb_badalign) begin
				sberror <= SBERROR_BADALIGN;
			end else begin
				sbbusy <= 1'b1;
				sb_current_is_write <= sb_want_start_write;
			end
		end
	end
end

assign sbus_addr  = sbaddress;
assign sbus_write = sb_current_is_write;
assign sbus_size  = sbaccess[1:0];
assign sbus_vld   = sbbusy;

// Replicate byte lanes to handle naturally-aligned cases.
assign sbus_wdata = sbaccess[1:0] == 2'b00 ? {4{sbdata[7:0]}}  :
                    sbaccess[1:0] == 2'b01 ? {2{sbdata[15:0]}} : sbdata;

// ----------------------------------------------------------------------------
// Abstract command data registers

wire abstractcs_busy;

// The same data0 register is aliased as a CSR on all harts connected to this
// DM. Cores may read data0 as a CSR when in debug mode, and may write it when:
//
// - That core is in debug mode, and...
// - We are currently executing an abstract command on that core
//
// The DM can also read/write data0 at all times.

reg [XLEN-1:0] abstract_data0;

assign hart_data0_rdata = {N_HARTS{abstract_data0}};

always @ (posedge clk or negedge rst_n) begin: update_hart_data0
	reg signed [31:0] i;
	if (!rst_n) begin
		abstract_data0 <= {XLEN{1'b0}};
	end else if (!dmactive) begin
		abstract_data0 <= {XLEN{1'b0}};
	end else if (dmi_write && dmi_regaddr == ADDR_DATA0) begin
		abstract_data0 <= dmi_pwdata;
	end else begin
		for (i = 0; i < N_HARTS; i = i + 1) begin
			if (hartsel == i[W_HARTSEL-1:0] && hart_data0_wen[i] && hart_halted[i] && abstractcs_busy)
				abstract_data0 <= hart_data0_wdata[i * XLEN +: XLEN];
		end
	end
end

reg [XLEN-1:0] progbuf0;
reg [XLEN-1:0] progbuf1;

always @ (posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		progbuf0 <= {XLEN{1'b0}};
		progbuf1 <= {XLEN{1'b0}};
	end else if (!dmactive) begin
		progbuf0 <= {XLEN{1'b0}};
		progbuf1 <= {XLEN{1'b0}};
	end else if (dmi_write && !abstractcs_busy) begin
		if (dmi_regaddr == ADDR_PROGBUF0)
			progbuf0 <= dmi_pwdata;
		if (dmi_regaddr == ADDR_PROGBUF1)
			progbuf1 <= dmi_pwdata;
	end
end

reg       abstractauto_autoexecdata;
reg [1:0] abstractauto_autoexecprogbuf;

always @ (posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		abstractauto_autoexecdata <= 1'b0;
		abstractauto_autoexecprogbuf <= 2'b00;
	end else if (!dmactive) begin
		abstractauto_autoexecdata <= 1'b0;
		abstractauto_autoexecprogbuf <= 2'b00;
	end else if (dmi_write && dmi_regaddr == ADDR_ABSTRACTAUTO) begin
		abstractauto_autoexecdata <= dmi_pwdata[0];
		abstractauto_autoexecprogbuf <= dmi_pwdata[17:16];
	end
end

// ----------------------------------------------------------------------------
// Abstract command state machine

localparam W_STATE = 4;
localparam S_IDLE            = 4'd0;

localparam S_ISSUE_REGREAD   = 4'd1;
localparam S_ISSUE_REGWRITE  = 4'd2;
localparam S_ISSUE_REGEBREAK = 4'd3;
localparam S_WAIT_REGEBREAK  = 4'd4;

localparam S_ISSUE_PROGBUF0  = 4'd5;
localparam S_ISSUE_PROGBUF1  = 4'd6;
localparam S_ISSUE_IMPEBREAK = 4'd7;
localparam S_WAIT_IMPEBREAK  = 4'd8;

localparam CMDERR_OK = 3'h0;
localparam CMDERR_BUSY = 3'h1;
localparam CMDERR_UNSUPPORTED = 3'h2;
localparam CMDERR_EXCEPTION = 3'h3;
localparam CMDERR_HALTRESUME = 3'h4;

reg [2:0]         abstractcs_cmderr;
reg [2:0]         abstractcs_cmderr_nxt;
reg [W_STATE-1:0] acmd_state;
reg [W_STATE-1:0] acmd_state_nxt;

assign abstractcs_busy = acmd_state != S_IDLE;

wire start_abstract_cmd = abstractcs_cmderr == CMDERR_OK && !abstractcs_busy && (
	(dmi_write && dmi_regaddr == ADDR_COMMAND) ||
	((dmi_write || dmi_read) && abstractauto_autoexecdata && dmi_regaddr == ADDR_DATA0) ||
	((dmi_write || dmi_read) && abstractauto_autoexecprogbuf[0] && dmi_regaddr == ADDR_PROGBUF0) ||
	((dmi_write || dmi_read) && abstractauto_autoexecprogbuf[1] && dmi_regaddr == ADDR_PROGBUF1)
);

wire dmi_access_illegal_when_busy =
	(dmi_write && (
		dmi_regaddr == ADDR_ABSTRACTCS || dmi_regaddr == ADDR_COMMAND || dmi_regaddr == ADDR_ABSTRACTAUTO ||
		dmi_regaddr == ADDR_DATA0 || dmi_regaddr == ADDR_PROGBUF0 || dmi_regaddr == ADDR_PROGBUF1)) ||
	(dmi_read && (
		dmi_regaddr == ADDR_DATA0 || dmi_regaddr == ADDR_PROGBUF0 || dmi_regaddr == ADDR_PROGBUF1));

// Decode what acmd may be triggered on this cycle, and whether it is
// supported -- command source may be a registered version of most recent
// command (if abstractauto is used) or a fresh command off the bus. We don't
// register the entire write data; repeats of unsupported commands are
// detected by just registering that the last written command was
// unsupported.

wire       acmd_new = dmi_write && dmi_regaddr == ADDR_COMMAND;

wire       acmd_new_postexec    = dmi_pwdata[18];
wire       acmd_new_transfer    = dmi_pwdata[17];
wire       acmd_new_write       = dmi_pwdata[16];
wire [4:0] acmd_new_regno       = dmi_pwdata[4:0];

// Note: regno and aarsize are permitted to have otherwise-invalid values if
// the transfer flag is not set.
wire       acmd_new_unsupported =
	(dmi_pwdata[31:24] != 8'h00                    ) || // Only Access Register command supported
	(dmi_pwdata[22:20] != 3'h2 && acmd_new_transfer) || // Must be 32 bits in size
	(dmi_pwdata[19]                                ) || // aarpostincrement not supported
	(dmi_pwdata[15:12] != 4'h1 && acmd_new_transfer) || // Only core register access supported
	(dmi_pwdata[11:5]  != 7'h0 && acmd_new_transfer);   // Only GPRs supported

reg        acmd_prev_postexec;
reg        acmd_prev_transfer;
reg        acmd_prev_write;
reg [4:0]  acmd_prev_regno;
reg        acmd_prev_unsupported;

always @ (posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		acmd_prev_postexec <= 1'b0;
		acmd_prev_transfer <= 1'b0;
		acmd_prev_write <= 1'b0;
		acmd_prev_regno <= 5'h0;
		acmd_prev_unsupported <= 1'b1;
	end else if (!dmactive) begin
		acmd_prev_postexec <= 1'b0;
		acmd_prev_transfer <= 1'b0;
		acmd_prev_write <= 1'b0;
		acmd_prev_regno <= 5'h0;
		acmd_prev_unsupported <= 1'b1;
	end else if (start_abstract_cmd && acmd_new) begin
		acmd_prev_postexec <= acmd_new_postexec;
		acmd_prev_transfer <= acmd_new_transfer;
		acmd_prev_write <= acmd_new_write;
		acmd_prev_regno <= acmd_new_regno;
		acmd_prev_unsupported <= acmd_new_unsupported;
	end
end

wire       acmd_postexec    = acmd_new ? acmd_new_postexec    : acmd_prev_postexec   ;
wire       acmd_transfer    = acmd_new ? acmd_new_transfer    : acmd_prev_transfer   ;
wire       acmd_write       = acmd_new ? acmd_new_write       : acmd_prev_write      ;
wire [4:0] acmd_regno       = acmd_new ? acmd_new_regno       : acmd_prev_regno      ;
wire       acmd_unsupported = acmd_new ? acmd_new_unsupported : acmd_prev_unsupported;

always @ (*) begin
	// Default: no state change
	acmd_state_nxt = acmd_state;
	abstractcs_cmderr_nxt = abstractcs_cmderr;

	if (dmi_write && dmi_regaddr == ADDR_ABSTRACTCS && !abstractcs_busy)
		abstractcs_cmderr_nxt = abstractcs_cmderr & ~dmi_pwdata[10:8];
	if (abstractcs_cmderr == CMDERR_OK && abstractcs_busy && dmi_access_illegal_when_busy)
		abstractcs_cmderr_nxt = CMDERR_BUSY;
	if (acmd_state != S_IDLE && hart_instr_caught_exception[hartsel])
		abstractcs_cmderr_nxt = CMDERR_EXCEPTION;

	case (acmd_state)
		S_IDLE: begin
			if (start_abstract_cmd) begin
				if (!hart_halted[hartsel] || !hart_available[hartsel]) begin
					abstractcs_cmderr_nxt = CMDERR_HALTRESUME;
				end else if (acmd_unsupported) begin
					abstractcs_cmderr_nxt = CMDERR_UNSUPPORTED;
				end else begin
					if (acmd_transfer && acmd_write)
						acmd_state_nxt = S_ISSUE_REGWRITE;
					else if (acmd_transfer && !acmd_write)
						acmd_state_nxt = S_ISSUE_REGREAD;
					else if (acmd_postexec)
						acmd_state_nxt = S_ISSUE_PROGBUF0;
					else
						acmd_state_nxt = S_IDLE;
				end
			end
		end

		S_ISSUE_REGREAD: begin
			if (hart_instr_data_rdy[hartsel])
				acmd_state_nxt = S_ISSUE_REGEBREAK;
		end
		S_ISSUE_REGWRITE: begin
			if (hart_instr_data_rdy[hartsel])
				acmd_state_nxt = S_ISSUE_REGEBREAK;
		end
		S_ISSUE_REGEBREAK: begin
			if (hart_instr_data_rdy[hartsel])
				acmd_state_nxt = S_WAIT_REGEBREAK;
		end
		S_WAIT_REGEBREAK: begin
			if (hart_instr_caught_ebreak[hartsel]) begin
				if (acmd_prev_postexec)
					acmd_state_nxt = S_ISSUE_PROGBUF0;
				else
					acmd_state_nxt = S_IDLE;
			end
		end

		S_ISSUE_PROGBUF0: begin
			if (hart_instr_data_rdy[hartsel])
				acmd_state_nxt = S_ISSUE_PROGBUF1;
		end
		S_ISSUE_PROGBUF1: begin
			if (hart_instr_caught_exception[hartsel] || hart_instr_caught_ebreak[hartsel]) begin
				acmd_state_nxt = S_IDLE;
			end else if (hart_instr_data_rdy[hartsel]) begin
				acmd_state_nxt = S_ISSUE_IMPEBREAK;
			end
		end
		S_ISSUE_IMPEBREAK: begin
			if (hart_instr_caught_exception[hartsel] || hart_instr_caught_ebreak[hartsel]) begin
				acmd_state_nxt = S_IDLE;
			end else if (hart_instr_data_rdy[hartsel]) begin
				acmd_state_nxt = S_WAIT_IMPEBREAK;
			end
		end
		S_WAIT_IMPEBREAK: begin
			if (hart_instr_caught_exception[hartsel] || hart_instr_caught_ebreak[hartsel]) begin
				acmd_state_nxt = S_IDLE;
			end
		end

		default: begin
			// Unreachable
		end

	endcase
end

always @ (posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		abstractcs_cmderr <= CMDERR_OK;
		acmd_state <= S_IDLE;
	end else if (!dmactive) begin
		abstractcs_cmderr <= CMDERR_OK;
		acmd_state <= S_IDLE;
	end else begin
		abstractcs_cmderr <= abstractcs_cmderr_nxt;
		acmd_state <= acmd_state_nxt;
	end
end

wire [N_HARTS-1:0] hart_instr_data_vld_nxt = {{N_HARTS-1{1'b0}},
	acmd_state_nxt == S_ISSUE_REGREAD || acmd_state_nxt == S_ISSUE_REGWRITE || acmd_state_nxt == S_ISSUE_REGEBREAK ||
	acmd_state_nxt == S_ISSUE_PROGBUF0 || acmd_state_nxt == S_ISSUE_PROGBUF1 || acmd_state_nxt == S_ISSUE_IMPEBREAK
} << hartsel;

wire [31:0] hart_instr_data_nxt =
	acmd_state_nxt == S_ISSUE_REGWRITE  ? 32'hbff02073 | {20'd0, acmd_regno,  7'd0} : // csrr xx, dmdata0
	acmd_state_nxt == S_ISSUE_REGREAD   ? 32'hbff01073 | {12'd0, acmd_regno, 15'd0} : // csrw dmdata0, xx
	acmd_state_nxt == S_ISSUE_PROGBUF0  ? progbuf0                                  :
	acmd_state_nxt == S_ISSUE_PROGBUF1  ? progbuf1                                  :
	                                      32'h00100073;                               // ebreak

reg [31:0] hart_instr_data_reg;
assign hart_instr_data = {N_HARTS{hart_instr_data_reg}};

always @ (posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		hart_instr_data_vld <= 1'b0;
		hart_instr_data_reg <= 32'h00000000;
	end else begin
		hart_instr_data_vld <= hart_instr_data_vld_nxt;
		if (hart_instr_data_vld_nxt) begin
			hart_instr_data_reg <= hart_instr_data_nxt;
		end
	end
end

// ----------------------------------------------------------------------------
// Status helper functions

function status_any;
	input [N_HARTS-1:0] status_mask;
begin
	status_any = status_mask[hartsel] || (hasel && |(status_mask & hart_array_mask));
end
endfunction

function status_all;
	input [N_HARTS-1:0] status_mask;
begin
	status_all = status_mask[hartsel] && (!hasel || ~|(~status_mask & hart_array_mask));
end
endfunction

function [1:0] status_all_any;
	input [N_HARTS-1:0] status_mask;
begin
	status_all_any = {
		status_all(status_mask),
		status_any(status_mask)
	};
end
endfunction

// ----------------------------------------------------------------------------
// DMI read data mux

always @ (*) begin
	case (dmi_regaddr)
	ADDR_DATA0:        dmi_prdata = abstract_data0;
	ADDR_DMCONTROL:    dmi_prdata = {
		1'b0,                             // haltreq is a W-only field
		1'b0,                             // resumereq is a W1 field
		status_any(dmcontrol_hartreset),
		1'b0,                             // ackhavereset is a W1 field
		1'b0,                             // reserved
		hasel,
		{{10-W_HARTSEL{1'b0}}, hartsel},  // hartsello
		10'h0,                            // hartselhi
		2'h0,                             // reserved
		2'h0,                             // set/clrresethaltreq are W1 fields
		dmcontrol_ndmreset,
		dmactive
	};
	ADDR_DMSTATUS:     dmi_prdata = {
		9'h0,                                               // reserved
		1'b1,                                               // impebreak = 1
		2'h0,                                               // reserved
		status_all_any(dmstatus_havereset),                 // allhavereset, anyhavereset
		status_all_any(dmstatus_resumeack),                 // allresumeack, anyresumeack
		hartsel >= N_HARTS && !(hasel && |hart_array_mask), // allnonexistent
		hartsel >= N_HARTS,                                 // anynonexistent
		status_all_any(~hart_available),                    // allunavail, anyunavail
		status_all_any(hart_running & hart_available),      // allrunning, anyrunning
		status_all_any(hart_halted & hart_available),       // allhalted, anyhalted
		1'b1,                                               // authenticated
		1'b0,                                               // authbusy
		1'b1,                                               // hasresethaltreq = 1 (we do support it)
		1'b0,                                               // confstrptrvalid
		4'd2                                                // version = 2: RISC-V debug spec 0.13.2
	};
	ADDR_HARTINFO:     dmi_prdata = {
		8'h0,                             // reserved
		4'h0,                             // nscratch = 0
		3'h0,                             // reserved
		1'b0,                             // dataccess = 0, data0 is mapped to each hart's CSR space
		4'h1,                             // datasize = 1, a single data CSR (data0) is available
		12'hbff                           // dataaddr, placed at the top of the M-custom space since
		                                  // the spec doesn't reserve a location for it.
	};
	ADDR_HALTSUM0:     dmi_prdata = {
		{XLEN - N_HARTS{1'b0}},
		hart_halted & hart_available
	};
	ADDR_HALTSUM1:     dmi_prdata = {
		{XLEN - 1{1'b0}},
		|(hart_halted & hart_available)
	};
	ADDR_HAWINDOWSEL:  dmi_prdata = 32'h00000000;
	ADDR_HAWINDOW:     dmi_prdata = {
		{32-N_HARTS{1'b0}},
		hart_array_mask
	};
	ADDR_ABSTRACTCS:   dmi_prdata = {
		3'h0,                             // reserved
		5'd2,                             // progbufsize = 2
		11'h0,                            // reserved
		abstractcs_busy,
		1'b0,
		abstractcs_cmderr,
		4'h0,
		4'd1                              // datacount = 1
	};
	ADDR_ABSTRACTAUTO: dmi_prdata = {
		14'h0,
		abstractauto_autoexecprogbuf,     // only progbuf0,1 present
		15'h0,
		abstractauto_autoexecdata         // only data0 present
	};
	ADDR_SBCS:          dmi_prdata = {
		3'h1,                             // version = 1
		6'h00,
		sbbusyerror,
		sbbusy,
		sbreadonaddr,
		sbaccess,
		sbautoincrement,
		sbreadondata,
		sberror,
		7'h20,                            // sbasize = 32
		5'b00111                          // 8, 16, 32-bit transfers supported
	} & {32{|HAVE_SBA}};
	ADDR_SBDATA0:      dmi_prdata = sbdata & {32{|HAVE_SBA}};
	ADDR_SBADDRESS0:   dmi_prdata = sbaddress & {32{|HAVE_SBA}};
	ADDR_CONFSTRPTR0:  dmi_prdata = 32'h4c296328;
	ADDR_CONFSTRPTR1:  dmi_prdata = 32'h20656b75;
	ADDR_CONFSTRPTR2:  dmi_prdata = 32'h6e657257;
	ADDR_CONFSTRPTR3:  dmi_prdata = 32'h31322720;
	ADDR_NEXTDM:       dmi_prdata = NEXT_DM_ADDR;
	ADDR_PROGBUF0:     dmi_prdata = progbuf0;
	ADDR_PROGBUF1:     dmi_prdata = progbuf1;
	default:           dmi_prdata = {XLEN{1'b0}};
	endcase
end

endmodule

`ifndef YOSYS
`default_nettype wire
`endif
