verilator/test_regress/t/t_case_table_normal.v

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