First FPGA Project - Getting Fancy with PWM

Pages
Contributors: Alchitry, Ell C
Favorited Favorite 2

Pulse Width Modulation

Before we jump into anything FPGA specific, we need to quickly cover what pulse width modulation (aka PWM) is.

PWM is a technique used by digital systems to approximate analog values. This is done by creating a series of pulses with a specific duty cycle. A duty cycle is simply the percentage of time a signal is high. A 100% duty cycle would simply be a fully on signal. A 50% duty cycle would have equal parts high and low.

The following is an example of a PWM signal with a 33% duty cycle.

pwm signal with 33 percent duty cycle

If the frequency of these pulses is high enough, in some applications, it can seem as if we are producing a value between high and low. This is useful for varying the brightness of LEDs.

LEDs are actually incredibly fast to turn on and off so when applying PWM signals to them they actually flicker. Luckily our eyes will average out this flickering if it is done fast enough making them look solid but dimmer. This effect is called persistence of vision.

Creating Pulses

We need to create a module that accepts an input value and produces a PWM signal with a duty cycle proportional to it.

This can be pretty easily done using a counter.

If we have a free-running counter, meaning it continuously is incremented, we can use this to set an output depending on the counter’s state.

If the threshold we are comparing to the counter is above the counter’s current value, we can output 1 and 0 otherwise. This would look like the following:

pwm with 33 percent duty cycle

Here, the compare value is 1/3 the max value of the counter so the duty cycle is 33%.

If we change the compare value, we change the duty cycle.

pwm with 66 pct duty cycle

Now the duty cycle is 66% as the compare value was increased.

We can make a simple module that will do this:

module pwm #(
    COMP_LENGTH = 8 : COMP_LENGTH > 0
  )(
    input clk,  // clock
    input rst,  // reset
    input compare[COMP_LENGTH],
    output pwm
  ) {

  .clk(clk) {
    .rst(rst) {
      dff counter[COMP_LENGTH];
    }
  }

  always {
    counter.d = counter.q + 1; // free running counter
    pwm = counter.q < compare;
  }
}

You could use this module to control an LED’s brightness and it would work pretty well. However, there are a few issues with it.

Glitches

First, this module isn’t glitch free. In the case of an LED, this doesn’t matter. However, for something that the width of each pulse actually matters (like a servo), this is important.

What do I mean by glitch free? Well imagine the current counter value is 25 and the current compare value is 10. The output is currently low since the counter is higher than the compare value. But what if we now change the compare value to 30? We will get a pulse 5 cycles long.

We never requested a pulse 5 cycles long. We were requesting pulses of 10 cycles then pulses of 30 cycles.

To fix this we need to only update the compare value when the counter is at its maximum value. We will need to save the current compare value until then using a DFF.

 module pwm #(
    COMP_LENGTH = 8 : COMP_LENGTH > 0
  )(
    input clk,  // clock
    input rst,  // reset
    input compare[COMP_LENGTH],
    output pwm
  ) {

  .clk(clk) {
    .rst(rst) {
      dff counter[COMP_LENGTH];
      dff last_comp[COMP_LENGTH];
    }
  }

  always {
    counter.d = counter.q + 1; // free running counter
    pwm = counter.q < last_comp.q;
    if (&counter.q)
      last_comp.d = compare;
  }
}

You can see I’m now comparing the counter with last_comp.q instead of the raw input compare value. I’m updating the value of last_comp with the input compare only when counter.q is all 1’s, aka its max value.

To check if counter.q is its max value, I’m using what is known as a reduction operator. In this case, I’m using the AND reduction operator. By putting the & in front of a value, every bit of that value will be AND’d with all the other bits. This will return 1 only if every bit is 1.

You can also use this with OR by using the pipe, |, symbol. This will return 1 if any of the bits are 1.

The XOR version uses a carrot, ^, and will return 1 if there are an odd number of 1s.

Using the reduction operator instead of an == comparison is convenient in a case like this because the width of counter.q is specified as a parameter. Its max value changes depending on the width but the max value will always be when the individual bits are all 1.

Now, no matter when or what we change compare to, the module will only ever output pulses of the width we have requested.

Prescaler

The next issue has to do with the fact that we can’t adjust the frequency of the pulses. They are simply a factor of how fast the clock is and how wide our counter is.

This is actually an issue with the LED because each time you turn it on and off you waste a little power charging and discharging capacitance on the line. If your frequency is too high, this parasitic loss becomes dominant and the LED will lose brightness. We want the pulse frequency to be fast enough we can’t see it but not obscenely faster. Something like 200Hz works great.

With an 8 bit counter, it is currently running at 100MHz/256 = 390.6KHz or roughly 2000 times faster than we want.

To adjust this, we can add a pre-scaler. This is a second counter that is used to increment our main counter after a certain number of cycles.

The simplest and most efficient way to do this is to add some extra bits to your main counter and only look at the most significant bits of it. By adding just one bit to the end we effectively divide the rate by 2. Two bits will divide it by 4 and so on in powers of 2.

module pwm #(
    PRESCALER = 11 : PRESCALER >= 0,
    COMP_LENGTH = 8 : COMP_LENGTH > 0
  )(
    input clk,  // clock
    input rst,  // reset
    input compare[COMP_LENGTH],
    output pwm
  ) {

  .clk(clk) {
    .rst(rst) {
      dff counter[COMP_LENGTH+PRESCALER];
      dff last_comp[COMP_LENGTH];
    }
  }

  always {
    counter.d = counter.q + 1; // free running counter
    pwm = counter.q[PRESCALER+:COMP_LENGTH] < last_comp.q;
    if (&counter.q)
      last_comp.d = compare;
  }
}

I added a parameter named PRESCALER which is the number of bits to use as a prescaler. This is added to COMP_LENGTH to create the width of the counter.

In the comparison, I used the start-width bit indexing syntax to select COMP_LENGTH bits starting at PRESCALER and going up. If PRESCALER is 0 and COMP_LENGTH is 8, this would read [0+:8] which would select bits 0-7. If PRESCALER was 11, this would read [11+:8] and select bits 11-18.

Top Value

Adding the prescaler gives you a crude way to adjust the frequency and is plenty for controlling LEDs. However, some applications require a more specific frequency. For these, you need to be able to set a custom top value.

The top value is simply the value that the counter will reset. In all our previous examples, this value has been the maximum value the counter could hold as it will reset automatically.

We can add an if statement to check the counter value and reset it manually to increase the frequency.

module pwm #(
    PRESCALER = 11 : PRESCALER >= 0,
    COMP_LENGTH = 8 : COMP_LENGTH > 0,
    TOP = $pow(2,COMP_LENGTH)-1 : TOP >= 0 && TOP < $pow(2,COMP_LENGTH)
  )(
    input clk,  // clock
    input rst,  // reset
    input compare[COMP_LENGTH],
    output pwm
  ) {

  .clk(clk) {
    .rst(rst) {
      dff counter[COMP_LENGTH+PRESCALER];
      dff last_comp[COMP_LENGTH];
    }
  }

  always {
    counter.d = counter.q + 1; // free running counter
    if (counter.q == TOP) {    // if we reach the TOP
      counter.d = 0;           // reset the counter
      last_comp.d = compare;   // and save the compare value
    }

    pwm = counter.q[PRESCALER+:COMP_LENGTH] < last_comp.q;
  }
}

In this version, I added the parameter TOP and set its default value to be its maximum value.

I used the $pow() function to compute the value of 2^COMP_LENGTH. This function like $clog2() used in the previous tutorial are computed during synthesis and aren’t in your design. Because of this, they can only be used with constant known values.

I also moved the updating of last_comp to be when the counter reaches TOP instead of it being all 1’s.

This is now a full fledged PWM module, but we really didn’t need to write it.

Components Library

Alchitry Labs has a bunch of built-in components that can be added to your project to do common tasks, such as PWM.


Go to Project->Add Components to open the Component Library.

Here you can go to LED Effects and select PWM.

Project Menu; Add components; Select LEDs and Select PWM

Click Add to add the component to your project.

You will find it under the Components branch in your project tree.

module pwm #(
    WIDTH = 8 : WIDTH > 0, // resolution of PWM counter
    TOP = 0   : TOP >= 0,  // max value of counter
    DIV = 0   : DIV >= 0   // clock pre-scaler
  )(
    input clk,          // clock
    input rst,          // reset
    input value[WIDTH], // duty cycle value
    input update,       // new value flag
    output pulse        // PWM output
  ){

  .clk(clk) {
    .rst(rst) {
      counter ctr(#SIZE(WIDTH), #DIV(DIV), #TOP(TOP));
      dff curValue[WIDTH];
      dff needUpdate;
    }
    // nextValue doesn't need reset
    dff nextValue[WIDTH];
  }

  always {
    // if ctr.value is 0 and we need to update
    if (!|ctr.value && needUpdate.q) {
      curValue.d = nextValue.q; // set our new value
      needUpdate.d = 0;         // don't need update now
    }

    // if value is valid
    if (update) {
      nextValue.d = value; // save it
      needUpdate.d = 1;    // flag for update
    }

    // if the counter is less than the set 
    // value output 1, otherwise output 0
    pulse = ctr.value < curValue.q;
  }
}

This version is very similar to ours with one exception. It assumes that the input value, which is used as the compare value, is only valid when update is 1.

It allows you to specify a new value at any time and it will save it until the new value can be used as the compare value.

There are some other differences as this component is actually using another component, a counter. The counter component is used to take care of the pre-scaling and top resetting for us.