diff --git a/vendor/Hazard3/hdl/arith/hazard3_alu.v b/vendor/Hazard3/hdl/arith/hazard3_alu.v new file mode 100644 index 0000000..1b798e5 --- /dev/null +++ b/vendor/Hazard3/hdl/arith/hazard3_alu.v @@ -0,0 +1,269 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +`default_nettype none + +module hazard3_alu #( +`include "hazard3_config.vh" +, +`include "hazard3_width_const.vh" +) ( + input wire [W_ALUOP-1:0] aluop, + input wire [6:0] funct7_32b, + input wire [2:0] funct3_32b, + input wire [W_DATA-1:0] op_a, + input wire [W_DATA-1:0] op_b, + output reg [W_DATA-1:0] result, + output wire cmp +); + +`include "hazard3_ops.vh" + +// ---------------------------------------------------------------------------- +// Fiddle around with add/sub, comparisons etc (all related). + +wire sub = !(aluop == ALUOP_ADD || (|EXTENSION_ZBA && aluop == ALUOP_SHXADD)); + +wire inv_op_b = sub && !( + aluop == ALUOP_AND || aluop == ALUOP_OR || aluop == ALUOP_XOR || aluop == ALUOP_RS2 +); + +wire [W_DATA-1:0] op_a_shifted = + |EXTENSION_ZBA && aluop == ALUOP_SHXADD ? ( + !funct3_32b[2] ? op_a << 1 : + !funct3_32b[1] ? op_a << 2 : op_a << 3 + ) : op_a; + +wire [W_DATA-1:0] op_b_inv = op_b ^ {W_DATA{inv_op_b}}; + +wire [W_DATA-1:0] sum = op_a_shifted + op_b_inv + {{W_DATA-1{1'b0}}, sub}; +wire [W_DATA-1:0] op_xor = op_a ^ op_b; + +wire cmp_is_unsigned = aluop == ALUOP_LTU || + |EXTENSION_ZBB && aluop == ALUOP_MAXU || + |EXTENSION_ZBB && aluop == ALUOP_MINU; + +wire lt = op_a[W_DATA-1] == op_b[W_DATA-1] ? sum[W_DATA-1] : + cmp_is_unsigned ? op_b[W_DATA-1] : op_a[W_DATA-1] ; + +assign cmp = aluop == ALUOP_SUB ? |op_xor : lt; + +// ---------------------------------------------------------------------------- +// Separate units for shift, ctz etc + +wire [W_DATA-1:0] shift_dout; +wire shift_right_nleft = + aluop == ALUOP_SRL || + aluop == ALUOP_SRA || + (|EXTENSION_ZBB && aluop == ALUOP_ROR ) || + (|EXTENSION_ZBS && aluop == ALUOP_BEXT ) || + (|EXTENSION_XH3BEXTM && aluop == ALUOP_BEXTM); + +wire shift_arith = aluop == ALUOP_SRA; +wire shift_rotate = |EXTENSION_ZBB & (aluop == ALUOP_ROR || aluop == ALUOP_ROL); + +hazard3_shift_barrel #( +`include "hazard3_config_inst.vh" +) shifter ( + .din (op_a), + .shamt (op_b[4:0]), + .right_nleft (shift_right_nleft), + .rotate (shift_rotate), + .arith (shift_arith), + .dout (shift_dout) +); + +reg [W_DATA-1:0] op_a_rev; +always @ (*) begin: rev_op_a + integer i; + for (i = 0; i < W_DATA; i = i + 1) begin + op_a_rev[i] = op_a[W_DATA - 1 - i]; + end +end + +// "leading" means starting at MSB. This is an LSB-first priority encoder, so +// "leading" is reversed and "trailing" is not. +wire [W_DATA-1:0] ctz_search_mask = aluop == ALUOP_CLZ ? op_a_rev : op_a; +wire [W_SHAMT:0] ctz_clz; + +hazard3_priority_encode #( + .W_REQ (W_DATA), + .HIGHEST_WINS (0) +) ctz_priority_encode ( + .req (ctz_search_mask), + .gnt (ctz_clz[W_SHAMT-1:0]) +); +// Special case: all-zeroes returns XLEN +assign ctz_clz[W_SHAMT] = ~|op_a; + +reg [W_SHAMT:0] cpop; +always @ (*) begin: cpop_count + integer i; + cpop = {W_SHAMT+1{1'b0}}; + for (i = 0; i < W_DATA; i = i + 1) begin + cpop = cpop + {{W_SHAMT{1'b0}}, op_a[i]}; + end +end + +reg [2*W_DATA-1:0] clmul64; + +always @ (*) begin: clmul_mul + integer i; + clmul64 = {2*W_DATA{1'b0}}; + for (i = 0; i < W_DATA; i = i + 1) begin + clmul64 = clmul64 ^ (({{W_DATA{1'b0}}, op_a} << i) & {2*W_DATA{op_b[i]}}); + end +end + +// funct3: 1=clmul, 2=clmulr, 3=clmulh, never 0. +wire [W_DATA-1:0] clmul = + !funct3_32b[1] ? clmul64[31: 0] : + !funct3_32b[0] ? clmul64[62:31] : clmul64[63:32]; + +reg [W_DATA-1:0] zip; +reg [W_DATA-1:0] unzip; +always @ (*) begin: do_zip_unzip + integer i; + for (i = 0; i < W_DATA; i = i + 1) begin + zip[i] = op_a[{i[0], i[4:1]}]; // Alternate high/low halves + unzip[i] = op_a[{i[3:0], i[4]}]; // All even then all odd + end +end + +reg [W_DATA-1:0] xperm8; +always @ (*) begin: do_xperm8 + integer i; + for (i = 0; i < W_DATA; i = i + 8) begin + if (|op_b[i + 2 +: 6]) begin + xperm8[i +: 8] = 8'h00; + end else begin + xperm8[i +: 8] = op_a[8 * op_b[i +: 2] +: 8]; + end + end +end + +reg [W_DATA-1:0] xperm4; +always @ (*) begin: do_xperm4 + integer i; + for (i = 0; i < W_DATA; i = i + 4) begin + if (op_b[i + 3]) begin + xperm4[i +: 4] = 4'h0; + end else begin + xperm4[i +: 4] = op_a[4 * op_b[i +: 3] +: 4]; + end + end +end + +// ---------------------------------------------------------------------------- +// Output mux, with simple operations inline + +// iCE40: We can implement all bitwise ops with 1 LUT4/bit total, since each +// result bit uses only two operand bits. Much better than feeding each into +// main mux tree. Doesn't matter for big-LUT FPGAs or for implementations with +// bitmanip extensions enabled. + +reg [W_DATA-1:0] bitwise; + +always @ (*) begin: bitwise_ops + case (aluop[1:0]) + ALUOP_AND[1:0]: bitwise = op_a & op_b_inv; + ALUOP_OR [1:0]: bitwise = op_a | op_b_inv; + ALUOP_XOR[1:0]: bitwise = op_a ^ op_b_inv; + ALUOP_RS2[1:0]: bitwise = op_b_inv; + endcase +end + +wire [W_DATA-1:0] zbs_mask = {{W_DATA-1{1'b0}}, 1'b1} << op_b[W_SHAMT-1:0]; + +always @ (*) begin + casez ({|EXTENSION_A, |EXTENSION_ZBA, |EXTENSION_ZBB, |EXTENSION_ZBC, + |EXTENSION_ZBS, |EXTENSION_ZBKB, |EXTENSION_ZBKX, |EXTENSION_XH3BEXTM, aluop}) + // Base ISA + {8'bzzzzzzzz, ALUOP_ADD }: result = sum; + {8'bzzzzzzzz, ALUOP_SUB }: result = sum; + {8'bzzzzzzzz, ALUOP_LT }: result = {{W_DATA-1{1'b0}}, lt}; + {8'bzzzzzzzz, ALUOP_LTU }: result = {{W_DATA-1{1'b0}}, lt}; + {8'bzzzzzzzz, ALUOP_SRL }: result = shift_dout; + {8'bzzzzzzzz, ALUOP_SRA }: result = shift_dout; + {8'bzzzzzzzz, ALUOP_SLL }: result = shift_dout; + // A or Zbb (written this way to avoid case overlap) + {8'b1zzzzzzz, ALUOP_MAX }, + {8'b0z1zzzzz, ALUOP_MAX }: result = lt ? op_b : op_a; + {8'b1zzzzzzz, ALUOP_MIN }, + {8'b0z1zzzzz, ALUOP_MIN }: result = lt ? op_a : op_b; + {8'b1zzzzzzz, ALUOP_MAXU }, + {8'b0z1zzzzz, ALUOP_MAXU }: result = lt ? op_b : op_a; + {8'b1zzzzzzz, ALUOP_MINU }, + {8'b0z1zzzzz, ALUOP_MINU }: result = lt ? op_a : op_b; + // Zba + {8'bz1zzzzzz, ALUOP_SHXADD }: result = sum; + // Zbb + {8'bzz1zzzzz, ALUOP_ANDN }: result = bitwise; + {8'bzz1zzzzz, ALUOP_ORN }: result = bitwise; + {8'bzz1zzzzz, ALUOP_XNOR }: result = bitwise; + {8'bzz1zzzzz, ALUOP_CLZ }: result = {{W_DATA-W_SHAMT-1{1'b0}}, ctz_clz}; + {8'bzz1zzzzz, ALUOP_CTZ }: result = {{W_DATA-W_SHAMT-1{1'b0}}, ctz_clz}; + {8'bzz1zzzzz, ALUOP_CPOP }: result = {{W_DATA-W_SHAMT-1{1'b0}}, cpop}; + {8'bzz1zzzzz, ALUOP_SEXT_B }: result = {{W_DATA-8{op_a[7]}}, op_a[7:0]}; + {8'bzz1zzzzz, ALUOP_SEXT_H }: result = {{W_DATA-16{op_a[15]}}, op_a[15:0]}; + {8'bzz1zzzzz, ALUOP_ZEXT_H }: result = {{W_DATA-16{1'b0}}, op_a[15:0]}; + {8'bzz1zzzzz, ALUOP_ORC_B }: result = {{8{|op_a[31:24]}}, {8{|op_a[23:16]}}, {8{|op_a[15:8]}}, {8{|op_a[7:0]}}}; + {8'bzz1zzzzz, ALUOP_REV8 }: result = {op_a[7:0], op_a[15:8], op_a[23:16], op_a[31:24]}; + {8'bzz1zzzzz, ALUOP_ROL }: result = shift_dout; + {8'bzz1zzzzz, ALUOP_ROR }: result = shift_dout; + // Zbc + {8'bzzz1zzzz, ALUOP_CLMUL }: result = clmul; + // Zbs + {8'bzzzz1zzz, ALUOP_BCLR }: result = op_a & ~zbs_mask; + {8'bzzzz1zzz, ALUOP_BSET }: result = op_a | zbs_mask; + {8'bzzzz1zzz, ALUOP_BINV }: result = op_a ^ zbs_mask; + {8'bzzzz1zzz, ALUOP_BEXT }: result = {{W_DATA-1{1'b0}}, shift_dout[0]}; + // Zbkb + {8'bzzzzz1zz, ALUOP_PACK }: result = {op_b[15:0], op_a[15:0]}; + {8'bzzzzz1zz, ALUOP_PACKH }: result = {{W_DATA-16{1'b0}}, op_b[7:0], op_a[7:0]}; + {8'bzzzzz1zz, ALUOP_BREV8 }: result = {op_a_rev[7:0], op_a_rev[15:8], op_a_rev[23:16], op_a_rev[31:24]}; + {8'bzzzzz1zz, ALUOP_UNZIP }: result = unzip; + {8'bzzzzz1zz, ALUOP_ZIP }: result = zip; + // Zbkx + {8'bzzzzzz1z, ALUOP_XPERM }: result = funct3_32b[2] ? xperm8 : xperm4; + // Xh3bextm + {8'bzzzzzzz1, ALUOP_BEXTM }: result = shift_dout & {24'h0, {~(8'hfe << funct7_32b[3:1])}}; + + default: result = bitwise; + endcase +end + +// ---------------------------------------------------------------------------- +// Properties for base-ISA instructions + +`ifdef HAZARD3_ASSERTIONS +`ifndef RISCV_FORMAL +// Really we're just interested in the shifts and comparisons, as these are +// the nontrivial ones. However, easier to test everything! + +wire clk; +always @ (posedge clk) begin + case(aluop) + default: begin end + ALUOP_ADD: assert(result == op_a + op_b); + ALUOP_SUB: assert(result == op_a - op_b); + ALUOP_LT: assert(result == $signed(op_a) < $signed(op_b)); + ALUOP_LTU: assert(result == op_a < op_b); + ALUOP_AND: assert(result == (op_a & op_b)); + ALUOP_OR: assert(result == (op_a | op_b)); + ALUOP_XOR: assert(result == (op_a ^ op_b)); + ALUOP_SRL: assert(result == op_a >> op_b[4:0]); + ALUOP_SRA: assert($signed(result) == $signed(op_a) >>> $signed(op_b[4:0])); + ALUOP_SLL: assert(result == op_a << op_b[4:0]); + endcase +end +`endif +`endif + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/arith/hazard3_branchcmp.v b/vendor/Hazard3/hdl/arith/hazard3_branchcmp.v new file mode 100644 index 0000000..8672715 --- /dev/null +++ b/vendor/Hazard3/hdl/arith/hazard3_branchcmp.v @@ -0,0 +1,52 @@ +/*****************************************************************************\ +| Copyright (C) 2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +`default_nettype none + +// The branch decision path through the ALU is slow because: +// +// - Sees immediates and PC on its inputs, as well as regs +// - Add/sub rather than just add (with complex decode of the sub condition) +// - 2 extra mux layers in front of adder if Zba extension is enabled +// +// So there is sometimes timing benefit to a dedicated branch comparator. + +module hazard3_branchcmp #( +`include "hazard3_config.vh" +, +`include "hazard3_width_const.vh" +) ( + input wire [31:0] cir, + input wire [W_DATA-1:0] op_a, + input wire [W_DATA-1:0] op_b, + output wire cmp +); + +`include "hazard3_ops.vh" + +wire [W_DATA-1:0] diff = op_a - op_b; + +// funct3 instruction +// ------------------ +// 000 BEQ +// 001 BNE +// 100 BLT +// 101 BGE +// 110 BLTU +// 111 BGEU + +wire cmp_is_unsigned = cir[13]; + +wire lt = op_a[W_DATA-1] == op_b[W_DATA-1] ? diff[W_DATA-1] : + cmp_is_unsigned ? op_b[W_DATA-1] : + op_a[W_DATA-1] ; + +assign cmp = cir[14] ? lt : op_a != op_b; + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/arith/hazard3_mul_fast.v b/vendor/Hazard3/hdl/arith/hazard3_mul_fast.v new file mode 100644 index 0000000..c9fde6e --- /dev/null +++ b/vendor/Hazard3/hdl/arith/hazard3_mul_fast.v @@ -0,0 +1,162 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// MUL-only (cfg: MUL_FAST) and MUL/MULH/MULHU/MULHSU (cfg: MUL_FAST && +// MULH_FAST) are handled by different circuits. In either case it's a simple +// behavioural multiply, and we rely on inference to get good performance on +// FPGA. + +`default_nettype none + +module hazard3_mul_fast #( +`include "hazard3_config.vh" +, +`include "hazard3_width_const.vh" +) ( + input wire clk, + input wire rst_n, + + input wire [W_MULOP-1:0] op, + input wire op_vld, + input wire [W_DATA-1:0] op_a, + input wire [W_DATA-1:0] op_b, + + output wire [W_DATA-1:0] result, + output reg result_vld +); + +`include "hazard3_ops.vh" + +localparam XLEN = W_DATA; + +//synthesis translate_off +generate if (MULH_FAST && !MUL_FAST) begin: err_require_mul_fast_for_mulh + initial $fatal("%m: MULH_FAST requires that MUL_FAST is also set."); +end endgenerate +generate if (MUL_FASTER && !MUL_FAST) begin: err_require_mul_fast_for_faster + initial $fatal("%m: MUL_FASTER requires that MUL_FAST is also set."); +end endgenerate +//synthesis translate_on + +// Latency of 1: +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + result_vld <= 1'b0; + end else begin + result_vld <= op_vld; + end +end + +// ---------------------------------------------------------------------------- +// Fast MUL only + +generate +if (!MULH_FAST) begin: mul_only + +// This pipestage is folded into the front of the DSP tiles on UP5k. Note the +// intention is to register the bypassed core regs at the end of X (since +// bypass is quite slow), then perform multiply combinatorially in stage M, +// and mux into MW result register. + +reg [XLEN-1:0] op_a_r; +reg [XLEN-1:0] op_b_r; + +if (MUL_FASTER) begin: op_passthrough + always @ (*) begin + op_a_r = op_a; + op_b_r = op_b; + end +end else begin: op_register + always @ (posedge clk) begin + if (op_vld) begin + op_a_r <= op_a; + op_b_r <= op_b; + end + end +end + +// This should be inferred as 3 DSP tiles on UP5k: +// +// 1. Register then multiply a[15: 0] and b[15: 0] +// 2. Register then multiply a[31:16] and b[15: 0], then directly add output of 1 +// 3. Register then multiply a[15: 0] and b[31:16], then directly add output of 2 +// +// So there is quite a long path (1x 16-bit multiply, then 2x 16-bit add). On +// other platforms you may just end up with a pile of gates. + +`ifndef RISCV_FORMAL_ALTOPS + +assign result = op_a_r * op_b_r; + +`else + +// riscv-formal can use a simpler function, since it's just confirming the +// result is correctly hooked up. +assign result = result_vld ? (op_a_r + op_b_r) ^ 32'h5876063e : 32'hdeadbeef; + +`endif + +// ---------------------------------------------------------------------------- +// Fast MUL/MULH/MULHU/MULHSU + +end else begin: mul_and_mulh + +reg [XLEN-1:0] op_a_r; +reg [XLEN-1:0] op_b_r; +reg [W_MULOP-1:0] op_r; + +if (MUL_FASTER) begin: op_passthrough + always @ (*) begin + op_a_r = op_a; + op_b_r = op_b; + op_r = op; + end +end else begin: op_register + always @ (posedge clk) begin + if (op_vld) begin + op_a_r <= op_a; + op_b_r <= op_b; + op_r <= op; + end + end +end + +wire op_a_signed = op_r == M_OP_MULH || op_r == M_OP_MULHSU; +wire op_b_signed = op_r == M_OP_MULH; + +wire [2*XLEN-1:0] op_a_sext = { + {XLEN{op_a_r[XLEN - 1] && op_a_signed}}, + op_a_r +}; + +wire [2*XLEN-1:0] op_b_sext = { + {XLEN{op_b_r[XLEN - 1] && op_b_signed}}, + op_b_r +}; + +wire [2*XLEN-1:0] result_full = op_a_sext * op_b_sext; + +`ifndef RISCV_FORMAL_ALTOPS + +assign result = op_r == M_OP_MUL ? result_full[0 +: XLEN] : result_full[XLEN +: XLEN]; + +`else + +assign result = + op_r == M_OP_MULH ? (op_a_r + op_b_r) ^ 32'hf6583fb7 : + op_r == M_OP_MULHSU ? (op_a_r - op_b_r) ^ 32'hecfbe137 : + op_r == M_OP_MULHU ? (op_a_r + op_b_r) ^ 32'h949ce5e8 : + op_r == M_OP_MUL ? (op_a_r + op_b_r) ^ 32'h5876063e : 32'hdeadbeef; + +`endif + +end +endgenerate + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/arith/hazard3_muldiv_seq.v b/vendor/Hazard3/hdl/arith/hazard3_muldiv_seq.v new file mode 100644 index 0000000..1fa4bdb --- /dev/null +++ b/vendor/Hazard3/hdl/arith/hazard3_muldiv_seq.v @@ -0,0 +1,294 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// Combined multiply/divide/modulo circuit. All operations performed at 1 bit +// per clock; aiming for minimal resource usage on iCE40 FPGA. Optionally the +// circuit can be unrolled for slightly higher performance. +// +// When op_kill is high, the current calculation halts immediately. op_vld can +// be asserted on the same cycle, and the new calculation begins without +// delay, regardless of op_rdy. This may be used by the processor on e.g. +// mispredict or trap. +// +// The actual multiply/divide hardware is unsigned. We handle signedness at +// input/output. + +`default_nettype none + +module hazard3_muldiv_seq #( +`include "hazard3_config.vh" +, +`include "hazard3_width_const.vh" +) ( + input wire clk, + input wire rst_n, + input wire [W_MULOP-1:0] op, + input wire op_vld, + output wire op_rdy, + input wire op_kill, + input wire [W_DATA-1:0] op_a, + input wire [W_DATA-1:0] op_b, + + output wire [W_DATA-1:0] result_h, // mulh* or rem* + output wire [W_DATA-1:0] result_l, // mul or div* + output wire result_vld +); + +`include "hazard3_ops.vh" + +//synthesis translate_off +generate if (|(MULDIV_UNROLL & (MULDIV_UNROLL - 1)) || ~|MULDIV_UNROLL) begin: err_pow2 + initial $fatal("%m: MULDIV_UNROLL must be a positive power of 2"); +end endgenerate +//synthesis translate_on + +localparam XLEN = W_DATA; +parameter W_CTR = $clog2(XLEN + 1); + +// ---------------------------------------------------------------------------- +// Operation decode, operand sign adjustment + +// On the first cycle, op_a and op_b go straight through to the accumulator +// and the divisor/multiplicand register. They are then adjusted in-place +// on the next cycle. This allows the same circuits to be reused for sign +// adjustment before output (and helps input timing). + +reg [W_MULOP-1:0] op_r; +reg [2*XLEN-1:0] accum; +reg [XLEN-1:0] op_b_r; +reg op_a_neg_r; +reg op_b_neg_r; + +wire op_a_signed = + op_r == M_OP_MULH || + op_r == M_OP_MULHSU || + op_r == M_OP_DIV || + op_r == M_OP_REM; + +wire op_b_signed = + op_r == M_OP_MULH || + op_r == M_OP_DIV || + op_r == M_OP_REM; + +wire op_a_neg = op_a_signed && accum[XLEN-1]; +wire op_b_neg = op_b_signed && op_b_r[XLEN-1]; + +// Non-divide parts of the circuit should be constant-folded if all the MUL +// operations are handled by the fast multiplier + +wire is_div = op_r[2] || (MUL_FAST && MULH_FAST); + +// Controls for modifying sign of all/part of accumulator +wire accum_neg_l; +wire accum_inv_h; +wire accum_incr_h; + +// ---------------------------------------------------------------------------- +// Arithmetic circuit + +// Combinatorials: +reg [2*XLEN-1:0] accum_next; +reg [2*XLEN-1:0] addend; +reg [2*XLEN-1:0] shift_tmp; +reg [2*XLEN-1:0] addsub_tmp; +reg neg_l_borrow; + +always @ (*) begin: alu + integer i; + // Multiply/divide iteration layers + accum_next = accum; + addend = {2*XLEN{1'b0}}; + addsub_tmp = {2*XLEN{1'b0}}; + neg_l_borrow = 1'b0; + for (i = 0; i < MULDIV_UNROLL; i = i + 1) begin + addend = {is_div && |op_b_r, op_b_r, {XLEN-1{1'b0}}}; + shift_tmp = is_div ? accum_next : accum_next >> 1; + addsub_tmp = shift_tmp + addend; + accum_next = (is_div ? !addsub_tmp[2 * XLEN - 1] : accum_next[0]) ? + addsub_tmp : shift_tmp; + if (is_div) + accum_next = {accum_next[2*XLEN-2:0], !addsub_tmp[2 * XLEN - 1]}; + end + // Alternative path for negation of all/part of accumulator + if (accum_neg_l) + {neg_l_borrow, accum_next[XLEN-1:0]} = {~accum[XLEN-1:0]} + 1'b1; + if (accum_incr_h || accum_inv_h) + accum_next[XLEN +: XLEN] = (accum[XLEN +: XLEN] ^ {XLEN{accum_inv_h}}) + + {{XLEN-1{1'b0}}, accum_incr_h}; +end + +// ---------------------------------------------------------------------------- +// Main state machine + +reg sign_preadj_done; +reg [W_CTR-1:0] ctr; +reg sign_postadj_done; +reg sign_postadj_carry; + +localparam CTR_TOP = XLEN[W_CTR-1:0]; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + ctr <= {W_CTR{1'b0}}; + sign_preadj_done <= 1'b1; + sign_postadj_done <= 1'b1; + sign_postadj_carry <= 1'b0; + op_r <= {W_MULOP{1'b0}}; + op_a_neg_r <= 1'b0; + op_b_neg_r <= 1'b0; + op_b_r <= {XLEN{1'b0}}; + accum <= {XLEN*2{1'b0}}; + end else if (op_kill || (op_vld && op_rdy)) begin + // Initialise circuit with operands + state + ctr <= op_vld ? CTR_TOP : {W_CTR{1'b0}}; + sign_preadj_done <= !op_vld; + sign_postadj_done <= !op_vld; + sign_postadj_carry <= 1'b0; + op_r <= op; + op_b_r <= op_b; + accum <= {{XLEN{1'b0}}, op_a}; + end else if (!sign_preadj_done) begin + // Pre-adjust sign if necessary, else perform first iteration immediately + op_a_neg_r <= op_a_neg; + op_b_neg_r <= op_b_neg; + sign_preadj_done <= 1'b1; + if (accum_neg_l || (op_b_neg ^ is_div)) begin + if (accum_neg_l) + accum[0 +: XLEN] <= accum_next[0 +: XLEN]; + if (op_b_neg ^ is_div) + op_b_r <= -op_b_r; + end else begin + ctr <= ctr - MULDIV_UNROLL[W_CTR-1:0]; + accum <= accum_next; + end + end else if (|ctr) begin + ctr <= ctr - MULDIV_UNROLL[W_CTR-1:0]; + accum <= accum_next; + end else if (!sign_postadj_done || sign_postadj_carry) begin + sign_postadj_done <= 1'b1; + if (accum_inv_h || accum_incr_h) + accum[XLEN +: XLEN] <= accum_next[XLEN +: XLEN]; + if (accum_neg_l) begin + accum[0 +: XLEN] <= accum_next[0 +: XLEN]; + if (!is_div) begin + sign_postadj_carry <= neg_l_borrow; + sign_postadj_done <= !neg_l_borrow; + end + end + end +end + +// ---------------------------------------------------------------------------- +// Sign adjustment control + +// Pre-adjustment: for any a, b we want |a|, |b|. Note that the magnitude of any +// 32-bit signed integer is representable by a 32-bit unsigned integer. + +// Post-adjustment for division: +// We seek q, r to satisfy a = b * q + r, where a and b are given, +// and |r| < |b|. One way to do this is if +// sgn(r) = sgn(a) +// sgn(q) = sgn(a) ^ sgn(b) +// This has additional nice properties like +// -(a / b) = (-a) / b = a / (-b) + +// Post-adjustment for multiplication: +// We have calculated the 2*XLEN result of |a| * |b|. +// Negate the entire accumulator if sgn(a) ^ sgn(b). +// This is done in two steps (to share div/mod circuit, and avoid 64-bit carry): +// - Negate lower half of accumulator, and invert upper half +// - Increment upper half if lower half carried + +wire do_postadj = ~|{ctr, sign_postadj_done}; +wire op_signs_differ = op_a_neg_r ^ op_b_neg_r; + +assign accum_neg_l = + !sign_preadj_done && op_a_neg || + do_postadj && !sign_postadj_carry && op_signs_differ && !(is_div && ~|op_b_r); + +assign {accum_incr_h, accum_inv_h} = + do_postadj && is_div && op_a_neg_r ? 2'b11 : + do_postadj && !is_div && op_signs_differ && !sign_postadj_carry ? 2'b01 : + do_postadj && !is_div && op_signs_differ && sign_postadj_carry ? 2'b10 : + 2'b00 ; + +// ---------------------------------------------------------------------------- +// Outputs + +assign op_rdy = ~|{ctr, accum_neg_l, accum_incr_h, accum_inv_h}; +assign result_vld = op_rdy; + +`ifndef RISCV_FORMAL_ALTOPS + +assign {result_h, result_l} = accum; + +`else + +// Provide arithmetically simpler alternative operations, to speed up formal checks +always assert(XLEN == 32); + +reg [XLEN-1:0] fml_a_saved; +reg [XLEN-1:0] fml_b_saved; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + fml_a_saved <= {XLEN{1'b0}}; + fml_b_saved <= {XLEN{1'b0}}; + end else if (op_vld && op_rdy) begin + fml_a_saved <= op_a; + fml_b_saved <= op_b; + end +end + +assign result_h = + op_r == M_OP_MULH ? (fml_a_saved + fml_b_saved) ^ 32'hf6583fb7 : + op_r == M_OP_MULHSU ? (fml_a_saved - fml_b_saved) ^ 32'hecfbe137 : + op_r == M_OP_MULHU ? (fml_a_saved + fml_b_saved) ^ 32'h949ce5e8 : + op_r == M_OP_REM ? (fml_a_saved - fml_b_saved) ^ 32'h8da68fa5 : + op_r == M_OP_REMU ? (fml_a_saved - fml_b_saved) ^ 32'h3138d0e1 : 32'hdeadbeef; + +assign result_l = + op_r == M_OP_MUL ? (fml_a_saved + fml_b_saved) ^ 32'h5876063e : + op_r == M_OP_DIV ? (fml_a_saved - fml_b_saved) ^ 32'h7f8529ec : + op_r == M_OP_DIVU ? (fml_a_saved - fml_b_saved) ^ 32'h10e8fd70 : 32'hdeadbeef; + +`endif + +// ---------------------------------------------------------------------------- +// Interface properties + +`ifdef HAZARD3_ASSERTIONS + +always @ (posedge clk) if (rst_n && $past(rst_n)) begin: properties + integer i; + reg alive; + + if ($past(op_rdy && !op_vld)) + assert(op_rdy); + + if (result_vld && $past(result_vld) && !$past(op_kill)) + assert($stable({result_h, result_l})); + + // Kill will halt an in-progress operation, but a new operation may be + // asserted simultaneously with kill. + if ($past(op_kill)) + assert(op_rdy == !$past(op_vld)); + + // We should be periodically ready (liveness property), unless new operations + // are forced in immediately, simultaneous with a kill, in which case there + // is no intermediate ready state. + alive = op_rdy || (op_kill && op_vld); + for (i = 1; i <= XLEN / MULDIV_UNROLL + 3; i = i + 1) + alive = alive || $past(op_rdy || (op_kill && op_vld), i); + assert(alive); +end + +`endif + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/arith/hazard3_onehot_encode.v b/vendor/Hazard3/hdl/arith/hazard3_onehot_encode.v new file mode 100644 index 0000000..b33dd9a --- /dev/null +++ b/vendor/Hazard3/hdl/arith/hazard3_onehot_encode.v @@ -0,0 +1,31 @@ +/*****************************************************************************\ +| Copyright (C) 2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// req: one-hot bitmap +// idx: index of the sole set bit in req + +`default_nettype none + +module hazard3_onehot_encode #( + parameter W_REQ = 16, + parameter W_GNT = $clog2(W_REQ) // do not modify +) ( + input wire [W_REQ-1:0] req, + output reg [W_GNT-1:0] gnt +); + +always @ (*) begin: encode + reg [W_GNT:0] i; + gnt = {W_GNT{1'b0}}; + for (i = 0; i < W_REQ; i = i + 1) begin + gnt = gnt | ({W_GNT{req[i[W_GNT-1:0]]}} & i[W_GNT-1:0]); + end +end + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/arith/hazard3_onehot_priority.v b/vendor/Hazard3/hdl/arith/hazard3_onehot_priority.v new file mode 100644 index 0000000..238fac2 --- /dev/null +++ b/vendor/Hazard3/hdl/arith/hazard3_onehot_priority.v @@ -0,0 +1,33 @@ +/*****************************************************************************\ +| Copyright (C) 2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// req: bitmap +// idx: bitmap with all bits clear except the least- (HIGHEST_WINS=0) or +// most- (HIGHEST_WINS=1) significant set bit in req. + +`default_nettype none + +module hazard3_onehot_priority #( + parameter W_REQ = 16, + parameter HIGHEST_WINS = 0 +) ( + input wire [W_REQ-1:0] req, + output reg [W_REQ-1:0] gnt +); + +always @ (*) begin: select + integer i; + for (i = 0; i < W_REQ; i = i + 1) begin + gnt[i] = req[i] && ~|(req & ( + HIGHEST_WINS ? ~({W_REQ{1'b1}} >> (W_REQ - 1 - i)) : ~({W_REQ{1'b1}} << i) + )); + end +end + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/arith/hazard3_onehot_priority_dynamic.v b/vendor/Hazard3/hdl/arith/hazard3_onehot_priority_dynamic.v new file mode 100644 index 0000000..123cdf8 --- /dev/null +++ b/vendor/Hazard3/hdl/arith/hazard3_onehot_priority_dynamic.v @@ -0,0 +1,77 @@ +/*****************************************************************************\ +| Copyright (C) 2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// req: bitmap of requests +// priority: packed array of dynamic priority level of each request +// gnt: one-hot bitmap with the highest-priority request. + +`default_nettype none + +module hazard3_onehot_priority_dynamic #( + parameter W_REQ = 8, + parameter N_PRIORITIES = 2, + parameter PRIORITY_HIGHEST_WINS = 1, // If 1, numerically highest level has greatest priority. + // Otherwise, numerically lowest wins. + parameter TIEBREAK_HIGHEST_WINS = 0, // If 1, highest-numbered request at the highest priority + // level wins the tiebreak. Otherwise, lowest-numbered. + // Do not modify: + parameter W_PRIORITY = $clog2(N_PRIORITIES) +) ( + input wire [W_REQ*W_PRIORITY-1:0] pri, + input wire [W_REQ-1:0] req, + output wire [W_REQ-1:0] gnt +); + +// 1. Stratify requests according to their level +reg [W_REQ-1:0] req_stratified [0:N_PRIORITIES-1]; +reg [N_PRIORITIES-1:0] level_has_req; + +always @ (*) begin: stratify + reg signed [31:0] i, j; + for (i = 0; i < N_PRIORITIES; i = i + 1) begin + for (j = 0; j < W_REQ; j = j + 1) begin + req_stratified[i][j] = req[j] && + pri[W_PRIORITY * j +: W_PRIORITY] == i[W_PRIORITY-1:0]; + end + level_has_req[i] = |req_stratified[i]; + end +end + +// 2. Select the highest level with active requests +wire [N_PRIORITIES-1:0] active_layer_sel; + +hazard3_onehot_priority #( + .W_REQ (N_PRIORITIES), + .HIGHEST_WINS (PRIORITY_HIGHEST_WINS) +) prisel_layer ( + .req (level_has_req), + .gnt (active_layer_sel) +); + +// 3. Mask only those requests at this level +reg [W_REQ-1:0] reqs_from_highest_layer; + +always @ (*) begin: mux_reqs_by_layer + integer i; + reqs_from_highest_layer = {W_REQ{1'b0}}; + for (i = 0; i < N_PRIORITIES; i = i + 1) + reqs_from_highest_layer = reqs_from_highest_layer | + (req_stratified[i] & {W_REQ{active_layer_sel[i]}}); +end + +// 4. Do a standard priority select on those requests as a tie break +hazard3_onehot_priority #( + .W_REQ (W_REQ), + .HIGHEST_WINS (TIEBREAK_HIGHEST_WINS) +) prisel_tiebreak ( + .req (reqs_from_highest_layer), + .gnt (gnt) +); + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/arith/hazard3_priority_encode.v b/vendor/Hazard3/hdl/arith/hazard3_priority_encode.v new file mode 100644 index 0000000..172e72a --- /dev/null +++ b/vendor/Hazard3/hdl/arith/hazard3_priority_encode.v @@ -0,0 +1,41 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// req: bitmap +// gnt: index of least set bit (HIGHEST_WINS=0) or most set bit (HIGHEST_WINS=1) + +`default_nettype none + +module hazard3_priority_encode #( + parameter W_REQ = 16, + parameter HIGHEST_WINS = 0, + parameter W_GNT = $clog2(W_REQ) // do not modify +) ( + input wire [W_REQ-1:0] req, + output wire [W_GNT-1:0] gnt +); + +wire [W_REQ-1:0] gnt_onehot; + +hazard3_onehot_priority #( + .W_REQ (W_REQ), + .HIGHEST_WINS (HIGHEST_WINS) +) priority_u ( + .req (req), + .gnt (gnt_onehot) +); + +hazard3_onehot_encode #( + .W_REQ (W_REQ) +) encode_u ( + .req (gnt_onehot), + .gnt (gnt) +); + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/arith/hazard3_shift_barrel.v b/vendor/Hazard3/hdl/arith/hazard3_shift_barrel.v new file mode 100644 index 0000000..9fb51be --- /dev/null +++ b/vendor/Hazard3/hdl/arith/hazard3_shift_barrel.v @@ -0,0 +1,66 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// Implement the three shifts (left logical, right logical, right arithmetic) +// using a single log-type barrel shifter. Around 240 LUTs for 32 bits. +// (7 layers of 32 2-input muxes, some extra LUTs and LUT inputs used for arith) + +`default_nettype none + +module hazard3_shift_barrel #( +`include "hazard3_config.vh" +, +`include "hazard3_width_const.vh" +) ( + input wire [W_DATA-1:0] din, + input wire [W_SHAMT-1:0] shamt, + input wire right_nleft, + input wire rotate, + input wire arith, + output reg [W_DATA-1:0] dout +); + +reg [W_DATA-1:0] din_rev; +reg [W_DATA-1:0] shift_accum; +reg sext; // haha + +always @ (*) begin: shift + integer i; + + for (i = 0; i < W_DATA; i = i + 1) + din_rev[i] = right_nleft ? din[W_DATA - 1 - i] : din[i]; + + sext = arith && din_rev[0]; + + shift_accum = din_rev; + for (i = 0; i < W_SHAMT; i = i + 1) begin + if (shamt[i]) begin + shift_accum = (shift_accum << (1 << i)) | + ({W_DATA{sext}} & ~({W_DATA{1'b1}} << (1 << i))) | + ({W_DATA{rotate && |EXTENSION_ZBB}} & (shift_accum >> (W_DATA - (1 << i)))); + end + end + + for (i = 0; i < W_DATA; i = i + 1) + dout[i] = right_nleft ? shift_accum[W_DATA - 1 - i] : shift_accum[i]; +end + +`ifdef HAZARD3_ASSERTIONS +always @ (*) begin + if (right_nleft && arith && !rotate) begin: asr + assert($signed(dout) == $signed(din) >>> $signed(shamt)); + end else if (right_nleft && !arith && !rotate) begin + assert(dout == din >> shamt); + end else if (!right_nleft && !arith && !rotate) begin + assert(dout == din << shamt); + end +end +`endif + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/arith/muldiv_model.py b/vendor/Hazard3/hdl/arith/muldiv_model.py new file mode 100755 index 0000000..2515786 --- /dev/null +++ b/vendor/Hazard3/hdl/arith/muldiv_model.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +# Quick reference model for sequential unsigned multiply/divide/modulo + +def div_step(w, accum, divisor): + sub_tmp = accum - (divisor << (w - 1)) + underflow = sub_tmp < 0 + if not underflow: + accum = sub_tmp + accum = (accum << 1) | (not underflow) + return accum + +def divmod(w, dividend, divisor, debug=True): + accum = dividend + for i in range(w): + accum_prev = accum + accum = div_step(w, accum, divisor) + if debug: + print("Step {:02d}: accum {:0{}x} -> {:0{}x}".format( + i, accum_prev, int(w / 2), accum, int(w / 2))) + return (accum >> w, accum & ((1 << w) - 1)) + +def mul_step(w, accum, multiplicand): + add_en = accum & 1 + accum = accum >> 1 + if add_en: + accum += (multiplicand << (w - 1)) + return accum + +def mul(w, multiplicand, multiplier, debug=True): + accum = multiplier + for i in range(w): + accum_prev = accum + accum = mul_step(w, accum, multiplicand) + if debug: + print("Step {:02d}: accum {:0{}x} -> {:0{}x}".format( + i, accum_prev, int(w / 2), accum, int(w / 2))) + return (accum >> w, accum & ((1 << w) - 1)) + +def divtest(w=4): + for i in range(2 ** w): + for j in range(1, 2 ** w): + gatemod, gatediv = divmod(w, i, j, debug=False) + goldmod, golddiv = (i % j, i // j) + print("{:02d} % {:02d} = {:02d} (gold {:02d}); ./. = {:02d} (gold {:02d})" + .format(i, j, gatemod, goldmod, gatediv, golddiv)) + assert(gatemod == goldmod) + assert(gatediv == golddiv) + +def multest(w=4): + for i in range(2 ** w): + for j in range(2 ** w): + gateh, gatel = mul(w, i, j, debug=False) + gold = i * j + goldl, goldh = (gold & ((1 << w) - 1), gold >> w) + print("{:02d} * {:02d} = ({:02d} (gold {:02d}), {:02d} (gold {:02d})" + .format(i, j, gateh, goldh, gatel, goldl)) + assert(gatel == goldl) + assert(gateh == goldh) + +if __name__ == "__main__": + print("Test division:") + divtest() + print("Test multiplication:") + multest() diff --git a/vendor/Hazard3/hdl/debug/cdc/hazard3_apb_async_bridge.v b/vendor/Hazard3/hdl/debug/cdc/hazard3_apb_async_bridge.v new file mode 100644 index 0000000..a0236f9 --- /dev/null +++ b/vendor/Hazard3/hdl/debug/cdc/hazard3_apb_async_bridge.v @@ -0,0 +1,200 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// APB-to-APB asynchronous bridge for connecting DTM to DM, in case DTM is in +// a different clock domain (e.g. running directly from crystal to get a +// fixed baud reference) +// +// Note this module depends on the hazard3_sync_1bit module (a flop-chain +// synchroniser) which should be reimplemented for your FPGA/process. + +`ifndef HAZARD3_REG_KEEP_ATTRIBUTE +`define HAZARD3_REG_KEEP_ATTRIBUTE (* keep = 1'b1 *) +`endif + +`default_nettype none + +module hazard3_apb_async_bridge #( + parameter W_ADDR = 8, + parameter W_DATA = 32, + parameter N_SYNC_STAGES = 2 +) ( + // Resets assumed to be synchronised externally + input wire clk_src, + input wire rst_n_src, + + input wire clk_dst, + input wire rst_n_dst, + + // APB port from Transport Module + input wire src_psel, + input wire src_penable, + input wire src_pwrite, + input wire [W_ADDR-1:0] src_paddr, + input wire [W_DATA-1:0] src_pwdata, + output wire [W_DATA-1:0] src_prdata, + output wire src_pready, + output wire src_pslverr, + + // APB port to Debug Module + output wire dst_psel, + output wire dst_penable, + output wire dst_pwrite, + output wire [W_ADDR-1:0] dst_paddr, + output wire [W_DATA-1:0] dst_pwdata, + input wire [W_DATA-1:0] dst_prdata, + input wire dst_pready, + input wire dst_pslverr +); + +// ---------------------------------------------------------------------------- +// Clock-crossing registers + +// We're using a modified req/ack handshake: +// +// - Initially both req and ack are low +// - src asserts req high +// - dst responds with ack high and begins transfer +// - src deasserts req once it sees ack high +// - dst deasserts ack once: +// - transfer is complete *and* +// - dst sees req deasserted +// - Once src sees ack low, a new transfer can begin. +// +// A NRZI toggle handshake might be more appropriate, but can cause spurious +// bus accesses when only one side of the link is reset. + +`HAZARD3_REG_KEEP_ATTRIBUTE reg src_req; +wire dst_req; + +`HAZARD3_REG_KEEP_ATTRIBUTE reg dst_ack; +wire src_ack; + +// Note the launch registers are not resettable. We maintain setup/hold on +// launch-to-capture paths thanks to the req/ack handshake. A stray reset +// could violate this. +// +// The req/ack logic itself can be reset safely because the receiving domain +// is protected from metastability by a 2FF synchroniser. + +`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_ADDR + W_DATA + 1 -1:0] src_paddr_pwdata_pwrite; // launch +`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_ADDR + W_DATA + 1 -1:0] dst_paddr_pwdata_pwrite; // capture + +`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_DATA + 1 -1:0] dst_prdata_pslverr; // launch +`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_DATA + 1 -1:0] src_prdata_pslverr; // capture + +hazard3_sync_1bit #( + .N_STAGES (N_SYNC_STAGES) +) sync_req ( + .clk (clk_dst), + .rst_n (rst_n_dst), + .i (src_req), + .o (dst_req) +); + +hazard3_sync_1bit #( + .N_STAGES (N_SYNC_STAGES) +) sync_ack ( + .clk (clk_src), + .rst_n (rst_n_src), + .i (dst_ack), + .o (src_ack) +); + +// ---------------------------------------------------------------------------- +// src state machine + +reg src_waiting_for_downstream; +reg src_pready_r; + +always @ (posedge clk_src or negedge rst_n_src) begin + if (!rst_n_src) begin + src_req <= 1'b0; + src_waiting_for_downstream <= 1'b0; + src_prdata_pslverr <= {W_DATA + 1{1'b0}}; + src_pready_r <= 1'b1; + end else if (src_waiting_for_downstream) begin + if (src_req && src_ack) begin + // Request was acknowledged, so deassert. + src_req <= 1'b0; + end else if (!(src_req || src_ack)) begin + // Downstream transfer has finished, data is valid. + src_pready_r <= 1'b1; + src_waiting_for_downstream <= 1'b0; + // Note this assignment is cross-domain (but data has been stable + // for duration of ack synchronisation delay): + src_prdata_pslverr <= dst_prdata_pslverr; + end + end else begin + // paddr, pwdata and pwrite are all valid during the setup phase, and + // APB defines the setup phase to always last one cycle and proceed + // to access phase. So, we can ignore penable, and pready is ignored. + if (src_psel) begin + src_pready_r <= 1'b0; + src_req <= 1'b1; + src_waiting_for_downstream <= 1'b1; + end + end +end + +// Bus request launch register is not resettable +always @ (posedge clk_src) begin + if (src_psel && !src_waiting_for_downstream) + src_paddr_pwdata_pwrite <= {src_paddr, src_pwdata, src_pwrite}; +end + +assign {src_prdata, src_pslverr} = src_prdata_pslverr; +assign src_pready = src_pready_r; + +// ---------------------------------------------------------------------------- +// dst state machine + +wire dst_bus_finish = dst_penable && dst_pready; +reg dst_psel_r; +reg dst_penable_r; + +always @ (posedge clk_dst or negedge rst_n_dst) begin + if (!rst_n_dst) begin + dst_ack <= 1'b0; + end else if (dst_req) begin + dst_ack <= 1'b1; + end else if (!dst_req && dst_ack && !dst_psel_r) begin + dst_ack <= 1'b0; + end +end + +always @ (posedge clk_dst or negedge rst_n_dst) begin + if (!rst_n_dst) begin + dst_psel_r <= 1'b0; + dst_penable_r <= 1'b0; + dst_paddr_pwdata_pwrite <= {W_ADDR + W_DATA + 1{1'b0}}; + end else if (dst_req && !dst_ack) begin + dst_psel_r <= 1'b1; + // Note this assignment is cross-domain. The src register has been + // stable for the duration of the req sync delay. + dst_paddr_pwdata_pwrite <= src_paddr_pwdata_pwrite; + end else if (dst_psel_r && !dst_penable_r) begin + dst_penable_r <= 1'b1; + end else if (dst_bus_finish) begin + dst_psel_r <= 1'b0; + dst_penable_r <= 1'b0; + end +end + +// Bus response launch register is not resettable +always @ (posedge clk_dst) begin + if (dst_bus_finish) + dst_prdata_pslverr <= {dst_prdata, dst_pslverr}; +end + +assign dst_psel = dst_psel_r; +assign dst_penable = dst_penable_r; +assign {dst_paddr, dst_pwdata, dst_pwrite} = dst_paddr_pwdata_pwrite; + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/debug/cdc/hazard3_reset_sync.v b/vendor/Hazard3/hdl/debug/cdc/hazard3_reset_sync.v new file mode 100644 index 0000000..3dc8c32 --- /dev/null +++ b/vendor/Hazard3/hdl/debug/cdc/hazard3_reset_sync.v @@ -0,0 +1,41 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// The output is asserted asynchronously when the input is asserted, +// but deasserted synchronously when clocked with the input deasserted. +// Input and output are both active-low. +// +// This is a baseline implementation -- you should replace it with cells +// specific to your FPGA/process + +`ifndef HAZARD3_REG_KEEP_ATTRIBUTE +`define HAZARD3_REG_KEEP_ATTRIBUTE (* keep = 1'b1 *) +`endif + +`default_nettype none + +module hazard3_reset_sync #( + parameter N_STAGES = 2 // Should be >= 2 +) ( + input wire clk, + input wire rst_n_in, + output wire rst_n_out +); + +`HAZARD3_REG_KEEP_ATTRIBUTE reg [N_STAGES-1:0] delay; + +always @ (posedge clk or negedge rst_n_in) + if (!rst_n_in) + delay <= {N_STAGES{1'b0}}; + else + delay <= {delay[N_STAGES-2:0], 1'b1}; + +assign rst_n_out = delay[N_STAGES-1]; + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/debug/cdc/hazard3_sync_1bit.v b/vendor/Hazard3/hdl/debug/cdc/hazard3_sync_1bit.v new file mode 100644 index 0000000..1e6af4d --- /dev/null +++ b/vendor/Hazard3/hdl/debug/cdc/hazard3_sync_1bit.v @@ -0,0 +1,39 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// A 2FF synchronizer to mitigate metastabilities. This is a baseline +// implementation -- you should replace it with cells specific to your +// FPGA/process + +`ifndef HAZARD3_REG_KEEP_ATTRIBUTE +`define HAZARD3_REG_KEEP_ATTRIBUTE (* keep = 1'b1 *) (* async_reg *) +`endif + +`default_nettype none + +module hazard3_sync_1bit #( + parameter N_STAGES = 2 // Should be >=2 +) ( + input wire clk, + input wire rst_n, + input wire i, + output wire o +); + +`HAZARD3_REG_KEEP_ATTRIBUTE reg [N_STAGES-1:0] sync_flops; + +always @ (posedge clk or negedge rst_n) + if (!rst_n) + sync_flops <= {N_STAGES{1'b0}}; + else + sync_flops <= {sync_flops[N_STAGES-2:0], i}; + +assign o = sync_flops[N_STAGES-1]; + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/debug/dm/hazard3_dm.f b/vendor/Hazard3/hdl/debug/dm/hazard3_dm.f new file mode 100644 index 0000000..155edad --- /dev/null +++ b/vendor/Hazard3/hdl/debug/dm/hazard3_dm.f @@ -0,0 +1 @@ +file hazard3_dm.v diff --git a/vendor/Hazard3/hdl/debug/dm/hazard3_dm.v b/vendor/Hazard3/hdl/debug/dm/hazard3_dm.v new file mode 100644 index 0000000..713c38f --- /dev/null +++ b/vendor/Hazard3/hdl/debug/dm/hazard3_dm.v @@ -0,0 +1,904 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// RISC-V Debug Module for Hazard3. Supports up to 32 cores (1 hart per core). + +`default_nettype none + +module hazard3_dm #( + // Where there are multiple harts per DM, the least-indexed hart is the + // least-significant on each concatenated hart access bus. + parameter N_HARTS = 1, + // Where there are multiple DMs, the address of each DM should be a + // multiple of 'h200, so that bits[8:2] decode correctly. + parameter NEXT_DM_ADDR = 32'h0000_0000, + // Implement support for system bus access: + parameter HAVE_SBA = 0, + + // Do not modify: + parameter XLEN = 32, // Do not modify + parameter W_HARTSEL = N_HARTS > 1 ? $clog2(N_HARTS) : 1 // Do not modify +) ( + // DM is assumed to be in same clock domain as core; clock crossing + // (if any) is inside DTM, or between DTM and DM. + input wire clk, + input wire rst_n, + + // APB access from Debug Transport Module + input wire dmi_psel, + input wire dmi_penable, + input wire dmi_pwrite, + input wire [8:0] dmi_paddr, + input wire [31:0] dmi_pwdata, + output reg [31:0] dmi_prdata, + output wire dmi_pready, + output wire dmi_pslverr, + + // Reset request/acknowledge. "req" is a pulse >= 1 cycle wide. "done" is + // level-sensitive, goes high once component is out of reset. + // + // The "sys" reset (ndmreset) is conventionally everything apart from DM + + // DTM, but, as per section 3.2 in 0.13.2 debug spec: "Exactly what is + // affected by this reset is implementation dependent, as long as it is + // possible to debug programs from the first instruction executed." So + // this could simply be an all-hart reset. + output wire sys_reset_req, + input wire sys_reset_done, + output wire [N_HARTS-1:0] hart_reset_req, + input wire [N_HARTS-1:0] hart_reset_done, + + // Hart run/halt control + output wire [N_HARTS-1:0] hart_req_halt, + output wire [N_HARTS-1:0] hart_req_halt_on_reset, + output wire [N_HARTS-1:0] hart_req_resume, + input wire [N_HARTS-1:0] hart_halted, + input wire [N_HARTS-1:0] hart_running, + + // Hart access to data0 CSR (assumed to be core-internal but per-hart) + output wire [N_HARTS*XLEN-1:0] hart_data0_rdata, + input wire [N_HARTS*XLEN-1:0] hart_data0_wdata, + input wire [N_HARTS-1:0] hart_data0_wen, + + // Hart instruction injection + output wire [N_HARTS*32-1:0] hart_instr_data, + output reg [N_HARTS-1:0] hart_instr_data_vld, + input wire [N_HARTS-1:0] hart_instr_data_rdy, + input wire [N_HARTS-1:0] hart_instr_caught_exception, + input wire [N_HARTS-1:0] hart_instr_caught_ebreak, + + // System bus access (optional) -- can be hooked up to the standalone AHB + // shim (hazard3_sbus_to_ahb.v) or the SBA input port on the processor + // wrapper, which muxes SBA into the processor's load/store bus access + // port. SBA does not increase debugger bus throughput, but supports + // minimally intrusive debug bus access for e.g. Segger RTT. + output wire [31:0] sbus_addr, + output wire sbus_write, + output wire [1:0] sbus_size, + output wire sbus_vld, + input wire sbus_rdy, + input wire sbus_err, + output wire [31:0] sbus_wdata, + input wire [31:0] sbus_rdata +); + +wire dmi_write = dmi_psel && dmi_penable && dmi_pready && dmi_pwrite; +wire dmi_read = dmi_psel && dmi_penable && dmi_pready && !dmi_pwrite; +assign dmi_pready = 1'b1; +assign dmi_pslverr = 1'b0; + +// Program buffer is fixed at 2 words plus impebreak. The main thing we care +// about is support for efficient memory block transfers using abstractauto; +// in this case 2 words + impebreak is sufficient for RV32I, and 1 word + +// impebreak is sufficient for RV32IC. +localparam PROGBUF_SIZE = 2; + +// ---------------------------------------------------------------------------- +// Address constants + +localparam ADDR_DATA0 = 7'h04; +// Other data registers not present. +localparam ADDR_DMCONTROL = 7'h10; +localparam ADDR_DMSTATUS = 7'h11; +localparam ADDR_HARTINFO = 7'h12; +localparam ADDR_HALTSUM1 = 7'h13; +localparam ADDR_HALTSUM0 = 7'h40; +// No HALTSUM2+ registers (we don't support >32 harts anyway) +localparam ADDR_HAWINDOWSEL = 7'h14; +localparam ADDR_HAWINDOW = 7'h15; +localparam ADDR_ABSTRACTCS = 7'h16; +localparam ADDR_COMMAND = 7'h17; +localparam ADDR_ABSTRACTAUTO = 7'h18; +localparam ADDR_CONFSTRPTR0 = 7'h19; +localparam ADDR_CONFSTRPTR1 = 7'h1a; +localparam ADDR_CONFSTRPTR2 = 7'h1b; +localparam ADDR_CONFSTRPTR3 = 7'h1c; +localparam ADDR_NEXTDM = 7'h1d; +localparam ADDR_PROGBUF0 = 7'h20; +localparam ADDR_PROGBUF1 = 7'h21; +// No authentication +localparam ADDR_SBCS = 7'h38; +localparam ADDR_SBADDRESS0 = 7'h39; +localparam ADDR_SBDATA0 = 7'h3c; + +// APB is byte-addressed, DM registers are word-addressed. +wire [6:0] dmi_regaddr = dmi_paddr[8:2]; + +// ---------------------------------------------------------------------------- +// Hart selection + +reg dmactive; + +// Some fiddliness to make sure we get a single-wide zero-valued signal when +// N_HARTS == 1 (so we can use this for indexing of per-hart signals) +reg [W_HARTSEL-1:0] hartsel; +wire [W_HARTSEL-1:0] hartsel_next; + +generate +if (N_HARTS > 1) begin: has_hartsel + + // Only the lower 10 bits of hartsel are supported + assign hartsel_next = dmi_write && dmi_regaddr == ADDR_DMCONTROL ? + dmi_pwdata[16 +: W_HARTSEL] : hartsel; + +end else begin: has_no_hartsel + + assign hartsel_next = 1'b0; + +end +endgenerate + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + hartsel <= {W_HARTSEL{1'b0}}; + end else if (!dmactive) begin + hartsel <= {W_HARTSEL{1'b0}}; + end else begin + hartsel <= hartsel_next; + end +end + +// Also implement the hart array mask if there is more than one hart. +reg [N_HARTS-1:0] hart_array_mask; +reg hasel; +wire [N_HARTS-1:0] hart_array_mask_next; +wire hasel_next; + +generate +if (N_HARTS > 1) begin: has_array_mask + + assign hart_array_mask_next = dmi_write && dmi_regaddr == ADDR_HAWINDOW ? + dmi_pwdata[N_HARTS-1:0] : hart_array_mask; + assign hasel_next = dmi_write && dmi_regaddr == ADDR_DMCONTROL ? + dmi_pwdata[26] : hasel; + +end else begin: has_no_array_mask + + assign hart_array_mask_next = 1'b0; + assign hasel_next = 1'b0; + +end +endgenerate + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + hart_array_mask <= {N_HARTS{1'b0}}; + hasel <= 1'b0; + end else if (!dmactive) begin + hart_array_mask <= {N_HARTS{1'b0}}; + hasel <= 1'b0; + end else begin + hart_array_mask <= hart_array_mask_next; + hasel <= hasel_next; + end +end + +// ---------------------------------------------------------------------------- +// Run/halt/reset control + +// Normal read/write fields for dmcontrol (note some of these are per-hart +// fields that get rotated into dmcontrol based on the current/next hartsel). +reg [N_HARTS-1:0] dmcontrol_haltreq; +reg [N_HARTS-1:0] dmcontrol_hartreset; +reg [N_HARTS-1:0] dmcontrol_resethaltreq; +reg dmcontrol_ndmreset; + +wire [N_HARTS-1:0] dmcontrol_op_mask; + +generate +if (N_HARTS > 1) begin: dmcontrol_multiple_harts + + // Selection is the hart selected by hartsel, *plus* the hart array mask + // if hasel is set. Note we don't need to use the "next" version of + // hart_array_mask since it can't change simultaneously with dmcontrol. + assign dmcontrol_op_mask = + (hartsel_next >= N_HARTS ? {N_HARTS{1'b0}} : {{N_HARTS-1{1'b0}}, 1'b1} << hartsel_next) + | ({N_HARTS{hasel_next}} & hart_array_mask); + +end else begin: dmcontrol_single_hart + + assign dmcontrol_op_mask = 1'b1; + +end +endgenerate + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + dmactive <= 1'b0; + dmcontrol_ndmreset <= 1'b0; + dmcontrol_haltreq <= {N_HARTS{1'b0}}; + dmcontrol_hartreset <= {N_HARTS{1'b0}}; + dmcontrol_resethaltreq <= {N_HARTS{1'b0}}; + end else if (!dmactive) begin + // Only dmactive is writable when !dmactive + if (dmi_write && dmi_regaddr == ADDR_DMCONTROL) + dmactive <= dmi_pwdata[0]; + dmcontrol_ndmreset <= 1'b0; + dmcontrol_haltreq <= {N_HARTS{1'b0}}; + dmcontrol_hartreset <= {N_HARTS{1'b0}}; + dmcontrol_resethaltreq <= {N_HARTS{1'b0}}; + end else if (dmi_write && dmi_regaddr == ADDR_DMCONTROL) begin + dmactive <= dmi_pwdata[0]; + dmcontrol_ndmreset <= dmi_pwdata[1]; + + dmcontrol_haltreq <= (dmcontrol_haltreq & ~dmcontrol_op_mask) | + ({N_HARTS{dmi_pwdata[31]}} & dmcontrol_op_mask); + + dmcontrol_hartreset <= (dmcontrol_hartreset & ~dmcontrol_op_mask) | + ({N_HARTS{dmi_pwdata[29]}} & dmcontrol_op_mask); + + dmcontrol_resethaltreq <= (dmcontrol_resethaltreq + & ~({N_HARTS{dmi_pwdata[2]}} & dmcontrol_op_mask)) + | ({N_HARTS{dmi_pwdata[3]}} & dmcontrol_op_mask); + end +end + +assign sys_reset_req = dmcontrol_ndmreset; +assign hart_reset_req = dmcontrol_hartreset; +assign hart_req_halt = dmcontrol_haltreq; +assign hart_req_halt_on_reset = dmcontrol_resethaltreq; + +reg [N_HARTS-1:0] hart_reset_done_prev; +reg [N_HARTS-1:0] dmstatus_havereset; +wire [N_HARTS-1:0] hart_available = hart_reset_done & {N_HARTS{sys_reset_done}}; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + hart_reset_done_prev <= {N_HARTS{1'b0}}; + end else begin + hart_reset_done_prev <= hart_reset_done; + end +end + +wire dmcontrol_ackhavereset = dmi_write && dmi_regaddr == ADDR_DMCONTROL && dmi_pwdata[28]; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + dmstatus_havereset <= {N_HARTS{1'b0}}; + end else if (!dmactive) begin + dmstatus_havereset <= {N_HARTS{1'b0}}; + end else begin + dmstatus_havereset <= (dmstatus_havereset | (hart_reset_done & ~hart_reset_done_prev)) + & ~({N_HARTS{dmcontrol_ackhavereset}} & dmcontrol_op_mask); + end +end + +reg [N_HARTS-1:0] dmstatus_resumeack; +reg [N_HARTS-1:0] dmcontrol_resumereq_sticky; + +// Note: we are required to ignore resumereq when haltreq is also set, as per +// spec (odd since the host is forbidden from writing both at once anyway). +// The wording is odd, it refers only to `haltreq` which is specifically the +// write-only `dmcontrol` field, not the underlying halt request state bits. +wire dmcontrol_resumereq = dmi_write && dmi_regaddr == ADDR_DMCONTROL && + dmi_pwdata[30] && !dmi_pwdata[31]; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + dmstatus_resumeack <= {N_HARTS{1'b0}}; + dmcontrol_resumereq_sticky <= {N_HARTS{1'b0}}; + end else if (!dmactive) begin + dmstatus_resumeack <= {N_HARTS{1'b0}}; + dmcontrol_resumereq_sticky <= {N_HARTS{1'b0}}; + end else begin + dmstatus_resumeack <= (dmstatus_resumeack + | (dmcontrol_resumereq_sticky & hart_running & hart_available)) + & ~({N_HARTS{dmcontrol_resumereq}} & dmcontrol_op_mask); + + dmcontrol_resumereq_sticky <= (dmcontrol_resumereq_sticky + & ~(hart_running & hart_available)) + | ({N_HARTS{dmcontrol_resumereq}} & dmcontrol_op_mask); + end +end + +assign hart_req_resume = dmcontrol_resumereq_sticky; + +// ---------------------------------------------------------------------------- +// System bus access + +reg [31:0] sbaddress; +reg [31:0] sbdata; + +// Update logic for address/data registers: + +reg sbbusy; +reg sbautoincrement; +reg [2:0] sbaccess; // Size of the transfer + +wire sbdata_write_blocked; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + sbaddress <= {32{1'b0}}; + sbdata <= {32{1'b0}}; + end else if (!dmactive) begin + sbaddress <= {32{1'b0}}; + sbdata <= {32{1'b0}}; + end else if (HAVE_SBA) begin + if (dmi_write && dmi_regaddr == ADDR_SBDATA0 && !sbdata_write_blocked) begin + // Note sbbusyerror and sberror block writes to sbdata0, as the + // write is required to have no side effects when they are set. + sbdata <= dmi_pwdata; + end else if (sbus_vld && sbus_rdy && !sbus_write && !sbus_err) begin + // Make sure the lower byte lanes see appropriately shifted data as + // long as the transfer is naturally aligned + sbdata <= sbaddress[1:0] == 2'b01 ? {sbus_rdata[31:8], sbus_rdata[15:8]} : + sbaddress[1:0] == 2'b10 ? {sbus_rdata[31:16], sbus_rdata[31:16]} : + sbaddress[1:0] == 2'b11 ? {sbus_rdata[31:8], sbus_rdata[31:24]} : sbus_rdata; + end + if (dmi_write && dmi_regaddr == ADDR_SBADDRESS0 && !sbbusy) begin + // Note sbaddress can't be written when busy, but + // sberror/sbbusyerror do not prevent writes. + sbaddress <= dmi_pwdata; + end else if (sbus_vld && sbus_rdy && !sbus_err && sbautoincrement) begin + // Note: address increments only following a successful transfer. + // Spec 0.13.2 weirdly implies address should increment following + // a sbdata0 read with sbautoincrement=1 and sbreadondata=0, but + // this seems to be a typo, fixed in later versions. + sbaddress <= sbaddress + ( + sbaccess[1:0] == 2'b00 ? 32'd1 : + sbaccess[1:0] == 2'b01 ? 32'd2 : 32'd4 + ); + end + end +end + +// Control logic: + +reg sbbusyerror; +reg sbreadonaddr; +reg sbreadondata; +reg [2:0] sberror; +reg sb_current_is_write; + +localparam SBERROR_OK = 3'h0; +localparam SBERROR_BADADDR = 3'h2; +localparam SBERROR_BADALIGN = 3'h3; +localparam SBERROR_BADSIZE = 3'h4; + +assign sbdata_write_blocked = sbbusy || sbbusyerror || |sberror; + +// Notes on behaviour of sbbusyerror: the sbbusyerror description says: +// +// "Set when the debugger attempts to read data while a read is in progress, +// or when the debugger initiates a new access while one is already in +// progress (while sbbusy is set)." +// +// However, sbaddress0 description says: +// +// "When the system bus master is busy, writes to this register will set +// sbbusyerror and don’t do anything else." +// +// ...not conditioned on sbreadonaddr. Likewise the sbdata0 description says: +// +// "If the bus master is busy then accesses set sbbusyerror, and don’t do +// anything else." +// +// ...not conditioned on sbreadondata. We are going to take the union of all +// the cases where the spec says we should raise an error: + +wire sb_access_illegal_when_busy = + dmi_regaddr == ADDR_SBDATA0 && (dmi_read || dmi_write) || + dmi_regaddr == ADDR_SBADDRESS0 && dmi_write; + +wire sb_want_start_write = dmi_write && dmi_regaddr == ADDR_SBDATA0; + +wire sb_want_start_read = + (sbreadonaddr && dmi_write && dmi_regaddr == ADDR_SBADDRESS0) || + (sbreadondata && dmi_read && dmi_regaddr == ADDR_SBDATA0); + +wire [1:0] sb_next_align = sbreadonaddr && dmi_write && dmi_regaddr == ADDR_SBADDRESS0 ? + dmi_pwdata[1:0] : sbaddress[1:0]; + +wire sb_badalign = + (sbaccess == 3'h1 && sb_next_align[0]) || + (sbaccess == 3'h2 && |sb_next_align[1:0]); + +wire sb_badsize = sbaccess > 3'h2; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + sbbusy <= 1'b0; + sbbusyerror <= 1'b0; + sbreadonaddr <= 1'b0; + sbreadondata <= 1'b0; + sbaccess <= 3'h0; + sbautoincrement <= 1'b0; + sberror <= 3'h0; + sb_current_is_write <= 1'b0; + end else if (!dmactive) begin + sbbusy <= 1'b0; + sbbusyerror <= 1'b0; + sbreadonaddr <= 1'b0; + sbreadondata <= 1'b0; + sbaccess <= 3'h0; + sbautoincrement <= 1'b0; + sberror <= 3'h0; + sb_current_is_write <= 1'b0; + end else if (HAVE_SBA) begin + if (dmi_write && dmi_regaddr == ADDR_SBCS) begin + // Assume a transfer is not in progress when written (per spec) + sbbusyerror <= sbbusyerror && !dmi_pwdata[22]; + sbreadonaddr <= dmi_pwdata[20]; + sbaccess <= dmi_pwdata[19:17]; + sbautoincrement <= dmi_pwdata[16]; + sbreadondata <= dmi_pwdata[15]; + sberror <= sberror & ~dmi_pwdata[14:12]; + end + if (sbbusy) begin + if (sb_access_illegal_when_busy) begin + sbbusyerror <= 1'b1; + end + if (sbus_vld && sbus_rdy) begin + sbbusy <= 1'b0; + if (sbus_err) begin + sberror <= SBERROR_BADADDR; + end + end + end else if ((sb_want_start_read || sb_want_start_write) && ~|sberror && !sbbusyerror) begin + if (sb_badsize) begin + sberror <= SBERROR_BADSIZE; + end else if (sb_badalign) begin + sberror <= SBERROR_BADALIGN; + end else begin + sbbusy <= 1'b1; + sb_current_is_write <= sb_want_start_write; + end + end + end +end + +assign sbus_addr = sbaddress; +assign sbus_write = sb_current_is_write; +assign sbus_size = sbaccess[1:0]; +assign sbus_vld = sbbusy; + +// Replicate byte lanes to handle naturally-aligned cases. +assign sbus_wdata = sbaccess[1:0] == 2'b00 ? {4{sbdata[7:0]}} : + sbaccess[1:0] == 2'b01 ? {2{sbdata[15:0]}} : sbdata; + +// ---------------------------------------------------------------------------- +// Abstract command data registers + +wire abstractcs_busy; + +// The same data0 register is aliased as a CSR on all harts connected to this +// DM. Cores may read data0 as a CSR when in debug mode, and may write it when: +// +// - That core is in debug mode, and... +// - We are currently executing an abstract command on that core +// +// The DM can also read/write data0 at all times. + +reg [XLEN-1:0] abstract_data0; + +assign hart_data0_rdata = {N_HARTS{abstract_data0}}; + +always @ (posedge clk or negedge rst_n) begin: update_hart_data0 + reg signed [31:0] i; + if (!rst_n) begin + abstract_data0 <= {XLEN{1'b0}}; + end else if (!dmactive) begin + abstract_data0 <= {XLEN{1'b0}}; + end else if (dmi_write && dmi_regaddr == ADDR_DATA0) begin + abstract_data0 <= dmi_pwdata; + end else begin + for (i = 0; i < N_HARTS; i = i + 1) begin + if (hartsel == i[W_HARTSEL-1:0] && hart_data0_wen[i] && hart_halted[i] && abstractcs_busy) + abstract_data0 <= hart_data0_wdata[i * XLEN +: XLEN]; + end + end +end + +reg [XLEN-1:0] progbuf0; +reg [XLEN-1:0] progbuf1; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + progbuf0 <= {XLEN{1'b0}}; + progbuf1 <= {XLEN{1'b0}}; + end else if (!dmactive) begin + progbuf0 <= {XLEN{1'b0}}; + progbuf1 <= {XLEN{1'b0}}; + end else if (dmi_write && !abstractcs_busy) begin + if (dmi_regaddr == ADDR_PROGBUF0) + progbuf0 <= dmi_pwdata; + if (dmi_regaddr == ADDR_PROGBUF1) + progbuf1 <= dmi_pwdata; + end +end + +reg abstractauto_autoexecdata; +reg [1:0] abstractauto_autoexecprogbuf; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + abstractauto_autoexecdata <= 1'b0; + abstractauto_autoexecprogbuf <= 2'b00; + end else if (!dmactive) begin + abstractauto_autoexecdata <= 1'b0; + abstractauto_autoexecprogbuf <= 2'b00; + end else if (dmi_write && dmi_regaddr == ADDR_ABSTRACTAUTO) begin + abstractauto_autoexecdata <= dmi_pwdata[0]; + abstractauto_autoexecprogbuf <= dmi_pwdata[17:16]; + end +end + +// ---------------------------------------------------------------------------- +// Abstract command state machine + +localparam W_STATE = 4; +localparam S_IDLE = 4'd0; + +localparam S_ISSUE_REGREAD = 4'd1; +localparam S_ISSUE_REGWRITE = 4'd2; +localparam S_ISSUE_REGEBREAK = 4'd3; +localparam S_WAIT_REGEBREAK = 4'd4; + +localparam S_ISSUE_PROGBUF0 = 4'd5; +localparam S_ISSUE_PROGBUF1 = 4'd6; +localparam S_ISSUE_IMPEBREAK = 4'd7; +localparam S_WAIT_IMPEBREAK = 4'd8; + +localparam CMDERR_OK = 3'h0; +localparam CMDERR_BUSY = 3'h1; +localparam CMDERR_UNSUPPORTED = 3'h2; +localparam CMDERR_EXCEPTION = 3'h3; +localparam CMDERR_HALTRESUME = 3'h4; + +reg [2:0] abstractcs_cmderr; +reg [2:0] abstractcs_cmderr_nxt; +reg [W_STATE-1:0] acmd_state; +reg [W_STATE-1:0] acmd_state_nxt; + +assign abstractcs_busy = acmd_state != S_IDLE; + +wire start_abstract_cmd = abstractcs_cmderr == CMDERR_OK && !abstractcs_busy && ( + (dmi_write && dmi_regaddr == ADDR_COMMAND) || + ((dmi_write || dmi_read) && abstractauto_autoexecdata && dmi_regaddr == ADDR_DATA0) || + ((dmi_write || dmi_read) && abstractauto_autoexecprogbuf[0] && dmi_regaddr == ADDR_PROGBUF0) || + ((dmi_write || dmi_read) && abstractauto_autoexecprogbuf[1] && dmi_regaddr == ADDR_PROGBUF1) +); + +wire dmi_access_illegal_when_busy = + (dmi_write && ( + dmi_regaddr == ADDR_ABSTRACTCS || dmi_regaddr == ADDR_COMMAND || dmi_regaddr == ADDR_ABSTRACTAUTO || + dmi_regaddr == ADDR_DATA0 || dmi_regaddr == ADDR_PROGBUF0 || dmi_regaddr == ADDR_PROGBUF1)) || + (dmi_read && ( + dmi_regaddr == ADDR_DATA0 || dmi_regaddr == ADDR_PROGBUF0 || dmi_regaddr == ADDR_PROGBUF1)); + +// Decode what acmd may be triggered on this cycle, and whether it is +// supported -- command source may be a registered version of most recent +// command (if abstractauto is used) or a fresh command off the bus. We don't +// register the entire write data; repeats of unsupported commands are +// detected by just registering that the last written command was +// unsupported. + +wire acmd_new = dmi_write && dmi_regaddr == ADDR_COMMAND; + +wire acmd_new_postexec = dmi_pwdata[18]; +wire acmd_new_transfer = dmi_pwdata[17]; +wire acmd_new_write = dmi_pwdata[16]; +wire [4:0] acmd_new_regno = dmi_pwdata[4:0]; + +// Note: regno and aarsize are permitted to have otherwise-invalid values if +// the transfer flag is not set. +wire acmd_new_unsupported = + (dmi_pwdata[31:24] != 8'h00 ) || // Only Access Register command supported + (dmi_pwdata[22:20] != 3'h2 && acmd_new_transfer) || // Must be 32 bits in size + (dmi_pwdata[19] ) || // aarpostincrement not supported + (dmi_pwdata[15:12] != 4'h1 && acmd_new_transfer) || // Only core register access supported + (dmi_pwdata[11:5] != 7'h0 && acmd_new_transfer); // Only GPRs supported + +reg acmd_prev_postexec; +reg acmd_prev_transfer; +reg acmd_prev_write; +reg [4:0] acmd_prev_regno; +reg acmd_prev_unsupported; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + acmd_prev_postexec <= 1'b0; + acmd_prev_transfer <= 1'b0; + acmd_prev_write <= 1'b0; + acmd_prev_regno <= 5'h0; + acmd_prev_unsupported <= 1'b1; + end else if (!dmactive) begin + acmd_prev_postexec <= 1'b0; + acmd_prev_transfer <= 1'b0; + acmd_prev_write <= 1'b0; + acmd_prev_regno <= 5'h0; + acmd_prev_unsupported <= 1'b1; + end else if (start_abstract_cmd && acmd_new) begin + acmd_prev_postexec <= acmd_new_postexec; + acmd_prev_transfer <= acmd_new_transfer; + acmd_prev_write <= acmd_new_write; + acmd_prev_regno <= acmd_new_regno; + acmd_prev_unsupported <= acmd_new_unsupported; + end +end + +wire acmd_postexec = acmd_new ? acmd_new_postexec : acmd_prev_postexec ; +wire acmd_transfer = acmd_new ? acmd_new_transfer : acmd_prev_transfer ; +wire acmd_write = acmd_new ? acmd_new_write : acmd_prev_write ; +wire [4:0] acmd_regno = acmd_new ? acmd_new_regno : acmd_prev_regno ; +wire acmd_unsupported = acmd_new ? acmd_new_unsupported : acmd_prev_unsupported; + +always @ (*) begin + // Default: no state change + acmd_state_nxt = acmd_state; + abstractcs_cmderr_nxt = abstractcs_cmderr; + + if (dmi_write && dmi_regaddr == ADDR_ABSTRACTCS && !abstractcs_busy) + abstractcs_cmderr_nxt = abstractcs_cmderr & ~dmi_pwdata[10:8]; + if (abstractcs_cmderr == CMDERR_OK && abstractcs_busy && dmi_access_illegal_when_busy) + abstractcs_cmderr_nxt = CMDERR_BUSY; + if (acmd_state != S_IDLE && hart_instr_caught_exception[hartsel]) + abstractcs_cmderr_nxt = CMDERR_EXCEPTION; + + case (acmd_state) + S_IDLE: begin + if (start_abstract_cmd) begin + if (!hart_halted[hartsel] || !hart_available[hartsel]) begin + abstractcs_cmderr_nxt = CMDERR_HALTRESUME; + end else if (acmd_unsupported) begin + abstractcs_cmderr_nxt = CMDERR_UNSUPPORTED; + end else begin + if (acmd_transfer && acmd_write) + acmd_state_nxt = S_ISSUE_REGWRITE; + else if (acmd_transfer && !acmd_write) + acmd_state_nxt = S_ISSUE_REGREAD; + else if (acmd_postexec) + acmd_state_nxt = S_ISSUE_PROGBUF0; + else + acmd_state_nxt = S_IDLE; + end + end + end + + S_ISSUE_REGREAD: begin + if (hart_instr_data_rdy[hartsel]) + acmd_state_nxt = S_ISSUE_REGEBREAK; + end + S_ISSUE_REGWRITE: begin + if (hart_instr_data_rdy[hartsel]) + acmd_state_nxt = S_ISSUE_REGEBREAK; + end + S_ISSUE_REGEBREAK: begin + if (hart_instr_data_rdy[hartsel]) + acmd_state_nxt = S_WAIT_REGEBREAK; + end + S_WAIT_REGEBREAK: begin + if (hart_instr_caught_ebreak[hartsel]) begin + if (acmd_prev_postexec) + acmd_state_nxt = S_ISSUE_PROGBUF0; + else + acmd_state_nxt = S_IDLE; + end + end + + S_ISSUE_PROGBUF0: begin + if (hart_instr_data_rdy[hartsel]) + acmd_state_nxt = S_ISSUE_PROGBUF1; + end + S_ISSUE_PROGBUF1: begin + if (hart_instr_caught_exception[hartsel] || hart_instr_caught_ebreak[hartsel]) begin + acmd_state_nxt = S_IDLE; + end else if (hart_instr_data_rdy[hartsel]) begin + acmd_state_nxt = S_ISSUE_IMPEBREAK; + end + end + S_ISSUE_IMPEBREAK: begin + if (hart_instr_caught_exception[hartsel] || hart_instr_caught_ebreak[hartsel]) begin + acmd_state_nxt = S_IDLE; + end else if (hart_instr_data_rdy[hartsel]) begin + acmd_state_nxt = S_WAIT_IMPEBREAK; + end + end + S_WAIT_IMPEBREAK: begin + if (hart_instr_caught_exception[hartsel] || hart_instr_caught_ebreak[hartsel]) begin + acmd_state_nxt = S_IDLE; + end + end + + default: begin + // Unreachable + end + + endcase +end + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + abstractcs_cmderr <= CMDERR_OK; + acmd_state <= S_IDLE; + end else if (!dmactive) begin + abstractcs_cmderr <= CMDERR_OK; + acmd_state <= S_IDLE; + end else begin + abstractcs_cmderr <= abstractcs_cmderr_nxt; + acmd_state <= acmd_state_nxt; + end +end + +wire [N_HARTS-1:0] hart_instr_data_vld_nxt = {{N_HARTS-1{1'b0}}, + acmd_state_nxt == S_ISSUE_REGREAD || acmd_state_nxt == S_ISSUE_REGWRITE || acmd_state_nxt == S_ISSUE_REGEBREAK || + acmd_state_nxt == S_ISSUE_PROGBUF0 || acmd_state_nxt == S_ISSUE_PROGBUF1 || acmd_state_nxt == S_ISSUE_IMPEBREAK +} << hartsel; + +wire [31:0] hart_instr_data_nxt = + acmd_state_nxt == S_ISSUE_REGWRITE ? 32'hbff02073 | {20'd0, acmd_regno, 7'd0} : // csrr xx, dmdata0 + acmd_state_nxt == S_ISSUE_REGREAD ? 32'hbff01073 | {12'd0, acmd_regno, 15'd0} : // csrw dmdata0, xx + acmd_state_nxt == S_ISSUE_PROGBUF0 ? progbuf0 : + acmd_state_nxt == S_ISSUE_PROGBUF1 ? progbuf1 : + 32'h00100073; // ebreak + +reg [31:0] hart_instr_data_reg; +assign hart_instr_data = {N_HARTS{hart_instr_data_reg}}; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + hart_instr_data_vld <= 1'b0; + hart_instr_data_reg <= 32'h00000000; + end else begin + hart_instr_data_vld <= hart_instr_data_vld_nxt; + if (hart_instr_data_vld_nxt) begin + hart_instr_data_reg <= hart_instr_data_nxt; + end + end +end + +// ---------------------------------------------------------------------------- +// Status helper functions + +function status_any; + input [N_HARTS-1:0] status_mask; +begin + status_any = status_mask[hartsel] || (hasel && |(status_mask & hart_array_mask)); +end +endfunction + +function status_all; + input [N_HARTS-1:0] status_mask; +begin + status_all = status_mask[hartsel] && (!hasel || ~|(~status_mask & hart_array_mask)); +end +endfunction + +function [1:0] status_all_any; + input [N_HARTS-1:0] status_mask; +begin + status_all_any = { + status_all(status_mask), + status_any(status_mask) + }; +end +endfunction + +// ---------------------------------------------------------------------------- +// DMI read data mux + +always @ (*) begin + case (dmi_regaddr) + ADDR_DATA0: dmi_prdata = abstract_data0; + ADDR_DMCONTROL: dmi_prdata = { + 1'b0, // haltreq is a W-only field + 1'b0, // resumereq is a W1 field + status_any(dmcontrol_hartreset), + 1'b0, // ackhavereset is a W1 field + 1'b0, // reserved + hasel, + {{10-W_HARTSEL{1'b0}}, hartsel}, // hartsello + 10'h0, // hartselhi + 2'h0, // reserved + 2'h0, // set/clrresethaltreq are W1 fields + dmcontrol_ndmreset, + dmactive + }; + ADDR_DMSTATUS: dmi_prdata = { + 9'h0, // reserved + 1'b1, // impebreak = 1 + 2'h0, // reserved + status_all_any(dmstatus_havereset), // allhavereset, anyhavereset + status_all_any(dmstatus_resumeack), // allresumeack, anyresumeack + hartsel >= N_HARTS && !(hasel && |hart_array_mask), // allnonexistent + hartsel >= N_HARTS, // anynonexistent + status_all_any(~hart_available), // allunavail, anyunavail + status_all_any(hart_running & hart_available), // allrunning, anyrunning + status_all_any(hart_halted & hart_available), // allhalted, anyhalted + 1'b1, // authenticated + 1'b0, // authbusy + 1'b1, // hasresethaltreq = 1 (we do support it) + 1'b0, // confstrptrvalid + 4'd2 // version = 2: RISC-V debug spec 0.13.2 + }; + ADDR_HARTINFO: dmi_prdata = { + 8'h0, // reserved + 4'h0, // nscratch = 0 + 3'h0, // reserved + 1'b0, // dataccess = 0, data0 is mapped to each hart's CSR space + 4'h1, // datasize = 1, a single data CSR (data0) is available + 12'hbff // dataaddr, placed at the top of the M-custom space since + // the spec doesn't reserve a location for it. + }; + ADDR_HALTSUM0: dmi_prdata = { + {XLEN - N_HARTS{1'b0}}, + hart_halted & hart_available + }; + ADDR_HALTSUM1: dmi_prdata = { + {XLEN - 1{1'b0}}, + |(hart_halted & hart_available) + }; + ADDR_HAWINDOWSEL: dmi_prdata = 32'h00000000; + ADDR_HAWINDOW: dmi_prdata = { + {32-N_HARTS{1'b0}}, + hart_array_mask + }; + ADDR_ABSTRACTCS: dmi_prdata = { + 3'h0, // reserved + 5'd2, // progbufsize = 2 + 11'h0, // reserved + abstractcs_busy, + 1'b0, + abstractcs_cmderr, + 4'h0, + 4'd1 // datacount = 1 + }; + ADDR_ABSTRACTAUTO: dmi_prdata = { + 14'h0, + abstractauto_autoexecprogbuf, // only progbuf0,1 present + 15'h0, + abstractauto_autoexecdata // only data0 present + }; + ADDR_SBCS: dmi_prdata = { + 3'h1, // version = 1 + 6'h00, + sbbusyerror, + sbbusy, + sbreadonaddr, + sbaccess, + sbautoincrement, + sbreadondata, + sberror, + 7'h20, // sbasize = 32 + 5'b00111 // 8, 16, 32-bit transfers supported + } & {32{|HAVE_SBA}}; + ADDR_SBDATA0: dmi_prdata = sbdata & {32{|HAVE_SBA}}; + ADDR_SBADDRESS0: dmi_prdata = sbaddress & {32{|HAVE_SBA}}; + ADDR_CONFSTRPTR0: dmi_prdata = 32'h4c296328; + ADDR_CONFSTRPTR1: dmi_prdata = 32'h20656b75; + ADDR_CONFSTRPTR2: dmi_prdata = 32'h6e657257; + ADDR_CONFSTRPTR3: dmi_prdata = 32'h31322720; + ADDR_NEXTDM: dmi_prdata = NEXT_DM_ADDR; + ADDR_PROGBUF0: dmi_prdata = progbuf0; + ADDR_PROGBUF1: dmi_prdata = progbuf1; + default: dmi_prdata = {XLEN{1'b0}}; + endcase +end + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/debug/dm/hazard3_sbus_to_ahb.v b/vendor/Hazard3/hdl/debug/dm/hazard3_sbus_to_ahb.v new file mode 100644 index 0000000..ab6949b --- /dev/null +++ b/vendor/Hazard3/hdl/debug/dm/hazard3_sbus_to_ahb.v @@ -0,0 +1,74 @@ +/*****************************************************************************\ +| Copyright (C) 2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// Standalone bus shim for connecting the DM's System Bus Access to AHB + +`default_nettype none + +module hazard3_sbus_to_ahb #( + parameter W_ADDR = 32, + parameter W_DATA = 32 +) ( + input wire clk, + input wire rst_n, + + input wire [W_ADDR-1:0] sbus_addr, + input wire sbus_write, + input wire [1:0] sbus_size, + input wire sbus_vld, + output wire sbus_rdy, + output wire sbus_err, + input wire [W_DATA-1:0] sbus_wdata, + output wire [W_DATA-1:0] sbus_rdata, + + output wire [W_ADDR-1:0] ahblm_haddr, + output wire ahblm_hwrite, + output wire [1:0] ahblm_htrans, + output wire [2:0] ahblm_hsize, + output wire [2:0] ahblm_hburst, + output wire [3:0] ahblm_hprot, + output wire ahblm_hmastlock, + input wire ahblm_hready, + input wire ahblm_hresp, + output wire [W_DATA-1:0] ahblm_hwdata, + input wire [W_DATA-1:0] ahblm_hrdata +); + +// Most signals are simple tie-throughs + +assign ahblm_haddr = sbus_addr; +assign ahblm_hwrite = sbus_write; +assign ahblm_hsize = {1'b0, sbus_size}; +assign ahblm_hwdata = sbus_wdata; + +// HPROT = noncacheable nonbufferable privileged data access: +assign ahblm_hprot = 4'b0011; +assign ahblm_hmastlock = 1'b0; +assign ahblm_hburst = 3'h0; + +assign sbus_err = ahblm_hresp; +assign sbus_rdata = ahblm_hrdata; + +// Handshaking + +reg dph_active; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + dph_active <= 1'b0; + end else if (ahblm_hready) begin + dph_active <= ahblm_htrans[1]; + end +end + +assign ahblm_htrans = sbus_vld && !dph_active ? 2'b10 : 2'b00; + +assign sbus_rdy = ahblm_hready && dph_active; + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/debug/dtm/hazard3_ecp5_jtag_dtm.f b/vendor/Hazard3/hdl/debug/dtm/hazard3_ecp5_jtag_dtm.f new file mode 100644 index 0000000..0447ef8 --- /dev/null +++ b/vendor/Hazard3/hdl/debug/dtm/hazard3_ecp5_jtag_dtm.f @@ -0,0 +1,6 @@ +file hazard3_ecp5_jtag_dtm.v +file hazard3_jtag_dtm_core.v + +file ../cdc/hazard3_apb_async_bridge.v +file ../cdc/hazard3_reset_sync.v +file ../cdc/hazard3_sync_1bit.v diff --git a/vendor/Hazard3/hdl/debug/dtm/hazard3_ecp5_jtag_dtm.v b/vendor/Hazard3/hdl/debug/dtm/hazard3_ecp5_jtag_dtm.v new file mode 100644 index 0000000..bfaca49 --- /dev/null +++ b/vendor/Hazard3/hdl/debug/dtm/hazard3_ecp5_jtag_dtm.v @@ -0,0 +1,194 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// The ECP5 JTAGG primitive (yes that is the correct spelling) allows you to +// add two custom DRs to the FPGA's chip TAP, selected using the 8-bit ER1 +// (0x32) and ER2 (0x38) instructions. +// +// Brian Swetland pointed out on Twitter that the standard RISC-V JTAG-DTM +// only uses two DRs (DTMCS and DMI), besides the standard IDCODE and BYPASS +// which are provided already by the ECP5 TAP. This file instantiates the +// guts of Hazard3's standard JTAG-DTM and connects the DTMCS and DMI +// registers to the JTAGG primitive's ER1/ER2 DRs. +// +// The exciting part is that upstream OpenOCD already allows you to set the IR +// length *and* set custom DTMCS/DMI IR values for RISC-V JTAG DTMs. This +// means with the right config file, you can access a debug module hung from +// the ECP5 TAP in this fashion using only upstream OpenOCD and gdb. + +`default_nettype none + +module hazard3_ecp5_jtag_dtm #( + parameter DTMCS_IDLE_HINT = 3'd4, + parameter W_PADDR = 9, + parameter ABITS = W_PADDR - 2 // do not modify +) ( + // This is synchronous to TCK and asserted for one TCK cycle only + output wire dmihardreset_req, + + // Bus clock + reset for Debug Module Interface + input wire clk_dmi, + input wire rst_n_dmi, + + // Debug Module Interface (APB) + output wire dmi_psel, + output wire dmi_penable, + output wire dmi_pwrite, + output wire [W_PADDR-1:0] dmi_paddr, + output wire [31:0] dmi_pwdata, + input wire [31:0] dmi_prdata, + input wire dmi_pready, + input wire dmi_pslverr +); + +// Signals to/from the ECP5 TAP + +wire jtdo2; +wire jtdo1; +wire jtdi; +wire jtck_posedge_dont_use; +wire jshift; +wire jupdate; +wire jrst_n; +wire jce2; +wire jce1; + +JTAGG jtag_u ( + .JTDO2 (jtdo2), + .JTDO1 (jtdo1), + .JTDI (jtdi), + .JTCK (jtck_posedge_dont_use), + .JRTI2 (/* unused */), + .JRTI1 (/* unused */), + .JSHIFT (jshift), + .JUPDATE (jupdate), + .JRSTN (jrst_n), + .JCE2 (jce2), + .JCE1 (jce1) +); + +// JTAGG primitive asserts its signals synchronously to JTCK's posedge, but +// you get weird and inconsistent results if you try to consume them +// synchronously on JTCK's posedge, possibly due to a lack of hold +// constraints in nextpnr. +// +// A quick hack is to move the sampling onto the negedge of the clock. This +// then creates more problems because we would be running our shift logic on +// a different edge from the control + CDC logic in the DTM core. +// +// So, even worse hack, move all our JTAG-domain logic onto the negedge +// (or near enough) by inverting the clock. + +wire jtck = !jtck_posedge_dont_use; + +localparam W_DR_SHIFT = ABITS + 32 + 2; + +reg core_dr_wen; +reg core_dr_ren; +reg core_dr_sel_dmi_ndtmcs; +reg dr_shift_en; +wire [W_DR_SHIFT-1:0] core_dr_wdata; +wire [W_DR_SHIFT-1:0] core_dr_rdata; + +// Decode our shift controls from the interesting ECP5 ones, and re-register +// onto JTCK negedge (our posedge). Note without re-registering we observe +// them a half-cycle (effectively one cycle) too early. This is another +// consequence of the stupid JTDI thing + +always @ (posedge jtck or negedge jrst_n) begin + if (!jrst_n) begin + core_dr_sel_dmi_ndtmcs <= 1'b0; + core_dr_wen <= 1'b0; + core_dr_ren <= 1'b0; + dr_shift_en <= 1'b0; + end else begin + if (jce1 || jce2) + core_dr_sel_dmi_ndtmcs <= jce2; + core_dr_ren <= (jce1 || jce2) && !jshift; + core_dr_wen <= jupdate; + dr_shift_en <= jshift; + end +end + +reg [W_DR_SHIFT-1:0] dr_shift; +assign core_dr_wdata = dr_shift; + +always @ (posedge jtck or negedge jrst_n) begin + if (!jrst_n) begin + dr_shift <= {W_DR_SHIFT{1'b0}}; + end else if (core_dr_ren) begin + dr_shift <= core_dr_rdata; + end else if (dr_shift_en) begin + dr_shift <= {jtdi, dr_shift[W_DR_SHIFT-1:1]}; + if (!core_dr_sel_dmi_ndtmcs) + dr_shift[31] <= jtdi; + end +end + +// Not documented on ECP5: as well as the posedge flop on JTDI, the ECP5 puts +// a negedge flop on JTDO1, JTDO2. (Conjecture based on dicking around with a +// logic analyser.) To get JTDOx to appear with the same timing as our shifter +// LSB (which we update on every JTCK negedge) we: +// +// - Register the LSB of the *next* value of dr_shift on the JTCK posedge, so +// half a cycle earlier than the actual dr_shift update +// +// - This then gets re-registered with the pointless JTDO negedge flops, so +// that it appears with the same timing as our DR shifter update. + +reg dr_shift_next_halfcycle; +always @ (negedge jtck or negedge jrst_n) begin + if (!jrst_n) begin + dr_shift_next_halfcycle <= 1'b0; + end else begin + dr_shift_next_halfcycle <= + core_dr_ren ? core_dr_rdata[0] : + dr_shift_en ? dr_shift[1] : dr_shift[0]; + end +end + +// We have only a single shifter for the ER1 and ER2 chains, so these are tied +// together: + +assign jtdo1 = dr_shift_next_halfcycle; +assign jtdo2 = dr_shift_next_halfcycle; + +// The actual DTM is in here: + +hazard3_jtag_dtm_core #( + .DTMCS_IDLE_HINT (DTMCS_IDLE_HINT), + .W_ADDR (ABITS) +) inst_hazard3_jtag_dtm_core ( + .tck (jtck), + .trst_n (jrst_n), + + .clk_dmi (clk_dmi), + .rst_n_dmi (rst_n_dmi), + + .dr_wen (core_dr_wen), + .dr_ren (core_dr_ren), + .dr_sel_dmi_ndtmcs (core_dr_sel_dmi_ndtmcs), + .dr_wdata (core_dr_wdata), + .dr_rdata (core_dr_rdata), + + .dmihardreset_req (dmihardreset_req), + + .dmi_psel (dmi_psel), + .dmi_penable (dmi_penable), + .dmi_pwrite (dmi_pwrite), + .dmi_paddr (dmi_paddr[W_PADDR-1:2]), + .dmi_pwdata (dmi_pwdata), + .dmi_prdata (dmi_prdata), + .dmi_pready (dmi_pready), + .dmi_pslverr (dmi_pslverr) +); + +assign dmi_paddr[1:0] = 2'b00; + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/debug/dtm/hazard3_jtag_dtm.f b/vendor/Hazard3/hdl/debug/dtm/hazard3_jtag_dtm.f new file mode 100644 index 0000000..aa9e2eb --- /dev/null +++ b/vendor/Hazard3/hdl/debug/dtm/hazard3_jtag_dtm.f @@ -0,0 +1,6 @@ +file hazard3_jtag_dtm.v +file hazard3_jtag_dtm_core.v + +file ../cdc/hazard3_apb_async_bridge.v +file ../cdc/hazard3_reset_sync.v +file ../cdc/hazard3_sync_1bit.v diff --git a/vendor/Hazard3/hdl/debug/dtm/hazard3_jtag_dtm.v b/vendor/Hazard3/hdl/debug/dtm/hazard3_jtag_dtm.v new file mode 100644 index 0000000..8cc76ab --- /dev/null +++ b/vendor/Hazard3/hdl/debug/dtm/hazard3_jtag_dtm.v @@ -0,0 +1,210 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// Implementation of standard RISC-V JTAG-DTM with an APB Debug Module +// Interface. The TAP itself is clocked directly by JTAG TCK; a clock +// crossing is instantiated internally between the TCK domain and the DMI bus +// clock domain. + +`default_nettype none + +module hazard3_jtag_dtm #( + parameter IDCODE = 32'h0000_0001, + parameter DTMCS_IDLE_HINT = 3'd4, + parameter W_PADDR = 9, + parameter ABITS = W_PADDR - 2 // do not modify +) ( + // Standard JTAG signals -- the JTAG hardware is clocked directly by TCK. + input wire tck, + input wire trst_n, + input wire tms, + input wire tdi, + output reg tdo, + + // This is synchronous to TCK and asserted for one TCK cycle only + output wire dmihardreset_req, + + // Bus clock + reset for Debug Module Interface + input wire clk_dmi, + input wire rst_n_dmi, + + // Debug Module Interface (APB) + output wire dmi_psel, + output wire dmi_penable, + output wire dmi_pwrite, + output wire [W_PADDR-1:0] dmi_paddr, + output wire [31:0] dmi_pwdata, + input wire [31:0] dmi_prdata, + input wire dmi_pready, + input wire dmi_pslverr +); + +// ---------------------------------------------------------------------------- +// TAP state machine + +reg [3:0] tap_state; +localparam S_RESET = 4'd0; +localparam S_RUN_IDLE = 4'd1; +localparam S_SELECT_DR = 4'd2; +localparam S_CAPTURE_DR = 4'd3; +localparam S_SHIFT_DR = 4'd4; +localparam S_EXIT1_DR = 4'd5; +localparam S_PAUSE_DR = 4'd6; +localparam S_EXIT2_DR = 4'd7; +localparam S_UPDATE_DR = 4'd8; +localparam S_SELECT_IR = 4'd9; +localparam S_CAPTURE_IR = 4'd10; +localparam S_SHIFT_IR = 4'd11; +localparam S_EXIT1_IR = 4'd12; +localparam S_PAUSE_IR = 4'd13; +localparam S_EXIT2_IR = 4'd14; +localparam S_UPDATE_IR = 4'd15; + +always @ (posedge tck or negedge trst_n) begin + if (!trst_n) begin + tap_state <= S_RESET; + end else case(tap_state) + S_RESET : tap_state <= tms ? S_RESET : S_RUN_IDLE ; + S_RUN_IDLE : tap_state <= tms ? S_SELECT_DR : S_RUN_IDLE ; + + S_SELECT_DR : tap_state <= tms ? S_SELECT_IR : S_CAPTURE_DR; + S_CAPTURE_DR : tap_state <= tms ? S_EXIT1_DR : S_SHIFT_DR ; + S_SHIFT_DR : tap_state <= tms ? S_EXIT1_DR : S_SHIFT_DR ; + S_EXIT1_DR : tap_state <= tms ? S_UPDATE_DR : S_PAUSE_DR ; + S_PAUSE_DR : tap_state <= tms ? S_EXIT2_DR : S_PAUSE_DR ; + S_EXIT2_DR : tap_state <= tms ? S_UPDATE_DR : S_SHIFT_DR ; + S_UPDATE_DR : tap_state <= tms ? S_SELECT_DR : S_RUN_IDLE ; + + S_SELECT_IR : tap_state <= tms ? S_RESET : S_CAPTURE_IR; + S_CAPTURE_IR : tap_state <= tms ? S_EXIT1_IR : S_SHIFT_IR ; + S_SHIFT_IR : tap_state <= tms ? S_EXIT1_IR : S_SHIFT_IR ; + S_EXIT1_IR : tap_state <= tms ? S_UPDATE_IR : S_PAUSE_IR ; + S_PAUSE_IR : tap_state <= tms ? S_EXIT2_IR : S_PAUSE_IR ; + S_EXIT2_IR : tap_state <= tms ? S_UPDATE_IR : S_SHIFT_IR ; + S_UPDATE_IR : tap_state <= tms ? S_SELECT_DR : S_RUN_IDLE ; + endcase +end + +// ---------------------------------------------------------------------------- +// Instruction register + +localparam W_IR = 5; +// All other encodings behave as BYPASS: +localparam IR_IDCODE = 5'h01; +localparam IR_DTMCS = 5'h10; +localparam IR_DMI = 5'h11; + +reg [W_IR-1:0] ir_shift; +reg [W_IR-1:0] ir; + +always @ (posedge tck or negedge trst_n) begin + if (!trst_n) begin + ir_shift <= {W_IR{1'b0}}; + ir <= IR_IDCODE; + end else if (tap_state == S_RESET) begin + ir_shift <= {W_IR{1'b0}}; + ir <= IR_IDCODE; + end else if (tap_state == S_CAPTURE_IR) begin + ir_shift <= ir; + end else if (tap_state == S_SHIFT_IR) begin + ir_shift <= {tdi, ir_shift[W_IR-1:1]}; + end else if (tap_state == S_UPDATE_IR) begin + ir <= ir_shift; + end +end + +// ---------------------------------------------------------------------------- +// Data registers + +// Shift register is sized to largest DR, which is DMI: +// {addr[7:0], data[31:0], op[1:0]} +localparam W_DR_SHIFT = ABITS + 32 + 2; + +reg [W_DR_SHIFT-1:0] dr_shift; + +// Signals to/from the DTM core, which implements the DTMCS and DMI registers +wire core_dr_wen; +wire core_dr_ren; +wire core_dr_sel_dmi_ndtmcs; +wire [W_DR_SHIFT-1:0] core_dr_wdata; +wire [W_DR_SHIFT-1:0] core_dr_rdata; + +always @ (posedge tck or negedge trst_n) begin + if (!trst_n) begin + dr_shift <= {W_DR_SHIFT{1'b0}}; + end else if (tap_state == S_SHIFT_DR) begin + dr_shift <= {tdi, dr_shift[W_DR_SHIFT-1:1]}; + // Shorten DR shift chain according to IR + if (ir == IR_DMI) + dr_shift[W_DR_SHIFT - 1] <= tdi; + else if (ir == IR_IDCODE || ir == IR_DTMCS) + dr_shift[31] <= tdi; + else // BYPASS + dr_shift[0] <= tdi; + end else if (tap_state == S_CAPTURE_DR) begin + if (ir == IR_DMI || ir == IR_DTMCS) begin + dr_shift <= core_dr_rdata; + end else if (ir == IR_IDCODE) begin + dr_shift <= {{W_DR_SHIFT-32{1'b0}}, IDCODE}; + end else begin // BYPASS + dr_shift <= {W_DR_SHIFT{1'b0}}; + end + end +end + +// Must retime shift data onto negedge before presenting on TDO + +always @ (negedge tck or negedge trst_n) begin + if (!trst_n) begin + tdo <= 1'b0; + end else begin + tdo <= tap_state == S_SHIFT_IR ? ir_shift[0] : + tap_state == S_SHIFT_DR ? dr_shift[0] : 1'b0; + end +end + +// ---------------------------------------------------------------------------- +// Core logic and bus interface + +assign core_dr_sel_dmi_ndtmcs = ir == IR_DMI; +assign core_dr_wen = (ir == IR_DMI || ir == IR_DTMCS) && tap_state == S_UPDATE_DR; +assign core_dr_ren = (ir == IR_DMI || ir == IR_DTMCS) && tap_state == S_CAPTURE_DR; + +assign core_dr_wdata = dr_shift; + +hazard3_jtag_dtm_core #( + .DTMCS_IDLE_HINT (DTMCS_IDLE_HINT), + .W_ADDR (ABITS) +) dtm_core ( + .tck (tck), + .trst_n (trst_n), + .clk_dmi (clk_dmi), + .rst_n_dmi (rst_n_dmi), + + .dmihardreset_req (dmihardreset_req), + + .dr_wen (core_dr_wen), + .dr_ren (core_dr_ren), + .dr_sel_dmi_ndtmcs (core_dr_sel_dmi_ndtmcs), + .dr_wdata (core_dr_wdata), + .dr_rdata (core_dr_rdata), + + .dmi_psel (dmi_psel), + .dmi_penable (dmi_penable), + .dmi_pwrite (dmi_pwrite), + .dmi_paddr (dmi_paddr[W_PADDR-1:2]), + .dmi_pwdata (dmi_pwdata), + .dmi_prdata (dmi_prdata), + .dmi_pready (dmi_pready), + .dmi_pslverr (dmi_pslverr) +); + +assign dmi_paddr[1:0] = 2'b00; + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/debug/dtm/hazard3_jtag_dtm_core.v b/vendor/Hazard3/hdl/debug/dtm/hazard3_jtag_dtm_core.v new file mode 100644 index 0000000..58744b0 --- /dev/null +++ b/vendor/Hazard3/hdl/debug/dtm/hazard3_jtag_dtm_core.v @@ -0,0 +1,185 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// DTMCS + DMI control logic, bus interface and bus clock domain crossing for +// a standard RISC-V APB JTAG-DTM. Essentially everything apart from the +// actual TAP controller, IR and shift registers. Instantiated by +// hazard3_jtag_dtm.v. +// +// This core logic can be reused and connected to some other serial transport +// or, for example, the ECP5 JTAGG primitive (see hazard5_ecp5_jtag_dtm.v) + +`default_nettype none + +module hazard3_jtag_dtm_core #( + parameter DTMCS_IDLE_HINT = 3'd4, + parameter W_ADDR = 8, + parameter W_DR_SHIFT = W_ADDR + 32 + 2 // do not modify +) ( + input wire tck, + input wire trst_n, + + input wire clk_dmi, + input wire rst_n_dmi, + + // DR capture/update (read/write) signals + input wire dr_wen, + input wire dr_ren, + input wire dr_sel_dmi_ndtmcs, + input wire [W_DR_SHIFT-1:0] dr_wdata, + output wire [W_DR_SHIFT-1:0] dr_rdata, + + // This is synchronous to TCK and asserted for one TCK cycle only + output reg dmihardreset_req, + + // Debug Module Interface (APB) + output wire dmi_psel, + output wire dmi_penable, + output wire dmi_pwrite, + output wire [W_ADDR-1:0] dmi_paddr, + output wire [31:0] dmi_pwdata, + input wire [31:0] dmi_prdata, + input wire dmi_pready, + input wire dmi_pslverr +); + +wire write_dmi = dr_wen && dr_sel_dmi_ndtmcs; +wire write_dtmcs = dr_wen && !dr_sel_dmi_ndtmcs; +wire read_dmi = dr_ren && dr_sel_dmi_ndtmcs; + +// ---------------------------------------------------------------------------- +// DMI bus adapter + +reg [1:0] dmi_cmderr; +reg dmi_busy; + +// DTM-domain bus, connected to a matching DM-domain bus via an APB crossing: +wire dtm_psel; +wire dtm_penable; +wire dtm_pwrite; +wire [W_ADDR-1:0] dtm_paddr; +wire [31:0] dtm_pwdata; +wire [31:0] dtm_prdata; +wire dtm_pready; +wire dtm_pslverr; + +// We are relying on some particular features of our APB clock crossing here +// to save some registers: +// +// - The transfer is launched immediately when psel is seen, no need to +// actually assert an access phase (as the standard allows the CDC to +// assume that access immediately follows setup) and no need to maintain +// pwrite/paddr/pwdata valid after the setup phase +// +// - prdata/pslverr remain valid after the transfer completes, until the next +// transfer completes +// +// These allow us to connect the upstream side of the CDC directly to our DR +// shifter without any sample/hold registers in between. + +// psel is only pulsed for one cycle, penable is not asserted. +assign dtm_psel = write_dmi && + (dr_wdata[1:0] == 2'd1 || dr_wdata[1:0] == 2'd2) && + !(dmi_busy || dmi_cmderr != 2'd0) && dtm_pready; +assign dtm_penable = 1'b0; + +// paddr/pwdata/pwrite are valid momentarily when psel is asserted. +assign dtm_paddr = dr_wdata[34 +: W_ADDR]; +assign dtm_pwrite = dr_wdata[1]; +assign dtm_pwdata = dr_wdata[2 +: 32]; + +always @ (posedge tck or negedge trst_n) begin + if (!trst_n) begin + dmi_busy <= 1'b0; + dmi_cmderr <= 2'd0; + end else if (read_dmi) begin + // Reading while busy sets the busy sticky error. Note the capture + // into shift register should also reflect this update on-the-fly + if (dmi_busy && dmi_cmderr == 2'd0) + dmi_cmderr <= 2'h3; + end else if (write_dtmcs) begin + // Writing dtmcs.dmireset = 1 clears a sticky error + if (dr_wdata[16]) + dmi_cmderr <= 2'd0; + end else if (write_dmi) begin + if (dtm_psel) begin + dmi_busy <= 1'b1; + end else if (dr_wdata[1:0] != 2'd0) begin + // DMI ignored operation, so set sticky busy + if (dmi_cmderr == 2'd0) + dmi_cmderr <= 2'd3; + end + end else if (dmi_busy && dtm_pready) begin + dmi_busy <= 1'b0; + if (dmi_cmderr == 2'd0 && dtm_pslverr) + dmi_cmderr <= 2'd2; + end +end + +// DTM logic is in TCK domain, actual DMI + DM is in processor domain + +hazard3_apb_async_bridge #( + .W_ADDR (W_ADDR), + .W_DATA (32), + .N_SYNC_STAGES (2) +) inst_hazard3_apb_async_bridge ( + .clk_src (tck), + .rst_n_src (trst_n), + + .clk_dst (clk_dmi), + .rst_n_dst (rst_n_dmi), + + .src_psel (dtm_psel), + .src_penable (dtm_penable), + .src_pwrite (dtm_pwrite), + .src_paddr (dtm_paddr), + .src_pwdata (dtm_pwdata), + .src_prdata (dtm_prdata), + .src_pready (dtm_pready), + .src_pslverr (dtm_pslverr), + + .dst_psel (dmi_psel), + .dst_penable (dmi_penable), + .dst_pwrite (dmi_pwrite), + .dst_paddr (dmi_paddr), + .dst_pwdata (dmi_pwdata), + .dst_prdata (dmi_prdata), + .dst_pready (dmi_pready), + .dst_pslverr (dmi_pslverr) +); + +// ---------------------------------------------------------------------------- +// DR read/write + +wire [W_DR_SHIFT-1:0] dtmcs_rdata = { + {W_ADDR{1'b0}}, + 19'h0, + DTMCS_IDLE_HINT[2:0], + dmi_cmderr, + W_ADDR[5:0], // abits + 4'd1 // version +}; + +wire [W_DR_SHIFT-1:0] dmi_rdata = { + {W_ADDR{1'b0}}, + dtm_prdata, + dmi_busy && dmi_cmderr == 2'd0 ? 2'd3 : dmi_cmderr +}; + +assign dr_rdata = dr_sel_dmi_ndtmcs ? dmi_rdata : dtmcs_rdata; + +always @ (posedge tck or negedge trst_n) begin + if (!trst_n) begin + dmihardreset_req <= 1'b0; + end else begin + dmihardreset_req <= write_dtmcs && dr_wdata[17]; + end +end + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/debug/dtm/hazard3_xilinx7_jtag_dtm.v b/vendor/Hazard3/hdl/debug/dtm/hazard3_xilinx7_jtag_dtm.v new file mode 100644 index 0000000..ac75737 --- /dev/null +++ b/vendor/Hazard3/hdl/debug/dtm/hazard3_xilinx7_jtag_dtm.v @@ -0,0 +1,162 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2025 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// Implement a RISC-V JTAG DTM tunnelled through a Xilinx BSCANE2 primitive. +// +// Xilinx allows up to four custom DRs to be added to the FPGA TAP controller. +// A JTAG-DTM only needs two: DTMCS and DMI. +// +// With the correct config, OpenOCD can treat the FPGA TAP as a JTAG DTM and +// access RISC-V debug directly. This allows you to debug internal RISC-V +// cores with the same JTAG interface you use to load the FPGA. +// +// CHAIN_DTMCS and CHAIN_DMI select which JTAG IR values are used to access +// these DRs. Values 1 through 4 correspond to Xilinx USER1 through USER4 +// instructions, which have IR values 0x02, 0x03, 0x22, 0x23. + +`default_nettype none + +module hazard3_xilinx7_jtag_dtm #( + parameter SEL_DTMCS = 3, + parameter SEL_DMI = 4, + parameter DTMCS_IDLE_HINT = 3'd4, + parameter W_PADDR = 9, + parameter ABITS = W_PADDR - 2 // do not modify +) ( + // This is synchronous to TCK and asserted for one TCK cycle only + output wire dmihardreset_req, + + // Bus clock + reset for Debug Module Interface + input wire clk_dmi, + input wire rst_n_dmi, + + // Debug Module Interface (APB) + output wire dmi_psel, + output wire dmi_penable, + output wire dmi_pwrite, + output wire [W_PADDR-1:0] dmi_paddr, + output wire [31:0] dmi_pwdata, + input wire [31:0] dmi_prdata, + input wire dmi_pready, + input wire dmi_pslverr +); + +// Signals to/from the Xilinx TAP + +wire jtck_unbuf; +wire jtck; +wire jtdo2; +wire jtdo1; +wire jtdi; +wire jshift; +wire jupdate; +wire jcapture; +wire jrst; +wire jrst_n = !jrst; +wire jce2; +wire jce1; + +BSCANE2 #( + .JTAG_CHAIN (SEL_DTMCS) // Value for USER command. +) bscan_dtmcs ( + .CAPTURE (jcapture), // CAPTURE output from TAP controller. + .DRCK (/* unused */), // Gated TCK output. When SEL is asserted, DRCK toggles when CAPTURE or SHIFT are asserted. + .RESET (jrst), // Reset output for TAP controller. + .RUNTEST (/* unused */), // Output asserted when TAP controller is in Run Test/Idle state. + .SEL (jce1), // USER instruction active output. + .SHIFT (jshift), // SHIFT output from TAP controller. + .TCK (jtck_unbuf), // Test Clock output. Fabric connection to TAP Clock pin. + .TDI (jtdi), // Test Data Input (TDI) output from TAP controller. + .TMS (/* unused */), // Test Mode Select output. Fabric connection to TAP. + .UPDATE (jupdate), // UPDATE output from TAP controller + .TDO (jtdo1) // Test Data Output (TDO) input for USER function. +); + +BSCANE2 #( + .JTAG_CHAIN (SEL_DMI) +) bscan_dmi ( + .CAPTURE (/* unused */), + .DRCK (/* unused */), + .RESET (/* unused */), + .RUNTEST (/* unused */), + .SEL (jce2), + .SHIFT (/* unused */), + .TCK (/* unused */), + .TDI (/* unused */), + .TMS (/* unused */), + .UPDATE (/* unused */), + .TDO (jtdo2) +); + +BUFG bufg_jtck ( + .I (jtck_unbuf), + .O (jtck) +); + +localparam W_DR_SHIFT = ABITS + 32 + 2; + +wire core_dr_wen = jupdate; +wire core_dr_ren = jcapture; +wire core_dr_sel_dmi_ndtmcs = !jce1; +wire dr_shift_en = jshift; +wire [W_DR_SHIFT-1:0] core_dr_wdata; +wire [W_DR_SHIFT-1:0] core_dr_rdata; + +reg [W_DR_SHIFT-1:0] dr_shift; +assign core_dr_wdata = dr_shift; + +always @ (posedge jtck or negedge jrst_n) begin + if (!jrst_n) begin + dr_shift <= {W_DR_SHIFT{1'b0}}; + end else if (core_dr_ren) begin + dr_shift <= core_dr_rdata; + end else if (dr_shift_en) begin + dr_shift <= {jtdi, dr_shift[W_DR_SHIFT-1:1]}; + if (!core_dr_sel_dmi_ndtmcs) + dr_shift[31] <= jtdi; + end +end + +// We have only a single shifter for the two DRs, so these are tied together: +assign jtdo1 = dr_shift[0]; +assign jtdo2 = dr_shift[0]; + +// The actual DTM is in here: + +hazard3_jtag_dtm_core #( + .DTMCS_IDLE_HINT (DTMCS_IDLE_HINT), + .W_ADDR (ABITS) +) inst_hazard3_jtag_dtm_core ( + .tck (jtck), + .trst_n (jrst_n), + + .clk_dmi (clk_dmi), + .rst_n_dmi (rst_n_dmi), + + .dr_wen (core_dr_wen), + .dr_ren (core_dr_ren), + .dr_sel_dmi_ndtmcs (core_dr_sel_dmi_ndtmcs), + .dr_wdata (core_dr_wdata), + .dr_rdata (core_dr_rdata), + + .dmihardreset_req (dmihardreset_req), + + .dmi_psel (dmi_psel), + .dmi_penable (dmi_penable), + .dmi_pwrite (dmi_pwrite), + .dmi_paddr (dmi_paddr[W_PADDR-1:2]), + .dmi_pwdata (dmi_pwdata), + .dmi_prdata (dmi_prdata), + .dmi_pready (dmi_pready), + .dmi_pslverr (dmi_pslverr) +); + +assign dmi_paddr[1:0] = 2'b00; + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/hazard3.f b/vendor/Hazard3/hdl/hazard3.f new file mode 100644 index 0000000..3a143d5 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3.f @@ -0,0 +1,22 @@ +file hazard3_core.v +file hazard3_cpu_1port.v +file hazard3_cpu_2port.v +file arith/hazard3_alu.v +file arith/hazard3_branchcmp.v +file arith/hazard3_mul_fast.v +file arith/hazard3_muldiv_seq.v +file arith/hazard3_onehot_encode.v +file arith/hazard3_onehot_priority.v +file arith/hazard3_onehot_priority_dynamic.v +file arith/hazard3_priority_encode.v +file arith/hazard3_shift_barrel.v +file hazard3_csr.v +file hazard3_decode.v +file hazard3_frontend.v +file hazard3_instr_decompress.v +file hazard3_irq_ctrl.v +file hazard3_pmp.v +file hazard3_power_ctrl.v +file hazard3_regfile_1w2r.v +file hazard3_triggers.v +include . diff --git a/vendor/Hazard3/hdl/hazard3_config.vh b/vendor/Hazard3/hdl/hazard3_config.vh new file mode 100644 index 0000000..e52c4c5 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_config.vh @@ -0,0 +1,259 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// Hazard3 CPU configuration parameters + +// To configure Hazard3 you can either edit this file, or set parameters on +// your top-level instantiation, it's up to you. These parameters are all +// plumbed through Hazard3's internal hierarchy to the appropriate places. + +// If you add a parameter here, you should add a matching line to +// hazard3_config_inst.vh to propagate the parameter through module +// instantiations. + +// ---------------------------------------------------------------------------- +// Reset state configuration + +// RESET_VECTOR: Address of first instruction executed. +parameter RESET_VECTOR = 32'h00000000, + +// MTVEC_INIT: Initial value of trap vector base. Bits clear in MTVEC_WMASK +// will never change from this initial value. Bits set in MTVEC_WMASK can be +// written/set/cleared as normal. +// +// Note that mtvec bits 1:0 do not affect the trap base (as per RISC-V spec). +// Bit 1 is don't care, bit 0 selects the vectoring mode: unvectored if == 0 +// (all traps go to mtvec), vectored if == 1 (exceptions go to mtvec, IRQs to +// mtvec + mcause * 4). This means MTVEC_INIT also sets the initial vectoring +// mode. +parameter MTVEC_INIT = 32'h00000000, + +// ---------------------------------------------------------------------------- +// Standard RISC-V ISA support + +// EXTENSION_A: Support for atomic read/modify/write instructions +parameter EXTENSION_A = 1, + +// EXTENSION_C: Support for compressed (variable-width) instructions +parameter EXTENSION_C = 1, + +// EXTENSION_E: Implement the RV32E base extension rather than RV32I. This +// reduces the number of integer registers from 31 to 15. +parameter EXTENSION_E = 0, + +// EXTENSION_M: Support for hardware multiply/divide/modulo instructions +parameter EXTENSION_M = 1, + +// EXTENSION_ZBA: Support for Zba address generation instructions +parameter EXTENSION_ZBA = 0, + +// EXTENSION_ZBB: Support for Zbb basic bit manipulation instructions +parameter EXTENSION_ZBB = 0, + +// EXTENSION_ZBC: Support for Zbc carry-less multiplication instructions +parameter EXTENSION_ZBC = 0, + +// EXTENSION_ZBKB: Support for Zbkb basic bit manipulation for cryptography +// Requires: Zbb. (This flag enables instructions in Zbkb which aren't in Zbb.) +parameter EXTENSION_ZBKB = 0, + +// EXTENSION_ZBKX: support for Zbkx crossbar permutation instructions +parameter EXTENSION_ZBKX = 0, + +// EXTENSION_ZBS: Support for Zbs single-bit manipulation instructions +parameter EXTENSION_ZBS = 0, + +// EXTENSION_ZCB: Support for Zcb basic additional compressed instructions +// Requires: EXTENSION_C. (Some Zcb instructions also require Zbb or M.) +// Note Zca is equivalent to C, as we do not support the F extension. +parameter EXTENSION_ZCB = 0, + +// EXTENSION_ZCLSD: Support for Zclsd compressed load/store pair instructions +// Requires: EXTENSION_ZILSD, EXTENSION_C. +parameter EXTENSION_ZCLSD = 0, + +// EXTENSION_ZCMP: Support for Zcmp push/pop instructions. +// Requires: EXTENSION_C. +parameter EXTENSION_ZCMP = 0, + +// EXTENSION_ZIFENCEI: Support for the fence.i instruction +// Optional, since a plain branch/jump will also flush the prefetch queue. +parameter EXTENSION_ZIFENCEI = 0, + +// EXTENSION_ZILSD: Support for Zilsd load/store pair instructions +parameter EXTENSION_ZILSD = 0, + +// ---------------------------------------------------------------------------- +// Custom RISC-V extensions + +// EXTENSION_XH3B: Custom bit-extract-multiple instructions for Hazard3 +parameter EXTENSION_XH3BEXTM = 0, + +// EXTENSION_XH3IRQ: Custom preemptive, prioritised interrupt support. Can be +// disabled if an external interrupt controller (e.g. PLIC) is used. If +// disabled, and NUM_IRQS > 1, the external interrupts are simply OR'd into +// mip.meip. +parameter EXTENSION_XH3IRQ = 0, + +// EXTENSION_XH3PMPM: PMPCFGMx CSRs to enforce PMP regions in M-mode without +// locking. Unlike ePMP mseccfg.rlb, locked and unlocked regions can coexist +parameter EXTENSION_XH3PMPM = 0, + +// EXTENSION_XH3POWER: Custom power management controls for Hazard3 +parameter EXTENSION_XH3POWER = 0, + +// ---------------------------------------------------------------------------- +// Standard CSR support + +// Note the Zicsr extension is implied by any of CSR_M_MANDATORY, CSR_M_TRAP, +// CSR_COUNTER. + +// CSR_M_MANDATORY: Bare minimum CSR support e.g. misa. Spec says must = 1 if +// CSRs are present, but I won't tell anyone. +parameter CSR_M_MANDATORY = 1, + +// CSR_M_TRAP: Include M-mode trap-handling CSRs, and enable trap support. +parameter CSR_M_TRAP = 1, + +// CSR_COUNTER: Include performance counters and Zicntr CSRs +parameter CSR_COUNTER = 0, + +// U_MODE: Support the U (user) execution mode. In U mode, the core performs +// unprivileged bus accesses, and software's access to CSRs is restricted. +// Additionally, if the PMP is included, the core may restrict U-mode +// software's access to memory. +// Requires: CSR_M_TRAP. +parameter U_MODE = 0, + +// PMP_REGIONS: Number of physical memory protection regions, or 0 for no PMP. +// PMP is more useful if U mode is supported, but this is not a requirement. +parameter PMP_REGIONS = 0, + +// PMP_GRAIN: This is the "G" parameter in the privileged spec. Minimum PMP +// region size is 1 << (G + 2) bytes. If G > 0, PMCFG.A can not be set to +// NA4 (will get set to OFF instead). If G > 1, the G - 1 LSBs of pmpaddr are +// read-only-0 when PMPCFG.A is OFF, and read-only-1 when PMPCFG.A is NAPOT. +parameter PMP_GRAIN = 0, + +// PMP_MATCH_NAPOT: Enable PMP support for the NAPOT (naturally-aligned +// power-of-two) and NA4 (naturally-aligned four-byte) matching modes. When +// disabled, attempting to select these modes will set the PMP region to OFF. +parameter PMP_MATCH_NAPOT = 1, + +// PMP_MATCH_TOR: Enable PMP support for the TOR (top-of-range) matching mode. +// When disabled, attempting to select this mode will set the region to OFF. +parameter PMP_MATCH_TOR = 0, + +// PMPADDR_HARDWIRED: If a bit is 1, the corresponding region's pmpaddr and +// pmpcfg registers are read-only. PMP_GRAIN is ignored on hardwired regions. +// It's recommended to make hardwired regions the highest-numbered, so they +// can be overridden by lower-numbered regions. +parameter PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}}, + +// PMPADDR_HARDWIRED_ADDR: Values of pmpaddr registers whose PMP_HARDWIRED +// bits are set to 1. Non-hardwired regions reset to all-zeroes. +parameter PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}}, + +// PMPCFG_RESET_VAL: Values of pmpcfg registers whose PMP_HARDWIRED bits are +// set to 1. Non-hardwired regions reset to all zeroes. +parameter PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}}, + +// DEBUG_SUPPORT: Support for run/halt and instruction injection from an +// external Debug Module, support for Debug Mode, and Debug Mode CSRs. +// Requires: CSR_M_MANDATORY, CSR_M_TRAP. +parameter DEBUG_SUPPORT = 0, + +// BREAKPOINT_TRIGGERS: Number of triggers which support type=2 execute=1 +// (but not store/load=1, i.e. not a watchpoint). Requires: DEBUG_SUPPORT +parameter BREAKPOINT_TRIGGERS = 0, + +// ---------------------------------------------------------------------------- +// External interrupt support + +// NUM_IRQS: Number of external IRQs. Minimum 1, maximum 512. Note that if +// EXTENSION_XH3IRQ (Hazard3 interrupt controller) is disabled then multiple +// external interrupts are simply OR'd into mip.meip. +parameter NUM_IRQS = 1, + +// IRQ_PRIORITY_BITS: Number of priority bits implemented for each interrupt +// in meipra, if EXTENSION_XH3IRQ is enabled. The number of distinct levels +// is (1 << IRQ_PRIORITY_BITS). Minimum 0, max 4. Note that multiple priority +// levels with a large number of IRQs will have a severe effect on timing. +parameter IRQ_PRIORITY_BITS = 0, + +// IRQ_INPUT_BYPASS: disable the input registers on the external interrupts, +// to reduce latency by one cycle. Can be applied on an IRQ-by-IRQ basis. +// Ignored if EXTENSION_XH3IRQ is disabled. +parameter IRQ_INPUT_BYPASS = {(NUM_IRQS > 0 ? NUM_IRQS : 1){1'b0}}, + +// ---------------------------------------------------------------------------- +// ID registers + +// JEDEC JEP106-compliant vendor ID, can be left at 0 if "not implemented or +// [...] this is a non-commercial implementation" (RISC-V spec). +// 31:7 is continuation code count, 6:0 is ID. Parity bit is not stored. +parameter MVENDORID_VAL = 32'h0, + +// Pointer to configuration structure blob, or all-zeroes. Must be at least +// 4-byte-aligned. +parameter MCONFIGPTR_VAL = 32'h0, + +// ---------------------------------------------------------------------------- +// Performance/size options + +// REDUCED_BYPASS: Remove all forwarding paths except X->X (so back-to-back +// ALU ops can still run at 1 CPI), to save area. +parameter REDUCED_BYPASS = 0, + +// MULDIV_UNROLL: Bits per clock for multiply/divide circuit, if present. Must +// be a power of 2. +parameter MULDIV_UNROLL = 1, + +// MUL_FAST: Use single-cycle multiply circuit for MUL instructions, retiring +// to stage 3. The sequential multiply/divide circuit is still used for MULH* +parameter MUL_FAST = 0, + +// MUL_FASTER: Retire fast multiply results to stage 2 instead of stage 3. +// Throughput is the same, but latency is reduced from 2 cycles to 1 cycle. +// Requires: MUL_FAST. +parameter MUL_FASTER = 0, + +// MULH_FAST: extend the fast multiply circuit to also cover MULH*, and remove +// the multiply functionality from the sequential multiply/divide circuit. +// Requires: MUL_FAST +parameter MULH_FAST = 0, + +// FAST_BRANCHCMP: Instantiate a separate comparator (eq/lt/ltu) for branch +// comparisons, rather than using the ALU. Improves fetch address delay, +// especially if Zba extension is enabled. Disabling may save area. +parameter FAST_BRANCHCMP = 1, + +// RESET_REGFILE: whether to support reset of the general purpose registers. +// There are around 1k bits in the register file, so the reset can be +// disabled e.g. to permit block-RAM inference on FPGA. +parameter RESET_REGFILE = 0, + +// BRANCH_PREDICTOR: enable branch prediction. The branch predictor consists +// of a single BTB entry which is allocated on a taken backward branch, and +// cleared on a mispredicted nontaken branch, a fence.i or a trap. Successful +// prediction eliminates the 1-cyle fetch bubble on a taken branch, usually +// making tight loops faster. +parameter BRANCH_PREDICTOR = 0, + +// MTVEC_WMASK: Mask of which bits in mtvec are writable. Full writability is +// recommended, because a common idiom in setup code is to set mtvec just +// past code that may trap, as a hardware "try {...} catch" block. +// +// - The vectoring mode can be made fixed by clearing the LSB of MTVEC_WMASK +// +// - In vectored mode, the vector table must be aligned to its size, rounded +// up to a power of two. +parameter MTVEC_WMASK = 32'hfffffffd, + +// ---------------------------------------------------------------------------- +// Port size parameters (do not modify) + +parameter W_ADDR = 32, // Do not modify +parameter W_DATA = 32 // Do not modify diff --git a/vendor/Hazard3/hdl/hazard3_config_inst.vh b/vendor/Hazard3/hdl/hazard3_config_inst.vh new file mode 100644 index 0000000..e1118ff --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_config_inst.vh @@ -0,0 +1,59 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// Pass-through of parameters defined in hazard3_config.vh, so that these can +// be set at instantiation rather than editing the config file, and will flow +// correctly down through the hierarchy. + +.RESET_VECTOR (RESET_VECTOR), +.MTVEC_INIT (MTVEC_INIT), +.EXTENSION_A (EXTENSION_A), +.EXTENSION_C (EXTENSION_C), +.EXTENSION_E (EXTENSION_E), +.EXTENSION_M (EXTENSION_M), +.EXTENSION_ZBA (EXTENSION_ZBA), +.EXTENSION_ZBB (EXTENSION_ZBB), +.EXTENSION_ZBC (EXTENSION_ZBC), +.EXTENSION_ZBKB (EXTENSION_ZBKB), +.EXTENSION_ZBKX (EXTENSION_ZBKX), +.EXTENSION_ZBS (EXTENSION_ZBS), +.EXTENSION_ZCB (EXTENSION_ZCB), +.EXTENSION_ZCLSD (EXTENSION_ZCLSD), +.EXTENSION_ZCMP (EXTENSION_ZCMP), +.EXTENSION_ZIFENCEI (EXTENSION_ZIFENCEI), +.EXTENSION_ZILSD (EXTENSION_ZILSD), +.EXTENSION_XH3BEXTM (EXTENSION_XH3BEXTM), +.EXTENSION_XH3IRQ (EXTENSION_XH3IRQ), +.EXTENSION_XH3PMPM (EXTENSION_XH3PMPM), +.EXTENSION_XH3POWER (EXTENSION_XH3POWER), +.CSR_M_MANDATORY (CSR_M_MANDATORY), +.CSR_M_TRAP (CSR_M_TRAP), +.CSR_COUNTER (CSR_COUNTER), +.U_MODE (U_MODE), +.PMP_REGIONS (PMP_REGIONS), +.PMP_GRAIN (PMP_GRAIN), +.PMP_MATCH_NAPOT (PMP_MATCH_NAPOT), +.PMP_MATCH_TOR (PMP_MATCH_TOR), +.PMP_HARDWIRED (PMP_HARDWIRED), +.PMP_HARDWIRED_ADDR (PMP_HARDWIRED_ADDR), +.PMP_HARDWIRED_CFG (PMP_HARDWIRED_CFG), +.DEBUG_SUPPORT (DEBUG_SUPPORT), +.BREAKPOINT_TRIGGERS (BREAKPOINT_TRIGGERS), +.NUM_IRQS (NUM_IRQS), +.IRQ_PRIORITY_BITS (IRQ_PRIORITY_BITS), +.IRQ_INPUT_BYPASS (IRQ_INPUT_BYPASS), +.MVENDORID_VAL (MVENDORID_VAL), +.MCONFIGPTR_VAL (MCONFIGPTR_VAL), +.REDUCED_BYPASS (REDUCED_BYPASS), +.MULDIV_UNROLL (MULDIV_UNROLL), +.MUL_FAST (MUL_FAST), +.MUL_FASTER (MUL_FASTER), +.MULH_FAST (MULH_FAST), +.FAST_BRANCHCMP (FAST_BRANCHCMP), +.BRANCH_PREDICTOR (BRANCH_PREDICTOR), +.MTVEC_WMASK (MTVEC_WMASK), +.RESET_REGFILE (RESET_REGFILE), +.W_ADDR (W_ADDR), +.W_DATA (W_DATA) diff --git a/vendor/Hazard3/hdl/hazard3_core.v b/vendor/Hazard3/hdl/hazard3_core.v new file mode 100644 index 0000000..1809b6c --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_core.v @@ -0,0 +1,1590 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2025 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +`ifdef HAZARD3_RVFI_STANDALONE +`include "hazard3_rvfi_standalone_defs.vh" +`endif + +`default_nettype none + +module hazard3_core #( +`include "hazard3_config.vh" +, +`include "hazard3_width_const.vh" +) ( + // Global signals + input wire clk, + input wire clk_always_on, + input wire rst_n, + + // Power control signals + output wire pwrup_req, + input wire pwrup_ack, + output wire clk_en, + output reg unblock_out, + input wire unblock_in, + + `ifdef RISCV_FORMAL + `RVFI_OUTPUTS , + `endif + + // Instruction fetch port + output wire bus_aph_req_i, + output wire bus_aph_panic_i, // e.g. branch mispredict + flush + input wire bus_aph_ready_i, + input wire bus_dph_ready_i, + input wire bus_dph_err_i, + + output wire [W_ADDR-1:0] bus_haddr_i, + output wire [2:0] bus_hsize_i, + output wire bus_priv_i, + input wire [W_DATA-1:0] bus_rdata_i, + + // Load/store port + output reg bus_aph_req_d, + output wire bus_aph_excl_d, + input wire bus_aph_ready_d, + input wire bus_dph_ready_d, + input wire bus_dph_err_d, + input wire bus_dph_exokay_d, + + output reg [W_ADDR-1:0] bus_haddr_d, + output reg [2:0] bus_hsize_d, + output reg bus_priv_d, + output reg bus_hwrite_d, + output reg [W_DATA-1:0] bus_wdata_d, + input wire [W_DATA-1:0] bus_rdata_d, + + // Memory ordering signals + output wire fence_i_vld, + output wire fence_d_vld, + input wire fence_rdy, + + // Debugger run/halt control + input wire dbg_req_halt, + input wire dbg_req_halt_on_reset, + input wire dbg_req_resume, + output wire dbg_halted, + output wire dbg_running, + // Debugger access to data0 CSR + input wire [W_DATA-1:0] dbg_data0_rdata, + output wire [W_DATA-1:0] dbg_data0_wdata, + output wire dbg_data0_wen, + // Debugger instruction injection + input wire [W_DATA-1:0] dbg_instr_data, + input wire dbg_instr_data_vld, + output wire dbg_instr_data_rdy, + output wire dbg_instr_caught_exception, + output wire dbg_instr_caught_ebreak, + + // Identification CSR values + input wire [W_DATA-1:0] mhartid_val, + input wire [3:0] eco_version, + + // Level-sensitive interrupt sources + input wire [NUM_IRQS-1:0] irq, // -> mip.meip + input wire soft_irq, // -> mip.msip + input wire timer_irq // -> mip.mtip +); + +`include "hazard3_ops.vh" +`include "rv_opcodes.vh" + +wire x_stall; +wire m_stall; + +localparam HSIZE_WORD = 3'd2; +localparam HSIZE_HWORD = 3'd1; +localparam HSIZE_BYTE = 3'd0; + +localparam [4:0] REGADDR_MASK = {~|EXTENSION_E, 4'hf}; + +wire debug_mode; +assign dbg_halted = DEBUG_SUPPORT && debug_mode; +assign dbg_running = DEBUG_SUPPORT && !debug_mode; + +// ---------------------------------------------------------------------------- +// Pipe Stage F + +wire f_jump_req; +wire [W_ADDR-1:0] f_jump_target; +wire f_jump_priv; +wire f_jump_rdy; +wire f_jump_now = f_jump_req && f_jump_rdy; + +wire f_frontend_pwrdown_ok; + +// Predecoded register numbers, for register file access +wire [W_REGADDR-1:0] f_rs1_coarse; +wire [W_REGADDR-1:0] f_rs2_coarse; +wire [W_REGADDR-1:0] f_rs1_fine; +wire [W_REGADDR-1:0] f_rs2_fine; + +wire [31:0] fd_cir; +wire [31:0] fd_cir_raw; +wire [1:0] fd_cir_err; +wire fd_cir_break_any; +wire fd_cir_break_d_mode; +wire [1:0] fd_cir_predbranch; +wire [1:0] fd_cir_vld; +wire fd_cir_is_32bit; +wire fd_cir_invalid_16bit; +wire fd_cir_is_uop; +wire fd_cir_uop_nonfinal; +wire fd_cir_uop_no_pc_update; +wire fd_cir_uop_atomic; + +wire [1:0] df_cir_use; +wire df_cir_flush_behind; + +wire df_uop_stall; +wire df_uop_clear; +wire df_lspair_phase_next; + +wire x_btb_set; +wire [W_ADDR-1:0] x_btb_set_src_addr; +wire x_btb_set_src_size; +wire [W_ADDR-1:0] x_btb_set_target_addr; +wire x_btb_clear; + +wire [W_ADDR-1:0] d_btb_target_addr; + +wire [W_ADDR-1:0] f_pmp_i_addr; +wire f_pmp_i_m_mode; +wire f_pmp_i_kill; + +wire [W_ADDR-1:0] f_trigger_addr; +wire f_trigger_m_mode; +wire [1:0] f_trigger_break_any; +wire [1:0] f_trigger_break_d_mode; + +assign bus_aph_panic_i = 1'b0; + +wire f_mem_size; +assign bus_hsize_i = f_mem_size ? HSIZE_WORD : HSIZE_HWORD; + +hazard3_frontend #( +`include "hazard3_config_inst.vh" +) frontend ( + .clk (clk), + .rst_n (rst_n), + + .mem_size (f_mem_size), + .mem_addr (bus_haddr_i), + .mem_priv (bus_priv_i), + .mem_addr_vld (bus_aph_req_i), + .mem_addr_rdy (bus_aph_ready_i), + + .mem_data (bus_rdata_i), + .mem_data_err (bus_dph_err_i), + .mem_data_vld (bus_dph_ready_i), + + .jump_target (f_jump_target), + .jump_priv (f_jump_priv), + .jump_target_vld (f_jump_req), + .jump_target_rdy (f_jump_rdy), + + .btb_set (x_btb_set), + .btb_set_src_addr (x_btb_set_src_addr), + .btb_set_src_size (x_btb_set_src_size), + .btb_set_target_addr (x_btb_set_target_addr), + .btb_clear (x_btb_clear), + .btb_target_addr_out (d_btb_target_addr), + + .cir (fd_cir), + .cir_raw (fd_cir_raw), + .cir_err (fd_cir_err), + .cir_predbranch (fd_cir_predbranch), + .cir_break_any (fd_cir_break_any), + .cir_break_d_mode (fd_cir_break_d_mode), + .cir_vld (fd_cir_vld), + .cir_is_32bit (fd_cir_is_32bit), + .cir_invalid_16bit (fd_cir_invalid_16bit), + .cir_is_uop (fd_cir_is_uop), + .cir_uop_nonfinal (fd_cir_uop_nonfinal), + .cir_uop_no_pc_update (fd_cir_uop_no_pc_update), + .cir_uop_atomic (fd_cir_uop_atomic), + + .cir_use (df_cir_use), + .cir_flush_behind (df_cir_flush_behind), + .uop_stall (df_uop_stall), + .uop_clear (df_uop_clear), + .df_lspair_phase_next (df_lspair_phase_next), + + .pwrdown_ok (f_frontend_pwrdown_ok), + .delay_first_fetch (!pwrup_ack), + + .predecode_rs1_coarse (f_rs1_coarse), + .predecode_rs2_coarse (f_rs2_coarse), + .predecode_rs1_fine (f_rs1_fine), + .predecode_rs2_fine (f_rs2_fine), + + .debug_mode (debug_mode), + .dbg_instr_data (dbg_instr_data), + .dbg_instr_data_vld (dbg_instr_data_vld), + .dbg_instr_data_rdy (dbg_instr_data_rdy), + + .pmp_i_addr (f_pmp_i_addr), + .pmp_i_m_mode (f_pmp_i_m_mode), + .pmp_i_kill (f_pmp_i_kill), + + .trigger_addr (f_trigger_addr), + .trigger_m_mode (f_trigger_m_mode), + .trigger_break_any (f_trigger_break_any), + .trigger_break_d_mode (f_trigger_break_d_mode) +); + +// ---------------------------------------------------------------------------- +// Pipe Stage X (Decode Logic) + +// X-check on pieces of instruction which frontend claims are valid +`ifdef HAZARD3_X_CHECKS +always @ (posedge clk) begin + if (rst_n) begin + if (|fd_cir_vld && (^fd_cir[15:0] === 1'bx)) begin + $display("CIR LSBs are X, should be valid!"); + $finish; + end + if (fd_cir_vld[1] && (^fd_cir === 1'bX)) begin + $display("CIR contains X, should be fully valid!"); + $finish; + end + end +end +`endif + +// To X +wire d_starved; +wire [W_DATA-1:0] d_imm; +wire [W_REGADDR-1:0] d_rs1; +wire [W_REGADDR-1:0] d_rs2; +wire [W_REGADDR-1:0] d_rd; +wire [2:0] d_funct3_32b; +wire [6:0] d_funct7_32b; +wire [W_ALUSRC-1:0] d_alusrc_a; +wire [W_ALUSRC-1:0] d_alusrc_b; +wire [W_ALUOP-1:0] d_aluop; +wire [W_MEMOP-1:0] d_memop; +wire [W_MULOP-1:0] d_mulop; +wire [W_BCOND-1:0] d_branchcond; +wire [W_ADDR-1:0] d_addr_offs; +wire d_addr_is_regoffs; +wire [W_ADDR-1:0] d_pc; +wire [W_EXCEPT-1:0] d_except; +wire d_sleep_wfi; +wire d_sleep_block; +wire d_sleep_unblock; +wire d_no_pc_increment; +wire d_uninterruptible; +wire d_fence_i; +wire d_fence_d; +wire d_csr_ren; +wire d_csr_wen; +wire [1:0] d_csr_wtype; +wire d_csr_w_imm; +wire [W_ADDR-1:0] d_lspair_offset; + +wire x_jump_not_except; +wire x_mmode_execution; +wire x_trap_wfi; + +wire [W_ADDR-1:0] debug_dpc_wdata; +wire debug_dpc_wen; +wire [W_ADDR-1:0] debug_dpc_rdata; + +hazard3_decode #( +`include "hazard3_config_inst.vh" +) decode_u ( + .clk (clk), + .rst_n (rst_n), + + .fd_cir (fd_cir), + .fd_cir_err (fd_cir_err), + .fd_cir_predbranch (fd_cir_predbranch), + .fd_cir_vld (fd_cir_vld), + .fd_cir_is_32bit (fd_cir_is_32bit), + .fd_cir_invalid_16bit (fd_cir_invalid_16bit), + .fd_cir_is_uop (fd_cir_is_uop), + .fd_cir_uop_nonfinal (fd_cir_uop_nonfinal), + .fd_cir_uop_no_pc_update (fd_cir_uop_no_pc_update), + .fd_cir_uop_atomic (fd_cir_uop_atomic), + + .df_cir_use (df_cir_use), + .df_cir_flush_behind (df_cir_flush_behind), + .df_uop_stall (df_uop_stall), + .df_uop_clear (df_uop_clear), + .df_lspair_phase_next (df_lspair_phase_next), + + .d_pc (d_pc), + .x_jump_not_except (x_jump_not_except), + + .debug_mode (debug_mode), + .m_mode (x_mmode_execution), + .trap_wfi (x_trap_wfi), + + .debug_dpc_wdata (debug_dpc_wdata), + .debug_dpc_wen (debug_dpc_wen), + .debug_dpc_rdata (debug_dpc_rdata), + + .d_starved (d_starved), + .x_stall (x_stall), + .f_jump_now (f_jump_now), + .f_jump_target (f_jump_target), + .d_btb_target_addr (d_btb_target_addr), + + .d_imm (d_imm), + .d_rs1 (d_rs1), + .d_rs2 (d_rs2), + .d_rd (d_rd), + .d_funct3_32b (d_funct3_32b), + .d_funct7_32b (d_funct7_32b), + .d_alusrc_a (d_alusrc_a), + .d_alusrc_b (d_alusrc_b), + .d_aluop (d_aluop), + .d_memop (d_memop), + .d_mulop (d_mulop), + .d_csr_ren (d_csr_ren), + .d_csr_wen (d_csr_wen), + .d_csr_wtype (d_csr_wtype), + .d_csr_w_imm (d_csr_w_imm), + .d_branchcond (d_branchcond), + .d_addr_offs (d_addr_offs), + .d_addr_is_regoffs (d_addr_is_regoffs), + .d_except (d_except), + .d_sleep_wfi (d_sleep_wfi), + .d_sleep_block (d_sleep_block), + .d_sleep_unblock (d_sleep_unblock), + .d_no_pc_increment (d_no_pc_increment), + .d_uninterruptible (d_uninterruptible), + .d_lspair_offset (d_lspair_offset), + .d_fence_i (d_fence_i), + .d_fence_d (d_fence_d) +); + +// ---------------------------------------------------------------------------- +// Pipe Stage X (Execution Logic) + +// Register the write which took place to the regfile on previous cycle, and bypass. +// This is an alternative to a write -> read bypass in the regfile, +// which we can't implement whilst maintaining BRAM inference compatibility (iCE40). +reg [W_REGADDR-1:0] mw_rd; +reg [W_DATA-1:0] mw_result; + +// From register file: +wire [W_DATA-1:0] x_rdata1; +wire [W_DATA-1:0] x_rdata2; + +// Combinational regs for muxing +reg [W_DATA-1:0] x_rs1_bypass; +reg [W_DATA-1:0] x_rs2_bypass; +reg [W_DATA-1:0] x_op_a; +reg [W_DATA-1:0] x_op_b; +wire [W_DATA-1:0] x_alu_result; +wire x_alu_cmp; + +wire [W_DATA-1:0] m_trap_addr; +wire m_trap_is_irq; +wire m_trap_enter_vld; +wire m_trap_enter_soon; +wire m_trap_enter_rdy = f_jump_rdy; + +wire x_mmode_loadstore; +wire m_mmode_trap_entry; + +reg [W_REGADDR-1:0] xm_rs1; +reg [W_REGADDR-1:0] xm_rs2; +reg [W_REGADDR-1:0] xm_rd; +reg [W_DATA-1:0] xm_result; +reg [1:0] xm_addr_align; +reg [W_MEMOP-1:0] xm_memop; +reg [W_EXCEPT-1:0] xm_except; +reg xm_except_to_d_mode; +reg xm_no_pc_increment; +reg xm_sleep_wfi; +reg xm_sleep_block; +reg xm_delay_irq_entry_on_ls_stagex; + +// ---------------------------------------------------------------------------- +// Stall logic + +// IRQs squeeze in between the instructions in X and M, so in this case X +// stalls but M can continue. -> X always stalls on M trap, M *may* stall. +wire x_stall_on_trap = m_trap_enter_vld && !m_trap_enter_rdy || + m_trap_enter_soon && !m_trap_enter_vld; + +// Stall inserted to avoid illegal pipelining of exclusive accesses on the bus +// (also gives time to update local monitor on direct lr.w -> sc.w instruction +// sequences). Note we don't check for AMOs in stage M, because AMOs fully +// fence off on their own completion before passing down the pipe. + +wire d_memop_is_amo = |EXTENSION_A && d_memop == MEMOP_AMO; + +wire x_stall_on_exclusive_overlap = |EXTENSION_A && ( + (d_memop_is_amo || d_memop == MEMOP_SC_W || d_memop == MEMOP_LR_W) && + (xm_memop == MEMOP_SC_W || xm_memop == MEMOP_LR_W) +); + +// AMOs are issued completely from X. We keep X stalled, and pass bubbles into +// M. Otherwise the exception handling would be even more of a mess. Phases +// 0-3 are read/write address/data phases. Phase 4 is error, due to HRESP or +// due to low HEXOKAY response to read. + +// Also need to clear AMO if it follows an excepting instruction. Note we +// still stall on phase 3 when hready is high if hresp is also high, since we +// then proceed to phase 4 for the error response. + +reg [2:0] x_amo_phase; +wire x_stall_on_amo = |EXTENSION_A && d_memop_is_amo && !m_trap_enter_soon && ( + x_amo_phase < 3'h3 || (x_amo_phase == 3'h3 && (!bus_dph_ready_d || !bus_dph_exokay_d || bus_dph_err_d)) +); + +// Read-after-write hazard detection (e.g. load-use) + +wire m_fast_mul_result_vld; +wire m_generating_result = + (xm_memop < MEMOP_SW) || + (|EXTENSION_A && xm_memop == MEMOP_LR_W) || + (|EXTENSION_A && xm_memop == MEMOP_SC_W) || // sc.w success result is data phase + (|EXTENSION_M && m_fast_mul_result_vld); + +reg x_stall_on_raw; + +always @ (*) begin + x_stall_on_raw = 1'b0; + if (REDUCED_BYPASS) begin + x_stall_on_raw = + |xm_rd && (xm_rd == d_rs1 || xm_rd == d_rs2) || + |mw_rd && (mw_rd == d_rs1 || mw_rd == d_rs2); + end else if (m_generating_result) begin + // With the full bypass network, load-use (or fast multiply-use) is the only RAW stall + if (|xm_rd && xm_rd == d_rs1) begin + // Store addresses cannot be bypassed later, so there is no exception here. + x_stall_on_raw = 1'b1; + end else if (|xm_rd && xm_rd == d_rs2) begin + // Store data can be bypassed in M. Any other instructions must stall. + x_stall_on_raw = !(d_memop == MEMOP_SW || d_memop == MEMOP_SH || d_memop == MEMOP_SB); + end + end +end + +wire x_stall_on_fence; +wire x_stall_muldiv; +wire x_jump_req; + +assign x_stall = + m_stall || + x_stall_on_trap || + x_stall_on_exclusive_overlap || + x_stall_on_amo || + x_stall_on_raw || + x_stall_on_fence || + x_stall_muldiv || + bus_aph_req_d && !bus_aph_ready_d || + x_jump_req && !f_jump_rdy; + +wire m_sleep_stall_release; + +wire x_loadstore_pmp_fail; +wire x_trig_step_break; +wire x_trig_break = fd_cir_break_any || x_trig_step_break; +wire x_trig_break_d_mode = fd_cir_break_d_mode; + +// ---------------------------------------------------------------------------- +// Execution logic + +// ALU, operand muxes and bypass + +// Approximate regnums were predecoded in stage 1, for regfile read. +// (Approximate in the sense that they are invalid when the instruction +// doesn't *have* a register operand on that port.) These aren't usable for +// hazard checking but are fine for bypass, and make the bypass mux +// independent of stage 2 decode. + +reg [W_REGADDR-1:0] d_rs1_predecoded; +reg [W_REGADDR-1:0] d_rs2_predecoded; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + d_rs1_predecoded <= {W_REGADDR{1'b0}}; + d_rs2_predecoded <= {W_REGADDR{1'b0}}; + end else if (d_starved || !x_stall) begin + d_rs1_predecoded <= f_rs1_fine & REGADDR_MASK; + d_rs2_predecoded <= f_rs2_fine & REGADDR_MASK; + end +end + +wire [W_REGADDR-1:0] d_rs1_predecoded_nxt = REGADDR_MASK & ( + d_starved || !x_stall ? f_rs1_fine : d_rs1_predecoded +); +wire [W_REGADDR-1:0] d_rs2_predecoded_nxt = REGADDR_MASK & ( + d_starved || !x_stall ? f_rs2_fine : d_rs2_predecoded +); + +wire [W_REGADDR-1:0] xm_rd_nxt; +wire [W_REGADDR-1:0] mw_rd_nxt; + +reg [2:0] x_rs1_select_regs_xm_mw; +reg [2:0] x_rs2_select_regs_xm_mw; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + x_rs1_select_regs_xm_mw <= 3'b000; + x_rs2_select_regs_xm_mw <= 3'b000; + end else begin + x_rs1_select_regs_xm_mw <= + 5'h00 == d_rs1_predecoded_nxt ? 3'b000 : + xm_rd_nxt == d_rs1_predecoded_nxt ? 3'b010 : + mw_rd_nxt == d_rs1_predecoded_nxt ? 3'b001 : 3'b100; + x_rs2_select_regs_xm_mw <= + 5'h00 == d_rs2_predecoded_nxt ? 3'b000 : + xm_rd_nxt == d_rs2_predecoded_nxt ? 3'b010 : + mw_rd_nxt == d_rs2_predecoded_nxt ? 3'b001 : 3'b100; + end +end + +`ifdef HAZARD3_ASSERTIONS +always @ (posedge clk) if (rst_n && !x_stall) begin + // If stage 2 sees a reg operand, it must have been correctly predecoded too. + if (|d_rs1) + assert(d_rs1_predecoded == d_rs1); + if (|d_rs2) + assert(d_rs2_predecoded == d_rs2); + // If no reg was predecoded, stage 2 decode must agree there is no reg operand. + if (~|d_rs1_predecoded) + assert(~|d_rs1); + if (~|d_rs2_predecoded) + assert(~|d_rs2); + // Make sure bypass mask updates to track older in-flight instructions + if (|d_rs1 && d_rs1 == xm_rd) assert(x_rs1_select_regs_xm_mw[1]); + else if (|d_rs1 && d_rs1 == mw_rd) assert(x_rs1_select_regs_xm_mw[0]); + else if (|d_rs1 ) assert(x_rs1_select_regs_xm_mw[2]); + else if (~|d_rs1_predecoded ) assert(~|x_rs1_select_regs_xm_mw); + if (|d_rs2 && d_rs2 == xm_rd) assert(x_rs2_select_regs_xm_mw[1]); + else if (|d_rs2 && d_rs2 == mw_rd) assert(x_rs2_select_regs_xm_mw[0]); + else if (|d_rs2 ) assert(x_rs2_select_regs_xm_mw[2]); + else if (~|d_rs2_predecoded ) assert(~|x_rs2_select_regs_xm_mw); +end +`endif + + +always @ (*) begin + + x_rs1_bypass = + (x_rdata1 & {W_DATA{x_rs1_select_regs_xm_mw[2]}}) | + (xm_result & {W_DATA{x_rs1_select_regs_xm_mw[1]}}) | + (mw_result & {W_DATA{x_rs1_select_regs_xm_mw[0]}}); + + x_rs2_bypass = + (x_rdata2 & {W_DATA{x_rs2_select_regs_xm_mw[2]}}) | + (xm_result & {W_DATA{x_rs2_select_regs_xm_mw[1]}}) | + (mw_result & {W_DATA{x_rs2_select_regs_xm_mw[0]}}); + + // AMO captures rdata into mw_result at end of read data phase, so we can + // feed back through the ALU. + if (|EXTENSION_A && x_amo_phase == 3'h2) + x_op_a = mw_result; + else if (d_alusrc_a) + x_op_a = d_pc; + else + x_op_a = x_rs1_bypass; + + if (d_alusrc_b) + x_op_b = d_imm; + else + x_op_b = x_rs2_bypass; +end + +hazard3_alu #( +`include "hazard3_config_inst.vh" +) alu ( + .aluop (d_aluop), + .funct3_32b (d_funct3_32b), + .funct7_32b (d_funct7_32b), + .op_a (x_op_a), + .op_b (x_op_b), + .result (x_alu_result), + .cmp (x_alu_cmp) +); + +// Load/store bus request + +wire x_unaligned_addr = d_memop != MEMOP_NONE && ( + bus_hsize_d == HSIZE_WORD && |bus_haddr_d[1:0] || + bus_hsize_d == HSIZE_HWORD && bus_haddr_d[0] +); + +reg mw_local_exclusive_reserved; + +wire x_memop_vld = d_memop != MEMOP_NONE && !( + |EXTENSION_A && d_memop == MEMOP_SC_W && !mw_local_exclusive_reserved || + |EXTENSION_A && d_memop_is_amo && x_amo_phase != 3'h0 && x_amo_phase != 3'h2 +); + +wire x_memop_write = + d_memop == MEMOP_SW || + d_memop == MEMOP_SH || + d_memop == MEMOP_SB || + (|EXTENSION_A && d_memop == MEMOP_SC_W) || + (|EXTENSION_A && d_memop_is_amo && x_amo_phase == 3'h2); + +// Always query the global monitor, except for store-conditional suppressed by local monitor. +assign bus_aph_excl_d = |EXTENSION_A && ( + d_memop == MEMOP_LR_W || + d_memop == MEMOP_SC_W || + d_memop_is_amo +); + +// AMO stalls the pipe, then generates two bus transfers per 4-cycle +// iteration, unless it bails out due to a bus fault or failed load +// reservation. +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + x_amo_phase <= 3'h0; + end else if (|EXTENSION_A && d_memop_is_amo && ( + bus_aph_ready_d || bus_dph_ready_d || + m_trap_enter_vld || x_unaligned_addr || x_loadstore_pmp_fail || + x_amo_phase == 3'h4 + )) begin + if (m_trap_enter_vld) begin + // Bail out, squash the in-progress AMO. + x_amo_phase <= 3'h0; + end else if (x_stall_on_raw || x_stall_on_exclusive_overlap || m_trap_enter_soon) begin + // First address phase stalled due to address dependency on + // previous load/mul/etc. Shouldn't be possible in later phases. + x_amo_phase <= 3'h0; + end else if (x_amo_phase == 3'h4) begin + // Clear fault phase once it goes through to stage 3 and excepts + if (!x_stall) + x_amo_phase <= 3'h0; + end else if (x_unaligned_addr || x_loadstore_pmp_fail) begin + x_amo_phase <= 3'h4; + end else if (x_amo_phase == 3'h1 && !bus_dph_exokay_d) begin + // Load reserve fail indicates the memory region does not support + // exclusives, so we will never succeed at store. Exception. + x_amo_phase <= 3'h4; + end else if ((x_amo_phase == 3'h1 || x_amo_phase == 3'h3) && bus_dph_err_d) begin + // Bus fault. Exception. + x_amo_phase <= 3'h4; + end else if (x_amo_phase == 3'h3) begin + // Either we're done, or the write failed. Either way, back to the start. + x_amo_phase <= 3'h0; + end else begin + // Default progression: read addr -> read data -> write addr -> write data + x_amo_phase <= x_amo_phase + 3'h1; + end + end +end + +`ifdef HAZARD3_ASSERTIONS +// Assertions for above block, extracted to avoid making assertions +// reset-sensitive (limitation of yosys-smtbmc) +always @ (posedge clk) if (rst_n) begin + if (|EXTENSION_A && d_memop_is_amo && ( + bus_aph_ready_d || bus_dph_ready_d || + m_trap_enter_vld || x_unaligned_addr || + x_amo_phase == 3'h4 + )) begin + if (m_trap_enter_vld) begin + // Should only happen during an address phase, *or* the fault phase. + assert(x_amo_phase == 3'h0 || x_amo_phase == 3'h2 || x_amo_phase == 3'h4); + // The fault phase only holds when we have a misaligned AMO directly behind + // a regular memory access that subsequently excepts, and the AMO has gone + // straight to fault phase due to misalignment. + if (x_amo_phase == 3'h4) + assert(x_unaligned_addr); + end else if (x_stall_on_raw || x_stall_on_exclusive_overlap || m_trap_enter_soon) begin + assert(x_amo_phase == 3'h0); + end else if (x_amo_phase == 3'h4) begin + // This should only happen when we are stalled on an older load/store etc + assert(!(x_stall && !m_stall)); + end + end +end +`endif + +`ifdef HAZARD3_ASSERTIONS +// General AMO design assertions +reg prop_dph_is_excl = 1'b0; +always @ (posedge clk) if (rst_n) begin + if (bus_aph_ready_d && bus_aph_req_d) begin + prop_dph_is_excl <= bus_aph_excl_d; + end else if (bus_dph_ready_d) begin + // It's possible for the dphase to end without the aphase ending due + // to e.g. SBA arbitration + prop_dph_is_excl <= 1'b0; + end + // Other states should be unreachable + assert(x_amo_phase <= 3'h4); + // First state should be 0 -- don't want anything carried from one AMO to the next. + if (x_stall_on_amo && !$past(x_stall_on_amo)) + assert(x_amo_phase == 3'h0); + // Should be in resting state between AMOs + if (!d_memop_is_amo) + assert(x_amo_phase == 3'h0); + // Error phase should have no stage 2 blockers, so it can pass to stage 3 to + // raise exception entry. It's ok to block behind a younger instruction, but.. + if (x_amo_phase == 3'h4) + assert(!x_stall || m_stall); + // ..the only way to reach AMO error phase without stage 3 clearing out should + // be an unaligned AMO address, which goes straight to error phase. + if (x_amo_phase == 3'h4 && m_stall) + assert(x_unaligned_addr); + // Should be impossible to get an unaligned address in the write address + // phase, since it would be picked up in the read address phase + if (x_amo_phase == 3'h2) + assert(!x_unaligned_addr); + // Error phase is either due to a bus response, or a misaligned address. + // Neither of these are write-address-phase. + if (x_amo_phase == 3'h4) + assert($past(x_amo_phase) != 3'h2); + // Make sure M is unstalled for passing store data through in phase 2 + if (x_amo_phase == 3'h2) + assert(!m_stall); + // Make sure we don't pipeline AMO aphases with other exclusive dphases + if (d_memop_is_amo && bus_aph_req_d) + assert(!prop_dph_is_excl); + // Only phases 0 (read aphase) and 2 (write aphase) assert new bus transfers + if (d_memop_is_amo && bus_aph_req_d) + assert(x_amo_phase == 3'h0 || x_amo_phase == 3'h2); +end +`endif + +// This adder is used for both branch targets and load/store addresses. +// Supporting all branch types already requires rs1 + I-fmt, and pc + B-fmt. +// B-fmt are almost identical to S-fmt, so we rs1 + S-fmt is almost free. + +wire [W_ADDR-1:0] x_addr_sum = + (d_lspair_offset & {W_ADDR{|EXTENSION_ZILSD}}) + + (d_addr_is_regoffs ? x_rs1_bypass : d_pc) + + d_addr_offs; + +always @ (*) begin + // Need to be careful not to use anything hready-sourced to gate htrans! + bus_haddr_d = x_addr_sum; + bus_hwrite_d = x_memop_write; + bus_priv_d = x_mmode_loadstore; + case (d_memop) + MEMOP_LW: bus_hsize_d = HSIZE_WORD; + MEMOP_SW: bus_hsize_d = HSIZE_WORD; + MEMOP_LH: bus_hsize_d = HSIZE_HWORD; + MEMOP_LHU: bus_hsize_d = HSIZE_HWORD; + MEMOP_SH: bus_hsize_d = HSIZE_HWORD; + MEMOP_LB: bus_hsize_d = HSIZE_BYTE; + MEMOP_LBU: bus_hsize_d = HSIZE_BYTE; + MEMOP_SB: bus_hsize_d = HSIZE_BYTE; + default: bus_hsize_d = HSIZE_WORD; + endcase + bus_aph_req_d = x_memop_vld && !( + x_stall_on_raw || + x_stall_on_exclusive_overlap || + x_loadstore_pmp_fail || + x_trig_break || + x_unaligned_addr || + m_trap_enter_soon || + ((xm_sleep_wfi || xm_sleep_block) && !m_sleep_stall_release) + ); +end + +// Fences are not issued until relevant buses are quiet. +wire m_dphase_in_flight; +assign x_stall_on_fence = + (d_fence_i && !(fence_rdy && !m_dphase_in_flight && f_frontend_pwrdown_ok)) || + (d_fence_d && !(fence_rdy && !m_dphase_in_flight)); + +wire x_fence_mask = + x_trig_break || + m_dphase_in_flight || + m_trap_enter_soon || + ((xm_sleep_wfi || xm_sleep_block) && !m_sleep_stall_release); + +assign fence_i_vld = d_fence_i && !x_fence_mask && f_frontend_pwrdown_ok; +assign fence_d_vld = d_fence_d && !x_fence_mask; + +// Multiply/divide + +wire [W_DATA-1:0] x_muldiv_result; +wire [W_DATA-1:0] m_fast_mul_result; + +wire x_use_fast_mul = d_aluop == ALUOP_MULDIV && ( + (MUL_FAST && d_mulop == M_OP_MUL ) || + (MULH_FAST && d_mulop == M_OP_MULH ) || + (MULH_FAST && d_mulop == M_OP_MULHU ) || + (MULH_FAST && d_mulop == M_OP_MULHSU) +); + +generate +if (EXTENSION_M) begin: has_muldiv + wire x_muldiv_op_vld; + wire x_muldiv_op_rdy; + wire x_muldiv_result_vld; + wire [W_DATA-1:0] x_muldiv_result_h; + wire [W_DATA-1:0] x_muldiv_result_l; + + reg x_muldiv_posted; + always @ (posedge clk or negedge rst_n) + if (!rst_n) + x_muldiv_posted <= 1'b0; + else + x_muldiv_posted <= (x_muldiv_posted || (x_muldiv_op_vld && x_muldiv_op_rdy)) && x_stall; + + wire x_muldiv_kill = m_trap_enter_soon; + + assign x_muldiv_op_vld = (d_aluop == ALUOP_MULDIV && !x_use_fast_mul) + && !(x_muldiv_posted || x_stall_on_raw || x_muldiv_kill); + + hazard3_muldiv_seq #( + `include "hazard3_config_inst.vh" + ) muldiv ( + .clk (clk), + .rst_n (rst_n), + .op (d_mulop), + .op_vld (x_muldiv_op_vld), + .op_rdy (x_muldiv_op_rdy), + .op_kill (x_muldiv_kill), + .op_a (x_rs1_bypass), + .op_b (x_rs2_bypass), + + .result_h (x_muldiv_result_h), + .result_l (x_muldiv_result_l), + .result_vld (x_muldiv_result_vld) + ); + + wire x_muldiv_result_is_high = + d_mulop == M_OP_MULH || + d_mulop == M_OP_MULHSU || + d_mulop == M_OP_MULHU || + d_mulop == M_OP_REM || + d_mulop == M_OP_REMU; + assign x_muldiv_result = x_muldiv_result_is_high ? x_muldiv_result_h : x_muldiv_result_l; + assign x_stall_muldiv = x_muldiv_op_vld || !x_muldiv_result_vld; + + if (MUL_FAST) begin: has_fast_mul + + // If MUL_FASTER is set, the multiplier produces its result + // combinatorially, so we never have to post a mul result to stage 3. + wire x_issue_fast_mul = x_use_fast_mul && |d_rd && !x_stall && !MUL_FASTER; + + hazard3_mul_fast #( + `include "hazard3_config_inst.vh" + ) mul_fast ( + .clk (clk), + .rst_n (rst_n), + + .op_vld (x_issue_fast_mul), + .op (d_mulop), + .op_a (x_rs1_bypass), + .op_b (x_rs2_bypass), + + .result (m_fast_mul_result), + .result_vld (m_fast_mul_result_vld) + ); + + end else begin: no_fast_mul + + assign m_fast_mul_result = {W_DATA{1'b0}}; + assign m_fast_mul_result_vld = 1'b0; + + end + +`ifdef HAZARD3_ASSERTIONS + always @ (posedge clk) if (d_aluop != ALUOP_MULDIV) assert(!x_stall_muldiv); +`endif + +end else begin: no_muldiv + + assign x_muldiv_result = {W_DATA{1'b0}}; + assign m_fast_mul_result = {W_DATA{1'b0}}; + assign m_fast_mul_result_vld = 1'b0; + assign x_stall_muldiv = 1'b0; + +end +endgenerate + +// Branch handling + +// For JALR, the LSB of the result must be cleared by hardware +wire [W_ADDR-1:0] x_jump_target = x_addr_sum & ~32'd1; +wire x_jump_misaligned = ~|EXTENSION_C && x_addr_sum[1]; +wire x_branch_cmp_noinvert; + +wire x_branch_was_predicted = |BRANCH_PREDICTOR && fd_cir_predbranch[0]; +wire x_branch_cmp = x_branch_cmp_noinvert ^ x_branch_was_predicted; + +generate +if (~|FAST_BRANCHCMP) begin: alu_branchcmp + + assign x_branch_cmp_noinvert = x_alu_cmp; + +end else begin: fast_branchcmp + + hazard3_branchcmp #( + `include "hazard3_config_inst.vh" + ) branchcmp_u ( + .cir (fd_cir), + .op_a (x_rs1_bypass), + .op_b (x_rs2_bypass), + .cmp (x_branch_cmp_noinvert) + ); + +end +endgenerate + +// Be careful not to take branches whose comparisons depend on a load result +wire x_jump_req_unchecked = !x_stall_on_raw && ( + d_branchcond == BCOND_ALWAYS || + d_branchcond == BCOND_ZERO && !x_branch_cmp || + d_branchcond == BCOND_NZERO && x_branch_cmp +); + +assign x_jump_req = x_jump_req_unchecked && !x_jump_misaligned && !x_trig_break; + +assign x_btb_set = |BRANCH_PREDICTOR && ( + x_jump_req_unchecked && d_addr_offs[W_ADDR - 1] && !x_branch_was_predicted && + (d_branchcond == BCOND_NZERO || d_branchcond == BCOND_ZERO) +); + +assign x_btb_clear = d_fence_i || (m_trap_enter_vld && m_trap_enter_rdy) || (|BRANCH_PREDICTOR && ( + x_branch_was_predicted && x_jump_req_unchecked +)); + +assign x_btb_set_src_addr = d_pc; +assign x_btb_set_target_addr = x_jump_target; +assign x_btb_set_src_size = fd_cir_is_32bit; + +// Memory protection + +wire [11:0] x_pmp_cfg_addr; +wire x_pmp_cfg_wen; +wire [W_DATA-1:0] x_pmp_cfg_wdata; +wire [W_DATA-1:0] x_pmp_cfg_rdata; + +generate +if (PMP_REGIONS > 0) begin: have_pmp + + wire x_loadstore_pmp_badperm; + assign x_loadstore_pmp_fail = x_loadstore_pmp_badperm && x_memop_vld && !debug_mode; + + // R and W are enforced at the point the load/store address is generated, + // in time to mask the address-phase request. X is enforced by checking + // fetch addresses during fetch data phase (stage F) and promoting PMP + // fails to bus faults. + + hazard3_pmp #( + `include "hazard3_config_inst.vh" + ) pmp ( + .clk (clk), + .rst_n (rst_n), + + .cfg_addr (x_pmp_cfg_addr), + .cfg_wen (x_pmp_cfg_wen), + .cfg_wdata (x_pmp_cfg_wdata), + .cfg_rdata (x_pmp_cfg_rdata), + + .i_addr (f_pmp_i_addr), + .i_m_mode (f_pmp_i_m_mode), + .i_kill (f_pmp_i_kill), + + .d_addr (bus_haddr_d), + .d_addr_addend_rs1 (x_rs1_bypass), + .d_addr_addend_imm (d_addr_offs), + .d_addr_addend_lspair_offs (d_lspair_offset), + .d_m_mode (x_mmode_loadstore), + .d_write (bus_hwrite_d), + .d_kill (x_loadstore_pmp_badperm) + ); + +end else begin: no_pmp + + assign x_pmp_cfg_rdata = 32'd0; + assign x_loadstore_pmp_fail = 1'b0; + assign f_pmp_i_kill = 1'b0; + +end +endgenerate + +// Triggers + +wire [11:0] x_trig_cfg_addr; +wire x_trig_cfg_wen; +wire [W_DATA-1:0] x_trig_cfg_wdata; +wire [W_DATA-1:0] x_trig_cfg_rdata; +wire x_trig_m_en; + +wire m_trig_event_interrupt; +wire m_trig_event_exception; +wire [3:0] m_trig_event_trap_cause; + +wire x_instr_ret; + +generate +if (DEBUG_SUPPORT != 0) begin: have_triggers + + // Connection of .fetch_d_mode is from the wrong stage: safe because we + // enter/exit debug mode via exceptions, which flush the frontend. + + hazard3_triggers #( + `include "hazard3_config_inst.vh" + ) triggers ( + .clk (clk), + .rst_n (rst_n), + + .cfg_addr (x_trig_cfg_addr), + .cfg_wen (x_trig_cfg_wen), + .cfg_wdata (x_trig_cfg_wdata), + .cfg_rdata (x_trig_cfg_rdata), + .trig_m_en (x_trig_m_en), + + .fetch_addr (f_trigger_addr), + .fetch_m_mode (f_trigger_m_mode), + .fetch_d_mode (debug_mode), + + .event_instr_ret (x_instr_ret), + + .event_interrupt (m_trig_event_interrupt), + .event_exception (m_trig_event_exception), + .event_trap_cause (m_trig_event_trap_cause), + .event_trap_enter (m_trap_enter_vld && m_trap_enter_rdy), + + .break_any (f_trigger_break_any), + .break_d_mode (f_trigger_break_d_mode), + + .break_m_step (x_trig_step_break), + + .x_d_mode (debug_mode), + .x_m_mode (x_mmode_execution) + ); + +end else begin: no_triggers + + assign x_trig_cfg_rdata = {W_DATA{1'b0}}; + assign f_trigger_break_any = 2'b00; + assign f_trigger_break_d_mode = 2'b00; + assign x_trig_step_break = 1'b0; + +end +endgenerate + +// CSRs and Trap Handling + +// Use opcode bits directly because d_rs1 is masked when RVE is enabled: +wire [W_DATA-1:0] x_csr_wdata = d_csr_w_imm ? + {{W_DATA-5{1'b0}}, fd_cir[RV_RS1_LSB +: 5]} : x_rs1_bypass; + +wire [W_DATA-1:0] x_csr_rdata; +wire x_csr_illegal_access; +wire x_csr_write_is_fetch_ordered; + +// "Previous" refers to next-most-recent instruction to be in D/X, i.e. the +// most recent instruction to reach stage M (which may or may not still be in M). +reg prev_instr_was_32_bit; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + xm_delay_irq_entry_on_ls_stagex <= 1'b0; + prev_instr_was_32_bit <= 1'b0; + end else begin + // Must hold off IRQ if we are in the second cycle of an address phase or + // later, since at that point the load/store can't be revoked. The IRQ is + // taken once this load/store moves to the next stage: if another load/store + // is chasing down the pipeline then this is immediately suppressed by the + // IRQ entry, before its address phase can begin. + + // Also hold off on AMOs, unless the AMO is transitioning to an address + // phase or completing. ("completing" excludes transitions to error phase.) + + xm_delay_irq_entry_on_ls_stagex <= bus_aph_req_d && !bus_aph_ready_d || + d_memop_is_amo && !( + x_amo_phase == 3'h3 && bus_dph_ready_d && !bus_dph_err_d || + // Read reservation failure failure also generates error + x_amo_phase == 3'h1 && bus_dph_ready_d && !bus_dph_err_d && bus_dph_exokay_d + ) || ( + (fence_i_vld || fence_d_vld) && !fence_rdy + ); + + if (!x_stall) + prev_instr_was_32_bit <= df_cir_use == 2'd2; + end +end + +wire [W_ADDR-1:0] m_exception_return_addr; + +wire [W_EXCEPT-1:0] x_except = + x_trig_break ? EXCEPT_EBREAK : + x_jump_req_unchecked && x_jump_misaligned ? EXCEPT_INSTR_MISALIGN : + x_csr_illegal_access ? EXCEPT_INSTR_ILLEGAL : + |EXTENSION_A && x_unaligned_addr && d_memop_is_amo ? EXCEPT_STORE_ALIGN : + |EXTENSION_A && x_amo_phase == 3'h4 && x_unaligned_addr ? EXCEPT_STORE_ALIGN : + |EXTENSION_A && x_amo_phase == 3'h4 ? EXCEPT_STORE_FAULT : + x_unaligned_addr && x_memop_write ? EXCEPT_STORE_ALIGN : + x_unaligned_addr && !x_memop_write ? EXCEPT_LOAD_ALIGN : + x_loadstore_pmp_fail && (d_memop_is_amo || bus_hwrite_d) ? EXCEPT_STORE_FAULT : + x_loadstore_pmp_fail ? EXCEPT_LOAD_FAULT : + x_csr_write_is_fetch_ordered ? EXCEPT_REFETCH : d_except; + +// If an instruction causes an exceptional condition we do not consider it to have retired. +wire x_except_counts_as_retire = + x_except == EXCEPT_EBREAK || + x_except == EXCEPT_REFETCH || + x_except == EXCEPT_MRET || + x_except == EXCEPT_ECALL_M || + x_except == EXCEPT_ECALL_U; + +assign x_instr_ret = |df_cir_use && (x_except == EXCEPT_NONE || x_except_counts_as_retire); + +assign m_dphase_in_flight = xm_memop != MEMOP_NONE && xm_memop != MEMOP_AMO; + +// Need to delay IRQ entry on sleep exit because, for deep sleep states, we +// can't access the bus until the power handshake has completed. +wire m_delay_irq_entry = xm_delay_irq_entry_on_ls_stagex || + ((xm_sleep_wfi || xm_sleep_block) && !m_sleep_stall_release); + +wire m_pwr_allow_clkgate; +wire m_pwr_allow_power_down; +wire m_pwr_allow_sleep_on_block; +wire m_wfi_wakeup_req; +wire m_clear_excl_on_trap_exit; + +hazard3_csr #( + .XLEN (W_DATA), +`include "hazard3_config_inst.vh" +) csr_u ( + .clk (clk), + .clk_always_on (clk_always_on), + .rst_n (rst_n), + + // Debugger signalling + .debug_mode (debug_mode), + .dbg_req_halt (dbg_req_halt), + .dbg_req_halt_on_reset (dbg_req_halt_on_reset), + .dbg_req_resume (dbg_req_resume), + + .dbg_instr_caught_exception (dbg_instr_caught_exception), + .dbg_instr_caught_ebreak (dbg_instr_caught_ebreak), + + .dbg_data0_rdata (dbg_data0_rdata), + .dbg_data0_wdata (dbg_data0_wdata), + .dbg_data0_wen (dbg_data0_wen), + + .debug_dpc_wdata (debug_dpc_wdata), + .debug_dpc_wen (debug_dpc_wen), + .debug_dpc_rdata (debug_dpc_rdata), + + // CSR access port + // *en_soon are early access strobes which are not a function of bus stall. + // Can generate access faults (hence traps), but do not actually perform access. + .addr (fd_cir[31:20]), // Always I-type immediate + .wdata (x_csr_wdata), + .wen_raw (d_csr_wen), + .wen_soon (d_csr_wen && !m_trap_enter_soon), + .wen (d_csr_wen && !m_trap_enter_soon && !x_stall), + .wtype (d_csr_wtype), + .rdata (x_csr_rdata), + .ren_soon (d_csr_ren && !m_trap_enter_soon), + .ren (d_csr_ren && !m_trap_enter_soon && !x_stall), + .illegal (x_csr_illegal_access), + .write_is_fetch_ordered (x_csr_write_is_fetch_ordered), + + // Trap signalling + .trap_addr (m_trap_addr), + .trap_is_irq (m_trap_is_irq), + .trap_enter_soon (m_trap_enter_soon), + .trap_enter_vld (m_trap_enter_vld), + .trap_enter_rdy (m_trap_enter_rdy), + .loadstore_dphase_pending (m_dphase_in_flight), + .delay_irq_entry (m_delay_irq_entry || d_uninterruptible), + .mepc_in (m_exception_return_addr), + + .pwr_allow_clkgate (m_pwr_allow_clkgate), + .pwr_allow_power_down (m_pwr_allow_power_down), + .pwr_allow_sleep_on_block (m_pwr_allow_sleep_on_block), + .pwr_wfi_wakeup_req (m_wfi_wakeup_req), + + .m_mode_execution (x_mmode_execution), + .m_mode_loadstore (x_mmode_loadstore), + .m_mode_trap_entry (m_mmode_trap_entry), + + // IRQ and exception requests + .irq (irq), + .irq_software (soft_irq), + .irq_timer (timer_irq), + .except (xm_except), + .except_to_d_mode (xm_except_to_d_mode), + + .pmp_cfg_addr (x_pmp_cfg_addr), + .pmp_cfg_wen (x_pmp_cfg_wen), + .pmp_cfg_wdata (x_pmp_cfg_wdata), + .pmp_cfg_rdata (x_pmp_cfg_rdata), + + .trig_cfg_addr (x_trig_cfg_addr), + .trig_cfg_wen (x_trig_cfg_wen), + .trig_cfg_wdata (x_trig_cfg_wdata), + .trig_cfg_rdata (x_trig_cfg_rdata), + .trig_m_en (x_trig_m_en), + + .trig_event_interrupt (m_trig_event_interrupt), + .trig_event_exception (m_trig_event_exception), + .trig_event_trap_cause (m_trig_event_trap_cause), + + // Other CSR-specific signalling + .clear_excl_on_trap_exit (m_clear_excl_on_trap_exit), + .trap_wfi (x_trap_wfi), + .instr_ret (x_instr_ret), + .mhartid_val (mhartid_val), + .eco_version (eco_version) +); + +// Pipe register + +assign xm_rd_nxt = REGADDR_MASK & ( + m_stall ? xm_rd : + x_stall || d_starved || m_trap_enter_soon ? 5'h0 : d_rd +); + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + xm_memop <= MEMOP_NONE; + xm_except <= EXCEPT_NONE; + xm_except_to_d_mode <= 1'b0; + xm_sleep_wfi <= 1'b0; + xm_sleep_block <= 1'b0; + unblock_out <= 1'b0; + xm_rs1 <= {W_REGADDR{1'b0}}; + xm_rs2 <= {W_REGADDR{1'b0}}; + xm_rd <= {W_REGADDR{1'b0}}; + xm_no_pc_increment <= 1'b0; + end else begin + unblock_out <= 1'b0; + if (!m_stall) begin + xm_rs1 <= REGADDR_MASK & d_rs1; + xm_rs2 <= REGADDR_MASK & d_rs2; + xm_rd <= REGADDR_MASK & d_rd; + // PC increment is suppressed non-final micro-ops, only needed for Zcmp: + xm_no_pc_increment <= d_no_pc_increment && |EXTENSION_ZCMP; + // If some X-sourced exception has squashed the address phase, need to squash the data phase too. + xm_memop <= x_except != EXCEPT_NONE ? MEMOP_NONE : d_memop; + xm_except <= x_except; + xm_except_to_d_mode <= x_trig_break_d_mode; + xm_sleep_wfi <= x_except == EXCEPT_NONE && d_sleep_wfi; + xm_sleep_block <= x_except == EXCEPT_NONE && d_sleep_block; + unblock_out <= x_except == EXCEPT_NONE && d_sleep_unblock; + // Note the d_starved term is required because it is possible + // (e.g. PMP X permission fail) to except when the frontend is + // starved, and we get a bad mepc if we let this jump ahead: + if (x_stall || d_starved || m_trap_enter_soon) begin + // Insert bubble + xm_rd <= {W_REGADDR{1'b0}}; + xm_memop <= MEMOP_NONE; + xm_except <= EXCEPT_NONE; + xm_except_to_d_mode <= 1'b0; + xm_sleep_wfi <= 1'b0; + xm_sleep_block <= 1'b0; + unblock_out <= 1'b0; + end + end else if (bus_dph_err_d) begin + // First phase of 2-phase AHB5 error response. Pass the exception along on + // this cycle, and on the next cycle the trap entry will be asserted, + // suppressing any load/store that may currently be in stage X. + xm_except <= + |EXTENSION_A && xm_memop == MEMOP_LR_W ? EXCEPT_LOAD_FAULT : + xm_memop <= MEMOP_LBU ? EXCEPT_LOAD_FAULT : EXCEPT_STORE_FAULT; + end + end +end + +`ifdef HAZARD3_ASSERTIONS +// Properties for previous block +always @ (posedge clk) if (rst_n) begin + if (!m_stall) begin + // Assuming downstream bus is compliant, err && !stalled is the + // last cycle of an error response, in which case this must have + // been previously captured as an exception. + if (bus_dph_err_d && x_amo_phase == 3'h0) begin + assert(xm_except == EXCEPT_LOAD_FAULT || xm_except == EXCEPT_STORE_FAULT); + end + end else if (bus_dph_err_d) begin + // There should be a bus transfer in stage 3 corresponding to this bus error + assert(xm_memop != MEMOP_NONE); + // We should capture this error exactly once, and if there were + // any other source of exception then the load/store should not + // have happened in the first place. Only exception is when the + // entry of the load/store trap is stalled on the frontend. + assert(xm_except == EXCEPT_NONE || (!m_trap_enter_rdy && ( + xm_except == EXCEPT_LOAD_FAULT || xm_except == EXCEPT_STORE_FAULT + ))); + // Make sure we don't have to clear any of the other stage 3 flags + assert(!xm_except_to_d_mode); + assert(!xm_sleep_wfi); + assert(!xm_sleep_block); + assert(!unblock_out); + end + // Some of the exception->ls paths are cut as they are supposed to be + // impossible -- make sure there is no way to have a load/store that + // is not squashed. (This includes debug entry!) + if (m_trap_enter_vld) begin + assert(!bus_aph_req_d); + end +end +`endif + +`ifdef HAZARD3_ASSERTIONS +always @ (posedge clk) if (rst_n) begin + // D bus errors must always squash younger load/stores + if ($past(bus_dph_err_d && !bus_dph_ready_d)) + assert(!bus_aph_req_d); + // Nonsensical to have an active stage 3 memop aligned with a + // stage-2-generated exception -- ought to be squashed. The outlier is an + // M-generated exception, i.e. a dataphase bus fault. Also AMOs are different. + if (xm_except != EXCEPT_NONE && !(bus_dph_err_d || $past(!m_trap_enter_rdy))) + assert(xm_memop == MEMOP_NONE || xm_memop == MEMOP_AMO); + assert(xm_rd == $past(xm_rd_nxt)); +end +`endif + +// Datapath flops +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + xm_result <= {W_DATA{1'b0}}; + xm_addr_align <= 2'b00; + end else if (!m_stall && !(|EXTENSION_A && x_amo_phase == 3'h3 && !bus_dph_ready_d)) begin + // AMOs need special attention (of course): + // - Steer captured read phase data in mw_result back through xm_result at end of AMO + // - Make sure xm_result (store data) doesn't transition during stalled write dphase + xm_result <= + d_csr_ren ? x_csr_rdata : + |EXTENSION_A && x_amo_phase == 3'h3 ? mw_result : + |MUL_FASTER && x_use_fast_mul ? m_fast_mul_result : + |EXTENSION_M && d_aluop == ALUOP_MULDIV ? x_muldiv_result : + x_alu_result; + xm_addr_align <= x_addr_sum[1:0]; + end +end + +// ---------------------------------------------------------------------------- +// Pipe Stage M + +reg [W_DATA-1:0] m_rdata_pick_sext; +reg [W_DATA-1:0] m_wdata; +reg [W_DATA-1:0] m_result; + +assign f_jump_req = x_jump_req || m_trap_enter_vld; +assign f_jump_target = m_trap_enter_vld ? m_trap_addr : x_jump_target; +assign f_jump_priv = m_trap_enter_vld ? m_mmode_trap_entry : x_mmode_execution; +assign x_jump_not_except = !m_trap_enter_vld; + +// Stalls and sleep control + +// EXCEPT_NONE clause is needed in the following sequence: +// - Cycle 0: hresp asserted, hready low. We set the exception to squash behind us. Bus stall high. +// - Cycle 1: hready high. For whatever reason, the frontend can't accept the trap address this cycle. +// - Cycle 2: Our dataphase has ended, so bus_dph_ready_d doesn't pulse again. m_bus_stall stuck high. +wire m_bus_stall = m_dphase_in_flight && !bus_dph_ready_d && xm_except == EXCEPT_NONE && !( + |EXTENSION_A && xm_memop == MEMOP_SC_W && !mw_local_exclusive_reserved +); + +assign m_stall = m_bus_stall || + (m_trap_enter_vld && !m_trap_enter_rdy && !m_trap_is_irq) || + ((xm_sleep_wfi || xm_sleep_block) && !m_sleep_stall_release); + +// Exception is taken against the instruction currently in M, so walk the PC +// back. IRQ is taken "in between" the instruction in M and the instruction +// in X, so set return to X program counter. Note that, if taking an +// exception, we know that the previous instruction to be in X (now in M) +// was *not* a taken branch, which is why we can just walk back the PC. +assign m_exception_return_addr = d_pc - ( + m_trap_is_irq ? 32'd0 : + xm_no_pc_increment ? 32'd0 : + prev_instr_was_32_bit ? 32'd4 : 32'd2 +); + +hazard3_power_ctrl power_ctrl ( + .clk_always_on (clk_always_on), + .rst_n (rst_n), + + .pwrup_req (pwrup_req), + .pwrup_ack (pwrup_ack), + .clk_en (clk_en), + + .allow_clkgate (m_pwr_allow_clkgate), + .allow_power_down (m_pwr_allow_power_down), + .allow_sleep_on_block (m_pwr_allow_sleep_on_block), + + .frontend_pwrdown_ok (f_frontend_pwrdown_ok), + + .sleeping_on_wfi (xm_sleep_wfi), + .wfi_wakeup_req (m_wfi_wakeup_req), + .sleeping_on_block (xm_sleep_block), + .block_wakeup_req_pulse (unblock_in), + .stall_release (m_sleep_stall_release) +); + +// Load/store data handling + +always @ (*) begin + // Local forwarding of store data + if (|mw_rd && xm_rs2 == mw_rd && !REDUCED_BYPASS) begin + m_wdata = mw_result; + end else begin + m_wdata = xm_result; + end + // Replicate store data to ensure appropriate byte lane is driven + case (xm_memop) + MEMOP_SH: bus_wdata_d = {2{m_wdata[15:0]}}; + MEMOP_SB: bus_wdata_d = {4{m_wdata[7:0]}}; + default: bus_wdata_d = m_wdata; + endcase + + casez ({xm_memop, xm_addr_align[1:0]}) + {MEMOP_LH , 2'b0z}: m_rdata_pick_sext = {{16{bus_rdata_d[15]}}, bus_rdata_d[15: 0]}; + {MEMOP_LH , 2'b1z}: m_rdata_pick_sext = {{16{bus_rdata_d[31]}}, bus_rdata_d[31:16]}; + {MEMOP_LHU , 2'b0z}: m_rdata_pick_sext = {{16{1'b0 }}, bus_rdata_d[15: 0]}; + {MEMOP_LHU , 2'b1z}: m_rdata_pick_sext = {{16{1'b0 }}, bus_rdata_d[31:16]}; + {MEMOP_LB , 2'b00}: m_rdata_pick_sext = {{24{bus_rdata_d[ 7]}}, bus_rdata_d[ 7: 0]}; + {MEMOP_LB , 2'b01}: m_rdata_pick_sext = {{24{bus_rdata_d[15]}}, bus_rdata_d[15: 8]}; + {MEMOP_LB , 2'b10}: m_rdata_pick_sext = {{24{bus_rdata_d[23]}}, bus_rdata_d[23:16]}; + {MEMOP_LB , 2'b11}: m_rdata_pick_sext = {{24{bus_rdata_d[31]}}, bus_rdata_d[31:24]}; + {MEMOP_LBU , 2'b00}: m_rdata_pick_sext = {{24{1'b0 }}, bus_rdata_d[ 7: 0]}; + {MEMOP_LBU , 2'b01}: m_rdata_pick_sext = {{24{1'b0 }}, bus_rdata_d[15: 8]}; + {MEMOP_LBU , 2'b10}: m_rdata_pick_sext = {{24{1'b0 }}, bus_rdata_d[23:16]}; + {MEMOP_LBU , 2'b11}: m_rdata_pick_sext = {{24{1'b0 }}, bus_rdata_d[31:24]}; + {MEMOP_LW , 2'bzz}: m_rdata_pick_sext = bus_rdata_d; + {MEMOP_LR_W, 2'bzz}: m_rdata_pick_sext = bus_rdata_d; + default: m_rdata_pick_sext = 32'hxxxx_xxxx; + endcase + + if (|EXTENSION_A && x_amo_phase == 3'h1) begin + // Capture AMO read data into mw_result for feeding back through the ALU. + m_result = bus_rdata_d; + end else if (|EXTENSION_A && xm_memop == MEMOP_SC_W) begin + // sc.w may fail due to negative response from either local or global monitor. + // Note the polarity of the result: 0 for success, 1 for failure. + m_result = {31'h0, !(mw_local_exclusive_reserved && bus_dph_exokay_d)}; + end else if (xm_memop != MEMOP_NONE && xm_memop != MEMOP_AMO) begin + m_result = m_rdata_pick_sext; + end else if (MUL_FAST && m_fast_mul_result_vld) begin + m_result = m_fast_mul_result; + end else begin + m_result = xm_result; + end +end + +// Local monitor update. +// - Set on a load-reserved with good response from global monitor +// - Cleared by any store-conditional +// - Cleared by non-debug-related trap exit + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + mw_local_exclusive_reserved <= 1'b0; + end else begin + if (|EXTENSION_A && (!m_stall || bus_dph_err_d)) begin + if (d_memop_is_amo) begin + mw_local_exclusive_reserved <= 1'b0; + end else if (xm_memop == MEMOP_SC_W && (bus_dph_ready_d || bus_dph_err_d)) begin + mw_local_exclusive_reserved <= 1'b0; + end else if (xm_memop == MEMOP_LR_W && bus_dph_ready_d) begin + // In theory, the bus should never report HEXOKAY when HRESP is asserted. + // Still might happen (e.g. if HEXOKAY is tied high), so mask HEXOKAY with + // HRESP to be sure a failed lr.w clears the monitor. + mw_local_exclusive_reserved <= bus_dph_exokay_d && !bus_dph_err_d; + end + end + if (m_clear_excl_on_trap_exit) begin + mw_local_exclusive_reserved <= 1'b0; + end + end +end + +// Note that exception entry prevents writeback, because the exception entry +// replaces the instruction in M. Interrupt entry does not prevent writeback, +// because the interrupt is notionally inserted in between the instruction in +// M and the instruction in X. + +wire m_reg_wen_if_nonzero = !m_bus_stall && xm_except == EXCEPT_NONE; +wire m_reg_wen = |xm_rd && m_reg_wen_if_nonzero; + +`ifdef HAZARD3_X_CHECKS +always @ (posedge clk) begin + if (rst_n) begin + if (m_reg_wen && (^m_result === 1'bX)) begin + $display("Writing X to register file!"); + $finish; + end + end +end +`endif + +`ifdef HAZARD3_ASSERTIONS +// We borrow mw_result during an AMO to capture rdata and feed back through +// the ALU, since it already has the right paths. Make sure this is safe. +// (Whatever instruction is in M ahead of AMO should have passed through by +// the time AMO has reached read dphase) +always @ (posedge clk) if (rst_n) begin + if (x_amo_phase == 3'h1) + assert(m_reg_wen_if_nonzero); + if (x_amo_phase == 3'h1) + assert(~|xm_rd); +end +`endif + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + mw_result <= {W_DATA{1'b0}}; + end else if (m_reg_wen_if_nonzero && !(|EXTENSION_A && x_amo_phase[1])) begin + // (don't trash the captured AMO read phase data during stage 2/3 of AMO -- we need it!) + mw_result <= m_result; + end +end + +assign mw_rd_nxt = REGADDR_MASK & ( + m_reg_wen_if_nonzero ? xm_rd : mw_rd +); + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + mw_rd <= {W_REGADDR{1'b0}}; + end else begin +`ifdef HAZARD3_X_CHECKS + if (!m_stall && ^bus_wdata_d === 1'bX) begin + $display("Writing Xs to memory!"); + $finish; + end +`endif + if (m_reg_wen_if_nonzero) begin + mw_rd <= REGADDR_MASK & xm_rd; + end + end +end + +hazard3_regfile_1w2r #( +`include "hazard3_config_inst.vh" +) regs ( + .clk (clk), + .rst_n (rst_n), + // On downstream stall, we feed D's addresses back into regfile + // so that output does not change. + .raddr1 (x_stall && !d_starved ? d_rs1 : f_rs1_coarse), + .rdata1 (x_rdata1), + .raddr2 (x_stall && !d_starved ? d_rs2 : f_rs2_coarse), + .rdata2 (x_rdata2), + + .waddr (xm_rd), + .wdata (m_result), + .wen (m_reg_wen) +); + +`ifdef RISCV_FORMAL +`include "hazard3_rvfi_monitor.vh" +`endif + +`ifdef HAZARD3_FORMAL_REGRESSION +// Each formal regression provides its own file with the below name: +`include "hazard3_formal_regression.vh" +`endif + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/hazard3_cpu_1port.v b/vendor/Hazard3/hdl/hazard3_cpu_1port.v new file mode 100644 index 0000000..bf3300a --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_cpu_1port.v @@ -0,0 +1,343 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// Single-ported top level file for Hazard3 CPU. This file instantiates the +// Hazard3 core, and arbitrates its instruction fetch and load/store signals +// down to a single AHB5 master port. + +`ifdef HAZARD3_RVFI_STANDALONE +`include "hazard3_rvfi_standalone_defs.vh" +`endif + +`default_nettype none + +module hazard3_cpu_1port #( +`include "hazard3_config.vh" +) ( + // Global signals + input wire clk, + input wire clk_always_on, + input wire rst_n, + + `ifdef RISCV_FORMAL + `RVFI_OUTPUTS , + `endif + + // Power control signals + output wire pwrup_req, + input wire pwrup_ack, + output wire clk_en, + output wire unblock_out, + input wire unblock_in, + + // AHB5 Master port + output reg [W_ADDR-1:0] haddr, + output reg hwrite, + output reg [1:0] htrans, + output reg [2:0] hsize, + output wire [2:0] hburst, + output reg [3:0] hprot, + output wire hmastlock, + output reg [7:0] hmaster, + output reg hexcl, + input wire hready, + input wire hresp, + input wire hexokay, + output wire [W_DATA-1:0] hwdata, + input wire [W_DATA-1:0] hrdata, + + // Memory ordering signals + output wire fence_i_vld, + output wire fence_d_vld, + input wire fence_rdy, + + // Debugger run/halt control + input wire dbg_req_halt, + input wire dbg_req_halt_on_reset, + input wire dbg_req_resume, + output wire dbg_halted, + output wire dbg_running, + // Debugger access to data0 CSR + input wire [W_DATA-1:0] dbg_data0_rdata, + output wire [W_DATA-1:0] dbg_data0_wdata, + output wire dbg_data0_wen, + // Debugger instruction injection + input wire [W_DATA-1:0] dbg_instr_data, + input wire dbg_instr_data_vld, + output wire dbg_instr_data_rdy, + output wire dbg_instr_caught_exception, + output wire dbg_instr_caught_ebreak, + + // Optional debug system bus access patch-through + input wire [W_ADDR-1:0] dbg_sbus_addr, + input wire dbg_sbus_write, + input wire [1:0] dbg_sbus_size, + input wire dbg_sbus_vld, + output wire dbg_sbus_rdy, + output wire dbg_sbus_err, + input wire [W_DATA-1:0] dbg_sbus_wdata, + output wire [W_DATA-1:0] dbg_sbus_rdata, + + // Identification CSR values + input wire [W_DATA-1:0] mhartid_val, + input wire [3:0] eco_version, + + // Level-sensitive interrupt sources + input wire [NUM_IRQS-1:0] irq, // -> mip.meip + input wire soft_irq, // -> mip.msip + input wire timer_irq // -> mip.mtip +); + +// ---------------------------------------------------------------------------- +// Processor core + +// Instruction fetch signals +wire core_aph_req_i; +wire core_aph_panic_i; +wire core_aph_ready_i; +wire core_dph_ready_i; +wire core_dph_err_i; + +wire [W_ADDR-1:0] core_haddr_i; +wire [2:0] core_hsize_i; +wire core_priv_i; +wire [W_DATA-1:0] core_rdata_i; + + +// Load/store signals +wire core_aph_req_d; +wire core_aph_excl_d; +wire core_aph_ready_d; +wire core_dph_ready_d; +wire core_dph_err_d; +wire core_dph_exokay_d; + +wire [W_ADDR-1:0] core_haddr_d; +wire [2:0] core_hsize_d; +wire core_priv_d; +wire core_hwrite_d; +wire [W_DATA-1:0] core_wdata_d; +wire [W_DATA-1:0] core_rdata_d; + + +hazard3_core #( +`include "hazard3_config_inst.vh" +) core ( + .clk (clk), + .clk_always_on (clk_always_on), + .rst_n (rst_n), + + .pwrup_req (pwrup_req), + .pwrup_ack (pwrup_ack), + .clk_en (clk_en), + .unblock_out (unblock_out), + .unblock_in (unblock_in), + + `ifdef RISCV_FORMAL + `RVFI_CONN , + `endif + + .bus_aph_req_i (core_aph_req_i), + .bus_aph_panic_i (core_aph_panic_i), + .bus_aph_ready_i (core_aph_ready_i), + .bus_dph_ready_i (core_dph_ready_i), + .bus_dph_err_i (core_dph_err_i), + .bus_haddr_i (core_haddr_i), + .bus_hsize_i (core_hsize_i), + .bus_priv_i (core_priv_i), + .bus_rdata_i (core_rdata_i), + + .bus_aph_req_d (core_aph_req_d), + .bus_aph_excl_d (core_aph_excl_d), + .bus_aph_ready_d (core_aph_ready_d), + .bus_dph_ready_d (core_dph_ready_d), + .bus_dph_err_d (core_dph_err_d), + .bus_dph_exokay_d (core_dph_exokay_d), + .bus_haddr_d (core_haddr_d), + .bus_hsize_d (core_hsize_d), + .bus_priv_d (core_priv_d), + .bus_hwrite_d (core_hwrite_d), + .bus_wdata_d (core_wdata_d), + .bus_rdata_d (core_rdata_d), + + .fence_i_vld (fence_i_vld), + .fence_d_vld (fence_d_vld), + .fence_rdy (fence_rdy), + + .dbg_req_halt (dbg_req_halt), + .dbg_req_halt_on_reset (dbg_req_halt_on_reset), + .dbg_req_resume (dbg_req_resume), + .dbg_halted (dbg_halted), + .dbg_running (dbg_running), + .dbg_data0_rdata (dbg_data0_rdata), + .dbg_data0_wdata (dbg_data0_wdata), + .dbg_data0_wen (dbg_data0_wen), + .dbg_instr_data (dbg_instr_data), + .dbg_instr_data_vld (dbg_instr_data_vld), + .dbg_instr_data_rdy (dbg_instr_data_rdy), + .dbg_instr_caught_exception (dbg_instr_caught_exception), + .dbg_instr_caught_ebreak (dbg_instr_caught_ebreak), + + .mhartid_val (mhartid_val), + .eco_version (eco_version), + + .irq (irq), + .soft_irq (soft_irq), + .timer_irq (timer_irq) +); + +// ---------------------------------------------------------------------------- +// Arbitration state machine + +wire bus_gnt_i; +wire bus_gnt_d; +wire bus_gnt_s; + +reg bus_hold_aph; +reg [2:0] bus_gnt_ids_prev; + +// Note use of clk_always_on: SBA may use this arbiter to access the bus +// whilst the core is asleep. + +always @ (posedge clk_always_on or negedge rst_n) begin + if (!rst_n) begin + bus_hold_aph <= 1'b0; + bus_gnt_ids_prev <= 3'h0; + end else begin + bus_hold_aph <= htrans[1] && !hready && !hresp; + bus_gnt_ids_prev <= {bus_gnt_i, bus_gnt_d, bus_gnt_s}; + end +end + +// Debug SBA access is lower priority than load/store, but higher than +// instruction fetch. This isn't ideal, but in a tight loop the core may be +// performing an instruction fetch or load/store on every single cycle, and +// this is a simple way to guarantee eventual success of debugger accesses. A +// more complex way would be to add a "panic timer" to boost a stalled sbus +// access over an instruction fetch. + +// Note that, often, the sbus will be disconnected: it doesn't provide any +// increase in debugger bus throughput compared with the program buffer and +// autoexec. It's useful for "minimally intrusive" debug bus access(i.e. less +// intrusive than halting the core and resuming it) e.g. for Segger RTT. + +reg bus_active_dph_s; + +assign {bus_gnt_i, bus_gnt_d, bus_gnt_s} = + bus_hold_aph ? bus_gnt_ids_prev : + core_aph_panic_i ? 3'b100 : + core_aph_req_d ? 3'b010 : + dbg_sbus_vld && !bus_active_dph_s ? 3'b001 : + core_aph_req_i ? 3'b100 : + 3'b000 ; +reg bus_active_dph_i; +reg bus_active_dph_d; + +always @ (posedge clk_always_on or negedge rst_n) begin + if (!rst_n) begin + bus_active_dph_i <= 1'b0; + bus_active_dph_d <= 1'b0; + bus_active_dph_s <= 1'b0; + end else if (hready) begin + bus_active_dph_i <= bus_gnt_i; + bus_active_dph_d <= bus_gnt_d; + bus_active_dph_s <= bus_gnt_s; + end +end + +// ---------------------------------------------------------------------------- +// Address phase request muxing + +localparam HTRANS_IDLE = 2'b00; +localparam HTRANS_NSEQ = 2'b10; + +wire [3:0] hprot_data = { + 2'b00, // Noncacheable/nonbufferable + core_priv_d, // Privileged or Normal as per core state + 1'b1 // Data access +}; + +wire [3:0] hprot_instr = { + 2'b00, // Noncacheable/nonbufferable + core_priv_i, // Privileged or Normal as per core state + 1'b0 // Instruction access +}; + +wire [3:0] hprot_sbus = { + 2'b00, // Noncacheable/nonbufferable + 1'b1, // Always privileged + 1'b1 // Data access +}; + +assign hburst = 3'b000; // HBURST_SINGLE +assign hmastlock = 1'b0; + +always @ (*) begin + if (bus_gnt_s) begin + htrans = HTRANS_NSEQ; + hexcl = 1'b0; + haddr = dbg_sbus_addr; + hsize = {1'b0, dbg_sbus_size}; + hwrite = dbg_sbus_write; + hprot = hprot_sbus; + hmaster = 8'h01; + end else if (bus_gnt_d) begin + htrans = HTRANS_NSEQ; + hexcl = core_aph_excl_d; + haddr = core_haddr_d; + hsize = core_hsize_d; + hwrite = core_hwrite_d; + hprot = hprot_data; + hmaster = 8'h00; + end else if (bus_gnt_i) begin + htrans = HTRANS_NSEQ; + hexcl = 1'b0; + haddr = core_haddr_i; + hsize = core_hsize_i; + hwrite = 1'b0; + hprot = hprot_instr; + hmaster = 8'h00; + end else begin + htrans = HTRANS_IDLE; + hexcl = 1'b0; + haddr = {W_ADDR{1'b0}}; + hsize = 3'h0; + hwrite = 1'b0; + hprot = 4'h0; + hmaster = 8'h00; + end +end + +assign hwdata = bus_active_dph_s ? dbg_sbus_wdata : core_wdata_d; + +// ---------------------------------------------------------------------------- +// Response routing + +// Data buses directly connected +assign core_rdata_d = hrdata; +assign core_rdata_i = hrdata; +assign dbg_sbus_rdata = hrdata; + +// Handhshake based on grant and bus stall +assign core_aph_ready_i = hready && bus_gnt_i; +assign core_dph_ready_i = bus_active_dph_i && hready; +assign core_dph_err_i = bus_active_dph_i && hresp; + +// D-side errors are reported even when not ready, so that the core can make +// use of the two-phase error response to cleanly squash a second load/store +// chasing the faulting one down the pipeline. +assign core_aph_ready_d = hready && bus_gnt_d; +assign core_dph_ready_d = bus_active_dph_d && hready; +assign core_dph_err_d = bus_active_dph_d && hresp; +assign core_dph_exokay_d = bus_active_dph_d && hexokay; + +assign dbg_sbus_err = bus_active_dph_s && hresp; +assign dbg_sbus_rdy = bus_active_dph_s && hready; + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/hazard3_cpu_2port.v b/vendor/Hazard3/hdl/hazard3_cpu_2port.v new file mode 100644 index 0000000..7356671 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_cpu_2port.v @@ -0,0 +1,327 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// Dual-ported top level file for Hazard3 CPU. This file instantiates the +// Hazard3 core, and interfaces its instruction fetch and load/store signals +// to a pair of AHB5 master ports. + +`ifdef HAZARD3_RVFI_STANDALONE +`include "hazard3_rvfi_standalone_defs.vh" +`endif + +`default_nettype none + +module hazard3_cpu_2port #( +`include "hazard3_config.vh" +) ( + // Global signals + input wire clk, + input wire clk_always_on, + input wire rst_n, + + // Power control signals + output wire pwrup_req, + input wire pwrup_ack, + output wire clk_en, + output wire unblock_out, + input wire unblock_in, + + `ifdef RISCV_FORMAL + `RVFI_OUTPUTS , + `endif + + // Instruction fetch port + output wire [W_ADDR-1:0] i_haddr, + output wire i_hwrite, + output wire [1:0] i_htrans, + output wire [2:0] i_hsize, + output wire [2:0] i_hburst, + output wire [3:0] i_hprot, + output wire i_hmastlock, + output wire [7:0] i_hmaster, + input wire i_hready, + input wire i_hresp, + output wire [W_DATA-1:0] i_hwdata, + input wire [W_DATA-1:0] i_hrdata, + + // Load/store port + output wire [W_ADDR-1:0] d_haddr, + output wire d_hwrite, + output wire [1:0] d_htrans, + output wire [2:0] d_hsize, + output wire [2:0] d_hburst, + output wire [3:0] d_hprot, + output wire d_hmastlock, + output wire [7:0] d_hmaster, + output wire d_hexcl, + input wire d_hready, + input wire d_hresp, + input wire d_hexokay, + output wire [W_DATA-1:0] d_hwdata, + input wire [W_DATA-1:0] d_hrdata, + + // Memory ordering signals + output wire fence_i_vld, + output wire fence_d_vld, + input wire fence_rdy, + + // Debugger run/halt control + input wire dbg_req_halt, + input wire dbg_req_halt_on_reset, + input wire dbg_req_resume, + output wire dbg_halted, + output wire dbg_running, + // Debugger access to data0 CSR + input wire [W_DATA-1:0] dbg_data0_rdata, + output wire [W_DATA-1:0] dbg_data0_wdata, + output wire dbg_data0_wen, + // Debugger instruction injection + input wire [W_DATA-1:0] dbg_instr_data, + input wire dbg_instr_data_vld, + output wire dbg_instr_data_rdy, + output wire dbg_instr_caught_exception, + output wire dbg_instr_caught_ebreak, + // Optional debug system bus access patch-through + input wire [W_ADDR-1:0] dbg_sbus_addr, + input wire dbg_sbus_write, + input wire [1:0] dbg_sbus_size, + input wire dbg_sbus_vld, + output wire dbg_sbus_rdy, + output wire dbg_sbus_err, + input wire [W_DATA-1:0] dbg_sbus_wdata, + output wire [W_DATA-1:0] dbg_sbus_rdata, + + // Identification CSR values + input wire [W_DATA-1:0] mhartid_val, + input wire [3:0] eco_version, + + // Level-sensitive interrupt sources + input wire [NUM_IRQS-1:0] irq, // -> mip.meip + input wire soft_irq, // -> mip.msip + input wire timer_irq // -> mip.mtip +); + +// ---------------------------------------------------------------------------- +// Processor core + +// Instruction fetch signals +wire core_aph_req_i; +wire core_aph_ready_i; +wire core_dph_ready_i; +wire core_dph_err_i; + +wire [W_ADDR-1:0] core_haddr_i; +wire [2:0] core_hsize_i; +wire core_priv_i; +wire [W_DATA-1:0] core_rdata_i; + + +// Load/store signals +wire core_aph_req_d; +wire core_aph_excl_d; +wire core_aph_ready_d; +wire core_dph_ready_d; +wire core_dph_err_d; +wire core_dph_exokay_d; + +wire [W_ADDR-1:0] core_haddr_d; +wire [2:0] core_hsize_d; +wire core_priv_d; +wire core_hwrite_d; +wire [W_DATA-1:0] core_wdata_d; +wire [W_DATA-1:0] core_rdata_d; + +hazard3_core #( +`include "hazard3_config_inst.vh" +) core ( + .clk (clk), + .clk_always_on (clk_always_on), + .rst_n (rst_n), + + .pwrup_req (pwrup_req), + .pwrup_ack (pwrup_ack), + .clk_en (clk_en), + .unblock_out (unblock_out), + .unblock_in (unblock_in), + + `ifdef RISCV_FORMAL + `RVFI_CONN , + `endif + + .bus_aph_req_i (core_aph_req_i), + .bus_aph_panic_i (/* unused for 2port */), + .bus_aph_ready_i (core_aph_ready_i), + .bus_dph_ready_i (core_dph_ready_i), + .bus_dph_err_i (core_dph_err_i), + .bus_haddr_i (core_haddr_i), + .bus_hsize_i (core_hsize_i), + .bus_priv_i (core_priv_i), + .bus_rdata_i (core_rdata_i), + + .bus_aph_req_d (core_aph_req_d), + .bus_aph_excl_d (core_aph_excl_d), + .bus_aph_ready_d (core_aph_ready_d), + .bus_dph_ready_d (core_dph_ready_d), + .bus_dph_err_d (core_dph_err_d), + .bus_dph_exokay_d (core_dph_exokay_d), + .bus_haddr_d (core_haddr_d), + .bus_hsize_d (core_hsize_d), + .bus_priv_d (core_priv_d), + .bus_hwrite_d (core_hwrite_d), + .bus_wdata_d (core_wdata_d), + .bus_rdata_d (core_rdata_d), + + .fence_i_vld (fence_i_vld), + .fence_d_vld (fence_d_vld), + .fence_rdy (fence_rdy), + + .dbg_req_halt (dbg_req_halt), + .dbg_req_halt_on_reset (dbg_req_halt_on_reset), + .dbg_req_resume (dbg_req_resume), + .dbg_halted (dbg_halted), + .dbg_running (dbg_running), + .dbg_data0_rdata (dbg_data0_rdata), + .dbg_data0_wdata (dbg_data0_wdata), + .dbg_data0_wen (dbg_data0_wen), + .dbg_instr_data (dbg_instr_data), + .dbg_instr_data_vld (dbg_instr_data_vld), + .dbg_instr_data_rdy (dbg_instr_data_rdy), + .dbg_instr_caught_exception (dbg_instr_caught_exception), + .dbg_instr_caught_ebreak (dbg_instr_caught_ebreak), + + .mhartid_val (mhartid_val), + .eco_version (eco_version), + + .irq (irq), + .soft_irq (soft_irq), + .timer_irq (timer_irq) +); + +// ---------------------------------------------------------------------------- +// Instruction port + +localparam HTRANS_IDLE = 2'b00; +localparam HTRANS_NSEQ = 2'b10; + +assign i_haddr = core_haddr_i; +assign i_htrans = core_aph_req_i ? HTRANS_NSEQ : HTRANS_IDLE; +assign i_hsize = core_hsize_i; + +reg dphase_active_i; +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + dphase_active_i <= 1'b0; + end else if (i_hready) begin + dphase_active_i <= core_aph_req_i; + end +end + +`ifdef HAZARD3_ASSERTIONS +// Wake->sleep transition must wait for outstanding instruction fetches to +// complete, in particular because the arbiter clock will stop +always @ (posedge clk) if (!rst_n) assert(clk_en || !(core_aph_req_i || dphase_active_i)); +`endif + +assign core_aph_ready_i = i_hready && core_aph_req_i; +assign core_dph_ready_i = i_hready && dphase_active_i; +assign core_dph_err_i = i_hready && dphase_active_i && i_hresp; + +assign core_rdata_i = i_hrdata; + +assign i_hwrite = 1'b0; +assign i_hburst = 3'h0; +assign i_hmastlock = 1'b0; +assign i_hmaster = 8'h00; +assign i_hwdata = {W_DATA{1'b0}}; + +assign i_hprot = { + 2'b00, // Noncacheable/nonbufferable + core_priv_i, // Privileged or Normal as per core state + 1'b0 // Instruction access +}; + +// ---------------------------------------------------------------------------- +// Load/store port + +// The debug module has optional System Bus Access support, which can be muxed +// into the processor's D port here (or connected to a standalone AHB shim). +// This confers absolutely no advantage for debugger bus throughput, but +// allows the debugger to access the bus with minimal disturbance to the +// processor. + +wire bus_gnt_d; +wire bus_gnt_s; + +reg bus_hold_aph; +reg [1:0] bus_gnt_ds_prev; +reg bus_active_dph_d; +reg bus_active_dph_s; + +// clk_always_on is used because SBA may access the bus through this arbiter +// whilst the core is asleep (same is not true for I-side interface) + +always @ (posedge clk_always_on or negedge rst_n) begin + if (!rst_n) begin + bus_hold_aph <= 1'b0; + bus_gnt_ds_prev <= 2'h0; + end else begin + bus_hold_aph <= d_htrans[1] && !d_hready && !d_hresp; + bus_gnt_ds_prev <= {bus_gnt_d, bus_gnt_s}; + end +end + +assign {bus_gnt_d, bus_gnt_s} = + bus_hold_aph ? bus_gnt_ds_prev : + core_aph_req_d ? 2'b10 : + dbg_sbus_vld && !bus_active_dph_s ? 2'b01 : + 2'b00 ; + +always @ (posedge clk_always_on or negedge rst_n) begin + if (!rst_n) begin + bus_active_dph_d <= 1'b0; + bus_active_dph_s <= 1'b0; + end else if (d_hready) begin + bus_active_dph_d <= bus_gnt_d; + bus_active_dph_s <= bus_gnt_s; + end +end + +assign d_htrans = bus_gnt_d || bus_gnt_s ? HTRANS_NSEQ : HTRANS_IDLE; + +assign d_haddr = bus_gnt_s ? dbg_sbus_addr : core_haddr_d; +assign d_hwrite = bus_gnt_s ? dbg_sbus_write : core_hwrite_d; +assign d_hsize = bus_gnt_s ? {1'b0, dbg_sbus_size} : core_hsize_d; +assign d_hexcl = bus_gnt_s ? 1'b0 : core_aph_excl_d; + +assign d_hprot = { + 2'b00, // Noncacheable/nonbufferable + bus_gnt_s || core_priv_d, // Privileged or Normal as per core state + 1'b1 // Data access +}; + +assign d_hwdata = bus_active_dph_s ? dbg_sbus_wdata : core_wdata_d; + +// D-side errors are reported even when not ready, so that the core can make +// use of the two-phase error response to cleanly squash a second load/store +// chasing the faulting one down the pipeline. +assign core_aph_ready_d = d_hready && bus_gnt_d; +assign core_dph_ready_d = bus_active_dph_d && d_hready; +assign core_dph_err_d = bus_active_dph_d && d_hresp; +assign core_dph_exokay_d = bus_active_dph_d && d_hexokay; +assign core_rdata_d = d_hrdata; + +assign dbg_sbus_err = bus_active_dph_s && d_hresp; +assign dbg_sbus_rdy = bus_active_dph_s && d_hready; +assign dbg_sbus_rdata = d_hrdata; + +assign d_hburst = 3'h0; +assign d_hmastlock = 1'b0; +assign d_hmaster = bus_gnt_s ? 8'h01 : 8'h00; + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/hazard3_csr.v b/vendor/Hazard3/hdl/hazard3_csr.v new file mode 100644 index 0000000..3f95557 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_csr.v @@ -0,0 +1,1642 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +`default_nettype none + +// Control and Status Registers (CSRs) +// Also includes CSR-related logic like interrupt enable/masking, +// trap vector calculation. + +module hazard3_csr #( + parameter XLEN = 32, // Must be 32 +`include "hazard3_config.vh" +, +`include "hazard3_width_const.vh" +) ( + input wire clk, + input wire clk_always_on, + input wire rst_n, + + // Debug signalling + output reg debug_mode, + + input wire dbg_req_halt, + input wire dbg_req_halt_on_reset, + input wire dbg_req_resume, + + output wire dbg_instr_caught_exception, + output wire dbg_instr_caught_ebreak, + + input wire [W_DATA-1:0] dbg_data0_rdata, + output wire [W_DATA-1:0] dbg_data0_wdata, + output wire dbg_data0_wen, + + output wire [W_ADDR-1:0] debug_dpc_wdata, + output wire debug_dpc_wen, + input wire [W_ADDR-1:0] debug_dpc_rdata, + + // Read port is combinatorial. + // Write port is synchronous, and write effects will be observed on the next clock cycle. + // The *_soon strobes are versions which the core does not gate with its stall signal. + // These are needed because: + // - Core stall is a function of bus stall + // - Illegal CSR accesses produce trap entry + // - Trap entry (not necessarily caused by CSR access) gates outgoing bus accesses + // - Through-paths from e.g. hready to htrans are problematic for timing/implementation + input wire [11:0] addr, + input wire [XLEN-1:0] wdata, + input wire wen, + input wire wen_soon, // wen will be asserted once some stall condition clears + input wire wen_raw, // raw, ungated instruction decode signal, for D-mode regs + input wire [1:0] wtype, + output reg [XLEN-1:0] rdata, + input wire ren, + input wire ren_soon, // ren will be asserted once some stall condition clears + output wire illegal, + output wire write_is_fetch_ordered, + + // Trap signalling + // *We* tell the core that we are taking a trap, and where to, based on: + // - Synchronous exception inputs from the core + // - External IRQ signals + // - Masking etc based on the state of CSRs like mie + // + // We do this by raising trap_enter_vld, and keeping it raised until trap_enter_rdy + // goes high. trap_addr has the absolute value of trap target address. + // Once trap_enter_vld && _rdy, mepc_in is copied to mepc, and other trap state is set. + // + // Note that an exception input can go away, e.g. if the pipe gets flushed. In this + // case we lower trap_enter_vld. + output wire [XLEN-1:0] trap_addr, + output wire trap_is_irq, + output wire trap_enter_vld, + input wire trap_enter_rdy, + // True when we are about to trap, but are waiting for an excepting or + // potentially-excepting instruction to clear M first. The instruction in X + // is suppressed, X PC does not increment but still tracks exception + // addresses. + output wire trap_enter_soon, + // We need to know about load/stores in data phase because their exception + // status is still unknown, so fence them off before entering debug mode. + input wire loadstore_dphase_pending, + // Asserted when the instruction in stage 2 can't be squashed (e.g. to + // keep an AHB address phase stable), or instruction fetch request can't + // be issued (e.g. waiting for wake from wfi state). + input wire delay_irq_entry, + input wire [XLEN-1:0] mepc_in, + + // Power control signalling + output wire pwr_allow_clkgate, + output wire pwr_allow_power_down, + output wire pwr_allow_sleep_on_block, + output wire pwr_wfi_wakeup_req, + + // Each of these may be performed at a different privilege level from the others: + output wire m_mode_execution, + output wire m_mode_trap_entry, + output wire m_mode_loadstore, + + // Exceptions must *not* be a function of bus stall. + input wire [W_EXCEPT-1:0] except, + input wire except_to_d_mode, + + // Level-sensitive interrupt sources + input wire [NUM_IRQS-1:0] irq, + input wire irq_software, + input wire irq_timer, + + // PMP config interface + output wire [11:0] pmp_cfg_addr, + output reg pmp_cfg_wen, + output wire [W_DATA-1:0] pmp_cfg_wdata, + input wire [W_DATA-1:0] pmp_cfg_rdata, + + // Trigger config interface + output wire [11:0] trig_cfg_addr, + output reg trig_cfg_wen, + output wire [W_DATA-1:0] trig_cfg_wdata, + input wire [W_DATA-1:0] trig_cfg_rdata, + output wire trig_m_en, + + // Interrupt/exception trigger events + output wire trig_event_interrupt, + output wire trig_event_exception, + output wire [3:0] trig_event_trap_cause, + + // Other CSR-specific signalling + output wire clear_excl_on_trap_exit, + output wire trap_wfi, + input wire instr_ret, + input wire [W_DATA-1:0] mhartid_val, + input wire [3:0] eco_version +); + +`include "hazard3_ops.vh" +`include "hazard3_csr_addr.vh" + +localparam X0 = {XLEN{1'b0}}; + +// ---------------------------------------------------------------------------- +// CSR state + update logic +// ---------------------------------------------------------------------------- +// Names are (reg)_(field) + +// Generic update logic for write/set/clear of an entire CSR: +wire [XLEN-1:0] wdata_update = + wtype == CSR_WTYPE_C ? rdata & ~wdata : + wtype == CSR_WTYPE_S ? rdata | wdata : wdata; + +function [XLEN-1:0] update_nonconst; + input [XLEN-1:0] prev; + input [XLEN-1:0] nonconst; +begin + update_nonconst = (wdata_update & nonconst) | (prev & ~nonconst) ; +end +endfunction + +wire enter_debug_mode; +wire exit_debug_mode; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + debug_mode <= 1'b0; + end else if (DEBUG_SUPPORT) begin + debug_mode <= (debug_mode && !exit_debug_mode) || enter_debug_mode; + end +end + +// Core execution state, 1 -> M-mode, 0 -> U-mode (if implemented) +reg m_mode; +wire wen_m_mode = wen && (m_mode || debug_mode); +wire ren_m_mode = ren && (m_mode || debug_mode); + +// ---------------------------------------------------------------------------- +// Standard trap-handling CSRs + +wire debug_suppresses_trap_update = DEBUG_SUPPORT && (debug_mode || enter_debug_mode); + +wire trapreg_update = trap_enter_vld && trap_enter_rdy && !debug_suppresses_trap_update; +wire trapreg_update_enter = trapreg_update && except != EXCEPT_MRET && except != EXCEPT_REFETCH; +wire trapreg_update_exit = trapreg_update && except == EXCEPT_MRET; + +// Local monitor flag cleared on trap exit (but not on debug-mode exit, to +// permit stepping through LR/SC sequences): +assign clear_excl_on_trap_exit = trapreg_update_exit; + +reg mstatus_mpie; +reg mstatus_mie; +reg mstatus_mpp; // only MSB is implemented +reg mstatus_mprv; +reg mstatus_tw; + +// Spec text from priv-1.12: +// "An MRET or SRET instruction is used to return from a trap in M-mode or +// S-mode respectively. When executing an xRET instruction, supposing xPP +// holds the value y, xIE is set to xPIE; the privilege mode is changed to +// y; xPIE is set to 1; and xPP is set to the least-privileged supported +// mode (U if U-mode is implemented, else M). If xPP=M, xRET also sets +// MPRV=0." + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + m_mode <= 1'b1; + mstatus_mpie <= 1'b0; + mstatus_mie <= 1'b0; + mstatus_mpp <= 1'b1; + mstatus_mprv <= 1'b0; + mstatus_tw <= 1'b0; + end else if (!CSR_M_TRAP) begin + // Explicit tie-off when unimplemented; avoid synthesis warning about + // flops insensitive to clk. Should match the rst_n case above so that + // flops are constant-propagated away. + m_mode <= 1'b1; + mstatus_mpie <= 1'b0; + mstatus_mie <= 1'b0; + mstatus_mpp <= 1'b1; + mstatus_mprv <= 1'b0; + mstatus_tw <= 1'b0; + end else begin + if (trapreg_update_exit) begin + mstatus_mpie <= 1'b1; + mstatus_mie <= mstatus_mpie; + if (U_MODE) begin + m_mode <= mstatus_mpp; + mstatus_mpp <= 1'b0; + if (!mstatus_mpp) begin + mstatus_mprv <= 1'b0; + end + end + end else if (trapreg_update_enter) begin + mstatus_mpie <= mstatus_mie; + mstatus_mie <= 1'b0; + if (U_MODE) begin + m_mode <= 1'b1; + mstatus_mpp <= m_mode; + end + end else if (wen_m_mode && addr == MSTATUS) begin + mstatus_mpie <= wdata_update[7]; + mstatus_mie <= wdata_update[3]; + mstatus_mprv <= wdata_update[17]; + // Note only the MSB of MPP is implemented. It reads back as 11 or 00. + mstatus_mpp <= wdata_update[12] || !U_MODE; + mstatus_tw <= wdata_update[21] && U_MODE; + end else if (wen_raw && debug_mode && addr == DCSR && U_MODE && DEBUG_SUPPORT) begin + // Debugger can change/observe core state directly through + // dcsr.prv (this doesn't affect debugger operation, as all + // operations have an effective level of M-mode whilst the core + // is halted for debug.) + m_mode <= wdata_update[1]; + end + if (!U_MODE) begin + // Explicitly tie-off to constant; some synthesis tools don't like + // it when variables are only sensitive to the reset. + m_mode <= 1'b1; + mstatus_mpp <= 1'b1; + end + end +end + +// Trap all U-mode WFIs if timeout bit is set. Note that the debug spec +// says "The `wfi` instruction acts as a `nop`" during program buffer +// execution, and nops do not trap, so in debug mode we allow WFIs but +// immediately wake them. +assign trap_wfi = mstatus_tw && !(debug_mode || m_mode); + +reg [XLEN-1:0] mscratch; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + mscratch <= X0; + end else if (!CSR_M_TRAP) begin + // Explicit tie-off when unimplemented + mscratch <= X0; + end else begin + if (wen_m_mode && addr == MSCRATCH) begin + mscratch <= wdata_update; + end + end +end + +// Trap vector base +reg [XLEN-1:0] mtvec_reg; +wire [XLEN-1:0] mtvec = mtvec_reg & ({XLEN{1'b1}} << 2); +wire irq_vector_enable = mtvec_reg[0]; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + mtvec_reg <= MTVEC_INIT; + end else if (!CSR_M_TRAP) begin + // Explicit tie-off when unimplemented + mtvec_reg <= MTVEC_INIT; + end else begin + if (wen_m_mode && addr == MTVEC) begin + mtvec_reg <= update_nonconst(mtvec_reg, MTVEC_WMASK); + end + end +end + +// Exception program counter +reg [XLEN-1:0] mepc; +// mepc only holds values aligned to instruction alignment +localparam MEPC_MASK = {{XLEN-2{1'b1}}, |EXTENSION_C, 1'b0}; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + mepc <= X0; + end else if (!CSR_M_TRAP) begin + // Explicit tie-off when unimplemented + mepc <= X0; + end else begin + if (trapreg_update_enter) begin + mepc <= mepc_in & MEPC_MASK; + end else if (wen_m_mode && addr == MEPC) begin + mepc <= wdata_update & MEPC_MASK; + end + end +end + +// Interrupt enable (reserved bits are tied to 0) +reg [XLEN-1:0] mie; +localparam MIE_WMASK = 32'h00000888; // meie, mtie, msie + +wire meicontext_clearts; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + mie <= X0; + end else if (!CSR_M_TRAP) begin + // Explicit tie-off when unimplemented + mie <= X0; + end else begin + if (wen_m_mode && addr == MIE) begin + mie <= update_nonconst(mie, MIE_WMASK); + end else if (wen_m_mode && addr == MEICONTEXT) begin + // meicontext.clearts/mtiesave/msiesave can be used to clear and + // save standard timer and soft IRQ enables, simultaneously with + // saving external interrupt context. + mie[7] <= (mie[7] || wdata_update[3]) && !meicontext_clearts; + mie[3] <= (mie[3] || wdata_update[2]) && !meicontext_clearts; + end + end +end + +// Interrupt pending register (assigned later). In our implementation this +// register is entirely read-only. +wire [XLEN-1:0] mip; + +// Trap cause registers. The non-constant bits can be written by software, and +// update automatically on trap entry. The `code` field need only be large +// enough to support causes that will be set by hardware, in our case 4 bits. +// (Note this is different to scause which always has a hard minimum of 5 +// bits.) +reg mcause_irq; +reg [3:0] mcause_code; +wire mcause_irq_next; +wire [3:0] mcause_code_next; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + mcause_irq <= 1'b0; + mcause_code <= 4'h0; + end else if (!CSR_M_TRAP) begin + // Explicit tie-off when unimplemented + mcause_irq <= 1'b0; + mcause_code <= 4'h0; + end else begin + if (trapreg_update_enter) begin + mcause_irq <= mcause_irq_next; + mcause_code <= mcause_code_next; + end else if (wen_m_mode && addr == MCAUSE) begin + {mcause_irq, mcause_code} <= {wdata_update[31], wdata_update[3:0]}; + end + end +end + +// ---------------------------------------------------------------------------- +// Standard identification CSRs + +// mimpid records the Hazard3 release version. This is updated manually with +// each release. + +// Set to 1 if this RTL has not been released to the stable branch: +localparam [0:0] MIMPID_PRERELEASE = 1'b0; + +// Major version, e.g the 1 in v1.2.3: +localparam [3:0] MIMPID_MAJOR = 4'd1; +// Minor version, e.g. the 2 in v1.2.3: +localparam [7:0] MIMPID_MINOR = 8'd1; +// Patch version, e.g. the 3 in v1.2.3: +localparam [7:0] MIMPID_PATCH = 8'd0; + +// Note the eco_version field is connected externally -- on ASIC this may be +// connected to tie cells so it can be incremented following a metal ECO. +wire [31:0] mimpid_rdata = { + MIMPID_PRERELEASE, + 3'd0, + MIMPID_MAJOR, + MIMPID_MINOR, + MIMPID_PATCH, + eco_version, + 4'h0 +}; + +localparam [31:0] MISA_VAL = { + 2'h1, // MXL: 32-bit + {XLEN-28{1'b0}}, // WLRL + + 2'd0, // Z, Y, no + 1'b1, // X is always set due to (at least) Xh3misa + 2'd0, // V, W, no + |U_MODE, + 7'd0, // T...N, no + |EXTENSION_M, + 3'd0, // L...J, no + ~|EXTENSION_E, // RVI base ISA + 3'd0, // H, G, F, no + |EXTENSION_E, // RVE base ISA + 1'b0, // D, no + |EXTENSION_C, + &{ // B is defined as ZbaZbbZbs + |EXTENSION_ZBA, + |EXTENSION_ZBB, + |EXTENSION_ZBS + }, + |EXTENSION_A +}; + +// ---------------------------------------------------------------------------- +// Custom external IRQ controller + +wire [XLEN-1:0] irq_ctrl_rdata; +wire external_irq_pending; + +generate +if (|EXTENSION_XH3IRQ) begin: have_irq_ctrl + + hazard3_irq_ctrl #( + `include "hazard3_config_inst.vh" + ) irq_ctrl ( + .clk (clk), + .clk_always_on (clk_always_on), + .rst_n (rst_n), + + .addr (addr), + .wtype (wtype), + .wen_m_mode (wen_m_mode), + .ren_m_mode (ren_m_mode), + .wdata_raw (wdata), + .wdata (wdata_update), + .rdata (irq_ctrl_rdata), + + .trapreg_update_enter (trapreg_update_enter), + .trapreg_update_exit (trapreg_update_exit), + .trap_entry_is_eirq (mcause_irq_next && mcause_code_next == 4'hb), + + .meicontext_clearts (meicontext_clearts), + .mie_mtie (mie[7]), + .mie_msie (mie[3]), + + .irq (irq), + .external_irq_pending (external_irq_pending) + ); + +end else begin: no_irq_ctrl + + reg external_irq_pending_r; + + always @ (posedge clk_always_on or negedge rst_n) begin + if (!rst_n) begin + external_irq_pending_r <= 1'b0; + end else begin + external_irq_pending_r <= |irq; + end + end + + assign irq_ctrl_rdata = {W_DATA{1'b0}}; + assign meicontext_clearts = 1'b0; + assign external_irq_pending = external_irq_pending_r; + +end +endgenerate + +// ---------------------------------------------------------------------------- +// Custom sleep/power control CSRs + +reg msleep_sleeponblock; +reg msleep_powerdown; +reg msleep_deepsleep; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + msleep_sleeponblock <= 1'b0; + msleep_powerdown <= 1'b0; + msleep_deepsleep <= 1'b0; + end else if (wen_m_mode && addr == MSLEEP) begin + msleep_sleeponblock <= wdata_update[2] && |EXTENSION_XH3POWER; + msleep_powerdown <= wdata_update[1] && |EXTENSION_XH3POWER; + msleep_deepsleep <= wdata_update[0] && |EXTENSION_XH3POWER; + end +end + +assign pwr_allow_sleep_on_block = msleep_sleeponblock; +assign pwr_allow_power_down = msleep_powerdown; +assign pwr_allow_clkgate = msleep_deepsleep; + +// ---------------------------------------------------------------------------- +// Custom identification CSRs + +// Derive implied extensions: + +// B is a shorthand for ZbaZbbZbs: +localparam [0:0] EXTENSION_B = |EXTENSION_ZBA && |EXTENSION_ZBB && |EXTENSION_ZBS; +// RV32I and RV32E are complementary: +localparam [0:0] EXTENSION_I = ~|EXTENSION_E; +// Zbkc is a subset of Zbc: +localparam [0:0] EXTENSION_ZBKC = |EXTENSION_ZBC; +// Zca is (instructions-wise) a subset of C: +localparam [0:0] EXTENSION_ZCA = |EXTENSION_C; +// We have data-independent timing for all Zkt instructions except for mulh, +// mulhsu executed on the sequential multiply/divide circuit: +localparam [0:0] EXTENSION_ZKT = !(|EXTENSION_M && !(|MUL_FAST && |MULH_FAST)); +// Zmmul is (instructions-wise) a subset of M: +localparam [0:0] EXTENSION_ZMMUL = |EXTENSION_M; + +localparam [31:0] h3misa_standard_len = 32'd77; + +localparam [127:0] h3misa_standard_extensions = + ({127'd0, |EXTENSION_A } << 0 ) | + ({127'd0, |EXTENSION_B } << 1 ) | + ({127'd0, |EXTENSION_C } << 2 ) | + ({127'd0, |EXTENSION_E } << 4 ) | + ({127'd0, |EXTENSION_I } << 8 ) | + ({127'd0, |EXTENSION_M } << 12) | + ({127'd0, |EXTENSION_ZBA } << 27) | + ({127'd0, |EXTENSION_ZBB } << 28) | + ({127'd0, |EXTENSION_ZBC } << 29) | + ({127'd0, |EXTENSION_ZBKB } << 30) | + ({127'd0, |EXTENSION_ZBC } << 31) | + ({127'd0, |EXTENSION_ZBKX } << 32) | + ({127'd0, |EXTENSION_ZBS } << 33) | + ({127'd0, |EXTENSION_ZKT } << 46) | + ({127'd0, |EXTENSION_C } << 66) | + ({127'd0, |EXTENSION_ZCB } << 67) | + ({127'd0, |EXTENSION_ZILSD } << 72) | + ({127'd0, |EXTENSION_ZCLSD } << 73) | + ({127'd0, |EXTENSION_ZCMP } << 74) | + ({127'd0, |EXTENSION_ZIFENCEI} << 75) | + ({127'd0, |EXTENSION_ZMMUL } << 76) | + 128'd0; + +localparam [31:0] h3misa_custom_len = 32'd5; + +localparam [255:0] h3misa_custom_extensions = { + 32'd0, // Reserved + 32'd0, // Reserved + 32'd0, // Reserved + 32'h01_00_00_00 & {32{|EXTENSION_XH3BEXTM}}, // Xh3bextm + 32'h01_00_00_00 & {32{|EXTENSION_XH3POWER}}, // Xh3power + 32'h01_00_00_00 & {32{|EXTENSION_XH3PMPM}}, // Xh3pmpm + 32'h01_00_00_00 & {32{|EXTENSION_XH3IRQ}}, // Xh3irq + 32'h01_00_00_00 & {32{|CSR_M_MANDATORY}} // Xh3misa +}; + +wire [63:0] h3misa_info = { + h3misa_custom_len, + h3misa_standard_len +}; + +wire [31:0] h3misa_rdata = + !wdata[10] ? h3misa_standard_extensions[32 * wdata[1:0] +: 32] : + !wdata[8] ? h3misa_info [32 * wdata[ 0] +: 32] : + h3misa_custom_extensions [32 * wdata[2:0] +: 32]; + +// ---------------------------------------------------------------------------- +// Counters + +reg mcountinhibit_cy; +reg mcountinhibit_ir; + +reg [XLEN-1:0] mcycleh; +reg [XLEN-1:0] mcycle; +reg [XLEN-1:0] minstreth; +reg [XLEN-1:0] minstret; + +// Writing to either half suppresses increment of the entire 64-bit register. +// It doesn't just replace the value of one 32-bit half. See: +// https://github.com/riscv/riscv-isa-manual/issues/1255 +wire mcycle_stopped = mcountinhibit_cy || debug_mode || wen_m_mode && ( + addr == MCYCLEH || addr == MCYCLE +); + +wire minstret_stopped = mcountinhibit_ir || debug_mode || wen_m_mode && ( + addr == MINSTRETH || addr == MINSTRET +); + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + mcycleh <= X0; + mcycle <= X0; + minstreth <= X0; + minstret <= X0; + // Counters inhibited by default to save energy + mcountinhibit_cy <= 1'b1; + mcountinhibit_ir <= 1'b1; + end else begin + if (!mcycle_stopped) begin + {mcycleh, mcycle} <= + ({mcycleh, mcycle} + 64'd1) & {2*XLEN{|CSR_COUNTER}}; + end + if (instr_ret && !minstret_stopped) begin + {minstreth, minstret} <= + ({minstreth, minstret} + 64'd1) & {2*XLEN{|CSR_COUNTER}}; + end + if (wen_m_mode) begin + if (addr == MCYCLEH) begin + mcycleh <= wdata_update & {XLEN{|CSR_COUNTER}}; + end else if (addr == MCYCLE) begin + mcycle <= wdata_update & {XLEN{|CSR_COUNTER}}; + end else if (addr == MINSTRETH) begin + minstreth <= wdata_update & {XLEN{|CSR_COUNTER}}; + end else if (addr == MINSTRET) begin + minstret <= wdata_update & {XLEN{|CSR_COUNTER}}; + end else if (addr == MCOUNTINHIBIT) begin + {mcountinhibit_ir, mcountinhibit_cy} <= {wdata_update[2], wdata_update[0]} | {2{~|CSR_COUNTER}}; + end + end + end +end + +// Note we still implement tm, even though the time/timeh CSRs are +// unimplemented. The tm bit provides a standard way to configure whether +// M-mode software permits the trapped access to reach the timer registers. +// (This is in fact required by the RVM-CSI platform spec.) +reg mcounteren_cy; +reg mcounteren_tm; +reg mcounteren_ir; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + mcounteren_cy <= 1'b0; + mcounteren_tm <= 1'b0; + mcounteren_ir <= 1'b0; + end else if (wen_m_mode && addr == MCOUNTEREN) begin + mcounteren_cy <= wdata_update[0] && |CSR_COUNTER && |U_MODE; + mcounteren_tm <= wdata_update[1] && |CSR_COUNTER && |U_MODE; + mcounteren_ir <= wdata_update[2] && |CSR_COUNTER && |U_MODE; + end +end + +// ---------------------------------------------------------------------------- +// Debug-mode CSRs + +// The following DCSR bits are read/writable as normal: +// - ebreakm (bit 15) +// - ebreaku (bit 12) if U-mode is supported +// - step (bit 2) +// The following are read-only volatile: +// - cause (bits 8:6) +// All others are hardwired constants. + +reg dcsr_ebreakm; +reg dcsr_ebreaku; +reg dcsr_step; +reg [2:0] dcsr_cause; +wire [2:0] dcause_next; + +localparam DCSR_CAUSE_EBREAK = 3'h1; +localparam DCSR_CAUSE_TRIGGER = 3'h2; +localparam DCSR_CAUSE_HALTREQ = 3'h3; +localparam DCSR_CAUSE_STEP = 3'h4; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + dcsr_ebreakm <= 1'b0; + dcsr_ebreaku <= 1'b0; + dcsr_step <= 1'b0; + dcsr_cause <= 3'h0; + end else begin + // Note we can use the ungated write enable in debug mode since a CSR + // write to a valid D-mode CSR address is guaranteed not to generate + // an exception (of any kind, including PMP) in D-mode. + if (debug_mode && wen_raw && addr == DCSR) begin + {dcsr_ebreakm, dcsr_ebreaku, dcsr_step} <= { + wdata_update[15], + wdata_update[12] && U_MODE, + wdata_update[2] + } & {3{|DEBUG_SUPPORT}}; + end + if (enter_debug_mode) begin + dcsr_cause <= dcause_next & {3{|DEBUG_SUPPORT}}; + end + end +end + +// DPC is mapped to the real PC in the decode module. We set it to the +// exception return address when entering Debug Mode, and whilst in Debug +// Mode we can write to it as though it were a CSR. Note only IALIGN'd values +// can be written to dpc. +assign debug_dpc_wdata = (debug_mode ? wdata_update : mepc_in) & (~X0 << 2 - EXTENSION_C); +assign debug_dpc_wen = debug_mode ? wen_raw && addr == DPC : enter_debug_mode; + +// Debug Module's data0 register is mapped into the core's CSR space: +assign dbg_data0_wdata = wdata; +assign dbg_data0_wen = debug_mode && wen_raw && addr == DMDATA0; + +reg tcontrol_mte; +reg tcontrol_mpte; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + tcontrol_mpte <= 1'b0; + tcontrol_mte <= 1'b0; + end else if (wen_m_mode && addr == TCONTROL) begin + tcontrol_mpte <= wdata_update[7] && DEBUG_SUPPORT; + tcontrol_mte <= wdata_update[3] && DEBUG_SUPPORT; + end else if (DEBUG_SUPPORT && trapreg_update_enter) begin + tcontrol_mte <= 1'b0; + tcontrol_mpte <= tcontrol_mte; + end else if (DEBUG_SUPPORT && trapreg_update_exit) begin + tcontrol_mte <= tcontrol_mpte; + // Unlike mstatus.mie/mpie, tcontrol.mpte is unchanged by trap exit. + end +end + +// ---------------------------------------------------------------------------- +// Read port + detect addressing of unmapped CSRs +// ---------------------------------------------------------------------------- + +reg decode_match; + +// Address match conditions -- certain CSRs are inaccessible if their +// conditions are not met: +wire match_drw = DEBUG_SUPPORT && debug_mode; +wire match_mrw = m_mode || debug_mode; +wire match_mro = (m_mode || debug_mode) && !wen_soon; +wire match_urw = U_MODE && 1'b1; +wire match_uro = U_MODE && !wen_soon; + +always @ (*) begin + decode_match = 1'b0; + rdata = {XLEN{1'b0}}; + pmp_cfg_wen = 1'b0; + trig_cfg_wen = 1'b0; + case (addr) + + // ------------------------------------------------------------------------ + // Mandatory CSRs + + MISA: if (CSR_M_MANDATORY) begin + // WARL, so it is legal to be tied constant + decode_match = match_mrw; + rdata = MISA_VAL; + end + MVENDORID: if (CSR_M_MANDATORY) begin + decode_match = match_mro; + rdata = MVENDORID_VAL; + end + MARCHID: if (CSR_M_MANDATORY) begin + decode_match = match_mro; + // Hazard3's open source architecture ID + rdata = 32'd27; + end + MIMPID: if (CSR_M_MANDATORY) begin + decode_match = match_mro; + rdata = mimpid_rdata; + end + MHARTID: if (CSR_M_MANDATORY) begin + decode_match = match_mro; + rdata = mhartid_val; + end + + MCONFIGPTR: if (CSR_M_MANDATORY) begin + decode_match = match_mro; + rdata = MCONFIGPTR_VAL; + end + + MSTATUS: if (CSR_M_MANDATORY || CSR_M_TRAP) begin + decode_match = match_mrw; + rdata = { + 1'b0, // Never any dirty state besides GPRs + 8'd0, // (WPRI) + 1'b0, // TSR (Trap SRET), tied 0 if no S mode. + mstatus_tw, // TW (Timeout Wait) + 1'b0, // TVM (trap virtual memory), tied 0 if no S mode. + 1'b0, // MXR (Make eXecutable Readable), tied 0 if no S mode. + 1'b0, // SUM, tied 0 if no S mode + mstatus_mprv, // MPRV (modify privilege) + 4'd0, // XS, FS always "off" (no extension state to clear!) + {2{mstatus_mpp}}, // MPP (M-mode previous privilege), only M and U supported + 2'd0, // (WPRI) + 1'b0, // SPP, tied 0 if S mode not supported + mstatus_mpie, // Previous interrupt enable + 3'd0, // No S, U + mstatus_mie, // Interrupt enable + 3'd0 // No S, U + }; + end + + // MSTATUSH is all zeroes (fields are MBE and SBE, which are zero because + // we are pure little-endian.) Prior to priv-1.12 MSTATUSH could be left + // unimplemented in this case, but now it must be decoded even if + // hardwired to 0. + MSTATUSH: if (CSR_M_MANDATORY || CSR_M_TRAP) begin + decode_match = match_mrw; + end + + // MEDELEG, MIDELEG should not exist for no-S-mode implementations. Will + // raise illegal instruction exception if accessed. + + // MENVCFG is seemingly mandatory as of M-mode v1.12, if U-mode is + // implemented. All of its fields are tied to 0 in our implementation. + MENVCFGH: if (U_MODE) begin + decode_match = match_mrw; + end + MENVCFG: if (U_MODE) begin + decode_match = match_mrw; + end + + // ------------------------------------------------------------------------ + // Trap-handling CSRs + + // This is a 32 bit synthesised register with set/clear/write/read, don't + // turn it on unless we really have to + MSCRATCH: if (CSR_M_TRAP && CSR_M_MANDATORY) begin + decode_match = match_mrw; + rdata = mscratch; + end + + MEPC: if (CSR_M_TRAP) begin + decode_match = match_mrw; + rdata = mepc; + end + + MCAUSE: if (CSR_M_TRAP) begin + decode_match = match_mrw; + rdata = { + mcause_irq, // Sign bit is 1 for IRQ, 0 for exception + {27{1'b0}}, // Padding + mcause_code[3:0] // Enough for 16 external IRQs, which is all we have room for in mip/mie + }; + end + + MTVAL: if (CSR_M_TRAP) begin + decode_match = match_mrw; + // Hardwired to 0 + end + + MIE: if (CSR_M_TRAP) begin + decode_match = match_mrw; + rdata = mie; + end + + MIP: if (CSR_M_TRAP) begin + // Writes are permitted, but ignored. + decode_match = match_mrw; + rdata = mip; + end + + MTVEC: if (CSR_M_TRAP) begin + decode_match = match_mrw; + rdata = { + mtvec[XLEN-1:2], // BASE + 1'b0, // Reserved mode bit gets WARL'd to 0 + irq_vector_enable // MODE is vectored (2'h1) or direct (2'h0) + }; + end + + // ------------------------------------------------------------------------ + // Counter CSRs + + // Get the tied WARLs out the way first + MHPMCOUNTER3: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER4: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER5: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER6: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER7: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER8: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER9: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER10: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER11: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER12: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER13: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER14: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER15: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER16: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER17: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER18: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER19: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER20: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER21: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER22: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER23: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER24: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER25: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER26: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER27: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER28: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER29: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER30: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER31: if (CSR_COUNTER) begin decode_match = match_mrw; end + + MHPMCOUNTER3H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER4H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER5H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER6H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER7H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER8H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER9H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER10H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER11H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER12H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER13H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER14H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER15H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER16H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER17H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER18H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER19H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER20H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER21H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER22H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER23H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER24H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER25H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER26H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER27H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER28H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER29H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER30H: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMCOUNTER31H: if (CSR_COUNTER) begin decode_match = match_mrw; end + + MHPMEVENT3: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT4: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT5: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT6: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT7: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT8: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT9: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT10: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT11: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT12: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT13: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT14: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT15: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT16: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT17: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT18: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT19: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT20: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT21: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT22: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT23: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT24: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT25: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT26: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT27: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT28: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT29: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT30: if (CSR_COUNTER) begin decode_match = match_mrw; end + MHPMEVENT31: if (CSR_COUNTER) begin decode_match = match_mrw; end + + MCOUNTINHIBIT: if (CSR_COUNTER) begin + decode_match = match_mrw; + rdata = { + 29'd0, + mcountinhibit_ir, + 1'b0, + mcountinhibit_cy + }; + end + + MCYCLE: if (CSR_COUNTER) begin + decode_match = match_mrw; + rdata = mcycle; + end + MINSTRET: if (CSR_COUNTER) begin + decode_match = match_mrw; + rdata = minstret; + end + + MCYCLEH: if (CSR_COUNTER) begin + decode_match = match_mrw; + rdata = mcycleh; + end + MINSTRETH: if (CSR_COUNTER) begin + decode_match = match_mrw; + rdata = minstreth; + end + + MCOUNTEREN: if (CSR_COUNTER && U_MODE) begin + decode_match = match_mrw; + rdata = { + 29'd0, + mcounteren_ir, + mcounteren_tm, + mcounteren_cy + }; + end + + // ------------------------------------------------------------------------ + // PMP CSRs (bridge to PMP config interface) + + // If PMP is present, all 16 registers are present, but some may be WARL'd + // to 0 depending on how many regions are actually implemented. + PMPCFG0: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPCFG1: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPCFG2: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPCFG3: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR0: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR1: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR2: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR3: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR4: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR5: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR6: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR7: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR8: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR9: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR10: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR11: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR12: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR13: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR14: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + PMPADDR15: if (PMP_REGIONS > 0) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + + // MSECCFG is strictly optional, and we don't implement any of its + // features (ePMP etc) so we don't decode it. + + // ------------------------------------------------------------------------ + // U-mode CSRs + + // The read-only counters are always visible to M mode, and are visible to + // U mode if the corresponding mcounteren bit is set. + CYCLE: if (CSR_COUNTER) begin + decode_match = mcounteren_cy ? match_uro : match_mro; + rdata = mcycle; + end + CYCLEH: if (CSR_COUNTER) begin + decode_match = mcounteren_cy ? match_uro : match_mro; + rdata = mcycleh; + end + + INSTRET: if (CSR_COUNTER) begin + decode_match = mcounteren_ir ? match_uro : match_mro; + rdata = minstret; + end + INSTRETH: if (CSR_COUNTER) begin + decode_match = mcounteren_ir ? match_uro : match_mro; + rdata = minstreth; + end + + // ------------------------------------------------------------------------ + // Trigger Module CSRs + + // When debug support is enabled, we always have basic trigger support + // (instruction count, interrupt and exception). Breakpoints are optional. + TSELECT: if (DEBUG_SUPPORT) begin + decode_match = match_mrw; + trig_cfg_wen = match_mrw && wen; + rdata = trig_cfg_rdata; + end + + TDATA1: if (DEBUG_SUPPORT) begin + decode_match = match_mrw; + trig_cfg_wen = match_mrw && wen; + rdata = trig_cfg_rdata; + end + + TDATA2: if (DEBUG_SUPPORT) begin + decode_match = match_mrw; + trig_cfg_wen = match_mrw && wen; + rdata = trig_cfg_rdata; + end + + TDATA3: if (DEBUG_SUPPORT) begin + // Unimplemented but must be accessible, so hardwired to 0. + decode_match = match_mrw; + rdata = 32'h00000000; + end + + TINFO: if (DEBUG_SUPPORT) begin + // Note tinfo is a read-write CSR (writes don't trap) even though it + // is entirely read-only. + decode_match = match_mrw; + trig_cfg_wen = match_mrw && wen; + rdata = trig_cfg_rdata; + end + + TCONTROL: if (DEBUG_SUPPORT) begin + decode_match = match_mrw; + rdata = { + 24'h0, + tcontrol_mpte, + 3'h0, + tcontrol_mte, + 3'h0 + }; + end + + // ------------------------------------------------------------------------ + // Debug CSRs + + DCSR: if (DEBUG_SUPPORT) begin + decode_match = match_drw; + rdata = { + 4'h4, // xdebugver = 4, for 0.13.2 debug spec + 12'd0, // reserved + dcsr_ebreakm, + 2'h0, // No mode 2/1 + dcsr_ebreaku, + 1'b0, // stepie = 0, no interrupts in single-step mode + 1'b1, // stopcount = 1, no counter increment in debug mode + 1'b1, // stoptime = 0, no core-local timer increment in debug mode + dcsr_cause, + 1'b0, // reserved + 1'b0, // mprven = 0, debugger always acts as M-mode + 1'b0, // nmip = 0, we have no NMI + dcsr_step, + {2{m_mode}} // priv = M or U, report the core state directly + }; + end + + DPC: if (DEBUG_SUPPORT) begin + decode_match = match_drw; + rdata = debug_dpc_rdata; + end + + DMDATA0: if (DEBUG_SUPPORT) begin + decode_match = match_drw; + rdata = dbg_data0_rdata; + end + + // ------------------------------------------------------------------------ + // Custom CSRs + + PMPCFGM0: if (PMP_REGIONS > 0 && EXTENSION_XH3PMPM) begin + decode_match = match_mrw; + pmp_cfg_wen = match_mrw && wen; + rdata = pmp_cfg_rdata; + end + + MEIEA: if (CSR_M_TRAP && EXTENSION_XH3IRQ) begin + decode_match = match_mrw; + rdata = irq_ctrl_rdata; + end + + MEIPA: if (CSR_M_TRAP && EXTENSION_XH3IRQ) begin + decode_match = match_mrw; + rdata = irq_ctrl_rdata; + end + + MEIFA: if (CSR_M_TRAP && EXTENSION_XH3IRQ) begin + decode_match = match_mrw; + rdata = irq_ctrl_rdata; + end + + MEIPRA: if (CSR_M_TRAP && EXTENSION_XH3IRQ) begin + decode_match = match_mrw; + rdata = irq_ctrl_rdata; + end + + MEINEXT: if (CSR_M_TRAP && EXTENSION_XH3IRQ) begin + decode_match = match_mrw; + rdata = irq_ctrl_rdata; + end + + MEICONTEXT: if (CSR_M_TRAP && EXTENSION_XH3IRQ) begin + decode_match = match_mrw; + rdata = irq_ctrl_rdata; + end + + MSLEEP: if (EXTENSION_XH3POWER) begin + decode_match = match_mrw; + rdata = { + 29'h0, + msleep_sleeponblock, + msleep_powerdown, + msleep_deepsleep + }; + end + + H3MISA: if (CSR_M_MANDATORY) begin + decode_match = match_mrw; + rdata = h3misa_rdata; + end + + default: begin end + endcase +end + +assign illegal = (wen_soon || ren_soon) && !decode_match; + +// Trigger refetch of sequentially-next instructions on certain CSR updates: +assign write_is_fetch_ordered = wen_raw && !debug_mode && ( + (PMP_REGIONS > 0 && addr >= PMPADDR0 && addr <= PMPADDR15) || + (PMP_REGIONS > 0 && addr >= PMPCFG0 && addr <= PMPCFG3 ) || + (DEBUG_SUPPORT > 0 && addr == TDATA1 ) || + (DEBUG_SUPPORT > 0 && addr == TDATA2 ) || + (DEBUG_SUPPORT > 0 && addr == TCONTROL ) +); + +// ---------------------------------------------------------------------------- +// Debug run/halt + +// req_resume_prev is to cut an in->out path from request to trap addr. +reg have_just_reset; +reg step_halt_req; +reg dbg_req_resume_prev; +reg dbg_req_halt_prev; +reg dbg_req_halt_on_reset_sticky; +reg pending_dbg_resume_prev; + +wire pending_dbg_resume = (pending_dbg_resume_prev || dbg_req_resume_prev) && debug_mode; + +// Note exception entry is also considered a step -- in this case we are +// supposed to take the trap and then break to debug mode before executing the +// first trap instruction. *IRQ* entry does not occur when step is 1 (because +// we hardwire dcsr.stepie to 0) +wire step_event = instr_ret || (trap_enter_vld && trap_enter_rdy); + +// Halt request input register needs to always be clocked, because a WFI needs +// to fall through if the debugger requests halt of a sleeping core. +always @ (posedge clk_always_on or negedge rst_n) begin + if (!rst_n) begin + dbg_req_halt_prev <= 1'b0; + end else begin + // Just a delayed version of the request from outside of the core. + // Delay is fine because the DM awaits ack before deasserting. + dbg_req_halt_prev <= dbg_req_halt && DEBUG_SUPPORT != 0; + end +end + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + have_just_reset <= |DEBUG_SUPPORT; + dbg_req_halt_on_reset_sticky <= 1'b0; + step_halt_req <= 1'b0; + dbg_req_resume_prev <= 1'b0; + pending_dbg_resume_prev <= 1'b0; + end else if (DEBUG_SUPPORT) begin + have_just_reset <= 1'b0; + + // One-cycle delay on assertion of reset halt req is harmless because + // of a matching delay on the first instruction fetch (reset_holdoff). + if (have_just_reset && dbg_req_halt_on_reset) begin + dbg_req_halt_on_reset_sticky <= 1'b1; + end else if (enter_debug_mode) begin + dbg_req_halt_on_reset_sticky <= 1'b0; + end + + dbg_req_resume_prev <= dbg_req_resume; + + if (debug_mode) begin + step_halt_req <= 1'b0; + end else if (dcsr_step && step_event) begin + step_halt_req <= 1'b1; + end + + pending_dbg_resume_prev <= pending_dbg_resume; + end +end + +// We can enter the halted state in an IRQ-like manner (squeeze in between the +// instructions in stage 2 and stage 3) or in an exception-like manner +// (replace the instruction in stage 3). +// +// Halt request and step-break are IRQ-like. We need to be careful when a halt +// request lines up with an instruction in M which either has generated an +// exception (e.g. an ecall) or may yet generate an exception (a load). In +// this case the correct thing to do is to: +// +// - Squash whatever instruction may be in X, and inhibit X PC increment +// - Wait until after the exception entry is taken (or the load/store +// completes successfully) +// - Immediately trigger an IRQ-like debug entry. +// +// This ensures the debugger sees mcause/mepc set correctly, with dpc pointing +// to the handler entry point, if the instruction excepts. + +wire exception_req_any; +wire halt_delayed_by_exception = exception_req_any || loadstore_dphase_pending; + +wire want_halt_except = DEBUG_SUPPORT && !debug_mode && ( + dcsr_ebreakm && m_mode && except == EXCEPT_EBREAK || + dcsr_ebreaku && !m_mode && except == EXCEPT_EBREAK || + except_to_d_mode && except == EXCEPT_EBREAK +); + +// Note exception-like causes (trigger, ebreak) are higher priority than +// IRQ-like. +// +// We must mask halt_req with delay_irq_entry (true on cycle 2 and beyond of +// load/store address phase) because at that point we can't suppress the bus +// access. Others are fine because they are never mid-instruction in stage 2. +wire want_halt_irq_if_no_exception = DEBUG_SUPPORT && !debug_mode && !want_halt_except && ( + (dbg_req_halt_prev && !delay_irq_entry) || + dbg_req_halt_on_reset_sticky || + step_halt_req +); + +// Exception (or potential exception due to load/store) in M delays halt +// entry. The halt intention still blocks X, so we can't get blocked forever +// by a string of load/stores. This is just here to get sequencing between an +// exception (or potential exception) in M, and debug mode entry. +wire want_halt_irq = want_halt_irq_if_no_exception && !halt_delayed_by_exception; + +assign dcause_next = + except == EXCEPT_EBREAK && except_to_d_mode ? 3'h2 : // trigger (priority 4) + except == EXCEPT_EBREAK ? 3'h1 : // ebreak (priority 3) + dbg_req_halt_prev || dbg_req_halt_on_reset ? 3'h3 : // halt or reset-halt (priority 1, 2) + 3'h4; // single-step (priority 0) + +wire trap_is_debug_entry = |DEBUG_SUPPORT && !debug_mode && (want_halt_irq || want_halt_except); +assign enter_debug_mode = trap_is_debug_entry && trap_enter_rdy; + +assign exit_debug_mode = debug_mode && pending_dbg_resume && trap_enter_rdy; + +// Report back to DM instruction injector to tell it its instruction sequence +// has finished (ebreak) or crashed out +assign dbg_instr_caught_ebreak = debug_mode && except == EXCEPT_EBREAK && trap_enter_rdy; + +// Note we exclude ebreak from here regardless of dcsr.ebreakm, since we are +// already in debug mode at this point +assign dbg_instr_caught_exception = debug_mode && except != EXCEPT_NONE && + except != EXCEPT_EBREAK && trap_enter_rdy; + +// ---------------------------------------------------------------------------- +// Trap request generation + +reg irq_software_r; +reg irq_timer_r; + +// Always clocked, as it's used to generate a wakeup. +always @ (posedge clk_always_on or negedge rst_n) begin + if (!rst_n) begin + irq_software_r <= 1'b0; + irq_timer_r <= 1'b0; + end else begin + irq_software_r <= irq_software; + irq_timer_r <= irq_timer; + end +end + +assign mip = { + 20'h0, // Reserved + external_irq_pending, // meip, Global pending bit for external IRQs + 3'h0, // Reserved + irq_timer_r, // mtip, interrupt from memory-mapped timer peripheral + 3'h0, // Reserved + irq_software_r, // msip, software interrupt from memory-mapped register + 3'h0 // Reserved +}; + +wire irq_active = |(mip & mie) && (mstatus_mie || !m_mode) && !dcsr_step; + +// WFI clear respects individual interrupt enables but ignores mstatus.mie. +// Additionally, wfi is treated as a nop during single-stepping and D-mode. +// Note that the IRQs and debug halt request input registers are clocked by +// clk_always_on, so that a wakeup can be generated when asleep. Note also +// that want_halt_irq_if_no_exception can be masked while asleep, so also +// feed in the raw halt request as a wakeup source. +assign pwr_wfi_wakeup_req = |(mip & mie) || dcsr_step || debug_mode || + want_halt_irq_if_no_exception || dbg_req_halt_prev; + +// Priority order from priv spec: external > software > timer +wire [3:0] standard_irq_num = + mip[11] && mie[11] ? 4'd11 : + mip[3] && mie[3] ? 4'd3 : + mip[7] && mie[7] ? 4'd7 : 4'd0; + +// ebreak may be treated as a halt-to-debugger or a regular M-mode exception, +// depending on dcsr.ebreakm. +assign exception_req_any = except != EXCEPT_NONE && !( + except == EXCEPT_EBREAK && (m_mode ? dcsr_ebreakm : dcsr_ebreaku) +); + +wire [3:0] mcause_irq_num = irq_active ? standard_irq_num : 4'd0; + +wire [3:0] vector_sel = !exception_req_any && irq_vector_enable ? mcause_irq_num : 4'd0; + +// Note in the refetch case we're relying on the aliasing of dpc and pc +// (The intent is to fetch the sequentially-next instruction again) +assign trap_addr = + except == EXCEPT_MRET ? mepc : + except == EXCEPT_REFETCH ? debug_dpc_rdata : + pending_dbg_resume ? debug_dpc_rdata : + mtvec | {26'h0, vector_sel, 2'h0}; + +// Check for exception-like or IRQ-like trap entry; any debug mode entry takes +// priority over any regular trap. +assign trap_is_irq = DEBUG_SUPPORT && (want_halt_except || want_halt_irq) ? + !want_halt_except : !exception_req_any; + +// When there is a loadstore dphase pending, we block off the IRQ trap +// request, but still assert the trap_enter_soon flag. This blocks issue of +// further load/stores which have not yet been locked into aphase, so the IRQ +// can eventually go through. It's not safe to enter an IRQ when a dphase is +// in progress, because that dphase could subsequently except, and sample the +// IRQ vector PC instead of the load/store instruction PC. + +assign trap_enter_vld = + CSR_M_TRAP && (exception_req_any || + !delay_irq_entry && !debug_mode && irq_active && !loadstore_dphase_pending) || + DEBUG_SUPPORT && ( + (!delay_irq_entry && want_halt_irq) || want_halt_except || pending_dbg_resume); + +assign trap_enter_soon = trap_enter_vld || ( + |DEBUG_SUPPORT && want_halt_irq_if_no_exception || + !delay_irq_entry && !debug_mode && irq_active && loadstore_dphase_pending +); + +assign mcause_irq_next = !exception_req_any; +assign mcause_code_next = exception_req_any ? except : mcause_irq_num; + +// Report trap entry to trigger unit +assign trig_event_exception = |DEBUG_SUPPORT && trapreg_update_enter && !trap_is_irq; +assign trig_event_interrupt = |DEBUG_SUPPORT && trapreg_update_enter && trap_is_irq; +assign trig_event_trap_cause = {4{|DEBUG_SUPPORT}} & mcause_code_next; + +// ---------------------------------------------------------------------------- +// Privilege state outputs + +assign pmp_cfg_addr = addr; +assign pmp_cfg_wdata = wdata_update; + +assign trig_cfg_addr = addr; +assign trig_cfg_wdata = wdata_update; +assign trig_m_en = tcontrol_mte; + +// Effective privilege for execution. Used for: +// - Privilege level of branch target fetches (frontend keeps fetch privilege +// constant during sequential fetch) +// - Checking PC against PMP execute permission + +assign m_mode_execution = !U_MODE || debug_mode || m_mode; + +// Effective privilege for trap entry. Used for: +// - Privilege level of trap target fetches (frontend keeps fetch privilege +// constant during sequential fetch) + +assign m_mode_trap_entry = !U_MODE || ( + except == EXCEPT_MRET ? mstatus_mpp : 1'b1 +); + +// Effective privilege for load/stores. Used for: +// - Privilege level of load/stores on the bus +// - Checking load/stores against PMP read/write permission + +assign m_mode_loadstore = !U_MODE || debug_mode || ( + mstatus_mprv ? mstatus_mpp : m_mode +); + +// ---------------------------------------------------------------------------- +// Properties + +`ifdef HAZARD3_ASSERTIONS + +// Keep track of whether we are in a trap (only for formal property purposes) +reg in_trap; + +always @ (posedge clk or negedge rst_n) + if (!rst_n) + in_trap <= 1'b0; + else + in_trap <= (in_trap || (trap_enter_vld && trap_enter_rdy)) + && !(trap_enter_vld && trap_enter_rdy && except == EXCEPT_MRET); + +always @ (posedge clk) begin +`ifdef RISCV_FORMAL + // Assume there are no nested exceptions, to stop riscv-formal from doing + // annoying things like stopping instructions from retiring by repeatedly + // feeding in invalid instructions + if (in_trap) + assume(except == EXCEPT_NONE || except == EXCEPT_MRET); + + // Note the actual IRQ registers have been moved into hazard3_irq_ctrl + // (and are now optional) so repeat them here for the property + reg [NUM_IRQS-1:0] irq_r; + always @ (posedge clk_always_on or negedge rst_n) begin + if (!rst_n) begin + irq_r <= {NUM_IRQS{1'b0}}; + end else begin + irq_r <= irq; + end + end + + // Assume IRQs are not deasserted on cycles where exception entry does not + // take place + if (!trap_enter_rdy) + assume(~|(irq_r & ~irq)); +`endif + + // Make sure CSR accesses are flushed + if (trap_enter_vld && trap_enter_rdy) + assert(!(wen || ren)); + + // Writing to CSR on cycle after trap entry -- should be impossible, a CSR + // access instruction should have been flushed or moved down to stage 3, and + // no fetch could have arrived by now + if ($past(trap_enter_vld && trap_enter_rdy)) + assert(!wen); + + // Should be impossible to get into the trap and exit immediately: + if (in_trap && !$past(in_trap)) + assert(except != EXCEPT_MRET); + + // Should be impossible to get to another mret so soon after exiting: + if ($past(except == EXCEPT_MRET && trap_enter_vld && trap_enter_rdy)) + assert(except != EXCEPT_MRET); + + // Must be impossible to enter two traps on consecutive cycles. Most importantly: + // + // - IRQ -> Except: no new instruction could have been fetched by this point, + // and an exception by e.g. a left-behind store data phase would sample the + // wrong PC. + // + // - Except -> IRQ: would need to re-set mstatus.mie first, shouldn't happen + // + // Sole exclusion is mret. We can return from an interrupt/exception and take + // a new interrupt on the next cycle. + + if ($past(trap_enter_vld && trap_enter_rdy && except != EXCEPT_MRET)) + assert(!(trap_enter_vld && trap_enter_rdy)); + + // Just to stress that this is the the only case: + if (trap_enter_vld && trap_enter_rdy && $past(trap_enter_vld && trap_enter_rdy)) + assert($past(except == EXCEPT_MRET)); + + if (rst_n && $past(trap_enter_vld && !trap_enter_rdy && !trap_is_irq)) begin + // Exception which didn't go through should not disappear + assert(trap_enter_vld); + // Exception should not be replaced by IRQ + assert(!trap_is_irq); + end + + // This must be a breakpoint exception (presumably from the trigger unit). + if (except_to_d_mode) begin + assert(except == EXCEPT_EBREAK); + end + + // Exceptions should squash load/stores before they reach dphase. + if (except != EXCEPT_NONE && !(except == EXCEPT_LOAD_FAULT || except == EXCEPT_STORE_FAULT)) begin + assert(!loadstore_dphase_pending); + end + + // Debug entries are trap entries! (we should also assert in core.v that + // trap entries are never coincident with active load/store address + // phases, which gives some confidence that debug entry does not gobble + // load/stores.) + if (enter_debug_mode) begin + assert(trap_enter_vld); + end +end + +`endif + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/hazard3_csr_addr.vh b/vendor/Hazard3/hdl/hazard3_csr_addr.vh new file mode 100644 index 0000000..035af00 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_csr_addr.vh @@ -0,0 +1,204 @@ +/*****************************************************************************\ +| Copyright (C) 2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// List of addresses for CSRs implemented by Hazard3, including custom CSRs. + +// ---------------------------------------------------------------------------- +// M-mode CSRs + +// Machine Information Registers (RO) +localparam MVENDORID = 12'hf11; // Vendor ID. +localparam MARCHID = 12'hf12; // Architecture ID. +localparam MIMPID = 12'hf13; // Implementation ID. +localparam MHARTID = 12'hf14; // Hardware thread ID. +localparam MCONFIGPTR = 12'hf15; // Pointer to configuration data structure. + +// Machine Trap Setup (RW) +localparam MSTATUS = 12'h300; // Machine status register. +localparam MSTATUSH = 12'h310; // As of priv-1.12 this must be present even if tied 0. +localparam MISA = 12'h301; // ISA and extensions +localparam MEDELEG = 12'h302; // Machine exception delegation register. +localparam MIDELEG = 12'h303; // Machine interrupt delegation register. +localparam MIE = 12'h304; // Machine interrupt-enable register. +localparam MTVEC = 12'h305; // Machine trap-handler base address. +localparam MCOUNTEREN = 12'h306; // Machine counter enable. + +// Machine Trap Handling (RW) +localparam MSCRATCH = 12'h340; // Scratch register for machine trap handlers. +localparam MEPC = 12'h341; // Machine exception program counter. +localparam MCAUSE = 12'h342; // Machine trap cause. +localparam MTVAL = 12'h343; // Machine bad address or instruction. +localparam MIP = 12'h344; // Machine interrupt pending. + +// Machine Memory Protection (RW) +localparam PMPCFG0 = 12'h3a0; // Physical memory protection configuration. +localparam PMPCFG1 = 12'h3a1; // Physical memory protection configuration, RV32 only. +localparam PMPCFG2 = 12'h3a2; // Physical memory protection configuration. +localparam PMPCFG3 = 12'h3a3; // Physical memory protection configuration, RV32 only. +localparam PMPADDR0 = 12'h3b0; // Physical memory protection address register. +localparam PMPADDR1 = 12'h3b1; // ... +localparam PMPADDR2 = 12'h3b2; +localparam PMPADDR3 = 12'h3b3; +localparam PMPADDR4 = 12'h3b4; +localparam PMPADDR5 = 12'h3b5; +localparam PMPADDR6 = 12'h3b6; +localparam PMPADDR7 = 12'h3b7; +localparam PMPADDR8 = 12'h3b8; +localparam PMPADDR9 = 12'h3b9; +localparam PMPADDR10 = 12'h3ba; +localparam PMPADDR11 = 12'h3bb; +localparam PMPADDR12 = 12'h3bc; +localparam PMPADDR13 = 12'h3bd; +localparam PMPADDR14 = 12'h3be; +localparam PMPADDR15 = 12'h3bf; + +localparam MSECCFG = 12'h747; +localparam MSECCFGH = 12'h757; + +// Performance counters (RW) +localparam MCYCLE = 12'hb00; // Raw cycles since start of day +localparam MINSTRET = 12'hb02; // Instruction retire count since start of day +localparam MHPMCOUNTER3 = 12'hb03; // WARL (we tie to 0) +localparam MHPMCOUNTER4 = 12'hb04; // WARL (we tie to 0) +localparam MHPMCOUNTER5 = 12'hb05; // WARL (we tie to 0) +localparam MHPMCOUNTER6 = 12'hb06; // WARL (we tie to 0) +localparam MHPMCOUNTER7 = 12'hb07; // WARL (we tie to 0) +localparam MHPMCOUNTER8 = 12'hb08; // WARL (we tie to 0) +localparam MHPMCOUNTER9 = 12'hb09; // WARL (we tie to 0) +localparam MHPMCOUNTER10 = 12'hb0a; // WARL (we tie to 0) +localparam MHPMCOUNTER11 = 12'hb0b; // WARL (we tie to 0) +localparam MHPMCOUNTER12 = 12'hb0c; // WARL (we tie to 0) +localparam MHPMCOUNTER13 = 12'hb0d; // WARL (we tie to 0) +localparam MHPMCOUNTER14 = 12'hb0e; // WARL (we tie to 0) +localparam MHPMCOUNTER15 = 12'hb0f; // WARL (we tie to 0) +localparam MHPMCOUNTER16 = 12'hb10; // WARL (we tie to 0) +localparam MHPMCOUNTER17 = 12'hb11; // WARL (we tie to 0) +localparam MHPMCOUNTER18 = 12'hb12; // WARL (we tie to 0) +localparam MHPMCOUNTER19 = 12'hb13; // WARL (we tie to 0) +localparam MHPMCOUNTER20 = 12'hb14; // WARL (we tie to 0) +localparam MHPMCOUNTER21 = 12'hb15; // WARL (we tie to 0) +localparam MHPMCOUNTER22 = 12'hb16; // WARL (we tie to 0) +localparam MHPMCOUNTER23 = 12'hb17; // WARL (we tie to 0) +localparam MHPMCOUNTER24 = 12'hb18; // WARL (we tie to 0) +localparam MHPMCOUNTER25 = 12'hb19; // WARL (we tie to 0) +localparam MHPMCOUNTER26 = 12'hb1a; // WARL (we tie to 0) +localparam MHPMCOUNTER27 = 12'hb1b; // WARL (we tie to 0) +localparam MHPMCOUNTER28 = 12'hb1c; // WARL (we tie to 0) +localparam MHPMCOUNTER29 = 12'hb1d; // WARL (we tie to 0) +localparam MHPMCOUNTER30 = 12'hb1e; // WARL (we tie to 0) +localparam MHPMCOUNTER31 = 12'hb1f; // WARL (we tie to 0) + +localparam MCYCLEH = 12'hb80; // High halves of each counter +localparam MINSTRETH = 12'hb82; +localparam MHPMCOUNTER3H = 12'hb83; +localparam MHPMCOUNTER4H = 12'hb84; +localparam MHPMCOUNTER5H = 12'hb85; +localparam MHPMCOUNTER6H = 12'hb86; +localparam MHPMCOUNTER7H = 12'hb87; +localparam MHPMCOUNTER8H = 12'hb88; +localparam MHPMCOUNTER9H = 12'hb89; +localparam MHPMCOUNTER10H = 12'hb8a; +localparam MHPMCOUNTER11H = 12'hb8b; +localparam MHPMCOUNTER12H = 12'hb8c; +localparam MHPMCOUNTER13H = 12'hb8d; +localparam MHPMCOUNTER14H = 12'hb8e; +localparam MHPMCOUNTER15H = 12'hb8f; +localparam MHPMCOUNTER16H = 12'hb90; +localparam MHPMCOUNTER17H = 12'hb91; +localparam MHPMCOUNTER18H = 12'hb92; +localparam MHPMCOUNTER19H = 12'hb93; +localparam MHPMCOUNTER20H = 12'hb94; +localparam MHPMCOUNTER21H = 12'hb95; +localparam MHPMCOUNTER22H = 12'hb96; +localparam MHPMCOUNTER23H = 12'hb97; +localparam MHPMCOUNTER24H = 12'hb98; +localparam MHPMCOUNTER25H = 12'hb99; +localparam MHPMCOUNTER26H = 12'hb9a; +localparam MHPMCOUNTER27H = 12'hb9b; +localparam MHPMCOUNTER28H = 12'hb9c; +localparam MHPMCOUNTER29H = 12'hb9d; +localparam MHPMCOUNTER30H = 12'hb9e; +localparam MHPMCOUNTER31H = 12'hb9f; + +localparam MCOUNTINHIBIT = 12'h320; // Count inhibit register for mcycle/minstret +localparam MHPMEVENT3 = 12'h323; // WARL (we tie to 0) +localparam MHPMEVENT4 = 12'h324; // WARL (we tie to 0) +localparam MHPMEVENT5 = 12'h325; // WARL (we tie to 0) +localparam MHPMEVENT6 = 12'h326; // WARL (we tie to 0) +localparam MHPMEVENT7 = 12'h327; // WARL (we tie to 0) +localparam MHPMEVENT8 = 12'h328; // WARL (we tie to 0) +localparam MHPMEVENT9 = 12'h329; // WARL (we tie to 0) +localparam MHPMEVENT10 = 12'h32a; // WARL (we tie to 0) +localparam MHPMEVENT11 = 12'h32b; // WARL (we tie to 0) +localparam MHPMEVENT12 = 12'h32c; // WARL (we tie to 0) +localparam MHPMEVENT13 = 12'h32d; // WARL (we tie to 0) +localparam MHPMEVENT14 = 12'h32e; // WARL (we tie to 0) +localparam MHPMEVENT15 = 12'h32f; // WARL (we tie to 0) +localparam MHPMEVENT16 = 12'h330; // WARL (we tie to 0) +localparam MHPMEVENT17 = 12'h331; // WARL (we tie to 0) +localparam MHPMEVENT18 = 12'h332; // WARL (we tie to 0) +localparam MHPMEVENT19 = 12'h333; // WARL (we tie to 0) +localparam MHPMEVENT20 = 12'h334; // WARL (we tie to 0) +localparam MHPMEVENT21 = 12'h335; // WARL (we tie to 0) +localparam MHPMEVENT22 = 12'h336; // WARL (we tie to 0) +localparam MHPMEVENT23 = 12'h337; // WARL (we tie to 0) +localparam MHPMEVENT24 = 12'h338; // WARL (we tie to 0) +localparam MHPMEVENT25 = 12'h339; // WARL (we tie to 0) +localparam MHPMEVENT26 = 12'h33a; // WARL (we tie to 0) +localparam MHPMEVENT27 = 12'h33b; // WARL (we tie to 0) +localparam MHPMEVENT28 = 12'h33c; // WARL (we tie to 0) +localparam MHPMEVENT29 = 12'h33d; // WARL (we tie to 0) +localparam MHPMEVENT30 = 12'h33e; // WARL (we tie to 0) +localparam MHPMEVENT31 = 12'h33f; // WARL (we tie to 0) + +// Other standard M-mode CSRs: +localparam MENVCFG = 12'h30a; +localparam MENVCFGH = 12'h31a; + +// Custom M-mode CSRs: +localparam PMPCFGM0 = 12'hbd0; // Make PMP regions M-mode without locking +// bd1 // (reserved for >32 regions) + +localparam MEIEA = 12'hbe0; // External interrupt pending array +localparam MEIPA = 12'hbe1; // External interrupt enable array +localparam MEIFA = 12'hbe2; // External interrupt force array +localparam MEIPRA = 12'hbe3; // External interrupt priority array +localparam MEINEXT = 12'hbe4; // Next external interrupt +localparam MEICONTEXT = 12'hbe5; // External interrupt context register + +localparam MSLEEP = 12'hbf0; // M-mode sleep control register +localparam H3MISA = 12'hbf1; // Hazard3 M-mode ISA identification register + +// ---------------------------------------------------------------------------- +// U-mode CSRs + +// Read-only aliases of M-mode counter CSRs: +localparam CYCLE = 12'hc00; +localparam TIME = 12'hc01; +localparam INSTRET = 12'hc02; +localparam CYCLEH = 12'hc80; +localparam TIMEH = 12'hc81; +localparam INSTRETH = 12'hc82; + +// Custom U-mode CSRs +localparam SLEEP = 12'h8f0; // U-mode subset of M-mode sleep control + +// ---------------------------------------------------------------------------- +// Trigger Module + +localparam TSELECT = 12'h7a0; +localparam TDATA1 = 12'h7a1; +localparam TDATA2 = 12'h7a2; +localparam TDATA3 = 12'h7a3; +localparam TINFO = 12'h7a4; +localparam TCONTROL = 12'h7a5; +localparam MCONTEXT = 12'h7a8; + +// ---------------------------------------------------------------------------- +// D-mode CSRs + +localparam DCSR = 12'h7b0; +localparam DPC = 12'h7b1; +localparam DMDATA0 = 12'hbff; // Custom read/write diff --git a/vendor/Hazard3/hdl/hazard3_decode.v b/vendor/Hazard3/hdl/hazard3_decode.v new file mode 100644 index 0000000..b8bd750 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_decode.v @@ -0,0 +1,594 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2023 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +`default_nettype none + +module hazard3_decode #( +`include "hazard3_config.vh" +, +`include "hazard3_width_const.vh" +) ( + input wire clk, + input wire rst_n, + + input wire [31:0] fd_cir, + input wire [1:0] fd_cir_err, + input wire [1:0] fd_cir_predbranch, + input wire [1:0] fd_cir_vld, + input wire fd_cir_is_32bit, + input wire fd_cir_invalid_16bit, + input wire fd_cir_is_uop, + input wire fd_cir_uop_nonfinal, + input wire fd_cir_uop_no_pc_update, + input wire fd_cir_uop_atomic, + + output wire [1:0] df_cir_use, + output wire df_cir_flush_behind, + + output wire df_uop_stall, + output wire df_uop_clear, + output wire df_lspair_phase_next, + + output wire [W_ADDR-1:0] d_pc, + + input wire debug_mode, + input wire m_mode, + input wire trap_wfi, + + input wire [W_ADDR-1:0] debug_dpc_wdata, + input wire debug_dpc_wen, + output wire [W_ADDR-1:0] debug_dpc_rdata, + + output wire d_starved, + input wire x_stall, + input wire f_jump_now, + input wire [W_ADDR-1:0] f_jump_target, + input wire x_jump_not_except, + input wire [W_ADDR-1:0] d_btb_target_addr, + + output reg [W_DATA-1:0] d_imm, + output reg [W_REGADDR-1:0] d_rs1, + output reg [W_REGADDR-1:0] d_rs2, + output reg [W_REGADDR-1:0] d_rd, + output reg [2:0] d_funct3_32b, + output reg [6:0] d_funct7_32b, + output reg [W_ALUSRC-1:0] d_alusrc_a, + output reg [W_ALUSRC-1:0] d_alusrc_b, + output reg [W_ALUOP-1:0] d_aluop, + output reg [W_MEMOP-1:0] d_memop, + output reg [W_MULOP-1:0] d_mulop, + output reg d_csr_ren, + output reg d_csr_wen, + output reg [1:0] d_csr_wtype, + output reg d_csr_w_imm, + output reg [W_BCOND-1:0] d_branchcond, + output reg [W_ADDR-1:0] d_addr_offs, + output reg d_addr_is_regoffs, + output reg [W_EXCEPT-1:0] d_except, + output reg d_sleep_wfi, + output reg d_sleep_block, + output reg d_sleep_unblock, + output wire d_no_pc_increment, + output wire d_uninterruptible, + output wire [W_ADDR-1:0] d_lspair_offset, + output reg d_fence_i, + output reg d_fence_d +); + +`include "rv_opcodes.vh" +`include "hazard3_ops.vh" + +localparam HAVE_CSR = CSR_M_MANDATORY || CSR_M_TRAP || CSR_COUNTER; + +// ---------------------------------------------------------------------------- + +wire [31:0] d_instr = fd_cir | { + 30'd0, {2{~|EXTENSION_C}} +}; + +reg d_invalid_32bit; +wire d_invalid = fd_cir_invalid_16bit || d_invalid_32bit; + +assign d_uninterruptible = |EXTENSION_ZCMP && fd_cir_uop_atomic; + +`ifdef HAZARD3_ASSERTIONS +always @ (posedge clk) if (rst_n) begin + assert(!(d_invalid && fd_cir_is_uop)); + assert(!(d_invalid && fd_cir_uop_atomic)); +end +`endif + +wire d_lspair_nonfinal; +// Signal to null the mepc offset when taking an exception on this +// instruction (because uops in a sequence *which can except*, so excluding +// the final sp adjust on popret/popretz, will all have the same PC as the +// next uop, which will be in stage 2 when they take their exception) +assign d_no_pc_increment = fd_cir_uop_nonfinal || d_lspair_nonfinal; + +assign df_uop_stall = x_stall || d_starved; + +// Note !df_cir_flush_behind because the jump in cm.popret/popretz is the +// *penultimate* instruction: we execute the stack adjustment in the fetch +// bubble to save a cycle, still need to finish the uop sequence. +// +// The sp adjust cannot generate an exception (it's an `add` with the same +// PMP.X and breakpoint comparison results as earlier uops) and interrupts are +// suppressed for this part of the sequence. +assign df_uop_clear = f_jump_now && !df_cir_flush_behind; + +// Decode various immediate formats +wire [31:0] d_imm_i = {{21{d_instr[31]}}, d_instr[30:20]}; +wire [31:0] d_imm_s = {{21{d_instr[31]}}, d_instr[30:25], d_instr[11:7]}; +wire [31:0] d_imm_b = {{20{d_instr[31]}}, d_instr[7], d_instr[30:25], d_instr[11:8], 1'b0}; +wire [31:0] d_imm_u = {d_instr[31:12], {12{1'b0}}}; +wire [31:0] d_imm_j = {{12{d_instr[31]}}, d_instr[19:12], d_instr[20], d_instr[30:21], 1'b0}; + +// ---------------------------------------------------------------------------- +// PC/CIR control + +// Must not flag bus error for a valid 16-bit instruction *followed by* an +// error, because instruction fetch errors are speculative, and can be +// flushed by e.g. a branch instruction. Note the 16 LSBs must be valid for +// us to know an instruction's size. +wire d_except_instr_bus_fault = fd_cir_vld > 2'd0 && fd_cir_err[0] || + fd_cir_vld > 2'd1 && fd_cir_is_32bit && fd_cir_err[1]; + +assign d_starved = ~|fd_cir_vld || fd_cir_vld[0] && fd_cir_is_32bit; + +wire d_stall = x_stall || d_starved || fd_cir_uop_nonfinal || d_lspair_nonfinal; + +assign df_cir_use = + d_starved || d_stall ? 2'h0 : + fd_cir_is_32bit ? 2'h2 : 2'h1; + +// CIR Locking is required if we successfully assert a jump request, but +// decode is stalled. It is not possible to gate the jump request if the +// stall depends on bus stall (as this would create a through-path from bus +// stall to bus request) so instead we instruct the frontend to preserve the +// stalled instruction when flushing, and fill in behind it. +// +// Once the stall clears, the stalled instruction can execute its remaining +// side effects e.g. writing a link value to the register file. +wire jump_caused_by_d = f_jump_now && x_jump_not_except; +wire assert_cir_lock = jump_caused_by_d && d_stall; + +// CIR lock ends naturally when an instruction (not just uop) graduates to the +// next stage: +wire finished_cir_lock = !d_stall; + +// CIR lock can meet an untimely end due to trap entry. One way to reach this +// is a dphase load fault on the final load in a cm.popret: here the `ret` +// issues a fetch address while stalled on the first dphase cycle, then is +// flushed by trap on second cycle. +wire deassert_cir_lock = finished_cir_lock || (f_jump_now && !x_jump_not_except); + +reg cir_lock_prev; +wire cir_lock = (cir_lock_prev && !deassert_cir_lock) || assert_cir_lock; +assign df_cir_flush_behind = assert_cir_lock && !cir_lock_prev; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + cir_lock_prev <= 1'b0; + end else begin + cir_lock_prev <= cir_lock; + end +end + +reg [W_ADDR-1:0] pc; +wire [W_ADDR-1:0] pc_seq_next = pc + ( + |EXTENSION_ZCMP && fd_cir_is_uop && fd_cir_uop_no_pc_update ? 32'd0 : + fd_cir_is_32bit ? 32'd4 : 32'd2 +); + +assign d_pc = pc; +assign debug_dpc_rdata = pc; + +// Frontend should mark the whole instruction, and nothing but the +// instruction, as a predicted branch. This goes wrong when we execute the +// address containing the predicted branch twice with different 16-bit +// alignments (!). We need to issue a branch-to-self to get back on a linear +// path, otherwise PC and CIR will diverge and we will misexecute. +wire partial_predicted_branch = !d_starved && + |BRANCH_PREDICTOR && fd_cir_is_32bit && ^fd_cir_predbranch; + +wire predicted_branch = |BRANCH_PREDICTOR && fd_cir_predbranch[0]; + +// Generally locking takes place on a stalled jump/branch, which may need the +// original PC available to produce a link address when it unstalls. An +// exception to this is jumps in micro-op sequences: in this case the jump is +// the penultimate instruction in the sequence (ret before addi sp) and we +// need to capture the pc mid-uop-sequence. +wire hold_pc_on_cir_lock = assert_cir_lock && !(fd_cir_is_uop && !fd_cir_uop_no_pc_update && !x_stall); +wire update_pc_on_cir_unlock = cir_lock_prev && finished_cir_lock && !fd_cir_uop_no_pc_update; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + pc <= RESET_VECTOR; + end else begin + if (debug_dpc_wen) begin + pc <= debug_dpc_wdata; + end else if (debug_mode) begin + pc <= pc; + end else if ((f_jump_now && !hold_pc_on_cir_lock) || update_pc_on_cir_unlock) begin + pc <= f_jump_target; + end else if (!f_jump_now && fd_cir_uop_nonfinal && !fd_cir_uop_no_pc_update && !x_stall) begin + // End of previously stalled jr uop in cm.popret and cm.popretz: + // safe to update PC as next instruction (addi sp) cannot trap. + pc <= f_jump_target; + end else if (!d_stall && !cir_lock) begin + // If this instruction is a predicted-taken branch (and has not + // generated a mispredict recovery jump) then set PC to the + // prediction target instead of the sequentially next PC + pc <= predicted_branch ? d_btb_target_addr : pc_seq_next; + end + end +end + +`ifdef HAZARD3_ASSERTIONS +always @ (posedge clk) if (rst_n) begin + if (~|fd_cir_vld) assert(!fd_cir_is_uop); + if (fd_cir_uop_no_pc_update) assert(fd_cir_is_uop); + if (fd_cir_uop_nonfinal) assert(fd_cir_is_uop); + if ($past(df_uop_clear)) assert(!fd_cir_is_uop); + // Important to avoid spurious PC updates following a trap on the final + // load of a cm.popret: + if ($past(df_uop_clear)) assert(!fd_cir_uop_no_pc_update); +end +`endif + +wire [W_ADDR-1:0] branch_offs = + !fd_cir_is_32bit && predicted_branch ? 32'd2 : + fd_cir_is_32bit && predicted_branch ? 32'd4 : d_imm_b; + +always @ (*) begin + casez ({|EXTENSION_A, d_instr[6:2]}) + {1'bz, 5'b11011}: d_addr_offs = d_imm_j ; // JAL + {1'bz, 5'b11000}: d_addr_offs = branch_offs ; // Branches + {1'bz, 5'b01000}: d_addr_offs = d_imm_s ; // Store + {1'bz, 5'b11001}: d_addr_offs = d_imm_i ; // JALR + {1'bz, 5'b00000}: d_addr_offs = d_imm_i ; // Loads + {1'b1, 5'b01011}: d_addr_offs = 32'h0000_0000; // Atomics + default: d_addr_offs = 32'hxxxx_xxxx; + endcase + if (partial_predicted_branch) begin + d_addr_offs = 32'h0000_0000; + end +end + +// ---------------------------------------------------------------------------- +// Track phase of load/store pair instructions (Zilsd and Zclsd) + +// This could be shared with uop_ctr (for Zcmp) but the two are fundamentally +// different: Zcmp has 16-bit instructions which expand to sequences of +// 32-bit, whereas Zilsd has multi-phase 32-bit instructions and Zclsd has +// direct 16-bit aliases of those instructions. Therefore it's cleaner to +// separate the phasing from the decompression for Zilsd/Zclsd. + +wire d_lspair_phase; + +// Reorder accesses to avoid clobbering rs1 (base) in first half of load: +wire d_lspair_reg_sel = d_lspair_phase == d_instr[15]; + +generate +if (EXTENSION_ZILSD) begin: have_lspair_reg_sel + reg d_lspair_phase_r; + assign d_lspair_phase = d_lspair_phase_r; + reg instr_is_lspair; + always @ (*) begin + casez ({d_invalid || d_starved, d_instr}) + {1'b0, `RVOPC_LD}: instr_is_lspair = 1'b1; + {1'b0, `RVOPC_SD}: instr_is_lspair = 1'b1; + default: instr_is_lspair = 1'b0; + endcase + end + + always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + d_lspair_phase_r <= 1'b0; + end else begin + d_lspair_phase_r <= df_lspair_phase_next; + end + end + + assign df_lspair_phase_next = + !d_stall || f_jump_now ? 1'b0 : + instr_is_lspair && !x_stall ? 1'b1 : d_lspair_phase_r; + + assign d_lspair_nonfinal = instr_is_lspair && !d_lspair_phase_r; + + assign d_lspair_offset = { + 29'h0, + d_lspair_reg_sel && instr_is_lspair, + 2'h0 + }; + +end else begin: no_lspair_reg_sel + + assign d_lspair_phase = 1'b0; + assign df_lspair_phase_next = 1'b0; + assign d_lspair_nonfinal = 1'b0; + assign d_lspair_offset = 32'd0; + +end +endgenerate + +// ---------------------------------------------------------------------------- +// Decode X controls + +localparam X0 = {W_REGADDR{1'b0}}; + +// First decode the instruction bits, based on available extensions and +// privilege state, without gating in any stall/exception signals. +reg [W_REGADDR-1:0] raw_rs1; +reg [W_REGADDR-1:0] raw_rs2; +reg [W_REGADDR-1:0] raw_rd; +reg [W_DATA-1:0] raw_imm; +reg [W_ALUSRC-1:0] raw_alusrc_a; +reg [W_ALUSRC-1:0] raw_alusrc_b; +reg [W_ALUOP-1:0] raw_aluop; +reg [W_MEMOP-1:0] raw_memop; +reg [W_MULOP-1:0] raw_mulop; +reg raw_csr_ren; +reg raw_csr_wen; +reg [1:0] raw_csr_wtype; +reg raw_csr_w_imm; +reg [W_BCOND-1:0] raw_branchcond; +reg raw_addr_is_regoffs; +reg [W_EXCEPT-1:0] raw_except; +reg raw_sleep_wfi; +reg raw_sleep_block; +reg raw_sleep_unblock; +reg raw_fence_i; +reg raw_fence_d; + +always @ (*) begin + // Assign some defaults + raw_rs1 = d_instr[19:15]; + raw_rs2 = d_instr[24:20]; + raw_rd = d_instr[11: 7]; + raw_imm = d_imm_i; + raw_alusrc_a = ALUSRCA_RS1; + raw_alusrc_b = ALUSRCB_RS2; + raw_aluop = ALUOP_ADD; + raw_memop = MEMOP_NONE; + raw_mulop = M_OP_MUL; + raw_csr_ren = 1'b0; + raw_csr_wen = 1'b0; + raw_csr_wtype = CSR_WTYPE_W; + raw_csr_w_imm = 1'b0; + raw_branchcond = BCOND_NEVER; + raw_addr_is_regoffs = 1'b0; + raw_except = EXCEPT_NONE; + raw_sleep_wfi = 1'b0; + raw_sleep_block = 1'b0; + raw_sleep_unblock = 1'b0; + raw_fence_i = 1'b0; + raw_fence_d = 1'b0; + // Note this funct3/funct7 are valid only for 32-bit instructions. They + // are useful for clusters of related ALU ops, such as sh*add, clmul. + d_funct3_32b = fd_cir[14:12]; + d_funct7_32b = fd_cir[31:25]; + + d_invalid_32bit = 1'b0; + + casez (d_instr) + `RVOPC_BEQ: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_rd = X0; raw_aluop = ALUOP_SUB; raw_branchcond = BCOND_ZERO; end + `RVOPC_BNE: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_rd = X0; raw_aluop = ALUOP_SUB; raw_branchcond = BCOND_NZERO; end + `RVOPC_BLT: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_rd = X0; raw_aluop = ALUOP_LT; raw_branchcond = BCOND_NZERO; end + `RVOPC_BGE: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_rd = X0; raw_aluop = ALUOP_LT; raw_branchcond = BCOND_ZERO; end + `RVOPC_BLTU: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_rd = X0; raw_aluop = ALUOP_LTU; raw_branchcond = BCOND_NZERO; end + `RVOPC_BGEU: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_rd = X0; raw_aluop = ALUOP_LTU; raw_branchcond = BCOND_ZERO; end + `RVOPC_JALR: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_branchcond = BCOND_ALWAYS; raw_addr_is_regoffs = 1'b1; + raw_rs2 = X0; raw_aluop = ALUOP_ADD; raw_alusrc_a = ALUSRCA_PC; raw_alusrc_b = ALUSRCB_IMM; raw_imm = fd_cir_is_32bit ? 32'd4 : 32'd2; end + `RVOPC_JAL: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_branchcond = BCOND_ALWAYS; raw_rs1 = X0; + raw_rs2 = X0; raw_aluop = ALUOP_ADD; raw_alusrc_a = ALUSRCA_PC; raw_alusrc_b = ALUSRCB_IMM; raw_imm = fd_cir_is_32bit ? 32'd4 : 32'd2; end + `RVOPC_LUI: begin raw_aluop = ALUOP_RS2; raw_imm = d_imm_u; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; raw_rs1 = X0; end + `RVOPC_AUIPC: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_aluop = ALUOP_ADD; raw_imm = d_imm_u; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; raw_alusrc_a = ALUSRCA_PC; raw_rs1 = X0; end + `RVOPC_ADDI: begin raw_aluop = ALUOP_ADD; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end + `RVOPC_SLLI: begin raw_aluop = ALUOP_SLL; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end + `RVOPC_SLTI: begin raw_aluop = ALUOP_LT; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end + `RVOPC_SLTIU: begin raw_aluop = ALUOP_LTU; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end + `RVOPC_XORI: begin raw_aluop = ALUOP_XOR; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end + `RVOPC_SRLI: begin raw_aluop = ALUOP_SRL; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end + `RVOPC_SRAI: begin raw_aluop = ALUOP_SRA; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end + `RVOPC_ORI: begin raw_aluop = ALUOP_OR; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end + `RVOPC_ANDI: begin raw_aluop = ALUOP_AND; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end + `RVOPC_ADD: begin raw_aluop = ALUOP_ADD; end + `RVOPC_SUB: begin raw_aluop = ALUOP_SUB; end + `RVOPC_SLL: begin raw_aluop = ALUOP_SLL; end + `RVOPC_SLTU: begin raw_aluop = ALUOP_LTU; end + `RVOPC_XOR: begin raw_aluop = ALUOP_XOR; end + `RVOPC_SRL: begin raw_aluop = ALUOP_SRL; end + `RVOPC_SRA: begin raw_aluop = ALUOP_SRA; end + `RVOPC_OR: begin raw_aluop = ALUOP_OR; end + `RVOPC_AND: begin raw_aluop = ALUOP_AND; end + `RVOPC_LB: begin raw_addr_is_regoffs = 1'b1; raw_rs2 = X0; raw_memop = MEMOP_LB; end + `RVOPC_LH: begin raw_addr_is_regoffs = 1'b1; raw_rs2 = X0; raw_memop = MEMOP_LH; end + `RVOPC_LW: begin raw_addr_is_regoffs = 1'b1; raw_rs2 = X0; raw_memop = MEMOP_LW; end + `RVOPC_LBU: begin raw_addr_is_regoffs = 1'b1; raw_rs2 = X0; raw_memop = MEMOP_LBU; end + `RVOPC_LHU: begin raw_addr_is_regoffs = 1'b1; raw_rs2 = X0; raw_memop = MEMOP_LHU; end + `RVOPC_SB: begin raw_addr_is_regoffs = 1'b1; raw_aluop = ALUOP_RS2; raw_memop = MEMOP_SB; raw_rd = X0; end + `RVOPC_SH: begin raw_addr_is_regoffs = 1'b1; raw_aluop = ALUOP_RS2; raw_memop = MEMOP_SH; raw_rd = X0; end + `RVOPC_SW: begin raw_addr_is_regoffs = 1'b1; raw_aluop = ALUOP_RS2; raw_memop = MEMOP_SW; raw_rd = X0; end + + `RVOPC_SLT: begin + raw_aluop = ALUOP_LT; + if (|EXTENSION_XH3POWER && ~|raw_rd && ~|raw_rs1) begin + if (raw_rs2 == 5'h00) begin + // h3.block (power management hint) + d_invalid_32bit = trap_wfi; + raw_sleep_block = !trap_wfi; + end else if (raw_rs2 == 5'h01) begin + // h3.unblock (power management hint) + d_invalid_32bit = trap_wfi; + raw_sleep_unblock = !trap_wfi; + end + end + end + + `RVOPC_MUL: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_MUL; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_MULH: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_MULH; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_MULHSU: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_MULHSU; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_MULHU: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_MULHU; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_DIV: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_DIV; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_DIVU: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_DIVU; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_REM: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_REM; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_REMU: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_REMU; end else begin d_invalid_32bit = 1'b1; end + + `RVOPC_LR_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_LR_W; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_SC_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_SC_W; raw_aluop = ALUOP_RS2; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_AMOSWAP_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_RS2; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_AMOADD_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_ADD; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_AMOXOR_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_XOR; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_AMOAND_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_AND; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_AMOOR_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_OR; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_AMOMIN_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_MIN; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_AMOMAX_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_MAX; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_AMOMINU_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_MINU; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_AMOMAXU_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_MAXU; end else begin d_invalid_32bit = 1'b1; end + + `RVOPC_LD: if (EXTENSION_ZILSD) begin raw_addr_is_regoffs = 1'b1; raw_rs2 = X0; raw_memop = MEMOP_LW; raw_rd = {d_instr[11: 8], d_lspair_reg_sel}; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_SD: if (EXTENSION_ZILSD) begin raw_addr_is_regoffs = 1'b1; raw_rd = X0; raw_memop = MEMOP_SW; raw_rs2 = {d_instr[24:21], d_lspair_reg_sel}; raw_aluop = ALUOP_RS2; end else begin d_invalid_32bit = 1'b1; end + + `RVOPC_SH1ADD: if (EXTENSION_ZBA) begin raw_aluop = ALUOP_SHXADD; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_SH2ADD: if (EXTENSION_ZBA) begin raw_aluop = ALUOP_SHXADD; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_SH3ADD: if (EXTENSION_ZBA) begin raw_aluop = ALUOP_SHXADD; end else begin d_invalid_32bit = 1'b1; end + + `RVOPC_ANDN: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ANDN; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_CLZ: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_CLZ; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_CPOP: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_CPOP; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_CTZ: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_CTZ; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_MAX: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_MAX; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_MAXU: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_MAXU; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_MIN: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_MIN; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_MINU: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_MINU; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_ORC_B: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ORC_B; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_ORN: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ORN; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_REV8: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_REV8; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_ROL: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ROL; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_ROR: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ROR; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_RORI: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ROR; raw_rs2 = X0; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_SEXT_B: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_SEXT_B; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_SEXT_H: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_SEXT_H; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_XNOR: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_XNOR; end else begin d_invalid_32bit = 1'b1; end + // Note: ZEXT_H is a subset of PACK from Zbkb. This is fine as long + // as this case appears first, since Zbkb implies Zbb on Hazard3. + `RVOPC_ZEXT_H: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ZEXT_H; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end + + `RVOPC_CLMUL: if (EXTENSION_ZBC) begin raw_aluop = ALUOP_CLMUL; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_CLMULH: if (EXTENSION_ZBC) begin raw_aluop = ALUOP_CLMUL; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_CLMULR: if (EXTENSION_ZBC) begin raw_aluop = ALUOP_CLMUL; end else begin d_invalid_32bit = 1'b1; end + + `RVOPC_BCLR: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BCLR; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_BCLRI: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BCLR; raw_rs2 = X0; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_BEXT: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BEXT; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_BEXTI: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BEXT; raw_rs2 = X0; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_BINV: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BINV; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_BINVI: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BINV; raw_rs2 = X0; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_BSET: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BSET; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_BSETI: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BSET; raw_rs2 = X0; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; end else begin d_invalid_32bit = 1'b1; end + + `RVOPC_PACK: if (EXTENSION_ZBKB) begin raw_aluop = ALUOP_PACK; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_PACKH: if (EXTENSION_ZBKB) begin raw_aluop = ALUOP_PACKH; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_BREV8: if (EXTENSION_ZBKB) begin raw_aluop = ALUOP_BREV8; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_UNZIP: if (EXTENSION_ZBKB) begin raw_aluop = ALUOP_UNZIP; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_ZIP: if (EXTENSION_ZBKB) begin raw_aluop = ALUOP_ZIP; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end + + `RVOPC_XPERM8: if (EXTENSION_ZBKX) begin raw_aluop = ALUOP_XPERM; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_XPERM4: if (EXTENSION_ZBKX) begin raw_aluop = ALUOP_XPERM; end else begin d_invalid_32bit = 1'b1; end + + `RVOPC_H3_BEXTM: if (EXTENSION_XH3BEXTM) begin + raw_aluop = ALUOP_BEXTM; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_H3_BEXTMI: if (EXTENSION_XH3BEXTM) begin + raw_aluop = ALUOP_BEXTM; raw_rs2 = X0; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_CSRRW: if (HAVE_CSR) begin raw_rs2 = X0; raw_imm = d_imm_i; raw_csr_wen = 1'b1 ; raw_csr_ren = |raw_rd; raw_csr_wtype = CSR_WTYPE_W; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_CSRRS: if (HAVE_CSR) begin raw_rs2 = X0; raw_imm = d_imm_i; raw_csr_wen = |raw_rs1; raw_csr_ren = 1'b1 ; raw_csr_wtype = CSR_WTYPE_S; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_CSRRC: if (HAVE_CSR) begin raw_rs2 = X0; raw_imm = d_imm_i; raw_csr_wen = |raw_rs1; raw_csr_ren = 1'b1 ; raw_csr_wtype = CSR_WTYPE_C; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_CSRRWI: if (HAVE_CSR) begin raw_rs2 = X0; raw_imm = d_imm_i; raw_csr_wen = 1'b1 ; raw_csr_ren = |raw_rd; raw_csr_wtype = CSR_WTYPE_W; raw_csr_w_imm = 1'b1; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_CSRRSI: if (HAVE_CSR) begin raw_rs2 = X0; raw_imm = d_imm_i; raw_csr_wen = |raw_rs1; raw_csr_ren = 1'b1 ; raw_csr_wtype = CSR_WTYPE_S; raw_csr_w_imm = 1'b1; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_CSRRCI: if (HAVE_CSR) begin raw_rs2 = X0; raw_imm = d_imm_i; raw_csr_wen = |raw_rs1; raw_csr_ren = 1'b1 ; raw_csr_wtype = CSR_WTYPE_C; raw_csr_w_imm = 1'b1; end else begin d_invalid_32bit = 1'b1; end + + `RVOPC_FENCE: begin raw_rs2 = X0; raw_fence_d = 1'b1; end // Note rs1/rd are zero in instruction + `RVOPC_FENCE_I: if (EXTENSION_ZIFENCEI) begin raw_except = debug_mode ? EXCEPT_NONE : EXCEPT_REFETCH; raw_fence_i = 1'b1; end else begin d_invalid_32bit = 1'b1; end // note rs1/rs2/rd are zero in instruction + `RVOPC_ECALL: if (HAVE_CSR) begin raw_except = m_mode || !U_MODE ? EXCEPT_ECALL_M : EXCEPT_ECALL_U; raw_rs2 = X0; raw_rs1 = X0; raw_rd = X0; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_EBREAK: if (HAVE_CSR) begin raw_except = EXCEPT_EBREAK; raw_rs2 = X0; raw_rs1 = X0; raw_rd = X0; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_MRET: if (HAVE_CSR && m_mode) begin raw_except = EXCEPT_MRET; raw_rs2 = X0; raw_rs1 = X0; raw_rd = X0; end else begin d_invalid_32bit = 1'b1; end + `RVOPC_WFI: if (HAVE_CSR && !trap_wfi) begin raw_sleep_wfi = 1'b1; raw_rs2 = X0; raw_rs1 = X0; raw_rd = X0; end else begin d_invalid_32bit = 1'b1; end + + default: begin d_invalid_32bit = 1'b1; end + endcase + + if (|EXTENSION_E && (raw_rd[4] || raw_rs1[4] || raw_rs2[4])) begin + d_invalid_32bit = 1'b1; + end +end + +// Then gate key signals based on CIR fullness, fetch faults etc. The split +// helps to avoid an event scheduling feedback loop that makes simulators +// unhappy and slow, particularly verilator + +localparam [4:0] REGADDR_MASK = {~|EXTENSION_E, 4'hf}; + +always @ (*) begin + // Pass through by default + d_rs1 = raw_rs1 & REGADDR_MASK; + d_rs2 = raw_rs2 & REGADDR_MASK; + d_rd = raw_rd & REGADDR_MASK; + d_imm = raw_imm; + d_alusrc_a = raw_alusrc_a; + d_alusrc_b = raw_alusrc_b; + d_aluop = raw_aluop; + d_memop = raw_memop; + d_mulop = raw_mulop; + d_csr_ren = raw_csr_ren; + d_csr_wen = raw_csr_wen; + d_csr_wtype = raw_csr_wtype; + d_csr_w_imm = raw_csr_w_imm; + d_branchcond = raw_branchcond; + d_addr_is_regoffs = raw_addr_is_regoffs; + d_except = raw_except; + d_sleep_wfi = raw_sleep_wfi; + d_sleep_block = raw_sleep_block; + d_sleep_unblock = raw_sleep_unblock; + d_fence_i = raw_fence_i; + d_fence_d = raw_fence_d; + + if (d_invalid || d_starved || d_except_instr_bus_fault || partial_predicted_branch) begin + d_rs1 = {W_REGADDR{1'b0}}; + d_rs2 = {W_REGADDR{1'b0}}; + d_rd = {W_REGADDR{1'b0}}; + d_memop = MEMOP_NONE; + d_branchcond = BCOND_NEVER; + d_csr_ren = 1'b0; + d_csr_wen = 1'b0; + d_except = EXCEPT_NONE; + d_sleep_wfi = 1'b0; + d_sleep_block = 1'b0; + d_sleep_unblock = 1'b0; + d_fence_i = 1'b0; + d_fence_d = 1'b0; + + if (EXTENSION_M) + d_aluop = ALUOP_ADD; + + if (d_except_instr_bus_fault) + d_except = EXCEPT_INSTR_FAULT; + else if (d_invalid && !d_starved) + d_except = EXCEPT_INSTR_ILLEGAL; + end + if (partial_predicted_branch) begin + d_addr_is_regoffs = 1'b0; + d_branchcond = BCOND_ALWAYS; + end + if (cir_lock_prev) begin + d_branchcond = BCOND_NEVER; + end +end + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/hazard3_frontend.v b/vendor/Hazard3/hdl/hazard3_frontend.v new file mode 100644 index 0000000..330fe78 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_frontend.v @@ -0,0 +1,906 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +`default_nettype none + +module hazard3_frontend #( +`include "hazard3_config.vh" +) ( + input wire clk, + input wire rst_n, + + // Fetch interface + // addr_vld may be asserted at any time, but after assertion, + // neither addr nor addr_vld may change until the cycle after addr_rdy. + // There is no backpressure on the data interface; the front end + // must ensure it does not request data it cannot receive. + // addr_rdy and dat_vld may be functions of hready, and + // may not be used to compute combinational outputs. + output wire mem_size, // 1'b1 -> 32 bit access + output wire [W_ADDR-1:0] mem_addr, + output wire mem_priv, + output wire mem_addr_vld, + input wire mem_addr_rdy, + input wire [W_DATA-1:0] mem_data, + input wire mem_data_err, + input wire mem_data_vld, + + // Jump/flush interface + // Processor may assert vld at any time. The request will not go through + // unless rdy is high. Processor *may* alter request during this time. + // Inputs must not be a function of hready. + input wire [W_ADDR-1:0] jump_target, + input wire jump_priv, + input wire jump_target_vld, + output wire jump_target_rdy, + + // Interface to the branch target buffer. `src_addr` is the address of the + // last halfword of a taken backward branch. The frontend redirects fetch + // such that `src_addr` appears to be sequentially followed by `target`. + input wire btb_set, + input wire [W_ADDR-1:0] btb_set_src_addr, + input wire btb_set_src_size, + input wire [W_ADDR-1:0] btb_set_target_addr, + input wire btb_clear, + output wire [W_ADDR-1:0] btb_target_addr_out, + + // Interface to Decode + output reg [31:0] cir, // Current instruction register; pre-expanded to 32-bit + output wire [31:0] cir_raw, // Unexpanded instruction data + output reg [1:0] cir_vld, // number of valid halfwords in CIR + input wire [1:0] cir_use, // number of halfwords D intends to consume + // *may* be a function of hready + output wire [1:0] cir_err, // Bus error on upper/lower halfword of CIR. + output wire [1:0] cir_predbranch, // Set for last halfword of a predicted-taken branch + output wire cir_break_any, // Set for exact match of a breakpoint address on CIR LSB + output wire cir_break_d_mode, // As above but specifically break to debug mode + output reg cir_is_32bit, // Can't be decoded from CIR due to pre-expansion + output reg cir_invalid_16bit, // Expanded an invalid 32-bit instruction + output reg cir_is_uop, // Current instruction is part of a micro-op sequence + output reg cir_uop_nonfinal, // ...and there are more to follow in this instruction + output reg cir_uop_no_pc_update, // Suppress PC increment or jump (note the jump in cm.popret is not the final uop!) + output reg cir_uop_atomic, // Prevent IRQ entry, so intermediate states are not observed + input wire uop_stall, + input wire uop_clear, + + // "flush_behind": do not flush the oldest instruction when accepting a + // jump request (but still flush younger instructions). Sometimes a + // stalled instruction may assert a jump request, because e.g. the stall + // is dependent on a bus stall signal so can't gate the request. + input wire cir_flush_behind, + // Required for regnum predecode when Zilsd is enabled: + input wire df_lspair_phase_next, + + // Signal to power controller that power down is safe. (When going to + // sleep, first the pipeline is stalled, and then the power controller + // waits for the frontend to naturally come to a halt before releasing + // its power request. This avoids manually halting the frontend.) + output wire pwrdown_ok, + // Signal to delay the first instruction fetch following reset, because + // powerup has not yet been negotiated. + input wire delay_first_fetch, + + // Provide the rs1/rs2 register numbers which will be in CIR next cycle. + // Coarse: valid if this instruction has a nonzero register operand. + // (Suitable for regfile read) + output reg [4:0] predecode_rs1_coarse, + output reg [4:0] predecode_rs2_coarse, + // Fine: like coarse, but accurate zeroing when the operand is implicit. + // (Suitable for bypass. Still not precise enough for stall logic.) + output reg [4:0] predecode_rs1_fine, + output reg [4:0] predecode_rs2_fine, + + // Debugger instruction injection: instruction fetch is suppressed when in + // debug halt state, and the DM can then inject instructions into the last + // entry of the prefetch queue using the vld/rdy handshake. + input wire debug_mode, + input wire [W_DATA-1:0] dbg_instr_data, + input wire dbg_instr_data_vld, + output wire dbg_instr_data_rdy, + + // PMP query->kill interface for X permission checks + output wire [W_ADDR-1:0] pmp_i_addr, + output wire pmp_i_m_mode, + input wire pmp_i_kill, + + // Trigger unit query->break interface for breakpoints + output wire [W_ADDR-1:0] trigger_addr, + output wire trigger_m_mode, + input wire [1:0] trigger_break_any, + input wire [1:0] trigger_break_d_mode + +); + +`include "rv_opcodes.vh" + +localparam W_BUNDLE = 16; +// This is the minimum for full throughput (enough to avoid dropping data when +// decode stalls) and there is no significant advantage to going larger. +localparam FIFO_DEPTH = 2; + +// ---------------------------------------------------------------------------- +// Fetch queue + +wire jump_now = jump_target_vld && jump_target_rdy; +reg [1:0] mem_data_hwvld; + +// PMP X faults are checked in parallel with the fetch (fine if executable +// memory is read-idempotent) and failures are promoted to bus errors: +wire pmp_kill_fetch_dph; +wire mem_or_pmp_err = mem_data_err || pmp_kill_fetch_dph; + +// Similarly, breakpoint matches are checked during fetch data phase. These +// are called mem_xxx because they are the breakpoint metadata for the data +// coming back from memory in this dphase. +wire [1:0] mem_break_any; +wire [1:0] mem_break_d_mode; + +// Mark data as containing a predicted-taken branch instruction so that +// mispredicts can be recovered -- need to track both halfwords so that we +// can mark the entire instruction, and nothing but the instruction: +reg [1:0] mem_data_predbranch; + +// Bus errors (and other metadata) travel alongside data. They cause an +// exception if the core decodes the instruction, but until then can be +// flushed harmlessly. + +reg [W_DATA-1:0] fifo_mem [0:FIFO_DEPTH]; +reg fifo_err [0:FIFO_DEPTH]; +reg [1:0] fifo_break_any [0:FIFO_DEPTH]; +reg [1:0] fifo_break_d_mode [0:FIFO_DEPTH]; +reg [1:0] fifo_predbranch [0:FIFO_DEPTH]; +reg [1:0] fifo_valid_hw [0:FIFO_DEPTH]; +reg fifo_valid [0:FIFO_DEPTH]; +reg fifo_valid_m1 [0:FIFO_DEPTH]; + +wire [W_DATA-1:0] fifo_rdata = fifo_mem[0]; +wire fifo_full = fifo_valid[FIFO_DEPTH - 1]; +wire fifo_empty = !fifo_valid[0]; +wire fifo_almost_full = fifo_valid[FIFO_DEPTH - 2]; + +wire fifo_push; +wire fifo_pop; +wire fifo_dbg_inject = DEBUG_SUPPORT && dbg_instr_data_vld && dbg_instr_data_rdy; + +always @ (*) begin: boundary_conditions + integer i; + fifo_mem[FIFO_DEPTH] = mem_data; + fifo_predbranch[FIFO_DEPTH] = 2'b00; + fifo_err[FIFO_DEPTH] = 1'b0; + fifo_break_any[FIFO_DEPTH] = 2'b00; + fifo_break_d_mode[FIFO_DEPTH] = 2'b00; + for (i = 0; i <= FIFO_DEPTH; i = i + 1) begin + fifo_valid[i] = |EXTENSION_C ? |fifo_valid_hw[i] : fifo_valid_hw[i][0]; + // valid-to-right condition: i == 0 || fifo_valid[i - 1], but without + // using negative array bound (seems broken in Yosys?) or OOB in the + // short circuit case (gives lint although result is well-defined) + if (i == 0) begin + fifo_valid_m1[i] = 1'b1; + end else begin + fifo_valid_m1[i] = fifo_valid[i - 1]; + end + end +end + +always @ (posedge clk or negedge rst_n) begin: fifo_update + integer i; + if (!rst_n) begin + for (i = 0; i < FIFO_DEPTH; i = i + 1) begin + fifo_valid_hw[i] <= 2'b00; + fifo_mem[i] <= 32'd0; + fifo_err[i] <= 1'b0; + fifo_break_any[i] <= 2'b00; + fifo_break_d_mode[i] <= 2'b00; + fifo_predbranch[i] <= 2'b00; + end + // This exists only for loop boundary conditions, but is tied off in + // this synchronous process to work around a Verilator scheduling + // issue (see issue #21) + fifo_valid_hw[FIFO_DEPTH] <= 2'b00; + end else begin + for (i = 0; i < FIFO_DEPTH; i = i + 1) begin + if (fifo_pop || (fifo_push && !fifo_valid[i])) begin + fifo_mem[i] <= fifo_valid[i + 1] ? fifo_mem[i + 1] : mem_data; + fifo_err[i] <= fifo_valid[i + 1] ? fifo_err[i + 1] : mem_or_pmp_err; + fifo_break_any[i] <= fifo_valid[i + 1] ? fifo_break_any[i + 1] : mem_break_any; + fifo_break_d_mode[i] <= fifo_valid[i + 1] ? fifo_break_d_mode[i + 1] : mem_break_d_mode; + fifo_predbranch[i] <= fifo_valid[i + 1] ? fifo_predbranch[i + 1] : mem_data_predbranch; + end + fifo_valid_hw[i] <= + jump_now ? 2'h0 : + fifo_valid[i + 1] && fifo_pop ? fifo_valid_hw[i + 1] : + fifo_valid[i] && fifo_pop ? mem_data_hwvld & {2{fifo_push}} : + fifo_valid[i] ? fifo_valid_hw[i] : + fifo_push && !fifo_pop && fifo_valid_m1[i] ? mem_data_hwvld : 2'h0; + end + // Allow DM to inject instructions directly into the lowest-numbered + // queue entry. This mux should not extend critical path since it is + // balanced with the instruction-assembly muxes on the queue bypass + // path. Note that flush takes precedence over debug injection + // (and the debug module design must account for this) + if (fifo_dbg_inject) begin + fifo_mem[0] <= dbg_instr_data; + fifo_err[0] <= 1'b0; + fifo_predbranch[0] <= 2'b00; + fifo_break_any[0] <= 2'b00; + fifo_break_d_mode[0] <= 2'b00; + fifo_valid_hw[0] <= jump_now ? 2'b00 : 2'b11; + end + fifo_valid_hw[FIFO_DEPTH] <= 2'b00; + end +end + +`ifdef HAZARD3_ASSERTIONS +always @ (posedge clk) if (rst_n) begin + // FIFO validity must be compact, so we can always consume from the end + if (!fifo_valid[0]) begin + assert(!fifo_valid[1]); + end +end +`endif + +assign pwrdown_ok = (fifo_full && !jump_target_vld) || debug_mode; + +// ---------------------------------------------------------------------------- +// Branch target buffer + +wire [W_ADDR-1:0] btb_src_addr; +wire btb_src_size; +wire [W_ADDR-1:0] btb_target_addr; +wire btb_valid; + +generate +if (BRANCH_PREDICTOR) begin: have_btb + reg [W_ADDR-1:0] btb_src_addr_r; + reg btb_src_size_r; + reg [W_ADDR-1:0] btb_target_addr_r; + reg btb_valid_r; + assign btb_src_addr = btb_src_addr_r; + assign btb_src_size = btb_src_size_r; + assign btb_target_addr = btb_target_addr_r; + assign btb_valid = btb_valid_r; + always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + btb_src_addr_r <= {W_ADDR{1'b0}}; + btb_src_size_r <= 1'b0; + btb_target_addr_r <= {W_ADDR{1'b0}}; + btb_valid_r <= 1'b0; + end else if (btb_clear) begin + // Clear takes precedences over set. E.g. if a taken branch is in + // stage 2 and an exception is in stage 3, we must clear the BTB. + btb_valid_r <= 1'b0; + end else if (btb_set) begin + btb_src_addr_r <= btb_set_src_addr; + btb_src_size_r <= btb_set_src_size; + btb_target_addr_r <= btb_set_target_addr; + btb_valid_r <= 1'b1; + end + end +end else begin: no_btb + assign btb_src_addr = {W_ADDR{1'b0}}; + assign btb_src_size = 1'b0; + assign btb_target_addr = {W_ADDR{1'b0}}; + assign btb_valid = 1'b0; +end +endgenerate + +// Decode uses the target address to set the PC to the correct branch target +// value following a predicted-taken branch (as normally it would update PC +// by following an X jump request, and in this case there is none). +// +// Note this assumes the BTB target has not changed by the time the predicted +// branch arrives at decode! This is always true because the only way for the +// target address to change is when an older branch is taken, which would +// flush the younger predicted-taken branch before it reaches decode. + +assign btb_target_addr_out = btb_target_addr; + +// ---------------------------------------------------------------------------- +// Fetch request generation + +// Fetch addr runs ahead of the PC, in word increments. +reg [W_ADDR-1:0] fetch_addr; +reg fetch_priv; +reg btb_prev_start_of_overhanging; +reg [1:0] mem_aph_hwvld; +reg mem_addr_hold; + +wire btb_match_word = |BRANCH_PREDICTOR && btb_valid && ( + fetch_addr[W_ADDR-1:2] == btb_src_addr[W_ADDR-1:2] +); + +// Catch case where predicted-taken branch instruction extends into next word: +wire btb_src_overhanging = btb_src_size && btb_src_addr[1]; + +// Suppress case where we have jumped immediately after a word-aligned halfword-sized +// branch, and the jump target went into fetch_addr due to an address-phase hold: +wire btb_jumped_beyond = !btb_src_size && !btb_src_addr[1] && !mem_aph_hwvld[0]; + +wire btb_match_current_addr = btb_match_word && !btb_src_overhanging && !btb_jumped_beyond; +wire btb_match_next_addr = btb_match_word && btb_src_overhanging; + +wire btb_match_now = btb_match_current_addr || btb_prev_start_of_overhanging; + +// Post-increment if jump request is going straight through +wire [W_ADDR-1:0] jump_target_post_increment = + {jump_target[W_ADDR-1:2], 2'b00} + + {{W_ADDR-3{1'b0}}, mem_addr_rdy && !mem_addr_hold, 2'b00}; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + fetch_addr <= RESET_VECTOR; + // M-mode at reset: + fetch_priv <= 1'b1; + btb_prev_start_of_overhanging <= 1'b0; + end else begin + if (jump_now) begin + fetch_addr <= jump_target_post_increment; + fetch_priv <= jump_priv || !U_MODE; + btb_prev_start_of_overhanging <= 1'b0; + end else if (mem_addr_vld && mem_addr_rdy) begin + if (btb_match_now && |BRANCH_PREDICTOR) begin + fetch_addr <= {btb_target_addr[W_ADDR-1:2], 2'b00}; + end else begin + fetch_addr <= fetch_addr + 32'd4; + end + btb_prev_start_of_overhanging <= btb_match_next_addr; + end + end +end + +// Combinatorially generate the address-phase request + +reg reset_holdoff; +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + reset_holdoff <= 1'b1; + end else begin + reset_holdoff <= (|EXTENSION_XH3POWER && delay_first_fetch) ? reset_holdoff : 1'b0; + // This should be impossible, but assert to be sure, because it *will* + // change the fetch address (and we shouldn't check it in hardware if + // we can prove it doesn't happen) + end +end + +`ifdef HAZARD3_ASSERTIONS +always @ (posedge clk) if (rst_n) begin + assert(!(jump_target_vld && reset_holdoff)); +end +`endif + +reg [W_ADDR-1:0] mem_addr_r; +reg mem_priv_r; +reg mem_addr_vld_r; + +// Downstream accesses are always word-sized word-aligned. +assign mem_addr = mem_addr_r; +assign mem_priv = mem_priv_r; +assign mem_addr_vld = mem_addr_vld_r && !reset_holdoff; +assign mem_size = 1'b1; + +wire fetch_stall; + +always @ (*) begin + mem_addr_r = fetch_addr; + mem_priv_r = fetch_priv; + mem_addr_vld_r = 1'b1; + case (1'b1) + mem_addr_hold : begin mem_addr_r = fetch_addr; end + jump_target_vld || reset_holdoff : begin + mem_addr_r = {jump_target[W_ADDR-1:2], 2'b00}; + mem_priv_r = jump_priv || !U_MODE; + end + DEBUG_SUPPORT && debug_mode : begin mem_addr_vld_r = 1'b0; end + !fetch_stall : begin mem_addr_r = fetch_addr; end + default : begin mem_addr_vld_r = 1'b0; end + endcase +end + +assign jump_target_rdy = !mem_addr_hold; + +// ---------------------------------------------------------------------------- +// Bus Pipeline Tracking + +// Keep track of some useful state of the memory interface + +reg [1:0] pending_fetches; +reg [1:0] ctr_flush_pending; + +wire [1:0] pending_fetches_next = pending_fetches + (mem_addr_vld && !mem_addr_hold) - mem_data_vld; + +// Using the non-registered version of pending_fetches would improve FIFO +// utilisation, but create a combinatorial path from hready to address phase! +// This means at least a 2-word FIFO is required for full fetch throughput. +assign fetch_stall = fifo_full + || fifo_almost_full && |pending_fetches + || pending_fetches > 2'h1; + +// Debugger only injects instructions when the frontend is at rest and empty. +assign dbg_instr_data_rdy = DEBUG_SUPPORT && !fifo_valid[0] && ~|ctr_flush_pending; + +wire cir_room_for_fetch; +// If fetch data is forwarded past the FIFO, ensure it is not also written to it. +assign fifo_push = mem_data_vld && ~|ctr_flush_pending && !(cir_room_for_fetch && fifo_empty) + && !(DEBUG_SUPPORT && debug_mode); + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + mem_addr_hold <= 1'b0; + pending_fetches <= 2'h0; + ctr_flush_pending <= 2'h0; + end else begin + mem_addr_hold <= mem_addr_vld && !mem_addr_rdy; + pending_fetches <= pending_fetches_next; + if (jump_now) begin + ctr_flush_pending <= pending_fetches - mem_data_vld; + end else if (|ctr_flush_pending && mem_data_vld) begin + ctr_flush_pending <= ctr_flush_pending - 1'b1; + end + end +end + +`ifdef HAZARD3_ASSERTIONS +always @ (posedge clk) if (rst_n) begin + assert(ctr_flush_pending <= pending_fetches); + assert(pending_fetches < 2'd3); + assert(!(mem_data_vld && !pending_fetches)); +end +`endif + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + mem_data_hwvld <= 2'b11; + mem_aph_hwvld <= 2'b11; + mem_data_predbranch <= 2'b00; + end else begin + if (jump_now) begin + if (|EXTENSION_C) begin + if (mem_addr_rdy) begin + mem_aph_hwvld <= 2'b11; + mem_data_hwvld <= {1'b1, !jump_target[1]}; + end else begin + mem_aph_hwvld <= {1'b1, !jump_target[1]}; + end + end + mem_data_predbranch <= 2'b00; + end else if (mem_addr_vld && mem_addr_rdy) begin + if (|EXTENSION_C) begin + // If a predicted-taken branch instruction only spans the first + // half of a word, need to flag the second half as invalid. + mem_data_hwvld <= mem_aph_hwvld & { + !(|BRANCH_PREDICTOR && btb_match_now && (btb_src_addr[1] == btb_src_size)), + 1'b1 + }; + // Also need to take the alignment of the destination into account. + mem_aph_hwvld <= { + 1'b1, + !(|BRANCH_PREDICTOR && btb_match_now && btb_target_addr[1]) + }; + end + mem_data_predbranch <= + |BRANCH_PREDICTOR && btb_match_word ? ( + btb_src_addr[1] ? 2'b10 : + btb_src_size ? 2'b11 : 2'b01 + ) : + |BRANCH_PREDICTOR && btb_prev_start_of_overhanging ? ( + 2'b01 + ) : 2'b00; + end + end +end + +// ---------------------------------------------------------------------------- +// PMP and trigger unit interfacing: query -> kill/break + +wire [W_ADDR-1:0] pmp_trigger_check_dph_addr; +wire pmp_trigger_check_dph_m_mode; + +// Register the fetch address into stage F so that the PMP can check it in +// parallel with the bus data phase. Feels wasteful to have a separate +// register, but using the fetch_addr counter is fraught due to the way that +// new addresses go into it or past it (depending on aphase hold). + +generate +if (PMP_REGIONS > 0 || DEBUG_SUPPORT != 0) begin: have_check_reg + + reg [W_ADDR-1:0] check_addr_dph; + reg check_m_mode_dph; + always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + check_addr_dph <= {W_ADDR{1'b0}}; + check_m_mode_dph <= 1'b0; + end else if (mem_addr_vld && mem_addr_rdy) begin + check_addr_dph <= mem_addr; + check_m_mode_dph <= mem_priv; + end + end + + assign pmp_trigger_check_dph_addr = check_addr_dph; + assign pmp_trigger_check_dph_m_mode = check_m_mode_dph; + +end else begin: no_check_reg + + assign pmp_trigger_check_dph_addr = {W_ADDR{1'b0}}; + assign pmp_trigger_check_dph_m_mode = 1'b0; + +end +endgenerate + +generate +if (PMP_REGIONS == 0) begin: no_pmp + + assign pmp_i_addr = {W_ADDR{1'b0}}; + assign pmp_i_m_mode = 1'b0; + assign pmp_kill_fetch_dph = 1'b0; + +end else begin: have_pmp + + assign pmp_i_addr = pmp_trigger_check_dph_addr; + assign pmp_i_m_mode = pmp_trigger_check_dph_m_mode; + assign pmp_kill_fetch_dph = pmp_i_kill && !debug_mode; + +end +endgenerate + +generate +if (DEBUG_SUPPORT == 0) begin: no_triggers + + assign trigger_addr = {W_ADDR{1'b0}}; + assign trigger_m_mode = 1'b0; + assign mem_break_any = 2'b00; + assign mem_break_d_mode = 2'b00; + +end else begin: have_triggers + + assign trigger_addr = pmp_trigger_check_dph_addr; + assign trigger_m_mode = pmp_trigger_check_dph_m_mode; + assign mem_break_any = trigger_break_any & {|EXTENSION_C, 1'b1}; + assign mem_break_d_mode = trigger_break_d_mode & {|EXTENSION_C, 1'b1}; + +end +endgenerate + +// ---------------------------------------------------------------------------- +// Instruction buffer + +// The instruction buffer is a 3 x ~16-bit shift register: +// +// * 2 x 16-bit entries form the 32-bit current instruction register (CIR) +// which is the processor's decode window +// +// * 1 x 16-bit entry allows the decode window to be non-32-bit-aligned with +// respect to the 2 x 32-bit prefetch queue entries, which are always +// naturally aligned in memory (if fully populated). +// +// The third entry should be trimmed for non-RVC configurations due to +// constant-folding on EXTENSION_C; it is unnecessary here because the +// instructions are always 32-bit-aligned. + +// The entries ("slots") are slightly larger than 16 bits because they also +// contain metadata like bus errors: +localparam W_SLOT = 4 + W_BUNDLE; +localparam SLOT_BREAK_ANY_BIT = 3 + W_BUNDLE; +localparam SLOT_BREAK_D_MODE_BIT = 2 + W_BUNDLE; +localparam SLOT_ERR_BIT = 1 + W_BUNDLE; +localparam SLOT_PREDBRANCH_BIT = 0 + W_BUNDLE; + +reg [3*W_SLOT-1:0] buf_contents; +reg [1:0] buf_level; + +wire fetch_data_vld = !fifo_empty || (mem_data_vld && ~|ctr_flush_pending && !debug_mode); + +wire [W_DATA-1:0] fetch_data = fifo_empty ? mem_data : fifo_rdata; +wire [1:0] fetch_data_hwvld = fifo_empty ? mem_data_hwvld : fifo_valid_hw[0]; +wire fetch_bus_err = fifo_empty ? mem_or_pmp_err : fifo_err[0]; +wire [1:0] fetch_break_any = fifo_empty ? mem_break_any : fifo_break_any[0]; +wire [1:0] fetch_break_d_mode = fifo_empty ? mem_break_d_mode : fifo_break_d_mode[0]; +wire [1:0] fetch_predbranch = fifo_empty ? mem_data_predbranch : fifo_predbranch[0]; + +wire [W_SLOT-1:0] fetch_contents_hw1 = { + fetch_break_any[1], + fetch_break_d_mode[1], + fetch_bus_err, + fetch_predbranch[1], + fetch_data[W_BUNDLE +: W_BUNDLE] +}; + +wire [W_SLOT-1:0] fetch_contents_hw0 = { + fetch_break_any[0], + fetch_break_d_mode[0], + fetch_bus_err, + fetch_predbranch[0], + fetch_data[0 +: W_BUNDLE] +}; + +wire [2*W_SLOT-1:0] fetch_contents_aligned = { + fetch_contents_hw1, + fetch_data_hwvld[0] || ~|EXTENSION_C ? fetch_contents_hw0 : fetch_contents_hw1 +}; + +// Shift not-yet-used contents down to backfill D's consumption. We don't care +// about anything which is invalid or will be overlaid with fresh data, so +// choose these values in a way that minimises muxes. +wire [3*W_SLOT-1:0] buf_shifted = + cir_use[1] ? {buf_contents[W_SLOT +: 2 * W_SLOT], buf_contents[2 * W_SLOT +: W_SLOT]} : + cir_use[0] && EXTENSION_C ? {buf_contents[2 * W_SLOT +: W_SLOT], buf_contents[W_SLOT +: 2 * W_SLOT]} : + buf_contents; + +wire [1:0] level_next_no_fetch = buf_level - cir_use; + +// Overlay fresh fetch data onto the shifted/recycled buffer contents. Again, +// if something won't be looked at, generate the cheapest possible garbage. +assign cir_room_for_fetch = level_next_no_fetch <= (|EXTENSION_C && ~&fetch_data_hwvld ? 2'h2 : 2'h1); +assign fifo_pop = cir_room_for_fetch && !fifo_empty; + +wire [3*W_SLOT-1:0] buf_shifted_plus_fetch = + !cir_room_for_fetch ? buf_shifted : + level_next_no_fetch[1] && |EXTENSION_C ? {fetch_contents_aligned[0 +: W_SLOT], buf_shifted[0 +: 2 * W_SLOT]} : + level_next_no_fetch[0] && |EXTENSION_C ? {fetch_contents_aligned, buf_shifted[0 +: W_SLOT]} : + {buf_shifted[2 * W_SLOT +: W_SLOT], fetch_contents_aligned}; + +wire [1:0] fetch_fill_amount = cir_room_for_fetch && fetch_data_vld ? ( + &fetch_data_hwvld || ~|EXTENSION_C ? 2'h2 : 2'h1 +) : 2'h0; + +wire [1:0] buf_level_next = {1'b1, |EXTENSION_C} & ( + jump_now && cir_flush_behind ? (cir_is_32bit ? 2'h2 : 2'h1) : + jump_now ? 2'h0 : level_next_no_fetch + fetch_fill_amount +); + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + buf_level <= 2'h0; + cir_vld <= 2'h0; + // Mysterious reset value ensures address buses are zero in reset + // (see definition of d_addr_offs in hazard3_decode) + buf_contents <= {{3 * W_SLOT - 2{1'b0}}, 2'b11}; + end else begin + buf_level <= buf_level_next; + cir_vld <= buf_level_next & ~(buf_level_next >> 1'b1); + buf_contents <= buf_shifted_plus_fetch; + end +end + +`ifdef HAZARD3_ASSERTIONS +reg [1:0] prop_past_buf_level; // Workaround for weird non-constant $past reset issue +always @ (posedge clk) begin + if (!rst_n) begin + prop_past_buf_level <= 2'h0; + end else begin + prop_past_buf_level <= buf_level; + + assert(cir_vld <= 2); + assert(cir_use <= cir_vld); + if (!jump_now) assert(buf_level_next >= level_next_no_fetch); + // We fetch 32 bits per cycle, max. If this happens it's due to negative overflow. + if (prop_past_buf_level == 2'h0) + assert(buf_level != 2'h3); + end +end +`endif + +assign cir_err = { + buf_contents[1 * W_SLOT + SLOT_ERR_BIT], + buf_contents[0 * W_SLOT + SLOT_ERR_BIT] +}; + +assign cir_predbranch = { + buf_contents[1 * W_SLOT + SLOT_PREDBRANCH_BIT], + buf_contents[0 * W_SLOT + SLOT_PREDBRANCH_BIT] +}; + +assign cir_break_any = buf_contents[0 * W_SLOT + SLOT_BREAK_ANY_BIT] && |cir_vld; + +assign cir_break_d_mode = buf_contents[0 * W_SLOT + SLOT_BREAK_D_MODE_BIT] && |cir_vld; + +// ---------------------------------------------------------------------------- +// Register number predecode + +wire [31:0] next_instr = { + buf_shifted_plus_fetch[1 * W_SLOT +: W_BUNDLE], + buf_shifted_plus_fetch[0 * W_SLOT +: W_BUNDLE] +}; + +wire next_instr_is_32bit = next_instr[1:0] == 2'b11 || ~|EXTENSION_C; + +wire [3:0] decomp_uop_step; +wire [3:0] uop_ctr = decomp_uop_step & {4{|EXTENSION_ZCMP}}; + +wire [4:0] zcmp_pushpop_rs2 = + uop_ctr == 4'h0 ? 5'd01 : // ra + uop_ctr == 4'h1 ? 5'd08 : // s0 + uop_ctr == 4'h2 ? 5'd09 : // s1 + 5'd15 + {1'b0, uop_ctr} ; // s2-s11 + +wire [4:0] zcmp_pushpop_rs1 = + uop_ctr < 4'hd ? 5'd02 : // sp (addr base reg) + uop_ctr == 4'hd ? 5'd00 : // zero (clear a0) + uop_ctr == 4'he ? 5'd01 : // ra (ret) + 5'd02 ; // sp (stack adj) + +wire [4:0] zcmp_sa01_r1s = {|next_instr[9:8], ~|next_instr[9:8], next_instr[9:7]}; +wire [4:0] zcmp_sa01_r2s = {|next_instr[4:3], ~|next_instr[4:3], next_instr[4:2]}; + +wire [4:0] zcmp_mvsa01_rs1 = {4'h5, uop_ctr[0]}; +wire [4:0] zcmp_mva01s_rs1 = uop_ctr[0] ? zcmp_sa01_r2s : zcmp_sa01_r1s; + +// "coarse" because the mapping of pair (x0, x1) -> (x0, x0) is not yet applied +wire [4:0] zilsd_rs2_coarse = { next_instr[24:21], df_lspair_phase_next ^ ~next_instr[15]}; +wire [4:0] zclsd_sd_rs2_coarse = {2'b01, next_instr[4:3], df_lspair_phase_next ^ ~next_instr[7] }; +wire [4:0] zclsd_sdsp_rs2_coarse = { next_instr[6:3], df_lspair_phase_next ^ 1'b1 }; + +always @ (*) begin + + casez ({next_instr_is_32bit, |EXTENSION_ZCMP, next_instr[15:0]}) + {1'b1, 1'bz, 16'bzzzzzzzzzzzzzzzz}: predecode_rs1_coarse = next_instr[19:15]; // 32-bit R, S, B formats + {1'b0, 1'bz, 16'b00zzzzzzzzzzzz00}: predecode_rs1_coarse = 5'd2; // c.addi4spn + don't care + {1'b0, 1'bz, 16'b0zzzzzzzzzzzzz01}: predecode_rs1_coarse = next_instr[11:7]; // c.addi, c.addi16sp + don't care (jal, li) + {1'b0, 1'bz, 16'bz1zzzzzzzzzzzz10}: predecode_rs1_coarse = 5'd2; // c.lwsp, c.swsp, c.ldsp, c.sdsp + {1'b0, 1'bz, 16'bz00zzzzzzzzzzz10}: predecode_rs1_coarse = next_instr[11:7]; // c.slli, c.mv, c.add + {1'b0, 1'b1, 16'b1011zzzzzzzzzz10}: predecode_rs1_coarse = zcmp_pushpop_rs1; // cm.push, cm.pop* + {1'b0, 1'b1, 16'b1010zzzzz0zzzz10}: predecode_rs1_coarse = zcmp_mvsa01_rs1; // cm.mvsa01 + {1'b0, 1'b1, 16'b1010zzzzz1zzzz10}: predecode_rs1_coarse = zcmp_mva01s_rs1; // cm.mva01s + default: predecode_rs1_coarse = {2'b01, next_instr[9:7]}; + endcase + + casez ({next_instr_is_32bit, |EXTENSION_ZCMP, |EXTENSION_ZILSD, |EXTENSION_ZCLSD, next_instr[15:0]}) + {1'b1, 1'bz, 1'b1, 1'bz, 16'bzz11zzzzz0z0zzzz}: predecode_rs2_coarse = zilsd_rs2_coarse; // ld, sd (Zilsd) + {1'b1, 1'bz, 1'b0, 1'bz, 16'bzz11zzzzz0z0zzzz}: predecode_rs2_coarse = next_instr[24:20]; // ld, sd (no Zilsd) + + {1'b1, 1'bz, 1'bz, 1'bz, 16'bzz0zzzzzzzzzzzzz}: predecode_rs2_coarse = next_instr[24:20]; // (cover remaining 32-bit + {1'b1, 1'bz, 1'bz, 1'bz, 16'bzz10zzzzzzzzzzzz}: predecode_rs2_coarse = next_instr[24:20]; // patterns, without overlap) + {1'b1, 1'bz, 1'bz, 1'bz, 16'bzz11zzzzz1zzzzzz}: predecode_rs2_coarse = next_instr[24:20]; + {1'b1, 1'bz, 1'bz, 1'bz, 16'bzz11zzzzz0z1zzzz}: predecode_rs2_coarse = next_instr[24:20]; + + {1'b0, 1'bz, 1'b1, 1'b1, 16'bzz1zzzzzzzzzzz00}: predecode_rs2_coarse = zclsd_sd_rs2_coarse; + {1'b0, 1'bz, 1'bz, 1'bz, 16'bzz0zzzzzzzzzzz10}: predecode_rs2_coarse = next_instr[6:2]; // c.add, c.swsp + {1'b0, 1'b1, 1'bz, 1'bz, 16'bz01zzzzzzzzzzz10}: predecode_rs2_coarse = zcmp_pushpop_rs2; // cm.push + {1'b0, 1'bz, 1'b1, 1'b1, 16'bz11zzzzzzzzzzz10}: predecode_rs2_coarse = zclsd_sdsp_rs2_coarse; + default: predecode_rs2_coarse = {2'b01, next_instr[4:2]}; + endcase + + // The "fine" predecode targets those instructions which either: + // - Have an implicit zero-register operand in their expanded form (e.g. c.beqz) + // - Do not have a register operand on that port, but rely on the port being 0 + // We don't care about instructions which ignore the reg ports, e.g. ebreak + + casez ({|EXTENSION_C, next_instr}) + // -> addi rd, x0, imm: + {1'b1, 16'hzzzz, `RVOPC_C_LI}: predecode_rs1_fine = 5'd0; + {1'b1, 16'hzzzz, `RVOPC_C_MV}: begin + if (next_instr[6:2] == 5'd0) begin + // c.jr has rs1 as normal + predecode_rs1_fine = predecode_rs1_coarse; + end else begin + // -> add rd, x0, rs2: + predecode_rs1_fine = 5'd0; + end + end + default: predecode_rs1_fine = predecode_rs1_coarse; + endcase + + casez ({|EXTENSION_C, |EXTENSION_ZILSD, |EXTENSION_ZCLSD, next_instr}) + {1'b1, 1'bz, 1'bz, 16'hzzzz, `RVOPC_C_BEQZ}: predecode_rs2_fine = 5'd0; // -> beq rs1, x0, label + {1'b1, 1'bz, 1'bz, 16'hzzzz, `RVOPC_C_BNEZ}: predecode_rs2_fine = 5'd0; // -> bne rs1, x0, label + {1'b1, 1'b1, 1'bz, `RVOPC_SD }: predecode_rs2_fine = predecode_rs2_coarse & {5{|next_instr[24:21]}}; + {1'b1, 1'b1, 1'b1, 16'hzzzz, `RVOPC_C_SDSP}: predecode_rs2_fine = predecode_rs2_coarse & {5{|next_instr[ 6: 3]}}; + default: predecode_rs2_fine = predecode_rs2_coarse; + endcase + + if (|EXTENSION_E) begin + predecode_rs1_coarse[4] = 1'b0; + predecode_rs2_coarse[4] = 1'b0; + predecode_rs1_fine[4] = 1'b0; + predecode_rs2_fine[4] = 1'b0; + end + +end + +// ---------------------------------------------------------------------------- +// Instruction decompression + +// Instructions are decompressed at the end of stage 1 (fetch data phase). On +// ASIC, where the register file is synthesised with muxes, this puts +// decompression somewhat in parallel with register file read, which uses +// approximately decoded regnums. + +generate +if (~|EXTENSION_C) begin: no_decompress + + // No decompression; instructions decoded directly from prefetch buffer + always @ (*) begin + cir = { + buf_contents[1 * W_SLOT +: W_BUNDLE], + buf_contents[0 * W_SLOT +: W_BUNDLE] + } | 32'd3; + cir_is_32bit = 1'b1; + cir_invalid_16bit = ~&buf_contents[1:0]; + cir_is_uop = 1'b0; + cir_uop_nonfinal = 1'b0; + cir_uop_no_pc_update = 1'b0; + cir_uop_atomic = 1'b0; + end + + assign decomp_uop_step = 4'h0; + +end else begin: have_decompress + + wire decomp_instr_is_32bit; + wire [31:0] decomp_instr_out; + wire decomp_is_uop; + wire decomp_is_final_uop; + wire decomp_uop_no_pc_update; + wire decomp_uop_atomic; + wire decomp_invalid; + + wire first_uop = ~|decomp_uop_step; + // Ensure the first uop goes straight through, as it is registered into CIR: + wire uop_stall_non_first = first_uop ? ~|buf_level_next : uop_stall; + // Ensure the uop counter stops at 0 after rolling over once: + wire uop_stall_on_repeat = cir_is_uop && !cir_uop_nonfinal && ~|cir_use; + + hazard3_instr_decompress #( + `include "hazard3_config_inst.vh" + ) decomp ( + .clk (clk), + .rst_n (rst_n), + + .instr_in (next_instr), + + .instr_is_32bit (decomp_instr_is_32bit), + .instr_out (decomp_instr_out), + + .instr_out_is_uop (decomp_is_uop), + .instr_out_is_final_uop (decomp_is_final_uop), + .instr_out_uop_no_pc_update (decomp_uop_no_pc_update), + .instr_out_uop_atomic (decomp_uop_atomic), + .instr_out_uop_stall (uop_stall_non_first || uop_stall_on_repeat), + .instr_out_uop_clear (uop_clear), + .df_uop_step (decomp_uop_step), + + .invalid (decomp_invalid) + ); + + wire cir_clken = + ~|cir_vld || (!cir_vld[1] && &buf_contents[1:0]) || + |cir_use || (|EXTENSION_ZCMP && cir_is_uop && !uop_stall) || + (|EXTENSION_ZCMP && uop_clear); + + wire cir_is_uop_next = decomp_is_uop && |buf_level_next; + wire cir_uop_nonfinal_next = cir_is_uop_next && !decomp_is_final_uop; + + always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + cir <= 32'd3; + cir_is_32bit <= 1'b0; + cir_invalid_16bit <= 1'b0; + cir_is_uop <= 1'b0; + cir_uop_nonfinal <= 1'b0; + cir_uop_no_pc_update <= 1'b0; + cir_uop_atomic <= 1'b0; + end else if (cir_clken) begin + cir <= decomp_instr_out | 32'd3; + cir_is_32bit <= decomp_instr_is_32bit; + cir_invalid_16bit <= decomp_invalid; + cir_is_uop <= |EXTENSION_ZCMP && !uop_clear && cir_is_uop_next; + cir_uop_nonfinal <= |EXTENSION_ZCMP && !uop_clear && cir_uop_nonfinal_next; + cir_uop_no_pc_update <= |EXTENSION_ZCMP && !uop_clear && decomp_uop_no_pc_update; + cir_uop_atomic <= |EXTENSION_ZCMP && !uop_clear && decomp_uop_atomic; + end + end + +end +endgenerate + +assign cir_raw = { + buf_contents[1 * W_SLOT +: W_BUNDLE], + buf_contents[0 * W_SLOT +: W_BUNDLE] +}; + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/hazard3_instr_decompress.v b/vendor/Hazard3/hdl/hazard3_instr_decompress.v new file mode 100644 index 0000000..ddfab1a --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_instr_decompress.v @@ -0,0 +1,478 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2023 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// Little instructions go in, big instructions come out + +`default_nettype none + +module hazard3_instr_decompress #( +`include "hazard3_config.vh" +) ( + input wire clk, + input wire rst_n, + + input wire [31:0] instr_in, + + output reg instr_is_32bit, + output reg [31:0] instr_out, + + // If instruction is a non-final uop, need to suppress PC update, and null + // the PC offset in the mepc address in stage 3. + output wire instr_out_is_uop, + output wire instr_out_is_final_uop, + output wire instr_out_uop_no_pc_update, + // Indicate instr_out is a uop from the noninterruptible part of a uop + // sequence. If one uop is noninterruptible, all following uops until the + // end of the sequence are also noninterruptible. + output wire instr_out_uop_atomic, + // Current ucode sequence is stalled on downstream execution + input wire instr_out_uop_stall, + input wire instr_out_uop_clear, + + // To regnum decoder in frontend + output wire [3:0] df_uop_step, + + output reg invalid +); + +`include "rv_opcodes.vh" + +localparam W_REGADDR = 5; +localparam PASSTHROUGH = ~|EXTENSION_C; + +// Long-register formats: cr, ci, css +// Short-register formats: ciw, cl, cs, cb, cj +wire [W_REGADDR-1:0] rd_l = instr_in[11:7]; +wire [W_REGADDR-1:0] rs1_l = instr_in[11:7]; +wire [W_REGADDR-1:0] rs2_l = instr_in[6:2]; +wire [W_REGADDR-1:0] rd_s = {2'b01, instr_in[4:2]}; +wire [W_REGADDR-1:0] rs1_s = {2'b01, instr_in[9:7]}; +wire [W_REGADDR-1:0] rs2_s = {2'b01, instr_in[4:2]}; + +// Mapping of cx -> x immediate formats (we are *expanding* instructions, not +// decoding them): + +wire [31:0] imm_ci = { + {7{instr_in[12]}}, + instr_in[6:2], + 20'h00000 +}; + +wire [31:0] imm_cj = { + instr_in[12], + instr_in[8], + instr_in[10:9], + instr_in[6], + instr_in[7], + instr_in[2], + instr_in[11], + instr_in[5:3], + {9{instr_in[12]}}, + 12'h000 +}; + +wire [31:0] imm_cb ={ + {4{instr_in[12]}}, + instr_in[6:5], + instr_in[2], + 13'h0000, + instr_in[11:10], + instr_in[4:3], + instr_in[12], + 7'h00 +}; + +wire [31:0] imm_c_lb = { + 10'h0, + instr_in[5], + instr_in[6], + 20'h00000 +}; + +wire [31:0] imm_c_lh = { + 10'h000, + instr_in[5], + 1'b0, + 20'h00000 +}; + +function [31:0] rfmt_rd; input [4:0] rd; begin rfmt_rd = {20'h00000, rd, 7'h00}; end endfunction +function [31:0] rfmt_rs1; input [4:0] rs1; begin rfmt_rs1 = {12'h000, rs1, 15'h0000}; end endfunction +function [31:0] rfmt_rs2; input [4:0] rs2; begin rfmt_rs2 = {7'h00, rs2, 20'h00000}; end endfunction + +// ---------------------------------------------------------------------------- +// Push/pop and friends + +// The longest uop sequence is a maximal cm.popretz: +// +// - 13x lw (counter = 0..12) +// - 1x addi to set a0 to zero (counter = 13 ) < atomic section +// - 1x jalr to jump through ra (counter = 14 ) < atomic section +// - 1x addi to adjust sp (counter = 15 ) < atomic section + +wire [3:0] uop_ctr; +reg [3:0] uop_ctr_nxt_in_seq; +reg in_uop_seq; +reg uop_no_pc_update; + +wire zcmp_is_pushpop = instr_in[12]; +wire uop_seq_end = |EXTENSION_ZCMP && (zcmp_is_pushpop ? uop_ctr == 4'hf : uop_ctr[0]); +wire uop_atomic = |EXTENSION_ZCMP && (zcmp_is_pushpop ? uop_ctr >= 4'he : uop_ctr[0]); + +wire [3:0] uop_ctr_nxt = + instr_out_uop_clear ? 4'h0 : + instr_out_uop_stall ? uop_ctr : uop_ctr_nxt_in_seq; + +assign instr_out_is_uop = in_uop_seq; +assign instr_out_is_final_uop = uop_seq_end; +assign instr_out_uop_atomic = uop_atomic; +assign instr_out_uop_no_pc_update = uop_no_pc_update; +assign df_uop_step = uop_ctr; + +// The offset from current sp value to the lowest-addressed saved register, +64. +wire [3:0] zcmp_rlist = instr_in[7:4]; +wire [3:0] zcmp_n_regs = zcmp_rlist == 4'hf ? 4'hd : zcmp_rlist - 4'h3; +wire zcmp_rlist_invalid = zcmp_rlist < 4'h4 || (|EXTENSION_E && zcmp_rlist > 4'h6); + +wire [11:0] zcmp_stack_adj_base = + zcmp_rlist == 4'hf ? 12'h040 : + zcmp_rlist >= 4'hc ? 12'h030 : + zcmp_rlist >= 4'h8 ? 12'h020 : 12'h010; + +wire [11:0] zcmp_stack_adj = zcmp_stack_adj_base + {6'h00, instr_in[3:2], 4'h0}; + +// Note we perform all load/stores before moving the stack pointer. +wire [11:0] zcmp_stack_lw_offset = -{6'h00, {zcmp_n_regs - uop_ctr}, 2'h0} + zcmp_stack_adj; +wire [11:0] zcmp_stack_sw_offset = -{6'h00, {zcmp_n_regs - uop_ctr}, 2'h0}; + +wire [4:0] zcmp_ls_reg = + uop_ctr == 4'h0 ? 5'd01 : // ra + uop_ctr == 4'h1 ? 5'd08 : // s0 + uop_ctr == 4'h2 ? 5'd09 : // s1 + 5'd15 + {1'b0, uop_ctr}; // s2-s11 (s2 == x18) + +wire [31:0] zcmp_push_sw_instr = `RVOPC_NOZ_SW | rfmt_rs1(5'd2) | rfmt_rs2(zcmp_ls_reg) | { + zcmp_stack_sw_offset[11:5], 13'h0000, zcmp_stack_sw_offset[4:0], 7'h00 +}; + +wire [31:0] zcmp_pop_lw_instr = `RVOPC_NOZ_LW | rfmt_rd(zcmp_ls_reg) | rfmt_rs1(5'd2)| { + zcmp_stack_lw_offset[11:0], 20'h00000 +}; + +wire [31:0] zcmp_push_stack_adj_instr = `RVOPC_NOZ_ADDI | rfmt_rd(5'd2) | rfmt_rs1(5'd2) | { + -zcmp_stack_adj, + 20'h00000 +}; + +wire [31:0] zcmp_pop_stack_adj_instr = `RVOPC_NOZ_ADDI | rfmt_rd(5'd2) | rfmt_rs1(5'd2) | { + zcmp_stack_adj, + 20'h00000 +}; + +wire [4:0] zcmp_sa01_r1s = {|instr_in[9:8], ~|instr_in[9:8], instr_in[9:7]}; +wire [4:0] zcmp_sa01_r2s = {|instr_in[4:3], ~|instr_in[4:3], instr_in[4:2]}; + +wire zcmp_sa01_invalid = |EXTENSION_E && |{instr_in[9:8], instr_in[4:3]}; + +// ---------------------------------------------------------------------------- + +generate +if (PASSTHROUGH) begin: instr_passthrough + always @ (*) begin + instr_is_32bit = 1'b1; + instr_out = instr_in; + invalid = 1'b0; + end +end else begin: instr_decompress + always @ (*) begin + if (instr_in[1:0] == 2'b11) begin + instr_is_32bit = 1'b1; + instr_out = instr_in; + invalid = 1'b0; + in_uop_seq = 1'b0; + uop_no_pc_update = 1'b0; + uop_ctr_nxt_in_seq = uop_ctr; + end else begin + instr_is_32bit = 1'b0; + instr_out = 32'd0; + invalid = 1'b0; + in_uop_seq = 1'b0; + uop_no_pc_update = 1'b0; + uop_ctr_nxt_in_seq = uop_ctr; + casez (instr_in[15:0]) + `RVOPC_C_ADDI4SPN: begin + instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(rd_s) | rfmt_rs1(5'd2) + | {2'h0, instr_in[10:7], instr_in[12:11], instr_in[5], instr_in[6], 2'b00, 20'h00000}; + invalid = ~|instr_in[12:2]; // Always-invalid all-zeroes instruction + end + `RVOPC_C_LW: instr_out = `RVOPC_NOZ_LW | rfmt_rd(rd_s) | rfmt_rs1(rs1_s) + | {5'h00, instr_in[5], instr_in[12:10], instr_in[6], 2'b00, 20'h00000}; + `RVOPC_C_SW: instr_out = `RVOPC_NOZ_SW | rfmt_rs2(rs2_s) | rfmt_rs1(rs1_s) + | {5'h00, instr_in[5], instr_in[12], 13'h0000, instr_in[11:10], instr_in[6], 2'b00, 7'h00}; + `RVOPC_C_ADDI: instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(rd_l) | rfmt_rs1(rs1_l) | imm_ci; + `RVOPC_C_JAL: instr_out = `RVOPC_NOZ_JAL | rfmt_rd(5'd1) | imm_cj; + `RVOPC_C_J: instr_out = `RVOPC_NOZ_JAL | rfmt_rd(5'd0) | imm_cj; + `RVOPC_C_LI: instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(rd_l) | imm_ci; + `RVOPC_C_LUI: begin + if (rd_l == 5'd2) begin + // addi16sp + instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(5'd2) | rfmt_rs1(5'd2) | + {{3{instr_in[12]}}, instr_in[4:3], instr_in[5], instr_in[2], instr_in[6], 24'h000000}; + end else begin + instr_out = `RVOPC_NOZ_LUI | rfmt_rd(rd_l) | {{15{instr_in[12]}}, instr_in[6:2], 12'h000}; + end + invalid = ~|{instr_in[12], instr_in[6:2]}; // RESERVED if imm == 0 + end + `RVOPC_C_SLLI: instr_out = `RVOPC_NOZ_SLLI | rfmt_rd(rs1_l) | rfmt_rs1(rs1_l) | imm_ci; + `RVOPC_C_SRAI: instr_out = `RVOPC_NOZ_SRAI | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | imm_ci; + `RVOPC_C_SRLI: instr_out = `RVOPC_NOZ_SRLI | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | imm_ci; + `RVOPC_C_ANDI: instr_out = `RVOPC_NOZ_ANDI | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | imm_ci; + `RVOPC_C_AND: instr_out = `RVOPC_NOZ_AND | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | rfmt_rs2(rs2_s); + `RVOPC_C_OR: instr_out = `RVOPC_NOZ_OR | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | rfmt_rs2(rs2_s); + `RVOPC_C_XOR: instr_out = `RVOPC_NOZ_XOR | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | rfmt_rs2(rs2_s); + `RVOPC_C_SUB: instr_out = `RVOPC_NOZ_SUB | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | rfmt_rs2(rs2_s); + `RVOPC_C_ADD: begin + if (|rs2_l) begin + instr_out = `RVOPC_NOZ_ADD | rfmt_rd(rd_l) | rfmt_rs1(rs1_l) | rfmt_rs2(rs2_l); + end else if (|rs1_l) begin // jalr + instr_out = `RVOPC_NOZ_JALR | rfmt_rd(5'd1) | rfmt_rs1(rs1_l); + end else begin // ebreak + instr_out = `RVOPC_NOZ_EBREAK; + end + end + `RVOPC_C_MV: begin + if (|rs2_l) begin // mv + instr_out = `RVOPC_NOZ_ADD | rfmt_rd(rd_l) | rfmt_rs2(rs2_l); + end else begin // jr + instr_out = `RVOPC_NOZ_JALR | rfmt_rs1(rs1_l); + invalid = ~|rs1_l; // RESERVED + end + end + `RVOPC_C_LWSP: begin + instr_out = `RVOPC_NOZ_LW | rfmt_rd(rd_l) | rfmt_rs1(5'd2) | + {4'h0, instr_in[3:2], instr_in[12], instr_in[6:4], 2'b00, 20'h00000}; + invalid = ~|rd_l; // RESERVED + end + `RVOPC_C_SWSP: instr_out = `RVOPC_NOZ_SW | rfmt_rs2(rs2_l) | rfmt_rs1(5'd2) + | {4'h0, instr_in[8:7], instr_in[12], 13'h0000, instr_in[11:9], 2'b00, 7'h00}; + `RVOPC_C_BEQZ: instr_out = `RVOPC_NOZ_BEQ | rfmt_rs1(rs1_s) | imm_cb; + `RVOPC_C_BNEZ: instr_out = `RVOPC_NOZ_BNE | rfmt_rs1(rs1_s) | imm_cb; + + // Optional Zcb instructions: + `RVOPC_C_LBU: begin + instr_out = `RVOPC_NOZ_LBU | rfmt_rd(rd_s) | rfmt_rs1(rs1_s) | imm_c_lb; + invalid = ~|EXTENSION_ZCB; + end + `RVOPC_C_LHU: begin + instr_out = `RVOPC_NOZ_LHU | rfmt_rd(rd_s) | rfmt_rs1(rs1_s) | imm_c_lh; + invalid = ~|EXTENSION_ZCB; + end + `RVOPC_C_LH: begin + instr_out = `RVOPC_NOZ_LH | rfmt_rd(rd_s) | rfmt_rs1(rs1_s) | imm_c_lh; + invalid = ~|EXTENSION_ZCB; + end + `RVOPC_C_SB: begin + instr_out = `RVOPC_NOZ_SB | rfmt_rs2(rd_s) | rfmt_rs1(rs1_s) | imm_c_lb >> 13; + invalid = ~|EXTENSION_ZCB; + end + `RVOPC_C_SH: begin + instr_out = `RVOPC_NOZ_SH | rfmt_rs2(rd_s) | rfmt_rs1(rs1_s) | imm_c_lh >> 13; + invalid = ~|EXTENSION_ZCB; + end + `RVOPC_C_ZEXT_B: begin + instr_out = `RVOPC_NOZ_ANDI | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | 32'h0ff00000; + invalid = ~|EXTENSION_ZCB; + end + `RVOPC_C_SEXT_B: begin + instr_out = `RVOPC_NOZ_SEXT_B | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s); + invalid = ~|EXTENSION_ZCB || ~|EXTENSION_ZBB; + end + `RVOPC_C_ZEXT_H: begin + instr_out = `RVOPC_NOZ_ZEXT_H | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s); + invalid = ~|EXTENSION_ZCB || ~|EXTENSION_ZBB; + end + `RVOPC_C_SEXT_H: begin + instr_out = `RVOPC_NOZ_SEXT_H | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s); + invalid = ~|EXTENSION_ZCB || ~|EXTENSION_ZBB; + end + `RVOPC_C_NOT: begin + instr_out = `RVOPC_NOZ_XORI | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | 32'hfff00000; + invalid = ~|EXTENSION_ZCB; + end + `RVOPC_C_MUL: begin + instr_out = `RVOPC_NOZ_MUL | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | rfmt_rs2(rs2_s); + invalid = ~|EXTENSION_ZCB || ~|EXTENSION_M; + end + + // Optional Zclsd instructions: + `RVOPC_C_LD: begin + instr_out = `RVOPC_NOZ_LD | rfmt_rd(rd_s) | rfmt_rs1(rs1_s) + | {4'h0, instr_in[6:5], instr_in[12:10], 3'b000, 20'h00000}; + invalid = ~|EXTENSION_ZILSD || ~|EXTENSION_ZCLSD; + end + `RVOPC_C_SD: begin + instr_out = `RVOPC_NOZ_SD | rfmt_rs2(rs2_s) | rfmt_rs1(rs1_s) + | {4'h0, instr_in[6:5], instr_in[12], 13'h0000, instr_in[11:10], 3'b000, 7'h00}; + invalid = ~|EXTENSION_ZILSD || ~|EXTENSION_ZCLSD; + end + `RVOPC_C_LDSP: begin + instr_out = `RVOPC_NOZ_LD | rfmt_rd(rd_l) | rfmt_rs1(5'd2) | + {3'h0, instr_in[4:2], instr_in[12], instr_in[6:5], 3'b000, 20'h00000}; + invalid = ~|EXTENSION_ZILSD || ~|EXTENSION_ZCLSD || ~|rd_l; // RESERVED + end + `RVOPC_C_SDSP: begin + instr_out = `RVOPC_NOZ_SD | rfmt_rs2(rs2_l) | rfmt_rs1(5'd2) + | {3'h0, instr_in[9:7], instr_in[12], 13'h0000, instr_in[11:10], 3'b000, 7'h00}; + invalid = ~|EXTENSION_ZILSD || ~|EXTENSION_ZCLSD; + end + + // Optional Zcmp instructions: + `RVOPC_CM_PUSH: if (~|EXTENSION_ZCMP || zcmp_rlist_invalid) begin + invalid = 1'b1; + end else if (uop_ctr == 4'hf) begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = 4'h0; + instr_out = zcmp_push_stack_adj_instr; + end else begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = uop_ctr + 4'h1; + instr_out = zcmp_push_sw_instr; + uop_no_pc_update = 1'b1; + if (uop_ctr_nxt_in_seq == zcmp_n_regs) begin + uop_ctr_nxt_in_seq = 4'hf; + end + end + + `RVOPC_CM_POP: if (~|EXTENSION_ZCMP || zcmp_rlist_invalid) begin + invalid = 1'b1; + end else if (uop_ctr == 4'hf) begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = 4'h0; + instr_out = zcmp_pop_stack_adj_instr; + end else begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = uop_ctr + 4'h1; + uop_no_pc_update = 1'b1; + instr_out = zcmp_pop_lw_instr; + if (uop_ctr_nxt_in_seq == zcmp_n_regs) begin + uop_ctr_nxt_in_seq = 4'hf; + end + end + + `RVOPC_CM_POPRET: if (~|EXTENSION_ZCMP || zcmp_rlist_invalid) begin + invalid = 1'b1; + end else if (uop_ctr == 4'he) begin + // Note although this is only the first instruction in the uninterruptible sequence, + // we mark this instruction as uninterruptible: there is some special case logic to + // allow this jump to execute without flushing the final stack adjust uop, which can + // cause the wrong exception PC to be sampled if this uop is interrupted. + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = uop_ctr + 4'h1; + instr_out = `RVOPC_NOZ_JALR | rfmt_rs1(5'd1); + end else if (uop_ctr == 4'hf) begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = 4'h0; + uop_no_pc_update = 1'b1; + instr_out = zcmp_pop_stack_adj_instr; + end else begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = uop_ctr + 4'h1; + instr_out = zcmp_pop_lw_instr; + uop_no_pc_update = 1'b1; + if (uop_ctr_nxt_in_seq == zcmp_n_regs) begin + uop_ctr_nxt_in_seq = 4'he; + end + end + + `RVOPC_CM_POPRETZ: if (~|EXTENSION_ZCMP || zcmp_rlist_invalid) begin + invalid = 1'b1; + end else if (uop_ctr == 4'hd) begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = uop_ctr + 4'h1; + uop_no_pc_update = 1'b1; + instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(5'd10); // li a0, 0 + end else if (uop_ctr == 4'he) begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = uop_ctr + 4'h1; + instr_out = `RVOPC_NOZ_JALR | rfmt_rs1(5'd1); + end else if (uop_ctr == 4'hf) begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = 4'h0; + uop_no_pc_update = 1'b1; + instr_out = zcmp_pop_stack_adj_instr; + end else begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = uop_ctr + 4'h1; + uop_no_pc_update = 1'b1; + instr_out = zcmp_pop_lw_instr; + if (uop_ctr_nxt_in_seq == zcmp_n_regs) begin + uop_ctr_nxt_in_seq = 4'hd; + end + end + + `RVOPC_CM_MVSA01: if (~|EXTENSION_ZCMP || zcmp_sa01_invalid) begin + invalid = 1'b1; + end else if (uop_ctr == 4'h0) begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = uop_ctr + 4'h1; + uop_no_pc_update = 1'b1; + instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(zcmp_sa01_r1s) | rfmt_rs1(5'd10); + end else begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = 4'h0; + instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(zcmp_sa01_r2s) | rfmt_rs1(5'd11); + end + + `RVOPC_CM_MVA01S: if (~|EXTENSION_ZCMP || zcmp_sa01_invalid) begin + invalid = 1'b1; + end else if (uop_ctr == 4'h0) begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = uop_ctr + 4'h1; + uop_no_pc_update = 1'b1; + instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(5'd10) | rfmt_rs1(zcmp_sa01_r1s); + end else begin + in_uop_seq = 1'b1; + uop_ctr_nxt_in_seq = 4'h0; + instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(5'd11) | rfmt_rs1(zcmp_sa01_r2s); + end + + default: invalid = 1'b1; + endcase + end + end +end +endgenerate + +generate +if (EXTENSION_ZCMP) begin: have_uop_ctr + reg [3:0] uop_ctr_r; + assign uop_ctr = uop_ctr_r; + always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + uop_ctr_r <= 4'h0; + end else begin + uop_ctr_r <= uop_ctr_nxt; +`ifdef HAZARD3_ASSERTIONS + assert(in_uop_seq || uop_ctr_r == 4'h0); + assert(in_uop_seq || zcmp_ls_reg == 5'h01); + assert(in_uop_seq || !uop_atomic); + assert(in_uop_seq || !uop_no_pc_update); + if (uop_seq_end) begin + assert(in_uop_seq); + assert(instr_out_uop_stall || uop_ctr_nxt == 4'h0); + end +`endif + end + end +end else begin: no_uop_ctr + assign uop_ctr = 4'h0; +end +endgenerate + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/hazard3_irq_ctrl.v b/vendor/Hazard3/hdl/hazard3_irq_ctrl.v new file mode 100644 index 0000000..068e034 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_irq_ctrl.v @@ -0,0 +1,327 @@ +/*****************************************************************************\ +| Copyright (C) 2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +`default_nettype none + +// Hazard3 interrupt controller. Support for up to 512 external interrupt +// lines, with up to 16 levels of preemption. + +module hazard3_irq_ctrl #( +`include "hazard3_config.vh" +) ( + input wire clk, + input wire clk_always_on, + input wire rst_n, + + // CSR interface + input wire [11:0] addr, + input wire [1:0] wtype, + input wire wen_m_mode, + input wire ren_m_mode, + input wire [W_DATA-1:0] wdata_raw, + input wire [W_DATA-1:0] wdata, + output reg [W_DATA-1:0] rdata, + + // Trap entry/exit signals for context update + input wire trapreg_update_enter, + input wire trapreg_update_exit, + input wire trap_entry_is_eirq, + + // Interface for clearing and saving mie.mtie/msie via meicontext + output wire meicontext_clearts, + input wire mie_mtie, + input wire mie_msie, + + // External IRQ inputs: + input wire [NUM_IRQS-1:0] irq, + + // mip.meip: + output wire external_irq_pending +); + +`include "hazard3_ops.vh" +`include "hazard3_csr_addr.vh" + +localparam MAX_IRQS = 512; +localparam [3:0] IRQ_PRIORITY_MASK = ~(4'hf >> IRQ_PRIORITY_BITS); +localparam W_IRQ_INDEX = $clog2(MAX_IRQS); + +// ---------------------------------------------------------------------------- +// IRQ input flops + +// Register external IRQ signals (mainly to avoid a through-path from IRQs to +// bus request signals). Always clocked, as it's used to generate a wakeup. +// Input registers can be removed on a per-IRQ basis, but this should be done +// with care as it does create a through-path from the IRQ to the bus. + +wire [NUM_IRQS-1:0] irq_r; + +genvar g; +generate +for (g = 0; g < NUM_IRQS; g = g + 1) begin: irq_reg_loop + if (IRQ_INPUT_BYPASS[g]) begin: no_reg + assign irq_r[g] = irq[g]; + end else begin: have_reg + reg q; + always @ (posedge clk_always_on or negedge rst_n) begin + if (!rst_n) begin + q <= 1'b0; + end else begin + q <= irq[g]; + end + end + assign irq_r[g] = q; + end +end +endgenerate + +// ---------------------------------------------------------------------------- +// CSR write + +// Assigned later: +wire [W_IRQ_INDEX-1:0] meinext_irq; +wire meinext_noirq; +reg [3:0] eirq_highest_priority; + +// Interrupt array registers: +reg [NUM_IRQS-1:0] meiea; +reg [NUM_IRQS-1:0] meifa; +reg [4*NUM_IRQS-1:0] meipra; + +// Padded vectors for CSR readout +wire [MAX_IRQS-1:0] meiea_rdata = {{MAX_IRQS-NUM_IRQS{1'b0}}, meiea}; +wire [MAX_IRQS-1:0] meifa_rdata = {{MAX_IRQS-NUM_IRQS{1'b0}}, meifa}; +wire [4*MAX_IRQS-1:0] meipra_rdata = {{4*(MAX_IRQS-NUM_IRQS){1'b0}}, meipra}; + +always @ (posedge clk or negedge rst_n) begin: update_irq_reg_arrays + reg signed [31:0] i; + if (!rst_n) begin + meiea <= {NUM_IRQS{1'b0}}; + meifa <= {NUM_IRQS{1'b0}}; + meipra <= {4*NUM_IRQS{1'b0}}; + end else begin + for (i = 0; i < NUM_IRQS; i = i + 1) begin + // CSR write update. Note raw wdata is used for array indexing -- + // necessary for correctness, and also avoid a loop with rdata. + if (wen_m_mode && addr == MEIEA && $signed(wdata_raw[4:0]) == i[W_IRQ_INDEX-1:4]) begin + meiea[i] <= wdata[16 + (i % 16)]; + end + if (wen_m_mode && addr == MEIFA && $signed(wdata_raw[4:0]) == i[W_IRQ_INDEX-1:4]) begin + meifa[i] <= wdata[16 + (i % 16)]; + end + if (wen_m_mode && addr == MEIPRA && $signed(wdata_raw[6:0]) == i[W_IRQ_INDEX-1:2]) begin + meipra[4 * i +: 4] <= wdata[16 + 4 * (i % 4) +: 4] & IRQ_PRIORITY_MASK; + end + // Clear IRQ force when the corresponding IRQ is sampled from meinext + // (so that an IRQ can be posted *once* without modifying the ISR source) + if (meinext_irq == i[W_IRQ_INDEX-1:0] && ren_m_mode && addr == MEINEXT && !meinext_noirq) begin + meifa[i[$clog2(NUM_IRQS)-1:0]] <= 1'b0; + end + end + end +end + +reg [3:0] meicontext_pppreempt; +reg [3:0] meicontext_ppreempt; +reg [4:0] meicontext_preempt; +reg meicontext_noirq; +reg [W_IRQ_INDEX-1:0] meicontext_irq; +reg meicontext_mreteirq; + +wire [4:0] preempt_level_next = meinext_noirq ? 5'h10 : ( + (5'd1 << (4 - IRQ_PRIORITY_BITS)) + {1'b0, eirq_highest_priority} +); + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + meicontext_pppreempt <= 4'h0; + meicontext_ppreempt <= 4'h0; + meicontext_preempt <= 5'h0; + meicontext_noirq <= 1'b1; + meicontext_irq <= {W_IRQ_INDEX{1'b0}}; + meicontext_mreteirq <= 1'b0; + end else if (trapreg_update_enter) begin + if (trap_entry_is_eirq) begin + // Priority save. Note the MSB of preempt needn't be saved since, + // when it is set, preemption is impossible, so we won't be here. + meicontext_pppreempt <= meicontext_ppreempt & IRQ_PRIORITY_MASK; + meicontext_ppreempt <= meicontext_preempt[3:0] & IRQ_PRIORITY_MASK; + // Setting preempt isn't strictly necessary, since an updating read + // of meinext ought to be performed before re-enabling IRQs via + // mstatus.mie, but it seems the least surprising thing to do: + meicontext_preempt <= preempt_level_next & {1'b1, IRQ_PRIORITY_MASK}; + meicontext_mreteirq <= 1'b1; + end else begin + meicontext_mreteirq <= 1'b0; + end + end else if (trapreg_update_exit) begin + meicontext_mreteirq <= 1'b0; + if (meicontext_mreteirq) begin + // Priority restore + meicontext_pppreempt <= 4'h0; + meicontext_ppreempt <= meicontext_pppreempt & IRQ_PRIORITY_MASK; + meicontext_preempt <= {1'b0, meicontext_ppreempt & IRQ_PRIORITY_MASK}; + end + end else if (wen_m_mode && addr == MEICONTEXT) begin + meicontext_pppreempt <= wdata[31:28] & IRQ_PRIORITY_MASK; + meicontext_ppreempt <= wdata[27:24] & IRQ_PRIORITY_MASK; + meicontext_preempt <= wdata[20:16] & {1'b1, IRQ_PRIORITY_MASK}; + meicontext_noirq <= wdata[15]; + meicontext_irq <= wdata[12:4]; + meicontext_mreteirq <= wdata[0]; + end else if (wen_m_mode && addr == MEINEXT && wdata[0]) begin + // Interrupt has been sampled, with the update request set, so update + // the context (including preemption level) appropriately. + meicontext_preempt <= preempt_level_next & {1'b1, IRQ_PRIORITY_MASK}; + meicontext_noirq <= meinext_noirq; + meicontext_irq <= meinext_irq; + end +end + +assign meicontext_clearts = wen_m_mode && wtype != CSR_WTYPE_C && addr == MEICONTEXT && wdata_raw[1]; + +// ---------------------------------------------------------------------------- +// External interrupt logic + +// Trap request is asserted when there is an interrupt at or above our current +// preemption level. meinext displays interrupts at or above our *previous* +// preemption level: this masking helps avoid re-taking IRQs in frames that you +// have preempted. + +wire [NUM_IRQS-1:0] meipa = irq_r | meifa; +wire [MAX_IRQS-1:0] meipa_rdata = {{MAX_IRQS-NUM_IRQS{1'b0}}, meipa}; + +reg [NUM_IRQS-1:0] eirq_active_above_preempt; +reg [NUM_IRQS-1:0] eirq_active_above_ppreempt; + +always @ (*) begin: eirq_compare + integer i; + for (i = 0; i < NUM_IRQS; i = i + 1) begin + eirq_active_above_preempt[i] = meipa[i] && meiea[i] && {1'b0, meipra[i * 4 +: 4]} >= meicontext_preempt; + eirq_active_above_ppreempt[i] = meipa[i] && meiea[i] && meipra[i * 4 +: 4] >= meicontext_ppreempt; + end +end + +assign external_irq_pending = |eirq_active_above_preempt; +assign meinext_noirq = ~|eirq_active_above_ppreempt; + +// Two things remaining to calculate: +// +// - What is the IRQ number of the highest-priority pending IRQ that is above +// meicontext.ppreempt +// - What is the priority of that IRQ +// +// In the second case we can relax the calculation to ignore ppreempt, since it +// only needs to be valid if such an IRQ exists. Currently we choose to reuse +// the same priority selector (possibly longer critpath while saving area), but +// we could use a second priority selector that ignores ppreempt masking. + +wire [NUM_IRQS-1:0] highest_eirq_onehot; +wire [W_IRQ_INDEX-1:0] meinext_irq_unmasked; + +hazard3_onehot_priority_dynamic #( + .W_REQ (NUM_IRQS), + .N_PRIORITIES (16), + .PRIORITY_HIGHEST_WINS (1), + .TIEBREAK_HIGHEST_WINS (0) +) eirq_priority_u ( + .pri (meipra[4*NUM_IRQS-1:0] & {NUM_IRQS{IRQ_PRIORITY_MASK}}), + .req (eirq_active_above_ppreempt), + .gnt (highest_eirq_onehot) +); + +always @ (*) begin: get_highest_eirq_priority + integer i; + eirq_highest_priority = 4'h0; + for (i = 0; i < NUM_IRQS; i = i + 1) begin + eirq_highest_priority = eirq_highest_priority | ( + meipra[4 * i +: 4] & {4{highest_eirq_onehot[i]}} + ); + end +end + +wire [$clog2(NUM_IRQS)-1:0] meinext_irq_unmasked_nopad; + +hazard3_onehot_encode #( + .W_REQ (NUM_IRQS) +) eirq_encode_u ( + .req (highest_eirq_onehot), + .gnt (meinext_irq_unmasked_nopad) +); + +generate +if ($clog2(NUM_IRQS) == $clog2(MAX_IRQS)) begin: encode_eirq_no_padding + assign meinext_irq_unmasked = meinext_irq_unmasked_nopad; +end else begin: encode_eirq_padded + assign meinext_irq_unmasked = { + {$clog2(MAX_IRQS) - $clog2(NUM_IRQS){1'b0}}, + meinext_irq_unmasked_nopad + }; +end +endgenerate + +// It is unnecessary to mask meinext_irq based on meinext_noirq because: +// - The value of the CSR field is unimportant when noirq is set +// - There are no IRQ inputs to the priority selector when there +// are no IRQs, so result is already 0. +assign meinext_irq = meinext_irq_unmasked; + +// ---------------------------------------------------------------------------- +// CSR read + +always @ (*) begin + rdata = {W_DATA{1'b0}}; + case (addr) + + MEIEA: rdata = { + meiea_rdata[wdata_raw[4:0] * 16 +: 16], + 16'h0 + }; + + MEIPA: rdata = { + meipa_rdata[wdata_raw[4:0] * 16 +: 16], + 16'h0 + }; + + MEIFA: rdata = { + meifa_rdata[wdata_raw[4:0] * 16 +: 16], + 16'h0 + }; + + MEIPRA: rdata = { + meipra_rdata[wdata_raw[6:0] * 16 +: 16], + 16'h0 + }; + + MEINEXT: rdata = { + meinext_noirq, + 20'h0, + meinext_irq, + 2'h0 + }; + + MEICONTEXT: rdata = { + meicontext_pppreempt, + meicontext_ppreempt, + 3'h0, + meicontext_preempt, + meicontext_noirq, + 2'h0, + meicontext_irq, + mie_mtie && meicontext_clearts, + mie_msie && meicontext_clearts, + 1'b0, + meicontext_mreteirq + }; + + default: rdata = {W_DATA{1'b0}}; + endcase +end + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/hazard3_ops.vh b/vendor/Hazard3/hdl/hazard3_ops.vh new file mode 100644 index 0000000..b34401d --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_ops.vh @@ -0,0 +1,124 @@ +/*****************************************************************************\ +| Copyright (C) 2021 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// ALU operation selectors + +localparam ALUOP_ADD = 6'h00; +localparam ALUOP_SUB = 6'h01; +localparam ALUOP_LT = 6'h02; +localparam ALUOP_LTU = 6'h04; +localparam ALUOP_AND = 6'h06; +localparam ALUOP_OR = 6'h07; +localparam ALUOP_XOR = 6'h08; +localparam ALUOP_SRL = 6'h09; +localparam ALUOP_SRA = 6'h0a; +localparam ALUOP_SLL = 6'h0b; +localparam ALUOP_MULDIV = 6'h0c; +localparam ALUOP_RS2 = 6'h0d; // differs from AND/OR/XOR in [1:0] +// Bitmanip ALU operations (some also used by AMOs): +localparam ALUOP_SHXADD = 6'h20; +localparam ALUOP_CLZ = 6'h23; +localparam ALUOP_CPOP = 6'h24; +localparam ALUOP_CTZ = 6'h25; +localparam ALUOP_ANDN = 6'h26; // Same LSBs as non-inverted +localparam ALUOP_ORN = 6'h27; // Same LSBs as non-inverted +localparam ALUOP_XNOR = 6'h28; // Same LSBs as non-inverted +localparam ALUOP_MAX = 6'h29; +localparam ALUOP_MAXU = 6'h2a; +localparam ALUOP_MIN = 6'h2b; +localparam ALUOP_MINU = 6'h2c; +localparam ALUOP_ORC_B = 6'h2d; +localparam ALUOP_REV8 = 6'h2e; +localparam ALUOP_ROL = 6'h2f; +localparam ALUOP_ROR = 6'h30; +localparam ALUOP_SEXT_B = 6'h31; +localparam ALUOP_SEXT_H = 6'h32; +localparam ALUOP_ZEXT_H = 6'h33; + +localparam ALUOP_CLMUL = 6'h34; + +localparam ALUOP_BCLR = 6'h35; +localparam ALUOP_BEXT = 6'h36; +localparam ALUOP_BINV = 6'h37; +localparam ALUOP_BSET = 6'h38; + +localparam ALUOP_PACK = 6'h39; +localparam ALUOP_PACKH = 6'h3a; +localparam ALUOP_BREV8 = 6'h3b; +localparam ALUOP_ZIP = 6'h3c; +localparam ALUOP_UNZIP = 6'h3d; + +localparam ALUOP_BEXTM = 6'h3e; + +localparam ALUOP_XPERM = 6'h3f; + +// Parameters to control ALU input muxes. Bypass mux paths are +// controlled by X, so D has no parameters to choose these. + +localparam ALUSRCA_RS1 = 1'h0; +localparam ALUSRCA_PC = 1'h1; + +localparam ALUSRCB_RS2 = 1'h0; +localparam ALUSRCB_IMM = 1'h1; + +localparam MEMOP_LW = 5'h00; +localparam MEMOP_LH = 5'h01; +localparam MEMOP_LB = 5'h02; +localparam MEMOP_LHU = 5'h03; +localparam MEMOP_LBU = 5'h04; +localparam MEMOP_SW = 5'h05; +localparam MEMOP_SH = 5'h06; +localparam MEMOP_SB = 5'h07; + +localparam MEMOP_LR_W = 5'h08; +localparam MEMOP_SC_W = 5'h09; +localparam MEMOP_AMO = 5'h0a; +localparam MEMOP_NONE = 5'h10; + +localparam BCOND_NEVER = 2'h0; +localparam BCOND_ALWAYS = 2'h1; +localparam BCOND_ZERO = 2'h2; +localparam BCOND_NZERO = 2'h3; + +// CSR access types + +localparam CSR_WTYPE_W = 2'h0; +localparam CSR_WTYPE_S = 2'h1; +localparam CSR_WTYPE_C = 2'h2; + +// Exceptional condition signals which travel alongside (or instead of) +// instructions in the pipeline. These are speculative and can be flushed on +// e.g. branch mispredict. These mostly align with mcause values. + +localparam EXCEPT_NONE = 4'hf; + +localparam EXCEPT_INSTR_MISALIGN = 4'h0; +localparam EXCEPT_INSTR_FAULT = 4'h1; +localparam EXCEPT_INSTR_ILLEGAL = 4'h2; +localparam EXCEPT_EBREAK = 4'h3; +localparam EXCEPT_LOAD_ALIGN = 4'h4; +localparam EXCEPT_LOAD_FAULT = 4'h5; +localparam EXCEPT_STORE_ALIGN = 4'h6; +localparam EXCEPT_STORE_FAULT = 4'h7; +localparam EXCEPT_ECALL_U = 4'h8; +// MRET, Return from M-mode: not really an exception, but handled like one +localparam EXCEPT_MRET = 4'ha; +localparam EXCEPT_ECALL_M = 4'hb; +// spare: c +// spare: d +// REFETCH: flush and refetch sequentially-following instructions, e.g. on +// executing fence.i. Jumps from stage 3 to get ordering against L/S dphase. +localparam EXCEPT_REFETCH = 4'he; + +// Operations for M extension (these are just instr[14:12]) + +localparam M_OP_MUL = 3'h0; +localparam M_OP_MULH = 3'h1; +localparam M_OP_MULHSU = 3'h2; +localparam M_OP_MULHU = 3'h3; +localparam M_OP_DIV = 3'h4; +localparam M_OP_DIVU = 3'h5; +localparam M_OP_REM = 3'h6; +localparam M_OP_REMU = 3'h7; diff --git a/vendor/Hazard3/hdl/hazard3_pmp.v b/vendor/Hazard3/hdl/hazard3_pmp.v new file mode 100644 index 0000000..9c1f1fb --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_pmp.v @@ -0,0 +1,380 @@ +/*****************************************************************************\ +| Copyright (C) 2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +`default_nettype none + +// Physical memory protection unit + +module hazard3_pmp #( +`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, + + // Fetch address query + input wire [W_ADDR-1:0] i_addr, + input wire i_m_mode, + output wire i_kill, + + // Load/store address query + input wire [W_ADDR-1:0] d_addr, + // Broken out separately for carry-save: + input wire [W_ADDR-1:0] d_addr_addend_rs1, + input wire [W_ADDR-1:0] d_addr_addend_imm, + input wire [W_ADDR-1:0] d_addr_addend_lspair_offs, + input wire d_m_mode, + input wire d_write, + output wire d_kill +); + +localparam PMP_A_NAPOT = 2'b11; +localparam PMP_A_NA4 = 2'b10; +localparam PMP_A_TOR = 2'b01; +localparam PMP_A_OFF = 2'b00; + +// Which values are supported in A field (unsupported are mapped to OFF): +localparam [3:0] PMP_A_SUPPORTED = { + |PMP_MATCH_NAPOT, + |PMP_MATCH_NAPOT && PMP_GRAIN == 0, + |PMP_MATCH_TOR, + 1'b1 +}; + +`include "hazard3_csr_addr.vh" + +generate +if (PMP_REGIONS == 0) begin: no_pmp + +// This should already be stubbed out in core.v, but use a generate here too +// so that we don't get a warning for elaborating this module with a region +// count of 0. + +always @ (*) cfg_rdata = {W_DATA{1'b0}}; +assign i_kill = 1'b0; +assign d_kill = 1'b0; + +end else begin: have_pmp + +// ---------------------------------------------------------------------------- +// Config registers and read/write interface + +// Whether a region's configuration is writable; this is non-trivial when TOR +// is supported because locking region i + 1 can also lock region i. +wire [PMP_REGIONS-1:0] region_locked; + +reg [PMP_REGIONS-1:0] pmpcfg_l; +reg [1:0] pmpcfg_a [0:PMP_REGIONS-1]; +reg [PMP_REGIONS-1:0] pmpcfg_x; +reg [PMP_REGIONS-1:0] pmpcfg_w; +reg [PMP_REGIONS-1:0] pmpcfg_r; + +// Address register contains bits 33:2 of the address (to support 16 GiB +// physical address space). We don't implement bits 33 or 32. +reg [W_ADDR-3:0] pmpaddr [0:PMP_REGIONS-1]; + +// Hazard3 extension for applying PMP regions to M-mode without locking. +// Different from ePMP mseccfg.rlb: low-numbered regions may be locked for +// security reasons, but higher-numbered regions should stll be available for +// other purposes e.g. stack guarding, peripheral emulation +reg [PMP_REGIONS-1:0] pmpcfg_m; + +always @ (posedge clk or negedge rst_n) begin: cfg_update + reg signed [31:0] i; + if (!rst_n) begin + for (i = 0; i < PMP_REGIONS; i = i + 1) begin + pmpcfg_l[i] <= PMP_HARDWIRED[i] ? PMP_HARDWIRED_CFG[8 * i + 7] : 1'b0; + pmpcfg_a[i] <= PMP_HARDWIRED[i] ? PMP_HARDWIRED_CFG[8 * i + 3 +: 2] : 2'h0; + pmpcfg_x[i] <= PMP_HARDWIRED[i] ? PMP_HARDWIRED_CFG[8 * i + 2] : 1'b0; + pmpcfg_w[i] <= PMP_HARDWIRED[i] ? PMP_HARDWIRED_CFG[8 * i + 1] : 1'b0; + pmpcfg_r[i] <= PMP_HARDWIRED[i] ? PMP_HARDWIRED_CFG[8 * i + 0] : 1'b0; + + pmpaddr[i] <= PMP_HARDWIRED[i] ? PMP_HARDWIRED_ADDR[32 * i +: 30] : + PMP_GRAIN > 1 ? ~(~30'h0 << (PMP_GRAIN - 1)) : 30'h0; + end + pmpcfg_m <= {PMP_REGIONS{1'b0}}; + end else if (cfg_wen) begin + for (i = 0; i < PMP_REGIONS; i = i + 1) begin + if (cfg_addr == PMPCFG0 + i[13:2] && !region_locked[i]) begin + if (PMP_HARDWIRED[i]) begin + // Keep tied to hardwired value (but still make the "register" sensitive to clk) + pmpcfg_l[i] <= PMP_HARDWIRED_CFG[8 * i + 7]; + pmpcfg_a[i] <= PMP_HARDWIRED_CFG[8 * i + 3 +: 2]; + pmpcfg_x[i] <= PMP_HARDWIRED_CFG[8 * i + 2]; + pmpcfg_w[i] <= PMP_HARDWIRED_CFG[8 * i + 1]; + pmpcfg_r[i] <= PMP_HARDWIRED_CFG[8 * i + 0]; + pmpaddr[i] <= PMP_HARDWIRED_ADDR[32 * i +: 30]; + end else begin + pmpcfg_l[i] <= cfg_wdata[i % 4 * 8 + 7]; + pmpcfg_x[i] <= cfg_wdata[i % 4 * 8 + 2]; + pmpcfg_w[i] <= cfg_wdata[i % 4 * 8 + 1]; + pmpcfg_r[i] <= cfg_wdata[i % 4 * 8 + 0]; + // Unsupported A values are mapped to OFF (it's a WARL field). + pmpcfg_a[i] <= PMP_A_SUPPORTED[cfg_wdata[i % 4 * 8 + 3 +: 2]] ? + cfg_wdata[i % 4 * 8 + 3 +: 2] : PMP_A_OFF; + end + end + if (cfg_addr == PMPADDR0 + i[11:0] && !region_locked[i]) begin + // This implements one bit too many when G > 0 and only + // PMP_MATCH_TOR is enabled, however that bit is ignored for + // both rdata and address matching, so should be trimmed. + if (PMP_GRAIN > 1) begin + pmpaddr[i] <= cfg_wdata[W_ADDR-3:0] | ~(~30'h0 << (PMP_GRAIN - 1)); + end else begin + pmpaddr[i] <= cfg_wdata[W_ADDR-3:0]; + end + end + end + if (cfg_addr == PMPCFGM0) begin + pmpcfg_m <= cfg_wdata[PMP_REGIONS-1:0] & ~PMP_HARDWIRED & {PMP_REGIONS{|EXTENSION_XH3PMPM}}; + end + end +end + + +always @ (*) begin: cfg_read + reg signed [31:0] i; + cfg_rdata = {W_DATA{1'b0}}; + for (i = 0; i < PMP_REGIONS; i = i + 1) begin + if (cfg_addr == PMPCFG0 + i[13:2]) begin + cfg_rdata[i % 4 * 8 +: 8] = { + pmpcfg_l[i], + 2'b00, + pmpcfg_a[i], + pmpcfg_x[i], + pmpcfg_w[i], + pmpcfg_r[i] + }; + end else if (cfg_addr == PMPADDR0 + i[11:0]) begin + if (PMP_GRAIN >= 2 && pmpcfg_a[i][1]) begin + // Bits G-2:0 read back as all-ones when A is NA4 or NAPOT. + cfg_rdata[W_ADDR-3:0] = pmpaddr[i] | ~({W_ADDR-2{1'b1}} << (PMP_GRAIN - 1)); + end else if (PMP_GRAIN >= 1 && !pmpcfg_a[i][1]) begin + // Bits G-1:0 read back as all-zeroes when A is OFF or TOR. + cfg_rdata[W_ADDR-3:0] = pmpaddr[i] & ({W_ADDR-2{1'b1}} << PMP_GRAIN); + end else begin + cfg_rdata[W_ADDR-3:0] = pmpaddr[i]; + end + end + end + if (cfg_addr == PMPCFGM0) begin + cfg_rdata = {{32-PMP_REGIONS{1'b0}}, pmpcfg_m} & {32{|EXTENSION_XH3PMPM}}; + end +end + +// ---------------------------------------------------------------------------- +// Region locking rules + +reg [PMP_REGIONS-1:0] pmp_region_is_tor; +always @ (*) begin: check_region_is_tor + integer i; + for (i = 0; i < PMP_REGIONS; i = i + 1) begin + pmp_region_is_tor[i] = PMP_MATCH_TOR && pmpcfg_a[i] == PMP_A_TOR; + end +end + +assign region_locked = pmpcfg_l | ((pmpcfg_l & pmp_region_is_tor) >> 1); + +// ---------------------------------------------------------------------------- +// Match addresses against regions + +wire [PMP_REGIONS-1:0] d_match_napot; +wire [PMP_REGIONS-1:0] i_match_napot; +wire [PMP_REGIONS-1:0] d_match_tor; +wire [PMP_REGIONS-1:0] i_match_tor; + +if (PMP_MATCH_NAPOT != 0) begin: have_napot + + reg [PMP_REGIONS-1:0] d_match_napot_r; + reg [PMP_REGIONS-1:0] i_match_napot_r; + + assign d_match_napot = d_match_napot_r; + assign i_match_napot = i_match_napot_r; + + // Decode PMPCFGx.A and PMPADDRx into a 32-bit address mask and address + reg [W_ADDR-1:0] match_mask [0:PMP_REGIONS-1]; + reg [W_ADDR-1:0] match_addr [0:PMP_REGIONS-1]; + + // Encoding: (noting ADDR is a 4-byte address, not a word address): + // CFG.A | ADDR | Region size + // ------+----------+------------ + // NA4 | y..yyyyy | 4 bytes + // NAPOT | y..yyyy0 | 8 bytes + // NAPOT | y..yyy01 | 16 bytes + // NAPOT | y..yy011 | 32 bytes + // NAPOT | y..y0111 | 64 bytes + // etc. + // + // So, with the exception of NA4, the rule is to check all bits more + // significant than the least-significant 0 bit. + + always @ (*) begin: decode_match_mask_addr + integer i, j; + for (i = 0; i < PMP_REGIONS; i = i + 1) begin + if (!pmpcfg_a[i][0]) begin + match_mask[i] = {{W_ADDR-2{1'b1}}, 2'b00}; + end else begin + // Bits 1:0 are always 0. Bit 2 is 0 because NAPOT is at least 8 bytes. + match_mask[i] = {W_ADDR{1'b0}}; + for (j = 3; j < W_ADDR; j = j + 1) begin + match_mask[i][j] = match_mask[i][j - 1] || !pmpaddr[i][j - 3]; + end + end + match_addr[i] = {pmpaddr[i], 2'b00} & match_mask[i]; + end + end + + // We check only the least-addressed byte of each access. See later + // comments for an argument as to why this is sufficient. + + always @ (*) begin: check_d_match + integer i; + for (i = PMP_REGIONS - 1; i >= 0; i = i - 1) begin + d_match_napot_r[i] = pmpcfg_a[i][1] && + (d_addr & match_mask[i]) == match_addr[i]; + i_match_napot_r[i] = pmpcfg_a[i][1] && + (i_addr & match_mask[i]) == match_addr[i]; + end + end + +end else begin: no_napot + + assign d_match_napot = {PMP_REGIONS{1'b0}}; + assign i_match_napot = {PMP_REGIONS{1'b0}}; + +end + +if (PMP_MATCH_TOR != 0) begin: have_tor + + reg [PMP_REGIONS-1:0] d_match_tor_r; + reg [PMP_REGIONS-1:0] i_match_tor_r; + reg [W_ADDR-1:0] watermark [0:PMP_REGIONS-1]; + reg [PMP_REGIONS-1:0] d_lt; + reg [PMP_REGIONS-1:0] i_lt; + + assign d_match_tor = d_match_tor_r; + assign i_match_tor = i_match_tor_r; + + always @ (*) begin: compare + integer i; + for (i = 0; i < PMP_REGIONS; i = i + 1) begin + watermark[i] = { + pmpaddr[i][W_ADDR-3:0] & (~30'h0 << PMP_GRAIN), + 2'b00 + }; + // Bring terms in separately to try to encourage adder merging + d_lt[i] = ( + d_addr_addend_rs1 + d_addr_addend_imm + d_addr_addend_lspair_offs + ) < watermark[i]; + i_lt[i] = i_addr < watermark[i]; + end + end + + wire [PMP_REGIONS-1:0] d_prev_ge = ~(d_lt << 1); + wire [PMP_REGIONS-1:0] i_prev_ge = ~(i_lt << 1); + + always @ (*) begin: match + integer i; + for (i = 0; i < PMP_REGIONS; i = i + 1) begin + d_match_tor_r[i] = d_lt[i] && d_prev_ge[i] && pmpcfg_a[i] == PMP_A_TOR; + i_match_tor_r[i] = i_lt[i] && i_prev_ge[i] && pmpcfg_a[i] == PMP_A_TOR; + end + end + +end else begin: no_tor + + assign d_match_tor = {PMP_REGIONS{1'b0}}; + assign i_match_tor = {PMP_REGIONS{1'b0}}; + +end + +// ---------------------------------------------------------------------------- +// Decode permissions from matches + +// For load/stores we assume any non-naturally-aligned transfers trigger a +// misaligned load/store/AMO exception, so we only need to decode the PMP +// attribute for the first byte of the access. Note the spec gives us freedom +// to report *either* a load/store/AMO access fault (mcause = 5, 7) or a +// load/store/AMO alignment fault (mcause = 4, 6), in the case that both +// happen, and we choose alignment fault in this case. + +reg d_m; // Hazard3 extension (M-mode without locking) +reg d_l; +reg d_r; +reg d_w; + +always @ (*) begin: check_d_match + integer i; + d_m = 1'b0; + d_l = 1'b0; + d_r = 1'b0; + d_w = 1'b0; + // Lowest-numbered match wins, so work down from the top. This should be + // inferred as a priority mux structure (cascade mux). + for (i = PMP_REGIONS - 1; i >= 0; i = i - 1) begin + if (d_match_napot[i] || d_match_tor[i]) begin + d_m = pmpcfg_m[i]; + d_l = pmpcfg_l[i]; + d_r = pmpcfg_r[i]; + d_w = pmpcfg_w[i]; + end + end +end + +// Instructions work similarly because we check *fetches*, not instructions. +// Fetch is always word-sized word-aligned. The spec permits this: +// +// "On some implementations, misaligned loads, stores, and instruction fetches +// may also be decomposed into multiple accesses, some of which may succeed +// before an access-fault exception occurs." +// +// Hazard3 separately checks the naturally-aligned fetches that occur in the +// course of fetching a non-naturally-aligned instruction. This means +// instruction fetch spanning two different regions which both grant X +// permission *is* permitted, unlike the RP2350 version of Hazard3. + +reg i_m; // Hazard3 extension (M-mode without locking) +reg i_l; +reg i_x; + +always @ (*) begin: check_i_match + integer i; + i_m = 1'b0; + i_l = 1'b0; + i_x = 1'b0; + for (i = PMP_REGIONS - 1; i >= 0; i = i - 1) begin + if (i_match_napot[i] || i_match_tor[i]) begin + i_m = pmpcfg_m[i]; + i_l = pmpcfg_l[i]; + i_x = pmpcfg_x[i]; + end + end +end + +// ---------------------------------------------------------------------------- +// Access rules + +// M-mode gets to ignore protections, unless the lock or M-mode bit is set. + +assign d_kill = (!d_m_mode || d_l || d_m) && ( + (!d_write && !d_r) || + ( d_write && !d_w) +); + +assign i_kill = (!i_m_mode || i_l || i_m) && !i_x; + +end +endgenerate + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/hazard3_power_ctrl.v b/vendor/Hazard3/hdl/hazard3_power_ctrl.v new file mode 100644 index 0000000..eab5582 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_power_ctrl.v @@ -0,0 +1,173 @@ +/*****************************************************************************\ +| Copyright (C) 2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +`default_nettype none + +// Wake/sleep (power) state machine for Hazard3 + +module hazard3_power_ctrl #( +`include "hazard3_config.vh" +) ( + input wire clk_always_on, + input wire rst_n, + + // 4-phase (Gray code) req/ack handshake for requesting and releasing + // power+clock enable on non-processor hardware, e.g. the bus fabric. This + // can also be used for an external controller to gate the processor's clk + // input, rather than the clk_en signal below. + output reg pwrup_req, + input wire pwrup_ack, + + // Top-level clock enable for an optional clock gate on the processor's clk + // input (but not clk_always_on, which clocks this module and the IRQ input + // flops). This allows the processor to clock-gate when sleeping. It's + // acceptable for the clock gate cell to have one cycle of delay when + // clk_en changes. + output reg clk_en, + + // Power state controls from CSRs + input wire allow_clkgate, + input wire allow_power_down, + input wire allow_sleep_on_block, + + // Signal from frontend that it has stalled against the WFI pipeline + // stall, and we are now clear to enter a deep sleep state + input wire frontend_pwrdown_ok, + + input wire sleeping_on_wfi, + input wire wfi_wakeup_req, + input wire sleeping_on_block, + input wire block_wakeup_req_pulse, + output reg stall_release +); + +// ---------------------------------------------------------------------------- +// Wake/sleep state machine + +localparam W_STATE = 2; +localparam S_AWAKE = 2'h0; +localparam S_ENTER_ASLEEP = 2'h1; +localparam S_ASLEEP = 2'h2; +localparam S_ENTER_AWAKE = 2'h3; + +reg [W_STATE-1:0] state; +reg block_wakeup_req; + +wire active_wake_req = + (sleeping_on_block && (block_wakeup_req || wfi_wakeup_req)) || + (sleeping_on_wfi && wfi_wakeup_req); + +// Note: we assert our power up request during reset, and *assume* that the +// power up acknowledge is also high at reset. If this is a problem, extend +// the core reset. + +always @ (posedge clk_always_on or negedge rst_n) begin + if (!rst_n) begin + state <= S_AWAKE; + pwrup_req <= 1'b1; + clk_en <= 1'b1; + stall_release <= 1'b0; + end else begin + stall_release <= 1'b0; + case (state) + S_AWAKE: if (sleeping_on_wfi || sleeping_on_block) begin + if (stall_release) begin + // The last cycle of an ongoing which we have just released. Sit + // tight, this instruction will move down the pipeline at the + // end of this cycle. (There is an assertion that this doesn't + // happen twice.) + state <= S_AWAKE; + end else if (active_wake_req) begin + // Skip deep sleep if it would immediately fall through. + stall_release <= 1'b1; + end else if ((allow_power_down || allow_clkgate) && (sleeping_on_wfi || allow_sleep_on_block)) begin + if (frontend_pwrdown_ok) begin + pwrup_req <= !allow_power_down; + clk_en <= !allow_clkgate; + state <= allow_power_down ? S_ENTER_ASLEEP : S_ASLEEP; + end else begin + // Stay awake until it is safe to power down (i.e. until our + // instruction fetch goes quiet). + state <= S_AWAKE; + end + end else begin + // No power state change. Just sit with the pipeline stalled. + state <= S_AWAKE; + end + end + S_ENTER_ASLEEP: if (!pwrup_ack) begin + state <= S_ASLEEP; + end + S_ASLEEP: if (active_wake_req) begin + pwrup_req <= 1'b1; + clk_en <= 1'b1; + // Still go through the enter state for non-power-down wakeup, in + // case the clock gate cell has a 1 cycle delay. + state <= S_ENTER_AWAKE; + end + S_ENTER_AWAKE: if (pwrup_ack || !allow_power_down) begin + state <= S_AWAKE; + stall_release <= 1'b1; + end + default: begin + state <= S_AWAKE; + end + endcase + end +end + +`ifdef HAZARD3_ASSERTIONS +// Regs are a workaround for the non-constant reset value issue with +// $past() in yosys-smtbmc. +reg past_sleeping; +reg past_stall_release; +always @ (posedge clk_always_on) begin + if (!rst_n) begin + past_sleeping <= 1'b0; + past_stall_release <= 1'b0; + end else begin + past_sleeping <= sleeping_on_wfi || sleeping_on_block; + past_stall_release <= stall_release; + // These must always be mutually exclusive. + assert(!(sleeping_on_wfi && sleeping_on_block)); + if (stall_release) begin + // Presumably there was a stall which we just released + assert(past_sleeping); + // Presumably we are still in that stall + assert(sleeping_on_wfi|| sleeping_on_block); + // It takes one cycle to do a release and enter a new sleep state, so a + // double release should be impossible. + assert(!past_stall_release); + end + if (state == S_ASLEEP) begin + assert(allow_power_down || allow_clkgate); + end + end +end +`endif + +// ---------------------------------------------------------------------------- +// Pulse->level for block wakeup + +// Unblock signal is sticky: a prior unblock with no block since will cause +// the next block to immediately fall through. + +always @ (posedge clk_always_on or negedge rst_n) begin + if (!rst_n) begin + block_wakeup_req <= 1'b0; + end else begin + // Note the OR takes precedence over the AND, so we don't miss a second + // unblock that arrives at the instant we wake up. + block_wakeup_req <= (block_wakeup_req && !( + sleeping_on_block && stall_release + )) || block_wakeup_req_pulse; + end +end + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/hazard3_regfile_1w2r.v b/vendor/Hazard3/hdl/hazard3_regfile_1w2r.v new file mode 100644 index 0000000..1bd2379 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_regfile_1w2r.v @@ -0,0 +1,84 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// Register file +// Single write port, dual read port + +`default_nettype none + +module hazard3_regfile_1w2r #( +`include "hazard3_config.vh" +) ( + input wire clk, + input wire rst_n, + + input wire [4:0] raddr1, + output reg [W_DATA-1:0] rdata1, + + input wire [4:0] raddr2, + output reg [W_DATA-1:0] rdata2, + + input wire [4:0] waddr, + input wire [W_DATA-1:0] wdata, + input wire wen +); + +localparam N_REGS = EXTENSION_E == 0 ? 32 : 16; +localparam [4:0] REGNUM_MASK = {~|EXTENSION_E, 4'hf}; + +wire [4:0] raddr1_masked = raddr1 & REGNUM_MASK; +wire [4:0] raddr2_masked = raddr2 & REGNUM_MASK; +wire [4:0] waddr_masked = waddr & REGNUM_MASK; + +generate +if (RESET_REGFILE) begin: real_dualport_reset + // This will presumably always be implemented with flops + reg [W_DATA-1:0] mem [0:N_REGS-1]; + + integer i; + always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + for (i = 0; i < N_REGS; i = i + 1) begin + mem[i] <= {W_DATA{1'b0}}; + end + rdata1 <= {W_DATA{1'b0}}; + rdata2 <= {W_DATA{1'b0}}; + end else begin + if (wen) begin + mem[waddr_masked] <= wdata; + end + rdata1 <= mem[raddr1_masked]; + rdata2 <= mem[raddr2_masked]; + end + end +end else begin: real_dualport_noreset + // This should be inference-compatible on FPGAs with dual-port (or 1R1W) BRAMs + `ifdef YOSYS + `ifdef FPGA_ICE40 + // We do not require write-to-read bypass logic on the BRAM + (* no_rw_check *) + `endif + `endif + // Optionally force use of distributed RAM on Xilinx for better timing + `ifdef HAZARD3_REGFILE_RAM_STYLE_DISTRIBUTED + (* ram_style = "distributed" *) + `endif + reg [W_DATA-1:0] mem [0:N_REGS-1]; + + always @ (posedge clk) begin + if (wen) begin + mem[waddr_masked] <= wdata; + end + rdata1 <= mem[raddr1_masked]; + rdata2 <= mem[raddr2_masked]; + end +end +endgenerate + +endmodule + +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/vendor/Hazard3/hdl/hazard3_rvfi_monitor.vh b/vendor/Hazard3/hdl/hazard3_rvfi_monitor.vh new file mode 100644 index 0000000..15cbe02 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_rvfi_monitor.vh @@ -0,0 +1,377 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2025 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// ---------------------------------------------------------------------------- +// RVFI Instrumentation +// ---------------------------------------------------------------------------- +// To be included into hazard3_core.v for use with riscv-formal. +// Contains some state modelling to diagnose exactly what the core is doing, +// and report this in a way RVFI understands. +// We consider instructions to "retire" as they cross the M/W pipe register. +// +// All modelling signals prefixed with rvfm (riscv-formal monitor) + +// ---------------------------------------------------------------------------- +// Instruction monitor + +// Diagnose whether X, M contain valid in-flight instructions, to produce +// rvfi_valid signal. + +wire rvfm_x_valid = fd_cir_vld >= 2 || (fd_cir_vld >= 1 && fd_cir_raw[1:0] != 2'b11); + +reg rvfm_m_valid; +reg [31:0] rvfm_m_instr; + +wire rvfm_m_trap = xm_except != EXCEPT_NONE && xm_except != EXCEPT_MRET && m_trap_enter_rdy; + +reg rvfi_valid_r; +reg [31:0] rvfi_insn_r; +reg rvfi_trap_r; + +assign rvfi_valid = rvfi_valid_r; +assign rvfi_insn = rvfi_insn_r; +assign rvfi_trap = rvfi_trap_r; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + rvfm_m_valid <= 1'b0; + rvfi_valid_r <= 1'b0; + rvfi_trap_r <= 1'b0; + rvfi_insn_r <= 32'h0; + end else begin + if (!x_stall) begin + // X instruction squashed by any trap, as it's in the branch + // shadow. + rvfm_m_valid <= |df_cir_use && !m_trap_enter_vld; + rvfm_m_instr <= {fd_cir_raw[31:16] & {16{df_cir_use[1]}}, fd_cir_raw[15:0]}; + end else if (!m_stall) begin + rvfm_m_valid <= 1'b0; + end + rvfi_valid_r <= rvfm_m_valid && !m_stall; + // Instructions which experienced fetch faults are reported as + // all-zeroes, per riscv-formal docs. + rvfi_insn_r <= rvfm_m_instr & {32{ + xm_except != EXCEPT_INSTR_FAULT && + xm_except != EXCEPT_INSTR_MISALIGN + }}; + rvfi_trap_r <= rvfm_m_trap; + end +end + +`ifdef HAZARD3_ASSERTIONS +always @ (posedge clk) if (rst_n) begin + // Sanity checks for above + if (d_rd != 5'h0) + assert(rvfm_x_valid); + if (xm_rd != 5'h0) + assert(rvfm_m_valid); +end +`endif + +// Track whether an instruction is the first of an interrupt or exception; +// when a trap happens, a flag is installed in stage X, and once a new +// instruction arrives the flag travels alongside it down to the RVFI port. +reg rvfm_x_intr; +reg rvfm_m_intr; +reg rvfi_intr_r; +always @ (posedge clk) begin + if (!rst_n) begin + rvfm_x_intr <= 1'b0; + rvfm_m_intr <= 1'b0; + rvfi_intr_r <= 1'b0; + end else begin + rvfm_x_intr <= (rvfm_x_intr && (x_stall || d_starved || fd_cir_uop_nonfinal || df_lspair_phase_next)) || + (m_trap_enter_vld && m_trap_enter_rdy); + if (!x_stall) begin + rvfm_m_intr <= rvfm_x_intr; + end + if (!m_stall) begin + rvfi_intr_r <= rvfm_m_intr; + end + end +end + + +// Hazard3 is an in-order core: +reg [63:0] rvfm_retire_ctr; +assign rvfi_order = rvfm_retire_ctr; +always @ (posedge clk or negedge rst_n) + if (!rst_n) + rvfm_retire_ctr <= 0; + else if (rvfi_valid) + rvfm_retire_ctr <= rvfm_retire_ctr + 1; + +assign rvfi_intr = rvfi_intr_r && rvfi_valid; + +// ---------------------------------------------------------------------------- +// PC and jump monitor + +reg [31:0] rvfm_xm_pc; +reg [31:0] rvfm_xm_pc_next; + +// Record a jump target that was issued while stalled +reg rvfm_x_saw_f_jump; +reg [31:0] rvfm_x_saw_f_jump_target; +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + rvfm_x_saw_f_jump <= 1'b0; + rvfm_x_saw_f_jump_target <= 32'd0; + end else if (f_jump_now && !(m_trap_enter_vld && m_trap_enter_rdy) && (x_stall || (fd_cir_is_uop && fd_cir_uop_nonfinal))) begin + // Record fetch address issued by instruction. Note this case is gated + // on m_trap_enter_vld && m_trap_enter_rdy (not just vld). If + // !m_trap_enter_rdy it is still possible to get a new fetch address + // in the following case: + // + // * M instr is a load/store (in dphase) + // + // * IRQ is asserted, but blocked by the load/store dphase to avoid + // trashing exception PC of a potential data-phase bus fault + // + // * X instr is a jump, which stalls due to the IRQ assertion + // + // * X instr's PC goes through to frontend during stall (and would be + // flushed if the IRQ went through) because stall cannot gate fetch + // address request to avoid AHB through-path. + // + // * IRQ's trap address is not immediately accepted by frontend due to + // address-phase stall on issuing jump instr's address + // + // * IRQ deasserts on the next cycle, so its trap address is not accepted. + rvfm_x_saw_f_jump <= 1'b1; + rvfm_x_saw_f_jump_target <= f_jump_target; + end else if (!x_stall && !(fd_cir_is_uop && fd_cir_uop_nonfinal)) begin + rvfm_x_saw_f_jump <= 1'b0; + end else if (m_trap_enter_vld && m_trap_enter_rdy) begin + // E.g. trap during uop sequence + rvfm_x_saw_f_jump <= 1'b0; + end +end + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + rvfm_xm_pc <= 0; + rvfm_xm_pc_next <= 0; + end else begin + if (!x_stall) begin + // For cm.popret and cm.popretz the PC actually changes on the + // penultimate uop; ignore this and retain the initial PC. Abuse + // knowledge that the PC update is always in the atomic section, + // and the first instruction is always interruptible. + if (!(fd_cir_is_uop && fd_cir_uop_atomic)) begin + rvfm_xm_pc <= d_pc; + end + rvfm_xm_pc_next <= + f_jump_now ? f_jump_target : + rvfm_x_saw_f_jump ? rvfm_x_saw_f_jump_target : + d_pc + (fd_cir_raw[1:0] == 2'b11 ? 32'h4 : 32'h2); + end + end +end + +reg [31:0] rvfi_pc_rdata_r; +reg [31:0] rvfi_pc_wdata_r; + +assign rvfi_pc_rdata = rvfi_pc_rdata_r; +assign rvfi_pc_wdata = rvfi_pc_wdata_r; + +always @ (posedge clk) begin + if (!m_stall) begin + rvfi_pc_rdata_r <= rvfm_xm_pc; + rvfi_pc_wdata_r <= + m_trap_enter_vld && m_trap_enter_rdy && xm_except != EXCEPT_NONE ? + m_trap_addr : rvfm_xm_pc_next; + end +end + +// ---------------------------------------------------------------------------- +// Register file monitor: + +// When writeback is suppressed due to trap, the previous instruction is left +// in the writeback buffer (and can be re-bypassed from there). Make sure not +// to report this as a writeback on RVFI. +reg rvfm_writeback_mask; +always @ (posedge clk) begin + if (!m_stall) begin + rvfm_writeback_mask <= m_reg_wen_if_nonzero; + end +end + +assign rvfi_rd_addr = mw_rd & {5{rvfm_writeback_mask}}; +assign rvfi_rd_wdata = |mw_rd && rvfm_writeback_mask ? mw_result : 32'h0; + +// Do not reimplement internal bypassing logic. Danger of implementing +// it correctly here but incorrectly in core. + +reg [31:0] rvfm_xm_rdata1; +reg [31:0] rvfm_xm_rdata2; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + rvfm_xm_rdata1 <= 32'h0; + rvfm_xm_rdata2 <= 32'h0; + end else if (!x_stall) begin + // rs*_bypass may have garbage on them for instructions with *no* + // register operands (due to some interesting optimisations), though + // d_rs* is still driven to 0 to disable stalling on that register + // lane. riscv-formal still likes to see zeroes from x0, so fix that + // up here. This shouldn't cover up any bugs, since a + // register-operand instruction would still *use* the garbage value. + rvfm_xm_rdata1 <= |d_rs1 ? x_rs1_bypass : 32'h0; + rvfm_xm_rdata2 <= |d_rs2 ? x_rs2_bypass : 32'h0; + end +end + +reg [4:0] rvfi_rs1_addr_r; +reg [4:0] rvfi_rs2_addr_r; +reg [31:0] rvfi_rs1_rdata_r; +reg [31:0] rvfi_rs2_rdata_r; + +assign rvfi_rs1_addr = rvfi_rs1_addr_r; +assign rvfi_rs2_addr = rvfi_rs2_addr_r; +assign rvfi_rs1_rdata = rvfi_rs1_rdata_r; +assign rvfi_rs2_rdata = rvfi_rs2_rdata_r; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + rvfi_rs1_addr_r <= 5'h0; + rvfi_rs2_addr_r <= 5'h0; + rvfi_rs1_rdata_r <= 32'h0; + rvfi_rs2_rdata_r <= 32'h0; + end else begin + rvfi_rs1_addr_r <= m_stall ? 5'h0 : xm_rs1; + rvfi_rs2_addr_r <= m_stall ? 5'h0 : xm_rs2; + rvfi_rs1_rdata_r <= rvfm_xm_rdata1; + rvfi_rs2_rdata_r <= xm_rs2 == mw_rd && |xm_rs2 ? m_wdata : rvfm_xm_rdata2; + end +end + +// ---------------------------------------------------------------------------- +// Load/store monitor: based on bus signals, NOT processor internals. +// Marshal up a description of the current data phase, and then register this +// into the RVFI signals. + +`ifdef HAZARD3_ASSERTIONS +`ifndef RISCV_FORMAL_ALIGNED_MEM +initial $fatal; +`endif +`endif + +reg [31:0] rvfm_haddr_dph; +reg rvfm_hwrite_dph; +reg [1:0] rvfm_htrans_dph; +reg [2:0] rvfm_hsize_dph; + +always @ (posedge clk) begin + if (bus_aph_ready_d) begin + rvfm_htrans_dph <= {bus_aph_req_d, 1'b0}; + rvfm_haddr_dph <= bus_haddr_d; + rvfm_hwrite_dph <= bus_hwrite_d; + rvfm_hsize_dph <= bus_hsize_d; + end +end + +wire [3:0] rvfm_mem_bytemask_dph = ( + rvfm_hsize_dph == 3'h0 ? 4'h1 : + rvfm_hsize_dph == 3'h1 ? 4'h3 : + 4'hf + ) << rvfm_haddr_dph[1:0]; + +reg [31:0] rvfi_mem_addr_r; +reg [3:0] rvfi_mem_rmask_r; +reg [31:0] rvfi_mem_rdata_r; +reg [3:0] rvfi_mem_wmask_r; +reg [31:0] rvfi_mem_wdata_r; +reg rvfi_mem_fault_r; +// May have to hold the strobes for multiple cycles following a bus +// fault, as the trap entry may not go through immediately (depending +// on instruction-side bus stall): +reg rvfm_mem_hold; + +assign rvfi_mem_addr = rvfi_mem_addr_r; +assign rvfi_mem_rdata = rvfi_mem_rdata_r; +assign rvfi_mem_wdata = rvfi_mem_wdata_r; + +assign rvfi_mem_fault = rvfi_mem_fault_r; +assign rvfi_mem_wmask = rvfi_mem_wmask_r & {4{!rvfi_mem_fault_r}}; +assign rvfi_mem_rmask = rvfi_mem_rmask_r & {4{!rvfi_mem_fault_r}}; +assign rvfi_mem_fault_rmask = rvfi_mem_rmask_r & {4{ rvfi_mem_fault_r}}; +assign rvfi_mem_fault_wmask = rvfi_mem_wmask_r & {4{ rvfi_mem_fault_r}}; + +always @ (posedge clk) begin + rvfm_mem_hold <= (rvfm_mem_hold || (rvfm_htrans_dph && bus_dph_ready_d)) && m_stall; + if (xm_memop == MEMOP_AMO) begin + // AMO has completed in stage X. Progressing to stage M without MEMOP + // going to NONE then there has been no trap, therefore no stall, + // therefore no time for another address to have issued: + assert(!m_stall); + rvfi_mem_addr_r <= rvfm_haddr_dph; + // Always 32-bit, always both read and write: + rvfi_mem_rmask_r <= 4'hf; + rvfi_mem_wmask_r <= 4'hf; + // Has been juggled since the read that matched the winning write: + rvfi_mem_rdata_r <= xm_result; + // Incidentally captured on previous cycle: + rvfi_mem_wdata_r <= rvfi_mem_wdata_r; + end else if (bus_dph_ready_d) begin + // RVFI has an AXI-like concept of byte strobes, rather than AHB-like + rvfi_mem_addr_r <= rvfm_haddr_dph & 32'hffff_fffc; + {rvfi_mem_rmask_r, rvfi_mem_wmask_r} <= 0; + if (rvfm_htrans_dph[1] && rvfm_hwrite_dph) begin + rvfi_mem_wmask_r <= rvfm_mem_bytemask_dph; + rvfi_mem_wdata_r <= bus_wdata_d; + end else if (rvfm_htrans_dph[1] && !rvfm_hwrite_dph) begin + rvfi_mem_rmask_r <= rvfm_mem_bytemask_dph; + rvfi_mem_rdata_r <= bus_rdata_d; + end + rvfi_mem_fault_r <= bus_dph_err_d; + end else if (!rvfm_mem_hold) begin + rvfi_mem_rmask_r <= 4'h0; + rvfi_mem_wmask_r <= 4'h0; + rvfi_mem_fault_r <= 1'b0; + end + // Also need to report rvfi_mem_fault on a fetch fault + if (xm_except == EXCEPT_INSTR_FAULT && m_trap_enter_vld && m_trap_enter_rdy) begin + rvfi_mem_fault_r <= 1'b1; + end +end + +// ---------------------------------------------------------------------------- +// Constraints + +// Trying to keep internal constraints to a minimum. + +// Limit sleep duration for liveness checks +// TODO is it possible to do this in a way that doesn't assume the wakeup logic is functional? +`ifdef RISCV_FORMAL_FAIRNESS +reg [7:0] rvfm_sleep_counter; +always @ (posedge clk) begin + if (!rst_n) begin + rvfm_sleep_counter <= 8'd00; + end else if (xm_sleep_wfi || xm_sleep_block) begin + rvfm_sleep_counter <= rvfm_sleep_counter + 8'h01; + assume(rvfm_sleep_counter < 8'd5); + end else begin + rvfm_sleep_counter <= 8'd00; + end +end +`endif + +// ---------------------------------------------------------------------------- +// Tie-offs + +// Note: Hazard3 does not have any instructions which irrversibly halt +// execution. For the liveness check (RISCV_FORMAL_FAIRNESS is defined), +// length of stalls is constrained and WFI is assumed to wake immediately +// after going to sleep. +assign rvfi_halt = 1'b0; + +// Note: this always reports M-mode, which is not correct if the U_MODE config +// is set. However no riscv-formal checks currently use this signal. +assign rvfi_mode = 2'h3; + +// Maximum XLEN is always 32 bits +assign rvfi_ixl = 2'h1; + + diff --git a/vendor/Hazard3/hdl/hazard3_rvfi_standalone_defs.vh b/vendor/Hazard3/hdl/hazard3_rvfi_standalone_defs.vh new file mode 100644 index 0000000..58f1bb9 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_rvfi_standalone_defs.vh @@ -0,0 +1,96 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2025 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// Macros for building Hazard3 with RVFI trace port outside of the +// riscv-formal test harness. For example, when including the trace port in a +// synthesised processor. +`ifdef HAZARD3_RVFI_STANDALONE + +`define RISCV_FORMAL +`define RISCV_FORMAL_NRET 1 +`define RISCV_FORMAL_XLEN 32 +`define RISCV_FORMAL_ILEN 32 +`define RISCV_FORMAL_MEM_FAULT +`define RISCV_FORMAL_ALIGNED_MEM + +`define RVFI_OUTPUTS \ + output wire rvfi_valid, \ + output wire [63:0] rvfi_order, \ + output wire [31:0] rvfi_insn, \ + output wire rvfi_trap, \ + output wire rvfi_halt, \ + output wire rvfi_intr, \ + output wire [1:0] rvfi_mode, \ + output wire [1:0] rvfi_ixl, \ + output wire [4:0] rvfi_rs1_addr, \ + output wire [4:0] rvfi_rs2_addr, \ + output wire [31:0] rvfi_rs1_rdata, \ + output wire [31:0] rvfi_rs2_rdata, \ + output wire [4:0] rvfi_rd_addr, \ + output wire [31:0] rvfi_rd_wdata, \ + output wire [31:0] rvfi_pc_rdata, \ + output wire [31:0] rvfi_pc_wdata, \ + output wire [31:0] rvfi_mem_addr, \ + output wire [3:0] rvfi_mem_rmask, \ + output wire [3:0] rvfi_mem_wmask, \ + output wire [31:0] rvfi_mem_rdata, \ + output wire [31:0] rvfi_mem_wdata, \ + output wire rvfi_mem_fault, \ + output wire [3:0] rvfi_mem_fault_rmask, \ + output wire [3:0] rvfi_mem_fault_wmask + +`define RVFI_WIRES \ + wire rvfi_valid; \ + wire [63:0] rvfi_order; \ + wire [31:0] rvfi_insn; \ + wire rvfi_trap; \ + wire rvfi_halt; \ + wire rvfi_intr; \ + wire [1:0] rvfi_mode; \ + wire [1:0] rvfi_ixl; \ + wire [4:0] rvfi_rs1_addr; \ + wire [4:0] rvfi_rs2_addr; \ + wire [31:0] rvfi_rs1_rdata; \ + wire [31:0] rvfi_rs2_rdata; \ + wire [4:0] rvfi_rd_addr; \ + wire [31:0] rvfi_rd_wdata; \ + wire [31:0] rvfi_pc_rdata; \ + wire [31:0] rvfi_pc_wdata; \ + wire [31:0] rvfi_mem_addr; \ + wire [3:0] rvfi_mem_rmask; \ + wire [3:0] rvfi_mem_wmask; \ + wire [31:0] rvfi_mem_rdata; \ + wire [31:0] rvfi_mem_wdata; \ + wire rvfi_mem_fault; \ + wire [3:0] rvfi_mem_fault_rmask; \ + wire [3:0] rvfi_mem_fault_wmask; + +`define RVFI_CONN \ + .rvfi_valid (rvfi_valid), \ + .rvfi_order (rvfi_order), \ + .rvfi_insn (rvfi_insn), \ + .rvfi_trap (rvfi_trap), \ + .rvfi_halt (rvfi_halt), \ + .rvfi_intr (rvfi_intr), \ + .rvfi_mode (rvfi_mode), \ + .rvfi_ixl (rvfi_ixl), \ + .rvfi_rs1_addr (rvfi_rs1_addr), \ + .rvfi_rs2_addr (rvfi_rs2_addr), \ + .rvfi_rs1_rdata (rvfi_rs1_rdata), \ + .rvfi_rs2_rdata (rvfi_rs2_rdata), \ + .rvfi_rd_addr (rvfi_rd_addr), \ + .rvfi_rd_wdata (rvfi_rd_wdata), \ + .rvfi_pc_rdata (rvfi_pc_rdata), \ + .rvfi_pc_wdata (rvfi_pc_wdata), \ + .rvfi_mem_addr (rvfi_mem_addr), \ + .rvfi_mem_rmask (rvfi_mem_rmask), \ + .rvfi_mem_wmask (rvfi_mem_wmask), \ + .rvfi_mem_rdata (rvfi_mem_rdata), \ + .rvfi_mem_wdata (rvfi_mem_wdata), \ + .rvfi_mem_fault (rvfi_mem_fault), \ + .rvfi_mem_fault_rmask (rvfi_mem_fault_rmask), \ + .rvfi_mem_fault_wmask (rvfi_mem_fault_wmask) + + `endif diff --git a/vendor/Hazard3/hdl/hazard3_triggers.v b/vendor/Hazard3/hdl/hazard3_triggers.v new file mode 100644 index 0000000..0dff196 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_triggers.v @@ -0,0 +1,499 @@ +/*****************************************************************************\ +| 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 diff --git a/vendor/Hazard3/hdl/hazard3_width_const.vh b/vendor/Hazard3/hdl/hazard3_width_const.vh new file mode 100644 index 0000000..9b838a0 --- /dev/null +++ b/vendor/Hazard3/hdl/hazard3_width_const.vh @@ -0,0 +1,20 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +// These really ought to be localparams, but are occasionally needed for +// passing flags around between modules, so are made available as parameters +// instead. It's ugly, but better scope hygiene than the preprocessor. These +// parameters should not be changed from their default values. + +parameter W_REGADDR = 5, + +parameter W_ALUOP = 6, +parameter W_ALUSRC = 1, +parameter W_MEMOP = 5, +parameter W_BCOND = 2, +parameter W_SHAMT = 5, + +parameter W_EXCEPT = 4, +parameter W_MULOP = 3 diff --git a/vendor/Hazard3/hdl/rv_opcodes.vh b/vendor/Hazard3/hdl/rv_opcodes.vh new file mode 100644 index 0000000..9582338 --- /dev/null +++ b/vendor/Hazard3/hdl/rv_opcodes.vh @@ -0,0 +1,276 @@ +/*****************************************************************************\ +| Copyright (C) 2021-2022 Luke Wren | +| SPDX-License-Identifier: Apache-2.0 | +\*****************************************************************************/ + +localparam RV_RS1_LSB = 15; +localparam RV_RS1_BITS = 5; +localparam RV_RS2_LSB = 20; +localparam RV_RS2_BITS = 5; +localparam RV_RD_LSB = 7; +localparam RV_RD_BITS = 5; + +// Note: these are preprocessor macros, rather than the usual localparams, +// because it's quite difficult to get a definitive citation from 1364-2005 +// for whether Z values are propagated through a localparam to a casez. +// Multiple tools complain about it, so just this once I'll use macros. + +`ifndef HAZARD3_RVOPC_MACROS +`define HAZARD3_RVOPC_MACROS + +// Base ISA (some of these are Z now) +`define RVOPC_BEQ 32'b?????????????????000?????1100011 +`define RVOPC_BNE 32'b?????????????????001?????1100011 +`define RVOPC_BLT 32'b?????????????????100?????1100011 +`define RVOPC_BGE 32'b?????????????????101?????1100011 +`define RVOPC_BLTU 32'b?????????????????110?????1100011 +`define RVOPC_BGEU 32'b?????????????????111?????1100011 +`define RVOPC_JALR 32'b?????????????????000?????1100111 +`define RVOPC_JAL 32'b?????????????????????????1101111 +`define RVOPC_LUI 32'b?????????????????????????0110111 +`define RVOPC_AUIPC 32'b?????????????????????????0010111 +`define RVOPC_ADDI 32'b?????????????????000?????0010011 +`define RVOPC_SLLI 32'b0000000??????????001?????0010011 +`define RVOPC_SLTI 32'b?????????????????010?????0010011 +`define RVOPC_SLTIU 32'b?????????????????011?????0010011 +`define RVOPC_XORI 32'b?????????????????100?????0010011 +`define RVOPC_SRLI 32'b0000000??????????101?????0010011 +`define RVOPC_SRAI 32'b0100000??????????101?????0010011 +`define RVOPC_ORI 32'b?????????????????110?????0010011 +`define RVOPC_ANDI 32'b?????????????????111?????0010011 +`define RVOPC_ADD 32'b0000000??????????000?????0110011 +`define RVOPC_SUB 32'b0100000??????????000?????0110011 +`define RVOPC_SLL 32'b0000000??????????001?????0110011 +`define RVOPC_SLT 32'b0000000??????????010?????0110011 +`define RVOPC_SLTU 32'b0000000??????????011?????0110011 +`define RVOPC_XOR 32'b0000000??????????100?????0110011 +`define RVOPC_SRL 32'b0000000??????????101?????0110011 +`define RVOPC_SRA 32'b0100000??????????101?????0110011 +`define RVOPC_OR 32'b0000000??????????110?????0110011 +`define RVOPC_AND 32'b0000000??????????111?????0110011 +`define RVOPC_LB 32'b?????????????????000?????0000011 +`define RVOPC_LH 32'b?????????????????001?????0000011 +`define RVOPC_LW 32'b?????????????????010?????0000011 +`define RVOPC_LBU 32'b?????????????????100?????0000011 +`define RVOPC_LHU 32'b?????????????????101?????0000011 +`define RVOPC_SB 32'b?????????????????000?????0100011 +`define RVOPC_SH 32'b?????????????????001?????0100011 +`define RVOPC_SW 32'b?????????????????010?????0100011 +`define RVOPC_FENCE 32'b????????????00000000000000001111 +`define RVOPC_FENCE_I 32'b00000000000000000001000000001111 +`define RVOPC_ECALL 32'b00000000000000000000000001110011 +`define RVOPC_EBREAK 32'b00000000000100000000000001110011 +`define RVOPC_CSRRW 32'b?????????????????001?????1110011 +`define RVOPC_CSRRS 32'b?????????????????010?????1110011 +`define RVOPC_CSRRC 32'b?????????????????011?????1110011 +`define RVOPC_CSRRWI 32'b?????????????????101?????1110011 +`define RVOPC_CSRRSI 32'b?????????????????110?????1110011 +`define RVOPC_CSRRCI 32'b?????????????????111?????1110011 +`define RVOPC_MRET 32'b00110000001000000000000001110011 +`define RVOPC_SYSTEM 32'b?????????????????????????1110011 +`define RVOPC_WFI 32'b00010000010100000000000001110011 + +// M extension +`define RVOPC_MUL 32'b0000001??????????000?????0110011 +`define RVOPC_MULH 32'b0000001??????????001?????0110011 +`define RVOPC_MULHSU 32'b0000001??????????010?????0110011 +`define RVOPC_MULHU 32'b0000001??????????011?????0110011 +`define RVOPC_DIV 32'b0000001??????????100?????0110011 +`define RVOPC_DIVU 32'b0000001??????????101?????0110011 +`define RVOPC_REM 32'b0000001??????????110?????0110011 +`define RVOPC_REMU 32'b0000001??????????111?????0110011 + +// A extension +`define RVOPC_LR_W 32'b00010??00000?????010?????0101111 +`define RVOPC_SC_W 32'b00011????????????010?????0101111 +`define RVOPC_AMOSWAP_W 32'b00001????????????010?????0101111 +`define RVOPC_AMOADD_W 32'b00000????????????010?????0101111 +`define RVOPC_AMOXOR_W 32'b00100????????????010?????0101111 +`define RVOPC_AMOAND_W 32'b01100????????????010?????0101111 +`define RVOPC_AMOOR_W 32'b01000????????????010?????0101111 +`define RVOPC_AMOMIN_W 32'b10000????????????010?????0101111 +`define RVOPC_AMOMAX_W 32'b10100????????????010?????0101111 +`define RVOPC_AMOMINU_W 32'b11000????????????010?????0101111 +`define RVOPC_AMOMAXU_W 32'b11100????????????010?????0101111 + +// Zba (address generation) +`define RVOPC_SH1ADD 32'b0010000??????????010?????0110011 +`define RVOPC_SH2ADD 32'b0010000??????????100?????0110011 +`define RVOPC_SH3ADD 32'b0010000??????????110?????0110011 + +// Zbb (basic bit manipulation) +`define RVOPC_ANDN 32'b0100000??????????111?????0110011 +`define RVOPC_CLZ 32'b011000000000?????001?????0010011 +`define RVOPC_CPOP 32'b011000000010?????001?????0010011 +`define RVOPC_CTZ 32'b011000000001?????001?????0010011 +`define RVOPC_MAX 32'b0000101??????????110?????0110011 +`define RVOPC_MAXU 32'b0000101??????????111?????0110011 +`define RVOPC_MIN 32'b0000101??????????100?????0110011 +`define RVOPC_MINU 32'b0000101??????????101?????0110011 +`define RVOPC_ORC_B 32'b001010000111?????101?????0010011 +`define RVOPC_ORN 32'b0100000??????????110?????0110011 +`define RVOPC_REV8 32'b011010011000?????101?????0010011 +`define RVOPC_ROL 32'b0110000??????????001?????0110011 +`define RVOPC_ROR 32'b0110000??????????101?????0110011 +`define RVOPC_RORI 32'b0110000??????????101?????0010011 +`define RVOPC_SEXT_B 32'b011000000100?????001?????0010011 +`define RVOPC_SEXT_H 32'b011000000101?????001?????0010011 +`define RVOPC_XNOR 32'b0100000??????????100?????0110011 +`define RVOPC_ZEXT_H 32'b000010000000?????100?????0110011 + +// Zbc (carry-less multiply) +`define RVOPC_CLMUL 32'b0000101??????????001?????0110011 +`define RVOPC_CLMULH 32'b0000101??????????011?????0110011 +`define RVOPC_CLMULR 32'b0000101??????????010?????0110011 + +// Zbs (single-bit manipulation) +`define RVOPC_BCLR 32'b0100100??????????001?????0110011 +`define RVOPC_BCLRI 32'b0100100??????????001?????0010011 +`define RVOPC_BEXT 32'b0100100??????????101?????0110011 +`define RVOPC_BEXTI 32'b0100100??????????101?????0010011 +`define RVOPC_BINV 32'b0110100??????????001?????0110011 +`define RVOPC_BINVI 32'b0110100??????????001?????0010011 +`define RVOPC_BSET 32'b0010100??????????001?????0110011 +`define RVOPC_BSETI 32'b0010100??????????001?????0010011 + +// Zbkb (basic bit manipulation for crypto) (minus those in Zbb) +`define RVOPC_PACK 32'b0000100??????????100?????0110011 +`define RVOPC_PACKH 32'b0000100??????????111?????0110011 +`define RVOPC_BREV8 32'b011010000111?????101?????0010011 +`define RVOPC_UNZIP 32'b000010001111?????101?????0010011 +`define RVOPC_ZIP 32'b000010001111?????001?????0010011 + +// Zbkc is a subset of Zbc. + +// Zbkx (crossbar permutation) +`define RVOPC_XPERM8 32'b0010100??????????100?????0110011 +`define RVOPC_XPERM4 32'b0010100??????????010?????0110011 + +// Zilsd (load/store pair) +`define RVOPC_LD 32'b?????????????????011????00000011 // rd[0] == 0 +`define RVOPC_SD 32'b???????????0?????011?????0100011 // rs2[0] == 0 + +// Hazard3 custom instructions + +// Xh3bextm (Hazard3 multi-bit extract): multi-bit versions of bext/bexti from Zbs +`define RVOPC_H3_BEXTM 32'b000???0??????????000?????0001011 // custom-0 funct3=0 +`define RVOPC_H3_BEXTMI 32'b000???0??????????100?????0001011 // custom-0 funct3=4 + +// C Extension +`define RVOPC_C_ADDI4SPN 16'b000???????????00 // *** illegal if imm 0 +`define RVOPC_C_LW 16'b010???????????00 +`define RVOPC_C_SW 16'b110???????????00 + +`define RVOPC_C_ADDI 16'b000???????????01 +`define RVOPC_C_JAL 16'b001???????????01 +`define RVOPC_C_J 16'b101???????????01 +`define RVOPC_C_LI 16'b010???????????01 +// addi16sp when rd=2: +`define RVOPC_C_LUI 16'b011???????????01 // *** reserved if imm 0 (for both LUI and ADDI16SP) +`define RVOPC_C_SRLI 16'b100000????????01 // On RV32 imm[5] (instr[12]) must be 0, else reserved NSE. +`define RVOPC_C_SRAI 16'b100001????????01 // On RV32 imm[5] (instr[12]) must be 0, else reserved NSE. +`define RVOPC_C_ANDI 16'b100?10????????01 +`define RVOPC_C_SUB 16'b100011???00???01 +`define RVOPC_C_XOR 16'b100011???01???01 +`define RVOPC_C_OR 16'b100011???10???01 +`define RVOPC_C_AND 16'b100011???11???01 +`define RVOPC_C_BEQZ 16'b110???????????01 +`define RVOPC_C_BNEZ 16'b111???????????01 + +`define RVOPC_C_SLLI 16'b0000??????????10 // On RV32 imm[5] (instr[12]) must be 0, else reserved NSE. +// jr if !rs2: +`define RVOPC_C_MV 16'b1000??????????10 // *** reserved if JR and !rs1 (instr[11:7]) +// jalr if !rs2: +`define RVOPC_C_ADD 16'b1001??????????10 // *** EBREAK if !instr[11:2] +`define RVOPC_C_LWSP 16'b010???????????10 // *** reserved if rd=x0 +`define RVOPC_C_SWSP 16'b110???????????10 + +// Zcb simple additional compressed instructions +`define RVOPC_C_LBU 16'b100000????????00 +`define RVOPC_C_LHU 16'b100001???0????00 +`define RVOPC_C_LH 16'b100001???1????00 +`define RVOPC_C_SB 16'b100010????????00 +`define RVOPC_C_SH 16'b100011???0????00 +`define RVOPC_C_ZEXT_B 16'b100111???1100001 +`define RVOPC_C_SEXT_B 16'b100111???1100101 +`define RVOPC_C_ZEXT_H 16'b100111???1101001 +`define RVOPC_C_SEXT_H 16'b100111???1101101 +`define RVOPC_C_NOT 16'b100111???1110101 +`define RVOPC_C_MUL 16'b100111???10???01 + +// Zclsd load/store pair instructions +`define RVOPC_C_LD 16'b011??????????000 // rd[0] == 0 +`define RVOPC_C_LDSP 16'b011?????0?????10 // rd[0] == 0 +`define RVOPC_C_SD 16'b111??????????000 // rs2[0] == 0 +`define RVOPC_C_SDSP 16'b111??????????010 // rs2[0] == 0 + +// Zcmp push/pop instructions +`define RVOPC_CM_PUSH 16'b10111000??????10 +`define RVOPC_CM_POP 16'b10111010??????10 +`define RVOPC_CM_POPRETZ 16'b10111100??????10 +`define RVOPC_CM_POPRET 16'b10111110??????10 +`define RVOPC_CM_MVSA01 16'b101011???01???10 +`define RVOPC_CM_MVA01S 16'b101011???11???10 + +// Copies provided here with 0 instead of ? so that these can be used to build 32-bit instructions in the decompressor + +`define RVOPC_NOZ_BEQ 32'b00000000000000000000000001100011 +`define RVOPC_NOZ_BNE 32'b00000000000000000001000001100011 +`define RVOPC_NOZ_BLT 32'b00000000000000000100000001100011 +`define RVOPC_NOZ_BGE 32'b00000000000000000101000001100011 +`define RVOPC_NOZ_BLTU 32'b00000000000000000110000001100011 +`define RVOPC_NOZ_BGEU 32'b00000000000000000111000001100011 +`define RVOPC_NOZ_JALR 32'b00000000000000000000000001100111 +`define RVOPC_NOZ_JAL 32'b00000000000000000000000001101111 +`define RVOPC_NOZ_LUI 32'b00000000000000000000000000110111 +`define RVOPC_NOZ_AUIPC 32'b00000000000000000000000000010111 +`define RVOPC_NOZ_ADDI 32'b00000000000000000000000000010011 +`define RVOPC_NOZ_SLLI 32'b00000000000000000001000000010011 +`define RVOPC_NOZ_SLTI 32'b00000000000000000010000000010011 +`define RVOPC_NOZ_SLTIU 32'b00000000000000000011000000010011 +`define RVOPC_NOZ_XORI 32'b00000000000000000100000000010011 +`define RVOPC_NOZ_SRLI 32'b00000000000000000101000000010011 +`define RVOPC_NOZ_SRAI 32'b01000000000000000101000000010011 +`define RVOPC_NOZ_ORI 32'b00000000000000000110000000010011 +`define RVOPC_NOZ_ANDI 32'b00000000000000000111000000010011 +`define RVOPC_NOZ_ADD 32'b00000000000000000000000000110011 +`define RVOPC_NOZ_SUB 32'b01000000000000000000000000110011 +`define RVOPC_NOZ_SLL 32'b00000000000000000001000000110011 +`define RVOPC_NOZ_SLT 32'b00000000000000000010000000110011 +`define RVOPC_NOZ_SLTU 32'b00000000000000000011000000110011 +`define RVOPC_NOZ_XOR 32'b00000000000000000100000000110011 +`define RVOPC_NOZ_SRL 32'b00000000000000000101000000110011 +`define RVOPC_NOZ_SRA 32'b01000000000000000101000000110011 +`define RVOPC_NOZ_OR 32'b00000000000000000110000000110011 +`define RVOPC_NOZ_AND 32'b00000000000000000111000000110011 +`define RVOPC_NOZ_LB 32'b00000000000000000000000000000011 +`define RVOPC_NOZ_LH 32'b00000000000000000001000000000011 +`define RVOPC_NOZ_LW 32'b00000000000000000010000000000011 +`define RVOPC_NOZ_LBU 32'b00000000000000000100000000000011 +`define RVOPC_NOZ_LHU 32'b00000000000000000101000000000011 +`define RVOPC_NOZ_SB 32'b00000000000000000000000000100011 +`define RVOPC_NOZ_SH 32'b00000000000000000001000000100011 +`define RVOPC_NOZ_SW 32'b00000000000000000010000000100011 +`define RVOPC_NOZ_FENCE 32'b00000000000000000000000000001111 +`define RVOPC_NOZ_FENCE_I 32'b00000000000000000001000000001111 +`define RVOPC_NOZ_ECALL 32'b00000000000000000000000001110011 +`define RVOPC_NOZ_EBREAK 32'b00000000000100000000000001110011 +`define RVOPC_NOZ_CSRRW 32'b00000000000000000001000001110011 +`define RVOPC_NOZ_CSRRS 32'b00000000000000000010000001110011 +`define RVOPC_NOZ_CSRRC 32'b00000000000000000011000001110011 +`define RVOPC_NOZ_CSRRWI 32'b00000000000000000101000001110011 +`define RVOPC_NOZ_CSRRSI 32'b00000000000000000110000001110011 +`define RVOPC_NOZ_CSRRCI 32'b00000000000000000111000001110011 +`define RVOPC_NOZ_SYSTEM 32'b00000000000000000000000001110011 + +// Non-RV32I instructions for Zcb: +`define RVOPC_NOZ_MUL 32'b00000010000000000000000000110011 +`define RVOPC_NOZ_SEXT_B 32'b01100000010000000001000000010011 +`define RVOPC_NOZ_SEXT_H 32'b01100000010100000001000000010011 +`define RVOPC_NOZ_ZEXT_H 32'b00001000000000000100000000110011 + +// Non-RV32I instructions for Zclsd: +`define RVOPC_NOZ_LD 32'b00000000000000000011000000000011 +`define RVOPC_NOZ_SD 32'b00000000000000000011000000100011 + +`endif diff --git a/vendor/Hazard3/project_paths.mk b/vendor/Hazard3/project_paths.mk new file mode 100644 index 0000000..6d50d69 --- /dev/null +++ b/vendor/Hazard3/project_paths.mk @@ -0,0 +1,7 @@ +# Set up root paths used by Makefiles (there is a lot of cross-referencing, +# e.g. tests referencing the HDL directory). This .mk file is +# (eventually) included by every Makefile in the project. + +PROJ_ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +HDL := $(PROJ_ROOT)/hdl +SCRIPTS := $(PROJ_ROOT)/scripts diff --git a/vendor/Hazard3/scripts/Readme.md b/vendor/Hazard3/scripts/Readme.md new file mode 100644 index 0000000..95ad64a --- /dev/null +++ b/vendor/Hazard3/scripts/Readme.md @@ -0,0 +1,6 @@ +FPGA Scripts +============ + +A loose collection of scripts I've collected while playing with FPGAs at home. Simulation, synthesis, so forth. All of them are terrible, some of them work. + +The most useful one is `listfiles` which provides a simple way of describing source-level dependencies and producing file lists for e.g. a synthesis tool. diff --git a/vendor/Hazard3/scripts/ckcompress b/vendor/Hazard3/scripts/ckcompress new file mode 100755 index 0000000..058b632 --- /dev/null +++ b/vendor/Hazard3/scripts/ckcompress @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 + +# Based on Commander Keen compresssion algorithm (a variant of LZSS) + +import sys +import argparse + +def get_symbol(data, window): + it = iter(data) + needle = bytearray([next(it)]) + try: + while window.find(needle) >= 0: + needle.append(next(it)) + except StopIteration: + pass + + matchlen = len(needle) - 1 + if matchlen <= 1: + return (1, (False, needle[0])) + else: + matchpos = len(window) - 1 - window.find(needle[:-1]) + return (matchlen, (True, matchpos, matchlen - 1)) + +def iter_symbols(data, windowsize): + data = data[:] + window = bytearray() + + while True: + consumed, symbol = get_symbol(data, window) + yield symbol + del window[:max(0, len(window) + consumed - windowsize)] + window.extend(data[:consumed]) + del data[:consumed] + +def bitmap(bits): + bm = 0 + for i, bit in enumerate(bits): + bm = bm | (bool(bit) << i) + return bm + +def iter_bytes(data, windowsize): + syms = iter(iter_symbols(data, windowsize)) + holding = [] + eof = False + while not eof: + try: + holding.append(next(syms)) + except StopIteration: + eof = True + if eof and len(holding) or len(holding) >= 8: + yield bitmap(sym[0] for sym in holding) + for sym in holding: + yield from sym[1:] + holding.clear() + +def compress(data, windowsize): + return bytearray(iter_bytes(data, windowsize)) + +def decompress(cdata, windowsize): + out = bytearray() + window = bytearray() + symbolcount = 0 + it = iter(cdata) + while True: + if symbolcount == 0: + try: + bitmap = next(it) + except StopIteration: + break + symbolcount = 7 + else: + symbolcount -= 1 + if bitmap & 1: + start = len(window) - 1 - next(it) + count = next(it) + 1 + new = window[start:start + count] + out.extend(new) + window.extend(new) + else: + try: + lit = next(it) + except StopIteration: + break + window.append(lit) + out.append(lit) + del window[:max(0, len(window) - windowsize)] + bitmap = bitmap >> 1 + + return out + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("input", help="Input file name") + parser.add_argument("output", help="Output file name") + parser.add_argument("-d", "--decompress", action="store_true", help="Decompress input (default is to compress)") + parser.add_argument("-w", "--window", help="Compression window size") + args = parser.parse_args() + if args.input == "-": + ifile = sys.stdin + else: + ifile = open(args.input, "rb") + if args.output == "-": + ofile = sys.stdout + else: + ofile = open(args.output, "wb") + if args.window is None: + args.window = 256 + + ibuf = bytearray(ifile.read()) + ifile.close() + + if args.decompress: + obuf = decompress(ibuf, args.window) + else: + obuf = compress(ibuf, args.window) + + ofile.write(obuf) + ofile.close() diff --git a/vendor/Hazard3/scripts/extract_ref_nets b/vendor/Hazard3/scripts/extract_ref_nets new file mode 100755 index 0000000..00a6cd7 --- /dev/null +++ b/vendor/Hazard3/scripts/extract_ref_nets @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# Turn KiCad netlist into a PCF file for icestorm + +import argparse +import re +import shlex +from collections import OrderedDict + +def sanitize_net_name(s): + s = s.replace("~", "") + s = re.sub(r"(\d+)$", r"[\1]", s) + return s + +def natural_key(s): + return tuple(int(x) if x.isdigit() else x for x in re.split(r"(\d+)", s)) + +def extract_nets(fh, ref, filters): + nets = [] + + name = None + namevalid = False + + for l in fh: + m = re.match(r"^\s*\(net \([^\)]*\) \(name ([^\)]+)\)", l) + if m: + name = m.group(1).strip().strip("/").lower() + namevalid = not any(name.startswith(s) for s in [ + "\"net", "+", "-", "gnd", "vcc", "vdd", "vss", *filters + ]) + elif namevalid: + m = re.match(r"^\s*\(node \(ref ([^\)]+)\) \(pin ([^\)]+)\)", l) + if m and m.group(1) == ref: + nets.append((name, m.group(2))) + return nets + +def align_buses(nets): + buses = OrderedDict() + onets = [] + # First figure out what is/isn't a bus + for net in nets: + if "[" in net[0]: + busname = net[0].split("[")[0] + if busname not in buses: + buses[busname] = [] + buses[busname].append(net) + else: + onets.append(net) + # Then right-justify the buses + for busname, bus in buses.items(): + indices = list(int(net[0].split('[')[1].strip(']')) for net in bus) + lsb = min(indices) + for index, net in zip(indices, bus): + onets.append(("{}[{}]".format(busname, index - lsb), net[1])) + return onets + + +def write_pcf(fh, nets): + fh.write("# Generated with extract_ref_nets\n\n") + for name, loc in sorted(nets, key = lambda x: natural_key(x[0])): + fh.write("set_io {:<20} {}\n".format(name, loc)) + +def main(): + parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("netfile", help="Path to KiCad .net file") + parser.add_argument("ref", help="Component reference whose named nets will be extracted") + parser.add_argument("--output", "-o", help="Output file name") + parser.add_argument("--filters", "-f", help="-f \"abc xyz\": filter out nets beginning with abc or xyz") + args = parser.parse_args() + opath = "chip.pcf" if args.output is None else args.output + filters = [] if args.filters is None else shlex.split(args.filters) + with open(args.netfile) as ifile: + nets = extract_nets(ifile, args.ref, filters) + nets = map(lambda n: (sanitize_net_name(n[0]), n[1]), nets) + nets = align_buses(nets) + with open(opath, "w") as ofile: + write_pcf(ofile, nets) + +if __name__ == "__main__": + main() diff --git a/vendor/Hazard3/scripts/formal.mk b/vendor/Hazard3/scripts/formal.mk new file mode 100644 index 0000000..6de0c60 --- /dev/null +++ b/vendor/Hazard3/scripts/formal.mk @@ -0,0 +1,43 @@ +# Must define: +# DOTF: .f file containing root of file list +# TOP: name of top-level module + +SRCS=$(shell PROJ_ROOT=$(PROJ_ROOT) HDL=$(HDL) $(SCRIPTS)/listfiles --relative -f flat $(DOTF)) +INCDIRS=$(shell PROJ_ROOT=$(PROJ_ROOT) HDL=$(HDL) $(SCRIPTS)/listfiles --relative -f flati $(DOTF)) + +YOSYS=yosys +YOSYS_SMTBMC=$(YOSYS)-smtbmc + +DEPTH?=20 +COVER_APPEND?=3 +YOSYS_SMT_SOLVER?=z3 + +DEFINES?= + +PREP_CMD =read_verilog -formal +PREP_CMD+=$(addprefix -I,$(INCDIRS)) +PREP_CMD+=$(addprefix -D,$(DEFINES) ) +PREP_CMD+= $(SRCS); +PREP_CMD+=prep -top $(TOP); async2sync; dffunmap; write_smt2 -wires $(TOP).smt2 + +BMC_ARGS=-s $(YOSYS_SMT_SOLVER) --dump-vcd $(TOP).vcd -t $(DEPTH) +IND_ARGS=-i $(BMC_ARGS) +COV_ARGS = -c $(BMC_ARGS) --append $(COVER_APPEND) +.PHONY: prove prep bmc induct clean + +prove: bmc induct + +prep: + $(YOSYS) -p "$(PREP_CMD)" > prep.log + +bmc: prep + $(YOSYS_SMTBMC) $(BMC_ARGS) $(TOP).smt2 | tee bmc.log + +induct: prep + $(YOSYS_SMTBMC) $(IND_ARGS) $(TOP).smt2 | tee induct.log + +cover: prep + $(YOSYS_SMTBMC) $(COV_ARGS) $(TOP).smt2 | tee cover.log + +clean:: + rm -f $(TOP).vcd $(TOP).smt2 srcs.mk prep.log bmc.log induct.log cover.log diff --git a/vendor/Hazard3/scripts/gui_run.tcl b/vendor/Hazard3/scripts/gui_run.tcl new file mode 100644 index 0000000..069b1c5 --- /dev/null +++ b/vendor/Hazard3/scripts/gui_run.tcl @@ -0,0 +1,13 @@ +# Navigate up directories until we find a file called "Default.wcfg" +# Stop if we get to root + +set cfgdir [file normalize .]; +while {"$cfgdir" != "/"} { + if [file exists $cfgdir/Default.wcfg] { + wcfg open $cfgdir/Default.wcfg + break + } + set cfgdir [file normalize $cfgdir/..] +} + +run 100us; \ No newline at end of file diff --git a/vendor/Hazard3/scripts/listfiles b/vendor/Hazard3/scripts/listfiles new file mode 100755 index 0000000..8a27ea1 --- /dev/null +++ b/vendor/Hazard3/scripts/listfiles @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 + +import shlex +import os +import sys +import argparse +import re + +help_str = """ + +Tool for building file lists, into formats required by various tools. +".f" files contain four types of command: + +file (filename) + adds a file to the list +include (dir) + add a directory to include path, if output format supports this +wildcard (.extension) (dir) + add all files with a given extension in a given directory +list (filename) + recurse on another filelist file + +The idea is that each component in a project has a +.f file which lists all of the Verilog (e.g.) files +for that component. Higher-level .f files will hierarchically +include the lower-level ones. + +In this way, you can build large flat file lists to pass into the +various tools, but never have to *write* large flat file lists. + +It also makes it easier to specify parts of your design hierarchy +for a given tool. For example, if you just want to synthesise your +CPU, you can run "listfiles cpu.f -f flat" +""" + +def wildcard(dir, extension): + prev_dir = os.getcwd() + os.chdir(dir) + files = [os.path.abspath(f) for f in os.listdir() if os.path.splitext(f)[-1] == extension] + os.chdir(prev_dir) + return files + +def read_filelist(fname): + files = [] + includes = [] + f = open(fname) + prev_dir = os.getcwd() + os.chdir(os.path.dirname(os.path.abspath(fname))) + for l in f.readlines(): + l = l.split("#")[0].strip() + if l == "": + continue + words = shlex.split(l) + if words[0] == "file": + assert(len(words) == 2) + files.append(os.path.abspath(os.path.expandvars(words[1]))) + elif words[0] == "include": + assert(len(words) == 2) + includes.append(os.path.abspath(os.path.expandvars(words[1]))) + elif words[0] == "wildcard": + assert(len(words) == 3) + files.extend(wildcard(os.path.expandvars(words[2]), words[1])) + elif words[0] == "list": + assert(len(words) == 2) + newfiles, newincludes = read_filelist(os.path.expandvars(words[1])) + files.extend(newfiles) + includes.extend(newincludes) + else: + raise Exception("In filelist {}: Invalid command \"{}\"".format(fname, words[0])) + os.chdir(prev_dir) + return (files, includes) + +formats = { + "isim": lambda f, i: "verilog work {} {}\n".format(" ".join(f), " ".join("-i " + inc for inc in i)), + "flat": lambda f, i: " ".join(f) + "\n", + "flati": lambda f, i: " ".join(i) + "\n", + "make": lambda f, i: "SRCS={}\nINCDIRS={}\n".format(" ".join(f), " ".join(i)) +} + +if __name__ == "__main__": + parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter) + parser.epilog = help_str + parser.add_argument("src", help="File list source file") + parser.add_argument("--format", "-f", help="Format to generate output in. Allowed: isim, make, flat (default)") + parser.add_argument("--relative", "-r", action="store_true", help="Use relative paths in output file") + parser.add_argument("--relativeto", help="Use relative paths, relative to some specified path") + parser.add_argument("--output", "-o", help="Output file name") + args = parser.parse_args() + if args.format is None: + args.format = "flat" + files, includes = read_filelist(args.src) + # Uniquify whilst preserving order + func = lambda l: list(dict.fromkeys(l)) + if args.relative: + func = lambda l, func=func: [os.path.relpath(f) for f in func(l)] + elif args.relativeto: + func = lambda l, func=func: [os.path.relpath(f, args.relativeto) for f in func(l)] + + files, includes = map(func, (files, includes)) + if args.format not in formats: + sys.exit("Unknown format: " + args.format) + ofile = sys.stdout if args.output is None else open(args.output, "w") + ofile.write(formats[args.format](files, includes)) diff --git a/vendor/Hazard3/scripts/mkflashexec b/vendor/Hazard3/scripts/mkflashexec new file mode 100755 index 0000000..62b5881 --- /dev/null +++ b/vendor/Hazard3/scripts/mkflashexec @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import argparse +import struct + +parser = argparse.ArgumentParser() +parser.add_argument("ifile") +parser.add_argument("ofile") +args = parser.parse_args() +data = open(args.ifile, "rb").read() +with open(args.ofile, "wb") as ofile: + ofile.write("RISCBoy".encode() + bytes(1)) + ofile.write(struct.pack("= 8: + yield accum & 0xff + accum = accum >> 8 + accum_size -= 8 + +class BinHeader: + def __init__(self, filename, arrayname=None): + if arrayname is None: + arrayname = filename.split(".")[0] + self.f = open(filename, "w") + self.out_count = 0 + self.f.write( + "#ifndef _IMG_ASSET_SECTION\n" \ + "#define _IMG_ASSET_SECTION \".data\"\n" \ + "#endif\n\n" \ + f"static const char __attribute__((aligned(16), section(_IMG_ASSET_SECTION \".{arrayname}\"))) {arrayname}[] = {{\n\t" + ) + + def write(self, bs): + for b in bs: + self.f.write("0x{:02x}".format(b) + (",\n\t" if self.out_count % 16 == 15 else ", ")) + self.out_count += 1 + + def close(self): + self.f.write("\n};\n") + self.f.close() + +# Fixed dither -- note every number 0...15 appears once (thanks Graham) +dither_pattern_4x4 = [ + [0 , 8 , 2 , 10], + [12 , 4 , 14 , 6 ], + [3 , 11 , 1 , 9 ], + [15 , 7 , 13 , 5 ], +] + +def format_channel(data, msb, lsb, dither=False, dithercoord=None): + # Assume data to be 8 bits + out_width = msb - lsb + 1 + assert(out_width <= 8) + if dither: + ditherval = dither_pattern_4x4[dithercoord[1] % 4][dithercoord[0] % 4] + shamt = (8 - out_width) - 4 + if shamt >= 0: + data += ditherval << shamt + else: + data += ditherval >> -shamt + data = min(data, 0xff) + return (data >> (8 - out_width)) << lsb + +def format_rgb_pixel(pix, fmt, dither=False, dithercoord=None): + accum = 0 + for p, f in zip(pix, fmt): + accum |= format_channel(p, f[0], f[1], dither, dithercoord) + if len(pix) == len(fmt) - 1: + accum |= format_channel(0xff, fmt[-1][0], fmt[-1][1]) + return accum + +# TODO would be kind of nice to generate these based on format string but I don't need that yet +rgb_formats = { + "argb1555": (16, ((14, 10), (9, 5), (4, 0), (15, 15))), + "rgab5515": (16, ((15, 11), (10, 6), (4, 0), (5, 5))), + "bgar5515": (16, ((4, 0), (10, 6), (15, 11), (5, 5))), + "rgb565" : (16, ((15, 11), (10, 5), (4, 0))), + "argb1232": (8, ((6, 5), (4, 2), (1, 0), (7, 7))), + "ragb2132": (8, ((7, 6), (4, 2), (1, 0), (5, 5))), + "rgb332" : (8, ((7, 5), (4, 2), (1, 0))), + "r2" : (2, ((1, 0), (-1, 0), (-1, 0))), + "r1" : (1, ((0, 0), (-1, 0), (-1, 0))), +} + +def format_pixel(format, src_has_transparency, pixel, dither=False, dithercoord=None): + assert(format in FORMATS) + if format in rgb_formats: + return (rgb_formats[format][0], format_rgb_pixel(pixel, rgb_formats[format][1], dither, dithercoord)) + elif format in ["p8", "p4", "p2", "p1"]: + size = int(format[1:]) + return (size, (pixel + src_has_transparency) & ((1 << size) - 1)) + else: + raise Exception() + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("input", help="Input file name") + parser.add_argument("output", help="Output file name") + parser.add_argument("--tilesize", "-t", help="Tile size (pixels), default 8", + default="8", choices=[str(2 ** i) for i in range(3, 11)]) + parser.add_argument("--single", "-s", action="store_true", + help="The input consists of a single image of arbitrary width/height, rather than a tileset") + parser.add_argument("--format", "-f", help="Output pixel format, default argb1555", + default="argb1555", choices=FORMATS) + parser.add_argument("--dither", "-d", action="store_true", + help="Apply a simple fixed dither pattern when packing RGB files") + parser.add_argument("--metadata", "-m", action="store_true", + help="Write out opacity metadata at end of file for faster alpha blit (must be used with --single)") + args = parser.parse_args() + img = Image.open(args.input) + if args.single: + tsize_x = img.width + tsize_y = img.height + else: + tsize_x = int(args.tilesize) + tsize_y = tsize_x + if args.metadata and not args.single: + sys.exit("--metadata must be used with --single") + + format_is_paletted = args.format.startswith("p") + image_is_transparent = img.mode == "RGBA" and img.getextrema()[3][0] < 255 + if args.metadata and not image_is_transparent: + sys.exit("Can't write opacity metadata for a non-transparent image") + + friendly_out_name = os.path.basename(args.input).split(".")[0] + + if format_is_paletted: + ncolours_max = 1 << int(args.format[1:]) + ncolours_actual = min(ncolours_max, len(img.getcolors())) + pimg = img.quantize(ncolours_max) + palette = pimg.getpalette() + # TODO haven't found a sane way to make PIL map transparency to palette + if image_is_transparent: + for x in range(img.width): + for y in range(img.height): + if not (img.getpixel((x, y))[3] & 0x80): + pimg.putpixel((x, y), 255) + + if args.output.endswith(".h"): + pfile = BinHeader(args.output + ".pal", arrayname=friendly_out_name + "_pal") + else: + pfile = open(args.output + ".pal", "wb") + if image_is_transparent: + pfile.write(bytes(2)) + pfile.write(bytes(bytes_from_bitstream_le( + format_pixel("argb1555", False, palette[i:i+3]) for i in range(0, ncolours_actual * 3, 3) + ))) + if ncolours_actual < ncolours_max: + pfile.write(bytes(2 * (ncolours_max - ncolours_actual))) + pfile.close() + + img = pimg + + if args.output.endswith(".h"): + ofile = BinHeader(args.output, arrayname=friendly_out_name) + else: + ofile = open(args.output, "wb") + + for y in range(0, img.height - (tsize_y - 1), tsize_y): + for x in range(0, img.width - (tsize_x - 1), tsize_x): + tile = img.crop((x, y, x + tsize_x, y + tsize_y)) + ofile.write(bytes(bytes_from_bitstream_le( + format_pixel(args.format, image_is_transparent, tile.getpixel((i, j)), args.dither, dithercoord=(i, j)) for j in range(tsize_y) for i in range(tsize_x) + ))) + if args.metadata: + assert(tsize_x * tsize_y % 4 == 0) + for y in range(0, tsize_y): + opacity = list(img.getpixel((x, y))[3] >= 128 for x in range(tsize_x)) + try: + first_transparent = opacity.index(True) + last_transparent = tsize_x - 1 - list(reversed(opacity)).index(True) + continuous_span = all(opacity[first_transparent:last_transparent + 1]) + ofile.write(struct.pack("> 7) + byte = (byte << 1) & 0xff + bits.extend(49 * [0]) # at least 49 dummy cycles required at end. + print("Starting") + gpio.output(fpga_rst, 0) + gpio.output(fpga_ss, 0) + gpio.output(fpga_sclk, 1) # CPOL = 1 (clock idle high) + gpio.output(fpga_sdi, 0) + sleep(0.001) + gpio.output(fpga_rst, 1) + sleep(0.001) + gpio.output(fpga_ss, 1) + for i in range(8): + gpio.output(fpga_sclk, 0) + gpio.output(fpga_sclk, 1) + # CPHA = 1 (data captured on trailing edge of clock pulse) + gpio.output(fpga_ss, 0) + for bit in bits: + gpio.output(fpga_sdi, bit) + gpio.output(fpga_sclk, 0) + gpio.output(fpga_sclk, 1) + if gpio.input(fpga_done): + print("CDONE high, yay!") + else: + print("CDONE not high, something may have gone wrong") + +if __name__ == "__main__": + if len(sys.argv) != 2: + exit("Usage: piceprog (file.bin)") + prog(sys.argv[1]) diff --git a/vendor/Hazard3/scripts/regblock b/vendor/Hazard3/scripts/regblock new file mode 100755 index 0000000..cebdb83 --- /dev/null +++ b/vendor/Hazard3/scripts/regblock @@ -0,0 +1,526 @@ +#!/usr/bin/env python3 + +# Regblock generation script + +import argparse +import re +import sys +import textwrap +import yaml +from collections import OrderedDict +from math import log2, ceil + +# Regblock data structures and parser/loader + +class RegBlock: + bus_types = ["apb"] + field_properties = ["name", "b", "access", "info", "rst", "concat"] + def __init__(self, name, w_data, w_addr, bus, info): + self.name = name + self.w_data = w_data + self.w_addr = w_addr + if bus not in self.bus_types: + raise Exception("Unknown bus type: {}".format(bus)) + self.bus = bus + self.info = info + self.regs = [] + + def add(self, reg): + self.regs.append(reg) + + @staticmethod + def load(file): + y = yaml.load(file.read(), Loader=yaml.FullLoader) + if "bus" not in y: + sys.exit("Must specify bus type with \"bus: x\"") + if "data" not in y: + sys.exit("Must specify data width with \"data: x\"") + if "addr" not in y: + sys.exit("Must specify address width with \"addr: x\"") + if "name" not in y: + sys.exit("Must specify regblock name with \"name: x\"") + rb = RegBlock(y["name"], y["data"], y["addr"], y["bus"], y["info"] if "info" in y else None) + param_dict = {"W_DATA": y["data"]} + if ("params" in y): + param_dict = {**param_dict, **y["params"]} + param_resolve = lambda x: x if type(x) is int else int(eval(x, {**param_dict})) + # Expand any generate blocks (one level only) + reglist = [] + for i, rspec in enumerate(y["regs"]): + if "generate" not in rspec: + reglist.append(rspec) + continue + yaml_lines = [] + emit = lambda x: yaml_lines.append(str(x)) + exec(rspec["generate"], {"_": emit, **param_dict}, dict()) + newregs = yaml.load("\n".join(yaml_lines), Loader=yaml.FullLoader) + if newregs is not None: + reglist.extend(newregs) + # Then process the expanded reglist + for rspec in reglist: + reg = Register(rspec["name"], rb.w_data, rspec["info"] if "info" in rspec else None, rspec["wstb"] if "wstb" in rspec else None) + rb.add(reg) + for fspec in rspec["bits"]: + for key in fspec: + if key not in RegBlock.field_properties: + raise Exception("'{}' is not a valid property for a field.".format(key)) + bitrange = fspec["b"] + if type(bitrange) is int: + bitrange = [bitrange, bitrange] + reg.add(Field( + fspec["name"] if "name" in fspec else "", + param_resolve(bitrange[0]), + param_resolve(bitrange[1]), + fspec["access"], + fspec["rst"] if "rst" in fspec else 0, + fspec["info"] if "info" in fspec else None, + fspec["concat"] if "concat" in fspec else None + )) + return rb + + def accept(self, visitor): + visitor.pre(self) + for reg in self.regs: + reg.accept(visitor) + visitor.post(self) + +class Register: + def __init__(self, name, width, info, wstrobe=None): + assert(width > 0) + self.name = name + self.width = width + self.info = info + self.occupancy = [None] * width + self.fields = OrderedDict() + self.wstrobe = wstrobe + + def add(self, field): + if field.lsb >= self.width or field.msb >= self.width or field.lsb < 0 or field.msb < 0: + raise Exception("Field {} extends outside of register {}".format(field.name, self.name)) + if field.name in self.fields: + raise Exception("{} already has a field called \"{}\"".format(self.name, field.name)) + for i in range(field.lsb, field.msb + 1): + if self.occupancy[i] is not None: + raise Exception("Field {} overlaps {} in register {}".format(field.name, self.occupancy[i], self.name)) + self.occupancy[i] = field.name + self.fields[field.name] = field + field.parent = self + + def accept(self, visitor): + visitor.pre(self) + for field in self.fields.values(): + field.accept(visitor) + visitor.post(self) + + +class Field: + access_types = ["ro", "rov", "wo", "rw", "rf", "wf", "rwf", "sc", "w1c"] + def __init__(self, name, msb, lsb, access, resetval=0, info="", concat=None, parent=None): + if lsb > msb: + raise Exception("Field width must be >= 0 in field {}".format(name)) + if access not in self.access_types: + raise Exception("Unknown access type: {}. Recognised types: {}".format(access, ", ".join(self.access_types))) + if access in ["sc", "w1c"] and msb != lsb: + raise Exception("Field width must be 1 for access type '{}'".format(access)) + self.name = name + self.msb = msb + self.lsb = lsb + self.access = access + self.resetval = resetval + self.info = info + self.concat = concat + self.parent = parent + + @property + def width_decl(self): + if self.msb == self.lsb: + return "" + else: + return "[{}:0]".format(self.msb - self.lsb) + + @property + def width(self): + return self.msb - self.lsb + 1 + + @property + def fullname(self): + if self.name == "": + return self.parent.name + else: + return "{}_{}".format(self.parent.name, self.name) + + def accept(self, visitor): + visitor.pre(self) + +# Base class for various useful lumps of functionality +# which are applied as traversals on a regblock description tree +class RegBlockVisitor: + def pre(self, x): + if type(x) is RegBlock: + self.pre_regblock(x) + elif type(x) is Register: + self.pre_register(x) + elif type(x) is Field: + self.pre_field(x) + else: + raise TypeError() + + def post(self, x): + if type(x) is RegBlock: + self.post_regblock(x) + elif type(x) is Register: + self.post_register(x) + elif type(x) is Field: + self.post_field(x) + else: + raise TypeError() + + def pre_regblock(self, rb): + raise NotImplementedError() + + def pre_register(self, r): + raise NotImplementedError() + + def pre_field(self, f): + raise NotImplementedError() + + def post_regblock(self, rb): + raise NotImplementedError() + + def post_register(self, r): + raise NotImplementedError() + + def post_field(self, f): + raise NotImplementedError() + + +# Verilog generation + +def width2str(w): + return "[{}:0]".format(w - 1) + +def c_block_comment(lines, width=80, align="<"): + blines = [] + blines.append("/" + (width - 1) * "*") + line_fmt = "* {:" + align + str(width - 4) + "} *" + for l in lines: + blines.append(line_fmt.format(l.strip())) + blines.append((width - 1) * "*" + "/") + return blines + +def c_line_comment(line): + if not hasattr(c_line_comment, "wrap"): + c_line_comment.wrap = textwrap.TextWrapper(width=80, initial_indent="// ", subsequent_indent="// ") + return "\n".join(c_line_comment.wrap.wrap(line.strip())) + +class Verilog: + def __init__(self): + self.header = [] + self.ports = [] + self.decls = [] + self.logic_comb = [] + self.logic_rst = [] + self.logic_clk = [] + + def __add__(self, other): + new = Verilog() + new.header.extend(self.header) + for n, s, o in zip( + [new.ports, new.decls, new.logic_comb, new.logic_rst, new.logic_clk], + [self.ports, self.decls, self.logic_comb, self.logic_rst, self.logic_clk], + [other.ports, other.decls, other.logic_comb, other.logic_rst, other.logic_clk]): + n.extend(s) + n.extend(o) + return new + + def __str__(self): + strs = [] + strs.extend(self.header) + strs.extend("\t" + s + ("" if s == "" or s.startswith("//") else ",") for s in self.ports[:-1]) + if len(self.ports) > 0: + strs.append("\t" + self.ports[-1]) + strs.append(");") + strs.append("") + strs.extend(self.decls); + strs.append("") + strs.append("always @ (*) begin") + strs.extend("\t" + s for s in self.logic_comb) + strs.append("end") + strs.append("") + strs.append("always @ (posedge clk or negedge rst_n) begin") + strs.append("\tif (!rst_n) begin") + strs.extend("\t\t" + s for s in self.logic_rst) + strs.append("\tend else begin") + strs.extend("\t\t" + s for s in self.logic_clk) + strs.append("\tend") + strs.append("end") + strs.append("") + strs.append("endmodule\n") + return "\n".join(strs) + + +class VerilogWriter(RegBlockVisitor): + def __init__(self): + self.v = Verilog() + self.regname = None + self.concat = OrderedDict() + self.wstrobe = OrderedDict() + + def pre_regblock(self, rb): + v = self.v + v.header.extend(c_block_comment(["AUTOGENERATED BY REGBLOCK", "Do not edit manually.", + "Edit the source file (or regblock utility) and regenerate."], align = "^")) + v.header.append("") + v.header.append(c_line_comment("{:<20} : {}".format("Block name", rb.name))) + v.header.append(c_line_comment("{:<20} : {}".format("Bus type", rb.bus))) + v.header.append(c_line_comment("{:<20} : {}".format("Bus data width", rb.w_data))) + v.header.append(c_line_comment("{:<20} : {}".format("Bus address width", rb.w_addr))) + v.header.append("") + if rb.info is not None: + v.header.append(c_line_comment(rb.info)) + v.header.append("") + v.header.append("module {}_regs (".format(rb.name)) + v.ports.extend(["input wire clk", "input wire rst_n",]) + addr_mask = (1 << 2 + ceil(log2(len(rb.regs)))) - 1 + addr_mask = addr_mask & (-1 << ceil(log2(rb.w_data / 8))) + if rb.bus == "apb": + v.ports.append("") + v.ports.append("// APB Port") + v.ports.append("input wire apbs_psel") + v.ports.append("input wire apbs_penable") + v.ports.append("input wire apbs_pwrite") + v.ports.append("input wire {} apbs_paddr".format(width2str(rb.w_addr))) + v.ports.append("input wire {} apbs_pwdata".format(width2str(rb.w_data))) + v.ports.append("output wire {} apbs_prdata".format(width2str(rb.w_data))) + v.ports.append("output wire apbs_pready") + v.ports.append("output wire apbs_pslverr") + v.decls.append("// APB adapter") + v.decls.append("wire {} wdata = apbs_pwdata;".format(width2str(rb.w_data))) + v.decls.append("reg {} rdata;".format(width2str(rb.w_data))) + v.decls.append("wire wen = apbs_psel && apbs_penable && apbs_pwrite;") + v.decls.append("wire ren = apbs_psel && apbs_penable && !apbs_pwrite;") + v.decls.append("wire {} addr = apbs_paddr & {}'h{:x};".format(width2str(rb.w_addr), rb.w_addr, addr_mask)) + v.decls.append("assign apbs_prdata = rdata;") + v.decls.append("assign apbs_pready = 1'b1;") + v.decls.append("assign apbs_pslverr = 1'b0;") + v.decls.append("") + v.ports.append("") + v.ports.append("// Register interfaces") + for i, reg in enumerate(rb.regs): + v.decls.append("localparam ADDR_{} = {};".format(reg.name.upper(), i * 4)) + v.decls.append("") + for reg in rb.regs: + v.decls.append("wire __{}_wen = wen && addr == ADDR_{};".format(reg.name, reg.name.upper())) + v.decls.append("wire __{}_ren = ren && addr == ADDR_{};".format(reg.name, reg.name.upper())) + v.logic_comb.append("case (addr)") + for reg in rb.regs: + v.logic_comb.append("\tADDR_{}: rdata = __{}_rdata;".format(reg.name.upper(), reg.name)) + v.logic_comb.append("\tdefault: rdata = {}'h0;".format(rb.w_data)) + v.logic_comb.append("endcase") + + def post_regblock(self, rb): + for name, conns in self.concat.items(): + concat_name = "concat_{}_o".format(name) + width = sum(f[0] for f in conns) + self.v.ports.append("output wire [{}:0] {}".format(width - 1, concat_name)) + self.v.decls.append("assign {} = {{{}}};".format(concat_name, ", ".join(f[1] for f in reversed(conns)))) + max_strobe_index = OrderedDict() + for name, conns in self.wstrobe.items(): + self.v.logic_rst.append("wstrobe_{} <= 1'b0;".format(name)) + self.v.logic_clk.append("wstrobe_{} <= {};".format(name, " || ".join( + "__{}_wen".format(conn) for conn in conns))) + if name.endswith("]"): + shortname = name.split("[")[0] + idx = int(name.split("[")[-1][:-1]) + if shortname in max_strobe_index: + max_strobe_index[shortname] = max(max_strobe_index[shortname], idx) + else: + max_strobe_index[shortname] = idx + else: + self.v.ports.append("output reg wstrobe_{}".format(name)) + for name, max_idx in max_strobe_index.items(): + self.v.ports.append("output reg [{}:0] wstrobe_{}".format(max_idx, name)) + + def pre_register(self, reg): + v = self.v + self.regname = reg.name + v.decls.append("") + rdata_conns = [] + empty_count = 0 + last_occupant = None + for occupant in reversed(reg.occupancy): + if occupant is None: + empty_count += 1 + elif occupant != last_occupant: + if empty_count > 0: + rdata_conns.append("{}'h0".format(empty_count)) + empty_count = 0 + rdata_conns.append("{}_rdata".format(reg.fields[occupant].fullname)) + last_occupant = occupant + if empty_count > 0: + rdata_conns.append("{}'h0".format(empty_count)) + for field in reg.fields.values(): + lsb = reg.occupancy.index(field.name) + msb = lsb - 1 + reg.occupancy.count(field.name) + index = "[{}]".format(lsb) if msb == lsb else "[{}:{}]".format(msb, lsb) + v.decls.append("wire {} {}_wdata = wdata{};".format(field.width_decl, field.fullname, index)) + v.decls.append("wire {} {}_rdata;".format(field.width_decl, field.fullname)) + v.decls.append("wire {} __{}_rdata = {{{}}};".format(width2str(reg.width), reg.name, ", ".join(rdata_conns))) + + if reg.wstrobe is not None: + if not reg.wstrobe in self.wstrobe: + self.wstrobe[reg.wstrobe] = [] + self.wstrobe[reg.wstrobe].append(reg.name) + + def post_register(self, reg): + pass + + def pre_field(self, f): + v = self.v + rname = self.regname + fname = f.fullname + if f.access in ["rov", "rf", "rwf", "w1c"]: + v.ports.append("input wire {} {}_i".format(f.width_decl, fname)) + if f.access in ["rov", "rf", "rwf"]: + v.decls.append("assign {}_rdata = {}_i;".format(fname, fname)) + if f.access in ["wo", "rw", "wf", "rwf", "sc", "w1c"]: + v.ports.append("output reg {} {}_o".format(f.width_decl, fname)) + if f.access in ["wf", "rwf"]: + v.ports.append("output reg {}_wen".format(fname)) + v.logic_comb.append("{}_wen = __{}_wen;".format(fname, rname)) + v.logic_comb.append("{}_o = {}_wdata;".format(fname, fname)) + if f.access in ["rf", "rwf"]: + v.ports.append("output reg {}_ren".format(fname)) + v.logic_comb.append("{}_ren = __{}_ren;".format(fname, rname)) + if f.access in ["rw"]: + v.decls.append("assign {}_rdata = {}_o;".format(fname, fname)) + if f.access in ["rw", "wo"]: + v.logic_rst.append("{}_o <= {}'h{:x};".format(fname, f.width, f.resetval)) + v.logic_clk.append("if (__{}_wen)".format(rname)) + v.logic_clk.append("\t{}_o <= {}_wdata;".format(fname, fname)) + if f.access in ["w1c"]: + v.logic_rst.append("{} <= {}'h{:x};".format(fname, f.width, f.resetval)) + v.decls.append("reg {} {};".format(f.width_decl, fname)) + v.decls.append("assign {}_rdata = {};".format(fname, fname)) + v.logic_clk.append("{0} <= ({0} && !(__{1}_wen && {0}_wdata)) || {0}_i;".format(fname, rname)) + v.logic_comb.append("{0}_o = {0};".format(fname)) + if f.access in ["sc"]: + v.logic_comb.append("{0}_o = {0}_wdata & {{{1}{{__{2}_wen}}}};".format(fname, f.width, rname)) + if f.access in ["ro", "wo", "wf", "sc"]: + v.decls.append("assign {}_rdata = {}'h{:x};".format(fname, f.width, f.resetval)) + + if f.concat is not None: + if not f.access in ["wo", "rw", "wf", "rwf", "sc"]: + raise Exception("concat specified for port with no regblock output") + if not f.concat in self.concat: + self.concat[f.concat] = [] + self.concat[f.concat].append((f.width, "{}_o".format(fname))) + + +# C header generation + +class HeaderWriter(RegBlockVisitor): + def __init__(self): + self.lines = [] + self.blockname = None + self.regname = None + + def __str__(self): + return "".join(l + "\n" for l in self.lines) + + def pre_regblock(self, rb): + lines = self.lines + self.blockname = rb.name + lines.extend(c_block_comment(["AUTOGENERATED BY REGBLOCK", "Do not edit manually.", + "Edit the source file (or regblock utility) and regenerate."], align = "^")) + lines.append("") + lines.append("#ifndef _{}_REGS_H_".format(rb.name.upper())) + lines.append("#define _{}_REGS_H_".format(rb.name.upper())) + lines.append("") + lines.append(c_line_comment("{:<20} : {}".format("Block name", rb.name))) + lines.append(c_line_comment("{:<20} : {}".format("Bus type", rb.bus))) + lines.append(c_line_comment("{:<20} : {}".format("Bus data width", rb.w_data))) + lines.append(c_line_comment("{:<20} : {}".format("Bus address width", rb.w_addr))) + lines.append("") + if rb.info is not None: + lines.append(c_line_comment(rb.info)) + lines.append("") + for i, reg in enumerate(rb.regs): + lines.append("#define {}_{}_OFFS {}".format(rb.name.upper(), reg.name.upper(), i * 4)) + + def post_regblock(self, rb): + self.lines.append("") + self.lines.append("#endif // _{}_REGS_H_".format(rb.name.upper())) + + def pre_register(self, reg): + lines = self.lines + lines.append("") + lines.extend(c_block_comment([reg.name.upper()], align = "^")) + lines.append("") + if reg.info is not None: + lines.append(c_line_comment(reg.info)) + lines.append("") + self.regname = reg.name + + def post_register(self, reg): + pass + + def pre_field(self, f): + fname = f.fullname.upper() + lines = self.lines + lines.append(c_line_comment("Field: {} Access: {}".format(fname, f.access.upper()))) + fname = self.blockname.upper() + "_" + fname + if f.info is not None: + lines.append(c_line_comment(f.info)) + lines.append("#define {}_LSB {}".format(fname, f.lsb)) + lines.append("#define {}_BITS {}".format(fname, f.width)) + mask = 0 + for i in range(32): + if i in range(f.lsb, f.msb + 1): + mask = mask | (1 << i) + lines.append("#define {}_MASK {:#x}".format(fname, mask)) + +def change_ext(fname, ext): + return ".".join(fname.split(".")[0:-1] + [ext.strip(".")]) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("src", help="Source file to read from (pass - for stdin)") + parser.add_argument("--verilog", "-v", help="Verilog file to write to (pass - for stdout)") + parser.add_argument("--cheader", "-c", help="C header file to write to (pass - for stdout)") + parser.add_argument("--all", "-a", action="store_true", help="Generate all output types, with default filenames") + args = parser.parse_args() + sfile = None + vfile = None + hfile = None + if args.src == "-" and args.all: + exit("Cannot use --all with stdin input") + if args.src == "-": + sfile = sys.stdin + else: + sfile = open(args.src) + rb = RegBlock.load(sfile) + + if args.verilog is not None or args.all: + if args.verilog == "-": + vfile = sys.stdout + elif args.verilog is None: + vfile = open(change_ext(args.src, ".v"), "w") + else: + vfile = open(args.verilog, "w") + if args.cheader is not None or args.all: + if (args.cheader == "-"): + hfile = sys.stdout + elif args.verilog is None: + hfile = open(change_ext(args.src, ".h"), "w") + else: + hfile = open(args.cheader, "w") + + if vfile is not None: + vw = VerilogWriter() + rb.accept(vw) + vfile.write(str(vw.v)) + if hfile is not None: + hw = HeaderWriter() + rb.accept(hw) + hfile.write(str(hw)) diff --git a/vendor/Hazard3/scripts/sim.mk b/vendor/Hazard3/scripts/sim.mk new file mode 100644 index 0000000..a995831 --- /dev/null +++ b/vendor/Hazard3/scripts/sim.mk @@ -0,0 +1,26 @@ +TOP ?= tb +DOTF ?= $(TOP).f +SIMNAME?=simulation + +SIM_VARS = PLATFORM=lin64 LD_LIBRARY_PATH=$XILINX/lib/$PLATFORM +FUSE ?= $(SIM_VARS) fuse +SIMSCRIPT ?= $(SCRIPTS)/sim_run.tcl +GUISCRIPT ?= $(SCRIPTS)/gui_run.tcl + +# Kill implicit rules +.SUFFIXES: +.IMPLICIT: + +sim: build + (cd sim; $(SIM_VARS) ./$(SIMNAME) -tclbatch $(SIMSCRIPT)) + +gui: build + (cd sim; $(SIM_VARS) ./$(SIMNAME) -gui -tclbatch $(GUISCRIPT)) + +build: + mkdir -p sim + $(SCRIPTS)/listfiles --relativeto sim -f isim $(DOTF) -o sim/sim.prj + (cd sim; $(FUSE) -d SIM -prj sim.prj $(TOP) -o $(SIMNAME)) + +clean:: + rm -rf sim \ No newline at end of file diff --git a/vendor/Hazard3/scripts/sim_run.tcl b/vendor/Hazard3/scripts/sim_run.tcl new file mode 100644 index 0000000..a7d3d8d --- /dev/null +++ b/vendor/Hazard3/scripts/sim_run.tcl @@ -0,0 +1 @@ +run 1s; diff --git a/vendor/Hazard3/scripts/software.mk b/vendor/Hazard3/scripts/software.mk new file mode 100644 index 0000000..0bc1868 --- /dev/null +++ b/vendor/Hazard3/scripts/software.mk @@ -0,0 +1,55 @@ +SRCS ?= $(wildcard *.c) $(wildcard *.S) +APPNAME ?= test + +OBJS = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SRCS))) + +CROSS_PREFIX=riscv32-unknown-elf- +CC=$(CROSS_PREFIX)gcc +LD=$(CROSS_PREFIX)gcc +OBJCOPY=$(CROSS_PREFIX)objcopy +OBJDUMP=$(CROSS_PREFIX)objdump + +MARCH?=rv32ic +LDSCRIPT?=memmap.ld +override CCFLAGS+=-c -march=$(MARCH) $(addprefix -I ,$(INCDIRS)) +override CCFLAGS+=-Wall -Wextra -Wno-parentheses +override LDFLAGS+=-march=$(MARCH) -T $(LDSCRIPT) + +# Override to -D to get all sections +DISASSEMBLE?=-d + +.SUFFIXES: +.SECONDARY: +.PHONY: all clean +all: compile + +%.o: %.c + $(CC) $(CCFLAGS) $< -o $@ + +%.o: %.S + $(CC) $(CCFLAGS) $< -o $@ + +$(APPNAME).elf: $(OBJS) + $(LD) $(LDFLAGS) $(OBJS) $(addprefix -l,$(LIBS)) -o $(APPNAME).elf + +%.bin: %.elf + $(OBJCOPY) -O binary $< $@ + +%8.hex: %.elf + $(OBJCOPY) -O verilog $< $@ + +%32.hex: %8.hex + $(SCRIPTS)/vhexwidth -w 32 $< -o $@ + +$(APPNAME).dis: $(APPNAME).elf + @echo ">>>>>>>>> Memory map:" > $(APPNAME).dis + $(OBJDUMP) -h $(APPNAME).elf >> $(APPNAME).dis + @echo >> $(APPNAME).dis + @echo ">>>>>>>>> Disassembly:" >> $(APPNAME).dis + $(OBJDUMP) $(DISASSEMBLE) $(APPNAME).elf >> $(APPNAME).dis + + +compile:: $(APPNAME)32.hex $(APPNAME).dis $(APPNAME).bin + +clean:: + rm -f $(APPNAME).elf $(APPNAME)32.hex $(APPNAME)8.hex $(APPNAME).dis $(APPNAME).bin $(OBJS) diff --git a/vendor/Hazard3/scripts/synth_ecp5.mk b/vendor/Hazard3/scripts/synth_ecp5.mk new file mode 100644 index 0000000..1d4823d --- /dev/null +++ b/vendor/Hazard3/scripts/synth_ecp5.mk @@ -0,0 +1,82 @@ +YOSYS=yosys +NEXTPNR=nextpnr-ecp5 +TRELLIS?=/usr/share/trellis + +CHIPNAME?=chip +DEVICE?=um5g-85k +PACKAGE?=CABGA381 +DEVICE_IDCODE?=0x41113043 + +DEFINES+=FPGA FPGA_ECP5 + +SYNTH_OPT?= +PNR_OPT?= + +SYNTH_CMD=read_verilog $(addprefix -I,$(INCDIRS)) $(addprefix -D,$(DEFINES)) $(SRCS); +ifneq (,$(TOP)) + SYNTH_CMD+=hierarchy -top $(TOP); +endif +SYNTH_CMD+=synth_ecp5 $(SYNTH_OPT) -json $(CHIPNAME).json + +# Kill implicit rules +.SUFFIXES: +.IMPLICIT: + +.PHONY: all romfiles synth clean program dump + +all: bit + +romfiles:: +synth: romfiles $(CHIPNAME).json +dump: romfiles +pnr: synth $(CHIPNAME).config +bit: pnr $(CHIPNAME).bit $(CHIPNAME).svf + +SRCS=$(shell PROJ_ROOT=$(PROJ_ROOT) HDL=$(HDL) $(SCRIPTS)/listfiles --relative -f flat $(DOTF)) +INCDIRS=$(shell PROJ_ROOT=$(PROJ_ROOT) HDL=$(HDL) $(SCRIPTS)/listfiles --relative -f flati $(DOTF)) + +dump: + $(YOSYS) -p "$(SYNTH_CMD); write_verilog $(CHIPNAME)_synth.v" + +$(CHIPNAME).json: $(SRCS) + @echo ">>> Synth" + @echo + $(YOSYS) -p "$(SYNTH_CMD)" > synth.log + tail -n 35 synth.log + + +$(CHIPNAME).config: $(CHIPNAME).json $(CHIPNAME).lpf + @echo ">>> Place and Route" + @echo + $(NEXTPNR) -r --placer sa --$(DEVICE) --package $(PACKAGE) --lpf $(CHIPNAME).lpf --json $(CHIPNAME).json --textcfg $@ $(PNR_OPT) --quiet --log pnr.log + @grep "Info: Max frequency for clock " pnr.log | tail -n 1 + +$(CHIPNAME).bit: $(CHIPNAME).config + @echo ">>> Generate Bitstream" + @echo + ecppack --compress --svf $(CHIPNAME).svf --idcode $(DEVICE_IDCODE) $< $@ + +$(CHIPNAME).svf: $(CHIPNAME).bit + +clean:: + rm -f $(CHIPNAME).json $(CHIPNAME).asc $(CHIPNAME).bit $(CHIPNAME)_synth.v + rm -f synth.log pnr.log + +# Code for trying n different pnr seeds and reporting results + +PNR_N_TRIES := 100 +PNR_TRY_LIST := $(shell seq $(PNR_N_TRIES)) + +pnr_sweep: $(addprefix pnr_try,$(PNR_TRY_LIST)) + +define make-sweep-target +pnr_try$1: synth + @echo ">>> Starting sweep $1" + $(NEXTPNR) --seed $1 --placer sa --$(DEVICE) --package $(PACKAGE) --lpf $(CHIPNAME).lpf --json $(CHIPNAME).json --textcfg pnr_try$1.config $(PNR_OPT) --quiet --log pnr$1.log + @grep "Info: Max frequency for clock " pnr$1.log | tail -n 1 +endef + +$(foreach try,$(PNR_TRY_LIST),$(eval $(call make-sweep-target,$(try)))) + +clean:: + rm -f pnr_try*.asc pnr*.log diff --git a/vendor/Hazard3/scripts/synth_ice40.mk b/vendor/Hazard3/scripts/synth_ice40.mk new file mode 100644 index 0000000..59c197c --- /dev/null +++ b/vendor/Hazard3/scripts/synth_ice40.mk @@ -0,0 +1,80 @@ +YOSYS=yosys +NEXTPNR=nextpnr-ice40 +CHIPNAME?=chip +DEVICE?=hx8k +PACKAGE?=bg121 + +DEFINES+=FPGA FPGA_ICE40 + +PRE_SYNTH_CMD?= +SYNTH_OPT?= +PNR_OPT?= + +SYNTH_CMD=read_verilog $(addprefix -I,$(INCDIRS)) $(addprefix -D,$(DEFINES)) $(SRCS); +ifneq (,$(TOP)) + SYNTH_CMD+=hierarchy -top $(TOP); +endif +SYNTH_CMD+=$(PRE_SYNTH_CMD) +SYNTH_CMD+=synth_ice40 $(SYNTH_OPT); write_json $(CHIPNAME).json + +# Kill implicit rules +.SUFFIXES: +.IMPLICIT: + +.PHONY: all romfiles synth clean program dump + +all: bit + +romfiles:: +synth: romfiles $(CHIPNAME).json +dump: romfiles +pnr: synth $(CHIPNAME).asc +bit: pnr $(CHIPNAME).bin + +SRCS=$(shell PROJ_ROOT=$(PROJ_ROOT) HDL=$(HDL) $(SCRIPTS)/listfiles --relative -f flat $(DOTF)) +INCDIRS=$(shell PROJ_ROOT=$(PROJ_ROOT) HDL=$(HDL) $(SCRIPTS)/listfiles --relative -f flati $(DOTF)) + +dump: + $(YOSYS) -p "$(SYNTH_CMD); write_verilog $(CHIPNAME)_synth.v" + +$(CHIPNAME).json: $(SRCS) + @echo ">>> Synth" + @echo + $(YOSYS) -p "$(SYNTH_CMD)" > synth.log + tail -n 35 synth.log + + +$(CHIPNAME).asc: $(CHIPNAME).json $(CHIPNAME).pcf + @echo ">>> Place and Route" + @echo + $(NEXTPNR) -r --$(DEVICE) --package $(PACKAGE) --pcf $(CHIPNAME).pcf --json $(CHIPNAME).json --asc $(CHIPNAME).asc $(PNR_OPT) --quiet --log pnr.log + @grep "Info: Max frequency for clock " pnr.log | tail -n 1 + +$(CHIPNAME).bin: $(CHIPNAME).asc + @echo ">>> Generate Bitstream" + @echo + icepack -s $(CHIPNAME).asc $(CHIPNAME).bin + +clean:: + rm -f $(CHIPNAME).json $(CHIPNAME).asc $(CHIPNAME).bin $(CHIPNAME)_synth.v + rm -f synth.log pnr.log + +# Code for trying n different pnr seeds and reporting results + +PNR_N_TRIES := 100 +PNR_TRY_LIST := $(shell seq $(PNR_N_TRIES)) + +pnr_sweep: $(addprefix pnr_try,$(PNR_TRY_LIST)) + +define make-sweep-target +pnr_try$1: synth + @echo ">>> Starting sweep $1" + -$(NEXTPNR) --seed $1 --$(DEVICE) --package $(PACKAGE) --pcf $(CHIPNAME).pcf --json $(CHIPNAME).json --asc pnr_try$1.asc $(PNR_OPT) --quiet --log pnr$1.log + @grep "Info: Max frequency for clock " pnr$1.log | tail -n 1 +endef + +$(foreach try,$(PNR_TRY_LIST),$(eval $(call make-sweep-target,$(try)))) + +clean:: + rm -f pnr_try*.asc pnr*.log + diff --git a/vendor/Hazard3/scripts/uartprog b/vendor/Hazard3/scripts/uartprog new file mode 100755 index 0000000..7c80ebb --- /dev/null +++ b/vendor/Hazard3/scripts/uartprog @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 + +# Simple utility for programming SPI flash via a UART shell in the bootloader. +# The bootloader possesses a 1-page buffer which we can write to, read from and +# checksum. +# It also possesses commands for transfers between this buffer and the SPI flash, +# flash erasure, and launching the 2nd stage boot code. + +import argparse +import os +import serial +import serial.tools.list_ports +import sys +import time + +PAGESIZE = 2 ** 8 +SECTORSIZE = 2 ** 12 +BLOCKSIZE = 2 ** 16 + +SRAM_LOAD_ADDR = 0x2 << 28 +SRAM_EXEC_ADDR = SRAM_LOAD_ADDR + 0xc0 + +class ProtocolError(Exception): + pass + +class CMD: + NOP = '\n'.encode() + WRITE_BUF = 'w'.encode() + READ_BUF = 'r'.encode() + GET_CHECKSUM = 'c'.encode() + SET_ADDR = 'a'.encode() + WRITE_FLASH = 'W'.encode() + READ_FLASH = 'R'.encode() + ERASE_SECTOR = 'E'.encode() + ERASE_BLOCK = 'B'.encode() + BOOT_2ND = '2'.encode() + LOAD_MEM = 'l'.encode() + EXEC_MEM = 'x'.encode() + ACK = ':'.encode() + +def reset_shell(port): + while True: + port.write(CMD.NOP * (PAGESIZE + 1)) + port.flushOutput() + time.sleep(0.02) + port.flushInput() + port.write(CMD.NOP) + time.sleep(0.02) + if port.readable() and port.read_all().endswith(CMD.ACK): + break + +def check_ack(port): + resp = port.read() + if len(resp) == 0 or resp != CMD.ACK: + print("Got '{!r}'".format(resp)) + raise ProtocolError() + +def write_buf(port, data, check=True): + assert(len(data) == 256) + port.write(CMD.WRITE_BUF) + port.write(data) + if check: + check_ack(port) + # TODO checksum + +def read_buf(port, pipelined=False): + if not pipelined: + port.write(CMD.READ_BUF) + resp = port.read(PAGESIZE) + if len(resp) != PAGESIZE: + raise ProtocolError() + return resp + # TODO checksum + +def set_addr(port, addr): + addr_b = bytes([ + (addr >> 16) & 0xff, + (addr >> 8) & 0xff, + (addr >> 0) & 0xff + ]) + port.write(CMD.SET_ADDR + addr_b) + echo = port.read(3) + if echo != addr_b: + raise ProtocolError() + check_ack(port) + +def progress(header, frac, width=40): + n = int(width * frac) + sys.stdout.write("\r" + header + "▕" + "▒" * n + " " * (width - n) + "▏") + +def read_flash(port, addr, size): + set_addr(port, addr) + data = bytes() + addr_range = range(addr, addr + size, PAGESIZE) + for a in addr_range: + port.write(CMD.READ_FLASH) + port.write(CMD.READ_BUF) # Should have space for 2 cmds in FIFO. Hoist this one up from read_buf() + progress("Read: ", (a - addr) / size) + check_ack(port) + data += read_buf(port, pipelined=True) + progress("Read: ", 1) + if len(data) > size: + data = data[:size] # ouch + return data + +def write_flash(port, addr, data): + assert(addr % PAGESIZE == 0) + if len(data) % PAGESIZE != 0: + data += bytes(PAGESIZE - len(data) % PAGESIZE) + set_addr(port, addr) + for i in range(len(data) // PAGESIZE): + write_buf(port, data[i * PAGESIZE : (i + 1) * PAGESIZE], check=False) + port.write(CMD.WRITE_FLASH) + progress("Write: ", i / (len(data) // PAGESIZE)) + check_ack(port) + check_ack(port) + progress("Write: ", 1) + +def erase_flash(port, addr, size): + end = addr + size + if end % SECTORSIZE != 0: + end += SECTORSIZE - end % SECTORSIZE + start = addr - addr % SECTORSIZE + set_addr(port, start) + a = start + while a < end: + progress("Erase: ", (a - start) / (end - start)) + if end - a >= BLOCKSIZE and a % BLOCKSIZE == 0: + port.write(CMD.ERASE_BLOCK) + a += BLOCKSIZE + else: + port.write(CMD.ERASE_SECTOR) + a += SECTORSIZE + check_ack(port) + progress("Erase: ", 1) + +def run_2nd_stage(port): + set_addr(port, 0x123456) + port.write(CMD.BOOT_2ND) + check_ack(port) + +def load_mem(port, data): + set_addr(port, 0xb00000 | SRAM_LOAD_ADDR) + port.write(CMD.LOAD_MEM + bytes([ + (len(data) >> 16) & 0xff, + (len(data) >> 8) & 0xff, + (len(data) >> 0) & 0xff + ])) + check_ack(port) + port.write(data) # yup + check_ack(port) + +def exec_mem(port): + set_addr(port, 0xb00000 | SRAM_EXEC_ADDR) + port.write(CMD.EXEC_MEM) + check_ack(port) + +def any_int(x): + return int(x, 0) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("file", nargs="?", help="Filename to read from or dump to.") + parser.add_argument("--uart", "-u", help="Path to UART device (e.g. /dev/ttyUSB0)") + parser.add_argument("--baud", "-b", help="Baud rate for UART. Default 1 Mbaud") + parser.add_argument("--write", "-w", action="store_true", + help="Write to flash (default is read)") + parser.add_argument("--verify", "-v", action="store_true", + help="Verify contents after programming (use with --write)") + parser.add_argument("--run", "-r", action="store_true", + help="Run code from flash after programming") + parser.add_argument("--execute", "-x", action="store_true", + help="Load code directly into SRAM and execute it. Not to be combined with -w,-v,-r") + parser.add_argument("--start", "-s", type=any_int, help="Base address to start read/write") + parser.add_argument("--len", "-l", type=any_int, help="Number of bytes to read, or override file length for write.") + args = parser.parse_args() + + # Do some simple parameter checking to make it harder to accidentally trash your device + if args.write and args.file is None: + sys.exit("Must specify filename for write") + if args.start is None: + args.start = 0 + if args.uart is None: + args.uart = sorted(p.device for p in serial.tools.list_ports.comports())[-1] + if args.baud is None: + args.baud = 1000000 + if args.verify and not args.write: + sys.exit("Verify is only valid for a write operation") + if args.run and not args.write: + sys.exit("Run is only valid for a write operation") + if args.write and (args.start % PAGESIZE != 0): + sys.exit("Writes must be aligned on a {}-byte boundary.".format(PAGESIZE)) + if args.execute and (args.write or args.verify or args.run or args.start or args.len): + sys.exit("--execute is not compatible with flash-related arguments") + if args.write or args.execute: + try: + filesize = os.stat(args.file).st_size + if args.len is None: + args.len = filesize + except: + sys.exit("Could not open file '{}'".format(args.file)) + else: + if args.len is None: + args.len = PAGESIZE + + # Don't want to overwrite their image if they forget -w + if not (args.write or args.execute) and args.file and os.path.exists(args.file): + resp = "" + while not resp in ["y", "n"]: + resp = input("File '{}' exists. Overwrite? (y/n) ".format(args.file)) + resp = resp.lower().strip() + if resp == "n": + sys.exit(0) + + # Summarise what we're about to do + if args.execute: + print("Loading {} bytes to SRAM and running".format(args.len)) + else: + print("{} {} bytes {} {}, starting at address 0x{:06x}".format( + "Writing" + (" and verifying" if args.verify else "") if args.write else "Reading", + args.len, + "from" if args.write else "to", + "stdout" if args.file is None else "file " + args.file, + args.start, + ", and verifying" if args.verify else "" + )) + + # And then do it + # need to allow for page erase, upward of 60 ms. (Uh, actually it seems to be much longer) + port = serial.Serial(args.uart, args.baud, timeout=1) + print("Waiting for bootloader on {}...".format(port.name)) + reset_shell(port) + print("") + + if args.execute: + print("Loading...") + load_mem(port, open(args.file, "rb").read()) + print("Load ok, running...") + exec_mem(port) + elif args.write: + data = open(args.file, "rb").read() + if len(data) > args.len: + data = data[:args.len] + else: + data = data + bytes(args.len - len(data)) # zero padding + + start = time.time() + erase_flash(port, args.start, args.len) + print(" Took {:.1f} s\n".format(time.time() - start)) + start = time.time() + write_flash(port, args.start, data) + print(" Took {:.1f} s\n".format(time.time() - start)) + if args.verify: + start = time.time() + readback = read_flash(port, args.start, args.len) + print(" Took {:.1f} s".format(time.time() - start)) + if readback != data: + sys.exit("Verification failed.") # TODO: better info + if args.run: + print("Launching flash second stage") + run_2nd_stage(port) + print("Done") + else: + start = time.time() + data = read_flash(port, args.start, args.len) + print(" Took {:.1f} s\nDone".format(time.time() - start)) + if args.file: + open(args.file, "wb").write(data) + else: + for i, byte in enumerate(data): + if i % 8 == 0: + sys.stdout.write("{:06x}: ".format(args.start + i)) + sys.stdout.write("{:02x}".format(byte)) + sys.stdout.write("\n" if i % 8 == 7 else " ") + sys.stdout.write("\n") diff --git a/vendor/Hazard3/scripts/uniquetiles b/vendor/Hazard3/scripts/uniquetiles new file mode 100755 index 0000000..0889345 --- /dev/null +++ b/vendor/Hazard3/scripts/uniquetiles @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +import argparse +from PIL import Image +from collections import OrderedDict + +__doc__ = """Utility for stripping non-unique tiles from a tileset image. +Outputs the uniquified image, and an index map file from original tile indices +to new tile indices.""" + +parser = argparse.ArgumentParser() +parser.add_argument("input", help="Input file name") +parser.add_argument("output", help="Output file name") +parser.add_argument("--tilesize", "-t", help="Tile size (pixels), default 8", + default="8", choices=["8", "16"]) +parser.epilog = __doc__ +args = parser.parse_args() + +img = Image.open(args.input) +tilesize = int(args.tilesize) + +tile_first_seen = {} +index_mapping = [] +output_images = [] + +src_index = 0 +for y in range(0, img.height - (tilesize - 1), tilesize): + for x in range(0, img.width - (tilesize - 1), tilesize): + tile = img.crop((x, y, x + tilesize, y + tilesize)) + tiledata = tuple(tile.getdata()) + if tiledata in tile_first_seen: + index_mapping.append((src_index, tile_first_seen[tiledata])) + else: + tile_first_seen[tiledata] = len(output_images) + index_mapping.append((src_index, len(output_images))) + output_images.append(tile) + src_index += 1 + +print("Found {} unique tile images".format(len(output_images))) + +oimg = Image.new("RGBA", (tilesize * len(output_images), tilesize)) +for i, tile in enumerate(output_images): + oimg.paste(tile, (i * tilesize, 0)) + +with open(args.output, "wb") as ofile: + oimg.save(ofile) +with open(args.output + ".index_map", "w") as mapfile: + for old, new in index_mapping: + mapfile.write("{}, {}\n".format(old, new)) diff --git a/vendor/Hazard3/scripts/vhexwidth b/vendor/Hazard3/scripts/vhexwidth new file mode 100755 index 0000000..dde2107 --- /dev/null +++ b/vendor/Hazard3/scripts/vhexwidth @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +# Tool for converting width of verilog hex files + +import sys +import argparse +import re + +# Assume little-endian, and that widths are multiples of 4 + +def width_convert(ilines, width, base): + olines = [] + pad_fmt = "{:>0" + str(width // 4) + "}" + accum = "" + for l in ilines: + if l.startswith("@"): + if len(accum): + olines.append(pad_fmt.format(accum)) + accum = "" + # TODO: address scaling wrong if input not byte-sized: + olines.append("@{:x}".format((int(l[1:], 16) - base) // (width // 8))) + continue + for num in re.findall(r"[0-9a-fA-F]+", l): + accum = num + accum + while len(accum) >= width // 4: + olines.append(accum[len(accum) - width // 4 :]) + accum = accum[:len(accum) - width // 4] + if len(accum): + olines.append(pad_fmt.format(accum)) + return olines + +def parseint(arg, name, default): + if arg is None: + arg = default + try: + if type(arg) is str and arg.startswith("0x"): + arg = int(arg, 16) + else: + arg = int(arg) + except ValueError: + sys.exit("Invalid value for {}: {}".format(name, arg)) + return arg + +if __name__ == "__main__": + parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("input", help="Input file name") + parser.add_argument("--width", "-w", help="Output hex width (default 32)") + parser.add_argument("--base", "-b", help="Base address for @ commands (subtracted)") + parser.add_argument("--output", "-o", help="Output file name") + args = parser.parse_args() + if args.output is None: + ofile = sys.stdout + else: + ofile = open(args.output, "w") + if args.input is None: + ifile = sys.stdin + else: + ifile = open(args.input) + width = parseint(args.width, "width", 32) + base = parseint(args.base, "base", 0) + olines = width_convert(ifile, width, base) + for l in olines: + ofile.write(l + "\n") \ No newline at end of file diff --git a/vendor/Hazard3/test/project_paths.mk b/vendor/Hazard3/test/project_paths.mk new file mode 100644 index 0000000..934f996 --- /dev/null +++ b/vendor/Hazard3/test/project_paths.mk @@ -0,0 +1,2 @@ +# Link up to project root +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../project_paths.mk diff --git a/vendor/Hazard3/test/sim/common/hazard3_csr.h b/vendor/Hazard3/test/sim/common/hazard3_csr.h new file mode 100644 index 0000000..413ac4a --- /dev/null +++ b/vendor/Hazard3/test/sim/common/hazard3_csr.h @@ -0,0 +1,66 @@ +#ifndef _HAZARD3_CSR_H +#define _HAZARD3_CSR_H + +#ifndef __ASSEMBLER__ +#include "stdint.h" +#endif + +#define hazard3_csr_dmdata0 0xbff // Debug-mode shadow CSR for DM data transfer + +#define hazard3_csr_meiea 0xbe0 // External interrupt pending array +#define hazard3_csr_meipa 0xbe1 // External interrupt enable array +#define hazard3_csr_meifa 0xbe2 // External interrupt force array +#define hazard3_csr_meipra 0xbe3 // External interrupt priority array +#define hazard3_csr_meinext 0xbe4 // Next external interrupt +#define hazard3_csr_meicontext 0xbe5 // External interrupt context register + +#define hazard3_csr_msleep 0xbf0 // M-mode sleep control register + +#define hazard3_csr_pmpcfgm0 0xbd0 // Non-locking M-mode enables for PMP regions + +#define _read_csr(csrname) ({ \ + uint32_t __csr_tmp_u32; \ + asm volatile ("csrr %0, " #csrname : "=r" (__csr_tmp_u32)); \ + __csr_tmp_u32; \ +}) + +#define _write_csr(csrname, data) ({ \ + asm volatile ("csrw " #csrname ", %0" : : "r" (data)); \ +}) + +#define _set_csr(csrname, data) ({ \ + asm volatile ("csrs " #csrname ", %0" : : "r" (data)); \ +}) + +#define _clear_csr(csrname, data) ({ \ + asm volatile ("csrc " #csrname ", %0" : : "r" (data)); \ +}) + +#define _read_write_csr(csrname, data) ({ \ + uint32_t __csr_tmp_u32; \ + asm volatile ("csrrw %0, " #csrname ", %1" : "=r" (__csr_tmp_u32) : "r" (data)); \ + __csr_tmp_u32; \ +}) + +#define _read_set_csr(csrname, data) ({ \ + uint32_t __csr_tmp_u32; \ + asm volatile ("csrrs %0, " #csrname ", %1" : "=r" (__csr_tmp_u32) : "r" (data)); \ + __csr_tmp_u32; \ +}) + +#define _read_clear_csr(csrname, data) ({ \ + uint32_t __csr_tmp_u32; \ + asm volatile ("csrrc %0, " #csrname ", %1" : "=r" (__csr_tmp_u32) : "r" (data)); \ + __csr_tmp_u32; \ +}) + +// Argument macro expansion layer +#define read_csr(csrname) _read_csr(csrname) +#define write_csr(csrname, data) _write_csr(csrname, data) +#define set_csr(csrname, data) _set_csr(csrname, data) +#define clear_csr(csrname, data) _clear_csr(csrname, data) +#define read_write_csr(csrname, data) _read_write_csr(csrname, data) +#define read_set_csr(csrname, data) _read_set_csr(csrname, data) +#define read_clear_csr(csrname, data) _read_clear_csr(csrname, data) + +#endif diff --git a/vendor/Hazard3/test/sim/common/hazard3_instr.h b/vendor/Hazard3/test/sim/common/hazard3_instr.h new file mode 100644 index 0000000..f360d7e --- /dev/null +++ b/vendor/Hazard3/test/sim/common/hazard3_instr.h @@ -0,0 +1,32 @@ +#ifndef _HAZARD3_INSTR_H +#define _HAZARD3_INSTR_H + +#include + +// C macros for Hazard3 custom instructions + +// nbits must be a constant expression +#define __hazard3_bextm(nbits, rs1, rs2) ({\ + uint32_t __h3_bextm_rd; \ + asm (".insn r 0x0b, 0, %3, %0, %1, %2"\ + : "=r" (__h3_bextm_rd) \ + : "r" (rs1), "r" (rs2), "i" ((((nbits) - 1) & 0x7) << 1)\ + ); \ + __h3_bextm_rd; \ +}) + +// nbits and shamt must be constant expressions +#define __hazard3_bextmi(nbits, rs1, shamt) ({\ + uint32_t __h3_bextmi_rd; \ + asm (".insn i 0x0b, 0x4, %0, %1, %2"\ + : "=r" (__h3_bextmi_rd) \ + : "r" (rs1), "i" ((((nbits) - 1) & 0x7) << 6 | ((shamt) & 0x1f)) \ + ); \ + __h3_bextmi_rd; \ +}) + +#define __hazard3_block() asm ("slt x0, x0, x0" : : : "memory") + +#define __hazard3_unblock() asm ("slt x0, x0, x1" : : : "memory") + +#endif diff --git a/vendor/Hazard3/test/sim/common/hazard3_irq.h b/vendor/Hazard3/test/sim/common/hazard3_irq.h new file mode 100644 index 0000000..51ff599 --- /dev/null +++ b/vendor/Hazard3/test/sim/common/hazard3_irq.h @@ -0,0 +1,98 @@ +#ifndef _HAZARD3_IRQ_H +#define _HAZARD3_IRQ_H + +#include "hazard3_csr.h" +#include "stdint.h" +#include "stdbool.h" + +// Should match processor configuration in testbench: +#define NUM_IRQS 32 +#define MAX_PRIORITY 15 + +// Declarations for irq_dispatch.S +extern uintptr_t _external_irq_table[NUM_IRQS]; +extern uint32_t _external_irq_entry_count; + +#define h3irq_array_read(csr, index) (read_set_csr(csr, (index)) >> 16) + +#define h3irq_array_write(csr, index, data) (write_csr(csr, (index) | ((uint32_t)(data) << 16))) +#define h3irq_array_set(csr, index, data) (set_csr(csr, (index) | ((uint32_t)(data) << 16))) +#define h3irq_array_clear(csr, index, data) (clear_csr(csr, (index) | ((uint32_t)(data) << 16))) + +static inline void h3irq_enable(unsigned int irq, bool enable) { + if (enable) { + h3irq_array_set(hazard3_csr_meiea, irq >> 4, 1u << (irq & 0xfu)); + } + else { + h3irq_array_clear(hazard3_csr_meiea, irq >> 4, 1u << (irq & 0xfu)); + } +} + +static inline bool h3irq_pending(unsigned int irq) { + return h3irq_array_read(hazard3_csr_meipa, irq >> 4) & (1u << (irq & 0xfu)); +} + +static inline void h3irq_force_pending(unsigned int irq, bool force) { + if (force) { + h3irq_array_set(hazard3_csr_meifa, irq >> 4, 1u << (irq & 0xfu)); + } + else { + h3irq_array_clear(hazard3_csr_meifa, irq >> 4, 1u << (irq & 0xfu)); + } +} + +static inline bool h3irq_is_forced(unsigned int irq) { + return h3irq_array_read(hazard3_csr_meifa, irq >> 4) & (1u << (irq & 0xfu)); +} + +// -1 for no IRQ +static inline int h3irq_get_current_irq() { + uint32_t meicontext = read_csr(hazard3_csr_meicontext); + return meicontext & 0x8000u ? -1 : (meicontext >> 4) & 0x1ffu; +} + +static inline void h3irq_set_priority(unsigned int irq, uint32_t priority) { + // Don't want read-modify-write, but no instruction for atomically writing + // a bitfield. So, first drop priority to minimum, then set to the target + // value. It should be safe to drop an IRQ's priority below its current + // even from within that IRQ (but it is never safe to boost an IRQ when + // it may already be in an older stack frame) + h3irq_array_clear(hazard3_csr_meipra, irq >> 2, 0xfu << (4 * (irq & 0x3))); + h3irq_array_set(hazard3_csr_meipra, irq >> 2, (priority & 0xfu) << (4 * (irq & 0x3))); +} + +static inline void h3irq_set_handler(unsigned int irq, void (*handler)(void)) { + _external_irq_table[irq] = (uintptr_t)handler; +} + +static inline void global_irq_enable(bool en) { + // mstatus.mie + if (en) { + set_csr(mstatus, 0x8); + } + else { + clear_csr(mstatus, 0x8); + } +} + +static inline void external_irq_enable(bool en) { + // mie.meie + if (en) { + set_csr(mie, 0x800); + } + else { + clear_csr(mie, 0x800); + } +} + +static inline void timer_irq_enable(bool en) { + // mie.mtie + if (en) { + set_csr(mie, 0x080); + } + else { + clear_csr(mie, 0x080); + } +} + +#endif diff --git a/vendor/Hazard3/test/sim/common/init.S b/vendor/Hazard3/test/sim/common/init.S new file mode 100644 index 0000000..e9aae9b --- /dev/null +++ b/vendor/Hazard3/test/sim/common/init.S @@ -0,0 +1,234 @@ +#include "hazard3_csr.h" + +#define IO_BASE 0xc0000000 +#define IO_PRINT_CHAR (IO_BASE + 0x0) +#define IO_PRINT_U32 (IO_BASE + 0x4) +#define IO_EXIT (IO_BASE + 0x8) + +// Provide trap vector table, reset handler and weak default trap handlers for +// Hazard3. This is not a crt0: the reset handler calls an external _start + +.option push +.option norelax +.file 1 "vendor/Hazard3/test/sim/common/init.S" +.section .vectors,"ax",@progbits + +.macro VEC name:req +.p2align 2 +j \name +.p2align 2 +.endm + +// ---------------------------------------------------------------------------- +// Vector table (must be at least aligned to its size rounded up to power of 2) + +.p2align 12 +.vector_table: + +// Single exception vector, also takes IRQs if vectoring is disabled + + VEC handle_exception + +// Standard interrupts, if vectoring is enabled +// Note: global EIRQ does not fire. Instead we have 16 separate vectors + + // handle_exception ^^^ takes the slot where U-mode softirq would be + VEC .halt + VEC .halt + VEC isr_machine_softirq + VEC .halt + VEC .halt + VEC .halt + VEC isr_machine_timer + VEC .halt + VEC .halt + VEC .halt + VEC isr_external_irq + VEC .halt + VEC .halt + VEC .halt + VEC .halt + +// ---------------------------------------------------------------------------- +// Reset handler + + +.reset_handler: + // Set counters running, as they are off by default. This may trap if counters + // are unimplemented, so catch the trap and continue. + .loc 1 59 0 ; la a0, 1f + .loc 1 60 0 ; csrw mtvec, a0 + .loc 1 61 0 ; csrci mcountinhibit, 0x5 + .loc 1 62 0 ; j 2f +.p2align 2 +1: + .loc 1 65 0 ; csrw mcause, zero +2: + + // Set up trap vector table. mtvec LSB enables vectoring + .loc 1 69 0 ; la a0, .vector_table + 1 + .loc 1 70 0 ; csrw mtvec, a0 + + // Ensure gp is initialised on all cores -- don't wait for newlib _start + // as that is core-0-only +.option push +.option norelax + .loc 1 76 0 ; la gp, __global_pointer$ +.option pop + + // Put spare cores to sleep before setting up core 0 stack + // Note csrr is a NOP when there are no CSRs at all (no traps!): + .loc 1 81 0 ; li a0, 0 + .loc 1 82 0 ; csrr a0, mhartid + .loc 1 83 0 ; bnez a0, .core1_wait + + // Set up stack pointer before doing anything else + .loc 1 86 0 ; la sp, __stack_top + + // newlib _start expects argc, argv on the stack. Leave stack 16-byte aligned. + .loc 1 89 0 ; addi sp, sp, -16 + .loc 1 90 0 ; li a0, 1 + .loc 1 91 0 ; sw a0, (sp) + .loc 1 92 0 ; la a0, progname + .loc 1 93 0 ; sw a0, 4(sp) + + .loc 1 95 0 ; jal _start + .loc 1 96 0 ; j .halt + +.core1_wait: + // IRQs disabled, but soft IRQ unmasked -> soft IRQ will exit WFI. + csrci mstatus, 0x8 + csrw mie, 0x8 +.core1_wait_loop: + wfi + la a0, core1_entry_vector + lw a0, (a0) + beqz a0, .core1_wait_loop + la sp, __stack_top - 0x10000 + jalr a0 +.core1_finish: + wfi + j .core1_finish + +.p2align 2 +.global core1_entry_vector +core1_entry_vector: + .word 0 + +.global _exit +_exit: + li a1, IO_EXIT + sw a0, (a1) + +.global _sbrk +_sbrk: + la a1, heap_ptr + lw a2, (a1) + add a0, a0, a2 + sw a0, (a1) + mv a0, a2 + ret + +.p2align 2 +heap_ptr: + .word _end + +.global .halt +.halt: + j .halt + +progname: + .asciz "hazard3-testbench" + +.p2align 2 + +// ---------------------------------------------------------------------------- +// Weak handler/ISR symbols + +// Routine to print out trap name, trap address, and some core registers +// (x8..x15, ra, sp). The default handlers are all patched into this routine, +// so the CPU will print some basic diagnostics on any unhandled trap +// (assuming the processor is not internally completely broken) + +// argument in t0, IO pointer in t1, return in tp, trashes t0 and t2; +_tb_puts: +1: + lbu t2, (t0) + addi t0, t0, 1 + beqz t2, 2f + sw t2, IO_PRINT_CHAR - IO_BASE(t1) + j 1b +2: + jr tp + +.macro print_reg str reg + la t0, \str + jal tp, _tb_puts + sw \reg, IO_PRINT_U32 - IO_BASE(t1) +.endm + +_weak_handler_name_in_gp: + la t0, _str_unhandled_trap + li t1, IO_BASE + jal tp, _tb_puts + mv t0, gp + jal tp, _tb_puts + la t0, _str_at_mepc + jal tp, _tb_puts + csrr t0, mepc + sw t0, IO_PRINT_U32 - IO_BASE(t1) + csrr gp, mcause + bltz gp, 1f + print_reg _str_mcause gp +1: + print_reg _str_s0 s0 + print_reg _str_s1 s1 + print_reg _str_a0 a0 + print_reg _str_a1 a1 + print_reg _str_a2 a2 + print_reg _str_a3 a3 + print_reg _str_a4 a4 + print_reg _str_a5 a5 + print_reg _str_ra ra + print_reg _str_sp sp + li t2, -1 + sw t2, IO_EXIT - IO_BASE(t1) + // Should be unreachable: + j .halt + +_str_unhandled_trap: .asciz "*** Unhandled trap ***\n" +_str_at_mepc: .asciz " @ mepc = " +_str_mcause: .asciz " mcause = " +_str_s0: .asciz "s0: " +_str_s1: .asciz "s1: " +_str_a0: .asciz "a0: " +_str_a1: .asciz "a1: " +_str_a2: .asciz "a2: " +_str_a3: .asciz "a3: " +_str_a4: .asciz "a4: " +_str_a5: .asciz "a5: " +_str_ra: .asciz "ra: " +_str_sp: .asciz "sp: " +.p2align 2 + +// Provide a default weak handler for each trap, which calls into the above +// diagnostic routine with the trap name (a null-terminated string) in gp + +.macro weak_handler name:req +.p2align 2 +.global \name +.weak \name +\name: + la gp, _str_\name + j _weak_handler_name_in_gp +_str_\name: + .asciz "\name" +.endm + +weak_handler handle_exception +weak_handler isr_machine_softirq +weak_handler isr_machine_timer +weak_handler isr_external_irq + +// You can relax now +.option pop diff --git a/vendor/Hazard3/test/sim/common/irq_dispatch.S b/vendor/Hazard3/test/sim/common/irq_dispatch.S new file mode 100644 index 0000000..518fe05 --- /dev/null +++ b/vendor/Hazard3/test/sim/common/irq_dispatch.S @@ -0,0 +1,135 @@ +#include "hazard3_csr.h" + +.global isr_external_irq +isr_external_irq: + // Save caller saves and exception return state whilst IRQs are disabled. + // We can't be pre-empted during this time, but if a higher-priority IRQ + // arrives ("late arrival"), that will be the one displayed in meinext. + addi sp, sp, -80 + sw ra, 0(sp) + sw t0, 4(sp) + sw t1, 8(sp) + sw t2, 12(sp) + sw a0, 16(sp) + sw a1, 20(sp) + sw a2, 24(sp) + sw a3, 28(sp) + sw a4, 32(sp) + sw a5, 36(sp) +#if __riscv_i + sw a6, 40(sp) + sw a7, 44(sp) + sw t3, 48(sp) + sw t4, 52(sp) + sw t5, 56(sp) + sw t6, 60(sp) +#endif + + // Update a count of the number of external IRQ vector entries (just for + // use in tests) + la a0, _external_irq_entry_count + lw a1, (a0) + addi a1, a1, 1 + sw a1, (a0) + // Make sure to delete the above ^^^ if you use this code for real! + + csrr a0, mepc + sw a0, 64(sp) + // Make sure to set meicontext.clearts to clear and save mie.msie/mtie + // when saving context. + csrrsi a0, hazard3_csr_meicontext, 0x2 + sw a0, 68(sp) + csrr a0, mstatus + sw a0, 72(sp) + + j get_next_irq + +dispatch_irq: + // Preemption priority was configured by meinext update, so enable preemption: + csrsi mstatus, 0x8 + // meinext is pre-shifted by 2, so only an add is required to index table + la a1, _external_irq_table + add a1, a1, a0 + lw a1, (a1) + jalr ra, a1 + + // Disable IRQs on returning so we can sample the next IRQ + csrci mstatus, 0x8 + +get_next_irq: + // Sample the current highest-priority active IRQ (left-shifted by 2) from + // meinext, and write 1 to the LSB to tell hardware to tell hw to update + // meicontext with the preemption priority (and IRQ number) of this IRQ + csrrsi a0, hazard3_csr_meinext, 0x1 + // MSB will be set if there is no active IRQ at the current priority level + bgez a0, dispatch_irq + +no_more_irqs: + // Restore saved context and return from IRQ + lw a0, 64(sp) + csrw mepc, a0 + lw a0, 68(sp) + csrw hazard3_csr_meicontext, a0 + lw a0, 72(sp) + csrw mstatus, a0 + + lw ra, 0(sp) + lw t0, 4(sp) + lw t1, 8(sp) + lw t2, 12(sp) + lw a0, 16(sp) + lw a1, 20(sp) + lw a2, 24(sp) + lw a3, 28(sp) + lw a4, 32(sp) + lw a5, 36(sp) +#if __riscv_i + lw a6, 40(sp) + lw a7, 44(sp) + lw t3, 48(sp) + lw t4, 52(sp) + lw t5, 56(sp) + lw t6, 60(sp) +#endif + addi sp, sp, 80 + mret + +// ------------------------------------------------------------ +// Handler table and default handler symbols + +// Provide weak symbol for all IRQs, pointing to a breakpoint instruction: + +.macro decl_eirq num +.weak isr_irq\num +isr_irq\num: +.endm + +.macro ref_eirq num +.word isr_irq\num +.endm + +#define NUM_IRQS 32 + +.equ i, 0 +.rept NUM_IRQS +decl_eirq i +.equ i, i + 1 +.endr + ebreak + +// Soft vector table is preloaded to RAM, and by default contains the weak ISR +// symbols, but can also be patched at runtime: + +.section .data +.global _external_irq_table +_external_irq_table: + +.equ i, 0 +.rept NUM_IRQS +ref_eirq i +.equ i, i + 1 +.endr + +.global _external_irq_entry_count +_external_irq_entry_count: +.word 0 diff --git a/vendor/Hazard3/test/sim/common/link_hazard3.ld b/vendor/Hazard3/test/sim/common/link_hazard3.ld new file mode 100644 index 0000000..e784517 --- /dev/null +++ b/vendor/Hazard3/test/sim/common/link_hazard3.ld @@ -0,0 +1,48 @@ +OUTPUT_FORMAT("elf32-littleriscv") +OUTPUT_ARCH(riscv) +ENTRY(_start) + +SECTIONS +{ + /* Hazard3 testbench RAM window */ + . = 0x80000000; + PROVIDE(__stack_top = 0x80100000); + + /* Reset/trap vectors are in Hazard3 init.S (.vectors). Keep them first so + .reset_handler lands at 0x80000040 (Hazard3 RESET_VECTOR). */ + .text : ALIGN(4) + { + KEEP(*(.vectors)) + *(.text .text.*) + } + + /* Keep constants non-executable. Merging arbitrary-length strings into + .text makes some RISC-V objdump versions try to decode the final partial + instruction and abort instead of producing the teaching listing. */ + .rodata : ALIGN(4) + { + *(.rodata .rodata.*) + } + + .data : ALIGN(4) + { + __data_start = .; + *(.data .data.*) + *(.sdata .sdata.*) + __data_end = .; + } + + .bss : ALIGN(4) + { + __bss_start = .; + *(.bss .bss.* COMMON) + *(.sbss .sbss.*) + __bss_end = .; + } + + /* Conservative default (good enough for this ASM-only demo). */ + __global_pointer$ = __data_start + 0x800; + + _end = .; + PROVIDE(end = .); +} diff --git a/vendor/Hazard3/test/sim/common/memmap.ld b/vendor/Hazard3/test/sim/common/memmap.ld new file mode 100644 index 0000000..716c952 --- /dev/null +++ b/vendor/Hazard3/test/sim/common/memmap.ld @@ -0,0 +1,275 @@ +/* Script for -z combreloc */ +/* Copyright (C) 2014-2025 Free Software Foundation, Inc. + Copying and distribution of this script, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. */ +OUTPUT_FORMAT("elf32-littleriscv", "elf32-littleriscv", "elf32-littleriscv") +OUTPUT_ARCH(riscv) +ENTRY(_start) +SEARCH_DIR("/opt/riscv/gcc15/riscv32-unknown-elf/lib"); +SECTIONS +{ + . = 0x80000000; + PROVIDE(__stack_top = 0x80100000); + /* Place the build-id as close to the ELF headers as possible. This + maximises the chance the build-id will be present in core files, + which GDB can then use to locate the associated debuginfo file. */ + .interp : { *(.interp) } + .hash : { *(.hash) } + .gnu.hash : { *(.gnu.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rela.dyn : + { + *(.rela.init) + *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) + *(.rela.fini) + *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) + *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) + *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) + *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) + *(.rela.ctors) + *(.rela.dtors) + *(.rela.got) + *(.rela.sdata .rela.sdata.* .rela.gnu.linkonce.s.*) + *(.rela.sbss .rela.sbss.* .rela.gnu.linkonce.sb.*) + *(.rela.sdata2 .rela.sdata2.* .rela.gnu.linkonce.s2.*) + *(.rela.sbss2 .rela.sbss2.* .rela.gnu.linkonce.sb2.*) + *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) + *(.rela.ifunc) + } + .rela.plt : + { + *(.rela.plt) + PROVIDE_HIDDEN (__rela_iplt_start = .); + *(.rela.iplt) + PROVIDE_HIDDEN (__rela_iplt_end = .); + } + /* Start of the executable code region. */ + .init : + { + KEEP (*(SORT_NONE(.init))) + } + .plt : { *(.plt) *(.iplt) } + .text : + { + KEEP(*(.vectors)) + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.exit .text.exit.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(SORT(.text.sorted.*)) + *(.text .stub .text.* .gnu.linkonce.t.*) + /* .gnu.warning sections are handled specially by elf.em. */ + *(.gnu.warning) + } + .fini : + { + KEEP (*(SORT_NONE(.fini))) + } + PROVIDE (__etext = .); + PROVIDE (_etext = .); + PROVIDE (etext = .); + /* Start of the Read Only Data region. */ + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } + .rodata1 : { *(.rodata1) } + .sdata2 : + { + *(.sdata2 .sdata2.* .gnu.linkonce.s2.*) + } + .sbss2 : { *(.sbss2 .sbss2.* .gnu.linkonce.sb2.*) } + .eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .sframe : ONLY_IF_RO { *(.sframe) *(.sframe.*) } + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } + /* These sections are generated by the Sun/Oracle C++ compiler. */ + .exception_ranges : ONLY_IF_RO { *(.exception_ranges*) } + /* Various note sections. Placed here so that they are always included + in the read-only segment and not treated as orphan sections. The + current orphan handling algorithm does place note sections after R/O + data, but this is not guaranteed to always be the case. */ + .note.build-id : { *(.note.build-id) } + .note.GNU-stack : { *(.note.GNU-stack) } + .note.gnu-property : { *(.note.gnu-property) } + .note.ABI-tag : { *(.note.ABI-tag) } + .note.package : { *(.note.package) } + .note.dlopen : { *(.note.dlopen) } + .note.netbsd.ident : { *(.note.netbsd.ident) } + .note.openbsd.ident : { *(.note.openbsd.ident) } + /* Start of the Read Write Data region. */ + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); + /* Exception handling. */ + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .sframe : ONLY_IF_RW { *(.sframe) *(.sframe.*) } + .gnu_extab : ONLY_IF_RW { *(.gnu_extab) } + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } + .exception_ranges : ONLY_IF_RW { *(.exception_ranges*) } + /* Thread Local Storage sections. */ + .tdata : + { + PROVIDE_HIDDEN (__tdata_start = .); + *(.tdata .tdata.* .gnu.linkonce.td.*) + } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors)) + PROVIDE_HIDDEN (__init_array_end = .); + } + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) + KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors)) + PROVIDE_HIDDEN (__fini_array_end = .); + } + .ctors : + { + /* gcc uses crtbegin.o to find the start of + the constructors, so we make sure it is + first. Because this is a wildcard, it + doesn't matter if the user does not + actually link against crtbegin.o; the + linker won't look for a file to match a + wildcard. The wildcard also means that it + doesn't matter which directory crtbegin.o + is in. */ + KEEP (*crtbegin.o(.ctors)) + KEEP (*crtbegin?.o(.ctors)) + /* We don't want to include the .ctor section from + the crtend.o file until after the sorted ctors. + The .ctor section from the crtend file contains the + end of ctors marker and it must be last */ + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*crtbegin?.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } + .jcr : { KEEP (*(.jcr)) } + .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) } + .dynamic : { *(.dynamic) } + .got : { *(.got) *(.igot) } + . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 8 ? 8 : 0, .); + .got.plt : { *(.got.plt) *(.igot.plt) } + .data : + { + __DATA_BEGIN__ = .; + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } + .data1 : { *(.data1) } + /* We want the small data sections together, so single-instruction offsets + can access them all, and initialized data all before uninitialized, so + we can shorten the on-disk segment size. */ + .sdata : + { + __SDATA_BEGIN__ = .; + *(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*) + *(.sdata .sdata.* .gnu.linkonce.s.*) + } + _edata = .; + PROVIDE (edata = .); + . = ALIGN(ALIGNOF(NEXT_SECTION)); + __bss_start = .; + .sbss : + { + *(.dynsbss) + *(.sbss .sbss.* .gnu.linkonce.sb.*) + *(.scommon) + } + .bss : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + /* Align here to ensure that in the common case of there only being one + type of .bss section, the section occupies space up to _end. + Align after .bss to ensure correct alignment even if the + .bss section disappears because there are no input sections. + FIXME: Why do we need it? When there is no .bss section, we do not + pad the .data section. */ + . = ALIGN(. != 0 ? 32 / 8 : 1); + } + . = ALIGN(32 / 8); + /* Start of the Large Data region. */ + . = SEGMENT_START("ldata-segment", .); + . = ALIGN(32 / 8); + __BSS_END__ = .; + __global_pointer$ = MIN(__SDATA_BEGIN__ + 0x800, + MAX(__DATA_BEGIN__ + 0x800, __BSS_END__ - 0x800)); + _end = .; + PROVIDE (end = .); + . = DATA_SEGMENT_END (.); + /* Start of the Tiny Data region. */ + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 (INFO) : { *(.comment); LINKER_VERSION; } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1. */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions. */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2. */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2. */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions. */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3. */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF 5. */ + .debug_addr 0 : { *(.debug_addr) } + .debug_line_str 0 : { *(.debug_line_str) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_macro 0 : { *(.debug_macro) } + .debug_names 0 : { *(.debug_names) } + .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_str_offsets 0 : { *(.debug_str_offsets) } + .debug_sup 0 : { *(.debug_sup) } + .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) } + /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) *(.gnu_object_only) } +} + + diff --git a/vendor/Hazard3/test/sim/common/multilib-gen-gen.py b/vendor/Hazard3/test/sim/common/multilib-gen-gen.py new file mode 100755 index 0000000..28dc2ef --- /dev/null +++ b/vendor/Hazard3/test/sim/common/multilib-gen-gen.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +# Generate a multilib configure line for riscv-gnu-toolchain with useful +# combinations of extensions supported by both Hazard3 and mainline GCC +# (currently GCC 14). Use as: +# ./configure ... --with-multilib-generator="$(path/to/multilib-gen-gen.py)" + +base = "rv32i" +abi = "-ilp32--" +options = [ + "m", + "a", + "c", + "zba", + "zbb", + "zbc", + "zbs", + "zbkb", + "zbkx", + "zca", + "zcb", + "zcmp", + "zmmul" +] + +# Do not build for LHS except when *all of* RHS is also present. This cuts +# down on the number of configurations. A leading "!" means antidependency, +# i.e. an incompatibility. +depends_on = { + "m": ["!zmmul" ], + "zmmul": ["!m" ], + "zbb": ["m", "zba", "zbs" ], + "zba": ["m", "zbb", "zbs" ], + "zbs": ["m", "zba", "zbb" ], + "zbkb": ["zbb" ], + "zbc": ["zba", "zbb", "zbs", "zbkb"], + "zbkx": ["zba", "zbb", "zbs", "zbkb"], + "zifencei": ["zicsr" ], + "c": ["!zca" ], + "zca": ["!c" ], + "zcb": ["zca" ], + "zcmp": ["zca", "zcb", ], +} + +l = [] +for i in range(2 ** len(options)): + isa = base + violates_dependencies = False + for j in (j for j in range(len(options)) if i & (1 << j)): + opt = options[j] + if opt in depends_on: + for dep in depends_on[opt]: + inverted_dep = dep.startswith("!") + if inverted_dep: dep = dep[1:] + if inverted_dep == bool(i & (1 << options.index(dep))): + violates_dependencies = True + break + if violates_dependencies: + break + if len(opt) > 1: + isa += "_" + isa += opt + isa += "_zicsr_zifencei" + if not violates_dependencies: + l.append(isa + abi) + +# Bonus RV32E configs: +l.append("rv32e_zicsr_zifencei-ilp32e--") +l.append("rv32ema_zicsr_zifencei-ilp32e--") +l.append("rv32emac_zicsr_zifencei-ilp32e--") +l.append("rv32ema_zicsr_zifencei_zba_zbb_zbc_zbkb_zbkx_zbs_zca_zcb_zcmp-ilp32e--") + +print(";".join(l)) + +print(len(l)) diff --git a/vendor/Hazard3/test/sim/common/src_only_app.mk b/vendor/Hazard3/test/sim/common/src_only_app.mk new file mode 100644 index 0000000..6507a68 --- /dev/null +++ b/vendor/Hazard3/test/sim/common/src_only_app.mk @@ -0,0 +1,55 @@ +ifndef SRCS +$(error Must define list of test sources as SRCS) +endif + +ifndef APP +$(error Must define application name as APP) +endif + +DOTF ?= tb.f +CCFLAGS ?= +LDSCRIPT ?= ../common/memmap.ld +CROSS_PREFIX ?= riscv32-unknown-elf- +TBEXEC ?= ../tb_cxxrtl/tb +TBDIR := $(dir $(abspath $(TBEXEC))) +INCDIR ?= ../common +MAX_CYCLES ?= 100000 +TMP_PREFIX ?= tmp/ + +# Useless: +override CCFLAGS += -Wl,--no-warn-rwx-segments + +############################################################################### + +.SUFFIXES: +.PHONY: all run view tb clean clean_tb + +all: run + +run: $(TMP_PREFIX)$(APP).bin + $(TBEXEC) --bin $(TMP_PREFIX)$(APP).bin --vcd $(TMP_PREFIX)$(APP)_run.vcd --cycles $(MAX_CYCLES) + +view: run + gtkwave $(TMP_PREFIX)$(APP)_run.vcd + +bin: $(TMP_PREFIX)$(APP).bin + +tb: + $(MAKE) -C $(TBDIR) DOTF=$(DOTF) + +clean: + rm -rf $(TMP_PREFIX) + +clean_tb: clean + $(MAKE) -C $(TBDIR) clean + +############################################################################### + +$(TMP_PREFIX)$(APP).bin: $(TMP_PREFIX)$(APP).elf + $(CROSS_PREFIX)objcopy -O binary $^ $@ + $(CROSS_PREFIX)objdump -h $^ > $(TMP_PREFIX)$(APP).dis + $(CROSS_PREFIX)objdump -d $^ >> $(TMP_PREFIX)$(APP).dis + +$(TMP_PREFIX)$(APP).elf: $(SRCS) $(wildcard %.h) + mkdir -p $(TMP_PREFIX) + $(CROSS_PREFIX)gcc $(CCFLAGS) $(SRCS) -T $(LDSCRIPT) $(addprefix -I,$(INCDIR)) -o $@ diff --git a/vendor/Hazard3/test/sim/common/tb_cxxrtl_io.h b/vendor/Hazard3/test/sim/common/tb_cxxrtl_io.h new file mode 100644 index 0000000..d464aca --- /dev/null +++ b/vendor/Hazard3/test/sim/common/tb_cxxrtl_io.h @@ -0,0 +1,117 @@ +#ifndef _TB_CXXRTL_IO_H +#define _TB_CXXRTL_IO_H + +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- +// Testbench IO hardware layout + +#define IO_BASE 0xc0000000 + +typedef struct { + volatile uint32_t print_char; + volatile uint32_t print_u32; + volatile uint32_t exit; + uint32_t _pad0; + volatile uint32_t set_softirq; + volatile uint32_t clr_softirq; + volatile uint32_t globmon_en; + volatile uint32_t poison_addr; + volatile uint32_t set_irq; + uint32_t _pad2[3]; + volatile uint32_t clr_irq; + uint32_t _pad3[3]; +} io_hw_t; + +#define mm_io ((io_hw_t *const)IO_BASE) + +typedef struct { + volatile uint32_t mtime; + volatile uint32_t mtimeh; + volatile uint32_t mtimecmp; + volatile uint32_t mtimecmph; +} timer_hw_t; + +#define mm_timer ((timer_hw_t *const)(IO_BASE + 0x100)) + +// ---------------------------------------------------------------------------- +// Testbench IO convenience functions + +static inline void tb_putc(char c) { + mm_io->print_char = (uint32_t)c; +} + +static inline void tb_puts(const char *s) { + while (*s) + tb_putc(*s++); +} + +static inline void tb_put_u32(uint32_t x) { + mm_io->print_u32 = x; +} + +static inline void tb_exit(uint32_t ret) { + mm_io->exit = ret; +} + +#ifndef PRINTF_BUF_SIZE +#define PRINTF_BUF_SIZE 256 +#endif + +static inline void tb_printf(const char *fmt, ...) { + char buf[PRINTF_BUF_SIZE]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, PRINTF_BUF_SIZE, fmt, args); + tb_puts(buf); + va_end(args); +} + +#define tb_assert(cond, ...) if (!(cond)) {tb_printf(__VA_ARGS__); tb_exit(-1);} + +static inline void tb_set_softirq(int idx) { + mm_io->set_softirq = 1u << idx; +} + +static inline void tb_clr_softirq(int idx) { + mm_io->clr_softirq = 1u << idx; +} + +static inline bool tb_get_softirq(int idx) { + return (bool)(mm_io->set_softirq & (1u << idx)); +} + +static inline void tb_enable_global_monitor(bool en) { + mm_io->globmon_en = en; +} + +// Set an address to generate faults on any access +static inline void tb_set_poison_addr(uint32_t addr) { + asm volatile ("fence" : : : "memory"); + mm_io->poison_addr = addr; + asm volatile ("fence" : : : "memory"); +} + +static inline void tb_set_irq_masked(uint32_t mask) { + mm_io->set_irq = mask; +} + +static inline void tb_clr_irq_masked(uint32_t mask) { + mm_io->clr_irq = mask; +} + +static inline uint32_t tb_get_irq_mask() { + return mm_io->set_irq; +} + +extern volatile uintptr_t core1_entry_vector; + +static inline void tb_launch_core1(void (*entry)(void)) { + core1_entry_vector = (uintptr_t)entry; + tb_set_softirq(1); +} + +#endif diff --git a/vendor/Hazard3/test/sim/common/tb_uart.hpp b/vendor/Hazard3/test/sim/common/tb_uart.hpp new file mode 100644 index 0000000..b028d6a --- /dev/null +++ b/vendor/Hazard3/test/sim/common/tb_uart.hpp @@ -0,0 +1,34 @@ +#ifndef _TB_UART_HPP +#define _TB_UART_HPP + +#include + +extern "C" { +#include "tb_uart_io.h" +} + +class TbUart { +public: + explicit constexpr TbUart(unsigned index) : index_(index) {} + + void write(char c) const { tb_uart_write(index_, static_cast(c)); } + + void write(const char *str) const { + while (*str) + write(*str++); + } + + bool connected() const { return tb_uart_connected(index_); } + bool canRead() const { return tb_uart_can_read(index_); } + + // Returns [0..255] on success, or -1 if RX FIFO is empty. + int tryRead() const { return tb_uart_try_read(index_); } + + void clearOverrun() const { tb_uart_clear_overrun(index_); } + +private: + unsigned index_; +}; + +#endif + diff --git a/vendor/Hazard3/test/sim/common/tb_uart_io.h b/vendor/Hazard3/test/sim/common/tb_uart_io.h new file mode 100644 index 0000000..bc4829b --- /dev/null +++ b/vendor/Hazard3/test/sim/common/tb_uart_io.h @@ -0,0 +1,61 @@ +#ifndef _TB_UART_IO_H +#define _TB_UART_IO_H + +#include +#include + +// Testbench UART-over-TCP MMIO (see tb_common/include/tb_constants.h). +// +// Each UART is exposed as a raw TCP byte stream (one client at a time). +// Software should poll STATUS.RX_AVAIL before reading DATA. + +#ifndef TB_IO_BASE +#define TB_IO_BASE 0xC0000000u +#endif + +#define TB_UART_BASE 0x200u +#define TB_UART_STRIDE 0x20u + +#define TB_UART_DATA 0x00u +#define TB_UART_STATUS 0x04u +#define TB_UART_CTRL 0x08u + +#define TB_UART_STATUS_RX_AVAIL (1u << 0) +#define TB_UART_STATUS_TX_READY (1u << 1) +#define TB_UART_STATUS_CONNECTED (1u << 2) +#define TB_UART_STATUS_OVERRUN (1u << 3) + +#define TB_UART_CTRL_CLR_OVERRUN (1u << 0) + +static inline volatile uint32_t *tb_uart_reg(unsigned uart_idx, unsigned reg_off) { + return (volatile uint32_t *)(TB_IO_BASE + TB_UART_BASE + (uart_idx * TB_UART_STRIDE) + reg_off); +} + +static inline uint32_t tb_uart_status(unsigned uart_idx) { + return *tb_uart_reg(uart_idx, TB_UART_STATUS); +} + +static inline bool tb_uart_connected(unsigned uart_idx) { + return (tb_uart_status(uart_idx) & TB_UART_STATUS_CONNECTED) != 0; +} + +static inline bool tb_uart_can_read(unsigned uart_idx) { + return (tb_uart_status(uart_idx) & TB_UART_STATUS_RX_AVAIL) != 0; +} + +static inline int tb_uart_try_read(unsigned uart_idx) { + if (!tb_uart_can_read(uart_idx)) + return -1; + return (int)(*tb_uart_reg(uart_idx, TB_UART_DATA) & 0xffu); +} + +static inline void tb_uart_write(unsigned uart_idx, uint8_t byte) { + *tb_uart_reg(uart_idx, TB_UART_DATA) = (uint32_t)byte; +} + +static inline void tb_uart_clear_overrun(unsigned uart_idx) { + *tb_uart_reg(uart_idx, TB_UART_CTRL) = TB_UART_CTRL_CLR_OVERRUN; +} + +#endif + diff --git a/vendor/Hazard3/test/sim/project_paths.mk b/vendor/Hazard3/test/sim/project_paths.mk new file mode 100644 index 0000000..934f996 --- /dev/null +++ b/vendor/Hazard3/test/sim/project_paths.mk @@ -0,0 +1,2 @@ +# Link up to project root +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../project_paths.mk diff --git a/vendor/Hazard3/test/sim/tb_common/hdl/config_default.vh b/vendor/Hazard3/test/sim/tb_common/hdl/config_default.vh new file mode 100644 index 0000000..cbffe48 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/hdl/config_default.vh @@ -0,0 +1,50 @@ +// Default Hazard3 config for testbench: all ISA features + +localparam RESET_VECTOR = 32'h80000040; +localparam MTVEC_INIT = 32'h80000000; +localparam EXTENSION_A = 1; +localparam EXTENSION_C = 1; +localparam EXTENSION_E = 0; +localparam EXTENSION_M = 1; +localparam EXTENSION_ZBA = 1; +localparam EXTENSION_ZBB = 1; +localparam EXTENSION_ZBC = 1; +localparam EXTENSION_ZBKB = 1; +localparam EXTENSION_ZBKX = 1; +localparam EXTENSION_ZBS = 1; +localparam EXTENSION_ZCB = 1; +localparam EXTENSION_ZCLSD = 1; +localparam EXTENSION_ZCMP = 1; +localparam EXTENSION_ZIFENCEI = 1; +localparam EXTENSION_ZILSD = 1; +localparam EXTENSION_XH3BEXTM = 1; +localparam EXTENSION_XH3IRQ = 1; +localparam EXTENSION_XH3PMPM = 1; +localparam EXTENSION_XH3POWER = 1; +localparam CSR_M_MANDATORY = 1; +localparam CSR_M_TRAP = 1; +localparam CSR_COUNTER = 1; +localparam U_MODE = 1; +localparam PMP_REGIONS = 4; +localparam PMP_GRAIN = 0; +localparam PMP_MATCH_NAPOT = 1; +localparam PMP_MATCH_TOR = 1; +localparam PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}}; +localparam PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}}; +localparam PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}}; +localparam DEBUG_SUPPORT = 1; +localparam BREAKPOINT_TRIGGERS = 4; +localparam NUM_IRQS = 32; +localparam IRQ_PRIORITY_BITS = 4; +localparam IRQ_INPUT_BYPASS = {NUM_IRQS{1'b0}}; +localparam MVENDORID_VAL = 32'hdeadbeef; +localparam MCONFIGPTR_VAL = 32'h9abcdef0; +localparam REDUCED_BYPASS = 0; +localparam MULDIV_UNROLL = 2; +localparam MUL_FAST = 1; +localparam MUL_FASTER = 1; +localparam MULH_FAST = 1; +localparam FAST_BRANCHCMP = 1; +localparam RESET_REGFILE = 1; +localparam BRANCH_PREDICTOR = 1; +localparam MTVEC_WMASK = 32'hfffffffd; diff --git a/vendor/Hazard3/test/sim/tb_common/hdl/config_default_rve.vh b/vendor/Hazard3/test/sim/tb_common/hdl/config_default_rve.vh new file mode 100644 index 0000000..1e92250 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/hdl/config_default_rve.vh @@ -0,0 +1,50 @@ +// All ISA features (but RVE) + +localparam RESET_VECTOR = 32'h80000040; +localparam MTVEC_INIT = 32'h80000000; +localparam EXTENSION_A = 1; +localparam EXTENSION_C = 1; +localparam EXTENSION_E = 1; +localparam EXTENSION_M = 1; +localparam EXTENSION_ZBA = 1; +localparam EXTENSION_ZBB = 1; +localparam EXTENSION_ZBC = 1; +localparam EXTENSION_ZBKB = 1; +localparam EXTENSION_ZBKX = 1; +localparam EXTENSION_ZBS = 1; +localparam EXTENSION_ZCB = 1; +localparam EXTENSION_ZCLSD = 1; +localparam EXTENSION_ZCMP = 1; +localparam EXTENSION_ZIFENCEI = 1; +localparam EXTENSION_ZILSD = 1; +localparam EXTENSION_XH3BEXTM = 1; +localparam EXTENSION_XH3IRQ = 1; +localparam EXTENSION_XH3PMPM = 1; +localparam EXTENSION_XH3POWER = 1; +localparam CSR_M_MANDATORY = 1; +localparam CSR_M_TRAP = 1; +localparam CSR_COUNTER = 1; +localparam U_MODE = 1; +localparam PMP_REGIONS = 4; +localparam PMP_GRAIN = 0; +localparam PMP_MATCH_NAPOT = 1; +localparam PMP_MATCH_TOR = 1; +localparam PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}}; +localparam PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}}; +localparam PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}}; +localparam DEBUG_SUPPORT = 1; +localparam BREAKPOINT_TRIGGERS = 4; +localparam NUM_IRQS = 32; +localparam IRQ_PRIORITY_BITS = 4; +localparam IRQ_INPUT_BYPASS = {NUM_IRQS{1'b0}}; +localparam MVENDORID_VAL = 32'hdeadbeef; +localparam MCONFIGPTR_VAL = 32'h9abcdef0; +localparam REDUCED_BYPASS = 0; +localparam MULDIV_UNROLL = 2; +localparam MUL_FAST = 1; +localparam MUL_FASTER = 1; +localparam MULH_FAST = 1; +localparam FAST_BRANCHCMP = 1; +localparam RESET_REGFILE = 1; +localparam BRANCH_PREDICTOR = 1; +localparam MTVEC_WMASK = 32'hfffffffd; diff --git a/vendor/Hazard3/test/sim/tb_common/hdl/config_late_mul.vh b/vendor/Hazard3/test/sim/tb_common/hdl/config_late_mul.vh new file mode 100644 index 0000000..1f37ecc --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/hdl/config_late_mul.vh @@ -0,0 +1,50 @@ +// Default Hazard3 config for testbench: all ISA features + +localparam RESET_VECTOR = 32'h80000040; +localparam MTVEC_INIT = 32'h80000000; +localparam EXTENSION_A = 1; +localparam EXTENSION_C = 1; +localparam EXTENSION_E = 0; +localparam EXTENSION_M = 1; +localparam EXTENSION_ZBA = 1; +localparam EXTENSION_ZBB = 1; +localparam EXTENSION_ZBC = 1; +localparam EXTENSION_ZBKB = 1; +localparam EXTENSION_ZBKX = 1; +localparam EXTENSION_ZBS = 1; +localparam EXTENSION_ZCB = 1; +localparam EXTENSION_ZCLSD = 1; +localparam EXTENSION_ZCMP = 1; +localparam EXTENSION_ZIFENCEI = 1; +localparam EXTENSION_ZILSD = 1; +localparam EXTENSION_XH3BEXTM = 1; +localparam EXTENSION_XH3IRQ = 1; +localparam EXTENSION_XH3PMPM = 1; +localparam EXTENSION_XH3POWER = 1; +localparam CSR_M_MANDATORY = 1; +localparam CSR_M_TRAP = 1; +localparam CSR_COUNTER = 1; +localparam U_MODE = 1; +localparam PMP_REGIONS = 4; +localparam PMP_GRAIN = 0; +localparam PMP_MATCH_NAPOT = 1; +localparam PMP_MATCH_TOR = 1; +localparam PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}}; +localparam PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}}; +localparam PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}}; +localparam DEBUG_SUPPORT = 1; +localparam BREAKPOINT_TRIGGERS = 4; +localparam NUM_IRQS = 32; +localparam IRQ_PRIORITY_BITS = 4; +localparam IRQ_INPUT_BYPASS = {NUM_IRQS{1'b0}}; +localparam MVENDORID_VAL = 32'hdeadbeef; +localparam MCONFIGPTR_VAL = 32'h9abcdef0; +localparam REDUCED_BYPASS = 0; +localparam MULDIV_UNROLL = 1; +localparam MUL_FAST = 1; +localparam MUL_FASTER = 0; +localparam MULH_FAST = 0; +localparam FAST_BRANCHCMP = 1; +localparam RESET_REGFILE = 1; +localparam BRANCH_PREDICTOR = 1; +localparam MTVEC_WMASK = 32'hfffffffd; diff --git a/vendor/Hazard3/test/sim/tb_common/hdl/config_min.vh b/vendor/Hazard3/test/sim/tb_common/hdl/config_min.vh new file mode 100644 index 0000000..c3cfa7b --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/hdl/config_min.vh @@ -0,0 +1,51 @@ +// True minimum configuration -- enough to run hello world but no support for +// traps, debug, or any non-mandatory ISA extensions. + +localparam RESET_VECTOR = 32'h80000040; +localparam MTVEC_INIT = 32'h80000000; +localparam EXTENSION_A = 0; +localparam EXTENSION_C = 0; +localparam EXTENSION_E = 0; +localparam EXTENSION_M = 0; +localparam EXTENSION_ZBA = 0; +localparam EXTENSION_ZBB = 0; +localparam EXTENSION_ZBC = 0; +localparam EXTENSION_ZBKB = 0; +localparam EXTENSION_ZBKX = 0; +localparam EXTENSION_ZBS = 0; +localparam EXTENSION_ZCB = 0; +localparam EXTENSION_ZCLSD = 0; +localparam EXTENSION_ZCMP = 0; +localparam EXTENSION_ZIFENCEI = 0; +localparam EXTENSION_ZILSD = 0; +localparam EXTENSION_XH3BEXTM = 0; +localparam EXTENSION_XH3IRQ = 0; +localparam EXTENSION_XH3PMPM = 0; +localparam EXTENSION_XH3POWER = 0; +localparam CSR_M_MANDATORY = 0; +localparam CSR_M_TRAP = 0; +localparam CSR_COUNTER = 0; +localparam U_MODE = 0; +localparam PMP_REGIONS = 0; +localparam PMP_GRAIN = 0; +localparam PMP_MATCH_NAPOT = 1; +localparam PMP_MATCH_TOR = 0; +localparam PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}}; +localparam PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}}; +localparam PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}}; +localparam DEBUG_SUPPORT = 0; +localparam BREAKPOINT_TRIGGERS = 4; +localparam NUM_IRQS = 32; +localparam IRQ_PRIORITY_BITS = 0; +localparam IRQ_INPUT_BYPASS = {NUM_IRQS{1'b0}}; +localparam MVENDORID_VAL = 32'hdeadbeef; +localparam MCONFIGPTR_VAL = 32'h9abcdef0; +localparam REDUCED_BYPASS = 1; +localparam MULDIV_UNROLL = 1; +localparam MUL_FAST = 0; +localparam MUL_FASTER = 0; +localparam MULH_FAST = 0; +localparam FAST_BRANCHCMP = 0; +localparam RESET_REGFILE = 1; +localparam BRANCH_PREDICTOR = 0; +localparam MTVEC_WMASK = 32'hfffffffd; diff --git a/vendor/Hazard3/test/sim/tb_common/hdl/config_min_debug.vh b/vendor/Hazard3/test/sim/tb_common/hdl/config_min_debug.vh new file mode 100644 index 0000000..e28126b --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/hdl/config_min_debug.vh @@ -0,0 +1,51 @@ +// Minimum performance and unprivileged ISA feature set, but enough privileged +// ISA and debug support to run a more interesting test suite. + +localparam RESET_VECTOR = 32'h80000040; +localparam MTVEC_INIT = 32'h80000000; +localparam EXTENSION_A = 0; +localparam EXTENSION_C = 0; +localparam EXTENSION_E = 0; +localparam EXTENSION_M = 0; +localparam EXTENSION_ZBA = 0; +localparam EXTENSION_ZBB = 0; +localparam EXTENSION_ZBC = 0; +localparam EXTENSION_ZBKB = 0; +localparam EXTENSION_ZBKX = 0; +localparam EXTENSION_ZBS = 0; +localparam EXTENSION_ZCB = 0; +localparam EXTENSION_ZCLSD = 0; +localparam EXTENSION_ZCMP = 0; +localparam EXTENSION_ZIFENCEI = 0; +localparam EXTENSION_ZILSD = 0; +localparam EXTENSION_XH3BEXTM = 0; +localparam EXTENSION_XH3IRQ = 0; +localparam EXTENSION_XH3PMPM = 0; +localparam EXTENSION_XH3POWER = 0; +localparam CSR_M_MANDATORY = 1; +localparam CSR_M_TRAP = 1; +localparam CSR_COUNTER = 0; +localparam U_MODE = 1; +localparam PMP_REGIONS = 4; +localparam PMP_GRAIN = 0; +localparam PMP_MATCH_NAPOT = 1; +localparam PMP_MATCH_TOR = 0; +localparam PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}}; +localparam PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}}; +localparam PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}}; +localparam DEBUG_SUPPORT = 1; +localparam BREAKPOINT_TRIGGERS = 0; +localparam NUM_IRQS = 32; +localparam IRQ_PRIORITY_BITS = 0; +localparam IRQ_INPUT_BYPASS = {NUM_IRQS{1'b0}}; +localparam MVENDORID_VAL = 32'hdeadbeef; +localparam MCONFIGPTR_VAL = 32'h9abcdef0; +localparam REDUCED_BYPASS = 1; +localparam MULDIV_UNROLL = 1; +localparam MUL_FAST = 0; +localparam MUL_FASTER = 0; +localparam MULH_FAST = 0; +localparam FAST_BRANCHCMP = 0; +localparam RESET_REGFILE = 1; +localparam BRANCH_PREDICTOR = 0; +localparam MTVEC_WMASK = 32'hfffffffd; diff --git a/vendor/Hazard3/test/sim/tb_common/hdl/config_pmpfull.vh b/vendor/Hazard3/test/sim/tb_common/hdl/config_pmpfull.vh new file mode 100644 index 0000000..adf7a87 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/hdl/config_pmpfull.vh @@ -0,0 +1,50 @@ +// Default Hazard3 config for testbench: all ISA features + +localparam RESET_VECTOR = 32'h80000040; +localparam MTVEC_INIT = 32'h80000000; +localparam EXTENSION_A = 1; +localparam EXTENSION_C = 1; +localparam EXTENSION_E = 0; +localparam EXTENSION_M = 1; +localparam EXTENSION_ZBA = 1; +localparam EXTENSION_ZBB = 1; +localparam EXTENSION_ZBC = 1; +localparam EXTENSION_ZBKB = 1; +localparam EXTENSION_ZBKX = 1; +localparam EXTENSION_ZBS = 1; +localparam EXTENSION_ZCB = 1; +localparam EXTENSION_ZCLSD = 1; +localparam EXTENSION_ZCMP = 1; +localparam EXTENSION_ZIFENCEI = 1; +localparam EXTENSION_ZILSD = 1; +localparam EXTENSION_XH3BEXTM = 1; +localparam EXTENSION_XH3IRQ = 1; +localparam EXTENSION_XH3PMPM = 1; +localparam EXTENSION_XH3POWER = 1; +localparam CSR_M_MANDATORY = 1; +localparam CSR_M_TRAP = 1; +localparam CSR_COUNTER = 1; +localparam U_MODE = 1; +localparam PMP_REGIONS = 16; +localparam PMP_GRAIN = 0; +localparam PMP_MATCH_NAPOT = 1; +localparam PMP_MATCH_TOR = 1; +localparam PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}}; +localparam PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}}; +localparam PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}}; +localparam DEBUG_SUPPORT = 1; +localparam BREAKPOINT_TRIGGERS = 4; +localparam NUM_IRQS = 32; +localparam IRQ_PRIORITY_BITS = 4; +localparam IRQ_INPUT_BYPASS = {NUM_IRQS{1'b0}}; +localparam MVENDORID_VAL = 32'hdeadbeef; +localparam MCONFIGPTR_VAL = 32'h9abcdef0; +localparam REDUCED_BYPASS = 0; +localparam MULDIV_UNROLL = 2; +localparam MUL_FAST = 1; +localparam MUL_FASTER = 1; +localparam MULH_FAST = 1; +localparam FAST_BRANCHCMP = 1; +localparam RESET_REGFILE = 1; +localparam BRANCH_PREDICTOR = 1; +localparam MTVEC_WMASK = 32'hfffffffd; diff --git a/vendor/Hazard3/test/sim/tb_common/hdl/tb.f b/vendor/Hazard3/test/sim/tb_common/hdl/tb.f new file mode 100644 index 0000000..db7c58d --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/hdl/tb.f @@ -0,0 +1,2 @@ +file tb.v +list tb_common.f diff --git a/vendor/Hazard3/test/sim/tb_common/hdl/tb.v b/vendor/Hazard3/test/sim/tb_common/hdl/tb.v new file mode 100644 index 0000000..a93cccd --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/hdl/tb.v @@ -0,0 +1,341 @@ +// An integration of JTAG-DTM + DM + CPU for openocd to poke at over a remote +// bitbang socket + +`default_nettype none + +module tb #( + parameter W_DATA = 32, // do not modify + parameter W_ADDR = 32 // do not modify +) ( + // Global signals + input wire clk, + input wire rst_n, + + // JTAG port + input wire tck, + input wire trst_n, + input wire tms, + input wire tdi, + output wire tdo, + + // Instruction fetch port + output wire [W_ADDR-1:0] i_haddr, + output wire i_hwrite, + output wire [1:0] i_htrans, + output wire i_hexcl, + output wire [2:0] i_hsize, + output wire [2:0] i_hburst, + output wire [3:0] i_hprot, + output wire i_hmastlock, + output wire [7:0] i_hmaster, + input wire i_hready, + input wire i_hresp, + input wire i_hexokay, + output wire [W_DATA-1:0] i_hwdata, + input wire [W_DATA-1:0] i_hrdata, + + // Load/store port + output wire [W_ADDR-1:0] d_haddr, + output wire d_hwrite, + output wire [1:0] d_htrans, + output wire d_hexcl, + output wire [2:0] d_hsize, + output wire [2:0] d_hburst, + output wire [3:0] d_hprot, + output wire d_hmastlock, + output wire [7:0] d_hmaster, + input wire d_hready, + input wire d_hresp, + input wire d_hexokay, + output wire [W_DATA-1:0] d_hwdata, + input wire [W_DATA-1:0] d_hrdata, + + // Level-sensitive interrupt sources + input wire [NUM_IRQS-1:0] irq, // -> mip.meip + input wire [1:0] soft_irq, // -> mip.msip + input wire [1:0] timer_irq // -> mip.mtip +); + +// JTAG-DTM IDCODE, selected after TAP reset, would normally be a +// JEP106-compliant ID +localparam IDCODE = 32'hdeadbeef; + +wire dmi_psel; +wire dmi_penable; +wire dmi_pwrite; +wire [8:0] dmi_paddr; +wire [31:0] dmi_pwdata; +reg [31:0] dmi_prdata; +wire dmi_pready; +wire dmi_pslverr; + +wire dmihardreset_req; +wire assert_dmi_reset = !rst_n || dmihardreset_req; +wire rst_n_dmi; + +hazard3_reset_sync dmi_reset_sync_u ( + .clk (clk), + .rst_n_in (!assert_dmi_reset), + .rst_n_out (rst_n_dmi) +); + +// Note the idle hint of 8 cycles was empirically found to be the correct +// value for a 1:2 TCK:clk_dmi ratio. OpenOCD doesn't particularly care +// because it will just increase idle cycles until it stops seeing BUSY. + +hazard3_jtag_dtm #( + .IDCODE (IDCODE), + .DTMCS_IDLE_HINT (8) +) inst_hazard3_jtag_dtm ( + .tck (tck), + .trst_n (trst_n), + .tms (tms), + .tdi (tdi), + .tdo (tdo), + + .dmihardreset_req (dmihardreset_req), + + .clk_dmi (clk), + .rst_n_dmi (rst_n_dmi), + + .dmi_psel (dmi_psel), + .dmi_penable (dmi_penable), + .dmi_pwrite (dmi_pwrite), + .dmi_paddr (dmi_paddr), + .dmi_pwdata (dmi_pwdata), + .dmi_prdata (dmi_prdata), + .dmi_pready (dmi_pready), + .dmi_pslverr (dmi_pslverr) +); + +localparam N_HARTS = 1; +localparam XLEN = 32; + +wire sys_reset_req; +wire sys_reset_done; +wire [N_HARTS-1:0] hart_reset_req; +wire [N_HARTS-1:0] hart_reset_done; + +wire [N_HARTS-1:0] hart_req_halt; +wire [N_HARTS-1:0] hart_req_halt_on_reset; +wire [N_HARTS-1:0] hart_req_resume; +wire [N_HARTS-1:0] hart_halted; +wire [N_HARTS-1:0] hart_running; + +wire [N_HARTS*XLEN-1:0] hart_data0_rdata; +wire [N_HARTS*XLEN-1:0] hart_data0_wdata; +wire [N_HARTS-1:0] hart_data0_wen; + +wire [N_HARTS*XLEN-1:0] hart_instr_data; +wire [N_HARTS-1:0] hart_instr_data_vld; +wire [N_HARTS-1:0] hart_instr_data_rdy; +wire [N_HARTS-1:0] hart_instr_caught_exception; +wire [N_HARTS-1:0] hart_instr_caught_ebreak; + +wire [31:0] sbus_addr; +wire sbus_write; +wire [1:0] sbus_size; +wire sbus_vld; +wire sbus_rdy; +wire sbus_err; +wire [31:0] sbus_wdata; +wire [31:0] sbus_rdata; + +hazard3_dm #( + .N_HARTS (N_HARTS), + .HAVE_SBA (1), + .NEXT_DM_ADDR (0) +) dm ( + .clk (clk), + .rst_n (rst_n), + + .dmi_psel (dmi_psel), + .dmi_penable (dmi_penable), + .dmi_pwrite (dmi_pwrite), + .dmi_paddr (dmi_paddr), + .dmi_pwdata (dmi_pwdata), + .dmi_prdata (dmi_prdata), + .dmi_pready (dmi_pready), + .dmi_pslverr (dmi_pslverr), + + .sys_reset_req (sys_reset_req), + .sys_reset_done (sys_reset_done), + .hart_reset_req (hart_reset_req), + .hart_reset_done (hart_reset_done), + + .hart_req_halt (hart_req_halt), + .hart_req_halt_on_reset (hart_req_halt_on_reset), + .hart_req_resume (hart_req_resume), + .hart_halted (hart_halted), + .hart_running (hart_running), + + .hart_data0_rdata (hart_data0_rdata), + .hart_data0_wdata (hart_data0_wdata), + .hart_data0_wen (hart_data0_wen), + + .hart_instr_data (hart_instr_data), + .hart_instr_data_vld (hart_instr_data_vld), + .hart_instr_data_rdy (hart_instr_data_rdy), + .hart_instr_caught_exception (hart_instr_caught_exception), + .hart_instr_caught_ebreak (hart_instr_caught_ebreak), + + .sbus_addr (sbus_addr), + .sbus_write (sbus_write), + .sbus_size (sbus_size), + .sbus_vld (sbus_vld), + .sbus_rdy (sbus_rdy), + .sbus_err (sbus_err), + .sbus_wdata (sbus_wdata), + .sbus_rdata (sbus_rdata) +); + + +// Generate resynchronised reset for CPU based on upstream reset and +// on reset requests from DM. + +wire assert_cpu_reset = !rst_n || sys_reset_req || hart_reset_req[0]; +wire rst_n_cpu; + +hazard3_reset_sync cpu_reset_sync ( + .clk (clk), + .rst_n_in (!assert_cpu_reset), + .rst_n_out (rst_n_cpu) +); + +// Still some work to be done on the reset handshake -- this ought to be +// resynchronised to DM's reset domain here, and the DM should wait for a +// rising edge after it has asserted the reset pulse, to make sure the tail +// of the previous "done" is not passed on. +assign sys_reset_done = rst_n_cpu; +assign hart_reset_done = rst_n_cpu; + + +wire pwrup_req; +reg pwrup_ack; +wire clk_en; +wire unblock_out; +wire unblock_in = unblock_out; + +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + pwrup_ack <= 1'b1; + end else begin + pwrup_ack <= pwrup_req; + end +end + +wire fence_i_vld; +wire fence_d_vld; +reg [3:0] fence_rdy_delay_ctr; +wire fence_rdy = &fence_rdy_delay_ctr; +always @ (posedge clk or negedge rst_n) begin + if (!rst_n) begin + fence_rdy_delay_ctr <= 4'h0; + end else if (fence_i_vld || fence_d_vld) begin + fence_rdy_delay_ctr <= fence_rdy_delay_ctr + 4'h1; + end else begin + fence_rdy_delay_ctr <= 4'h0; + end +end + +// Clock gate is disabled, as CXXRTL currently can't simulated gated clocks +// due to a limitation of the scheduler design + +// // Latching clock gate. Does not insert an NBA delay on the gated clock, so +// // safe to exchange data between NBAs on the gated and non-gated clock. Does +// // not glitch as long as clk_en is driven from an NBA on the posedge of clk +// // (e.g. a normal RTL register). The clock stops *high*. + +// reg clk_gated; + +// always @ (*) begin +// if (clk_en) +// clk_gated = clk; +// end + +`ifndef CONFIG_HEADER +`define CONFIG_HEADER "config_default.vh" +`endif +`include `CONFIG_HEADER + +hazard3_cpu_2port #( +`include "hazard3_config_inst.vh" +) cpu ( + .clk (clk), + .clk_always_on (clk), + .rst_n (rst_n_cpu), + + .pwrup_req (pwrup_req), + .pwrup_ack (pwrup_ack), + .clk_en (clk_en), + .unblock_out (unblock_out), + .unblock_in (unblock_in), + + .i_haddr (i_haddr), + .i_hwrite (i_hwrite), + .i_htrans (i_htrans), + .i_hsize (i_hsize), + .i_hburst (i_hburst), + .i_hprot (i_hprot), + .i_hmastlock (i_hmastlock), + .i_hmaster (i_hmaster), + .i_hready (i_hready), + .i_hresp (i_hresp), + .i_hwdata (i_hwdata), + .i_hrdata (i_hrdata), + + .d_haddr (d_haddr), + .d_hexcl (d_hexcl), + .d_hwrite (d_hwrite), + .d_htrans (d_htrans), + .d_hsize (d_hsize), + .d_hburst (d_hburst), + .d_hprot (d_hprot), + .d_hmastlock (d_hmastlock), + .d_hmaster (d_hmaster), + .d_hready (d_hready), + .d_hresp (d_hresp), + .d_hexokay (d_hexokay), + .d_hwdata (d_hwdata), + .d_hrdata (d_hrdata), + + .fence_i_vld (fence_i_vld), + .fence_d_vld (fence_d_vld), + .fence_rdy (fence_rdy), + + .dbg_req_halt (hart_req_halt), + .dbg_req_halt_on_reset (hart_req_halt_on_reset), + .dbg_req_resume (hart_req_resume), + .dbg_halted (hart_halted), + .dbg_running (hart_running), + + .dbg_data0_rdata (hart_data0_rdata), + .dbg_data0_wdata (hart_data0_wdata), + .dbg_data0_wen (hart_data0_wen), + + .dbg_instr_data (hart_instr_data), + .dbg_instr_data_vld (hart_instr_data_vld), + .dbg_instr_data_rdy (hart_instr_data_rdy), + .dbg_instr_caught_exception (hart_instr_caught_exception), + .dbg_instr_caught_ebreak (hart_instr_caught_ebreak), + + .dbg_sbus_addr (sbus_addr), + .dbg_sbus_write (sbus_write), + .dbg_sbus_size (sbus_size), + .dbg_sbus_vld (sbus_vld), + .dbg_sbus_rdy (sbus_rdy), + .dbg_sbus_err (sbus_err), + .dbg_sbus_wdata (sbus_wdata), + .dbg_sbus_rdata (sbus_rdata), + + .mhartid_val (32'd0), + .eco_version (4'ha), + + .irq (irq), + .soft_irq (soft_irq[0]), + .timer_irq (timer_irq[0]) +); + +assign i_hexcl = 1'b0; + +endmodule diff --git a/vendor/Hazard3/test/sim/tb_common/hdl/tb_common.f b/vendor/Hazard3/test/sim/tb_common/hdl/tb_common.f new file mode 100644 index 0000000..d87d913 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/hdl/tb_common.f @@ -0,0 +1,7 @@ +file $HDL/debug/cdc/hazard3_reset_sync.v + +list $HDL/hazard3.f +list $HDL/debug/dm/hazard3_dm.f +list $HDL/debug/dtm/hazard3_jtag_dtm.f + +include . diff --git a/vendor/Hazard3/test/sim/tb_common/hdl/tb_multicore.f b/vendor/Hazard3/test/sim/tb_common/hdl/tb_multicore.f new file mode 100644 index 0000000..0541de6 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/hdl/tb_multicore.f @@ -0,0 +1,2 @@ +file tb_multicore.v +list tb_common.f diff --git a/vendor/Hazard3/test/sim/tb_common/hdl/tb_multicore.v b/vendor/Hazard3/test/sim/tb_common/hdl/tb_multicore.v new file mode 100644 index 0000000..e20c8e5 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/hdl/tb_multicore.v @@ -0,0 +1,358 @@ +// An integration of JTAG-DTM + DM + 2 single-ported CPUs for openocd to poke +// at over a remote bitbang socket + +`default_nettype none + +module tb #( + parameter W_ADDR = 32, // do not modify + parameter W_DATA = 32 // do not modify +) ( + // Global signals + input wire clk, + input wire rst_n, + + // JTAG port + input wire tck, + input wire trst_n, + input wire tms, + input wire tdi, + output wire tdo, + + // Core 0 bus (named I for consistency with 1-core 2-port tb) + output wire [W_ADDR-1:0] i_haddr, + output wire i_hwrite, + output wire [1:0] i_htrans, + output wire i_hexcl, + output wire [2:0] i_hsize, + output wire [2:0] i_hburst, + output wire [3:0] i_hprot, + output wire i_hmastlock, + output wire [7:0] i_hmaster, + input wire i_hready, + input wire i_hresp, + input wire i_hexokay, + output wire [W_DATA-1:0] i_hwdata, + input wire [W_DATA-1:0] i_hrdata, + + // Core 1 bus (named D for consistency with 1-core 2-port tb) + output wire [W_ADDR-1:0] d_haddr, + output wire d_hwrite, + output wire [1:0] d_htrans, + output wire d_hexcl, + output wire [2:0] d_hsize, + output wire [2:0] d_hburst, + output wire [3:0] d_hprot, + output wire d_hmastlock, + output wire [7:0] d_hmaster, + input wire d_hready, + input wire d_hresp, + input wire d_hexokay, + output wire [W_DATA-1:0] d_hwdata, + input wire [W_DATA-1:0] d_hrdata, + + // Level-sensitive interrupt sources + input wire [NUM_IRQS-1:0] irq, // -> mip.meip + input wire [1:0] soft_irq, // -> mip.msip + input wire [1:0] timer_irq // -> mip.mtip +); + +// JTAG-DTM IDCODE, selected after TAP reset, would normally be a +// JEP106-compliant ID +localparam IDCODE = 32'hdeadbeef; + +wire dmi_psel; +wire dmi_penable; +wire dmi_pwrite; +wire [8:0] dmi_paddr; +wire [31:0] dmi_pwdata; +reg [31:0] dmi_prdata; +wire dmi_pready; +wire dmi_pslverr; + +wire dmihardreset_req; +wire assert_dmi_reset = !rst_n || dmihardreset_req; +wire rst_n_dmi; + +hazard3_reset_sync dmi_reset_sync_u ( + .clk (clk), + .rst_n_in (!assert_dmi_reset), + .rst_n_out (rst_n_dmi) +); + +hazard3_jtag_dtm #( + .IDCODE (IDCODE), + .DTMCS_IDLE_HINT (8) +) inst_hazard3_jtag_dtm ( + .tck (tck), + .trst_n (trst_n), + .tms (tms), + .tdi (tdi), + .tdo (tdo), + + .dmihardreset_req (dmihardreset_req), + + .clk_dmi (clk), + .rst_n_dmi (rst_n_dmi), + + .dmi_psel (dmi_psel), + .dmi_penable (dmi_penable), + .dmi_pwrite (dmi_pwrite), + .dmi_paddr (dmi_paddr), + .dmi_pwdata (dmi_pwdata), + .dmi_prdata (dmi_prdata), + .dmi_pready (dmi_pready), + .dmi_pslverr (dmi_pslverr) +); + +localparam N_HARTS = 2; +localparam XLEN = 32; + +wire sys_reset_req; +wire sys_reset_done; +wire [N_HARTS-1:0] hart_reset_req; +wire [N_HARTS-1:0] hart_reset_done; + +wire [N_HARTS-1:0] hart_req_halt; +wire [N_HARTS-1:0] hart_req_halt_on_reset; +wire [N_HARTS-1:0] hart_req_resume; +wire [N_HARTS-1:0] hart_halted; +wire [N_HARTS-1:0] hart_running; + +wire [N_HARTS*XLEN-1:0] hart_data0_rdata; +wire [N_HARTS*XLEN-1:0] hart_data0_wdata; +wire [N_HARTS-1:0] hart_data0_wen; + +wire [N_HARTS*XLEN-1:0] hart_instr_data; +wire [N_HARTS-1:0] hart_instr_data_vld; +wire [N_HARTS-1:0] hart_instr_data_rdy; +wire [N_HARTS-1:0] hart_instr_caught_exception; +wire [N_HARTS-1:0] hart_instr_caught_ebreak; + +wire [31:0] sbus_addr; +wire sbus_write; +wire [1:0] sbus_size; +wire sbus_vld; +wire sbus_rdy; +wire sbus_err; +wire [31:0] sbus_wdata; +wire [31:0] sbus_rdata; + +hazard3_dm #( + .N_HARTS (N_HARTS), + .HAVE_SBA (1), + .NEXT_DM_ADDR (0) +) dm ( + .clk (clk), + .rst_n (rst_n), + + .dmi_psel (dmi_psel), + .dmi_penable (dmi_penable), + .dmi_pwrite (dmi_pwrite), + .dmi_paddr (dmi_paddr), + .dmi_pwdata (dmi_pwdata), + .dmi_prdata (dmi_prdata), + .dmi_pready (dmi_pready), + .dmi_pslverr (dmi_pslverr), + + .sys_reset_req (sys_reset_req), + .sys_reset_done (sys_reset_done), + .hart_reset_req (hart_reset_req), + .hart_reset_done (hart_reset_done), + + .hart_req_halt (hart_req_halt), + .hart_req_halt_on_reset (hart_req_halt_on_reset), + .hart_req_resume (hart_req_resume), + .hart_halted (hart_halted), + .hart_running (hart_running), + + .hart_data0_rdata (hart_data0_rdata), + .hart_data0_wdata (hart_data0_wdata), + .hart_data0_wen (hart_data0_wen), + + .hart_instr_data (hart_instr_data), + .hart_instr_data_vld (hart_instr_data_vld), + .hart_instr_data_rdy (hart_instr_data_rdy), + .hart_instr_caught_exception (hart_instr_caught_exception), + .hart_instr_caught_ebreak (hart_instr_caught_ebreak), + + .sbus_addr (sbus_addr), + .sbus_write (sbus_write), + .sbus_size (sbus_size), + .sbus_vld (sbus_vld), + .sbus_rdy (sbus_rdy), + .sbus_err (sbus_err), + .sbus_wdata (sbus_wdata), + .sbus_rdata (sbus_rdata) + +); + +// Generate resynchronised reset for CPU based on upstream reset and +// on reset requests from DM. + +wire assert_cpu_reset0 = !rst_n || sys_reset_req || hart_reset_req[0]; +wire assert_cpu_reset1 = !rst_n || sys_reset_req || hart_reset_req[1]; +wire rst_n_cpu0; +wire rst_n_cpu1; + +hazard3_reset_sync cpu0_reset_sync ( + .clk (clk), + .rst_n_in (!assert_cpu_reset0), + .rst_n_out (rst_n_cpu0) +); + +hazard3_reset_sync cpu1_reset_sync ( + .clk (clk), + .rst_n_in (!assert_cpu_reset1), + .rst_n_out (rst_n_cpu1) +); + +// Still some work to be done on the reset handshake -- this ought to be +// resynchronised to DM's reset domain here, and the DM should wait for a +// rising edge after it has asserted the reset pulse, to make sure the tail +// of the previous "done" is not passed on. +assign sys_reset_done = rst_n_cpu0 && rst_n_cpu1; +assign hart_reset_done = {rst_n_cpu1, rst_n_cpu0}; + +`ifndef CONFIG_HEADER +`define CONFIG_HEADER "config_default.vh" +`endif +`include `CONFIG_HEADER + +wire pwrup_req_cpu0; +wire pwrup_req_cpu1; +wire unblock_out_cpu0; +wire unblock_out_cpu1; + +hazard3_cpu_1port #( +`include "hazard3_config_inst.vh" +) cpu0 ( + .clk (clk), + .clk_always_on (clk), + .rst_n (rst_n_cpu0), + + .pwrup_req (pwrup_req_cpu0), + .pwrup_ack (pwrup_req_cpu0), + .clk_en (), + .unblock_out (unblock_out_cpu0), + .unblock_in (unblock_out_cpu1), + + .haddr (i_haddr), + .hexcl (i_hexcl), + .hwrite (i_hwrite), + .htrans (i_htrans), + .hsize (i_hsize), + .hburst (i_hburst), + .hprot (i_hprot), + .hmastlock (i_hmastlock), + .hmaster (i_hmaster), + .hready (i_hready), + .hresp (i_hresp), + .hexokay (i_hexokay), + .hwdata (i_hwdata), + .hrdata (i_hrdata), + + .fence_i_vld (), + .fence_d_vld (), + .fence_rdy (1'b1), + + .dbg_req_halt (hart_req_halt [0]), + .dbg_req_halt_on_reset (hart_req_halt_on_reset [0]), + .dbg_req_resume (hart_req_resume [0]), + .dbg_halted (hart_halted [0]), + .dbg_running (hart_running [0]), + + .dbg_data0_rdata (hart_data0_rdata [0 * XLEN +: XLEN]), + .dbg_data0_wdata (hart_data0_wdata [0 * XLEN +: XLEN]), + .dbg_data0_wen (hart_data0_wen [0]), + + .dbg_instr_data (hart_instr_data [0 * XLEN +: XLEN]), + .dbg_instr_data_vld (hart_instr_data_vld [0]), + .dbg_instr_data_rdy (hart_instr_data_rdy [0]), + .dbg_instr_caught_exception (hart_instr_caught_exception[0]), + .dbg_instr_caught_ebreak (hart_instr_caught_ebreak [0]), + + // SBA is routed through core 1, so tie off on core 0 + .dbg_sbus_addr (32'h0), + .dbg_sbus_write (1'b0), + .dbg_sbus_size (2'h0), + .dbg_sbus_vld (1'b0), + .dbg_sbus_rdy (), + .dbg_sbus_err (), + .dbg_sbus_wdata (32'h0), + .dbg_sbus_rdata (), + + .mhartid_val (32'd0), + .eco_version (4'ha), + + .irq (irq), + .soft_irq (soft_irq[0]), + .timer_irq (timer_irq[0]) +); + +hazard3_cpu_1port #( +`include "hazard3_config_inst.vh" +) cpu1 ( + .clk (clk), + .clk_always_on (clk), + .rst_n (rst_n_cpu1), + + .pwrup_req (pwrup_req_cpu1), + .pwrup_ack (pwrup_req_cpu1), + .clk_en (), + .unblock_out (unblock_out_cpu1), + .unblock_in (unblock_out_cpu0), + + .haddr (d_haddr), + .hexcl (d_hexcl), + .hwrite (d_hwrite), + .htrans (d_htrans), + .hsize (d_hsize), + .hburst (d_hburst), + .hprot (d_hprot), + .hmastlock (d_hmastlock), + .hmaster (d_hmaster), + .hready (d_hready), + .hresp (d_hresp), + .hexokay (d_hexokay), + .hwdata (d_hwdata), + .hrdata (d_hrdata), + + .fence_i_vld (), + .fence_d_vld (), + .fence_rdy (1'b1), + + .dbg_req_halt (hart_req_halt [1]), + .dbg_req_halt_on_reset (hart_req_halt_on_reset [1]), + .dbg_req_resume (hart_req_resume [1]), + .dbg_halted (hart_halted [1]), + .dbg_running (hart_running [1]), + + .dbg_data0_rdata (hart_data0_rdata [1 * XLEN +: XLEN]), + .dbg_data0_wdata (hart_data0_wdata [1 * XLEN +: XLEN]), + .dbg_data0_wen (hart_data0_wen [1]), + + .dbg_instr_data (hart_instr_data [1 * XLEN +: XLEN]), + .dbg_instr_data_vld (hart_instr_data_vld [1]), + .dbg_instr_data_rdy (hart_instr_data_rdy [1]), + .dbg_instr_caught_exception (hart_instr_caught_exception[1]), + .dbg_instr_caught_ebreak (hart_instr_caught_ebreak [1]), + + .dbg_sbus_addr (sbus_addr), + .dbg_sbus_write (sbus_write), + .dbg_sbus_size (sbus_size), + .dbg_sbus_vld (sbus_vld), + .dbg_sbus_rdy (sbus_rdy), + .dbg_sbus_err (sbus_err), + .dbg_sbus_wdata (sbus_wdata), + .dbg_sbus_rdata (sbus_rdata), + + .mhartid_val (32'd1), + .eco_version (4'ha), + + .irq (irq), + .soft_irq (soft_irq[1]), + .timer_irq (timer_irq[1]) +); + + +endmodule diff --git a/vendor/Hazard3/test/sim/tb_common/include/tb.h b/vendor/Hazard3/test/sim/tb_common/include/tb.h new file mode 100644 index 0000000..12a81a1 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/include/tb.h @@ -0,0 +1,108 @@ +#pragma once + +#include "tb_cli.h" +#include "tb_constants.h" +#include "tb_uart.h" + +#include +#include +#include + +#include +#include +#include + +struct mem_io_state; +class tb_top; + +struct bus_request { + uint32_t addr; + bus_size_t size; + bool write; + bool excl; + uint32_t wdata; + int reservation_id; + bus_request(): addr(0), size(SIZE_BYTE), write(0), excl(0), wdata(0), reservation_id(0) {} +}; + +struct bus_response { + uint32_t rdata; + int stall_cycles; + bool err; + bool exokay; + bus_response(): rdata(0), stall_cycles(0), err(false), exokay(true) {} +}; + +typedef bus_response (*mem_access_callback_t)(tb_top &tb, mem_io_state &memio, bus_request req); + +// Default callback: +bus_response tb_mem_access(tb_top &tb, mem_io_state &memio, bus_request req); + +// Abstract test harness class. Concrete implementations of this class contain +// the actual C++ cycle model as well as the glue for this interface. +class tb_top { +protected: + mem_access_callback_t mem_callback_i; + mem_access_callback_t mem_callback_d; + uint64_t rand_state[4]; +public: + FILE *logfile; + void set_mem_callback_i(mem_access_callback_t cb) {mem_callback_i = cb;} + void set_mem_callback_d(mem_access_callback_t cb) {mem_callback_d = cb;} + + tb_top(const tb_cli_args &args) { + mem_callback_i = tb_mem_access; + mem_callback_d = tb_mem_access; + seed_rand((const uint8_t*)"looks random to me", 18); + if (args.log_path != "") { + logfile = fopen(args.log_path.c_str(), "wb"); + } else { + logfile = stdout; + } + } + + void seed_rand(const uint8_t *data, size_t len); + uint32_t rand(); + + virtual void step(const tb_cli_args &args, mem_io_state &memio) = 0; + // Evaluate DUT at current signal values without advancing the core clock. + virtual void eval() = 0; + + virtual void set_trst_n(bool trst_n) = 0; + virtual void set_tck(bool tck) = 0; + virtual void set_tdi(bool tdi) = 0; + virtual void set_tms(bool tms) = 0; + virtual bool get_tdo() = 0; + + virtual void set_irq(uint32_t mask) = 0; + virtual void set_soft_irq(uint8_t mask) = 0; + virtual void set_timer_irq(uint8_t mask) = 0; +}; + +struct mem_io_state { + uint64_t mtime; + uint64_t mtimecmp[2]; + + bool exit_req; + uint32_t exit_code; + + uint8_t *mem; + + bool monitor_enabled; + bool reservation_valid[2]; + uint32_t reservation_addr[2]; + uint32_t poison_addr; + + uint8_t soft_irq_state; + uint32_t irq_state; + + tb_uart_state uart; + + mem_io_state(const tb_cli_args &args); + + ~mem_io_state() { + delete[] mem; + } + + void step(tb_top &tb); +}; diff --git a/vendor/Hazard3/test/sim/tb_common/include/tb_cli.h b/vendor/Hazard3/test/sim/tb_common/include/tb_cli.h new file mode 100644 index 0000000..753b136 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/include/tb_cli.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include + +struct tb_cli_args { + bool load_bin; + std::string bin_path; + bool dump_waves; + std::string waves_path; + std::vector> dump_ranges; + int64_t max_cycles; + bool propagate_return_code; + uint16_t port; + uint16_t vpi_port; + uint16_t gdb_port; + uint16_t uart0_port; + uint16_t uart1_port; + // JTAG_VPI socket polling backoff in core cycles (0 = poll every cycle). + // When idle, the testbench ramps up to this maximum backoff to reduce + // syscall overhead without adding huge latency between back-to-back packets. + uint32_t vpi_poll_cycles; + // When using --vpi-port, run N core clock cycles per JTAG TCK cycle during + // OpenOCD TMS sequences (helps DMI/APB CDC make progress and avoids BUSY). + // 0 disables this coupling (legacy behaviour). + uint32_t vpi_clk_per_tck; + bool dump_jtag; + std::string jtag_dump_path; + bool replay_jtag; + std::string jtag_replay_path; + std::string log_path; + std::string sig_path; +#ifdef CXXRTL_DEBUG_AGENT + bool run_agent; +#endif + tb_cli_args() { + load_bin = false; + dump_waves = false; + max_cycles = 0; + propagate_return_code = false; + port = 0; + vpi_port = 0; + gdb_port = 0; + uart0_port = 0; + uart1_port = 0; + vpi_poll_cycles = 256; + vpi_clk_per_tck = 2; + dump_jtag = false; + replay_jtag = false; +#ifdef CXXRTL_DEBUG_AGENT + run_agent = false; +#endif + } +}; + +void tb_parse_args(int argc, char **argv, tb_cli_args &args); diff --git a/vendor/Hazard3/test/sim/tb_common/include/tb_constants.h b/vendor/Hazard3/test/sim/tb_common/include/tb_constants.h new file mode 100644 index 0000000..8a1f44e --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/include/tb_constants.h @@ -0,0 +1,57 @@ +#pragma once + +#ifdef __x86_64__ +#define I64_FMT "%ld" +#else +#define I64_FMT "%lld" +#endif + +#define MEM_BASE 0x80000000 +#define MEM_SIZE (16 * 1024 * 1024) +#define N_RESERVATIONS (2) +#define RESERVATION_ADDR_MASK (0xfffffff8u) + +static const unsigned int IO_BASE = 0xc0000000; +enum { + IO_PRINT_CHAR = 0x000, + IO_PRINT_U32 = 0x004, + IO_EXIT = 0x008, + IO_SET_SOFTIRQ = 0x010, + IO_CLR_SOFTIRQ = 0x014, + IO_GLOBMON_EN = 0x018, + IO_POISON_ADDR = 0x01c, + IO_SET_IRQ = 0x020, + IO_CLR_IRQ = 0x030, + IO_MTIME = 0x100, + IO_MTIMEH = 0x104, + IO_MTIMECMP0 = 0x108, + IO_MTIMECMP0H = 0x10c, + IO_MTIMECMP1 = 0x110, + IO_MTIMECMP1H = 0x114 +}; + +// ---------------------------------------------------------------------------- +// Testbench UART-over-TCP MMIO +// +// Each UART is exposed as a raw TCP byte stream (one client at a time). +// Software should poll STATUS.RX_AVAIL before reading DATA. + +static constexpr uint32_t IO_UART_BASE = 0x200; +static constexpr uint32_t IO_UART_STRIDE = 0x20; +static constexpr uint32_t IO_UART_DATA = 0x00; +static constexpr uint32_t IO_UART_STATUS = 0x04; +static constexpr uint32_t IO_UART_CTRL = 0x08; +static constexpr uint32_t IO_UART_N = 2; + +static constexpr uint32_t TB_UART_STATUS_RX_AVAIL = 1u << 0; +static constexpr uint32_t TB_UART_STATUS_TX_READY = 1u << 1; +static constexpr uint32_t TB_UART_STATUS_CONNECTED = 1u << 2; +static constexpr uint32_t TB_UART_STATUS_OVERRUN = 1u << 3; + +static constexpr uint32_t TB_UART_CTRL_CLR_OVERRUN = 1u << 0; + +typedef enum { + SIZE_BYTE = 0, + SIZE_HWORD = 1, + SIZE_WORD = 2 +} bus_size_t; diff --git a/vendor/Hazard3/test/sim/tb_common/include/tb_gdb.h b/vendor/Hazard3/test/sim/tb_common/include/tb_gdb.h new file mode 100644 index 0000000..032f35b --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/include/tb_gdb.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include + +enum class tb_gdb_run_result { + ok = 0, + error, + exited, + timed_out +}; + +struct tb_gdb_target { + virtual ~tb_gdb_target() = default; + + // Register numbering follows the provided target.xml: + // x0..x31 = 0..31, pc = 32. + virtual uint32_t read_reg(uint32_t regno) = 0; + virtual void write_reg(uint32_t regno, uint32_t value) = 0; + + virtual bool read_mem(uint32_t addr, uint8_t *dst, size_t len) = 0; + virtual bool write_mem(uint32_t addr, const uint8_t *src, size_t len) = 0; + + // Advance execution until one instruction retires. + virtual tb_gdb_run_result step_instruction() = 0; + + virtual uint32_t exit_code() const = 0; + + // Optional: handle GDB "monitor" commands (qRcmd). Return true if handled. + // If handled, out_console is printed on the GDB console (can be empty). + virtual bool monitor_cmd(const std::string &cmd, std::string &out_console) { + (void)cmd; + out_console.clear(); + return false; + } +}; + +class tb_gdb_server { +public: + tb_gdb_server(uint16_t port, tb_gdb_target &target); + ~tb_gdb_server(); + + // Blocks until the session ends (disconnect/kill) or the target exits. + tb_gdb_run_result serve(); + +private: + uint16_t port_; + tb_gdb_target &target_; + + int server_fd_ = -1; + int client_fd_ = -1; + bool no_ack_mode_ = false; + + std::string target_xml_; + std::string memory_map_xml_; + + // Execute breakpoints (RSP Z0/Z1): stop before executing instruction at PC. + // When resuming from a breakpoint, ignore a match at the current PC once. + std::unordered_set breakpoints_; + uint32_t ignore_breakpoint_pc_ = 0xffffffffu; + + // Socket helpers + bool open_listen_socket_(); + bool accept_client_(); + void close_client_(); + + bool recv_packet_(std::string &out_payload, bool &got_interrupt); + bool send_packet_(const std::string &payload); + bool maybe_send_ack_(bool ok); + bool check_interrupt_(); + + // RSP helpers + static uint8_t checksum_(const std::string &s); + static int hex_val_(char c); + static std::string to_hex_bytes_(const uint8_t *data, size_t len); + static bool from_hex_bytes_(const std::string &hex, std::string &out_bytes); + static void append_u32_le_hex_(std::string &out, uint32_t v); + static bool parse_u32_hex_(const std::string &s, uint32_t &out); + + std::string handle_command_(const std::string &cmd, bool &run_command_consumed); + std::string stop_reply_(int signo) const; + std::string stop_reply_exit_() const; +}; diff --git a/vendor/Hazard3/test/sim/tb_common/include/tb_jtag.h b/vendor/Hazard3/test/sim/tb_common/include/tb_jtag.h new file mode 100644 index 0000000..a9db8c6 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/include/tb_jtag.h @@ -0,0 +1,58 @@ +#include +#include + +#include +#include +#include + +#include "tb.h" +#include "tb_cli.h" + +#define TCP_BUF_SIZE 256 + +struct tb_jtag_state { + enum class transport_t { + none, + remote_bitbang, + jtag_vpi + }; + + tb_cli_args args; + transport_t transport; + + int server_fd; + int sock_fd; + struct sockaddr_in sock_addr; + int sock_opt; + socklen_t sock_addr_len; + char txbuf[TCP_BUF_SIZE], rxbuf[TCP_BUF_SIZE]; + int rx_ptr; + int rx_remaining; + int tx_ptr; + + static constexpr int VPI_XFERT_MAX_SIZE = 512; + static constexpr int VPI_PKT_SIZE = 4 + VPI_XFERT_MAX_SIZE + VPI_XFERT_MAX_SIZE + 4 + 4; + uint8_t vpi_rxbuf[VPI_PKT_SIZE]; + int vpi_rx_count; + uint32_t vpi_poll_ctr; + uint32_t vpi_poll_backoff; + + std::ofstream jtag_dump_fd; + std::ifstream jtag_replay_fd; + + tb_jtag_state(const tb_cli_args &_args); + + // Returns true if an exit command was received from the JTAG socket + // If memio/cycle_count/timed_out are provided, the testbench may advance + // the core clock while processing JTAG_VPI TMS sequences (idle/wait cycles), + // so the DMI/APB CDC can make progress and OpenOCD doesn't spin on BUSY. + bool step( + tb_top &tb, + mem_io_state *memio = nullptr, + int64_t *cycle_count = nullptr, + bool *timed_out = nullptr, + uint32_t *core_cycles_advanced = nullptr + ); + + void close(); +}; diff --git a/vendor/Hazard3/test/sim/tb_common/include/tb_uart.h b/vendor/Hazard3/test/sim/tb_common/include/tb_uart.h new file mode 100644 index 0000000..f5575e8 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/include/tb_uart.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +#include + +struct tb_cli_args; + +// Simple UART-over-TCP bridge used by the Verilator/CXXRTL testbenches. +// +// Each UART is exposed as a raw TCP byte stream (one client at a time). +// - CPU TX: MMIO write -> queued -> non-blocking send() to connected client. +// - CPU RX: client -> queued -> MMIO read pops one byte (poll STATUS first). +struct tb_uart_state { + static constexpr uint32_t N_UARTS = 2; + + struct uart { + uint16_t port = 0; + int server_fd = -1; + int client_fd = -1; + bool overrun = false; + + // Bounded FIFOs to avoid unbounded memory growth if the CPU or client + // isn't keeping up. When full, RX drops new data and TX drops old data. + std::deque rx_fifo; + std::deque tx_fifo; + size_t rx_capacity = 4096; + size_t tx_capacity = 4096; + + sockaddr_in bind_addr {}; + + void init(uint16_t port_); + void close(); + void poll_accept(uint32_t uart_idx); + void poll_rx(uint32_t uart_idx); + void poll_tx(uint32_t uart_idx); + bool connected() const { return client_fd >= 0; } + }; + + uart uarts[N_UARTS]; + + tb_uart_state() = default; + explicit tb_uart_state(const tb_cli_args &args); + ~tb_uart_state(); + + void step(); + + uint32_t read_status(uint32_t uart_idx); + uint32_t read_data(uint32_t uart_idx); + void write_data(uint32_t uart_idx, uint8_t byte); + void write_ctrl(uint32_t uart_idx, uint32_t value); +}; + diff --git a/vendor/Hazard3/test/sim/tb_common/tb_cli.cpp b/vendor/Hazard3/test/sim/tb_common/tb_cli.cpp new file mode 100644 index 0000000..13f93de --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/tb_cli.cpp @@ -0,0 +1,162 @@ +#include "tb_cli.h" +#include "tb_constants.h" +#include + +static const char *help_str = +"Usage: tb [--bin x.bin] [--port n] [--vpi-port n] [--vcd x.vcd] [--dump start end] \\\n" +" [--cycles n] [--cpuret] [--jtagdump x] [--jtagreplay x] [--vpi-poll n] \\\n" +" [--gdb-port n] [--uart0-port n] [--uart1-port n]\n" +" [--vpi-clk-per-tck n]\n" +"\n" +" --bin x.bin : Flat binary file loaded to address 0x0 in RAM\n" +" --vcd x.vcd : Path to dump waveforms to\n" +" --dump start end : Print out memory contents from start to end (exclusive)\n" +" after execution finishes. Can be passed multiple times.\n" +" --cycles n : Maximum number of cycles to run before exiting.\n" +" Default is 0 (no maximum).\n" +" --port n : Port number to listen for openocd remote bitbang. Sim\n" +" runs in lockstep with JTAG bitbang, not free-running.\n" +" --vpi-port n : Port number to listen for openocd jtag_vpi. Faster than\n" +" remote bitbang, and sim is free-running.\n" +" --vpi-poll n : When using --vpi-port, back off up to n core cycles\n" +" between socket polls when OpenOCD is idle (0 = every cycle).\n" +" --gdb-port n : Listen for GDB RSP directly (no OpenOCD/JTAG).\n" +" --uart0-port n : Expose testbench UART0 as a TCP stream.\n" +" --uart1-port n : Expose testbench UART1 as a TCP stream.\n" +" --vpi-clk-per-tck n : When using --vpi-port, run n core clock cycles per\n" +" JTAG TCK cycle during OpenOCD TMS sequences, so DMI/APB\n" +" CDC can make progress without huge BUSY delays.\n" +" --cpuret : Testbench's return code is the return code written to\n" +" IO_EXIT by the CPU, or -1 if timed out.\n" +" --jtagdump : Dump OpenOCD JTAG bitbang commands to a file so they\n" +" can be replayed. (Lower perf impact than VCD dumping)\n" +" --jtagreplay : Play back some dumped OpenOCD JTAG bitbang commands\n" +" --logfile path : File to write testbench stdout\n" +" --sigfile path : File to write only the data from --dump commands\n" +" (hex, 32 bits per line, same as riscv-arch-test)\n" +#ifdef CXXRTL_DEBUG_AGENT +" --debug : Run CXXRTL debugger\n" +#endif +; + +static void exit_help(std::string errtext = "") { + std::cerr << errtext << help_str; + exit(-1); +} + +void tb_parse_args(int argc, char **argv, tb_cli_args &args) { + for (int i = 1; i < argc; ++i) { + std::string s(argv[i]); + if (s.substr(0, 11) == "+verilator+") { + // Skip arguments passed directly to verilator context + i += 1; + } else if (s.rfind("--", 0) != 0) { + std::cerr << "Unexpected positional argument " << s << "\n"; + exit_help(""); + } else if (s == "--bin") { + if (argc - i < 2) + exit_help("Option --bin requires an argument\n"); + args.load_bin = true; + args.bin_path = argv[i + 1]; + i += 1; + } else if (s == "--vcd") { + if (argc - i < 2) + exit_help("Option --vcd requires an argument\n"); + args.dump_waves = true; + args.waves_path = argv[i + 1]; + i += 1; + } else if (s == "--logfile") { + if (argc - i < 2) + exit_help("Option --logfile requires an argument\n"); + args.log_path = argv[i + 1]; + i += 1; + } else if (s == "--sigfile") { + if (argc - i < 2) + exit_help("Option --sigfile requires an argument\n"); + args.sig_path = argv[i + 1]; + i += 1; + } else if (s == "--jtagdump") { + if (argc - i < 2) + exit_help("Option --jtagdump requires an argument\n"); + args.dump_jtag = true; + args.jtag_dump_path = argv[i + 1]; + i += 1; + } else if (s == "--jtagreplay") { + if (argc - i < 2) + exit_help("Option --jtagreplay requires an argument\n"); + args.replay_jtag = true; + args.jtag_replay_path = argv[i + 1]; + i += 1; + } else if (s == "--dump") { + if (argc - i < 3) + exit_help("Option --dump requires 2 arguments\n"); + uint32_t first = std::stoul(argv[i + 1], 0, 0); + uint32_t last = std::stoul(argv[i + 2], 0, 0); + if (first < MEM_BASE || last > MEM_BASE + MEM_SIZE || first > last) { + std::cerr << "Invalid memory range\n"; + exit(-1); + } + args.dump_ranges.push_back(std::pair( + first, last + )); + i += 2; + } else if (s == "--cycles") { + if (argc - i < 2) + exit_help("Option --cycles requires an argument\n"); + args.max_cycles = std::stol(argv[i + 1], 0, 0); + i += 1; + } else if (s == "--port") { + if (argc - i < 2) + exit_help("Option --port requires an argument\n"); + args.port = std::stol(argv[i + 1], 0, 0); + i += 1; + } else if (s == "--vpi-port") { + if (argc - i < 2) + exit_help("Option --vpi-port requires an argument\n"); + args.vpi_port = std::stol(argv[i + 1], 0, 0); + i += 1; + } else if (s == "--gdb-port") { + if (argc - i < 2) + exit_help("Option --gdb-port requires an argument\n"); + args.gdb_port = std::stol(argv[i + 1], 0, 0); + i += 1; + } else if (s == "--uart0-port") { + if (argc - i < 2) + exit_help("Option --uart0-port requires an argument\n"); + args.uart0_port = std::stol(argv[i + 1], 0, 0); + i += 1; + } else if (s == "--uart1-port") { + if (argc - i < 2) + exit_help("Option --uart1-port requires an argument\n"); + args.uart1_port = std::stol(argv[i + 1], 0, 0); + i += 1; + } else if (s == "--vpi-poll") { + if (argc - i < 2) + exit_help("Option --vpi-poll requires an argument\n"); + args.vpi_poll_cycles = (uint32_t)std::stoul(argv[i + 1], 0, 0); + i += 1; + } else if (s == "--vpi-clk-per-tck") { + if (argc - i < 2) + exit_help("Option --vpi-clk-per-tck requires an argument\n"); + args.vpi_clk_per_tck = (uint32_t)std::stoul(argv[i + 1], 0, 0); + i += 1; + } else if (s == "--cpuret") { + args.propagate_return_code = true; +#ifdef CXXRTL_DEBUG_AGENT + } else if (s == "--debug") { + args.run_agent = true; +#endif + } else { + std::cerr << "Unrecognised argument " << s << "\n"; + exit_help(""); + } + } + if (!(args.load_bin || args.port != 0 || args.vpi_port != 0 || args.gdb_port != 0 || args.replay_jtag)) + exit_help("At least one of --bin, --port, --vpi-port, --gdb-port or --jtagreplay must be specified.\n"); + if ((args.port != 0) + (args.vpi_port != 0) + (args.gdb_port != 0) > 1) + exit_help("Can't combine --port/--vpi-port/--gdb-port\n"); + if (args.dump_jtag && args.port == 0) + exit_help("--jtagdump is only supported with --port (remote bitbang)\n"); + if (args.replay_jtag && (args.port != 0 || args.vpi_port != 0 || args.gdb_port != 0)) + exit_help("Can't specify --jtagreplay together with --port/--vpi-port/--gdb-port\n"); +} diff --git a/vendor/Hazard3/test/sim/tb_common/tb_gdb.cpp b/vendor/Hazard3/test/sim/tb_common/tb_gdb.cpp new file mode 100644 index 0000000..165b971 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/tb_gdb.cpp @@ -0,0 +1,679 @@ +#include "tb_gdb.h" +#include "tb_constants.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static bool send_all(int fd, const uint8_t *buf, size_t len) { + size_t sent = 0; + while (sent < len) { +#ifdef MSG_NOSIGNAL + ssize_t n = send(fd, buf + sent, len - sent, MSG_NOSIGNAL); +#else + ssize_t n = send(fd, buf + sent, len - sent, 0); +#endif + if (n < 0) { + if (errno == EINTR) + continue; + return false; + } + sent += (size_t)n; + } + return true; +} + +tb_gdb_server::tb_gdb_server(uint16_t port, tb_gdb_target &target) + : port_{port}, target_{target} { + target_xml_ = + "\n" + "\n" + "\n" + " riscv:rv32\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + char mm_line[128]; + snprintf( + mm_line, + sizeof(mm_line), + " \n", + (unsigned)MEM_BASE, + (unsigned)MEM_SIZE + ); + + memory_map_xml_ = + "\n" + "\n" + "\n" + + std::string(mm_line) + + "\n"; +} + +tb_gdb_server::~tb_gdb_server() { + close_client_(); + if (server_fd_ >= 0) { + ::close(server_fd_); + server_fd_ = -1; + } +} + +bool tb_gdb_server::open_listen_socket_() { + server_fd_ = socket(AF_INET, SOCK_STREAM, 0); + if (server_fd_ < 0) { + fprintf(stderr, "tb_gdb: socket() failed: %s\n", strerror(errno)); + return false; + } + + int opt = 1; + if (setsockopt(server_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + fprintf(stderr, "tb_gdb: setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno)); + return false; + } +#ifdef SO_REUSEPORT + (void)setsockopt(server_fd_, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)); +#endif + + sockaddr_in addr{}; + addr.sin_family = AF_INET; + // Debug services are local by default. Remote access belongs behind an + // explicit SSH tunnel, never an unauthenticated wildcard listener. + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = htons(port_); + if (bind(server_fd_, (sockaddr *)&addr, sizeof(addr)) < 0) { + fprintf(stderr, "tb_gdb: bind(127.0.0.1:%u) failed: %s\n", (unsigned)port_, strerror(errno)); + return false; + } + if (listen(server_fd_, 1) < 0) { + fprintf(stderr, "tb_gdb: listen() failed: %s\n", strerror(errno)); + return false; + } + return true; +} + +bool tb_gdb_server::accept_client_() { + sockaddr_in addr{}; + socklen_t addr_len = sizeof(addr); + client_fd_ = accept(server_fd_, (sockaddr *)&addr, &addr_len); + if (client_fd_ < 0) { + fprintf(stderr, "tb_gdb: accept() failed: %s\n", strerror(errno)); + return false; + } + + int flag = 1; + (void)setsockopt(client_fd_, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); + return true; +} + +void tb_gdb_server::close_client_() { + if (client_fd_ >= 0) { + ::close(client_fd_); + client_fd_ = -1; + } + no_ack_mode_ = false; + breakpoints_.clear(); + ignore_breakpoint_pc_ = 0xffffffffu; +} + +uint8_t tb_gdb_server::checksum_(const std::string &s) { + uint8_t sum = 0; + for (unsigned char c : s) + sum = (uint8_t)(sum + c); + return sum; +} + +int tb_gdb_server::hex_val_(char c) { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return 10 + (c - 'a'); + if (c >= 'A' && c <= 'F') + return 10 + (c - 'A'); + return -1; +} + +std::string tb_gdb_server::to_hex_bytes_(const uint8_t *data, size_t len) { + static const char *hex = "0123456789abcdef"; + std::string out; + out.reserve(len * 2); + for (size_t i = 0; i < len; ++i) { + out.push_back(hex[(data[i] >> 4) & 0xf]); + out.push_back(hex[data[i] & 0xf]); + } + return out; +} + +bool tb_gdb_server::from_hex_bytes_(const std::string &hex, std::string &out_bytes) { + out_bytes.clear(); + if (hex.size() % 2 != 0) + return false; + out_bytes.reserve(hex.size() / 2); + for (size_t i = 0; i < hex.size(); i += 2) { + int hi = hex_val_(hex[i]); + int lo = hex_val_(hex[i + 1]); + if (hi < 0 || lo < 0) + return false; + out_bytes.push_back((char)((hi << 4) | lo)); + } + return true; +} + +void tb_gdb_server::append_u32_le_hex_(std::string &out, uint32_t v) { + for (int i = 0; i < 4; ++i) { + const uint8_t b = (uint8_t)((v >> (8 * i)) & 0xffu); + static const char *hex = "0123456789abcdef"; + out.push_back(hex[(b >> 4) & 0xf]); + out.push_back(hex[b & 0xf]); + } +} + +bool tb_gdb_server::parse_u32_hex_(const std::string &s, uint32_t &out) { + if (s.empty()) + return false; + uint32_t v = 0; + for (char c : s) { + int h = hex_val_(c); + if (h < 0) + return false; + v = (v << 4) | (uint32_t)h; + } + out = v; + return true; +} + +bool tb_gdb_server::maybe_send_ack_(bool ok) { + if (no_ack_mode_) + return true; + const char c = ok ? '+' : '-'; + return send_all(client_fd_, (const uint8_t *)&c, 1); +} + +bool tb_gdb_server::send_packet_(const std::string &payload) { + std::string pkt; + pkt.reserve(payload.size() + 4); + pkt.push_back('$'); + pkt += payload; + pkt.push_back('#'); + uint8_t cksum = checksum_(payload); + static const char *hex = "0123456789abcdef"; + pkt.push_back(hex[(cksum >> 4) & 0xf]); + pkt.push_back(hex[cksum & 0xf]); + return send_all(client_fd_, (const uint8_t *)pkt.data(), pkt.size()); +} + +bool tb_gdb_server::check_interrupt_() { + uint8_t c = 0; + ssize_t n = recv(client_fd_, &c, 1, MSG_DONTWAIT | MSG_PEEK); + if (n < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + return false; + return true; + } + if (n == 0) + return true; + if (c != 0x03) + return false; + (void)recv(client_fd_, &c, 1, MSG_DONTWAIT); + return true; +} + +bool tb_gdb_server::recv_packet_(std::string &out_payload, bool &got_interrupt) { + out_payload.clear(); + got_interrupt = false; + while (true) { + uint8_t c = 0; + ssize_t n = recv(client_fd_, &c, 1, 0); + if (n == 0) + return false; + if (n < 0) { + if (errno == EINTR) + continue; + return false; + } + + if (c == 0x03) { + got_interrupt = true; + return true; + } + if (c == '+' || c == '-') { + continue; + } + if (c != '$') { + continue; + } + + std::string payload; + while (true) { + n = recv(client_fd_, &c, 1, 0); + if (n == 0) + return false; + if (n < 0) { + if (errno == EINTR) + continue; + return false; + } + if (c == '#') + break; + payload.push_back((char)c); + } + + char ck[2]; + for (int i = 0; i < 2; ++i) { + n = recv(client_fd_, &c, 1, 0); + if (n == 0) + return false; + if (n < 0) { + if (errno == EINTR) { + --i; + continue; + } + return false; + } + ck[i] = (char)c; + } + + bool ok = true; + const int hi = hex_val_(ck[0]); + const int lo = hex_val_(ck[1]); + if (hi < 0 || lo < 0) { + ok = false; + } else { + const uint8_t expected = (uint8_t)((hi << 4) | lo); + ok = expected == checksum_(payload); + } + + if (!maybe_send_ack_(ok)) + return false; + if (!ok) + continue; + + out_payload = std::move(payload); + return true; + } +} + +std::string tb_gdb_server::stop_reply_(int signo) const { + char buf[8]; + snprintf(buf, sizeof(buf), "S%02x", signo & 0xff); + return std::string(buf); +} + +std::string tb_gdb_server::stop_reply_exit_() const { + char buf[8]; + snprintf(buf, sizeof(buf), "W%02x", (unsigned)(target_.exit_code() & 0xffu)); + return std::string(buf); +} + +std::string tb_gdb_server::handle_command_(const std::string &cmd, bool &should_close) { + should_close = false; + + if (cmd.empty()) + return ""; + + // General queries + if (cmd == "?") + return stop_reply_(5); // SIGTRAP + + if (cmd.rfind("qSupported", 0) == 0) { + return "PacketSize=4000;qXfer:features:read+;qXfer:memory-map:read+;QStartNoAckMode+;swbreak+;hwbreak+;vContSupported+;qRcmd+"; + } + if (cmd == "QStartNoAckMode") { + no_ack_mode_ = true; + return "OK"; + } + if (cmd == "qAttached") + return "1"; + if (cmd == "qC") + return "QC1"; + if (cmd == "qfThreadInfo") + return "m1"; + if (cmd == "qsThreadInfo") + return "l"; + if (cmd.rfind("H", 0) == 0) + return "OK"; + if (cmd.rfind("qThreadExtraInfo", 0) == 0) { + const std::string s = "hart0"; + return to_hex_bytes_((const uint8_t *)s.data(), s.size()); + } + if (cmd.rfind("qSymbol", 0) == 0) + return "OK"; + if (cmd == "qTStatus") + return ""; + + // GDB "monitor" commands (OpenOCD-style). Payload is hex-encoded bytes. + if (cmd.rfind("qRcmd,", 0) == 0) { + const std::string hex = cmd.substr(strlen("qRcmd,")); + std::string decoded; + if (!from_hex_bytes_(hex, decoded)) + return "E01"; + + std::string console; + if (!target_.monitor_cmd(decoded, console)) + return ""; + + if (!console.empty()) { + const std::string payload = "O" + to_hex_bytes_((const uint8_t *)console.data(), console.size()); + (void)send_packet_(payload); + } + return "OK"; + } + + // Extended-remote mode request + if (cmd == "!") + return "OK"; + + // target.xml for riscv32 (x0..x31 + pc) + if (cmd.rfind("qXfer:features:read:target.xml:", 0) == 0) { + const std::string args = cmd.substr(strlen("qXfer:features:read:target.xml:")); + const size_t comma = args.find(','); + if (comma == std::string::npos) + return "E01"; + uint32_t off = 0, len = 0; + if (!parse_u32_hex_(args.substr(0, comma), off) || !parse_u32_hex_(args.substr(comma + 1), len)) + return "E01"; + if (off >= target_xml_.size()) + return "l"; + const size_t end = std::min(target_xml_.size(), (size_t)off + (size_t)len); + const bool more = end < target_xml_.size(); + std::string out; + out.reserve(1 + (end - off)); + out.push_back(more ? 'm' : 'l'); + out.append(target_xml_, off, end - off); + return out; + } + + // memory-map.xml (RAM region for disassembly / memory accessibility) + if (cmd.rfind("qXfer:memory-map:read::", 0) == 0) { + const std::string args = cmd.substr(strlen("qXfer:memory-map:read::")); + const size_t comma = args.find(','); + if (comma == std::string::npos) + return "E01"; + uint32_t off = 0, len = 0; + if (!parse_u32_hex_(args.substr(0, comma), off) || !parse_u32_hex_(args.substr(comma + 1), len)) + return "E01"; + if (off >= memory_map_xml_.size()) + return "l"; + const size_t end = std::min(memory_map_xml_.size(), (size_t)off + (size_t)len); + const bool more = end < memory_map_xml_.size(); + std::string out; + out.reserve(1 + (end - off)); + out.push_back(more ? 'm' : 'l'); + out.append(memory_map_xml_, off, end - off); + return out; + } + + // Registers + if (cmd == "g") { + std::string out; + out.reserve(33 * 8); + for (uint32_t r = 0; r < 33; ++r) + append_u32_le_hex_(out, target_.read_reg(r)); + return out; + } + if (cmd.size() >= 2 && cmd[0] == 'p') { + uint32_t regno = 0; + if (!parse_u32_hex_(cmd.substr(1), regno)) + return "E01"; + std::string out; + out.reserve(8); + append_u32_le_hex_(out, target_.read_reg(regno)); + return out; + } + if (cmd.size() >= 3 && cmd[0] == 'P') { + const size_t eq = cmd.find('='); + if (eq == std::string::npos) + return "E01"; + uint32_t regno = 0; + if (!parse_u32_hex_(cmd.substr(1, eq - 1), regno)) + return "E01"; + std::string bytes; + if (!from_hex_bytes_(cmd.substr(eq + 1), bytes)) + return "E01"; + if (bytes.size() < 4) + return "E01"; + uint32_t v = (uint8_t)bytes[0] | ((uint32_t)(uint8_t)bytes[1] << 8) | ((uint32_t)(uint8_t)bytes[2] << 16) | + ((uint32_t)(uint8_t)bytes[3] << 24); + target_.write_reg(regno, v); + return "OK"; + } + if (cmd.size() >= 1 && cmd[0] == 'G') { + std::string bytes; + if (!from_hex_bytes_(cmd.substr(1), bytes)) + return "E01"; + if (bytes.size() < 33 * 4) + return "E01"; + for (uint32_t r = 0; r < 33; ++r) { + const size_t i = r * 4; + uint32_t v = (uint8_t)bytes[i + 0] | ((uint32_t)(uint8_t)bytes[i + 1] << 8) | + ((uint32_t)(uint8_t)bytes[i + 2] << 16) | ((uint32_t)(uint8_t)bytes[i + 3] << 24); + target_.write_reg(r, v); + } + return "OK"; + } + + // Memory + if (cmd.size() >= 2 && cmd[0] == 'm') { + const size_t comma = cmd.find(','); + if (comma == std::string::npos) + return "E01"; + uint32_t addr = 0, len = 0; + if (!parse_u32_hex_(cmd.substr(1, comma - 1), addr) || !parse_u32_hex_(cmd.substr(comma + 1), len)) + return "E01"; + if (len == 0) + return ""; + std::string buf(len, '\0'); + if (!target_.read_mem(addr, (uint8_t *)&buf[0], (size_t)len)) + return "E01"; + return to_hex_bytes_((const uint8_t *)buf.data(), buf.size()); + } + if (cmd.size() >= 2 && cmd[0] == 'M') { + const size_t comma = cmd.find(','); + const size_t colon = cmd.find(':'); + if (comma == std::string::npos || colon == std::string::npos || comma > colon) + return "E01"; + uint32_t addr = 0, len = 0; + if (!parse_u32_hex_(cmd.substr(1, comma - 1), addr) || !parse_u32_hex_(cmd.substr(comma + 1, colon - comma - 1), len)) + return "E01"; + if (len == 0) + return "OK"; + std::string bytes; + if (!from_hex_bytes_(cmd.substr(colon + 1), bytes)) + return "E01"; + if (bytes.size() < len) + return "E01"; + if (!target_.write_mem(addr, (const uint8_t *)bytes.data(), len)) + return "E01"; + return "OK"; + } + + // Execute breakpoints. + // + // GDB's stock stepi implementation may place a temporary software + // breakpoint (Z0) or hardware breakpoint (Z1) at the predicted next PC, + // for example when stepping across an indirect jalr into a read-only text + // section. This testbench doesn't patch target memory, so both packet types + // are handled identically as execute breakpoints checked in the run loop. + if (cmd.rfind("Z0,", 0) == 0 || cmd.rfind("z0,", 0) == 0 || + cmd.rfind("Z1,", 0) == 0 || cmd.rfind("z1,", 0) == 0) { + const bool set = cmd[0] == 'Z'; + const size_t comma1 = cmd.find(','); + const size_t comma2 = cmd.find(',', comma1 + 1); + if (comma1 == std::string::npos || comma2 == std::string::npos) + return "E01"; + uint32_t addr = 0; + if (!parse_u32_hex_(cmd.substr(comma1 + 1, comma2 - comma1 - 1), addr)) + return "E01"; + if (set) + breakpoints_.insert(addr); + else + breakpoints_.erase(addr); + return "OK"; + } + + // vCont + if (cmd == "vCont?") + return "vCont;c;s"; + + auto do_step = [&](bool single_step) -> std::string { + const uint32_t start_pc = target_.read_reg(32); + const bool ignore_start_break = single_step || start_pc == ignore_breakpoint_pc_; + const bool consume_ignore_pc = start_pc == ignore_breakpoint_pc_; + if (consume_ignore_pc) + ignore_breakpoint_pc_ = 0xffffffffu; + + if (single_step) { + const tb_gdb_run_result r = target_.step_instruction(); + if (r == tb_gdb_run_result::exited) { + // In extended-remote mode keep the connection alive after target exit, + // so the user can issue monitor commands such as "reset halt". + return stop_reply_exit_(); + } + if (r == tb_gdb_run_result::timed_out) { + should_close = true; + return stop_reply_(14); // SIGALRM + } + ignore_breakpoint_pc_ = 0xffffffffu; + return stop_reply_(5); // SIGTRAP + } + + bool first_iter = true; + while (true) { + if (check_interrupt_()) { + ignore_breakpoint_pc_ = 0xffffffffu; + return stop_reply_(2); // SIGINT + } + + const uint32_t pc = target_.read_reg(32); + if (breakpoints_.count(pc) && !(first_iter && ignore_start_break && pc == start_pc)) { + ignore_breakpoint_pc_ = pc; + return stop_reply_(5); // SIGTRAP + } + + const tb_gdb_run_result r = target_.step_instruction(); + ignore_breakpoint_pc_ = 0xffffffffu; + if (r == tb_gdb_run_result::exited) { + // In extended-remote mode keep the connection alive after target exit, + // so the user can issue monitor commands such as "reset halt". + return stop_reply_exit_(); + } + if (r == tb_gdb_run_result::timed_out) { + should_close = true; + return stop_reply_(14); // SIGALRM + } + first_iter = false; + } + }; + + if (cmd.size() >= 1 && (cmd[0] == 'c' || cmd[0] == 's')) { + // Optional resume address + if (cmd.size() > 1) { + uint32_t addr = 0; + if (!parse_u32_hex_(cmd.substr(1), addr)) + return "E01"; + target_.write_reg(32, addr); + } + return do_step(cmd[0] == 's'); + } + if (cmd.rfind("vCont;", 0) == 0) { + if (cmd == "vCont;c") + return do_step(false); + if (cmd == "vCont;s") + return do_step(true); + return ""; + } + + // Detach / kill + if (cmd == "D") { + should_close = true; + return "OK"; + } + if (cmd == "k") { + should_close = true; + return "OK"; + } + + // Unknown/unsupported -> empty response + return ""; +} + +tb_gdb_run_result tb_gdb_server::serve() { + if (!open_listen_socket_()) + return tb_gdb_run_result::error; + + fprintf(stderr, "tb_gdb: listening on 127.0.0.1:%u\n", (unsigned)port_); + if (!accept_client_()) + return tb_gdb_run_result::error; + + fprintf(stderr, "tb_gdb: client connected\n"); + if (!send_packet_(stop_reply_(5))) + return tb_gdb_run_result::error; + + while (true) { + std::string cmd; + bool got_interrupt = false; + if (!recv_packet_(cmd, got_interrupt)) + break; + if (got_interrupt) { + if (!send_packet_(stop_reply_(2))) + break; + continue; + } + + bool should_close = false; + std::string reply = handle_command_(cmd, should_close); + if (!send_packet_(reply)) + break; + + if (should_close) { + if (!reply.empty() && reply[0] == 'W') + return tb_gdb_run_result::exited; + break; + } + } + + close_client_(); + return tb_gdb_run_result::ok; +} diff --git a/vendor/Hazard3/test/sim/tb_common/tb_jtag.cpp b/vendor/Hazard3/test/sim/tb_common/tb_jtag.cpp new file mode 100644 index 0000000..831eaaf --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/tb_jtag.cpp @@ -0,0 +1,481 @@ +#include "tb_cli.h" +#include "tb_jtag.h" + +#include +#include +#include +#include +#include + +// This file contains socket management logic and parsing of OpenOCD JTAG +// protocols: +// - remote_bitbang (simple, lockstep with CPU clock) +// - jtag_vpi (batched, higher throughput) + + +static int wait_for_connection_bitbang(int server_fd, uint16_t port, struct sockaddr *sock_addr, socklen_t *sock_addr_len) { + int sock_fd; + printf("Waiting for connection on port %u\n", port); + if (listen(server_fd, 3) < 0) { + fprintf(stderr, "listen failed\n"); + exit(-1); + } + sock_fd = accept(server_fd, sock_addr, sock_addr_len); + if (sock_fd < 0) { + fprintf(stderr, "accept failed\n"); + exit(-1); + } + printf("Connected\n"); + return sock_fd; +} + +static int wait_for_connection_vpi(int server_fd, uint16_t port, struct sockaddr *sock_addr, socklen_t *sock_addr_len) { + int sock_fd; + printf("Listening on port %u\n", port); + if (listen(server_fd, 3) < 0) { + fprintf(stderr, "listen failed\n"); + exit(-1); + } + sock_fd = accept(server_fd, sock_addr, sock_addr_len); + if (sock_fd < 0) { + fprintf(stderr, "accept failed\n"); + exit(-1); + } + printf("Connected\n"); + return sock_fd; +} + +static uint32_t load_le32(const uint8_t *p) { + return (uint32_t)p[0] + | ((uint32_t)p[1] << 8) + | ((uint32_t)p[2] << 16) + | ((uint32_t)p[3] << 24); +} + +static void store_le32(uint8_t *p, uint32_t v) { + p[0] = v & 0xffu; + p[1] = (v >> 8) & 0xffu; + p[2] = (v >> 16) & 0xffu; + p[3] = (v >> 24) & 0xffu; +} + +static bool get_bit_lsb0(const uint8_t *buf, uint32_t bit_idx) { + return (buf[bit_idx / 8] >> (bit_idx % 8)) & 0x1; +} + +static void set_bit_lsb0(uint8_t *buf, uint32_t bit_idx, bool value) { + const uint8_t mask = 1u << (bit_idx % 8); + if (value) { + buf[bit_idx / 8] |= mask; + } else { + buf[bit_idx / 8] &= ~mask; + } +} + +// Perform one JTAG clock cycle (TCK low -> high -> low) and return the TDO bit +// sampled *before* the rising edge. This matches OpenOCD's scan semantics. +static bool jtag_clock(tb_top &tb, bool tms, bool tdi) { + // Sample TDO (stable between the previous negedge and the next posedge), + // then generate a posedge+negedge pair with the new TMS/TDI values. + const bool tdo = tb.get_tdo(); + tb.set_tms(tms); + tb.set_tdi(tdi); + tb.set_tck(true); + tb.eval(); + + tb.set_tck(false); + tb.eval(); + + return tdo; +} + +static bool send_all(int fd, const uint8_t *buf, size_t len) { + size_t sent = 0; + while (sent < len) { + ssize_t n = send(fd, buf + sent, len - sent, 0); + if (n < 0) { + if (errno == EINTR) + continue; + return false; + } + sent += (size_t)n; + } + return true; +} + +tb_jtag_state::tb_jtag_state(const tb_cli_args &_args) { + args = _args; + transport = transport_t::none; + server_fd = -1; + sock_fd = -1; + sock_opt = 1; + sock_addr_len = sizeof(sock_addr); + rx_ptr = 0; + rx_remaining = 0; + tx_ptr = 0; + vpi_rx_count = 0; + vpi_poll_ctr = 0; + vpi_poll_backoff = 0; + + if (args.vpi_port != 0) { + transport = transport_t::jtag_vpi; + server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (server_fd < 0) { + fprintf(stderr, "socket creation failed: %s\n", strerror(errno)); + exit(-1); + } + + if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &sock_opt, sizeof(sock_opt)) < 0) { + fprintf(stderr, "setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno)); + exit(-1); + } +#ifdef SO_REUSEPORT + // Best-effort: can fail on some kernels/configs, but is not required. + (void)setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &sock_opt, sizeof(sock_opt)); +#endif + + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sock_addr.sin_port = htons(args.vpi_port); + if (bind(server_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0) { + fprintf(stderr, "bind failed: %s\n", strerror(errno)); + exit(-1); + } + + sock_fd = wait_for_connection_vpi(server_fd, args.vpi_port, (struct sockaddr *)&sock_addr, &sock_addr_len); + + // Low-latency local socket traffic helps performance a lot. + int flag = 1; + setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); + } else if (args.port != 0) { + transport = transport_t::remote_bitbang; + server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (server_fd < 0) { + fprintf(stderr, "socket creation failed: %s\n", strerror(errno)); + exit(-1); + } + + if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &sock_opt, sizeof(sock_opt)) < 0) { + fprintf(stderr, "setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno)); + exit(-1); + } +#ifdef SO_REUSEPORT + // Best-effort: can fail on some kernels/configs, but is not required. + (void)setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &sock_opt, sizeof(sock_opt)); +#endif + + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sock_addr.sin_port = htons(args.port); + if (bind(server_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0) { + fprintf(stderr, "bind failed: %s\n", strerror(errno)); + exit(-1); + } + + sock_fd = wait_for_connection_bitbang(server_fd, args.port, (struct sockaddr *)&sock_addr, &sock_addr_len); + } else if (args.replay_jtag) { + transport = transport_t::remote_bitbang; + } + + if (args.dump_jtag) { + jtag_dump_fd.open(args.jtag_dump_path); + if (!jtag_dump_fd.is_open()) { + std::cerr << "Failed to open \"" << args.jtag_dump_path << "\"\n"; + exit(-1); + } + } + + if (args.replay_jtag) { + jtag_replay_fd.open(args.jtag_replay_path); + if (!jtag_replay_fd.is_open()) { + std::cerr << "Failed to open \"" << args.jtag_replay_path << "\"\n"; + exit(-1); + } + } + +} + +// Return true if an exit command was received +bool tb_jtag_state::step(tb_top &tb, mem_io_state *memio, int64_t *cycle_count, bool *timed_out, uint32_t *core_cycles_advanced) { + if (transport == transport_t::none) { + if (core_cycles_advanced) + *core_cycles_advanced = 0; + return false; + } + + if (core_cycles_advanced) + *core_cycles_advanced = 0; + + const bool can_advance_core = transport == transport_t::jtag_vpi + && memio != nullptr + && cycle_count != nullptr + && timed_out != nullptr + && args.vpi_clk_per_tck != 0; + + auto advance_one_core_cycle = [&]() -> bool { + if (!can_advance_core) + return false; + if (args.max_cycles != 0 && *cycle_count >= args.max_cycles) { + *timed_out = true; + return true; + } + memio->step(tb); + tb.step(args, *memio); + ++(*cycle_count); + if (core_cycles_advanced) + ++(*core_cycles_advanced); + if (memio->exit_req) + return true; + if (args.max_cycles != 0 && *cycle_count >= args.max_cycles) { + *timed_out = true; + return true; + } + return false; + }; + + if (transport == transport_t::jtag_vpi) { + // Socket is blocking, but reads are non-blocking via MSG_DONTWAIT so the + // CPU can free-run when openocd is idle. + // + // Important: a recv() syscall every core cycle is very expensive. When + // OpenOCD is idle (no data available), back off for N core cycles + // (configurable). When data is available, process immediately (no + // throughput throttling between packets). + if (vpi_rx_count == 0 && args.vpi_poll_cycles != 0 && vpi_poll_ctr != 0) { + --vpi_poll_ctr; + return false; + } + + // Protocol constants must match OpenOCD's jtag_vpi driver. + static constexpr uint32_t CMD_RESET = 0; + static constexpr uint32_t CMD_TMS_SEQ = 1; + static constexpr uint32_t CMD_SCAN_CHAIN = 2; + static constexpr uint32_t CMD_SCAN_CHAIN_FLIP_TMS = 3; + static constexpr uint32_t CMD_STOP_SIMU = 4; + + static constexpr int OFF_CMD = 0; + static constexpr int OFF_OUT = 4; + static constexpr int OFF_IN = 4 + VPI_XFERT_MAX_SIZE; + static constexpr int OFF_LEN = 4 + VPI_XFERT_MAX_SIZE + VPI_XFERT_MAX_SIZE; + static constexpr int OFF_NB_BITS = OFF_LEN + 4; + + while (vpi_rx_count < VPI_PKT_SIZE) { + ssize_t n = recv(sock_fd, vpi_rxbuf + vpi_rx_count, VPI_PKT_SIZE - vpi_rx_count, MSG_DONTWAIT); + if (n < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // No data available right now. If we don't have a partial + // packet, back off for a while to reduce syscall overhead. + // + // Use an exponential backoff up to vpi_poll_cycles. This + // avoids large fixed delays between back-to-back OpenOCD + // packets (e.g. GDB single-step), while still reducing + // syscall rate when OpenOCD is truly idle. + if (vpi_rx_count == 0 && args.vpi_poll_cycles != 0) { + if (vpi_poll_backoff == 0) { + vpi_poll_backoff = 1; + } else if (vpi_poll_backoff < args.vpi_poll_cycles) { + uint32_t next = vpi_poll_backoff * 2; + if (next < vpi_poll_backoff) + next = args.vpi_poll_cycles; + if (next > args.vpi_poll_cycles) + next = args.vpi_poll_cycles; + vpi_poll_backoff = next; + } + vpi_poll_ctr = vpi_poll_backoff; + } + return false; + } + fprintf(stderr, "jtag_vpi recv failed: %s\n", strerror(errno)); + return true; + } + if (n == 0) { + printf("jtag_vpi connection closed\n"); + ::close(sock_fd); + sock_fd = wait_for_connection_vpi(server_fd, args.vpi_port, (struct sockaddr *)&sock_addr, &sock_addr_len); + int flag = 1; + setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); + vpi_rx_count = 0; + vpi_poll_ctr = 0; + vpi_poll_backoff = 0; + return false; + } + vpi_rx_count += (int)n; + // Any received data means OpenOCD is active again. + vpi_poll_backoff = 0; + } + vpi_poll_ctr = 0; + vpi_poll_backoff = 0; + + const uint32_t cmd = load_le32(vpi_rxbuf + OFF_CMD); + const uint32_t length = load_le32(vpi_rxbuf + OFF_LEN); + const uint32_t nb_bits = load_le32(vpi_rxbuf + OFF_NB_BITS); + const uint8_t *buf_out = vpi_rxbuf + OFF_OUT; + + bool got_exit_cmd = false; + bool core_stop = false; + switch (cmd) { + case CMD_RESET: { + // Best-effort: reset the TAP via TRST, and also provide 5 TMS=1 clocks + // to force Test-Logic-Reset on designs without TRST. + tb.set_trst_n(false); + tb.eval(); + tb.set_trst_n(true); + tb.eval(); + for (int i = 0; i < 5; ++i) { + jtag_clock(tb, true, false); + } + break; + } + case CMD_TMS_SEQ: { + if (length > (uint32_t)VPI_XFERT_MAX_SIZE || nb_bits > (uint32_t)VPI_XFERT_MAX_SIZE * 8 || length * 8 < nb_bits) { + fprintf(stderr, "jtag_vpi: invalid tms_seq length=%u nb_bits=%u\n", length, nb_bits); + got_exit_cmd = true; + break; + } + bool stop_due_to_core = false; + for (uint32_t i = 0; i < nb_bits; ++i) { + const bool tms = get_bit_lsb0(buf_out, i); + jtag_clock(tb, tms, false); + if (can_advance_core) { + for (uint32_t j = 0; j < args.vpi_clk_per_tck; ++j) { + if (advance_one_core_cycle()) { + stop_due_to_core = true; + break; + } + } + } + if (stop_due_to_core) + break; + } + if (stop_due_to_core) { + // Simulation requested stop (timeout/exit); caller will handle. + core_stop = true; + } + break; + } + case CMD_SCAN_CHAIN: + case CMD_SCAN_CHAIN_FLIP_TMS: { + if (length > (uint32_t)VPI_XFERT_MAX_SIZE || nb_bits > (uint32_t)VPI_XFERT_MAX_SIZE * 8 || length * 8 < nb_bits) { + fprintf(stderr, "jtag_vpi: invalid scan length=%u nb_bits=%u\n", length, nb_bits); + got_exit_cmd = true; + break; + } + + uint8_t resp[VPI_PKT_SIZE]; + memset(resp, 0, sizeof(resp)); + store_le32(resp + OFF_CMD, cmd); + store_le32(resp + OFF_LEN, length); + store_le32(resp + OFF_NB_BITS, nb_bits); + + uint8_t *buf_in = resp + OFF_IN; + for (uint32_t i = 0; i < nb_bits; ++i) { + const bool tdi = get_bit_lsb0(buf_out, i); + const bool tms = (cmd == CMD_SCAN_CHAIN_FLIP_TMS) && (i + 1 == nb_bits); + const bool tdo = jtag_clock(tb, tms, tdi); + set_bit_lsb0(buf_in, i, tdo); + } + + if (!send_all(sock_fd, resp, sizeof(resp))) { + fprintf(stderr, "jtag_vpi send failed: %s\n", strerror(errno)); + got_exit_cmd = true; + } + break; + } + case CMD_STOP_SIMU: + printf("OpenOCD requested stop simulation\n"); + got_exit_cmd = true; + break; + default: + fprintf(stderr, "jtag_vpi: unknown cmd=%u\n", cmd); + got_exit_cmd = true; + break; + } + + vpi_rx_count = 0; + if (core_stop) + return false; + return got_exit_cmd; + } + + // If JTAG is enabled, we run the simulator in lockstep with the remote + // bitbang commands, to get more consistent simulation traces. This slows + // down simulation quite a bit compared with normal free-running. + // + // Most bitbang commands complete in one cycle (e.g. TCK/TMS/TDI writes) + // but reads take 0 cycles, step=false. + bool got_exit_cmd = false; + bool step = false; + while (!step) { + if (rx_remaining > 0) { + char c = rxbuf[rx_ptr++]; + --rx_remaining; + + if (c == 'r' || c == 's') { + tb.set_trst_n(true); + step = true; + } else if (c == 't' || c == 'u') { + tb.set_trst_n(false); + } else if (c >= '0' && c <= '7') { + int mask = c - '0'; + tb.set_tck(mask & 0x4); + tb.set_tms(mask & 0x2); + tb.set_tdi(mask & 0x1); + step = true; + } else if (c == 'R') { + if (!args.replay_jtag) { + txbuf[tx_ptr++] = tb.get_tdo() ? '1' : '0'; + if (tx_ptr >= TCP_BUF_SIZE || rx_remaining == 0) { + send(sock_fd, txbuf, tx_ptr, 0); + tx_ptr = 0; + } + } + } else if (c == 'Q') { + printf("OpenOCD sent quit command\n"); + got_exit_cmd = true; + step = true; + } + } else { + // Potentially the last command was not a read command, but + // OpenOCD is still waiting for a last response from its + // last command packet before it sends us any more, so now is + // the time to flush TX. + if (tx_ptr > 0) { + if (!args.replay_jtag) + send(sock_fd, txbuf, tx_ptr, 0); + tx_ptr = 0; + } + rx_ptr = 0; + if (args.replay_jtag) { + rx_remaining = jtag_replay_fd.readsome(rxbuf, TCP_BUF_SIZE); + } else { + rx_remaining = read(sock_fd, &rxbuf, TCP_BUF_SIZE); + } + if (args.dump_jtag && rx_remaining > 0) { + jtag_dump_fd.write(rxbuf, rx_remaining); + } + if (rx_remaining == 0) { + if (args.port == 0) { + // Presumably EOF, so quit. + got_exit_cmd = true; + } else { + // The socket is closed. Wait for another connection. + sock_fd = wait_for_connection_bitbang(server_fd, args.port, (struct sockaddr *)&sock_addr, &sock_addr_len); + } + } + } + } + return got_exit_cmd; +} + +void tb_jtag_state::close() { + if (sock_fd >= 0) + ::close(sock_fd); + if (server_fd >= 0) + ::close(server_fd); + if (args.dump_jtag) { + jtag_dump_fd.close(); + } + if (args.replay_jtag) { + jtag_replay_fd.close(); + } +} diff --git a/vendor/Hazard3/test/sim/tb_common/tb_memio.cpp b/vendor/Hazard3/test/sim/tb_common/tb_memio.cpp new file mode 100644 index 0000000..df0336d --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/tb_memio.cpp @@ -0,0 +1,200 @@ +#include "tb.h" + +#include +#include + +mem_io_state::mem_io_state(const tb_cli_args &args) : uart(args) { + mtime = 0; + mtimecmp[0] = 0; + mtimecmp[1] = 0; + exit_req = false; + exit_code = 0; + monitor_enabled = false; + soft_irq_state = 0; + irq_state = 0; + for (int i = 0; i < N_RESERVATIONS; ++i) { + reservation_valid[i] = false; + reservation_addr[i] = 0; + } + poison_addr = -4u; + mem = new uint8_t[MEM_SIZE]; + for (size_t i = 0; i < MEM_SIZE; ++i) + mem[i] = 0; + + if (args.load_bin) { + std::ifstream fd(args.bin_path, std::ios::binary | std::ios::ate); + if (!fd){ + std::cerr << "Failed to open \"" << args.bin_path << "\"\n"; + exit(-1); + } + std::streamsize bin_size = fd.tellg(); + if (bin_size > MEM_SIZE) { + std::cerr << "Binary file (" << bin_size << " bytes) is larger than memory (" << MEM_SIZE << " bytes)\n"; + exit(-1); + } + fd.seekg(0, std::ios::beg); + fd.read((char*)mem, bin_size); + } +} + +bus_response tb_mem_access(tb_top &tb, mem_io_state &memio, bus_request req) { + bus_response resp; + + // Global monitor. When monitor is not enabled, HEXOKAY is tied high + if (memio.monitor_enabled) { + if (req.excl) { + // Always set reservation on read. Always clear reservation on + // write. On successful write, clear others' matching reservations. + if (req.write) { + resp.exokay = memio.reservation_valid[req.reservation_id] && + memio.reservation_addr[req.reservation_id] == (req.addr & RESERVATION_ADDR_MASK); + memio.reservation_valid[req.reservation_id] = false; + if (resp.exokay) { + for (int i = 0; i < N_RESERVATIONS; ++i) { + if (i == req.reservation_id) + continue; + if (memio.reservation_addr[i] == (req.addr & RESERVATION_ADDR_MASK)) + memio.reservation_valid[i] = false; + } + } + } else { + resp.exokay = true; + memio.reservation_valid[req.reservation_id] = true; + memio.reservation_addr[req.reservation_id] = req.addr & RESERVATION_ADDR_MASK; + } + } else { + resp.exokay = false; + // Non-exclusive write still clears others' reservations + if (req.write) { + for (int i = 0; i < N_RESERVATIONS; ++i) { + if (i == req.reservation_id) + continue; + if (memio.reservation_addr[i] == (req.addr & RESERVATION_ADDR_MASK)) + memio.reservation_valid[i] = false; + } + } + } + } + + + if (req.write) { + if (memio.monitor_enabled && req.excl && !resp.exokay) { + // Failed exclusive write; do nothing + } else if ((req.addr & -4u) == memio.poison_addr) { + resp.err = true; + } else if (req.addr >= MEM_BASE && req.addr <= MEM_BASE + MEM_SIZE - (1u << (int)req.size)) { + unsigned int n_bytes = 1u << (int)req.size; + // Note we are relying on hazard3's byte lane replication + for (unsigned int i = 0; i < n_bytes; ++i) { + memio.mem[req.addr + i - MEM_BASE] = req.wdata >> (8 * i) & 0xffu; + } + } else if (req.addr == IO_BASE + IO_PRINT_CHAR) { + const uint8_t ch = (uint8_t)(req.wdata & 0xffu); + fprintf(tb.logfile, "%c", (char)ch); + memio.uart.write_data(0, ch); + } else if (req.addr == IO_BASE + IO_PRINT_U32) { + fprintf(tb.logfile, "%08x\n", req.wdata); + } else if (req.addr == IO_BASE + IO_EXIT) { + if (!memio.exit_req) { + memio.exit_req = true; + memio.exit_code = req.wdata; + } + } else if (req.addr == IO_BASE + IO_SET_SOFTIRQ) { + memio.soft_irq_state |= req.wdata; + tb.set_soft_irq(memio.soft_irq_state); + } else if (req.addr == IO_BASE + IO_CLR_SOFTIRQ) { + memio.soft_irq_state &= ~req.wdata; + tb.set_soft_irq(memio.soft_irq_state); + } else if (req.addr == IO_BASE + IO_GLOBMON_EN) { + memio.monitor_enabled = req.wdata; + } else if (req.addr == IO_BASE + IO_POISON_ADDR) { + memio.poison_addr = req.wdata & -4u; + } else if (req.addr == IO_BASE + IO_SET_IRQ) { + memio.irq_state |= req.wdata; + tb.set_irq(memio.irq_state); + } else if (req.addr == IO_BASE + IO_CLR_IRQ) { + memio.irq_state &= ~req.wdata; + tb.set_irq(memio.irq_state); + } else if (req.addr == IO_BASE + IO_MTIME) { + memio.mtime = (memio.mtime & 0xffffffff00000000u) | req.wdata; + } else if (req.addr == IO_BASE + IO_MTIMEH) { + memio.mtime = (memio.mtime & 0x00000000ffffffffu) | ((uint64_t)req.wdata << 32); + } else if (req.addr == IO_BASE + IO_MTIMECMP0) { + memio.mtimecmp[0] = (memio.mtimecmp[0] & 0xffffffff00000000u) | req.wdata; + } else if (req.addr == IO_BASE + IO_MTIMECMP0H) { + memio.mtimecmp[0] = (memio.mtimecmp[0] & 0x00000000ffffffffu) | ((uint64_t)req.wdata << 32); + } else if (req.addr == IO_BASE + IO_MTIMECMP1) { + memio.mtimecmp[1] = (memio.mtimecmp[1] & 0xffffffff00000000u) | req.wdata; + } else if (req.addr == IO_BASE + IO_MTIMECMP1H) { + memio.mtimecmp[1] = (memio.mtimecmp[1] & 0x00000000ffffffffu) | ((uint64_t)req.wdata << 32); + } else if (req.addr >= IO_BASE + IO_UART_BASE && req.addr < IO_BASE + IO_UART_BASE + IO_UART_N * IO_UART_STRIDE) { + const uint32_t rel = req.addr - (IO_BASE + IO_UART_BASE); + const uint32_t uart_idx = rel / IO_UART_STRIDE; + const uint32_t reg_off = rel % IO_UART_STRIDE; + if (reg_off == IO_UART_DATA) { + const uint8_t ch = (uint8_t)(req.wdata & 0xffu); + if (uart_idx == 0) + fprintf(tb.logfile, "%c", (char)ch); + memio.uart.write_data(uart_idx, ch); + } else if (reg_off == IO_UART_CTRL) { + memio.uart.write_ctrl(uart_idx, req.wdata); + } else { + resp.err = true; + } + } else { + resp.err = true; + } + } else { + if (req.addr == (memio.poison_addr & -4u)) { + resp.err = true; + } else if (req.addr >= MEM_BASE && req.addr <= MEM_BASE + MEM_SIZE - (1u << (int)req.size)) { + req.addr &= ~0x3u; + req.addr -= MEM_BASE; + resp.rdata = + (uint32_t)memio.mem[req.addr] | + memio.mem[req.addr + 1] << 8 | + memio.mem[req.addr + 2] << 16 | + memio.mem[req.addr + 3] << 24; + } else if (req.addr >= IO_BASE + IO_UART_BASE && req.addr < IO_BASE + IO_UART_BASE + IO_UART_N * IO_UART_STRIDE) { + const uint32_t rel = req.addr - (IO_BASE + IO_UART_BASE); + const uint32_t uart_idx = rel / IO_UART_STRIDE; + const uint32_t reg_off = rel % IO_UART_STRIDE; + if (reg_off == IO_UART_STATUS) { + resp.rdata = memio.uart.read_status(uart_idx); + } else if (reg_off == IO_UART_DATA) { + resp.rdata = memio.uart.read_data(uart_idx); + } else { + resp.err = true; + } + } else if (req.addr == IO_BASE + IO_SET_SOFTIRQ || req.addr == IO_BASE + IO_CLR_SOFTIRQ) { + resp.rdata = memio.soft_irq_state; + } else if (req.addr == IO_BASE + IO_SET_IRQ || req.addr == IO_BASE + IO_CLR_IRQ) { + resp.rdata = memio.irq_state; + } else if (req.addr == IO_BASE + IO_MTIME) { + resp.rdata = memio.mtime; + } else if (req.addr == IO_BASE + IO_MTIMEH) { + resp.rdata = memio.mtime >> 32; + } else if (req.addr == IO_BASE + IO_MTIMECMP0) { + resp.rdata = memio.mtimecmp[0]; + } else if (req.addr == IO_BASE + IO_MTIMECMP0H) { + resp.rdata = memio.mtimecmp[0] >> 32; + } else if (req.addr == IO_BASE + IO_MTIMECMP1) { + resp.rdata = memio.mtimecmp[1]; + } else if (req.addr == IO_BASE + IO_MTIMECMP1H) { + resp.rdata = memio.mtimecmp[1] >> 32; + } else { + resp.err = true; + } + } + if (resp.err) { + resp.exokay = false; + } + return resp; +} + +void mem_io_state::step(tb_top &tb) { + // Default update logic for mtime, mtimecmp + ++mtime; + tb.set_timer_irq((uint8_t)((mtime >= mtimecmp[0]) | (mtime >= mtimecmp[1]) << 1)); + uart.step(); +} diff --git a/vendor/Hazard3/test/sim/tb_common/tb_rand.cpp b/vendor/Hazard3/test/sim/tb_common/tb_rand.cpp new file mode 100644 index 0000000..43b82d2 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/tb_rand.cpp @@ -0,0 +1,70 @@ +#include "tb.h" + +#include + +// TB pseudorandom number generator, using xoroshiro256++ -- original +// copyright notice follows. + +/* Written in 2019 by David Blackman and Sebastiano Vigna (vigna@acm.org) + +To the extent possible under law, the author has dedicated all copyright +and related and neighboring rights to this software to the public domain +worldwide. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +/* This is xoshiro256++ 1.0, one of our all-purpose, rock-solid generators. + It has excellent (sub-ns) speed, a state (256 bits) that is large + enough for any parallel application, and it passes all tests we are + aware of. + + For generating just floating-point numbers, xoshiro256+ is even faster. + + The state must be seeded so that it is not everywhere zero. If you have + a 64-bit seed, we suggest to seed a splitmix64 generator and use its + output to fill s. */ + +static inline uint64_t rotl(const uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +uint32_t tb_top::rand(void) { + const uint64_t result = rotl(rand_state[0] + rand_state[3], 23) + rand_state[0]; + + const uint64_t t = rand_state[1] << 17; + + rand_state[2] ^= rand_state[0]; + rand_state[3] ^= rand_state[1]; + rand_state[1] ^= rand_state[2]; + rand_state[0] ^= rand_state[3]; + + rand_state[2] ^= t; + + rand_state[3] = rotl(rand_state[3], 45); + + return result >> 32; +} + +void tb_top::seed_rand(const uint8_t *data, size_t len) { + // Initial state must not be all-zeroes + for (unsigned int i = 0; i < 4; ++i) { + rand_state[i] = 0xf005ba11u + i; + } + // Pour + stir method: XOR data in one bit at a time, with a xoroshiro + // permutation between each. + for (size_t i = 0; i < 8u * len; ++i) { + if (data[i / 8u] & (1u << (i % 8u))) { + rand_state[0] ^= 1u; + } + (void)rand(); + } +} diff --git a/vendor/Hazard3/test/sim/tb_common/tb_uart.cpp b/vendor/Hazard3/test/sim/tb_common/tb_uart.cpp new file mode 100644 index 0000000..a70ca1b --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_common/tb_uart.cpp @@ -0,0 +1,245 @@ +#include "tb_uart.h" + +#include "tb_cli.h" +#include "tb_constants.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace { + +void close_fd(int &fd) { + if (fd < 0) + return; + (void)::close(fd); + fd = -1; +} + +bool set_nonblocking(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) + return false; + return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0; +} + +int send_flags() { + int flags = 0; +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + return flags; +} + +} // namespace + +tb_uart_state::tb_uart_state(const tb_cli_args &args) { + uarts[0].init(args.uart0_port); + uarts[1].init(args.uart1_port); + if (args.uart0_port != 0) + std::cout << "UART0 listening on port " << args.uart0_port << "\n"; + if (args.uart1_port != 0) + std::cout << "UART1 listening on port " << args.uart1_port << "\n"; +} + +tb_uart_state::~tb_uart_state() { + for (uint32_t i = 0; i < N_UARTS; ++i) + uarts[i].close(); +} + +void tb_uart_state::uart::init(uint16_t port_) { + port = port_; + if (port == 0) + return; + + server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (server_fd < 0) { + std::cerr << "UART socket creation failed: " << strerror(errno) << "\n"; + exit(-1); + } + + int opt = 1; + if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + std::cerr << "UART setsockopt(SO_REUSEADDR) failed: " << strerror(errno) << "\n"; + exit(-1); + } +#ifdef SO_REUSEPORT + // Best-effort: can fail on some kernels/configs, but is not required. + (void)setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)); +#endif + + bind_addr.sin_family = AF_INET; + bind_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + bind_addr.sin_port = htons(port); + if (bind(server_fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) { + std::cerr << "UART bind failed: " << strerror(errno) << "\n"; + exit(-1); + } + if (listen(server_fd, 1) < 0) { + std::cerr << "UART listen failed: " << strerror(errno) << "\n"; + exit(-1); + } + if (!set_nonblocking(server_fd)) { + std::cerr << "UART fcntl(O_NONBLOCK) failed: " << strerror(errno) << "\n"; + exit(-1); + } +} + +void tb_uart_state::uart::close() { + close_fd(client_fd); + close_fd(server_fd); + port = 0; + overrun = false; + rx_fifo.clear(); + tx_fifo.clear(); +} + +void tb_uart_state::uart::poll_accept(uint32_t uart_idx) { + if (server_fd < 0 || client_fd >= 0) + return; + + sockaddr_in peer {}; + socklen_t peer_len = sizeof(peer); + int fd = accept(server_fd, (struct sockaddr *)&peer, &peer_len); + if (fd < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + return; + std::cerr << "UART" << uart_idx << " accept failed: " << strerror(errno) << "\n"; + return; + } + + client_fd = fd; + (void)set_nonblocking(client_fd); + + // Low-latency local socket traffic helps interactivity. + int flag = 1; + setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); +#ifdef SO_NOSIGPIPE + // Best-effort: avoid SIGPIPE on some platforms. + (void)setsockopt(client_fd, SOL_SOCKET, SO_NOSIGPIPE, &flag, sizeof(flag)); +#endif + + std::cout << "UART" << uart_idx << " connected\n"; +} + +void tb_uart_state::uart::poll_rx(uint32_t uart_idx) { + poll_accept(uart_idx); + if (client_fd < 0) + return; + + uint8_t buf[1024]; + while (true) { + ssize_t n = recv(client_fd, buf, sizeof(buf), MSG_DONTWAIT); + if (n > 0) { + for (ssize_t i = 0; i < n; ++i) { + if (rx_fifo.size() >= rx_capacity) { + overrun = true; + continue; + } + rx_fifo.push_back(buf[i]); + } + continue; + } + if (n == 0) { + close_fd(client_fd); + std::cout << "UART" << uart_idx << " disconnected\n"; + return; + } + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + return; + std::cerr << "UART" << uart_idx << " recv failed: " << strerror(errno) << "\n"; + close_fd(client_fd); + std::cout << "UART" << uart_idx << " disconnected\n"; + return; + } +} + +void tb_uart_state::uart::poll_tx(uint32_t uart_idx) { + poll_accept(uart_idx); + if (client_fd < 0 || tx_fifo.empty()) + return; + + uint8_t buf[1024]; + while (!tx_fifo.empty()) { + const size_t chunk = std::min(tx_fifo.size(), sizeof(buf)); + for (size_t i = 0; i < chunk; ++i) + buf[i] = tx_fifo[i]; + + ssize_t n = send(client_fd, buf, chunk, send_flags()); + if (n > 0) { + for (ssize_t i = 0; i < n; ++i) + tx_fifo.pop_front(); + continue; + } + if (n == 0) + return; + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + return; + std::cerr << "UART" << uart_idx << " send failed: " << strerror(errno) << "\n"; + close_fd(client_fd); + std::cout << "UART" << uart_idx << " disconnected\n"; + return; + } +} + +void tb_uart_state::step() { + for (uint32_t i = 0; i < N_UARTS; ++i) { + if (!uarts[i].tx_fifo.empty()) + uarts[i].poll_tx(i); + } +} + +uint32_t tb_uart_state::read_status(uint32_t uart_idx) { + if (uart_idx >= N_UARTS) + return 0; + uart &u = uarts[uart_idx]; + u.poll_rx(uart_idx); + u.poll_tx(uart_idx); + uint32_t status = TB_UART_STATUS_TX_READY; + if (!u.rx_fifo.empty()) + status |= TB_UART_STATUS_RX_AVAIL; + if (u.connected()) + status |= TB_UART_STATUS_CONNECTED; + if (u.overrun) + status |= TB_UART_STATUS_OVERRUN; + return status; +} + +uint32_t tb_uart_state::read_data(uint32_t uart_idx) { + if (uart_idx >= N_UARTS) + return 0xffffffffu; + uart &u = uarts[uart_idx]; + u.poll_rx(uart_idx); + if (u.rx_fifo.empty()) + return 0xffffffffu; + const uint8_t byte = u.rx_fifo.front(); + u.rx_fifo.pop_front(); + return byte; +} + +void tb_uart_state::write_data(uint32_t uart_idx, uint8_t byte) { + if (uart_idx >= N_UARTS) + return; + uart &u = uarts[uart_idx]; + if (u.server_fd < 0) + return; + if (u.tx_fifo.size() >= u.tx_capacity && !u.tx_fifo.empty()) + u.tx_fifo.pop_front(); + u.tx_fifo.push_back(byte); + u.poll_tx(uart_idx); +} + +void tb_uart_state::write_ctrl(uint32_t uart_idx, uint32_t value) { + if (uart_idx >= N_UARTS) + return; + uart &u = uarts[uart_idx]; + if (value & TB_UART_CTRL_CLR_OVERRUN) + u.overrun = false; +} diff --git a/vendor/Hazard3/test/sim/tb_verilator/.gitignore b/vendor/Hazard3/test/sim/tb_verilator/.gitignore new file mode 100644 index 0000000..8431ff0 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_verilator/.gitignore @@ -0,0 +1,3 @@ +build-* +tb +tb-* diff --git a/vendor/Hazard3/test/sim/tb_verilator/Makefile b/vendor/Hazard3/test/sim/tb_verilator/Makefile new file mode 100644 index 0000000..a7af104 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_verilator/Makefile @@ -0,0 +1,53 @@ +include ../project_paths.mk + +TOP := tb +DOTF := tb.f +CONFIG := default +TBEXEC := $(patsubst %.f,%,$(DOTF)) + +ifneq ($(CONFIG),default) + TBEXEC := $(TBEXEC)-$(CONFIG) +endif + +FILE_LIST := $(shell HDL=$(HDL) $(SCRIPTS)/listfiles ../tb_common/hdl/$(DOTF)) +VINCDIR := $(shell HDL=$(HDL) $(SCRIPTS)/listfiles -f flati ../tb_common/hdl/$(DOTF)) +BUILD_DIR := build-$(patsubst %.f,%,$(DOTF)) +VOBJ_DIR := $(BUILD_DIR)/obj_dir +VLIB := $(VOBJ_DIR)/V$(TOP)__ALL.a +VERILATED_OBJS := $(VOBJ_DIR)/verilated.o $(VOBJ_DIR)/verilated_threads.o + +VERILATOR_ROOT := $(shell verilator --getenv VERILATOR_ROOT) +CXX := g++ +VERILATOR := verilator + +TB_CFILES := tb.cpp $(wildcard ../tb_common/*.cpp) +CINCLUDE := $(VOBJ_DIR) $(VERILATOR_ROOT)/include ../tb_common/include + +.PHONY: clean all lint vcc vlib + +all: $(TBEXEC) +vcc: $(BUILD_DIR)/vcc.touch +vlib: $(BUILD_DIR)/vlib.touch + +$(BUILD_DIR)/vcc.touch: $(FILE_LIST) $(wildcard *.vh) + mkdir -p $(VOBJ_DIR) + $(VERILATOR) --Mdir $(VOBJ_DIR) --cc --top-module $(TOP) $(addprefix -I,$(VINCDIR)) -DCONFIG_HEADER="\"config_$(CONFIG).vh\"" $(FILE_LIST) + touch $@ + +$(BUILD_DIR)/vlib.touch: $(BUILD_DIR)/vcc.touch + # Verilator's archive (Vtb__ALL.a) does not include the run-time objects + # (verilated.o/verilated_threads.o). Build them explicitly so the final + # tb link step succeeds across Verilator versions/distros. + $(MAKE) VERILATOR_ROOT=$(VERILATOR_ROOT) -C $(VOBJ_DIR) -f V$(TOP).mk \ + V$(TOP)__ALL.a verilated.o verilated_threads.o + touch $@ + +clean:: + rm -rf $(BUILD_DIR) $(TBEXEC) + +$(TBEXEC): $(BUILD_DIR)/vlib.touch $(TB_CFILES) + $(CXX) -O3 -std=c++14 $(addprefix -D,$(CDEFINES) $(CDEFINES_$(DOTF))) \ + $(addprefix -I ,$(CINCLUDE)) $(TB_CFILES) $(VLIB) $(VERILATED_OBJS) -o $(TBEXEC) -pthread -latomic + +lint: + $(VERILATOR) --lint-only --top-module $(TOP) $(addprefix -I,$(VINCDIR)) -DCONFIG_HEADER="\"config_$(CONFIG).vh\"" $(FILE_LIST) diff --git a/vendor/Hazard3/test/sim/tb_verilator/openocd-jtag-vpi.cfg b/vendor/Hazard3/test/sim/tb_verilator/openocd-jtag-vpi.cfg new file mode 100644 index 0000000..5d51e26 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_verilator/openocd-jtag-vpi.cfg @@ -0,0 +1,13 @@ +adapter driver jtag_vpi +jtag_vpi set_address 127.0.0.1 +jtag_vpi set_port 5555 +transport select jtag + +set _CHIPNAME hazard3 +jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0xdeadbeef +set _TARGETNAME $_CHIPNAME.cpu +target create $_TARGETNAME riscv -chain-position $_TARGETNAME + +gdb report_data_abort enable +init +halt diff --git a/vendor/Hazard3/test/sim/tb_verilator/tb.cpp b/vendor/Hazard3/test/sim/tb_verilator/tb.cpp new file mode 100644 index 0000000..7fe3349 --- /dev/null +++ b/vendor/Hazard3/test/sim/tb_verilator/tb.cpp @@ -0,0 +1,438 @@ +#include "Vtb.h" +#include "Vtb___024root.h" +#include "verilated.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "tb.h" +#include "tb_cli.h" +#include "tb_gdb.h" +#include "tb_jtag.h" + +class tb_verilator_top: public tb_top { + VerilatedContext *contextp; + Vtb *top; + + // Loop-carried address-phase requests + bus_request req_i; + bus_request req_d; + bool req_i_vld = false; + bool req_d_vld = false; + +public: + tb_verilator_top(const tb_cli_args &parsed_args, int argc, char **argv); + ~tb_verilator_top() {delete top; delete contextp;} + + void step(const tb_cli_args &args, mem_io_state &memio) override; + void eval() override {top->eval();} + + void set_trst_n(bool trst_n) override {top->trst_n = trst_n;} + void set_tck(bool tck) override {top->tck = tck;} + void set_tdi(bool tdi) override {top->tdi = tdi;} + void set_tms(bool tms) override {top->tms = tms;} + bool get_tdo() override {return top->tdo;} + void set_irq(uint32_t mask) override {top->irq = mask;} + void set_soft_irq(uint8_t mask) override {top->soft_irq = mask;} + void set_timer_irq(uint8_t mask) override {top->timer_irq = mask;} + + Vtb___024root *rootp() { return top->rootp; } + + void reset_dut(const tb_cli_args &args, mem_io_state &memio, uint32_t assert_cycles = 8, uint32_t release_cycles = 0); + +}; + +tb_verilator_top::tb_verilator_top(const tb_cli_args &parsed_args, int argc, char **argv): tb_top {parsed_args} { + contextp = new VerilatedContext; + // Verilated context also gets a chance to parse the arguments. Any + // argument prefixed with "+verilator+" is ignored by our tb arg parsing. + contextp->commandArgs(argc, argv); + top = new Vtb{contextp}; + + req_i_vld = false; + req_d_vld = false; + req_i.reservation_id = 0; + req_d.reservation_id = 1; + + // Set bus interfaces to generate good IDLE responses at first + top->i_hready = true; + top->d_hready = true; + + // Reset + initial clock pulse + top->eval(); + top->clk = true; + top->tck = true; + top->eval(); + top->clk = false; + top->tck = false; + top->trst_n = true; + top->rst_n = true; + top->eval(); +} + +void tb_verilator_top::reset_dut(const tb_cli_args &args, mem_io_state &memio, uint32_t assert_cycles, uint32_t release_cycles) { + // Clear any loop-carried bus phase state. + req_i_vld = false; + req_d_vld = false; + + // Ensure a clean default handshake state at reset release. + top->i_hready = true; + top->d_hready = true; + + // Assert global reset (and JTAG TAP reset) for a few core cycles. + top->trst_n = false; + top->rst_n = false; + for (uint32_t i = 0; i < assert_cycles; ++i) + step(args, memio); + + // Release reset. By default, we don't advance any cycles after release so + // the CPU is effectively "reset+halted" until the debugger resumes. + top->trst_n = true; + top->rst_n = true; + for (uint32_t i = 0; i < release_cycles; ++i) + step(args, memio); +} + +void tb_verilator_top::step(const tb_cli_args &args, mem_io_state &memio) { + top->clk = false; + top->eval(); + top->clk = true; + top->eval(); + + // The two bus ports are handled identically. This enables swapping out of + // various `tb.v` hardware integration files containing: + // + // - A single, dual-ported processor (instruction fetch, load/store ports) + // - A single, single-ported processor (instruction fetch + load/store muxed internally) + // - A pair of single-ported processors, for dual-core debug tests + + if (top->d_hready) { + // Clear bus error by default + top->d_hresp = false; + + // Handle current data phase + req_d.wdata = top->d_hwdata; + bus_response resp; + if (req_d_vld) + resp = mem_callback_d(*this, memio, req_d); + else + resp.exokay = !memio.monitor_enabled; + if (resp.err) { + // Phase 1 of error response + top->d_hready = false; + top->d_hresp = true; + } + if (req_d_vld && !req_d.write) { + top->d_hrdata = resp.rdata; + } else { + top->d_hrdata = rand(); + } + top->d_hexokay = resp.exokay; + } else { + // hready=0. Currently this only happens when we're in the first + // phase of an error response, so go to phase 2. + top->d_hready = true; + } + + req_d_vld = false; + if (top->d_hready) { + // Progress current address phase to data phase + req_d_vld = top->d_htrans >> 1; + req_d.write = top->d_hwrite; + req_d.size = (bus_size_t)top->d_hsize; + req_d.addr = top->d_haddr; + req_d.excl = top->d_hexcl; + } + + if (top->i_hready) { + top->i_hresp = false; + + req_i.wdata = top->i_hwdata; + bus_response resp; + if (req_i_vld) + resp = mem_callback_i(*this, memio, req_i); + else + resp.exokay = !memio.monitor_enabled; + if (resp.err) { + // Phase 1 of error response + top->i_hready = false; + top->i_hresp = true; + } + if (req_i_vld && !req_i.write) { + top->i_hrdata = resp.rdata; + } else { + top->i_hrdata = rand(); + } + top->i_hexokay = resp.exokay; + } else { + // hready=0. Currently this only happens when we're in the first + // phase of an error response, so go to phase 2. + top->i_hready = true; + } + + req_i_vld = false; + if (top->i_hready) { + // Progress current address phase to data phase + req_i_vld = top->i_htrans >> 1; + req_i.write = top->i_hwrite; + req_i.size = (bus_size_t)top->i_hsize; + req_i.addr = top->i_haddr; + req_i.excl = top->i_hexcl; + } +} + +int main(int argc, char **argv) { + tb_cli_args args; + tb_parse_args(argc, argv, args); + + VerilatedContext *contextp = new VerilatedContext; + contextp->commandArgs(argc, argv); + + tb_jtag_state jtag(args); + mem_io_state memio(args); + tb_verilator_top tb(args, argc, argv); + + bool timed_out = false; + int64_t cycle = 0; + + if (args.gdb_port != 0) { + struct verilator_gdb_target final : tb_gdb_target { + tb_verilator_top &tb; + mem_io_state &memio; + const tb_cli_args &args; + Vtb___024root *root; + int64_t &cycle; + bool &timed_out; + + verilator_gdb_target(tb_verilator_top &tb_, mem_io_state &memio_, const tb_cli_args &args_, int64_t &cycle_, bool &timed_out_) + : tb(tb_), memio(memio_), args(args_), root(tb_.rootp()), cycle(cycle_), timed_out(timed_out_) {} + + uint32_t read_reg(uint32_t regno) override { + if (regno == 0) + return 0; + if (regno < 32) { + // GDB single-step stops with decode PC already advanced to the + // next instruction, while the just-computed register result can + // still be sitting in the M stage for one cycle before it is + // committed into the architectural register file. Overlay the + // pending writeback so the debugger sees a coherent post-step + // snapshot. + if (root->tb__DOT__cpu__DOT__core__DOT__m_reg_wen_if_nonzero && + regno == root->tb__DOT__cpu__DOT__core__DOT__xm_rd) { + return (uint32_t)root->tb__DOT__cpu__DOT__core__DOT__m_result; + } + return (uint32_t)root->tb__DOT__cpu__DOT__core__DOT__regs__DOT__real_dualport_reset__DOT__mem[regno]; + } + if (regno == 32) { + return (uint32_t)root->tb__DOT__cpu__DOT__core__DOT__decode_u__DOT__pc; + } + return 0; + } + + void write_reg(uint32_t regno, uint32_t value) override { + if (regno == 0) + return; + if (regno < 32) { + root->tb__DOT__cpu__DOT__core__DOT__regs__DOT__real_dualport_reset__DOT__mem[regno] = value; + tb.eval(); + return; + } + if (regno == 32) { + root->tb__DOT__cpu__DOT__core__DOT__decode_u__DOT__pc = value; + tb.eval(); + return; + } + } + + bool read_mem(uint32_t addr, uint8_t *dst, size_t len) override { + if (len == 0) + return true; + if (addr < MEM_BASE || addr + len > MEM_BASE + MEM_SIZE) + return false; + memcpy(dst, memio.mem + (addr - MEM_BASE), len); + return true; + } + + bool write_mem(uint32_t addr, const uint8_t *src, size_t len) override { + if (len == 0) + return true; + if (addr < MEM_BASE || addr + len > MEM_BASE + MEM_SIZE) + return false; + memcpy(memio.mem + (addr - MEM_BASE), src, len); + return true; + } + + tb_gdb_run_result step_instruction() override { + while (true) { + if (args.max_cycles != 0 && cycle >= args.max_cycles) { + timed_out = true; + return tb_gdb_run_result::timed_out; + } + + const bool will_retire = root->tb__DOT__cpu__DOT__core__DOT__x_instr_ret; + memio.step(tb); + tb.step(args, memio); + ++cycle; + + if (memio.exit_req) + return tb_gdb_run_result::exited; + if (args.max_cycles != 0 && cycle >= args.max_cycles) { + timed_out = true; + return tb_gdb_run_result::timed_out; + } + if (will_retire) + return tb_gdb_run_result::ok; + } + } + + uint32_t exit_code() const override { return memio.exit_code; } + + bool monitor_cmd(const std::string &cmd, std::string &out_console) override { + auto is_space = [](unsigned char c) { return std::isspace(c) != 0; }; + + // Normalise whitespace and case to make matching user-friendly. + size_t start = 0; + while (start < cmd.size() && is_space((unsigned char)cmd[start])) + ++start; + size_t end = cmd.size(); + while (end > start && is_space((unsigned char)cmd[end - 1])) + --end; + + std::string norm; + norm.reserve(end - start); + bool in_space = false; + for (size_t i = start; i < end; ++i) { + const unsigned char c = (unsigned char)cmd[i]; + if (is_space(c)) { + in_space = true; + continue; + } + if (in_space && !norm.empty()) + norm.push_back(' '); + in_space = false; + norm.push_back((char)std::tolower(c)); + } + + if (norm == "reset halt" || norm == "reset") { + // Clear external testbench state (but keep RAM contents). + memio.mtime = 0; + memio.mtimecmp[0] = 0; + memio.mtimecmp[1] = 0; + memio.exit_req = false; + memio.exit_code = 0; + memio.monitor_enabled = false; + memio.poison_addr = -4u; + memio.soft_irq_state = 0; + memio.irq_state = 0; + for (int i = 0; i < N_RESERVATIONS; ++i) { + memio.reservation_valid[i] = false; + memio.reservation_addr[i] = 0; + } + for (uint32_t i = 0; i < tb_uart_state::N_UARTS; ++i) { + memio.uart.uarts[i].overrun = false; + memio.uart.uarts[i].rx_fifo.clear(); + memio.uart.uarts[i].tx_fifo.clear(); + } + tb.set_soft_irq(0); + tb.set_timer_irq(0); + tb.set_irq(0); + + cycle = 0; + timed_out = false; + + tb.reset_dut(args, memio); + out_console = "reset halt\n"; + return true; + } + + return false; + } + }; + + verilator_gdb_target target(tb, memio, args, cycle, timed_out); + while (!memio.exit_req && !timed_out) { + tb_gdb_server gdb(args.gdb_port, target); + const tb_gdb_run_result r = gdb.serve(); + if (r == tb_gdb_run_result::error) + return 1; + } + } else { + while (args.max_cycles == 0 || cycle < args.max_cycles) { + uint32_t core_cycles_advanced = 0; + bool jtag_exit_cmd = jtag.step(tb, &memio, &cycle, &timed_out, &core_cycles_advanced); + + if (memio.exit_req) { + fprintf(tb.logfile, "CPU requested halt. Exit code %d\n", memio.exit_code); + fprintf(tb.logfile, "Ran for " I64_FMT " cycles\n", cycle); + break; + } + if (timed_out) { + fprintf(tb.logfile, "Max cycles reached\n"); + break; + } + if (jtag_exit_cmd) + break; + + // If the JTAG handler didn't advance the core clock, free-run for one cycle. + if (core_cycles_advanced == 0) { + memio.step(tb); + tb.step(args, memio); + ++cycle; + if (memio.exit_req) { + fprintf(tb.logfile, "CPU requested halt. Exit code %d\n", memio.exit_code); + fprintf(tb.logfile, "Ran for " I64_FMT " cycles\n", cycle); + break; + } + if (args.max_cycles != 0 && cycle >= args.max_cycles) { + fprintf(tb.logfile, "Max cycles reached\n"); + timed_out = true; + break; + } + } + } + } + + jtag.close(); + + for (auto r : args.dump_ranges) { + fprintf(tb.logfile, "Dumping memory from %08x to %08x:\n", r.first, r.second); + for (int i = 0; i < r.second - r.first; ++i) + fprintf(tb.logfile, "%02x%c", memio.mem[r.first + i - MEM_BASE], i % 16 == 15 ? '\n' : ' '); + fprintf(tb.logfile, "\n"); + } + + if (args.sig_path != "") { + FILE *sigfile = fopen(args.sig_path.c_str(), "wb"); + for (auto r : args.dump_ranges) { + for (uint32_t i = 0; i < r.second - r.first; i += 4) { + fprintf( + sigfile, + "%02x%02x%02x%02x\n", + memio.mem[r.first + i + 3 - MEM_BASE], + memio.mem[r.first + i + 2 - MEM_BASE], + memio.mem[r.first + i + 1 - MEM_BASE], + memio.mem[r.first + i + 0 - MEM_BASE] + ); + } + } + fclose(sigfile); + } + + if (args.propagate_return_code && timed_out) { + return -1; + } else if (args.propagate_return_code && memio.exit_req) { + return memio.exit_code; + } else { + return 0; + } +} + +// Needed on MacOS, haven't looked into why +double sc_time_stamp() { + return 0.0; +} diff --git a/vendor/lab-runtime/memops.c b/vendor/lab-runtime/memops.c new file mode 100644 index 0000000..3d71197 --- /dev/null +++ b/vendor/lab-runtime/memops.c @@ -0,0 +1,24 @@ +#include + +void *memcpy(void *dst, const void *src, size_t n) +{ + unsigned char *d = dst; + const unsigned char *s = src; + + for (size_t i = 0; i < n; ++i) { + d[i] = s[i]; + } + + return dst; +} + +void *memset(void *dst, int c, size_t n) +{ + unsigned char *d = dst; + + for (size_t i = 0; i < n; ++i) { + d[i] = (unsigned char)c; + } + + return dst; +}