There are two HDLs (Hardware Description Languages) in common use in the current tech industry: Verilog is the easier of the two, and it is more commonly used in North America. In Europe and among certain circles of advanced hardware engineers, VHDL (VHSIC (Very High Speed Integrated Circuit) Hardware Description Language) is more popular. However, VHDL is quite a bit more awkward than Verilog, and so many people who are just getting started in languages that describe digital logic prefer to learn Verilog first. Verilog's structure and syntax are based on C, the grandfather of most modern programming languages, while VHDL is based on Ada, a language which has historically been subject to much derision for its unforgiving syntax.
We'll begin with probably the simplest digital logic structure: The YES gate. The YES gate is really just an electrical buffer; It has one input and one output, and it takes whatever it receives for input and reproduces that on the output. So, if you put a 1 into a YES gate, a 1 comes out, and if you put a 0 into a YES gate, a 0 comes out. Obviously it performs no logic processing whatsoever, and in the real world it's used simply to condition electrical input. The Verilog code to create a YES gate looks like this:
module yesgate (a, b); input a; output b; assign b = a; endmodule
The first line declares that this is a Verilog module called yesgate, which has two ports, called "a" and "b". Note that Verilog names ARE case sensitive, so "yesgate" is NOT the same thing as "YESgate". Most modules have a certain number of ports, which really just represent wires that come out of the device. Ports like this can be inputs, outputs, or both.
The second and third lines, obviously, specify that a is an input, and b is an output. (If you want a port to be bi-directional, by the way, use the "inout" keyword to define it.)
The "assign" line is where it all happens. This line seems simple and straightforward at first: It says that whatever a is, b should become, right? Well, yes. But there's an important subtext in this line that we might as well explain right here and now, since it's not only an important point, it's also one of the key things that you need to understand about Verilog (especially if you come from a more traditional programming background). The "assign" keyword in Verilog is what's technically called a continuous assignment. What this means is that at any point in time, b essentially acts as if it were physically wired to a. If a changes, b will change correspondingly, without you having to tell b to change. This is because the program that you're writing in Verilog isn't just a simple computer program; you're designing something physical here. When you write in an HDL, you're not just working with software, you're actually describing hardware, and in the real world, protons, neutrons, and electrons don't wait for computer instructions to tell them to do anything. They obey the laws of physics, which supersede the laws of any computer language.
This is something that you're going to have to get used to when using HDLs. Unlike a computer program written in C or BASIC or assembler or whatever, which pretty much runs one thing at a time and goes through its list of instructions line-by-line, when you create hardware, doing one thing may cause a whole bunch of other things to happen unexpectedly. If you connect two wires together, electricity is going to flow through them, whether you wrote an instruction for that to happen or not. In an HDL, things can happen continuously, meaning that even as your program is running some instruction, completely unrelated things could be happening at the same time. This is exactly what's going to happen if you use the assign keyword: Even after your program moves on to other instructions, port b is going to be busy changing into whatever port a currently is. In this case, because we're making a YES gate, this is fine, since there's no circumstance under which we wouldn't want b to always equal a (unless we had some kind of specific timing requirements for the gate, which in this case, we don't), but be aware of this when using the assign keyword. If you just wanted to make a one-time assignment (i.e. make b equal a right now, but not have them forever wired together), then you'd just use a simple assignment statement, which could be done just by dropping the word "assign" and leaving the line "b = a;".
Now that we've gotten that out of the way, let's return to our YES gate. We've only got Verilog's equivalent to an "END" statement left: Every Verilog module needs to end in an "endmodule" line, so the compiler knows where the module ends.
That's it. It's really that simple. A Verilog NOT gate looks almost the same:
module notgate (a, b); input a; output b; assign b = ~a; endmodule
In Verilog, the tilde (~) represents a bitwise NOT operation. A NOT gate (also known as an inverter) takes whatever the input is, and outputs its opposite. So, a 1 becomes a 0, and a 0 becomes a 1.
Moving along, then, the following is a Verilog OR gate:
module orgate (a, b, y); input a, b; output y; assign y = a | b; endmodule
And here's a Verilog AND gate.
module andgate (a, b, y); input a, b; output y; assign y = a & b; endmodule
Here's a Verilog XOR gate.
module xorgate (a, b, y); input a, b; output y; assign y = a ^ b; endmodule
This is a Verilog NOR gate.
module norgate (a, b, y); input a, b; output y; assign y = ~(a | b); endmodule
And finally, the last of our basic Verilog gates is the NAND gate:
module nandgate (a, b, y); input a, b; output y; assign y = ~(a & b); endmodule
We now have all the fundamental gates of digital logic representable in an HDL.
If you want to know a bit more about how you might use an HDL like Verilog to create circuits through software, try reading about FPGAs.
Of course, making logic gates is just the beginning. To do more serious hardware design, some further details on Verilog are needed.
Let's get comments out of the way. Verilog supports exactly the same commenting style as C++. You can use /* block comments */ or // line comments. Both work.
Because it's not a computer programming language as such, Verilog isn't too concerned with data types. Verilog coders don't often worry about integers versus decimal values in their programs, but Verilog does have two basic entity types that are declared and assigned much like variables: Ports and registers.
Ports, as you've already seen, represent wires connected to the device.
Registers, on the other hand, are not physical connections. They are more like variables in conventional programming; They store values in memory, but they do not have any physical link to the outside world. A register can be declared within a module using the reg keyword. For example, the following line creates a register called "foo":
Another trait that makes these two data types look even more like conventional variables is their handling of multiple bits. Very often when working with digital logic, wires are not singular entities; Rather, several wires in a group together represent a digital value that contains several bits. Such a group of wires is called a bus, and ports in Verilog can be turned into buses with a syntax like this:
input [7:0] foo;
The above line creates a bus of eight input wires, numbered from 0 to 7. The bus itself is called foo, and you can refer to a single wire within this bus by using that wire's number in square brackets after the bus name. For example, the following line would assign the value of wire 4 in the "foo" bus to an output called "bar":
assign bar = foo;
See why I say it looks like a variable? It's exactly the sort of syntax used to address elements within an array in a high-level language like C. You can use this exact same multi-bit syntax for registers, although of course in that case, each number in the series doesn't correspond to an individual wire; rather, it just corresponds to an individual bit in the register.
Registers are useful for many things. They are, of course, used as CPU registers (hence their name). However, if you think beyond that obvious application, a Verilog "register" can be used as an array of several stored values. Remind you of anything? That's right, a so-called "register" can actually be used as memory. Using a register this way as RAM, you can basically turn a programmable device into a full-blown computer, containing its own CPU, memory, and input/output pins, all on a single chip! Such a configuration is what's come to be known as a SOC (System On a Chip).
Verilog's standard syntax for specifying a decimal, binary, hexadecimal, or octal number looks like this:
...Where "w" is a number indicating the width of the number (how many bits it uses), "s" is the number system in use (which can be "d" for decimal, "b" for binary, "h" for hexadecimal, or "o" for octal), and "nnn" is the actual number, however long it is.
For example, to represent the binary number 10011101 in Verilog, you'd type it like this:
To represent the hexadecimal number 4F2C, you'd type it like this:
Pretty simple, huh?
In a traditional programming language, assignment is usually done with an equals sign. For example, the following line of code...
a = b;
...Basically says "see whatever is stored in b, and change a to the same value". Verilog's assignment syntax is similar, but Verilog has two assignment operators: The blocking assignment operator is the traditional equals sign, while the non-blocking assignment operator is a less-than sign followed by an equals sign (in other words, it looks like <= ).
What's the difference? One of them performs the assignment immediately, while the other waits until the end of the current block of code to perform the assignment. To illustrate this, consider the following example:
a = b; c = a;
In this example, the first line will change a to equal b, and the second line will then change c to equal a. However, this will have the effect of changing c to equal b, because the first line has made a to equal whatever b is. That's fine if that's what you want, but there are times when you don't want any assignments to take place until after all the right-hand expressions have been evaluated. In that case, you'd use code like this:
a <= b; c <= a;
These assignment operators tell Verilog not to actually perform the assignment until the right-hand expressions have been evaulated. Thus, at the end, c will NOT necessarily be the same as b, because the line "c <= a" will turn c into whatever a was before a was changed to equal b. A key point to note about this behavior is that this means it doesn't matter what order you place non-blocking assignments in, but it DOES matter what order you place blocking assignments in. Since all the right-hand sides of non-blocking assignments are evaluated before the assignment actually takes place, it doesn't matter where you put the statement, because it will not be executed "in order", but blocking statements ARE executed in order, so their assignment will take place wherever they are in your program.
If you're a logical thinker, you're probably now wondering: Okay, so when do all the right-hand expressions get evaluated? Sure, it sounds great to be able to line up a bunch of assignments like this and have them wait until a later time to take place, but when is that later time? Answer: At the end of the current time step. Briefly, Verilog programs execute in a series of "time steps" which are somewhat analogous to the cycles of a CPU executing a conventional computer program. At the end of a time step, all non-blocking assignments get their right-hand sides evaluated, and then the results are assigned to the left-hand operands.
Electronic design engineers know that timing is crucial. Electronic signals must be sent at the proper point in time, or they may not be received by whatever is supposed to receive them. Sometimes, it's necessary to wait a set period of time before a circuit proceeds. For this, Verilog incorporates a simple way to add time delays to any line of code. It works like this:
#10 //code goes here
The "#10" preceding the code indicates that a delay of 10 time units should pass before the line of code is executed.
Like almost any good programming language, Verilog has an if statement. The syntax looks like this:
if (condition) begin (code to execute if condition is true) end
The indentation shown is not necessary, but it helps to make the code more readable.
Verilog has a case statement that works just like C's switch statement: It allows for several possible processes based on the outcome of an expression. The syntax for the case statement is simple:
case (expression) outcome1 : procedure1 outcome2 : procedure2 outcome3 : procedure3 //etc... endcase
Verilog's case statement is the key to making an instruction decoder for a CPU. A CPU's instruction decoder is the circuit which receives an opcode from memory and triggers an operation based on that opcode. With the case statement, you can make an instruction decoder very simply.
Where Verilog starts to depart somewhat from a conventional programming language is the "always" keyword. Code assigned to an "always" function always executes. Remember, when you code something in a regular programming language, the program is run by the computer one line at a time, so instructions are executed as the program flow reaches them. However, when you code something in an HDL like Verilog, you're not making something that will run line-by-line; you're modeling an actual, physical circuit. This means that the program doesn't have to wait for an instruction pointer to reach some line of code before it can execute; you can make a block of code "always" happen in some other segment of the device you're programming.
The always statement would be ideal for (for example) an interrupt. When a CPU is in operation, it can be made to stop doing whatever it's doing by an incoming interrupt signal. For this kind of application, you'd use a "sensitive list" with the always statement, which is simply a list of signals which will trigger the code in the always block. So, your code to handle the interrupt might look something like this:
always @ (int) begin //code to handle the interrupt goes here end
The @ symbol after the "always" keyword indicates that this code should always be run whenever "int" is true. So, int could be the name you've assigned to the CPU's interrupt pin.
The typical line that is used for any kind of a clock-driven logic chip like a CPU is this:
always @(posedge clk)
What this line basically means is that the code inside the block always executes on the positive-going edge of the "clk" line. This is vital to making a CPU work properly, since if you just say "always", the clock will not be driving the CPU at all; the CPU will simply run as fast as the FPGA can possibly go. By saying that the code should always execute when the clock line transitions high (but at no other time), you'll make your chip actually listen to its clock. Note that you can also use "negedge" if your CPU acts on the negative-going edges of its clock input.
If you don't use a sensitive list with the always keyword, you need to implement a delay so the instructions contained within it are not run constantly, but at specific intervals. Something like this:
always begin #10 //code goes here end
That's all the Verilog that exists here for now.
Back to the main page