Use Verilog to Describe a Combinational Circuit: the “If” and “Case” Statements - LEKULE

Breaking

3 Feb 2019

Use Verilog to Describe a Combinational Circuit: the “If” and “Case” Statements

Learn more about Verilog! This article discusses how to use "If" and "Case" statements for circuit description.

This article explains the use of Verilog “If” and “Case” statements to describe a combinational circuit. We’ll also take a look at the Verilog “Casex” and “Casez” statements and briefly discuss the potential pitfalls of using these two statements.

“If” Statement

In a previous article describing combinational circuits in Verilog, we discussed that the Verilog conditional operator can be used to check a condition when making an assignment with the “assign” keyword. Inside an “always” block, we can use the Verilog “if” statement to implement a similar functionality. The simplified syntax for an “If” statement is given below:

                    1 if (Expression) 
2  begin 
3  Procedural_statements_1;
4  end;
5 else
6  begin 
7  Procedural_statement_2;
8  end;
                  

The "Expression" is evaluated, if it’s true, the expressions within the first “begin” and “end” are executed. If it’s not true, the procedural statements corresponding to the “else” keyword are executed. When there is only one expression within a branch, the “begin” and “end” keywords can be removed. A more complex functionality can be described by using nested “if” statements. The following example shows the use of a nested structure to describe a priority encoder.

Example 1:

Use the Verilog “if” statement to describe a 4-to-2 priority encoder with the truth table below:



The following code is a Verilog description for this priority encoder:

                    1 module Prio_4_to_2(
2     input wire [3:0] x,
3     output reg [1:0] y,
4     output reg v
5     );
  
  
6   always @*
7   if (x[3] == 1'b1)
8    y = 2'b11;
9   else if (x[2] == 1'b1)
10    y = 2'b10;
11   else if (x[1] == 1'b1)
12    y = 2'b01;
13   else
14    y = 2'b00;
   
15   always @*
16   if (x[3] | x[2] | x[1] | x[0])
17    v = 1'b1;
18   else
19    v = 1'b0;
  
20 endmodule
                  

Line 6 introduces a useful Verilog notation. When describing a combinational circuit using an “always” block, we should list all of the inputs in the sensitivity list. Instead of listing all those inputs, we can simply use @* as used in Line 6 above. This informs the synthesis tool that all of the inputs are in the sensitivity list.

Lines 7 to 14 correspond to the nested “if” statement that describes the “y” output of the truth table. The input with the highest priority (x[3]) is checked first. If it’s logic high, the condition is evaluated as true and the output is set to 11. If it’s not true, the expression within the first “else” which is another “if” statement is executed. This second “if” statement examines x[2]. If it’s true, the output is set to 10 otherwise the expression within the next “else” statement is evaluated. As you can see, there’s another “if” statement within the “else” branch of Line 11.

Lines 15 to 19 use an “if” statement to describe the “v” output as given in the truth table. The condition checked within this “if” statement is defined using the Verilog bitwise OR operator. 
An ISE simulation of the above code is shown in Figure 1.

Figure 1

“Case” Statement

The simplified syntax of a “case” statement is given below:

                    1 case (control_expression)
2  option_1:
3   begin 
4   Procedural_statement_1;
5   end
6  option_2:
7   begin
8   Procedural_statement_2;
9   end
10         ...
11  option_n:
12   begin
13   Procedural_statement_n;
14   end
15  default:
16   begin
17   Procedural_statement_d;
18   end
19 endcase
                  

A “case” statement compares the “control_expression” with the values denoted by “option_1”, “option_2”, …, “option_n”. When a match is found, the corresponding procedural statements are executed. If there’s no match, the default statement is executed. When there is only one statement within a branch, we can remove the “begin” and “end” keywords.

Example 2:

Use the “case” statement to describe a one-bit 4-to-1 multiplexer. The inputs to be selected are “a”, “b”, “c”, and “d”. A two-bit signal, “sel”, is used to choose the desired input and assign it to “out1”.
The code for this example is as:

                    1 module Mux4_to_1(
2     input wire a,
3     input wire b,
4     input wire c,
5     input wire d,
6     input wire [1:0] sel,
7     output reg out1
8     );
  
9   always @*
10   case (sel)
11    2'b00:
12     out1 = a;
13    2'b01:
14     out1 = b;
15    2'b10:
16     out1 = c;
17    default:
18     out1 = d;
19   endcase

20 endmodule
                  

When “sel”=00, the output is equal to “a”. For “sel”=01, “out1” follows “b” and so on. Figure 2 shows an ISE simulation of the above code.


Figure 2

​ When the procedural statements under several branches of a “case” statement are the same, we can merge them in a single branch and make the code more compact and readable. For example, consider the truth table of a 4-to-2 priority encoder.



For x[3]=1, we don’t care about the value of the other three input bits (x[2], x[1], x[0]). Hence, there are eight different values that lead to “y”=11, “v”=1. These eight different values separated by a comma can be listed as a single branch of the “case” statement. This is shown in the following example which is the Verilog code for the above 4-to-2 priority encoder:

                    1 module Prio_4_to_2(
2     input wire [3:0] x,
3     output reg [1:0] y,
4     output reg v
5     );
  
  
6   always @*
7   case (x)
8    4’b1000, 4’b1001, 4’b1010, 4’b1011, 
9    4’b1100, 4’b1101, 4’b1110, 4b1111:
10     y = 2’b11;

11    4’b0100, 4’b0101, 4’b0110, 4b0111:
12     y= 2’b10;

13    4’b0010, 4b0011:
14     y= 2’b01;

15    default:
16     y=2’b00;
17   endcase   


18   always @*
19   if (x[3] | x[2] | x[1] | x[0])
20    v = 1'b1;
21   else
22    v = 1'b0;
  
23 endmodule
                  

Verilog has two other versions for the “case” statement: “casex” and “casez”. These can be used to specify don’t-care values when making comparisons to choose a branch. The following section gives details and examples. We’ll see that using “casex” and “casez” can make the description of certain structures, such as priority encoders, more compact.

The “Casex” Statement

The “casex” statement treats z, x, and the ? character as a don’t-care. For example, we can use the “casex” statement to simplify lines 6 to 17 of the previous example as:

                    6   always @*
7   casex (x)
8    4’b1???:
9     y = 2’b11;

10    4’b01??:
11     y= 2’b10;

12    4’b001?:
13     y= 2’b01;

14    default:
15     y=2’b00;
16   endcase
                  

Line 8 replaces 4’b1000, 4’b1001, 4’b1010 … of the previous code with 4’b1???. Here, only the MSB is important and the remaining three bits are don’t-cares. Hence, we can use the more compact and readable notation 4’b1??? instead of explicitly mentioning all the possible values.
It’s important to note that the “casex” statement can mask out the bit locations that contain z or x values from either side of the comparison. The following example clarifies this point:

                    1 always @(addr)
2 begin
3  casex(addr)
4   3’b101:
5    out=2’b10;
6   3’b111:
7    out=2’b01;
8   3’b0?1:
9    out=2’b00;
10   default:
11    out=2’b00;
12  endcase
13 end
                  

When “addr” is 001 or 011, “out” should be 00. Now, assume that “addr” is x11. What branch will be selected by the “casex” statement? You may say that none of 101, 111, or 0?1 match x11 so the default branch should be chosen and "out" should be 00. However, as mentioned above, the bit locations that contain z or x values will be masked no matter they are in the branch expression or in the expression within the parentheses after the “casex” statement. Therefore, the comparisons will ignore the MSB (because addr=x11) and the other two bits will determine the case branch. Hence, out=2’b01 (this corresponds to the first branch that leads to a match when ignoring the MSB). Figure 3 below shows an ISE simulation of this example.


Figure 3

We have to be very careful if our code has “casex” statements because an unknown input can choose a branch erroneously and we may fail to recognize the problem. As discussed in the next section, we can use a “casez” statement as a partial solution to this problem.

The “Casez” Statement

The “casez” statement treats only z, ? character as a don’t-care (z and ? are equivalent). Hence, replacing the “casex” of previous example with a “casez” can solve the problem discussed above. In this case, an unknown input (x) cannot cause an erroneous branch selection. However, we’ll have to be careful with inputs that may become high-impedance. With a “Casez” statement, the bit locations that contain z values will be masked no matter they are in the branch expression or in the expression within the parentheses after the “casez” statement. Hence, a floating or a tri-state input can still cause problems. Despite this potential pitfall, we sometimes prefer to use the “casez” statement because it’s a concise method to describe certain structures such as priority encoders, interrupt handlers and address decoders.




This article examined the use of the Verilog “If” and “Case” statements for describing a combinational circuit. We saw that the Verilog “Casex” and “Casez” statements can simplify the code when there are don’t-care values in our truth table. It’s usually recommended to use a “Casez” rather than a “Casex” statement. However, we still have to be careful about the errors that can originate from a high-impedance input.

No comments: