feat: publish FreeRTOS C FC06 card

This commit is contained in:
2026-07-19 16:36:03 +02:00
commit 0fc6158501
195 changed files with 60591 additions and 0 deletions
+269
View File
@@ -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
+52
View File
@@ -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
+162
View File
@@ -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
+294
View File
@@ -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
+31
View File
@@ -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
+33
View File
@@ -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
@@ -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
+41
View File
@@ -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
+66
View File
@@ -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
+65
View File
@@ -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()
+200
View File
@@ -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
+41
View File
@@ -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
+39
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
file hazard3_dm.v
+904
View File
@@ -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 dont do anything else."
//
// ...not conditioned on sbreadonaddr. Likewise the sbdata0 description says:
//
// "If the bus master is busy then accesses set sbbusyerror, and dont 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
+74
View File
@@ -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
+6
View File
@@ -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
+194
View File
@@ -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
+6
View File
@@ -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
+210
View File
@@ -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
+185
View File
@@ -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
+162
View File
@@ -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
+22
View File
@@ -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 .
+259
View File
@@ -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
+59
View File
@@ -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)
+1590
View File
File diff suppressed because it is too large Load Diff
+343
View File
@@ -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
+327
View File
@@ -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
+1642
View File
File diff suppressed because it is too large Load Diff
+204
View File
@@ -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
+594
View File
@@ -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
+906
View File
@@ -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
+478
View File
@@ -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
+327
View File
@@ -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
+124
View File
@@ -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;
+380
View File
@@ -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
+173
View File
@@ -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
+84
View File
@@ -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
+377
View File
@@ -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;
+96
View File
@@ -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
+499
View File
@@ -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
+20
View File
@@ -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
+276
View File
@@ -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
+7
View File
@@ -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
+6
View File
@@ -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.
+119
View File
@@ -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()
+79
View File
@@ -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()
+43
View File
@@ -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
+13
View File
@@ -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;
+103
View File
@@ -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))
+14
View File
@@ -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("<L", len(data)))
ofile.write(data)
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env python3
# Prepend a length and CRC32 checksum to a binary file so that it can be sent
# to the DOOMSoC UART bootloader. Both values are unsigned 32-bit
# little-endian. CRC32 is calculated with standard parameters (input and
# output reflected, seed all-ones, final XOR all-ones).
import argparse
import binascii
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(struct.pack("<L", len(data)))
ofile.write(struct.pack("<L", binascii.crc32(data)))
ofile.write(data)
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env python3
from PIL import Image
import argparse
import os
import struct
import sys
FORMATS = ["argb1555", "rgab5515", "bgar5515", "rgb565", "argb1232", "ragb2132", "rgb332", "r2", "r1", "p8", "p4", "p2", "p1"]
def bytes_from_bitstream_le(bitstream):
accum = 0
accum_size = 0
while True:
while accum_size < 8:
try:
nbits, newdata = next(bitstream)
except StopIteration:
return
accum = accum | (newdata << accum_size)
accum_size += nbits
while accum_size >= 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("<L", (last_transparent & 0xffff) | ((first_transparent & 0x7fff) << 16) | ((continuous_span & 1) << 31)))
except ValueError:
# Completely transparent row
ofile.write(struct.pack("<L", 0))
ofile.close()
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
# Raspberry Pi iceprog (piceprog)
# Tool for programming iCE40 FPGAs with Pi GPIOs
import sys
import RPi.GPIO as gpio
from time import sleep
from math import ceil
fpga_sclk = 19
fpga_sdi = 26
fpga_ss = 13
fpga_done = 6
fpga_rst = 5
gpio.setmode(gpio.BCM)
gpio.setwarnings(False)
for pin in [fpga_sclk, fpga_sdi, fpga_ss, fpga_rst]:
gpio.setup(pin, gpio.OUT)
gpio.setup(fpga_done, gpio.IN)
def prog(fname):
bitstream = list(open(fname, "rb").read())
print("{} bytes.".format(len(bitstream)))
bits = []
for byte in bitstream:
for i in range(8):
bits.append(byte >> 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])
+526
View File
@@ -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))
+26
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
run 1s;
+55
View File
@@ -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)
+82
View File
@@ -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
+80
View File
@@ -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
+275
View File
@@ -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")
+49
View File
@@ -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))
+63
View File
@@ -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")
+2
View File
@@ -0,0 +1,2 @@
# Link up to project root
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../project_paths.mk
+66
View File
@@ -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
+32
View File
@@ -0,0 +1,32 @@
#ifndef _HAZARD3_INSTR_H
#define _HAZARD3_INSTR_H
#include <stdint.h>
// 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
+98
View File
@@ -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
+234
View File
@@ -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
+135
View File
@@ -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
+48
View File
@@ -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 = .);
}
+275
View File
@@ -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) }
}
+75
View File
@@ -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))
+55
View File
@@ -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 $@
+117
View File
@@ -0,0 +1,117 @@
#ifndef _TB_CXXRTL_IO_H
#define _TB_CXXRTL_IO_H
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
// ----------------------------------------------------------------------------
// 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
+34
View File
@@ -0,0 +1,34 @@
#ifndef _TB_UART_HPP
#define _TB_UART_HPP
#include <cstdint>
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<std::uint8_t>(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
+61
View File
@@ -0,0 +1,61 @@
#ifndef _TB_UART_IO_H
#define _TB_UART_IO_H
#include <stdint.h>
#include <stdbool.h>
// 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
+2
View File
@@ -0,0 +1,2 @@
# Link up to project root
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../project_paths.mk
+50
View File
@@ -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;
@@ -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;
@@ -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;
+51
View File
@@ -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;
@@ -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;
+50
View File
@@ -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;
+2
View File
@@ -0,0 +1,2 @@
file tb.v
list tb_common.f
+341
View File
@@ -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
+7
View File
@@ -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 .
+2
View File
@@ -0,0 +1,2 @@
file tb_multicore.v
list tb_common.f
+358
View File
@@ -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
+108
View File
@@ -0,0 +1,108 @@
#pragma once
#include "tb_cli.h"
#include "tb_constants.h"
#include "tb_uart.h"
#include <cstdint>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
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);
};
+57
View File
@@ -0,0 +1,57 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
struct tb_cli_args {
bool load_bin;
std::string bin_path;
bool dump_waves;
std::string waves_path;
std::vector<std::pair<uint32_t, uint32_t>> 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);
@@ -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;
+85
View File
@@ -0,0 +1,85 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <unordered_set>
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<uint32_t> 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;
};
+58
View File
@@ -0,0 +1,58 @@
#include <fstream>
#include <cstdint>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#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();
};
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include <cstdint>
#include <deque>
#include <netinet/in.h>
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<uint8_t> rx_fifo;
std::deque<uint8_t> 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);
};
+162
View File
@@ -0,0 +1,162 @@
#include "tb_cli.h"
#include "tb_constants.h"
#include <iostream>
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<uint32_t, uint32_t>(
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");
}
+679
View File
@@ -0,0 +1,679 @@
#include "tb_gdb.h"
#include "tb_constants.h"
#include <algorithm>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <string>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <unistd.h>
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_ =
"<?xml version=\"1.0\"?>\n"
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n"
"<target>\n"
" <architecture>riscv:rv32</architecture>\n"
" <feature name=\"org.gnu.gdb.riscv.cpu\">\n"
" <reg name=\"x0\" bitsize=\"32\" type=\"int\" regnum=\"0\"/>\n"
" <reg name=\"x1\" bitsize=\"32\" type=\"int\" regnum=\"1\"/>\n"
" <reg name=\"x2\" bitsize=\"32\" type=\"int\" regnum=\"2\"/>\n"
" <reg name=\"x3\" bitsize=\"32\" type=\"int\" regnum=\"3\"/>\n"
" <reg name=\"x4\" bitsize=\"32\" type=\"int\" regnum=\"4\"/>\n"
" <reg name=\"x5\" bitsize=\"32\" type=\"int\" regnum=\"5\"/>\n"
" <reg name=\"x6\" bitsize=\"32\" type=\"int\" regnum=\"6\"/>\n"
" <reg name=\"x7\" bitsize=\"32\" type=\"int\" regnum=\"7\"/>\n"
" <reg name=\"x8\" bitsize=\"32\" type=\"int\" regnum=\"8\"/>\n"
" <reg name=\"x9\" bitsize=\"32\" type=\"int\" regnum=\"9\"/>\n"
" <reg name=\"x10\" bitsize=\"32\" type=\"int\" regnum=\"10\"/>\n"
" <reg name=\"x11\" bitsize=\"32\" type=\"int\" regnum=\"11\"/>\n"
" <reg name=\"x12\" bitsize=\"32\" type=\"int\" regnum=\"12\"/>\n"
" <reg name=\"x13\" bitsize=\"32\" type=\"int\" regnum=\"13\"/>\n"
" <reg name=\"x14\" bitsize=\"32\" type=\"int\" regnum=\"14\"/>\n"
" <reg name=\"x15\" bitsize=\"32\" type=\"int\" regnum=\"15\"/>\n"
" <reg name=\"x16\" bitsize=\"32\" type=\"int\" regnum=\"16\"/>\n"
" <reg name=\"x17\" bitsize=\"32\" type=\"int\" regnum=\"17\"/>\n"
" <reg name=\"x18\" bitsize=\"32\" type=\"int\" regnum=\"18\"/>\n"
" <reg name=\"x19\" bitsize=\"32\" type=\"int\" regnum=\"19\"/>\n"
" <reg name=\"x20\" bitsize=\"32\" type=\"int\" regnum=\"20\"/>\n"
" <reg name=\"x21\" bitsize=\"32\" type=\"int\" regnum=\"21\"/>\n"
" <reg name=\"x22\" bitsize=\"32\" type=\"int\" regnum=\"22\"/>\n"
" <reg name=\"x23\" bitsize=\"32\" type=\"int\" regnum=\"23\"/>\n"
" <reg name=\"x24\" bitsize=\"32\" type=\"int\" regnum=\"24\"/>\n"
" <reg name=\"x25\" bitsize=\"32\" type=\"int\" regnum=\"25\"/>\n"
" <reg name=\"x26\" bitsize=\"32\" type=\"int\" regnum=\"26\"/>\n"
" <reg name=\"x27\" bitsize=\"32\" type=\"int\" regnum=\"27\"/>\n"
" <reg name=\"x28\" bitsize=\"32\" type=\"int\" regnum=\"28\"/>\n"
" <reg name=\"x29\" bitsize=\"32\" type=\"int\" regnum=\"29\"/>\n"
" <reg name=\"x30\" bitsize=\"32\" type=\"int\" regnum=\"30\"/>\n"
" <reg name=\"x31\" bitsize=\"32\" type=\"int\" regnum=\"31\"/>\n"
" <reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\" regnum=\"32\"/>\n"
" </feature>\n"
"</target>\n";
char mm_line[128];
snprintf(
mm_line,
sizeof(mm_line),
" <memory type=\"ram\" start=\"0x%08x\" length=\"0x%08x\"/>\n",
(unsigned)MEM_BASE,
(unsigned)MEM_SIZE
);
memory_map_xml_ =
"<?xml version=\"1.0\"?>\n"
"<!DOCTYPE memory-map PUBLIC \"+//IDN gnu.org//DTD GDB Memory Map V1.0//EN\" "
"\"http://sourceware.org/gdb/gdb-memory-map.dtd\">\n"
"<memory-map>\n" +
std::string(mm_line) +
"</memory-map>\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<size_t>(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<size_t>(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;
}
+481
View File
@@ -0,0 +1,481 @@
#include "tb_cli.h"
#include "tb_jtag.h"
#include <cstring>
#include <cerrno>
#include <stdio.h>
#include <iostream>
#include <netinet/tcp.h>
// 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();
}
}
+200
View File
@@ -0,0 +1,200 @@
#include "tb.h"
#include <fstream>
#include <iostream>
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();
}
+70
View File
@@ -0,0 +1,70 @@
#include "tb.h"
#include <stdint.h>
// 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();
}
}
+245
View File
@@ -0,0 +1,245 @@
#include "tb_uart.h"
#include "tb_cli.h"
#include "tb_constants.h"
#include <algorithm>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <fcntl.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <unistd.h>
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;
}

Some files were not shown because too many files have changed in this diff Show More