500 lines
17 KiB
Verilog
500 lines
17 KiB
Verilog
/*****************************************************************************\
|
|
| Copyright (C) 2022 Luke Wren |
|
|
| SPDX-License-Identifier: Apache-2.0 |
|
|
\*****************************************************************************/
|
|
|
|
`default_nettype none
|
|
|
|
// The Hazard3 trigger unit always implements one trigger of each of the
|
|
// following types:
|
|
//
|
|
// * Instruction count trigger (type=3) with count=1 (can single-step U-mode
|
|
// from M-mode, or step M-mode foreground from an M-mode exception handler)
|
|
//
|
|
// * Interrupt trigger (type=4): trigger on mask of mtip/msip/meip interrupts
|
|
//
|
|
// * Exception trigger (type=5): trigger on mask of exception causes
|
|
//
|
|
// The following are optionally supported:
|
|
//
|
|
// * Instruction address triggers (type=2 execute=1 select=0) aka breakpoints
|
|
//
|
|
// Breakpoints always use exact address matches, and the timing is always
|
|
// "early". The number of breakpoints is configured by BREAKPOINT_TRIGGERS,
|
|
// which can be 0.
|
|
//
|
|
// Interrupt/exception triggers break after the core transfers to its trap
|
|
// handler, but before the first trap handler instruction executes. Only
|
|
// action=1 is supported for these triggers, since trap-on-trap is useless
|
|
// when M is the only privileged mode.
|
|
|
|
module hazard3_triggers #(
|
|
`include "hazard3_config.vh"
|
|
) (
|
|
input wire clk,
|
|
input wire rst_n,
|
|
|
|
// Config interface passed through CSR block
|
|
input wire [11:0] cfg_addr,
|
|
input wire cfg_wen,
|
|
input wire [W_DATA-1:0] cfg_wdata,
|
|
output reg [W_DATA-1:0] cfg_rdata,
|
|
|
|
// Global trigger-to-M-mode enable from tcontrol
|
|
input wire trig_m_en,
|
|
|
|
// Fetch address query from stage F
|
|
input wire [W_ADDR-1:0] fetch_addr,
|
|
input wire fetch_m_mode,
|
|
input wire fetch_d_mode,
|
|
|
|
// Trap trigger events from stage X
|
|
input wire event_instr_ret,
|
|
|
|
// Trap trigger events from stage M
|
|
input wire event_interrupt,
|
|
input wire event_exception,
|
|
input wire [3:0] event_trap_cause,
|
|
input wire event_trap_enter,
|
|
|
|
// F-aligned break request (for each halfword of word-sized word-aligned fetch)
|
|
output wire [1:0] break_any,
|
|
output wire [1:0] break_d_mode,
|
|
|
|
// X-aligned step break request (to M-mode only)
|
|
output wire break_m_step,
|
|
|
|
// Stage-X debug mode flag, for CSR protection (may or may not be the same
|
|
// as the query debug mode flag)
|
|
input wire x_d_mode,
|
|
// Stage-X M-mode flag, for enables on interrupt/exception triggers
|
|
input wire x_m_mode
|
|
);
|
|
|
|
`include "hazard3_csr_addr.vh"
|
|
|
|
generate
|
|
if (DEBUG_SUPPORT == 0) begin: no_triggers
|
|
|
|
// The instantiation of this block should already be stubbed out in core.v if
|
|
// there are no triggers, but we still get warnings for elaborating this
|
|
// module with zero triggers, so add a generate block here too.
|
|
|
|
always @ (*) cfg_rdata = {W_DATA{1'b0}};
|
|
assign break_any = 1'b0;
|
|
assign break_d_mode = 1'b0;
|
|
|
|
end else begin: have_triggers
|
|
|
|
localparam TINDEX_ICOUNT = BREAKPOINT_TRIGGERS + 0;
|
|
localparam TINDEX_INTERRUPT = BREAKPOINT_TRIGGERS + 1;
|
|
localparam TINDEX_EXCEPTION = BREAKPOINT_TRIGGERS + 2;
|
|
localparam N_TRIGGERS = BREAKPOINT_TRIGGERS + 3;
|
|
|
|
// If there are no breakpoints, we still have one dummy register (hardwired to
|
|
// zero) for Verilog wrangling purposes. It has no synthesis effect.
|
|
localparam N_BREAKPOINT_REGS = BREAKPOINT_TRIGGERS > 0 ? BREAKPOINT_TRIGGERS : 1;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Configuration state
|
|
|
|
localparam W_TSELECT = $clog2(N_TRIGGERS);
|
|
|
|
reg [W_TSELECT-1:0] tselect;
|
|
|
|
// Note tdata1 and mcontrol are the same CSR. tdata1 refers to the universal
|
|
// fields (type/dmode) and mcontrol refers to those fields specific to
|
|
// type=2 (address/data match), the only trigger type we implement.
|
|
|
|
// State for instruction address match triggers (breakpoints).
|
|
reg bp_tdata1_dmode [0:N_BREAKPOINT_REGS-1];
|
|
reg mcontrol_action [0:N_BREAKPOINT_REGS-1];
|
|
reg mcontrol_m [0:N_BREAKPOINT_REGS-1];
|
|
reg mcontrol_u [0:N_BREAKPOINT_REGS-1];
|
|
reg mcontrol_execute [0:N_BREAKPOINT_REGS-1];
|
|
reg [W_DATA-1:0] bp_tdata2 [0:N_BREAKPOINT_REGS-1];
|
|
|
|
// State for instruction count trigger
|
|
// (hardwired: count=1 dmode=0 action=0; Debug mode single step is already
|
|
// available via dcsr)
|
|
reg icount_m;
|
|
reg icount_u;
|
|
|
|
// State for interrupt trigger
|
|
// (hardwired: action=1; M-mode trap-on-trap is useless as you lose the
|
|
// original trap state)
|
|
reg trigger_irq_m;
|
|
reg trigger_irq_u;
|
|
reg trigger_irq_dmode;
|
|
reg [15:0] trigger_irq_cause;
|
|
|
|
localparam [15:0] IMPLEMENTED_IRQ_CAUSES = {
|
|
4'h0, // reserved
|
|
1'b1, // meip
|
|
3'h0, // reserved or unimplemented
|
|
1'b1, // mtip
|
|
3'h0, // reserved or unimplemented
|
|
1'b1, // msip
|
|
3'h0 // reserved or unimplemented
|
|
};
|
|
|
|
// State for exception trigger
|
|
// (hardwired: action=1; M-mode trap-on-trap is useless as you lose the
|
|
// original trap state)
|
|
reg trigger_exception_m;
|
|
reg trigger_exception_u;
|
|
reg trigger_exception_dmode;
|
|
reg [15:0] trigger_exception_cause;
|
|
|
|
localparam [15:0] IMPLEMENTED_EXCEPTION_CAUSES = {
|
|
4'h0, // reserved
|
|
1'b1, // 11 -> ecall from M-mode
|
|
2'h0, // reserved or unimplemented
|
|
|U_MODE, // 8 -> ecall from U-mode
|
|
1'b1, // 7 -> store/AMO fault
|
|
1'b1, // 6 -> store/AMO align
|
|
1'b1, // 5 -> load fault
|
|
1'b1, // 4 -> load align
|
|
1'b0, // 3 -> breakpoint; seems useless and risky so disallow
|
|
1'b1, // 2 -> illegal opcode
|
|
1'b1, // 1 -> fetch fault
|
|
~|EXTENSION_C // 0 -> fetch align (only when IALIGN is 32-bit)
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Configuration write port
|
|
|
|
localparam N_TRIGGERS_PADDED = 1 << $clog2(N_TRIGGERS);
|
|
wire [N_TRIGGERS_PADDED-1:0] tselect_match = {{N_TRIGGERS_PADDED-1{1'b0}}, 1'b1} << tselect;
|
|
|
|
always @ (posedge clk or negedge rst_n) begin: cfg_update
|
|
integer i;
|
|
if (!rst_n) begin
|
|
|
|
tselect <= {W_TSELECT{1'b0}};
|
|
|
|
icount_m <= 1'b0;
|
|
icount_u <= 1'b0;
|
|
|
|
trigger_irq_m <= 1'b0;
|
|
trigger_irq_u <= 1'b0;
|
|
trigger_irq_dmode <= 1'b0;
|
|
trigger_irq_cause <= 16'h0;
|
|
|
|
trigger_exception_m <= 1'b0;
|
|
trigger_exception_u <= 1'b0;
|
|
trigger_exception_dmode <= 1'b0;
|
|
trigger_exception_cause <= 16'h0;
|
|
|
|
for (i = 0; i < BREAKPOINT_TRIGGERS; i = i + 1) begin
|
|
bp_tdata1_dmode[i] <= 1'b0;
|
|
mcontrol_action[i] <= 1'b0;
|
|
mcontrol_m[i] <= 1'b0;
|
|
mcontrol_u[i] <= 1'b0;
|
|
mcontrol_execute[i] <= 1'b0;
|
|
bp_tdata2[i] <= {W_DATA{1'b0}};
|
|
end
|
|
|
|
end else begin
|
|
|
|
if (cfg_wen && cfg_addr == TSELECT) begin
|
|
|
|
tselect <= cfg_wdata[W_TSELECT-1:0];
|
|
|
|
end else if (cfg_wen && cfg_addr == TDATA1) begin
|
|
|
|
if (tselect_match[TINDEX_ICOUNT]) begin
|
|
// This trigger does not implement a dmode bit, as Debug-mode
|
|
// break on single-step is already provided by dcsr.step
|
|
icount_m <= cfg_wdata[9];
|
|
icount_u <= cfg_wdata[6] && |U_MODE;
|
|
end
|
|
if (tselect_match[TINDEX_INTERRUPT] && !(trigger_irq_dmode && !x_d_mode)) begin
|
|
trigger_irq_dmode <= cfg_wdata[27];
|
|
trigger_irq_m <= cfg_wdata[9];
|
|
trigger_irq_u <= cfg_wdata[6] && |U_MODE;
|
|
end
|
|
if (tselect_match[TINDEX_EXCEPTION] && !(trigger_exception_dmode && !x_d_mode)) begin
|
|
trigger_exception_dmode <= cfg_wdata[27];
|
|
trigger_exception_m <= cfg_wdata[9];
|
|
trigger_exception_u <= cfg_wdata[6] && |U_MODE;
|
|
end
|
|
for (i = 0; i < BREAKPOINT_TRIGGERS; i = i + 1) begin
|
|
if (tselect_match[i] && !(bp_tdata1_dmode[i] && !x_d_mode)) begin
|
|
if (x_d_mode) begin
|
|
bp_tdata1_dmode[i] <= cfg_wdata[27];
|
|
end
|
|
mcontrol_action[i] <= cfg_wdata[12];
|
|
mcontrol_m[i] <= cfg_wdata[6];
|
|
mcontrol_u[i] <= cfg_wdata[3] && |U_MODE;
|
|
mcontrol_execute[i] <= cfg_wdata[2];
|
|
end
|
|
end
|
|
|
|
end else if (cfg_wen && cfg_addr == TDATA2) begin
|
|
|
|
if (tselect_match[TINDEX_INTERRUPT] && !(trigger_irq_dmode && !x_d_mode)) begin
|
|
trigger_irq_cause <= cfg_wdata[15:0] & IMPLEMENTED_IRQ_CAUSES;
|
|
end
|
|
if (tselect_match[TINDEX_EXCEPTION] && !(trigger_exception_dmode && !x_d_mode)) begin
|
|
trigger_exception_cause <= cfg_wdata[15:0] & IMPLEMENTED_EXCEPTION_CAUSES;
|
|
end
|
|
for (i = 0; i < BREAKPOINT_TRIGGERS; i = i + 1) begin
|
|
if (tselect_match[i] && !(bp_tdata1_dmode[i] && !x_d_mode)) begin
|
|
bp_tdata2[i] <= cfg_wdata & {{W_ADDR-2{1'b1}}, |EXTENSION_C, 1'b0};
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
if ((x_d_mode || event_trap_enter) && break_m_step) begin
|
|
// count field is hardwired, so the trigger is required to disable
|
|
// itself by clearing its own enables
|
|
icount_m <= 1'b0;
|
|
icount_u <= 1'b0;
|
|
end
|
|
|
|
// With no breakpoints, there is still a dummy entry to avoid
|
|
// `generate` spaghetti; tools complain about comb processes without
|
|
// sensitivities etc, so just synchronously tie to 0:
|
|
if (BREAKPOINT_TRIGGERS == 0) begin
|
|
bp_tdata1_dmode[0] <= 1'b0;
|
|
mcontrol_action[0] <= 1'b0;
|
|
mcontrol_m[0] <= 1'b0;
|
|
mcontrol_u[0] <= 1'b0;
|
|
mcontrol_execute[0] <= 1'b0;
|
|
bp_tdata2[0] <= {W_DATA{1'b0}};
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Configuration read port
|
|
|
|
reg [W_DATA-1:0] tdata1_rdata [0:N_TRIGGERS_PADDED-1];
|
|
reg [W_DATA-1:0] tdata2_rdata [0:N_TRIGGERS_PADDED-1];
|
|
reg [W_DATA-1:0] tinfo_rdata [0:N_TRIGGERS_PADDED-1];
|
|
|
|
always @ (*) begin: generate_padded_rdata
|
|
|
|
// Default for unimplemented triggers
|
|
integer i;
|
|
for (i = 0; i < N_TRIGGERS_PADDED; i = i + 1) begin
|
|
tdata1_rdata[i] = {W_DATA{1'b0}};
|
|
tdata2_rdata[i] = {W_DATA{1'b0}};
|
|
tinfo_rdata[i] = 32'd1 << 0; // type = 0, no trigger
|
|
end
|
|
|
|
// Breakpoints are the first n triggers
|
|
for (i = 0; i < BREAKPOINT_TRIGGERS; i = i + 1) begin
|
|
tdata1_rdata[i] = {
|
|
4'h2, // type = address/data match
|
|
bp_tdata1_dmode[i],
|
|
6'h00, // maskmax = 0, exact match only
|
|
1'b0, // hit = 0, not implemented
|
|
1'b0, // select = 0, address match only
|
|
1'b0, // timing = 0, trigger before execution
|
|
2'h0, // sizelo = 0, unsized
|
|
{3'h0, mcontrol_action[i]}, // action = 0/1, break to M-mode/D-mode
|
|
1'b0, // chain = 0, chaining is useless for exact matches
|
|
4'h0, // match = 0, exact match only
|
|
mcontrol_m[i],
|
|
1'b0,
|
|
1'b0, // s = 0, no S-mode
|
|
mcontrol_u[i],
|
|
mcontrol_execute[i],
|
|
1'b0, // store = 0, this is not a watchpoint
|
|
1'b0 // load = 0, this is not a watchpoint
|
|
};
|
|
tdata2_rdata[i] = bp_tdata2[i];
|
|
tinfo_rdata[i] = 32'd1 << 2; // type = 2, address/data match
|
|
end
|
|
|
|
// Instruction count trigger
|
|
tdata1_rdata[TINDEX_ICOUNT] = {
|
|
4'h3, // type = instruction count
|
|
1'b0, // dmode = 0 (Debug mode already has dcsr.step)
|
|
2'h0, // reserved
|
|
1'b0, // hit = 0
|
|
14'd1, // count = 1, single-step only
|
|
icount_m,
|
|
1'b0, // reserved
|
|
1'b0, // s = 0, no S-mode
|
|
icount_u,
|
|
6'h0 // action = 0, break to M-mode
|
|
};
|
|
tinfo_rdata[TINDEX_ICOUNT] = 32'd1 << 3; // type = 3, instruction count
|
|
|
|
// Interrupt trigger
|
|
tdata1_rdata[TINDEX_INTERRUPT] = {
|
|
4'h4, // type = interrupt
|
|
trigger_irq_dmode,
|
|
1'b0, // hit = 0
|
|
16'h0, // reserved
|
|
trigger_irq_m,
|
|
1'b0, // reserved
|
|
1'b0, // s = 0, no S-mode
|
|
trigger_irq_u,
|
|
6'd1 // action = 1, break to Debug mode (if dmode=1)
|
|
};
|
|
tdata2_rdata[TINDEX_INTERRUPT] = {
|
|
16'h0,
|
|
trigger_irq_cause & IMPLEMENTED_IRQ_CAUSES
|
|
};
|
|
tinfo_rdata[TINDEX_INTERRUPT] = 32'd1 << 4;
|
|
|
|
// Exception trigger
|
|
tdata1_rdata[TINDEX_EXCEPTION] = {
|
|
4'h5, // type = exception
|
|
trigger_exception_dmode,
|
|
1'b0, // hit = 0
|
|
16'h0, // reserved
|
|
trigger_exception_m,
|
|
1'b0, // reserved
|
|
1'b0, // s = 0, no S-mode
|
|
trigger_exception_u,
|
|
6'd1 // action = 1, break to Debug mode (if dmode=1)
|
|
};
|
|
tdata2_rdata[TINDEX_EXCEPTION] = {
|
|
16'h0,
|
|
trigger_exception_cause & IMPLEMENTED_EXCEPTION_CAUSES
|
|
};
|
|
tinfo_rdata[TINDEX_EXCEPTION] = 32'd1 << 5;
|
|
|
|
end
|
|
|
|
always @ (*) begin
|
|
cfg_rdata = {W_DATA{1'b0}};
|
|
if (cfg_addr == TSELECT) begin
|
|
cfg_rdata = {{W_DATA-W_TSELECT{1'b0}}, tselect};
|
|
end else if (cfg_addr == TDATA1) begin
|
|
cfg_rdata = tdata1_rdata[tselect];
|
|
end else if (cfg_addr == TDATA2) begin
|
|
cfg_rdata = tdata2_rdata[tselect];
|
|
end else if (cfg_addr == TINFO) begin
|
|
cfg_rdata = tinfo_rdata[tselect];
|
|
end
|
|
end
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Interrupt/exception trigger logic
|
|
|
|
// Ignore tcontrol.mte as these triggers never target M-mode.
|
|
wire exception_trigger_match =
|
|
!x_d_mode && trigger_exception_dmode &&
|
|
(x_m_mode ? trigger_exception_m : trigger_exception_u) &&
|
|
event_exception &&
|
|
trigger_exception_cause[event_trap_cause] &&
|
|
IMPLEMENTED_EXCEPTION_CAUSES[event_trap_cause];
|
|
|
|
wire interrupt_trigger_match =
|
|
!x_d_mode && trigger_irq_dmode &&
|
|
(x_m_mode ? trigger_irq_m : trigger_irq_u) &&
|
|
event_interrupt &&
|
|
trigger_irq_cause[event_trap_cause] &&
|
|
IMPLEMENTED_IRQ_CAUSES[event_trap_cause];
|
|
|
|
// Asserted no later than the end of the aphase for the instruction fetch at
|
|
// mtvec. Tags the dphase of trap handler instruction fetches as containing
|
|
// breakpoints.
|
|
reg break_ie;
|
|
|
|
always @ (posedge clk or negedge rst_n) begin
|
|
if (!rst_n) begin
|
|
break_ie <= 1'b0;
|
|
end else begin
|
|
break_ie <= !x_d_mode && (break_ie || (
|
|
exception_trigger_match || interrupt_trigger_match
|
|
));
|
|
end
|
|
end
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Instruction count trigger logic (single-step under M-mode control)
|
|
|
|
wire step_break_enabled = trig_m_en && !x_d_mode && (
|
|
x_m_mode ? icount_m : icount_u
|
|
);
|
|
|
|
reg break_on_step;
|
|
|
|
always @ (posedge clk or negedge rst_n) begin
|
|
if (!rst_n) begin
|
|
break_on_step <= 1'b0;
|
|
end else begin
|
|
// Note icount triggers differ from dcsr.step in that they ignore
|
|
// exceptions, only triggering on retired instructions.
|
|
break_on_step <= !(x_d_mode || event_trap_enter) && (break_on_step || (
|
|
event_instr_ret && step_break_enabled
|
|
));
|
|
end
|
|
end
|
|
|
|
assign break_m_step = break_on_step;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Breakpoint trigger logic
|
|
|
|
// To reduce the fanin of jump and load/store gating in stage X, the address
|
|
// lookup is in stage F (fetch data phase). We check *fetch addresses*, not
|
|
// program counter values. Fetches are always word-sized and word-aligned.
|
|
//
|
|
// To ensure it is safe to do this, non-debug-mode writes to the TDATA1 and
|
|
// TDATA2 CSRs cause a prefetch flush, to maintain write-to-fetch ordering.
|
|
//
|
|
// It's possible for different breakpoints to match different halfwords of the
|
|
// fetch word. The trigger unit must report both matches separately, because
|
|
// it is not known at this point where the instruction boundaries are (we
|
|
// don't have the instruction data yet).
|
|
|
|
wire [N_BREAKPOINT_REGS-1:0] breakpoint_enabled;
|
|
wire [N_BREAKPOINT_REGS-1:0] breakpoint_match;
|
|
wire [N_BREAKPOINT_REGS-1:0] want_d_mode_break;
|
|
wire [N_BREAKPOINT_REGS-1:0] want_m_mode_break;
|
|
wire [N_BREAKPOINT_REGS-1:0] want_d_mode_break_hw0;
|
|
wire [N_BREAKPOINT_REGS-1:0] want_d_mode_break_hw1;
|
|
wire [N_BREAKPOINT_REGS-1:0] want_m_mode_break_hw0;
|
|
wire [N_BREAKPOINT_REGS-1:0] want_m_mode_break_hw1;
|
|
|
|
genvar g;
|
|
for (g = 0; g < N_BREAKPOINT_REGS; g = g + 1) begin: match_pc
|
|
// Detect tripped breakpoints
|
|
assign breakpoint_enabled[g] = mcontrol_execute[g] && !fetch_d_mode && (
|
|
fetch_m_mode ? mcontrol_m[g] : mcontrol_u[g]
|
|
);
|
|
assign breakpoint_match[g] = breakpoint_enabled[g] && fetch_addr == {bp_tdata2[g][W_DATA-1:2], 2'b00};
|
|
// Decide the type of break implied by the trip
|
|
assign want_d_mode_break[g] = breakpoint_match[g] && mcontrol_action[g] && bp_tdata1_dmode[g];
|
|
assign want_m_mode_break[g] = breakpoint_match[g] && !mcontrol_action[g] && trig_m_en;
|
|
// Report separately for each halfword, so the frontend can pass this
|
|
// through the prefetch buffer. A breakpoint exception is taken when
|
|
// the first halfword of an instruction (of any size) is flagged with
|
|
// a breakpoint, implying an exact match.
|
|
assign want_d_mode_break_hw0[g] = want_d_mode_break[g] && !bp_tdata2[g][1];
|
|
assign want_d_mode_break_hw1[g] = want_d_mode_break[g] && bp_tdata2[g][1];
|
|
assign want_m_mode_break_hw0[g] = want_m_mode_break[g] && !bp_tdata2[g][1];
|
|
assign want_m_mode_break_hw1[g] = want_m_mode_break[g] && bp_tdata2[g][1];
|
|
end
|
|
|
|
// Break flags to frontend (tag the current fetch dphase as containing a breakpoint):
|
|
|
|
assign break_any = {
|
|
|want_m_mode_break_hw1 || |want_d_mode_break_hw1 || break_ie,
|
|
|want_m_mode_break_hw0 || |want_d_mode_break_hw0 || break_ie
|
|
} & {2{BREAKPOINT_TRIGGERS > 0}};
|
|
|
|
assign break_d_mode = {
|
|
|want_d_mode_break_hw1 || break_ie,
|
|
|want_d_mode_break_hw0 || break_ie
|
|
} & {2{BREAKPOINT_TRIGGERS > 0}};
|
|
|
|
end
|
|
endgenerate
|
|
|
|
endmodule
|
|
|
|
`ifndef YOSYS
|
|
`default_nettype wire
|
|
`endif
|