In this tutorial, we’ll explore how to describe hardware PWM in Verilog, and will see how the modularity of Verilog code allows us to configure as many hardware PWM circuits in the Mojo as we want.
Here are all the parts you’ll need to follow along:
We’re going to build our Verilog code for this project on top of the Mojo Base Project provided by Embedded Micro. It is helpful to build off the base project, because the device specification and other initialization in ISE has already been set up for us! If you’d like, you can rename the project by renaming the .xise project file in the project directory. I named mine “MojoPWM.xise”.
Usually, we’d first declare names and pin numbers in the UCF for the different connections we are making to I/O pins on the Mojo. However for this project, we will use the onboard LEDs for which signal names and pin connections have already been specified. Thus, no additional declarations are necessary. We’ll start by creating a new Verilog module that specifies our hardware PWM behavior. Instead of placing the code directly in the mojo_top module, we’ll create a separate module to make use of modularity. If we want to create different hardware PWM circuits to run different LEDs, we’ll merely have to create new instances of this separate PWM module instead of copying and pasting large chunks of code. This will become more apparent shortly.
Right-click anywhere in the left-hand side “Hierarchy” window, and click on the option named “New Source…” Under the options list for “Source Type,” select “Verilog Module,” and name the file “PWM.v” This will create a new Verilog file with a skeleton for the PWM module.
Before we actually beginning writing code, let’s discuss our PWM implementation. As described before, the signal we’ll be generating with this hardware is periodic in nature, which means that its value is time-dependent. We’ll therefore have to specify its behavior with respect to a constantly ticking clock signal. This clock signal is already provided to us in the mojo_top module as a system input, and is a square wave that looks something like this:
Our hardware’s operation can be summarized as follows:
We choose the value 255 as our maximum counter value because this is the maximum integer number that can be stored in 8 bits (11111111). When incremented by 1 from this value, the counter will wrap back around to 00000000, as it would have overflown. To learn more about binary arithmetic and the binary representation of integers, check out the links in the appendix below!
Here’s a timing diagram that represents our hardware’s operation with respect to the internal clock signal:
Figure 2. Timing Diagram for an 8-bit Hardware PWM module with duty cycle 3/255. The max duty cycle value in binary is the same as the max counter value: 11111111
We’ll start our Verilog description of the PWM module with the input and output signals list:
input clk,
input rst,
input[7:0] duty,
output sig_drv
As you may guess from their names, these four signals are the clock, reset, duty cycle value, and PWM output signals respectively.
Next, we have to qualify the data type for our output signal, sig_drv. Verilog has two data types, wires (wire) and registers (reg). The differences between these two types are subtle for our application, however the main difference to note would be that when using always blocks as we will be in this project, one can only write to regs and not wires. We will discuss always blocks and their operation shortly. If signals from the signal list are left unqualified by the words wire or reg, Verilog implicitly declares them as wire type. In this case, we need to describe sig_drv as a reg, which we can do through the following line right after the module’s signal list:
reg sig_drv;
We will also be using an 8-bit counter as described above, which we’ll also have to set through an always block. Thus, we need to declare an 8 bit-wide counter as follows:
reg[7:0] counter;
You may have noticed that like many other programming languages, Verilog is zero-indexed, which means that counting always begins at the number 0. Thus, the indices of bits in an 8 bit-wide counter would have numbers 0 through 7.
Next, we must describe logic for counting up with the 8-bit counter and for driving the output signal. We can do that with always blocks! An always block is a Verilog structure that allows users to specify operations that will occur only when the triggering condition(s) for the always block is/are met. The basic structure for an always block is as follows:
always @ (…)
begin
…
end
Inside the parentheses following the “@” symbol, the user must specify the triggering conditions that decide when the logic inside the block executes. For our project, we need two always blocks:
always @(*)
begin
end
always @(posedge clk or posedge rst)
begin
end
In the first block, the triggering condition is “ * ”, which means that the logic inside that block will execute whenever any of the signals in the project change. Hardware engineers might refer to this block as combinational logic, logic that always defines output values as some function of input values. In this block, we will place the logic for our project that must function at all time instants, not just once every clock cycle: the driving of the output signal.
As described earlier, the output signal is driven either high or low based on whether the counter is greater than or less than the duty cycle. This functionality can be accomplished with the following lines of code within the always @ (*) block:
if (duty > counter)
begin
sig_drv = 1’b1;
end
else
begin
sig_drv = 1’b0;
end
The sig_drv signal is 1 bit wide (it only has the value 0 or 1…a single bit), so we preface the value we’re assigning to it with the characters “1’b”. From the code above, we can see that when the duty cycle is greater than the counter, the sig_drv line is driven to 1 (high), and otherwise, it is driven to 0 (low).
In the second block, the triggering condition is posedge clk or posedge rst. This means that the logic inside this block will only be executed once every clock cycle when the clock signal goes from low to high, or when the reset line goes from low to high. We’re going to use this always block to specify the counter increment that will occur once every clock cycle. This can be done with the following lines of code within this block:
if (rst)
begin
counter <= 8’b0;
end
else
begin
counter <= counter + 1;
end
The first segment of the if statement specifies that when the reset line goes high, the 8 bit-wide counter must be reset to all zeros. The second segment, the else condition, specifies that if the reset line is not high, the counter’s value must be incremented.
We can see that the value that is assigned to counter is dependent on its previous value. Hardware engineers refer to this type of logic as sequential, because outputs are functions of both inputs and past states.
The final aspect of this code to clarify is the “<=” operator, the non-blocking assignment operator, that was used to assign values to the counter variable. When we use the “=” operator, the blocking assignment operator, to assign values to signals as we have in the past, we actually tell Verilog implicitly that we want the code we’ve written to execute top-down. In other words, if we wrote two subsequent “=” assignment statements, the first assignment operation would have to finish executing before the second one began. This is fine and actually necessary in some logic, as we might use values that we assign in this manner to do subsequent computations.
However, in sequential logic that depends on a fast clock signal, we actually want all of our value assignments to occur somewhat in parallel (if they did not depend on each other), so that we do not delay the program enough to conflict with the next clock edge. In this program, we do not have multiple assignments that occur on each clock edge. However, if we did, using the non-blocking assignment operator would accomplish this.
The complete PWM module should look like this: