diff --git a/hdl/debug/dm/hazard3_dm.v b/hdl/debug/dm/hazard3_dm.v index 1b85ba4..5daf7ac 100644 --- a/hdl/debug/dm/hazard3_dm.v +++ b/hdl/debug/dm/hazard3_dm.v @@ -10,13 +10,16 @@ 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, + 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 = 1, - parameter XLEN = 32, // Do not modify - parameter W_HARTSEL = N_HARTS > 1 ? $clog2(N_HARTS) : 1 // Do not modify + // 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. @@ -63,7 +66,21 @@ module hazard3_dm #( 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 + input wire [N_HARTS-1:0] hart_instr_caught_ebreak, + + // System bus access (optional) -- can be hooked up to the standalone AHB + // shim (hazard3_sba_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; @@ -100,7 +117,10 @@ 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 +// 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]; @@ -289,6 +309,125 @@ 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 + +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 && !sbbusy) begin + sbdata <= dmi_pwdata; + end else if (sbus_vld && sbus_rdy && !sbus_write) begin + sbdata <= sbus_rdata; + end + if (dmi_write && dmi_regaddr == ADDR_SBADDRESS0 && !sbbusy) begin + sbaddress <= dmi_pwdata; + end else if (sbus_vld && sbus_rdy && sbautoincrement) begin + 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; + +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 sb_badalign = + (sbaccess == 3'h1 && sbaddress[0]) || + (sbaccess == 3'h2 && |sbaddress[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_want_start_read || sb_want_start_write) 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) 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; +assign sbus_wdata = sbdata; + // ---------------------------------------------------------------------------- // Abstract command data registers @@ -649,6 +788,21 @@ always @ (*) begin 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; diff --git a/hdl/hazard3_cpu_2port.v b/hdl/hazard3_cpu_2port.v index c71e552..66ffa84 100644 --- a/hdl/hazard3_cpu_2port.v +++ b/hdl/hazard3_cpu_2port.v @@ -64,6 +64,15 @@ module hazard3_cpu_2port #( output wire dbg_instr_data_rdy, output wire dbg_instr_caught_exception, output wire dbg_instr_caught_ebreak, + // Optional debug system bus access patch-through + input wire [31:0] dbg_sbus_addr, + input wire dbg_sbus_write, + input wire [1:0] dbg_sbus_size, + input wire dbg_sbus_vld, + output wire dbg_sbus_rdy, + output wire dbg_sbus_err, + input wire [31:0] dbg_sbus_wdata, + output wire [31:0] dbg_sbus_rdata, // Level-sensitive interrupt sources input wire [NUM_IRQ-1:0] irq, // -> mip.meip @@ -192,39 +201,78 @@ assign i_hprot = { // ---------------------------------------------------------------------------- // Load/store port -assign d_haddr = core_haddr_d; -assign d_htrans = core_aph_req_d ? HTRANS_NSEQ : HTRANS_IDLE; -assign d_hwrite = core_hwrite_d; -assign d_hsize = core_hsize_d; -assign d_hexcl = core_aph_excl_d; +// The debug module has optional System Bus Access support, which can be muxed +// into the processor's D port here (or connected to a standalone AHB shim). +// This confers absolutely no advantage for debugger bus throughput, but +// allows the debugger to access the bus with minimal disturbance to the +// processor. -reg dphase_active_d; -always @ (posedge clk or negedge rst_n) - if (!rst_n) - dphase_active_d <= 1'b0; - else if (d_hready) - dphase_active_d <= core_aph_req_d; +wire bus_gnt_d; +wire bus_gnt_s; + +reg bus_hold_aph; +reg [1:0] bus_gnt_ds_prev; +reg bus_active_dph_d; +reg bus_active_dph_s; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + bus_hold_aph <= 1'b0; + bus_gnt_ds_prev <= 2'h0; + end else begin + bus_hold_aph <= d_htrans[1] && !d_hready && !d_hresp; + bus_gnt_ds_prev <= {bus_gnt_d, bus_gnt_s}; + end +end + +assign {bus_gnt_d, bus_gnt_s} = + bus_hold_aph ? bus_gnt_ds_prev : + core_aph_req_d ? 2'b10 : + dbg_sbus_vld && !bus_active_dph_s ? 2'b01 : + 2'b00 ; +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + bus_active_dph_d <= 1'b0; + bus_active_dph_s <= 1'b0; + end else if (d_hready) begin + bus_active_dph_d <= bus_gnt_d; + bus_active_dph_s <= bus_gnt_s; + end +end + +assign d_htrans = bus_gnt_d || bus_gnt_s ? HTRANS_NSEQ : HTRANS_IDLE; + +assign d_haddr = bus_gnt_s ? dbg_sbus_addr : core_haddr_d; +assign d_hwrite = bus_gnt_s ? dbg_sbus_write : core_hwrite_d; +assign d_hsize = bus_gnt_s ? dbg_sbus_size : core_hsize_d; +assign d_hexcl = bus_gnt_s ? 1'b0 : core_aph_excl_d; + +assign d_hprot = { + 2'b00, // Noncacheable/nonbufferable + bus_gnt_s || core_priv_d, // Privileged or Normal as per core state + 1'b1 // Data access +}; + +assign d_hwdata = bus_active_dph_s ? dbg_sbus_wdata : core_wdata_d; // D-side errors are reported even when not ready, so that the core can make // use of the two-phase error response to cleanly squash a second load/store // chasing the faulting one down the pipeline. -assign core_aph_ready_d = d_hready && core_aph_req_d; -assign core_dph_ready_d = d_hready && dphase_active_d; -assign core_dph_err_d = dphase_active_d && d_hresp; -assign core_dph_exokay_d = dphase_active_d && d_hexokay; - +assign core_aph_ready_d = d_hready && bus_gnt_d; +assign core_dph_ready_d = bus_active_dph_d && d_hready; +assign core_dph_err_d = bus_active_dph_d && d_hresp; +assign core_dph_exokay_d = bus_active_dph_d && d_hexokay; assign core_rdata_d = d_hrdata; -assign d_hwdata = core_wdata_d; + +assign dbg_sbus_err = bus_active_dph_s && d_hresp; +assign dbg_sbus_rdy = bus_active_dph_s && d_hready; +assign dbg_sbus_rdata = d_hrdata; assign d_hburst = 3'h0; assign d_hmastlock = 1'b0; -assign d_hprot = { - 2'b00, // Noncacheable/nonbufferable - core_priv_d, // Privileged or Normal as per core state - 1'b1 // Data access -}; - endmodule +`ifndef YOSYS `default_nettype wire +`endif diff --git a/test/sim/tb_cxxrtl/tb.v b/test/sim/tb_cxxrtl/tb.v index 39d35d0..b5e41a0 100644 --- a/test/sim/tb_cxxrtl/tb.v +++ b/test/sim/tb_cxxrtl/tb.v @@ -124,6 +124,15 @@ wire [N_HARTS-1:0] hart_instr_data_rdy; wire [N_HARTS-1:0] hart_instr_caught_exception; wire [N_HARTS-1:0] hart_instr_caught_ebreak; +wire [31:0] sbus_addr; +wire sbus_write; +wire [1:0] sbus_size; +wire sbus_vld; +wire sbus_rdy; +wire sbus_err; +wire [31:0] sbus_wdata; +wire [31:0] sbus_rdata; + hazard3_dm #( .N_HARTS (N_HARTS), .NEXT_DM_ADDR (0) @@ -159,7 +168,16 @@ hazard3_dm #( .hart_instr_data_vld (hart_instr_data_vld), .hart_instr_data_rdy (hart_instr_data_rdy), .hart_instr_caught_exception (hart_instr_caught_exception), - .hart_instr_caught_ebreak (hart_instr_caught_ebreak) + .hart_instr_caught_ebreak (hart_instr_caught_ebreak), + + .sbus_addr (sbus_addr), + .sbus_write (sbus_write), + .sbus_size (sbus_size), + .sbus_vld (sbus_vld), + .sbus_rdy (sbus_rdy), + .sbus_err (sbus_err), + .sbus_wdata (sbus_wdata), + .sbus_rdata (sbus_rdata) ); @@ -230,6 +248,15 @@ hazard3_cpu_2port #( .dbg_instr_caught_exception (hart_instr_caught_exception), .dbg_instr_caught_ebreak (hart_instr_caught_ebreak), + .dbg_sbus_addr (sbus_addr), + .dbg_sbus_write (sbus_write), + .dbg_sbus_size (sbus_size), + .dbg_sbus_vld (sbus_vld), + .dbg_sbus_rdy (sbus_rdy), + .dbg_sbus_err (sbus_err), + .dbg_sbus_wdata (sbus_wdata), + .dbg_sbus_rdata (sbus_rdata), + .irq (irq), .soft_irq (soft_irq[0]), .timer_irq (timer_irq)