The C Programmers Guide to Verilog

Lara Simsic

July 09, 2003

Lara SimsicJuly 09, 2003

Now that hardware is designed in high-level languages, the fields of hardware and software development are beginning to merge. Here's an introduction to hardware design in Verilog for the uninitiated.

In the near future, embedded systems designers will be able to use hardware and software interchangeably depending on which best solves a specific design problem. Up until now, the barriers were so high for software engineers who wanted to learn hardware design that few explored the space. The barrier is crumbling because of the similarity between hardware description languages and programming languages. Also, several reasonably low-cost demonstration boards are available that include a field-programmable gate array (FPGA), a microprocessor, and tools that even a software developer can use to learn hardware design.

This article offers an example of this new design process using an FPGA. We'll look at how to implement pulse width modulation (PWM) in software and then turn the design into a logic block that can run from an FPGA and be controlled via software using a memory-mapped I/O interface. You can do everything in this article with the FPGA development kits that are available today from major FPGA manufacturers.

Hardware-software divide
Several things have changed that make it easier for software engineers to participate in hardware design. Both hardware and software modules are now designed using programming languages. As you know, C is the lingua franca of embedded software design. On the hardware side, Verilog is often the popular choice (though both VHDL and Verilog are popular). The syntax and structure of Verilog is similar to that of the C programming language, as the examples in this article will illustrate.

At the same time, hardware is getting easier to update and change. It used to be that software could be changed simply by downloading a new executable image while hardware could not. That's no longer entirely true. Just as a software developer can make a quick edit, recompile, and then download the new code into memory, hardware designers using programmable logic have a similar capability. Programmable logic changes the method for designing embedded systems by enabling you to change the hardware as easily as the software. In other words, it affords you the flexibility during design and debugging to choose the best way to handle these tasks—either in software or hardware.

Tools are available from FPGA vendors that enable a designer with a little knowledge of hardware to develop an embedded system for programmable logic, such as an FPGA. For example, the SOPC Builder from Altera (my employer) enables system designers to select and configure peripherals from an existing library as well as add user logic to create and tie peripherals together. With programmable logic and some hardware knowledge, software engineers can take advantage of the benefits of hardware to improve their systems.

PWM software
A PWM controller produces a stream of pulses like those shown in Figure 1. Usually the period and pulse width are specified. The duty cycle, or on time, is defined as the ratio of the pulse width to the period. Figure 1 shows a PWM waveform of about 33% duty cycle.


Figure 1: A PWM waveform

PWM is used in many applications, most frequently to control analog circuitry. Because the digital signal varies continuously at a relatively fast rate (depending on the period, of course), the resulting signal will have an average voltage value, which can be used to control an analog device. For example, if a stream of PWM pulses is sent to a motor, it will turn at a rate proportional to the duty cycle (from 0% to 100%). If the duty cycle is increased, the motor will turn faster; likewise, if the duty cycle is decreased the motor will slow.

For more information about PWM, consult Michael Barr's Beginner's Corner article "Introduction to Pulse Width Modulation" (September 2001, p. 103).

Generally speaking, PWM is implemented in hardware because the output signal must be continuously updated—going high from the start of each period for the proper time, then low for the remainder of the period. Software usually just handles the selection of the period and duty cycle and perhaps occasionally making changes to the duty cycle, to effect a behavioral change in whatever is attached to the PWM output.

There's no reason, however, that software couldn't be used to implement PWM, say by bit-banging a spare output pin. Writing such a PWM controller in software is a relatively trivial task and helps illustrate what we will do in Verilog shortly. Listing 1 shows the C code for PWM.

Listing 1: A bit-banging PWM controller implemented entirely in software


void
pwmTask(uint32_t pulse_width, uint32_t period)
{
  uint32_t  time_on  = pulse_width;
  uint32_t  time_off = period - pulse_width;

while (1) { pwm_output = 1; sleep(time_on); pwm_output = 0; sleep(time_off); } }

Based on the pulse_width and period arguments to this function, the PWM calculates the amount of time the output will be high and low. The infinite loop then sets the output pin high, waits for time_on time units to elapse, sets the output low, waits for time_off, and then repeats the cycle for the next period.

Verilog
Listing 2 shows a simple Verilog module implementing an 8-bit wide register with an asynchronous reset. The input of the register, in, is assigned to the output, out, upon the rising edge of the clock, unless the falling edge of the clr_n reset signal occurs (in which case the output is assigned a value of 0).

Listing 2: Verilog module for a register with asynchronous reset

module simple_register(in, out, clr_n, clk, a);

  // port declarations
  input
input
input [7:0]
input
output [7:0]
clr_n;
clk;
in;
a;
out;
  // signal declarations
  reg [7:0]
wire
out;
a;

  // implement a register with asynchronous clear
always @(posedge clk or negedge clr_n)
begin
    if (clr_n == 0) // could also be written if (!clr_n)
      out <= 0;="">
    else
      out <= in;="">
    end

// continuous assignment
assign a = !out[0];

endmodule

Glancing at the Verilog listing, you should notice several similarities to the C programming language. A semicolon is used to end each statement and the comment delimiters are the same (both /* */ and // are recognized). An == operator is also used to test equality. Verilog's if..then..else is similar to that of C, except that the keywords begin and end are used instead of curly braces. In fact, the begin and end keywords are optional for single-statement blocks, just like C's curly braces. Both Verilog and C are case sensitive as well.

Of course, one key difference between hardware and software is how they "run." A hardware design consists of many elements all running in parallel. Once the device is powered on, every element of the hardware is always executing. Depending on the control logic and the data input, of course, some elements of the device may not change their outputs. However, they're always "running."

In contrast, only one small portion of an entire software design (even one with multiple software tasks defined) is being executed at any one time. If there's just one processor, only one instruction is actually being executed at a time. The rest of the software can be considered dormant, unlike the rest of the hardware. Variables may exist with a valid value, but most of the time they're not involved in any processing.

This difference in behavior translates to differences in the way we program hardware and software code. Software is executed serially, so that each line of code is executed only after the line before it is complete (except for nonlinearities on interrupts or at the behest of an operating system).

A Verilog module starts with the module keyword followed by the name of the module and the port list, which is a list of the names of all the inputs and outputs of the module. The next section contains the port declarations. Note that all of the input and outputs appear in both the port list in the first line of the module and in the port declarations section.

In Verilog, two types of internal signals are widely used: reg and wire. These types differ in function. All parts have a signal by the same name implicitly declared as a wire. Therefore, the line declaring a as a wire is not necessary. A reg will hold the last assigned value so it doesn't need to be driven at all times. Signals of type wire are used for asynchronous logic and sometimes to connect signals. Because a reg holds the last value driven, inputs cannot be declared as a reg. An input can change at any time asynchronous to any event in the Verilog module. The main difference, however, is that signals of type reg can only be assigned a value in procedural blocks (discussed later) while signals of type wire can only be assigned a value outside of procedural blocks. Both signal types can appear on the right-hand side of the assignment operator inside or outside of any procedural block.

It's important to understand that using the reg keyword doesn't necessarily mean the compiler will create a register. The code in Listing 2 has one internal signal of type reg that's 8 bits wide and called out. This module infers a register because of the way the always block (a type of procedural block) is written. Notice that the signal a is a wire and thus is assigned a value only in the continuous assignment statement while out, a reg, is assigned a value only in the always block.

An always block is a type of procedural block used to update signals only when something changes. The group of expressions inside the parentheses of the always statement is called the sensitivity list; it's of the form:

(expression or expression ...)

The code inside the always block is executed whenever any expression in its sensitivity list is true. The Verilog keywords for rising edge and falling edge are posedge and negedge, respectively. These are often used in sensitivity lists. In the example shown, if the rising edge of the clk signal or the falling edge of the clr_n signal occurs, the statements inside the always block will be executed.

To infer a register, the output should be updated on the rising edge of the clock (falling edge would work too, but the rising edge is more common). Adding negedge clr_n makes the register reset upon the falling edge of the clr_n signal. Not all sensitivity lists will contain the keywords posedge or negedge, though, so there won't always be an actual register in the resulting hardware.

Inside the always block, the first statement asks if the falling edge of the clr_n signal occurred. If it did, then the next line of code sets out to 0. These lines of code implement the asynchronous reset portion of the register. If the conditional statement were:

if (negedge clr_n and clk == 1)

then it would be a synchronous reset that depends on the clock.

You may have noticed that the assignment operators inside the always block are different from the one used in the continuous assignment statement that begins with the assign keyword. The <> operator is used for nonblocking assignments while the = operator is used for blocking assignments.

In a group of blocking assignments, the first assignment is evaluated and assigned before the next blocking assignment is executed. This process is just like C's serial execution of statements. With nonblocking assignments, though, the right hand side of all assignments are evaluated and assigned simultaneously. Continuous assignment statements must use the blocking assignment (the compiler will give an error otherwise).

To make the code less prone to errors, it's recommended that you use nonblocking assignments for all assignments in an always block with sequential logic (for example, logic that you want implemented as registers). Most always blocks should use nonblocking assignment statements. If the always block has all combinatorial logic, then you'll want to use blocking assignments.

PWM hardware
One of the first tasks when writing a memory-mapped hardware module is to decide what the register map will look like from the software perspective. In the case of PWM, you want to be able to set the period and pulse width in software. In hardware, making a counter that counts system clock cycles is easy. Therefore, there will be two registers, the pulse_width and the period, both measured in clock cycles. Table 1 shows the register map for the PWM.

Table 1: Register map for PWM

Address
Name Width Description 0 period 32 bits Number of clock cycles for one period 1 pulse_width 32 bits Number of clock cycles the output will be high

< Previous
Page 1 of 2
Next >

Loading comments...

Most Commented

  • Currently no items

Parts Search Datasheets.com

KNOWLEDGE CENTER