Data selector
Home Up

 

Data selector in Verilog and VHDL

Practical considerations on file formats.

VHDL has a more strict syntax than Verilog. Verilog has a "C-style" syntax and VHDL has a syntax of its own.

A simple data selector will be implemented. It has three inputs A, B, SEL and one output Q.

It is asynchronous, it has no clock.

The truth table for Q might be written:

SEL Q
0 B
1 A

The project requires two significant source files, one constraint file and one logic file

The constraint file describes how symbols map to physical IC pins. This is written in a language of its own, not VHDL and not Verilog.

A peculiarity of FPGA projects is that the constraints file is not at the root of the project, instead it "belongs" to the top logic file. This seems odd, I believe that the constraints ought to be at the root of the project's hierarchy.

That aside it is possible to implement the data selector in a schematic, VHDL or Verilog.

A simple schematic is as follows:

Data selector logic circuit

In this version if SEL is zero the lower AND gate receives a one and it outputs B. If SEL is one then the upper AND gate receives a one and it outputs A.

This simple design is subject to a race condition, if both A and B are true then switching between them will probably produce a glitch, which would typically be ignored. If the selector was used as a clock switch then a third term (A and B) could be added to eliminate this.

A UCF file could be as follows:

# User Constraint File for Data Selector on Elbert 2

# DIP Switch 8 is selector for channels A and B
# Left Push Switch (SW1) is input A
# Right Push Switch (SW2) is input B
# LED D1 is output

NET "SEL" LOC = "P70" | PULLUP;
NET "A" LOC = "P80" | PULLUP;
NET "B" LOC = "P79" | PULLUP;

NET "Q" LOC = "P55";

Note that because the switches ground on the Elbert V2 every input ends up negated, though the outputs are wired as positive (on=true).

Simulation and testing:

It is desirable to simulate a logic design to ensure it complies with expected behavior.

Tests can be written in Verilog though they are structured a little differently to source files.

The ISE generates a template file, though it isn't as complete as the one I saw in an online tutorial. This confused me a bit but fortunately the tutorial had a link to the complete file.

// Verilog test fixture created from schematic /mnt/c/users/Oliver/Documents/ISE_Projects/elbert/data_selector/src/data_selector.sch - Tue Jul 11 19:08:30 2023

`timescale 1ns / 1ps

module data_selector_data_selector_sch_tb();

// Inputs
reg A;
reg B;
reg SEL;

// Output
wire Q;

// Bidirs

// Instantiate the UUT
data_selector UUT (
.A(A),
.Q(Q),
.B(B),
.SEL(SEL)
);
// Initialize Inputs
initial begin
A = 0;
B = 0;
SEL = 0; // Wait 100 ns for global reset to finish
#100;

// Add stimulus here
//125 ns
#25; B = 1'b1;
//150 ns
#25; B = 1'b0;
//175 ns
#25; SEL = 1'b1;
//200 ns
#25; A = 1'b1;
//225 ns
#25; A = 1'b0;
//250 ns
#25; B = 1'b1;
//275 ns
#25; B = 1'b0;
//300 ns
#25; // a = 1'b0; b = 1'b0; cin = 1'b0;
end
endmodule

This looks similar to a logic definition, but logic transitions are defined as happening at specific intervals, here denoted in nanoseconds.

I do not know why the values are expressly cast as bits (1'b0 for false, 1'b1 for true) but this is how it was in the example I worked from. My test sequence isn't particularly efficient, but with three inputs the test could be brute-forced in 8 steps assuming we don't bother looking for race conditions.

This simple design is subject to a race condition, if both A and B are true then switching between them will probably produce a glitch, which would typically be ignored. If the selector was used as a clock switch then a third term (A and B) could be added to eliminate this.

This could be extended to check the outcome of each step with:

// Verilog test fixture created from schematic /mnt/c/users/Oliver/Documents/ISE_Projects/elbert/data_selector/src/data_selector.sch - Tue Jul 11 19:08:30 2023

`timescale 1ns / 1ps

module data_selector_data_selector_sch_tb();

// Inputs
reg A;
reg B;
reg SEL;
reg EXPECTED;

// Output
wire Q;

// Bidirs

// Instantiate the UUT
data_selector UUT (
.A(A),
.Q(Q),
.B(B),
.SEL(SEL)
);
// Initialize Inputs
initial begin
A = 0;
B = 0;
SEL = 0; // Wait 100 ns for global reset to finish
#100;
EXPECTED=0;
if (Q !== EXPECTED) begin
$display("Error at time=%dns", $time);
end;
B = 1'b1;
#25;
EXPECTED=1;
if (Q !== EXPECTED) begin
$display("Error at time=%dns", $time);
end;
B = 1'b0;
#25;
EXPECTED=0;
if (Q !== EXPECTED) begin
$display("Error at time=%dns", $time);
end;
SEL = 1'b1;
#25;
EXPECTED=0;
if (Q !== EXPECTED) begin
$display("Error at time=%dns", $time);
end;
A = 1'b1;
#25;
EXPECTED=1;
if (Q !== EXPECTED) begin
$display("Error at time=%dns", $time);
end;
A = 1'b0;
#25;
EXPECTED=0;
if (Q !== EXPECTED) begin
$display("Error at time=%dns", $time);
end;
B = 1'b1;
#25;
EXPECTED=0;
if (Q !== EXPECTED) begin
$display("Error at time=%dns", $time);
end;
B = 1'b0;
#25;
EXPECTED=0;
if (Q !== EXPECTED) begin
$display("Error at time=%dns", $time);
end;
end
endmodule

The strict equals operators ("===", "!==") are used in the test fixture so that we don't match on "don't care" or "undefined" or other quasi-logic states. The triple equals doesn't synthesize into logic but it allows us to test for the "Z" state.We still use the conventional operators in source files that need to synthesize to logic.

This isn't quite like the situation in Javascript where "==" means "sort of looks like" and the triple equals operators are practically mandatory to suppress type type coercion.

We can do better though using a "task". A task is similar to a procedure or macro, but it appears in sequence with the verilog code where it is invoked. This is different to modules that exist separately side by side.

Using tasks we can rewrite our code to be a list of tests. I'm borrowing heavily from https://acg.cis.upenn.edu/milom/cis371-Spring12/lab/simulation/  as it is the clearest instruction I've found so far.

`timescale 1ns / 1ps

module data_selector_data_selector_sch_tb();

//error counter
   integer errors;

// Inputs
   reg A;
   reg B;
   reg SEL;
   reg EXPECTED;

// Output
   wire Q;

// Bidirs

// Instantiate the UUT
   data_selector UUT (
      .A(A),
      .Q(Q),
      .B(B),
      .SEL(SEL)
   );
// Initialize Inputs
   initial begin
      errors=0;
      $display("The simulation is starting");
      A = 0;
      B = 0;
      SEL = 0; // Wait 100 ns for global reset to finish
   #100;
      check_selector(0,0,0,0);
      check_selector(1,0,0,0);
      check_selector(0,1,0,1);
      check_selector(1,1,0,1);
      check_selector(0,0,1,0);
      check_selector(1,0,1,1);
      check_selector(0,1,1,0);
      check_selector(1,1,1,1);
      
      if (errors == 0) begin
         $display("The simulation completed without errors");
      end
      else begin
         $display("The simulation failed with %d errors", errors);
      end
   end
   task check_selector;
      input i_a;
      input i_b;
      input i_sel;
      input exp_q;
      
      begin
         A = i_a; B = i_b; SEL = i_sel;
      #25;
         if (Q !== exp_q) begin
            $display("Error at time=%dns Q=%b, expected=%b", $time, Q, exp_q);
            errors = errors + 1;
         end
      end
   endtask

endmodule

Notes:

This is not and cannot be a generalized test framework as the task has to be rewritten if the number of inputs and outputs changes

The '$display("The simulation is starting");' line looks redundant but I found that sometimes the test just doesn't run, so it is handy to have a line that just indicates that it started.

I found that the reason the test didn't run is I ran the simulator on the source file and not the test fixture. It is an easy mistake to make.

This test could be run against the Verilog version of the selector.

A minimal Verilog data selector could be written as:

`timescale 1ns / 1ps
module data_selector(
   input A,
   input B,
   input SEL,
   output wire Q
);
assign Q = SEL ? A : B;

endmodule

This uses the infamous Ternary operator "?" which is literally a "data selector" in code form. This introduces the concept of a Verilog "wire". A wire is a symbol that holds no state, it always evaluates to the expression assigned to it. The nearest C equivalent would probably be a macro:

#define Q (SEL?A:B)

but this isn't how we go about learning instead we look at the longer version:

`timescale 1ns / 1ps
module data_selector(
   input A,
   input B,
   input SEL,
   output reg Q
);
   always @(A or B or SEL)
   begin
      if (SEL)
      Q = A;
   else
      Q = B;
   end

endmodule

This introduces some more important concepts, "reg" and an "always" block.

Unlike wires a "reg" holds its state unless overwritten, usually in an "always" block. The "@" section gives the "sensitivity" of the block meaning the block only evaluates if a logic transition occurs in something it is sensitive to. Currently any input triggers an update so this behaves just like the assign version.

An easy error to make in an "always" block is to inadvertently create a path that doesn't lead to the register being updated, causing it to hold its previous state. This mistake could be missed by the crude test shown above unless we go out of our way to code tests to look for sticky bits.

In the test we see "reg" written to outside of an "always" but instead flow is controlled by time delays expressed using the "#". These do not synthesize into logic.

This is horrible but also passes:

`timescale 1ns / 1ps

module data_selector(
   input A,
   input B,
   input SEL,
   output reg Q
);
   always @(A or B or SEL)
   begin
      if (SEL)
         if (A)
            Q=1;
         else
            Q=0;
      else
      if (B)
         Q=1;
      else
         Q=0;
   end

endmodule

Meanwhile in VHDL

----------------------------------------------------------------------------------
-- Company: 
-- Engineer: 
-- 
-- Create Date: 18:26:45 07/09/2023 
-- Design Name: 
-- Module Name: data_selector - Behavioral 
-- Project Name: 
-- Target Devices: 
-- Tool versions: 
-- Description: 
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;

-- Uncomment the following library declaration if instantiating
-- any Xilinx primitives in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity data_selector is
  Port ( A : in STD_LOGIC;
         B : in STD_LOGIC;
         SEL : in STD_LOGIC;
         Q : out STD_LOGIC);
end data_selector;

architecture Behavioral of data_selector is

begin
  My_Proc: process (A, B, SEL)
  begin
    if (SEL = '1') then
      Q <= A;
    else
      Q <= B;
    end if;
  end process;


end Behavioral;

So the entity declaration looks a lot like a module declaration, and the architecture looks like its implementation.

The word "Behavioral" appears to be just a name, but it serves as bookends around the process.

The process looks a lot like an always block.

Logic states do not equate to true and false so we must explicitly test them.

VHDL currently does not have the Ternary operator so the minimal one line implementation isn't possible.

A one line implementation of the selector is possible still:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity data_selector is
  Port ( A : in STD_LOGIC;
         B : in STD_LOGIC;
         SEL : in STD_LOGIC;
         Q : out STD_LOGIC);
end data_selector;

architecture Behavioral of data_selector is

  begin

    Q <= (( SEL AND A) OR (NOT SEL AND B));

  end Behavioral;