Hazard3/hdl/debug/dm/hazard3_dm.v

902 lines
31 KiB
Verilog
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*****************************************************************************\
| 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 ? 3'h1 :
sbaccess[1:0] == 2'b01 ? 3'h2 : 3'h4
);
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 dont do anything else."
//
// ...not conditioned on sbreadonaddr. Likewise the sbdata0 description says:
//
// "If the bus master is busy then accesses set sbbusyerror, and dont 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
integer 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 && 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
// We only support abstractauto on data0 update (use case is bulk memory read/write)
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_PROGBUF0)) ||
(dmi_read && (
dmi_regaddr == ADDR_DATA0 || dmi_regaddr == ADDR_PROGBUF0 || dmi_regaddr == ADDR_PROGBUF0));
// 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_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_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_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 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 hart_instr_data_vld_nxt = {{N_HARTS{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_new_regno, 7'd0} : // csrr xx, dmdata0
acmd_state_nxt == S_ISSUE_REGREAD ? 32'hbff01073 | {12'd0, acmd_new_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