Hazard3/hdl/hazard3_triggers.v

164 lines
5.6 KiB
Verilog

/*****************************************************************************\
| Copyright (C) 2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
`default_nettype none
// Trigger unit. Currently only breakpoint (type=2 execute=1 select=0)
// triggers are supported. Only exact address matches are supported, and the
// timing is always "early".
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 (e.g. from tcontrol or mstatus.mie)
input wire trig_m_en,
// PC query
input wire [W_ADDR-1:0] pc,
input wire m_mode,
input wire d_mode,
// Break request
output wire break_any,
output wire break_d_mode
);
`include "hazard3_csr_addr.vh"
// ----------------------------------------------------------------------------
// Configuration state
parameter W_TSELECT = $clog2(BREAKPOINT_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.
reg tdata1_dmode [0:BREAKPOINT_TRIGGERS-1];
reg mcontrol_action [0:BREAKPOINT_TRIGGERS-1];
reg mcontrol_m [0:BREAKPOINT_TRIGGERS-1];
reg mcontrol_u [0:BREAKPOINT_TRIGGERS-1];
reg mcontrol_execute [0:BREAKPOINT_TRIGGERS-1];
reg [W_DATA-1:0] tdata2 [0:BREAKPOINT_TRIGGERS-1];
// ----------------------------------------------------------------------------
// Configuration write port
always @ (posedge clk or negedge rst_n) begin: cfg_update
integer i;
if (!rst_n) begin
tselect <= {W_TSELECT{1'b0}};
for (i = 0; i < BREAKPOINT_TRIGGERS; i = i + 1) begin
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;
tdata2[i] <= {W_DATA{1'b0}};
end
end else if (cfg_wen && cfg_addr == TSELECT) begin
tselect <= cfg_wdata[W_TSELECT-1:0];
end else if (cfg_wen && tselect < BREAKPOINT_TRIGGERS && !(tdata1_dmode[i] && !d_mode)) begin
// Handle writes to tselect-indexed registers (note writes to D-mode
// triggers in non-D-mode are ignored rather than raising an exception)
if (cfg_addr == TDATA1) begin
if (d_mode) begin
tdata1_dmode[tselect] <= cfg_wdata[27];
end
mcontrol_action[tselect] <= cfg_wdata[12];
mcontrol_m[tselect] <= cfg_wdata[6];
mcontrol_u[tselect] <= cfg_wdata[3];
mcontrol_execute[tselect] <= cfg_wdata[2];
end else if (cfg_addr == TDATA2) begin
tdata2[tselect] <= cfg_wdata;
end
end
end
// ----------------------------------------------------------------------------
// Configuration read port
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
if (tselect >= BREAKPOINT_TRIGGERS) begin
// Nonexistent -> type=0
cfg_rdata = {W_DATA{1'b0}};
end else begin
cfg_rdata = {
4'h2, // type = address/data match
tdata1_dmode[tselect],
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[tselect]}, // 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[tselect],
1'b0,
1'b0, // s = 0, no S-mode
mcontrol_u[tselect],
mcontrol_execute[tselect],
1'b0, // store = 0, this is not a watchpoint
1'b0 // load = 0, this is not a watchpoint
};
end
end else if (cfg_rdata == TDATA2) begin
if (tselect >= BREAKPOINT_TRIGGERS) begin
cfg_rdata = {W_DATA{1'b0}};
end else begin
cfg_rdata = tdata2[tselect];
end
end else if (cfg_rdata == TINFO) begin
if (tselect >= BREAKPOINT_TRIGGERS) begin
cfg_rdata = 32'h00000001; // type = 0, no trigger
end else begin
cfg_rdata = 32'h00000004; // type = 2, address/data match
end
end
end
// ----------------------------------------------------------------------------
// Trigger match logic
reg [BREAKPOINT_TRIGGERS-1:0] want_d_mode_break;
reg [BREAKPOINT_TRIGGERS-1:0] want_m_mode_break;
always @ (*) begin: match_pc
integer i;
want_m_mode_break = {BREAKPOINT_TRIGGERS{1'b0}};
want_d_mode_break = {BREAKPOINT_TRIGGERS{1'b0}};
for (i = 0; i < BREAKPOINT_TRIGGERS; i = i + 1) begin
if (mcontrol_execute[i] && tdata2[i] == pc && !d_mode && (m_mode ? mcontrol_m[i] : mcontrol_u[i])) begin
want_d_mode_break[i] = mcontrol_action[i] && tdata1_dmode[i];
want_m_mode_break[i] = !mcontrol_action[i] && trig_m_en;
end
end
end
assign break_any = |want_m_mode_break || |want_d_mode_break;
assign break_d_mode = |want_d_mode_break;
endmodule
`ifndef YOSYS
`default_nettype wire
`endif