ibex/rtl/ibex_debug_unit.sv
Alex Bradbury 27e68bd76e Convert from Solderpad to standard Apache 2.0 license
This change has been informed by advice from the lowRISC legal
committee.

The Solderpad 0.51 license states "the Licensor permits any Work
licensed under this License, at the option of the Licensee, to be
treated as licensed under the Apache License Version 2.0". We use this
freedom to convert license markings to Apache 2.0. This commit ensures
that we retain all authorship and copyright attribution information.
2019-04-26 15:05:17 +01:00

483 lines
15 KiB
Systemverilog

// Copyright lowRISC contributors.
// Copyright 2018 ETH Zurich and University of Bologna.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
////////////////////////////////////////////////////////////////////////////////
// Engineer: Andreas Traber - atraber@iis.ee.ethz.ch //
// //
// Additional contributions by: //
// Davide Schiavone - pschiavo@iis.ee.ethz.ch //
// //
// Design Name: Debug Unit //
// Project Name: ibex //
// Language: SystemVerilog //
// //
// Description: Debug controller //
// //
////////////////////////////////////////////////////////////////////////////////
`include "ibex_config.sv"
import ibex_defines::*;
module ibex_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