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