Hardware Description Language HDL Introduction HDL is a hardware description language used to design and document electronic systems. HDL allows designers to design at various levels of abstraction. It is the most widely used HDL with a user community of more than 50,000 active designers. Tools that can be used to learn is: 1. Xilinx ISE 10.1 (Newer versions don t have Testbench Waveform) Xilinx ISE (Integrated Synthesis Environment) is a software tool produced by Xilinx for synthesis and analysis of HDL designs, enabling the developer to synthesize ("compile") their designs, perform timing analysis, examine RTL diagrams, simulate a design's reaction to different stimuli, and configure the target device with the programmer. 2. http://www.referencedesigner.com/tutorials/verilog/verilog_01.php A very good website to learn verilog with examples and detailed explaination. It provide a free verilog compiler (icarus verilog or iverilog) and simulator (vvp) that runs under Windows using DOS commands. A graphic waveform viewer (gtkwave) is also provided so the output of the simulation can be viewed as waveforms. 3. http://www.techep.csi.cuny.edu/~zhangs/v.html This is online simulator. Simulating a design requires users to write their testbench file. Basics HDL is case sensitive. All verilog keywords are lower case (e.g. module,, initial,,...) Logic values in modelling hardware are logic-0, logic-1, high-impedance-z, don t care-x and unknown-x. The data types in verilog HDL is similar to a variable in programming, and belongs to one of the two data types, net data type and register data type. Nets represents structural connections between components and registers represent variables used to store data. A net declaration wire [3:0] sig or wire [0:3] sig will result in the variable sig to be of 4 bits wide. If no size is explicitly specified, the default size is 1 bit, e.g. wire test. The variable test is 1 bit signal. The wire net is the most commonly used net type. The different kinds of register data types that are supported for synthesis are reg and integer. Similar to a net declaration, a reg declaration explicitly specifies the size, that is, the corresponding number of bits of the variable in the hardware. For example, reg [0:3] sig or reg [3:0] sig will create a 4 bit variable, while reg test will create a 1 bit variable. Gate primitives: The gates have one scalar output and multiple scalar inputs. The first terminal in the list of gate terminals is an output and the other terminals are inputs. and N-input AND gate xor N-input EX-OR gate nand N-input NAND gate xnor N-input EX-NOR gate or N-input OR gate not N-output inverter nor N-input NOR gate
Modules are the building blocks of verilog designs. Using modules by another modules (instantiating) is called design hierarchy is a common way of creating a design. Ports allow communication between a module and its environment and can be associated by order or by name. Ports can be input, output or inout. Port connection rules: Inputs: internally must always be of type net, externally the inputs can be connected to variable reg or net type. Outputs: internally can be of type net or reg, externally the outputs can be connected to a variable net type. supports a design at many levels of abstraction. The major three are : Behavioral level (Register Tranfer Level) - a description of a circuit in terms of high-level logic and arithmetic operations and assignments. Working at this level will model in terms of what the circuit will do as opposed to using the lower-level logic gates which describe how the circuit can be implemented. Structural level - a description of a circuit at the abstraction level of logic gates. This type of model could be seen as a textual representation of a schematic. Data Flow level - a description of a circuit which can use logical expressions with continuous assignments. Example of designing using gate primitives: To create an AND gate from a NAND gate //Using structural model module and_from_nand(input X, input Y, output F); wire W; nand U1 (W, X, Y); nand U2 (F, W, W); //Testbench code module test(); reg X, Y; //the inputs are declared as reg wire F; //the output is declared as wire and_from_nand uut(.w(w),.y(y),.f(f)); //instantiate unit under test initial $monitor ( X=%b Y=%b F=%b, X, Y, F); //variables to be displayed X=0; Y=0; //initialise X and Y to 0 #10 X=1; //now X=1, Y=0 after delay of 10 time units #10 Y=1; //now X=1, Y=1 after delay of 10 time units #10 X=0; //now X=0, Y=1 after delay of 10 time units #10 $finish; // of simulation after delay of 10 time units Output produced: X=0 Y=0 F=0 X=1 Y=0 F=0 X=1 Y=1 F=1 X=0 Y=1 F=0 //The file simple.v from http://www.techep.csi.cuny.edu/~zhangs/v.html module simple (A,B,C,x,y); input A,B,C;
output x,y; wire e; and #(10) g1(e,a,b); not #(5) g2(y,c); or #(10) g3(x,e,y); //#(10) indicates the output delay of 10 delay units, g1 is the gate name //The testbench file simple_tb.v module simpletb; reg A, B, C; wire x, y; simple cwd(a, B, C, x, y); initial $display("\t\t time,\t\t A,\t B,\t C,\t x, \t y"); $monitor("%d, \t %b, \t %b, \t %b, \t %b, \t %b", $time, A, B, C, x, y); initial A=1'b0; B=1'b0; C=1'b0; #40 A=1'b1; B=1'b1; C=1'b1; #50 $finish; Compiling simple.v, simple_tb.v produces:- time, A, B, C, x, y 0, 0, 0, 0, x, x 5, 0, 0, 0, x, 1 15, 0, 0, 0, 1, 1 40, 1, 1, 1, 1, 1 45, 1, 1, 1, 1, 0 Operators: Logical operators Expressions connected by && and are evaluated from left to right. Evaluation stops as soon as the result is known. The result is a scalar value: o 0 if the relation is false o 1 if the relation is true o x if any of the operands has x (unknown) bits
! logic negation && logical and logical or Bitwise Operators Bitwise operators perform a bit wise operation on two operands. These operators take each bit in one operand and perform the operation with the corresponding bit in the other operand. If one operand is shorter than the other, it will be exted on the left side with zeroes to match the length of the longer operand. ~ negation & and inclusive or ^ exclusive or ^~ or ~^ exclusive nor (equivalence) Example 1: Implementing a combinational logic circuit:- Solution 1 Structural Level module AOI (input A, B, C, D, output F ); wire w1, w2; //internal connections and (w1, A, B); and (w2, C, D); or (F, w1, w2); Solution 2 Data Flow Level module AOI (input A, B, C, D, output F ); assign F = (A&B) (C&D); // F AB CD Simulation is process of verifying the functional characteristics of models at any level of abstraction. The hardware models are simulated using the simulators. The objective of simulating a hardware model is to test if the codes meets the functional requirements of the specification, and all the blocks in the model are functionally correct. To achieve this, there are two ways: using a testbench waveform (Xilinx ISE 10.1) or write a testbench. Example 2:- Implementing a Full Adder (Structural Model). module full_adder( input A, B, Cin, output S, Cout ); wire s1, c1, c2, c3; xor (s1, A, B); xor (s, Cin, s1); and (c1, A, B); and (c2, s1, Cin); or (Cout, c1, c2);
Example 3:- Implementing a Full Adder using Half-Adders and OR gate. //declare the half adder verilog module. module half_adder( input x, y, output s, c ); HA1 xor (s, x, y); and (c, x, y); HA2 //declare the full adder verilog module module full_adder( input a, b, cin, output sum, cout ); wire s1,c1,c2; half_adder HA1 (a, b, s1, c1); OR half_adder HA1 (.x(a),.y(b),.s(s1),.c(c1) ); half_adder HA2 (cin, s1, sum, c2); OR half_adder HA2 (.x(cin),.y(s1),.s(sum),.c(c2) ); or (cout, c1, c2); //complete the block diagram! There are three kind of constants in verilog HDL, integer, real and string. Real and string constants are not supported for synthesis. An integer constant can be written either in simple decimal or base format. When an integer is written in simple decimal form, it is interpreted as a signed number. The integer is represented in synthesis as 32 bits in 2 s complement form. If an integer is written in the base format form, then the integer is treated as an unsigned number. Some examples of integer declaration: 30 Signed number, 32 bits -2 Signed number, 32 bits in 2 s complement format 2 b10 Size of 2 bits 6 d-4 6-bit unsigned number (-4 is in 2 s complement using 6 bits) d-10 32-bit unsigned number (-10 is in 2 s complement using 32 bits) The basic value holders in hardware are wire, flip-flop and latch. The flip-flop is an edge-triggered storage element while the latch is a level-sensitive storage element. A variable in verilog HDL can either be of the net data type or the register data type. For synthesis, a variable of net type maps to a wire in hardware and a variable of the register type maps either to a wire or a storage element (flip-flop or latch) deping on the context under which the variable is assigned a value. In verilog HDL, a register variable retains its value through the entire simulation run, thus inferring memory. A continuous assignment statement represents, in hardware, logic that is derived from the expression on the right-hand side of the assignment statement driving the net that appears on the left-hand side of the assignment. The target of a continuous assignment is always a net driven by combinational logic. module Continuous (input StatIn, output StatOut) assign StatOut = ~StatIn; //continuous assignment (NOT gate) A procedural assignment statement represents, in hardware, logic that is derived from the expression on the right-hand side of the assignment statement driving the variable that appears on the left-hand side of the
assignment. Procedural assignments can appear only within an always statement. There are two kinds of procedural assignment statements: blocking and non-blocking. Blocking Statements: A blocking statement must be executed before the execution of the statements that follow it in a sequential block. module blocking (input clk, a, output c); wire clk, a; reg b, c; always @ (posedge clk ) b = a; c = b; Non-blocking Statements: Non-blocking statements allows to schedule assignments without blocking the procedural flow. The non-blocking procedural statement can be used whenever to make several register assignments within the same time step without regard to order or depence upon each other. It means that nonblocking statements resemble the actual hardware more than blocking assignments. module nonblocking (input clk,a, output c); wire clk, a; reg b, c; always @ (posedge clk ) b <= a; c <= b; The blocking and non-blocking nature of an assignment does not cause any change to the combinational logic generated from the assignment statement itself, but affects the use of the resultant value later on. A good recommation to follow is to use blocking assignments for modelling combinational logic and to use nonblocking assignments for modelling sequential logic. Always block As the name suggests, an always block executes always, unlike initial blocks which execute only once (at the ning of simulation). A second difference is that an always block should have a sensitive list or a delay associated with it. The sensitive list is the one which tells the always block when to execute the block of code, as shown below. The @ symbol after reserved word ' always', indicates that the block will be triggered "at" the condition in parenthesis after symbol @. One important note about always block: it can not drive wire data type, but can drive reg and integer data types. The conditional if-else statement The if - else statement controls the execution of other statements. In programming language like c, if - else controls the flow of program. When more than one statement needs to be executed for an if condition, then we need to use and as seen in earlier examples. Syntax : if if (condition)
statements; Syntax : if-else if (condition) statements; else statements; Syntax : nested if-else-if if (condition) statements; else if (condition) statements;...... else statements; The case statement The case statement compares an expression to a series of cases and executes the statement or statement group associated with the first matching case: case statement supports single or multiple statements. Group multiple statements using and keywords. Syntax of a case statement look as shown below. case () < case1 > : < statement > < case2 > : < statement >... default : < statement > case Some examples: always @ (a or b or sel) y = 0; if (sel == 0) y = a; else y = b; // 2:1 MUX example The above example is a 2:1 MUX, with input a and b; sel is the select input and y is the mux output. In any combinational logic, output changes whenever input changes. This theory when applied to always blocks means that the code inside always blocks needs to be executed whenever the input variables (or output controlling variables) change. These variables are the ones included in the sensitive list, namely a, b and sel. There are two types of sensitive list: level sensitive (for combinational circuits) and edge sensitive (for flip-flops). The code below is the same 2:1 MUX but the output y is now a flip-flop output. always @ (posedge clk) if (reset == 0)
y <= 0; else if (sel == 0) y <= a; else y <= b; Example of 2-1 multiplexer using case statement. module mux_case(out,cntrl,in1,in2); input cntrl,in1,in2; output out; reg out; always @ * case (cntrl) 1'b0 : out = in1; 1'b1 : out = in2; case Example 3:- Implementing a Full Adder (Behavioral Level). module full_adder_behavioral( input a, b, cin, output reg sum, cout); // output are to be declared as registers reg T1, T2, T3, S1; // as variables always @(a, b, cin) T1=a&b; //blocking assignments executed in sequence T2=b&cin; T3=cin&a; cout=t1 T2 T3; S1=a^b; sum=s1^cin; Sequential Circuits So far we have only been discussing combinational citcuits. This was good as far as our learning of language and its constructs are concerned. Practical FPGA circuits, however, almost always contains sequential circuits. Combinational circuts do not have memory and its present output is a function only of present inputs. A sequential circuit, on the other hand, has memory and its present output deps not only upon present input but also upon past input(s). A better term for past inputs is "state". A sequential circuit consists of finite states and its output deps upon present input and one of these states. Implicit in the design of the sequential circuits is a global clock and the circuit operates on the rising or falling edge of the clock.
Flip-Flops A D flip-flop is the most basic building block of sequential circuit. The basic D flip-flop is a sequential device that transfers the value of the D input to the Q output on every rising edge (or falling edge) of the clock. From the abstraction at the top level, a D flip-flop has an Clock and a Data D as input. It has one output designated as Q. For simplicity we do not assume presence of any reset signal. // D Flip Flop without Reset module d_ff (input wire Clock, input wire D, output reg Q ); always @(posedge Clock) //no need D Q = D; This D flip flop is a positive edge-triggered FF. An important thing to note is that the input signal D is not present in the sensitive list. The D signal is sampled only at the rising edge the clock signal. //Testbench for D flip flop `timescale 1ns / 1ps module stimulus; reg Clock, D; wire Q; // Instantiate the Unit Under Test (UUT) d_ff d1 (.Clock(Clock),.D(D),.Q(Q)); integer i; of initial $dumpfile("test.vcd"); $dumpvars(0, stimulus); D = 0; #8 D = 1; #10 D = 0; #10 D = 0; #10 D = 1; #10 D = 0; #10 D = 1; #40; //create VCD file for gtkwave initial //generate 10 clock pulses Clock = 0; for ( i = 0; i <= 10; i = i+1) #10 Clock = ~Clock; initial $monitor("clock=%d, D=%d, Q=%d ", Clock, D, Q); //Display output in tabular form
D-FF: (Behavioural model) One of the most useful sequential building blocks is a D flip-flop with an additional asynchronous reset pin. When the reset is not active, it operates as a basic D flip-flop. When the reset pin is active, the output is held to zero. module dff(d, clock, reset, q, qb); input d, clock, reset; output reg q, qb; always@(posedge clock) //no need reset and d case({reset,d}) 2'b00 :q=1'b0; //reset=0, d=0, q=0 2'b01 :q=1'b1; //reset=0, d=1, q=1 default: q=1'b0; //other q=0 because reset=1 case qb<=~q; T-FF: (Behavioural model) If the T input is high, the T flip-flop changes state ( toggles ) whenever the clock input is strobed. If the T input is low, the flip-flop holds the previous value. module tff(t, clock, reset, q, qb); input t, clock, reset; output reg q, qb; always@(posedge clock) //no need reset and t case({reset, t}) 2'b00 :q=q; //reset=0, t=0, q maintain its previous value (no change) 2'b01 :q=~q; //reset=0, t=1, q is toggled default: q=1'b0; //reset=1, q=0 case qb<=~q; JK-FF: (Behavioural model) The JK flip-flop augments the behavior of the SR flip-flop (J=Set, K=Reset) by interpreting the S = R = 1 condition as a flip or toggle command. Specifically, the combination J = 1, K = 0 is a command to set the flip-flop; the combination J = 0, K = 1 is a command to reset the flip-flop; and the combination J = K = 1 is a command to toggle the flip-flop. module jkff(j, k, clock, reset, q, qb); input j, k, clock, reset; output reg q, qb; always@(posedge clock) //no need reset, j and k. // But if reset does not work, may need to include reset case({reset, j, k}) 3'b100 :q=q; //reset=1, j=0, k=0, q hold its previous value 3'b101 :q=0; //reset=1, j=0, k=1, q=0 3'b110 :q=1; //reset=1, j=1, k=0, q=1 3'b111 :q=~q; //reset=1, j=1, k=1, q toggles default :q=0; //reset=0, q=0 case qb<=~q; //assign qb
Counters Circuit diagram of 4-bit asynchronous counting up counter using JK flip flops. Code for 4-bit Asynchronous up counter using JK-FF (Structural model): //uses the JK FF module above module ripple_count(j, k, clock, reset, q, qb); input j, k, clock, reset; output wire [3:0]q, qb; jkff JK1(j, k, clock, reset, q[0], qb[0]); jkff JK2(j, k, q[0], reset, q[1], qb[1]); jkff JK3(j, k, q[1], reset, q[2], qb[2]); jkff JK4(j, k, q[2], reset, q[3], qb[3]); //The testbench for the 4-bit asynchronous up counter using JK FF module ripple4tb; reg j,k,clock,reset; // Inputs wire [3:0] q,qb; // Outputs // Instantiate the Unit Under Test (UUT) ripple_count uut (.j(j),.k(k),.clock(clock),.reset(reset),.q(q),.qb(qb)); //Create clock for the counter. initial clock = 0; always #10 clock = ~clock; initial $dumpfile("test1.vcd"); $dumpvars(0, ripple4tb); //for the gtkwave waveform viewer // Initialize Inputs j=0; k=0; reset=0; //counter output=0000
//Apply inputs #40; reset=1; j=1; k=1; //counter will count up #700; reset=0; j=1;k=1; //counter reset to 0000 #800 reset=1; j=1;k=1; //counter will count up #1200 $finish; initial //for the DOS tabular display $monitor("t=%3d, j=%d, k=%d, reset=%d, q=%4b", $time, j, k, reset, q); Circuit diagram of a 4-bit Synchronous up counter using T-FF module tff(t,clock,reset,q,qb); input t,clock,reset; output reg q,qb; always@(posedge clock) //No need reset and t. But if reset does not work, need to include reset case({reset,t}) 2'b00 :q=q; 2'b01 :q=~q; default: q=0; case qb<=~q; module sync_up(t,clock,reset,q,qb); input t,clock, reset; output [3:0]q,qb; wire x1, x2;
tff T0(t, clock, reset, q[0], qb[0]); tff T1(q[0], clock, reset, q[1], qb[1]); and A1(x1, q[0], q[1]); tff T2(x1, clock, reset, q[2], qb[2]); and A2(x2, q[2], x1); tff T3(x2, clock, reset, q[3], qb[3]);