View Course Path

Verilog Code for Demultiplexer Using Behavioral Modeling

What is a demultiplexer?

A demultiplexer is a circuit that places the value of a single data input onto multiple data outputs. The demultiplexer circuit can also be implemented using a decoder circuit.

Here we are going to work with 1-to-4 demultiplexer. A 1-to-4 demultiplexer consists of

  • one input data line,
  • four outputs, and
  • two control lines to make selections.

The below diagram shows the circuit of the 1-to-4 demultiplexer. Here a1 and a0 are control or select lines y0, y1, y2, y3 are outputs, and Din is the data line.

1:4 demultiplexer

Now, let’s observe its truth table –

truth table of demultiplexer

The values of a1a0 determine which of the outputs are set to the value of Din. When Din=0, all the outputs are set to 0, including the one selected by the valuation of a1a0. When Din=1, the valuation of a1a0 sets the appropriate output (anyone from y0, y1, y2, y3) to 1.

Now that we have thoroughly understood the concepts of the demultiplexer, let’s dive directly into the Verilog code for the demultiplexer.

Different methods used in behavioral modeling of a demultiplexer

There are various styles of writing Verilog code in behavioral modeling for this circuit.

  1. case statements
  2. assignment statements
  3. if-else statements

Here we will be elaborating on the first two. Along the way, we would also emphasize some common design errors.

Verilog code for demultiplexer – Using case statements

The basic building block in Verilog HDL is a module, analogous to the ‘function’ in C. The module declaration is made as follows:

module Demultiplexer_1_to_4_case (output reg [3:0] Y, input [1:0] A, input din);

For starters, module is a keyword. It is followed by an identifier. Identifier=name of the module. After naming the module, in a pair of parentheses, we specify:

  • the direction of a port as input, output or inout.
  • Port size, and
  • port name.

Taking into consideration the first line of the code, Demultiplexer_1_to_4_case is the identifier, the input is called port direction. If a port has multiple bits, then it is known as a vector. Hence, [1:0] states that the port named as  A is a vector with MSB = 1 and LSB = 0.

The reg data object holds its value from one procedural assignment statement to the next and means it holds its value over simulation data cycles.

Another style of declaration in the port list is to declare the port size and port direction after the module declaration.

module Demultiplexer_1_to_4_case (Y, A, din);
output reg [3:0] Y;
input [1:0] A;
input din;

[3:0] here signifies that the output is of 4 bits. Next up, since its behavioral modeling style, here comes the always statement.

always @(Y, A) begin  

Using the always statement, a procedural statement in Verilog, we will run the program sequentially. (Y, A) is known as the sensitivity list or the trigger list. The sensitivity list includes all input signals used by the always block. It controls when the statements in the always block are to be evaluated. @ is a part of the syntax, used before the sensitivity list. In Verilog, begin embarks and  end concludes any block which contains more than one statement in it.

Note that the always statement always @(Y, A) could be written as always @ *. * would mean that the code itself has to decide on the input signals of the sensitivity list.

Then inside always block we write,

case (A)

The case statement in Verilog is analogous to switch-case in C language. First, let us see the general format to write a case statement in Verilog.

case (<expression>) 
    case_item1 :   <single statement>
    case_item2 :   <single statement>
    case_item3 :   begin
                    <multiple statements>
                   end
    default    :   <statement>
endcase

If the expression corresponds to any of the case_item, then those design statements are executed. Otherwise, the default case is executed. So, now we can write

case (A)
2'b00 : begin 
         Y[0] = din; Y[3:1] = 0;
        end
2'b01 : begin 
         Y[1] = din; Y[0]   = 0; 
        end
2'b10 : begin 
         Y[2] = din; Y[1:0] = 0; 
        end
2'b11 : begin 
         Y[3] = din; Y[2:0] = 0;
        end
endcase

As we see here in the first case, 2'b00 represents the case when the input A is 2'b00. These cases indicate that, according to the value of A, one of the four statements is selected. The colon then marks the end of a case item and starts the action that must happen in that particular case. The terms begin and end are part of the Verilog syntax if you are writing more than one statement in that block. After this, those statements are mentioned, such as the output port Y[0] should be attached to the din, Y[3:0] to 0, and so on, according to the truth table.

This modeling is based on the behavior of the circuit; hence it is called behavioral modeling. Observe that we are not specifying the structure of the circuit, we are only creating the logic of the circuit which can implement that hardware.

Here is the full code:

module Demultiplexer_1_to_4_case (output reg [3:0] Y, input [1:0] A, input din);
always @(Y, A) begin
    case (A)
        2'b00 : begin Y[0] = din; Y[3:1] = 0; end
        2'b01 : begin Y[1] = din; Y[0] = 0;   end
        2'b10 : begin Y[2] = din; Y[1:0] = 0; end
        2'b11 : begin Y[3] = din; Y[2:0] = 0; end
    endcase   
end
endmodule

Test bench for the demultiplexer

The test bench is the file through which we give inputs and observe the outputs. It is a setup to test our Verilog code.

The following line includes the pre-written file Demultiplexer_1_to_4_case.v into the testbench. We start by writing 'include which is a keyword to include a file. It is followed by the file name in inverted commas.

`include "Demultiplexer_1_to_4_case.v"

The next line declares the name of the module for testbench according to the syntax as mentioned above. But we do not specify any ports in this module as there will be ports inside the testbench and not outside. Here we declare the data types of the arguments used in the instantiation of the demultiplexer design. A continuous assignment statement assigns values to the wire datatype and makes a connection to an actual wire in the circuit.

module Demultiplexer_1_to_4_case_tb; 
wire [3:0] Y;
reg [1:0] A;
reg din;

Notice that the inputs in the demux here become the reg datatypes and the outputs are specified as wire.

Now, we instantiate a module in Verilog.  Instantiation is a very exciting concept and you will be using it frequently in all the examples in this Verilog course.

Demultiplexer_1_to_4_case Instant0 (Y, A, din);                // syntax for instantiation.

As in the include file Demultiplexer_1_to_4_case.v  we have a module named Demultiplexer_1_to_4_case which contains our circuit design. Now to test it, we should use the circuit, apply a few inputs, and check the outputs, right? For this purpose, we create an instance.

What happens in Verilog when you create an instance of the circuit?

What actually happens is that the whole circuitry/design gets copied in that testbench or that module, in which it was instantiated. There is nothing like calling of a function (which happens in other programming languages, like C programming) because the code here we are writing is for hardware.

Hardware can not be something where instruction is passed. It has to be there physically, so a copy of that circuit is created in the module where it was instantiated. Now, we write:

initial begin
    din = 1;
    A = 2'b00;
#1  A = 2'b01;
#1  A = 2'b10;
#1  A = 2'b11;
end

The inputs to the Verilog model are given test values in the initial block. Like din is given 1 value, A is first given 2'b00, 2 is the number of bits,'(called as a tick), b for binary, and the two bits to carry information. #1 gives a delay of one unit of time in between the test cases. Next is,

initial begin
    $monitor("%t| Din = %d| A[1] = %d| A[0] = %d| Y[0] = %d| Y[1] = %d| Y[2] = %d| Y[3] = %d",
              $time, din, A[1], A[0], Y[0], Y[1], Y[2], Y[3]);
end

To view our results in the console, we have the ‘monitor’ keyword. This is housed in an initial block. Note that %t is the format specifier for time, %b is for binary, %d for decimal. The port names after inverted commas are given the same order as required while assigning values. That is all required to build a testbench.

Here is the complete testbench for Verilog code using case statements.

include "Demultiplexer_1_to_4_case.v"
module Demultiplexer_1_to_4_case_tb;
wire [3:0] Y;
reg [1:0] A;
reg din;
Demultiplexer_1_to_4_case I0 (Y, A, din);
initial begin
    din = 1;
    A = 2'b00;
#1  A = 2'b01;
#1  A = 2'b10;
#1  A = 2'b11;
end
initial begin
    $monitor("%t| Din = %d| A[1] = %d| A[0] = %d| Y[0] = %d| Y[1] = %d| Y[2] = %d| Y[3] = %d",
              $time, din, A[1], A[0], Y[0], Y[1], Y[2], Y[3]);
end
endmodule

Verilog code for demultiplexer – Using assignment statement

First of all, we initiate by module and port declaration following the same syntax. We assign identifier as Demultiplexer_1_to_4_assign, input as A, din and output as Y.

module Demultiplexer_1_to_4_assign(output [3:0] Y, input [1:0] A, input din);

We also set up the size and type of the port, which can only be either input, outputs, or inout.

Then we assign the output as the logical and operation of the select lines and data line. assign is a keyword in which the expression or the signal on the right-hand side is evaluated and assigned to the expression on the left side.

assign Y[0] = din & (~A[0]) & (~A[1]);
assign Y[1] = din & (~A[1]) & A[0];
assign Y[2] = din & A[1] & (~A[0]);
assign Y[3] = din & A[1] & A[0];

A[0] means that we are addressing the zeroth bit of the multi-bit bus, similar goes for Y[1], we are accessing the first bit of Y vector. & stands for and operation, ~ is for not operation.

We give all the possible conditions as per our truth table of the demultiplexer.

This is also behavioral modeling as we are not identifying the circuitry, we are only assigning the outputs to bitwise and of data and select lines.

endmodule

This marks the end of the module. You may view the complete code here.

module Demultiplexer_1_to_4_assign(output [3:0] Y, input [1:0] A, input din);
assign Y[0] = din & (~A[0]) & (~A[1]);
assign Y[1] = din & (~A[1]) & A[0];
assign Y[2] = din & A[1] & (~A[0]);
assign Y[3] = din & A[1] & A[0];
endmodule

Test bench for the demultiplexer

The first line is:

 `include "Demultiplexer_1_to_4_assign.v"

It includes the Verilog file for the design. Notice that the file name has to be in inverted commas and no semicolon at the end.

Then write:

module Demultiplexer_1_to_4_assign_tb;

This assigns an identifier for the testbench and ends in a semicolon. Again, like previous testbench, no ports for the test bench.

wire [3:0] Y;
reg [1:0] A;
reg din;

Using keywords such as wire, reg, etc. we define the data type of the ports.

Demultiplexer_1_to_4_assign Instant1 (Y, A, din);

The preceding line instantiates the module Demultiplexer_1_to_4_assign as Instant1, which is an identifier and, the ports are given in the precise order. Then comes the initial block:

initial begin
    din = 1;
    A[1] = 0; A[0] = 0;
 #1 A[1] = 0; A[0] = 1;
 #1 A[1] = 1; A[0] = 0;
 #1 A[1] = 1; A[0] = 1;
end

The initial block contains the test inputs for the design. In the test cases here, we provide din = 1 with all the combinations of A with delays.

Here, this initial block is used to observe the ports and, more importantly, the output ports. $time is the value of the time unit for %t format specifier. Other than that, the syntax remains the same.

initial begin
    $monitor("%t| Din = %d| A[1] = %d| A[0] = %d| Y[0] = %d| Y[1] = %d| Y[2] = %d| Y[3] = %d",
              $time, din, A[1], A[0], Y[0], Y[1], Y[2], Y[3]);
end

monitor is a keyword in Verilog, and it displays the contents in the parenthesis on the console. Look at the whole code here.

`include "Demultiplexer_1_to_4_assign.v" 
module Demultiplexer_1_to_4_assign_tb; 
wire [3:0] Y; 
reg [1:0] A;
reg din; 
Demultiplexer_1_to_4_assign I0 (Y, A, din); 
initial begin 
din = 1;
A[1] = 0; A[0] = 0; 
 #1 A[1] = 0; A[0] = 1; 
 #1 A[1] = 1; A[0] = 0;
 #1 A[1] = 1; A[0] = 1; 
end 
initial begin
 $monitor("%t| Din = %d| A[1] = %d| A[0] = %d| Y[0] = %d| Y[1] = %d| Y[2] = %d| Y[3] = %d", 
 $time, din, A[1], A[0], Y[0], Y[1], Y[2], Y[3]); 
end endmodule

Hardware schematic for the demultiplexer

Here is the Hardware schematic which you may develop using Xilinx for demultiplexer.

hardware schematic of demultiplexer

 

Simulation log for the demultiplexer

Simulation log relating to our truth table.

output simulation waveforms of demultiplexerWe can observe that din is always 1; all combinations of A are made, the output can be verified easily. For example- A[0] = 0, A[1] = 0, see that the waveform of Y[0] is high.

Thanks for reading! If you have any queries, let us know in the comments section below!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.