mirror of
https://github.com/lowRISC/ibex.git
synced 2025-06-27 17:00:41 -04:00
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.
483 lines
15 KiB
Systemverilog
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
|