Start hacking together a DM

This commit is contained in:
Luke Wren 2021-07-11 05:11:19 +01:00
parent 5cc483898d
commit 0dce59daaf
1 changed files with 489 additions and 0 deletions

489
hdl/debug/dm/hazard3_dm.v Normal file
View File

@ -0,0 +1,489 @@
/**********************************************************************
* DO WHAT THE FUCK YOU WANT TO AND DON'T BLAME US PUBLIC LICENSE *
* Version 3, April 2008 *
* *
* Copyright (C) 2021 Luke Wren *
* *
* Everyone is permitted to copy and distribute verbatim or modified *
* copies of this license document and accompanying software, and *
* changing either is allowed. *
* *
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION *
* *
* 0. You just DO WHAT THE FUCK YOU WANT TO. *
* 1. We're NOT RESPONSIBLE WHEN IT DOESN'T FUCKING WORK. *
* *
*********************************************************************/
// RISC-V Debug Module for Hazard3
`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 'h100, so that the lower 8 bits 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 [7: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)
input wire [N_HARTS*XLEN-1:0] hart_data0_rdata,
output wire [N_HARTS*XLEN-1:0] hart_data0_wdata,
output 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 = 8'h04;
// Other data registers not present.
localparam ADDR_DMCONTROL = 8'h10;
localparam ADDR_DMSTATUS = 8'h11;
localparam ADDR_HARTINFO = 8'h12;
// No halt summary registers (assume no more than 32 harts)
// No array mask select registers
localparam ADDR_ABSTRACTCS = 8'h16;
localparam ADDR_COMMAND = 8'h17;
localparam ADDR_ABSTRACTAUTO = 8'h18;
localparam ADDR_CONFSTRPTR0 = 8'h19;
localparam ADDR_CONFSTRPTR1 = 8'h1a;
localparam ADDR_CONFSTRPTR2 = 8'h1b;
localparam ADDR_CONFSTRPTR3 = 8'h1c;
localparam ADDR_NEXTDM = 8'h1d;
localparam ADDR_PROGBUF0 = 8'h20;
localparam ADDR_PROGBUF1 = 8'h21;
// No authentication, no system bus access
// ----------------------------------------------------------------------------
// 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_paddr == ADDR_DMCONTROL ?
dmi_pwdata[16 +: W_HARTSEL] : hartsel;
end else begin: has_no_hartsel
assign hartsel_next = 1'b0;
end
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
// ----------------------------------------------------------------------------
// 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;
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_paddr == 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_paddr == ADDR_DMCONTROL) begin
dmactive <= dmi_pwdata[0];
dmcontrol_ndmreset <= dmi_pwdata[1];
dmcontrol_haltreq[hartsel_next] <= dmi_pwdata[31];
dmcontrol_hartreset[hartsel_next] <= dmi_pwdata[29];
// set/clear fields on this one for some reason
dmcontrol_resethaltreq[hartsel_next] <= dmcontrol_resethaltreq[hartsel_next]
&& !dmi_pwdata[2] || dmi_pwdata[3];
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
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);
// dmcontrol.ackhavereset:
if (dmi_write && dmi_paddr == ADDR_DMCONTROL && dmi_pwdata[28])
dmstatus_havereset[hartsel_next] <= 1'b0;
end
end
reg [N_HARTS-1:0] dmstatus_resumeack;
reg [N_HARTS-1:0] dmcontrol_resumereq_sticky;
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);
dmcontrol_resumereq_sticky <= dmcontrol_resumereq_sticky & ~hart_running;
// dmcontrol.resumereq:
if (dmi_write && dmi_paddr == ADDR_DMCONTROL && dmi_pwdata[30]) begin
dmcontrol_resumereq_sticky[hartsel_next] <= 1'b1;
dmstatus_resumeack[hartsel_next] <= 1'b0;
end
end
end
assign hart_req_resume = ~dmstatus_resumeack;
// ----------------------------------------------------------------------------
// Abstract command data registers
wire abstractcs_busy;
assign hart_data0_wdata = {N_HARTS{dmi_pwdata}};
assign hart_data0_wen = {{N_HARTS-1{1'b0}}, dmi_write && dmi_paddr == ADDR_DATA0 && !abstractcs_busy} << hartsel;
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_paddr == ADDR_PROGBUF0)
progbuf0 <= dmi_pwdata;
if (dmi_paddr == ADDR_PROGBUF1)
progbuf1 <= dmi_pwdata;
end
end
// We only support abstractauto on data0 update (use case is bulk memory read/write)
reg abstractauto_autoexecdata;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
abstractauto_autoexecdata <= 1'b0;
end else if (!dmactive) begin
abstractauto_autoexecdata <= 1'b0;
end else if (dmi_write && dmi_paddr == ADDR_ABSTRACTAUTO) begin
abstractauto_autoexecdata <= pwdata[1];
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 abstracts_busy = acmd_state != S_IDLE;
wire start_abstract_cmd = abstractcs_cmderr == CMDERR_OK && !abstractcs_busy && (
(dmi_write && dmi_paddr == ADDR_COMMAND) ||
((dmi_write || dmi_read) && abstractauto_autoexecdata && dmi_paddr == ADDR_DATA0
);
wire dmi_access_illegal_when_busy =
(dmi_write && (
dmi_paddr == ADDR_ABSTRACTCS || dmi_paddr == ADDR_COMMAND || dmi_paddr == ADDR_ABSTRACTAUTO ||
dmi_paddr == ADDR_DATA0 || dmi_paddr == ADDR_PROGBUF0 || dmi_paddr == ADDR_PROGBUF0)) ||
(dmi_read && (
dmi_paddr == ADDR_DATA0 || dmi_paddr == ADDR_PROGBUF0 || dmi_paddr == ADDR_PROGBUF0));
wire acmd_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
wire acmd_postexec = dmi_pwdata[18];
wire acmd_transfer = dmi_pwdata[17];
wire acmd_write = dmi_pwdata[16];
wire [4:0] acmd_regno = dmi_pwdata[4:0]
reg acmd_postexec_r;
reg [4:0] acmd_regno_r;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
abstractcs_cmderr <= CMDERR_OK;
abstractcs_state <= S_IDLE;
acmd_postexec_r <= 1'b0;
acmd_regno_r <= 5'h0;
end else if (!dmactive) begin
abstractcs_cmderr <= CMDERR_OK;
abstractcs_state <= S_IDLE;
end else begin
if (abstractcs_cmderr == CMDERR_OK && abstracts_busy && dmi_access_illegal_when_busy)
abstractcs_cmderr <= CMDERR_BUSY;
if (state != S_IDLE && hart_instr_caught_exception)
abstractcs_cmderr <= CMDERR_EXCEPTION;
case (acmd_state)
S_IDLE: begin
if (start_abstract_cmd) begin
if (!hart_halted[hartsel] || !hart_available[hartsel]) begin
abstracts_cmderr <= CMDERR_HALTRESUME;
end else if (acmd_unsupported)
abstractcs_cmderr <= CMDERR_UNSUPPORTED;
end else begin
acmd_postexec_r <= acmd_postexec;
acmd_regno_r <= acmd_regno;
if (acmd_transfer && acmd_write)
state <= S_ISSUE_REGWRITE;
else if (acmd_transfer && !acmd_write)
state <= S_ISSUE_REGREAD;
else if (acmd_postexec)
state <= S_ISSUE_PROGBUF0;
else
state <= S_IDLE;
end
end
end
S_ISSUE_REGREAD: begin
if (hart_instr_data_rdy[hartsel])
state <= S_ISSUE_REGEBREAK;
end
S_ISSUE_REGWRITE: begin
if (hart_instr_data_rdy[hartsel])
state <= S_ISSUE_REGEBREAK;
end
S_ISSUE_REGEBREAK: begin
if (hart_instr_data_rdy[hartsel])
state <= S_WAIT_REGEBREAK
end
S_WAIT_REGEBREAK: begin
if (hart_instr_caught_ebreak) begin
if (acmd_postexec_r)
state <= S_ISSUE_PROGBUF0;
else
state <= S_IDLE;
end
end
S_ISSUE_PROGBUF0: begin
if (hart_instr_data_rdy[hartsel])
state <= S_ISSUE_PROGBUF1;
end
S_ISSUE_PROGBUF1: begin
if (hart_instr_caught_exception || hart_instr_caught_ebreak) begin
state <= S_IDLE;
end else if (hart_instr_data_rdy[hartsel]) begin
state <= S_ISSUE_IMPEBREAK;
end
end
S_ISSUE_IMPEBREAK: begin
if (hart_instr_caught_exception || hart_instr_caught_ebreak) begin
state <= S_IDLE;
end else if (hart_instr_data_rdy[hartsel]) begin
state <= S_WAIT_IMPEBREAK;
end
end
S_WAIT_IMPEBREAK: begin
if (hart_instr_caught_exception || hart_instr_caught_ebreak) begin
state <= S_IDLE;
end
end
endcase
end
end
assign hart_instr_data_vld = {{N_HARTS{1'b0}},
state == S_ISSUE_REGREAD || state == S_ISSUE_REGWRITE || state == S_ISSUE_REGEBREAK ||
state == S_ISSUE_PROGBUF0 || state == S_ISSUE_PROGBUF1 || state == S_ISSUE_IMPEBREAK
} << hartsel;
assign hart_instr_data = {N_HARTS{
state == S_ISSUE_REGWRITE ? 32'h7b202073 | {20'd0, acmd_regno_r, 7'd0} : // csrr xx, data0
state == S_ISSUE_REGREAD ? 32'h7b201073 | {12'd0, acmd_regno_r, 15'd0} : // csrw data0, xx
state == S_ISSUE_PROGBUF0 ? progbuf0 :
state == S_ISSUE_PROGBUF1 ? progbuf1 :
32'h00100073 // ebreak
}};
// ----------------------------------------------------------------------------
// DMI read data mux
always @ (*) begin
case (dmi_paddr)
ADDR_DATA0: dmi_prdata = hart_data0_rdata[hartsel * XLEN +: XLEN];
ADDR_DMCONTROL: dmi_prdata = {
dmcontrol_haltreq[hartsel],
1'b0, // resumereq is a W1 field
dmcontrol_hartreset[hartsel],
1'b0, // ackhavereset is a W1 field
1'b0, // reserved
1'b0, // hasel hardwired 0 (no array mask)
{{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
{2{dmstatus_havereset[hartsel]}}, // allhavereset, anyhavereset
{2{dmstatus_resumeack[hartsel]}}, // allresumeack, anyresumeack
{2{hartsel >= N_HARTS}}, // allnonexistent, anynonexistent
{2{!hart_available[hartsel}}, // allunavail, anyunavail
{2{hart_running[hartsel]}}, // allrunning, anyrunning
{2{hart_halted[hartsel]}}, // 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 backed by a per-hart CSR
4'h1, // datasize = 1, a single data CSR (data0) is available
12'h7b2 // dataaddr, same location where dscratch0 would be if implemented
};
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 = {
31'h0,
abstractauto_autoexecdata // only data0 supported
};
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