ibex/debug_unit.sv
2016-06-03 14:04:44 +02:00

536 lines
17 KiB
Systemverilog

// Copyright 2016 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 //
// //
// Design Name: Debug Unit //
// Project Name: RI5CY //
// Language: SystemVerilog //
// //
// Description: Debug controller //
// //
////////////////////////////////////////////////////////////////////////////////
import riscv_defines::*;
module riscv_debug_unit
(
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 [ 4:0] regfile_raddr_o,
input logic [31:0] regfile_rdata_i,
// register file write port
output logic regfile_wreq_o,
output logic [ 4: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 [31:0] pc_ex_i,
input logic data_load_event_i,
input logic instr_valid_id_i,
input logic sleeping_i,
input logic branch_in_ex_i,
input logic branch_taken_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
enum logic [1:0] {IFID, IFEX, IDEX} pc_tracking_fsm_cs, pc_tracking_fsm_ns;
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;
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
//----------------------------------------------------------------------------
always_comb
begin
pc_tracking_fsm_ns = pc_tracking_fsm_cs;
ppc_int = pc_id_i;
npc_int = pc_if_i;
// PPC/NPC mux
unique case (pc_tracking_fsm_cs)
IFID: begin
ppc_int = pc_id_i;
npc_int = pc_if_i;
end
IFEX: begin
ppc_int = pc_ex_i;
npc_int = pc_if_i;
end
IDEX: begin
ppc_int = pc_ex_i;
npc_int = pc_id_i;
if (jump_req_o)
pc_tracking_fsm_ns = IFEX;
end
default: begin
pc_tracking_fsm_ns = IFID;
end
endcase
// set state if trap is encountered
if (dbg_ack_i) begin
pc_tracking_fsm_ns = IFID;
if (branch_in_ex_i) begin
if (branch_taken_i)
pc_tracking_fsm_ns = IFEX;
else
pc_tracking_fsm_ns = IDEX;
end else if (data_load_event_i) begin
// for p.elw
if (instr_valid_id_i)
pc_tracking_fsm_ns = IDEX;
else
pc_tracking_fsm_ns = IFEX;
end
end
end
always_ff @(posedge clk, negedge rst_n)
begin
if (~rst_n) begin
pc_tracking_fsm_cs <= IFID;
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
pc_tracking_fsm_cs <= pc_tracking_fsm_ns;
// 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
//----------------------------------------------------------------------------
// 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) );
endmodule // debug_unit