// 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