Author: Shibangshu Sen
Introduction
When digital circuits were originally implemented, they were designed using hand-drawn schematics using pencil and paper, then manufactured. If any error was present such as a misplaced or wrongly connected gate, it would make the circuit faulty and require a new design to be implemented and manufactured after fixing the error. So circuits need to be verified after design in order to eradicate such errors. In the past it was done by humans. With time, circuits grew exponentially as stated by Moore’s Law shown in Figure 1.
“The number of transistors in an integrated circuit doubles every two years.”
Figure 1: Moore’s Law is empirical in nature and is derived from historical progression and expected trends, not through any known theory or law. [Hannah Ritchie, Max Roser - Our World in Data]
It became harder to draw circuits schematically as it was extremely time-consuming. It was also near impossible to verify circuits at such a massive scale using our human mind. So, we needed a specialized language that could describe such large circuits and verify them. This is where hardware description languages, or HDLs come in handy. HDLs basically describe the structure and behavior of such circuits and can be used to verify such circuits. The HDL creates a description of the circuit using RTL (Register Transfer Level) code, which is then synthesized into netlists using synthesis tools. There are four major HDLs for analog circuit design:
VHDL
Verilog
SpectreHDL
HDL-A
For digital circuit design, various different HDLs are currently in use with the most popular being Verilog and SystemVerilog as shown in Figure 2.
Figure 2: The percentage of design projects using a specific HDL. Note how the share of SystemVerilog is increasing at the expense of Verilog and VHDL3. [Mentor Verilog]
Difference Between HDL and Programming Language
Although it may appear at first glance that HDLs and programming languages are the same, they have quite a few differences in their objectives.
HDLs have a concept of time which is not present in most other programming languages. It contains a keyword: delay, with which we can delay the execution of the next statement by a certain period of time. The attribute of time helps in realistic simulation of hardware that will be used in real-world. It is possible to create a buffer or an artificial clock by judicious use of loop statements but that is not inherently present in the language. Moreover, one can specify the units of time in HDL - ns (nanoseconds), ms (milliseconds), s (seconds) using the timescale keyword. This is however extremely difficult to reproduce in programming languages.
Programming languages have procedural assignment in nature. HDLs can have continuous assignments in the case of net data type variable. Any change in the input results in an immediate change in the process stack which results in the output changing. This is only possible due to the attribute of time in HDL. There is no concept of time in usual programming languages, so the execution is static. In order to check with the new value of variable, the code must be executed again.
HDLs are used to describe hardware – electronics and digital circuitry; thus, they contain the specifications of hardware which are executed with respect to time whereas programming languages are used to build programs for software.
In programming languages, the user always aims to optimize the code in order to ensure time complexity is minimum (space complexity matters little in modern computers). This is not the case in HDL where the user has to be attentive and careful while creating the RTL code. Optimizing the code can result in different output after execution: post-synthesis and pre-synthesis.
Programming languages are sequential in nature, i.e., the code executes from top to bottom in the main function sequentially. However, HDLs can be non-sequential in nature due to RTL logic. In Verilog, <= stands for non-blocking assignment which essentially means variables having this operator under the same block have the value assigned at the same time if no delay is given. For example, a = 0 means initially a contains the value of 0. Now, a = 1 and b = a result in 1 being assigned to a and then 1 being assigned to b. The assignment of b occurs after a, so b contains the updated value of a which is 1. However, a <= 1 and b <= a result in 1 being assigned to a, then 0 being assigned to b. The assignment of a and b occur simultaneously so b does not contain 1 since for b, a still has value 0. Both assignments are done concurrently. This is usually used in latch mechanisms and in pipelining.
Latch Mechanism can be demonstrated using a code for a JK flip flop. JK flip flop is a type of sequential digital circuit - a circuit whose present output depends on the present input as well as the previous output. Thus, it contains memory and thus requires a clock for determining time. In absence of time, there won’t be a concept of memory as there won’t be any differentiation of past and present for the circuit. The description of such a circuit cannot be achieved in usual programming language.
Figure 3: Truth Table of a JK Flip Flop triggering at the positive edge of clock. X basically means ‘don’t care.’ It is used in situations where, regardless of the input of the variable, the output remains the same. In Verilog, it is used for variables whose value isn’t known yet.
Code for JK Flip Flop:
module jff(j,k,clk,q,qb);
input j,k,clk;
output reg q,qb;
wire[1:0] s = {j,k};
always@(posedge clk)begin
case(s)
2’b01:
begin
q<=0;
qb<=1;
end
2’b10:
begin
q<=1;
qb<=0;
end
2’b11:
begin
q<=qb;
qb<=q;
end
default:
begin
q<=q;
qb<=qb;
end
endcase
end
endmodule
As we can see from the code simulating a JK f lip flop, <= basically allows for simultaneous assignment of q <= qb and qb <= q. Assuming q = 1 and qb = 0, if we had used = then q would have become 0 and qb would remain 0. Due to the <= operator, both the statements get executed simultaneously, resulting in q obtaining the value of qb and vice versa.
Register Transfer Level
The modules present in digital system can be understood through registers and the operations performed on them (shift, load, etc.).
RTL (Register Transfer Level) is a form of abstraction that is involved in the creation of digital circuits. Abstraction basically hides all the underlying unnecessary details about an object from users, allowing them to implement complex logic efficiently without worrying about the internal details of the method. It is also present in real life. Let’s say you want to watch some show on television. For that, you would need to switch on the television and transition to your desired channel using the remote control. However, you don’t need to know about the internal processes going on in the television, the design of the printed circuit boards inside the remote, the twisted nematic field effect in case of LCD TVs or Maxwell’s equations to operate it. It makes the user interface friendly. In the case of schematics, such a high level of abstraction is not present.
.
Figure 4: Abstraction chart showing an increase in level of abstraction as one ascends.
RTL is used in hardware description languages like Verilog and VHDL in order to create high level circuit representation. It was originally used to design processors whose circuits had registers as building blocks. RTL involves registers, which act just like variables in programming languages by storing information, the combinational logic operation performed on them, and a clock in order to control the change of information or state in the registers if a sequential circuit is to be determined. It models a circuit in terms of flow of digital signals between hardware and the logical operations performed on them. Using synthesis tools, an RTL hardware description can be converted to a gate level netlist (a list of electrical components in a circuit in the form of logic gates and the nodes they are connected to). This is the greatest advantage of HDL over schematics.
Designing a 4 bit up counter using Verilog
In order to show the usefulness of HDLs, we would need to design a test circuit. We will be writing the code of a 4 bit up counter below and simulating it using HDL as an example. Usually the main benefit of HDL is large scale synthesis and simulation of complex circuits that can be broken down into multiple subcircuits and instantiated. Here, we have considered a 4 bit up counter for brevity and simplicity’s sake.
Figure 5: 4 Bit Up Counter.
Code for 4 bit Up Counter:
module tff(q,clk,rst);
output reg q;
input clk,rst;
always@(posedge rst or negedge clk) begin
if(rst)
q<=1’b0;
else
q<=~q;
end
endmodule
module ripple_counter(q,clk,rst);
output [3:0] q;
input clk,rst;
tff t0(q[0],clk,rst);
tff t1(q[1],q[0],rst);
tff t2(q[2],q[1],rst);
tff t3(q[3],q[2],rst);
endmodule
As we can see, one can basically design the circuit without worrying about the underlying details – the discrete components, the logic gates required, and the external factors. A simple knowledge of flip-flops is sufficient for designing the circuit. We will be delving deeper into what each term in the design means:
module is used to declare any function present in the design as well as the main method. Just like functions in programming languages, it too must contain parameters and can be instantiated. Modules must be ended using endmodule.
The always block executes whenever the condition is satisfied. If there is no condition, the execution continues indefinitely. posedge (short form of positive edge) is an edge triggered clock, i.e., if the variable changes from low (0) to high (1), the output becomes 1. It occurs during the change in variable. negedge is the opposite of posedge (negative edge triggered clock).
reg means register, i.e., anything that stores a fixed value that is not continuously changing. It is not to be equated with the registers in the CPU, as it has nothing to do with it.
wire, unlike register, allows for continuous assignment of input, i.e., if an input to the variable at any moment changes, the output assigned to the datatype wire should change at that moment. It is hence dependent on time.
output and input are used to determine which variables act as input to the function and which variables are to be returned by the function.
Simulating a 4 bit up counter using Verilog
We have finished the designing part of our circuit; however, we have yet to do the verification part. This is where HDLs reign supreme as they offer a much easier way to verify circuits than the human mind. Verification is done through simulation of the circuit.
First, we need to write the testbench. A testbench is basically another module that is separate from our design module, which is used to test it. It is given a set of statements that apply input to our design, using which we can verify whether our circuit is working as intended or not. One must use a wide variety of test cases in order to verify whether the code is working as intended or not. For the 4 bit up counter, here is our testbench:
module test();
reg clk,rst;
wire [3:0] q;
ripple_counter rcc(.q(q),.clk(clk),.rst(rst));
initial
begin
clk = 1’b0;
forever
#5 clk = ~clk;
end
initial begin
$dumpfile("dumpvars.vcd");
$dumpvars(0,test);
rst = 1’b1;
#10 rst = 1’b0;
#200;
rst = 1’b1;
#10 rst = 1’b0;
#50;
$finish;
end
endmodule
Some more discussion of the code is necessary.
ripple_counter rcc(.q(q),.clk(clk),.rst(rst)) is basically used to call the main design module or the device under test. The variables have been instantiated.
initial unlike always is executed only once. There is a misconception that initial occurs at the beginning of the execution. Both initial and always occur simultaneously at the same time as HDL is concurrent in nature, the only difference is in the number of executions.
#x means a delay of x units of time where the units are set by timescale. For example, suppose a = 2. Now if, a <= 5 and b <= a result in 2 getting assigned to b due to concurrent execution of 2nd and 3rd line as non-blocking assignment is used. However, let's say we use a #delay keyword before b. a <=5 #1 b <=a. Now b gets assigned 5 as line 2 and 4 were not executed simultaneously. The #1 ensured that line 4 only gets executed after a delay of 1 unit.
$finish is used to end the execution of the program. As we have used forever, $finish is necessary or else the program will continue to execute infinitely.
dumpvars stores all the variables if we want to display them in EPWave form for verification. We can also use $monitor for verification but it is not ideal for counter verification. monitor is basically the equivalent of a print statement in the software programming language, except it shows the output whenever one of its parameter changes.
In the above testbench, we essentially change the input variables after certain intervals of time in order to verify our design.
clk is initialized to 1 (1’b1 means 1 bit in binary having digit 1) and then alternated with a delay of 2 units in an infinite loop to give us a pseudo timing circuit or a clock.
rst is used to reset the entire circuit.
Figure 6: EP Wave Output.
We can see using EPWave how the circuit behaves at a particular instant of time. At time = 40 units, the counter has counted up to 4. We can also inspect the individual toggle flip-flop outputs from the viewer. We see that Q3 changes at half the time period of Q2 and a quarter of Q1, which is true for ripple counter.
The EPWave is quite useful in verifying and identifying whether each module is working as intended or not. One can also use $monitor to verify but in general EPWave is much better if the input values are known to us.
Synthesis
After verification of the design through simulations and testbenches, we need to do synthesis. We haven’t talked much about synthesis. Synthesis basically means ‘to generate.’ It is used to generate the gate-level netlist from the RTL code after simulation. Synthesis is essential, as it gives us a gate level representation of the circuit. It uses a standard cell library containing simple cells such as logic gates like AND, OR, NOT, NOR, etc. and macro cells such as flip-flops, registers, and adders.
One of the main difficulties of learning Verilog is that only some features of the language can be used to create hardware (the ”synthesizable subset” of the language), and the person has to know which part of the language is synthesizable and which isn’t. Non-synthesizable Verilog is used in testbench code to test hardware written in synthesizable Verilog. Moreover, one must code carefully, or else the post-synthesis and pre-synthesis simulation may not match.
Figure 7: Synthesis Concept Diagram. [Asic World]
For example, suppose we need a 3:1 multiplexer.
Code A:
module encode(a,b,c,s,y);
output y;
input a,b,c;
input [1:0]s;
reg y;
always @(a or b or c or s) begin
y = 1’bx;
case(s)
2’b00: y = a;
2’b01: y = b;
2’b10: y = c;
endcase
end
endmodule
In code A, if s is taken to be 2’b11, none of the above cases occur and y is returned as 1’bx. As a result, simulation mismatch will occur between pre synthesis and post synthesis. Latch will be assigned at this value to hold the last value of y. To avoid a latch, we must add the missing case to code B.
module encode(a,b,c,s,y);
output y;
input a,b,c;
input [1:0]s;
reg y;
always @(a or b or c or s) begin
y = 1’bx;
case(s)
2’b00: y = a;
2’b01: y = b;
2’b10,2’b11 : y = c;
endcase
end
endmodule
In code B, after synthesis, we can map it to hardware – FPGA (Field Programmable Gate Array) or ASICs (Application Specific Integrated Circuit) depending upon our objectives and constraints - speed, power consumption, cost of production and flexibility. Once all the steps of the design cycle are done and the physical layout is complete, we can send it to a fabrication facility, and they will manufacture it for us if we want an ASIC. However, in laboratories, most people use FPGAs because they are cost-efficient and highly customizable even if there is a tradeoff in performance.
References
[1] Don Mills and Clifford E Cummings. RTL coding styles that yield simulation and synthesis mismatches. In SNUG (Synopsys Users Group) 1999 Proceedings, 1999.
[2] Frank Vahid. Digital design with RTL design, VHDL, and Verilog. John Wiley & Sons, 2010.
Comentários