/*****************************************************************************\ | 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