392 lines
13 KiB
Systemverilog
392 lines
13 KiB
Systemverilog
// DESCRIPTION: Verilator: Verilog Test module
|
|
//
|
|
// This file ONLY is placed under the Creative Commons Public Domain.
|
|
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
|
// SPDX-License-Identifier: CC0-1.0
|
|
//
|
|
// Case statements that become a "tiny" lookup table, followed by cases that must
|
|
// not be converted to one. Each output is compared against an equivalent reference
|
|
// computed without a case statement, so the reference itself is never tabled.
|
|
|
|
// verilog_format: off
|
|
`define stop $stop
|
|
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
|
|
`define checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
|
|
`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
|
|
// verilog_format: on
|
|
|
|
module t;
|
|
logic clk = 1'b0;
|
|
always #5 clk = ~clk;
|
|
|
|
logic [31:0] cyc = 0;
|
|
|
|
// Accept A: single output, blocking assignment, all selector values covered.
|
|
wire [2:0] accept_a_in = cyc[2:0];
|
|
logic [3:0] accept_a_out, accept_a_ref;
|
|
always_comb
|
|
case (accept_a_in)
|
|
3'd0: accept_a_out = 4'd3;
|
|
3'd1: accept_a_out = 4'd4;
|
|
3'd2: accept_a_out = 4'd5;
|
|
3'd3: accept_a_out = 4'd6;
|
|
3'd4: accept_a_out = 4'd7;
|
|
3'd5: accept_a_out = 4'd8;
|
|
3'd6: accept_a_out = 4'd9;
|
|
3'd7: accept_a_out = 4'd10;
|
|
endcase
|
|
assign accept_a_ref = 4'd3 + {1'b0, accept_a_in};
|
|
|
|
// Accept B: single output, non-blocking assignment, with a default value set before
|
|
// the case and not all selector values covered.
|
|
logic [3:0] accept_b_out, accept_b_ref;
|
|
// verilator lint_off CASEINCOMPLETE
|
|
always_ff @(posedge clk) begin
|
|
accept_b_out <= 4'hf;
|
|
case (cyc[1:0])
|
|
2'b00: accept_b_out <= 4'h1;
|
|
2'b01: accept_b_out <= 4'h2;
|
|
endcase
|
|
end
|
|
// verilator lint_on CASEINCOMPLETE
|
|
always_ff @(posedge clk)
|
|
accept_b_ref <= (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'hf;
|
|
|
|
// Accept C: two outputs of different widths, blocking assignment, with a default branch.
|
|
logic [2:0] accept_c_out_0, accept_c_ref_0;
|
|
logic [3:0] accept_c_out_1, accept_c_ref_1;
|
|
always_comb
|
|
case (cyc[1:0])
|
|
2'b00: begin
|
|
accept_c_out_0 = 3'd1;
|
|
accept_c_out_1 = 4'd6;
|
|
end
|
|
2'b01: begin
|
|
accept_c_out_0 = 3'd2;
|
|
accept_c_out_1 = 4'd5;
|
|
end
|
|
default: begin
|
|
accept_c_out_0 = 3'd0;
|
|
accept_c_out_1 = 4'd7;
|
|
end
|
|
endcase
|
|
assign accept_c_ref_0 = (cyc[1:0] == 2'b00) ? 3'd1 : (cyc[1:0] == 2'b01) ? 3'd2 : 3'd0;
|
|
assign accept_c_ref_1 = (cyc[1:0] == 2'b00) ? 4'd6 : (cyc[1:0] == 2'b01) ? 4'd5 : 4'd7;
|
|
|
|
// Accept D: two outputs, non-blocking assignment, empty default branch, with default
|
|
// values set before the case.
|
|
logic [2:0] accept_d_out_0, accept_d_ref_0;
|
|
logic [2:0] accept_d_out_1, accept_d_ref_1;
|
|
always_ff @(posedge clk) begin
|
|
accept_d_out_0 <= 3'd0;
|
|
accept_d_out_1 <= 3'd7;
|
|
case (cyc[1:0])
|
|
2'b00: begin
|
|
accept_d_out_0 <= 3'd1;
|
|
accept_d_out_1 <= 3'd6;
|
|
end
|
|
2'b01: begin
|
|
accept_d_out_0 <= 3'd2;
|
|
accept_d_out_1 <= 3'd5;
|
|
end
|
|
default: begin
|
|
end
|
|
endcase
|
|
end
|
|
always_ff @(posedge clk) begin
|
|
accept_d_ref_0 <= (cyc[1:0] == 2'b00) ? 3'd1 : (cyc[1:0] == 2'b01) ? 3'd2 : 3'd0;
|
|
accept_d_ref_1 <= (cyc[1:0] == 2'b00) ? 3'd6 : (cyc[1:0] == 2'b01) ? 3'd5 : 3'd7;
|
|
end
|
|
|
|
// Accept E: casez with a don't-care bit.
|
|
logic [3:0] accept_e_out, accept_e_ref;
|
|
always_comb
|
|
casez (cyc[1:0])
|
|
2'b1?: accept_e_out = 4'ha;
|
|
2'b0?: accept_e_out = 4'hb;
|
|
endcase
|
|
assign accept_e_ref = cyc[1] ? 4'ha : 4'hb;
|
|
|
|
// Accept F: an item that can never match, and an item listing multiple values.
|
|
logic [3:0] accept_f_out, accept_f_ref;
|
|
// verilator lint_off CASEWITHX
|
|
always_comb
|
|
casez (cyc[1:0])
|
|
2'bx0: accept_f_out = 4'h0; // X can never match in 2-state
|
|
2'b01, 2'b11: accept_f_out = 4'h5; // lists two values
|
|
default: accept_f_out = 4'h9;
|
|
endcase
|
|
// verilator lint_on CASEWITHX
|
|
assign accept_f_ref = (cyc[1:0] == 2'b01 || cyc[1:0] == 2'b11) ? 4'h5 : 4'h9;
|
|
|
|
// Accept G: items assign different subsets of two outputs, with default values (and an
|
|
// unrelated output) set before the case.
|
|
logic [3:0] accept_g_out_0, accept_g_ref_0;
|
|
logic [3:0] accept_g_out_1, accept_g_ref_1;
|
|
logic [3:0] accept_g_out_2, accept_g_ref_2;
|
|
// verilator lint_off CASEINCOMPLETE
|
|
always_comb begin
|
|
accept_g_out_0 = 4'h0;
|
|
accept_g_out_1 = 4'hf;
|
|
accept_g_out_2 = 4'h3; // not assigned in the case
|
|
case (cyc[1:0])
|
|
2'b00: accept_g_out_0 = 4'h1;
|
|
2'b01: accept_g_out_1 = 4'h2;
|
|
endcase
|
|
end
|
|
// verilator lint_on CASEINCOMPLETE
|
|
assign accept_g_ref_0 = (cyc[1:0] == 2'b00) ? 4'h1 : 4'h0;
|
|
assign accept_g_ref_1 = (cyc[1:0] == 2'b01) ? 4'h2 : 4'hf;
|
|
assign accept_g_ref_2 = 4'h3;
|
|
|
|
// Accept H: single output, non-blocking assignment, all selector values covered.
|
|
logic [3:0] accept_h_out, accept_h_ref;
|
|
always_ff @(posedge clk)
|
|
case (cyc[1:0])
|
|
2'b00: accept_h_out <= 4'h1;
|
|
2'b01: accept_h_out <= 4'h2;
|
|
2'b10: accept_h_out <= 4'h4;
|
|
2'b11: accept_h_out <= 4'h8;
|
|
endcase
|
|
always_ff @(posedge clk) accept_h_ref <= 4'h1 << cyc[1:0];
|
|
|
|
// Accept I: unique0 enum case; the selector may hold an out-of-range value.
|
|
typedef enum logic [1:0] {
|
|
E0,
|
|
E1,
|
|
E2
|
|
} e_t;
|
|
e_t accept_i_in;
|
|
assign accept_i_in = e_t'(cyc[1:0]);
|
|
logic [3:0] accept_i_out, accept_i_ref;
|
|
always_comb begin
|
|
accept_i_out = 4'hf;
|
|
unique0 case (accept_i_in)
|
|
E0: accept_i_out = 4'h1;
|
|
E1: accept_i_out = 4'h2;
|
|
E2: accept_i_out = 4'h3;
|
|
endcase
|
|
end
|
|
assign accept_i_ref = (cyc[1:0] == 2'd0) ? 4'h1
|
|
: (cyc[1:0] == 2'd1) ? 4'h2
|
|
: (cyc[1:0] == 2'd2) ? 4'h3 : 4'hf;
|
|
|
|
// Accept J: wide output, materialized as a normal (not tiny) lookup table.
|
|
logic [8:0] accept_j_out, accept_j_ref;
|
|
always_comb
|
|
case (cyc[3:0])
|
|
4'd0: accept_j_out = 9'h001;
|
|
4'd1: accept_j_out = 9'h002;
|
|
4'd2: accept_j_out = 9'h004;
|
|
4'd3: accept_j_out = 9'h008;
|
|
default: accept_j_out = 9'h010;
|
|
endcase
|
|
assign accept_j_ref = (cyc[3:0] < 4'd4) ? (9'h1 << cyc[3:0]) : 9'h010;
|
|
|
|
// Accept K: a non-constant assignment precedes the case.
|
|
logic [3:0] accept_k_out_0, accept_k_ref_0;
|
|
logic [3:0] accept_k_out_1, accept_k_ref_1;
|
|
always_comb begin
|
|
accept_k_out_1 = cyc[3:0] ^ 4'ha; // non-constant value
|
|
case (cyc[1:0])
|
|
2'b00: accept_k_out_0 = 4'h1;
|
|
2'b01: accept_k_out_0 = 4'h2;
|
|
2'b10: accept_k_out_0 = 4'h4;
|
|
2'b11: accept_k_out_0 = 4'h8;
|
|
endcase
|
|
end
|
|
assign accept_k_ref_0 = 4'h1 << cyc[1:0];
|
|
assign accept_k_ref_1 = cyc[3:0] ^ 4'ha;
|
|
|
|
// Accept L: the same output is given a default value twice before the case.
|
|
logic [3:0] accept_l_out, accept_l_ref;
|
|
// verilator lint_off CASEINCOMPLETE
|
|
always_comb begin
|
|
accept_l_out = 4'h1;
|
|
accept_l_out = 4'h6; // assigned a second time before the case
|
|
case (cyc[1:0])
|
|
2'b00: accept_l_out = 4'h2;
|
|
2'b01: accept_l_out = 4'h3;
|
|
endcase
|
|
end
|
|
// verilator lint_on CASEINCOMPLETE
|
|
assign accept_l_ref = (cyc[1:0] == 2'd0) ? 4'h2 : (cyc[1:0] == 2'd1) ? 4'h3 : 4'h6;
|
|
|
|
// The cases below are intentionally NOT converted to a lookup table.
|
|
|
|
// Reject A: an item whose body is not a simple assignment.
|
|
logic [3:0] reject_a_out, reject_a_ref;
|
|
always_comb begin
|
|
reject_a_out = 4'h0;
|
|
case (cyc[1:0])
|
|
2'b00: reject_a_out = 4'h1;
|
|
2'b01: if (cyc[0]) reject_a_out = 4'h2; // not a simple assignment
|
|
default: reject_a_out = 4'h3;
|
|
endcase
|
|
end
|
|
assign reject_a_ref = (cyc[1:0] == 2'd0) ? 4'h1 : (cyc[1:0] == 2'd1) ? 4'h2 : 4'h3;
|
|
|
|
// Reject B: an item assigns through a variable bit-select (the index is read).
|
|
logic [3:0] reject_b_out, reject_b_ref;
|
|
always_comb begin
|
|
reject_b_out = 4'h0;
|
|
case (cyc[1:0])
|
|
2'b00: reject_b_out[cyc[1:0]] = 1'b1;
|
|
default: reject_b_out = 4'h5;
|
|
endcase
|
|
end
|
|
assign reject_b_ref = (cyc[1:0] == 2'd0) ? 4'h1 : 4'h5;
|
|
|
|
// Reject C: an item assigns the same output twice.
|
|
logic [3:0] reject_c_out, reject_c_ref;
|
|
always_comb begin
|
|
reject_c_out = 4'h0;
|
|
case (cyc[1:0])
|
|
2'b00: begin
|
|
reject_c_out = 4'h1;
|
|
reject_c_out = 4'h2;
|
|
end
|
|
default: reject_c_out = 4'h3;
|
|
endcase
|
|
end
|
|
assign reject_c_ref = (cyc[1:0] == 2'd0) ? 4'h2 : 4'h3;
|
|
|
|
// Reject D: a non-constant case-item value.
|
|
logic [1:0] reject_d_in;
|
|
assign reject_d_in = cyc[1:0];
|
|
logic [3:0] reject_d_out, reject_d_ref;
|
|
always_comb begin
|
|
reject_d_out = 4'h0;
|
|
case (cyc[1:0])
|
|
reject_d_in: reject_d_out = 4'h7; // non-constant item value
|
|
default: reject_d_out = 4'h9;
|
|
endcase
|
|
end
|
|
assign reject_d_ref = 4'h7; // reject_d_in always equals the case expression
|
|
|
|
// Reject E: all items are empty.
|
|
logic [3:0] reject_e_out, reject_e_ref;
|
|
always_comb begin
|
|
reject_e_out = 4'h7;
|
|
case (cyc[2:0])
|
|
3'd0: ;
|
|
3'd1: ;
|
|
3'd2: ;
|
|
3'd3: ;
|
|
3'd4: ;
|
|
3'd5: ;
|
|
3'd6: ;
|
|
3'd7: ;
|
|
endcase
|
|
end
|
|
assign reject_e_ref = 4'h7;
|
|
|
|
// Reject F: an item uses a delayed (intra-assignment) assignment.
|
|
logic [3:0] reject_f_out, reject_f_ref;
|
|
always_ff @(posedge clk)
|
|
case (cyc[1:0])
|
|
2'b00: reject_f_out <= #1 4'h1; // delayed assignment
|
|
default: reject_f_out <= 4'h2;
|
|
endcase
|
|
always_ff @(posedge clk)
|
|
if (cyc[1:0] == 2'b00) reject_f_ref <= #1 4'h1;
|
|
else reject_f_ref <= 4'h2;
|
|
|
|
// Reject G: an output assigned with both blocking and non-blocking assignments. The three
|
|
// variants exercise the distinct ways the assignment kinds conflict. The deliberate
|
|
// mixing warnings are waived.
|
|
// verilator lint_off BLKANDNBLK
|
|
// verilator lint_off COMBDLY
|
|
// verilator lint_off CASEINCOMPLETE
|
|
// Variant 0: an item mixes a blocking and a non-blocking assignment to the same output.
|
|
logic [3:0] reject_g_out_0, reject_g_ref_0;
|
|
always_comb
|
|
case (cyc[1:0])
|
|
2'b00: reject_g_out_0 = 4'h1;
|
|
2'b01: reject_g_out_0 <= 4'h2;
|
|
default: reject_g_out_0 = 4'h3;
|
|
endcase
|
|
assign reject_g_ref_0 = (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'h3;
|
|
// Variant 1: blocking items, but the pre-case default is a non-blocking assignment.
|
|
logic [3:0] reject_g_out_1, reject_g_ref_1;
|
|
always_comb begin
|
|
reject_g_out_1 <= 4'h0;
|
|
case (cyc[1:0])
|
|
2'b00: reject_g_out_1 = 4'h1;
|
|
2'b01: reject_g_out_1 = 4'h2;
|
|
endcase
|
|
end
|
|
assign reject_g_ref_1 = (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'h0;
|
|
// Variant 2: non-blocking items, but the pre-case default is a blocking assignment.
|
|
logic [3:0] reject_g_out_2, reject_g_ref_2;
|
|
always_comb begin
|
|
reject_g_out_2 = 4'h0;
|
|
case (cyc[1:0])
|
|
2'b00: reject_g_out_2 <= 4'h1;
|
|
2'b01: reject_g_out_2 <= 4'h2;
|
|
endcase
|
|
end
|
|
assign reject_g_ref_2 = (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'h0;
|
|
// verilator lint_on CASEINCOMPLETE
|
|
// verilator lint_on COMBDLY
|
|
// verilator lint_on BLKANDNBLK
|
|
|
|
// Reject H: items assign a real (non-packed) output.
|
|
real reject_h_out, reject_h_ref;
|
|
always_comb
|
|
case (cyc[1:0])
|
|
2'b00: reject_h_out = 1.5;
|
|
2'b01: reject_h_out = 2.5;
|
|
default: reject_h_out = 9.0;
|
|
endcase
|
|
always_comb reject_h_ref = (cyc[1:0] == 2'b00) ? 1.5 : (cyc[1:0] == 2'b01) ? 2.5 : 9.0;
|
|
|
|
// Reject I: items assign a string (non-packed) output.
|
|
string reject_i_out, reject_i_ref;
|
|
always_comb
|
|
case (cyc[1:0])
|
|
2'b00: reject_i_out = "zero";
|
|
2'b01: reject_i_out = "one";
|
|
default: reject_i_out = "other";
|
|
endcase
|
|
always_comb reject_i_ref = (cyc[1:0] == 2'b00) ? "zero" : (cyc[1:0] == 2'b01) ? "one" : "other";
|
|
|
|
// Test driver/checker
|
|
always @(posedge clk) begin
|
|
`checkh(accept_a_out, accept_a_ref);
|
|
`checkh(accept_b_out, accept_b_ref);
|
|
`checkh(accept_c_out_0, accept_c_ref_0);
|
|
`checkh(accept_c_out_1, accept_c_ref_1);
|
|
`checkh(accept_d_out_0, accept_d_ref_0);
|
|
`checkh(accept_d_out_1, accept_d_ref_1);
|
|
`checkh(accept_e_out, accept_e_ref);
|
|
`checkh(accept_f_out, accept_f_ref);
|
|
`checkh(accept_g_out_0, accept_g_ref_0);
|
|
`checkh(accept_g_out_1, accept_g_ref_1);
|
|
`checkh(accept_g_out_2, accept_g_ref_2);
|
|
`checkh(accept_h_out, accept_h_ref);
|
|
`checkh(accept_i_out, accept_i_ref);
|
|
`checkh(accept_j_out, accept_j_ref);
|
|
`checkh(accept_k_out_0, accept_k_ref_0);
|
|
`checkh(accept_k_out_1, accept_k_ref_1);
|
|
`checkh(accept_l_out, accept_l_ref);
|
|
`checkh(reject_a_out, reject_a_ref);
|
|
`checkh(reject_b_out, reject_b_ref);
|
|
`checkh(reject_c_out, reject_c_ref);
|
|
`checkh(reject_d_out, reject_d_ref);
|
|
`checkh(reject_e_out, reject_e_ref);
|
|
`checkh(reject_f_out, reject_f_ref);
|
|
`checkh(reject_g_out_0, reject_g_ref_0);
|
|
`checkh(reject_g_out_1, reject_g_ref_1);
|
|
`checkh(reject_g_out_2, reject_g_ref_2);
|
|
`checkr(reject_h_out, reject_h_ref);
|
|
`checks(reject_i_out, reject_i_ref);
|
|
|
|
cyc <= cyc + 32'd1;
|
|
if (cyc == 32'd32) begin
|
|
$write("*-* All Finished *-*\n");
|
|
$finish;
|
|
end
|
|
end
|
|
endmodule
|