316 lines
12 KiB
Systemverilog
316 lines
12 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 "normal" (constant-pool) 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. Selectors are wide enough, with enough distinct values, that the
|
|
// branch lowering they replace is deep enough to make a table worthwhile.
|
|
|
|
// 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);
|
|
// 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.
|
|
logic [15:0] accept_a_out, accept_a_ref;
|
|
always_comb
|
|
case (cyc[3:0])
|
|
4'd0: accept_a_out = 16'h1111;
|
|
4'd1: accept_a_out = 16'h2222;
|
|
4'd2: accept_a_out = 16'h4444;
|
|
4'd3: accept_a_out = 16'h8888;
|
|
default: accept_a_out = 16'h0f0f;
|
|
endcase
|
|
assign accept_a_ref = (cyc[3:0] == 4'd0) ? 16'h1111
|
|
: (cyc[3:0] == 4'd1) ? 16'h2222
|
|
: (cyc[3:0] == 4'd2) ? 16'h4444
|
|
: (cyc[3:0] == 4'd3) ? 16'h8888 : 16'h0f0f;
|
|
|
|
// Accept B: single output, non-blocking assignment, with a default value set before
|
|
// the case and not all selector values covered.
|
|
logic [15:0] accept_b_out, accept_b_ref;
|
|
// verilator lint_off CASEINCOMPLETE
|
|
always_ff @(posedge clk) begin
|
|
accept_b_out <= 16'hffff;
|
|
case (cyc[3:0])
|
|
4'd0: accept_b_out <= 16'h0001;
|
|
4'd1: accept_b_out <= 16'h0002;
|
|
4'd2: accept_b_out <= 16'h0004;
|
|
4'd3: accept_b_out <= 16'h0008;
|
|
endcase
|
|
end
|
|
// verilator lint_on CASEINCOMPLETE
|
|
always_ff @(posedge clk)
|
|
accept_b_ref <= (cyc[3:0] == 4'd0) ? 16'h0001
|
|
: (cyc[3:0] == 4'd1) ? 16'h0002
|
|
: (cyc[3:0] == 4'd2) ? 16'h0004
|
|
: (cyc[3:0] == 4'd3) ? 16'h0008 : 16'hffff;
|
|
|
|
// Accept C: three outputs, blocking assignment, with a default branch.
|
|
logic [11:0] accept_c_out_0, accept_c_ref_0;
|
|
logic [11:0] accept_c_out_1, accept_c_ref_1;
|
|
logic [11:0] accept_c_out_2, accept_c_ref_2;
|
|
always_comb
|
|
case (cyc[3:0])
|
|
4'd0: begin
|
|
accept_c_out_0 = 12'h001;
|
|
accept_c_out_1 = 12'h010;
|
|
accept_c_out_2 = 12'h100;
|
|
end
|
|
4'd1: begin
|
|
accept_c_out_0 = 12'h002;
|
|
accept_c_out_1 = 12'h020;
|
|
accept_c_out_2 = 12'h200;
|
|
end
|
|
4'd2: begin
|
|
accept_c_out_0 = 12'h004;
|
|
accept_c_out_1 = 12'h040;
|
|
accept_c_out_2 = 12'h400;
|
|
end
|
|
4'd3: begin
|
|
accept_c_out_0 = 12'h008;
|
|
accept_c_out_1 = 12'h080;
|
|
accept_c_out_2 = 12'h800;
|
|
end
|
|
default: begin
|
|
accept_c_out_0 = 12'h000;
|
|
accept_c_out_1 = 12'h0ff;
|
|
accept_c_out_2 = 12'hfff;
|
|
end
|
|
endcase
|
|
assign accept_c_ref_0 = (cyc[3:0] == 4'd0) ? 12'h001 : (cyc[3:0] == 4'd1) ? 12'h002
|
|
: (cyc[3:0] == 4'd2) ? 12'h004 : (cyc[3:0] == 4'd3) ? 12'h008 : 12'h000;
|
|
assign accept_c_ref_1 = (cyc[3:0] == 4'd0) ? 12'h010 : (cyc[3:0] == 4'd1) ? 12'h020
|
|
: (cyc[3:0] == 4'd2) ? 12'h040 : (cyc[3:0] == 4'd3) ? 12'h080 : 12'h0ff;
|
|
assign accept_c_ref_2 = (cyc[3:0] == 4'd0) ? 12'h100 : (cyc[3:0] == 4'd1) ? 12'h200
|
|
: (cyc[3:0] == 4'd2) ? 12'h400 : (cyc[3:0] == 4'd3) ? 12'h800 : 12'hfff;
|
|
|
|
// Accept D: two outputs, non-blocking assignment, empty default branch, with default
|
|
// values set before the case.
|
|
logic [15:0] accept_d_out_0, accept_d_ref_0;
|
|
logic [15:0] accept_d_out_1, accept_d_ref_1;
|
|
always_ff @(posedge clk) begin
|
|
accept_d_out_0 <= 16'h0000;
|
|
accept_d_out_1 <= 16'hffff;
|
|
case (cyc[3:0])
|
|
4'd0: begin
|
|
accept_d_out_0 <= 16'h0001;
|
|
accept_d_out_1 <= 16'h0010;
|
|
end
|
|
4'd1: begin
|
|
accept_d_out_0 <= 16'h0002;
|
|
accept_d_out_1 <= 16'h0020;
|
|
end
|
|
4'd2: begin
|
|
accept_d_out_0 <= 16'h0004;
|
|
accept_d_out_1 <= 16'h0040;
|
|
end
|
|
4'd3: begin
|
|
accept_d_out_0 <= 16'h0008;
|
|
accept_d_out_1 <= 16'h0080;
|
|
end
|
|
default: begin
|
|
end
|
|
endcase
|
|
end
|
|
always_ff @(posedge clk) begin
|
|
accept_d_ref_0 <= (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd1) ? 16'h0002
|
|
: (cyc[3:0] == 4'd2) ? 16'h0004 : (cyc[3:0] == 4'd3) ? 16'h0008 : 16'h0000;
|
|
accept_d_ref_1 <= (cyc[3:0] == 4'd0) ? 16'h0010 : (cyc[3:0] == 4'd1) ? 16'h0020
|
|
: (cyc[3:0] == 4'd2) ? 16'h0040 : (cyc[3:0] == 4'd3) ? 16'h0080 : 16'hffff;
|
|
end
|
|
|
|
// Accept E: casez with don't-care bits.
|
|
logic [15:0] accept_e_out, accept_e_ref;
|
|
always_comb
|
|
casez (cyc[3:0])
|
|
4'b00??: accept_e_out = 16'haaaa;
|
|
4'b01??: accept_e_out = 16'hbbbb;
|
|
4'b10??: accept_e_out = 16'hcccc;
|
|
4'b11??: accept_e_out = 16'hdddd;
|
|
endcase
|
|
assign accept_e_ref = (cyc[3:2] == 2'd0) ? 16'haaaa : (cyc[3:2] == 2'd1) ? 16'hbbbb
|
|
: (cyc[3:2] == 2'd2) ? 16'hcccc : 16'hdddd;
|
|
|
|
// Accept F: an item that can never match, and an item listing multiple values.
|
|
logic [15:0] accept_f_out, accept_f_ref;
|
|
// verilator lint_off CASEWITHX
|
|
always_comb
|
|
casez (cyc[3:0])
|
|
4'bxxx0: accept_f_out = 16'h0000; // X can never match in 2-state
|
|
4'b0001, 4'b0011, 4'b0101: accept_f_out = 16'h5555; // lists three values
|
|
default: accept_f_out = 16'h9999;
|
|
endcase
|
|
// verilator lint_on CASEWITHX
|
|
assign accept_f_ref = (cyc[3:0] == 4'd1 || cyc[3:0] == 4'd3 || cyc[3:0] == 4'd5)
|
|
? 16'h5555 : 16'h9999;
|
|
|
|
// Accept G: items assign different subsets of two outputs, with default values (and an
|
|
// unrelated output) set before the case.
|
|
logic [15:0] accept_g_out_0, accept_g_ref_0;
|
|
logic [15:0] accept_g_out_1, accept_g_ref_1;
|
|
logic [15:0] accept_g_out_2, accept_g_ref_2;
|
|
always_comb begin
|
|
accept_g_out_0 = 16'h0000;
|
|
accept_g_out_1 = 16'hffff;
|
|
accept_g_out_2 = 16'h3333; // not assigned in the case
|
|
case (cyc[3:0])
|
|
4'd0: accept_g_out_0 = 16'h0001;
|
|
4'd1: accept_g_out_1 = 16'h0002;
|
|
4'd2: begin
|
|
accept_g_out_0 = 16'h0004;
|
|
accept_g_out_1 = 16'h0008;
|
|
end
|
|
4'd3: accept_g_out_0 = 16'h0010;
|
|
default: ;
|
|
endcase
|
|
end
|
|
assign accept_g_ref_0 = (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd2) ? 16'h0004
|
|
: (cyc[3:0] == 4'd3) ? 16'h0010 : 16'h0000;
|
|
assign accept_g_ref_1 = (cyc[3:0] == 4'd1) ? 16'h0002 : (cyc[3:0] == 4'd2) ? 16'h0008 : 16'hffff;
|
|
assign accept_g_ref_2 = 16'h3333;
|
|
|
|
// Accept H: unique0 enum case; the selector may hold an out-of-range value.
|
|
typedef enum logic [3:0] {
|
|
NE0,
|
|
NE1,
|
|
NE2,
|
|
NE3,
|
|
NE4
|
|
} ne_t;
|
|
ne_t accept_h_in;
|
|
assign accept_h_in = ne_t'(cyc[3:0]);
|
|
logic [15:0] accept_h_out, accept_h_ref;
|
|
always_comb begin
|
|
accept_h_out = 16'hffff;
|
|
unique0 case (accept_h_in)
|
|
NE0: accept_h_out = 16'h0001;
|
|
NE1: accept_h_out = 16'h0002;
|
|
NE2: accept_h_out = 16'h0003;
|
|
NE3: accept_h_out = 16'h0004;
|
|
NE4: accept_h_out = 16'h0005;
|
|
endcase
|
|
end
|
|
assign accept_h_ref = (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd1) ? 16'h0002
|
|
: (cyc[3:0] == 4'd2) ? 16'h0003 : (cyc[3:0] == 4'd3) ? 16'h0004
|
|
: (cyc[3:0] == 4'd4) ? 16'h0005 : 16'hffff;
|
|
|
|
// The cases below are intentionally NOT converted to a lookup table.
|
|
|
|
// Reject A: too few distinct values, so the branch lowering is cheaper than a load.
|
|
logic [15:0] reject_a_out, reject_a_ref;
|
|
always_comb
|
|
case (cyc[3:0])
|
|
4'd0: reject_a_out = 16'h0001;
|
|
4'd1: reject_a_out = 16'h0002;
|
|
default: reject_a_out = 16'h00ff;
|
|
endcase
|
|
assign reject_a_ref = (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd1) ? 16'h0002 : 16'h00ff;
|
|
|
|
// Reject B: a one-bit selector, too shallow to be worth a load.
|
|
logic [19:0] reject_b_out, reject_b_ref;
|
|
always_comb
|
|
case (cyc[0])
|
|
1'b0: reject_b_out = 20'h00001;
|
|
1'b1: reject_b_out = 20'h00002;
|
|
default: reject_b_out = 20'h00000;
|
|
endcase
|
|
assign reject_b_ref = cyc[0] ? 20'h00002 : 20'h00001;
|
|
|
|
// Reject C: a 12-bit selector, too wide to table.
|
|
logic [15:0] reject_c_out, reject_c_ref;
|
|
always_comb
|
|
case (cyc[11:0])
|
|
12'd0: reject_c_out = 16'h0001;
|
|
12'd1: reject_c_out = 16'h0002;
|
|
12'd2: reject_c_out = 16'h0004;
|
|
default: reject_c_out = 16'h0000;
|
|
endcase
|
|
assign reject_c_ref = (cyc[11:0] == 12'd0) ? 16'h0001
|
|
: (cyc[11:0] == 12'd1) ? 16'h0002
|
|
: (cyc[11:0] == 12'd2) ? 16'h0004 : 16'h0000;
|
|
|
|
// Reject D: a 17-bit selector, too wide to table.
|
|
logic [16:0] reject_d_in;
|
|
assign reject_d_in = cyc[16:0];
|
|
logic [15:0] reject_d_out, reject_d_ref;
|
|
// verilator lint_off CASEINCOMPLETE
|
|
always_comb begin
|
|
reject_d_out = 16'hbeef;
|
|
case (reject_d_in)
|
|
17'd0: reject_d_out = 16'h0001;
|
|
17'd1: reject_d_out = 16'h0002;
|
|
17'd2: reject_d_out = 16'h0004;
|
|
endcase
|
|
end
|
|
// verilator lint_on CASEINCOMPLETE
|
|
assign reject_d_ref = (reject_d_in == 17'd0) ? 16'h0001
|
|
: (reject_d_in == 17'd1) ? 16'h0002
|
|
: (reject_d_in == 17'd2) ? 16'h0004 : 16'hbeef;
|
|
|
|
// Reject E: a whole output and a sub-range of it assigned in different items.
|
|
logic [7:0] reject_e_out, reject_e_ref;
|
|
always_comb begin
|
|
reject_e_out = 8'h00;
|
|
reject_e_out[3:0] = 4'h0;
|
|
case (cyc[1:0])
|
|
2'b00: reject_e_out = 8'haa; // assigns the whole output
|
|
2'b01: reject_e_out[3:0] = 4'h5; // assigns a sub-range of the same output
|
|
default: ;
|
|
endcase
|
|
end
|
|
assign reject_e_ref = (cyc[1:0] == 2'd0) ? 8'haa : (cyc[1:0] == 2'd1) ? 8'h05 : 8'h00;
|
|
|
|
// Reject F: a sub-range's default value is overwritten by a later whole-output default
|
|
// before the case, so the sub-range's pre-case value is set elsewhere.
|
|
logic [31:0] reject_f_out, reject_f_ref;
|
|
always_comb begin
|
|
reject_f_out[15:0] = 16'h0005; // farther default for the sub-range
|
|
reject_f_out = 32'h0; // closer whole-output default overwrites the sub-range to 0
|
|
case (cyc[1:0])
|
|
2'b00: reject_f_out[15:0] = 16'habcd; // only the sub-range is assigned in the case
|
|
default: ;
|
|
endcase
|
|
end
|
|
assign reject_f_ref = (cyc[1:0] == 2'd0) ? 32'h0000abcd : 32'h00000000;
|
|
|
|
// 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_c_out_2, accept_c_ref_2);
|
|
`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(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);
|
|
|
|
cyc <= cyc + 32'd1;
|
|
if (cyc == 32'd32) begin
|
|
$write("*-* All Finished *-*\n");
|
|
$finish;
|
|
end
|
|
end
|
|
endmodule
|