// Copyright 2017 ETH Zurich and University of Bologna. // Copyright and related rights are licensed under the Solderpad Hardware // License, Version 0.51 (the “License”); you may not use this file except in // compliance with the License. You may obtain a copy of the License at // http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law // or agreed to in writing, software, hardware and materials distributed under // this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. //////////////////////////////////////////////////////////////////////////////// // Engineer: Andreas Traber - atraber@iis.ee.ethz.ch // // // // Additional contributions by: // // Davide Schiavone - pschiavo@iis.ee.ethz.ch // // // // Design Name: Debug Unit // // Project Name: zero-riscy // // Language: SystemVerilog // // // // Description: Debug controller // // // //////////////////////////////////////////////////////////////////////////////// `include "zeroriscy_config.sv" import zeroriscy_defines::*; module zeroriscy_debug_unit #( parameter REG_ADDR_WIDTH = 5 ) ( input logic clk, input logic rst_n, // Debug Interface input logic debug_req_i, output logic debug_gnt_o, output logic debug_rvalid_o, input logic [14:0] debug_addr_i, input logic debug_we_i, input logic [31:0] debug_wdata_i, output logic [31:0] debug_rdata_o, output logic debug_halted_o, input logic debug_halt_i, input logic debug_resume_i, // signals to core output logic [DBG_SETS_W-1:0] settings_o, input logic trap_i, // trap found, need to stop the core now input logic [5:0] exc_cause_i, // if it was a trap, then the exception controller knows more output logic stall_o, // after we got control, we control the stall signal output logic dbg_req_o, input logic dbg_ack_i, // register file read port output logic regfile_rreq_o, output logic [(REG_ADDR_WIDTH-1):0] regfile_raddr_o, input logic [31:0] regfile_rdata_i, // register file write port output logic regfile_wreq_o, output logic [(REG_ADDR_WIDTH-1):0] regfile_waddr_o, output logic [31:0] regfile_wdata_o, // CSR read/write port output logic csr_req_o, output logic [11:0] csr_addr_o, output logic csr_we_o, output logic [31:0] csr_wdata_o, input logic [31:0] csr_rdata_i, // Signals for PPC & NPC register input logic [31:0] pc_if_i, input logic [31:0] pc_id_i, input logic instr_valid_id_i, input logic sleeping_i, output logic jump_req_o, output logic [31:0] jump_addr_o ); enum logic [2:0] {RD_NONE, RD_CSR, RD_GPR, RD_DBGA, RD_DBGS} rdata_sel_q, rdata_sel_n; enum logic [0:0] {FIRST, SECOND} state_q, state_n; logic [DBG_SETS_W-1:0] settings_q, settings_n; // for timing critical register file access we need to keep those in FFs logic [14:0] addr_q; logic [31:0] wdata_q; // mainly for jumps logic regfile_rreq_q, regfile_rreq_n; logic jump_req_q, jump_req_n; // not timing critical logic csr_req_q, csr_req_n; logic regfile_wreq; enum logic [1:0] {RUNNING, HALT_REQ, HALT} stall_cs, stall_ns; logic [31:0] dbg_rdata; logic dbg_resume; logic dbg_halt; logic [5:0] dbg_cause_q, dbg_cause_n; logic dbg_ssth_q, dbg_ssth_n; logic ssth_clear; // ppc/npc tracking logic [31:0] ppc_int, npc_int; // address decoding, write and read controller always_comb begin rdata_sel_n = RD_NONE; state_n = FIRST; debug_gnt_o = 1'b0; regfile_rreq_n = 1'b0; regfile_wreq = 1'b0; csr_req_n = 1'b0; csr_we_o = 1'b0; jump_req_n = 1'b0; dbg_resume = 1'b0; dbg_halt = 1'b0; settings_n = settings_q; ssth_clear = 1'b0; if (debug_req_i) begin if (debug_we_i) begin //---------------------------------------------------------------------------- // write access //---------------------------------------------------------------------------- if (debug_addr_i[14]) begin // CSR access if (state_q == FIRST) begin // only grant in second cycle, address and data have been latched by then debug_gnt_o = 1'b0; state_n = SECOND; if (debug_halted_o) begin // access to internal registers are only allowed when the core is halted csr_req_n = 1'b1; end end else begin debug_gnt_o = 1'b1; // grant it even when invalid access to not block state_n = FIRST; csr_we_o = 1'b1; end end else begin // non-CSR access unique case (debug_addr_i[13:8]) 6'b00_0000: begin // Debug Registers, always accessible debug_gnt_o = 1'b1; unique case (debug_addr_i[6:2]) 5'b0_0000: begin // DBG_CTRL if (debug_wdata_i[16]) begin // HALT set if (~debug_halted_o) begin // not halt, so STOP dbg_halt = 1'b1; end end else begin // RESUME set if (debug_halted_o) begin dbg_resume = 1'b1; end end settings_n[DBG_SETS_SSTE] = debug_wdata_i[0]; end 5'b0_0001: begin // DBG_HIT ssth_clear = debug_wdata_i[0]; end 5'b0_0010: begin // DBG_IE settings_n[DBG_SETS_ECALL] = debug_wdata_i[11]; settings_n[DBG_SETS_ELSU] = debug_wdata_i[7] | debug_wdata_i[5]; settings_n[DBG_SETS_EBRK] = debug_wdata_i[3]; settings_n[DBG_SETS_EILL] = debug_wdata_i[2]; end default:; endcase end 6'b10_0000: begin // Debug Registers, only accessible when in debug debug_gnt_o = 1'b1; // grant it even when invalid access to not block if (debug_halted_o) begin unique case (debug_addr_i[6:2]) 5'b0_0000: jump_req_n = 1'b1; // DNPC default:; endcase end end 6'b00_0100: begin // General-Purpose Registers debug_gnt_o = 1'b1; // grant it even when invalid access to not block if (debug_halted_o) begin regfile_wreq = 1'b1; end end default: debug_gnt_o = 1'b1; // grant it even when invalid access to not block endcase end end else begin //---------------------------------------------------------------------------- // read access //---------------------------------------------------------------------------- if (debug_addr_i[14]) begin debug_gnt_o = 1'b1; // grant it even when invalid access to not block // CSR access if (debug_halted_o) begin // access to internal registers are only allowed when the core is halted csr_req_n = 1'b1; rdata_sel_n = RD_CSR; end end else begin // non-CSR access unique case (debug_addr_i[13:8]) 6'b00_0000: begin // Debug Registers, always accessible debug_gnt_o = 1'b1; rdata_sel_n = RD_DBGA; end 6'b10_0000: begin // Debug Registers, only accessible when in debug debug_gnt_o = 1'b1; // grant it even when invalid access to not block rdata_sel_n = RD_DBGS; end 6'b00_0100: begin // General-Purpose Registers debug_gnt_o = 1'b1; // grant it even when invalid access to not block if (debug_halted_o) begin regfile_rreq_n = 1'b1; rdata_sel_n = RD_GPR; end end default: debug_gnt_o = 1'b1; // grant it even when invalid access to not block endcase end end end end //---------------------------------------------------------------------------- // debug register read access // // Since those are combinational, we can do it in the cycle where we set // rvalid. The address has been latched into addr_q //---------------------------------------------------------------------------- always_comb begin dbg_rdata = '0; case (rdata_sel_q) RD_DBGA: begin unique case (addr_q[6:2]) 5'h00: dbg_rdata[31:0] = {15'b0, debug_halted_o, 15'b0, settings_q[DBG_SETS_SSTE]}; // DBG_CTRL 5'h01: dbg_rdata[31:0] = {15'b0, sleeping_i, 15'b0, dbg_ssth_q}; // DBG_HIT 5'h02: begin // DBG_IE dbg_rdata[31:16] = '0; dbg_rdata[15:12] = '0; dbg_rdata[11] = settings_q[DBG_SETS_ECALL]; dbg_rdata[10: 8] = '0; dbg_rdata[ 7] = settings_q[DBG_SETS_ELSU]; dbg_rdata[ 6] = 1'b0; dbg_rdata[ 5] = settings_q[DBG_SETS_ELSU]; dbg_rdata[ 4] = 1'b0; dbg_rdata[ 3] = settings_q[DBG_SETS_EBRK]; dbg_rdata[ 2] = settings_q[DBG_SETS_EILL]; dbg_rdata[ 1: 0] = '0; end 5'h03: dbg_rdata = {dbg_cause_q[5], 26'b0, dbg_cause_q[4:0]}; // DBG_CAUSE 5'h10: dbg_rdata = '0; // DBG_BPCTRL0 5'h12: dbg_rdata = '0; // DBG_BPCTRL1 5'h14: dbg_rdata = '0; // DBG_BPCTRL2 5'h16: dbg_rdata = '0; // DBG_BPCTRL3 5'h18: dbg_rdata = '0; // DBG_BPCTRL4 5'h1A: dbg_rdata = '0; // DBG_BPCTRL5 5'h1C: dbg_rdata = '0; // DBG_BPCTRL6 5'h1E: dbg_rdata = '0; // DBG_BPCTRL7 default:; endcase end RD_DBGS: begin unique case (addr_q[2:2]) 1'b0: dbg_rdata = npc_int; // DBG_NPC 1'b1: dbg_rdata = ppc_int; // DBG_PPC default:; endcase end default:; endcase end //---------------------------------------------------------------------------- // read data mux //---------------------------------------------------------------------------- always_comb begin debug_rdata_o = '0; case (rdata_sel_q) RD_CSR: debug_rdata_o = csr_rdata_i; RD_GPR: debug_rdata_o = regfile_rdata_i; RD_DBGA: debug_rdata_o = dbg_rdata; RD_DBGS: debug_rdata_o = dbg_rdata; default: ; endcase end //---------------------------------------------------------------------------- // rvalid generation //---------------------------------------------------------------------------- always_ff @(posedge clk, negedge rst_n) begin if (~rst_n) begin debug_rvalid_o <= 1'b0; end else begin debug_rvalid_o <= debug_gnt_o; // always give the rvalid one cycle after gnt end end //---------------------------------------------------------------------------- // stall control //---------------------------------------------------------------------------- always_comb begin stall_ns = stall_cs; dbg_req_o = 1'b0; stall_o = 1'b0; debug_halted_o = 1'b0; dbg_cause_n = dbg_cause_q; dbg_ssth_n = dbg_ssth_q; case (stall_cs) RUNNING: begin dbg_ssth_n = 1'b0; if (dbg_halt | debug_halt_i | trap_i) begin dbg_req_o = 1'b1; stall_ns = HALT_REQ; if (trap_i) begin if (settings_q[DBG_SETS_SSTE]) dbg_ssth_n = 1'b1; dbg_cause_n = exc_cause_i; end else begin dbg_cause_n = DBG_CAUSE_HALT; end end end HALT_REQ: begin dbg_req_o = 1'b1; if (dbg_ack_i) stall_ns = HALT; if (dbg_resume | debug_resume_i) stall_ns = RUNNING; end HALT: begin stall_o = 1'b1; debug_halted_o = 1'b1; if (dbg_resume | debug_resume_i) begin stall_ns = RUNNING; stall_o = 1'b0; end end endcase if (ssth_clear) dbg_ssth_n = 1'b0; end always_ff @(posedge clk, negedge rst_n) begin if (~rst_n) begin stall_cs <= RUNNING; dbg_cause_q <= DBG_CAUSE_HALT; dbg_ssth_q <= 1'b0; end else begin stall_cs <= stall_ns; dbg_cause_q <= dbg_cause_n; dbg_ssth_q <= dbg_ssth_n; end end //---------------------------------------------------------------------------- // NPC/PPC selection //---------------------------------------------------------------------------- assign ppc_int = pc_id_i; assign npc_int = pc_if_i; always_ff @(posedge clk, negedge rst_n) begin if (~rst_n) begin addr_q <= '0; wdata_q <= '0; state_q <= FIRST; rdata_sel_q <= RD_NONE; regfile_rreq_q <= 1'b0; csr_req_q <= 1'b0; jump_req_q <= 1'b0; settings_q <= 1'b0; end else begin // settings settings_q <= settings_n; // for the actual interface if (debug_req_i) begin addr_q <= debug_addr_i; wdata_q <= debug_wdata_i; state_q <= state_n; end if (debug_req_i | debug_rvalid_o) begin // wait for either req or rvalid to set those FFs // This makes sure that they are only triggered once when there is // only one request, and the FFs can be properly clock gated otherwise regfile_rreq_q <= regfile_rreq_n; csr_req_q <= csr_req_n; jump_req_q <= jump_req_n; rdata_sel_q <= rdata_sel_n; end end end assign regfile_rreq_o = regfile_rreq_q; assign regfile_raddr_o = addr_q[6:2]; assign regfile_wreq_o = regfile_wreq; assign regfile_waddr_o = debug_addr_i[6:2]; assign regfile_wdata_o = debug_wdata_i; assign csr_req_o = csr_req_q; assign csr_addr_o = addr_q[13:2]; assign csr_wdata_o = wdata_q; assign jump_req_o = jump_req_q; assign jump_addr_o = wdata_q; assign settings_o = settings_q; //---------------------------------------------------------------------------- // Assertions //---------------------------------------------------------------------------- `ifndef VERILATOR // check that no registers are accessed when we are not in debug mode assert property ( @(posedge clk) (debug_req_i) |-> ((debug_halted_o == 1'b1) || ((debug_addr_i[14] != 1'b1) && (debug_addr_i[13:7] != 5'b0_1001) && (debug_addr_i[13:7] != 5'b0_1000)) ) ) else $warning("Trying to access internal debug registers while core is not stalled"); // check that all accesses are word-aligned assert property ( @(posedge clk) (debug_req_i) |-> (debug_addr_i[1:0] == 2'b00) ); `endif endmodule // debug_unit