668 lines
23 KiB
Verilog
668 lines
23 KiB
Verilog
/*****************************************************************************\
|
|
| 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,
|
|
|
|
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*XLEN-1:0] hart_instr_data,
|
|
output wire [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
|
|
);
|
|
|
|
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, no system bus access
|
|
|
|
// 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;
|
|
|
|
wire dmcontrol_resumereq = dmi_write && dmi_regaddr == ADDR_DMCONTROL && dmi_pwdata[30];
|
|
|
|
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;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 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 [W_STATE-1:0] acmd_state;
|
|
|
|
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];
|
|
|
|
wire acmd_new_unsupported =
|
|
dmi_pwdata[31:24] != 8'h00 || // Only Access Register command supported
|
|
dmi_pwdata[22:20] != 3'h2 || // Must be 32 bits in size
|
|
dmi_pwdata[19] || // aarpostincrement not supported
|
|
dmi_pwdata[15:12] != 4'h1 || // Only core register access supported
|
|
dmi_pwdata[11:5] != 7'h0; // 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 @ (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
|
|
if (dmi_write && dmi_regaddr == ADDR_ABSTRACTCS && !abstractcs_busy)
|
|
abstractcs_cmderr <= abstractcs_cmderr & ~dmi_pwdata[10:8];
|
|
if (abstractcs_cmderr == CMDERR_OK && abstractcs_busy && dmi_access_illegal_when_busy)
|
|
abstractcs_cmderr <= CMDERR_BUSY;
|
|
if (acmd_state != S_IDLE && hart_instr_caught_exception[hartsel])
|
|
abstractcs_cmderr <= CMDERR_EXCEPTION;
|
|
case (acmd_state)
|
|
S_IDLE: begin
|
|
if (start_abstract_cmd) begin
|
|
if (!hart_halted[hartsel] || !hart_available[hartsel]) begin
|
|
abstractcs_cmderr <= CMDERR_HALTRESUME;
|
|
end else if (acmd_unsupported) begin
|
|
abstractcs_cmderr <= CMDERR_UNSUPPORTED;
|
|
end else begin
|
|
if (acmd_transfer && acmd_write)
|
|
acmd_state <= S_ISSUE_REGWRITE;
|
|
else if (acmd_transfer && !acmd_write)
|
|
acmd_state <= S_ISSUE_REGREAD;
|
|
else if (acmd_postexec)
|
|
acmd_state <= S_ISSUE_PROGBUF0;
|
|
else
|
|
acmd_state <= S_IDLE;
|
|
end
|
|
end
|
|
end
|
|
|
|
S_ISSUE_REGREAD: begin
|
|
if (hart_instr_data_rdy[hartsel])
|
|
acmd_state <= S_ISSUE_REGEBREAK;
|
|
end
|
|
S_ISSUE_REGWRITE: begin
|
|
if (hart_instr_data_rdy[hartsel])
|
|
acmd_state <= S_ISSUE_REGEBREAK;
|
|
end
|
|
S_ISSUE_REGEBREAK: begin
|
|
if (hart_instr_data_rdy[hartsel])
|
|
acmd_state <= S_WAIT_REGEBREAK;
|
|
end
|
|
S_WAIT_REGEBREAK: begin
|
|
if (hart_instr_caught_ebreak[hartsel]) begin
|
|
if (acmd_prev_postexec)
|
|
acmd_state <= S_ISSUE_PROGBUF0;
|
|
else
|
|
acmd_state <= S_IDLE;
|
|
end
|
|
end
|
|
|
|
S_ISSUE_PROGBUF0: begin
|
|
if (hart_instr_data_rdy[hartsel])
|
|
acmd_state <= S_ISSUE_PROGBUF1;
|
|
end
|
|
S_ISSUE_PROGBUF1: begin
|
|
if (hart_instr_caught_exception[hartsel] || hart_instr_caught_ebreak[hartsel]) begin
|
|
acmd_state <= S_IDLE;
|
|
end else if (hart_instr_data_rdy[hartsel]) begin
|
|
acmd_state <= S_ISSUE_IMPEBREAK;
|
|
end
|
|
end
|
|
S_ISSUE_IMPEBREAK: begin
|
|
if (hart_instr_caught_exception[hartsel] || hart_instr_caught_ebreak[hartsel]) begin
|
|
acmd_state <= S_IDLE;
|
|
end else if (hart_instr_data_rdy[hartsel]) begin
|
|
acmd_state <= S_WAIT_IMPEBREAK;
|
|
end
|
|
end
|
|
S_WAIT_IMPEBREAK: begin
|
|
if (hart_instr_caught_exception[hartsel] || hart_instr_caught_ebreak[hartsel]) begin
|
|
acmd_state <= S_IDLE;
|
|
end
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
|
|
assign hart_instr_data_vld = {{N_HARTS{1'b0}},
|
|
acmd_state == S_ISSUE_REGREAD || acmd_state == S_ISSUE_REGWRITE || acmd_state == S_ISSUE_REGEBREAK ||
|
|
acmd_state == S_ISSUE_PROGBUF0 || acmd_state == S_ISSUE_PROGBUF1 || acmd_state == S_ISSUE_IMPEBREAK
|
|
} << hartsel;
|
|
|
|
assign hart_instr_data = {N_HARTS{
|
|
acmd_state == S_ISSUE_REGWRITE ? 32'hbff02073 | {20'd0, acmd_prev_regno, 7'd0} : // csrr xx, dmdata0
|
|
acmd_state == S_ISSUE_REGREAD ? 32'hbff01073 | {12'd0, acmd_prev_regno, 15'd0} : // csrw dmdata0, xx
|
|
acmd_state == S_ISSUE_PROGBUF0 ? progbuf0 :
|
|
acmd_state == S_ISSUE_PROGBUF1 ? progbuf1 :
|
|
32'h00100073 // ebreak
|
|
}};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 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
|
|
{2{!hasel && hartsel >= N_HARTS}}, // allnonexistent, 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_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
|