Monday, March 3, 2014

Attaching Peripherals to MicroBlaze MCS: 8-Bit PWM

Last time, I showed how to implement a MicroBlaze MCS soft processor core on the Papilio One.  However fun that is, in reality we have just created an expensive microcontroller with no peripherals to work with.  Today we will modify our existing project to add a simple 8-bit PWM module that we can control from our C application.

Our PWM module will consist of an 8-bit counter whose value is compared against a provided value.  If at any point in time the counter's value is less than the compare value, the output will go high.  Otherwise, it will stay low.  The diagram below, shamelessly stolen from the ATmega328P datasheet, explains the concept fairly well.  TCNTn would be the value of our counter, the dashes represent our compare value, and OCnx would be our output.


To accomplish this, we will first make an 8-bit counter Verilog module.  Afterwards, we will make a PWM module that instantiates the 8-bit counter and performs the comparison.  Once this is done, we can attach the PWM module to the GPO port of our microcontroller to set our compare value in software.

Our 8-bit counter module is very simple.  We have a clock input and a value output.  Every time we see a rising edge of the clock, we will increment the counter by one.  The counter will eventually overflow to zero, starting the next PWM period.
`timescale 1ns / 1ps

module Counter(clk, value);
 input wire clk;
 output reg [7:0] value = 0;

 always @(posedge clk)
 begin
  value <= value + 1;
 end
endmodule
 Next, the PWM module.  The PWM module will take an input clock and 8-bit compare value, and output a single bit.  The module instantiates the counter and feeds the clock signal through to it.  A separate combinational logic block compares the output of the counter to the specified compare value.  If counterVal is less than the compare value, the expression will evaluate to true, setting out high.  Otherwise, it evaluates to false and out will be low.
`timescale 1ns / 1ps

module PWM(clk, compare, out);

 input wire clk;
 input wire [7:0] compare;
 output reg out = 0;

 wire [7:0] counterVal;

 Counter c(clk, counterVal);

 always @(*)
 begin
  out <= counterVal < compare;
 end

endmodule
Simple as that!  Now, all that's left on the hardware side is to connect the PWM module to the MicroBlaze.  We can accomplish this first instantiating the PWM module in our top-level module, and then connecting our 8-bit GPO from the MicroBlaze to the compare input of the PWM module instead of the pins on the Papilio.  We can then connect the output of the PWM module to one bit of our existing output register.  We need to set the rest of the bits to something in order for the design to compile correctly.  I chose to connect them to zero.  One thing to note is that this is setting the remaining bits to a 7-bit integer zero; it does not set every individual bit to zero.  Here is the modified top-level module:
`timescale 1ns / 1ps

module TopLevel(Clk, Reset, UART_Rx, UART_Tx, GPO1, GPI1);

 input wire Clk;
 input wire Reset;
 input wire UART_Rx;
 output wire UART_Tx;
 output wire [7:0] GPO1;
 input wire [7:0] GPI1;

 wire pwmOut; 
 assign GPO1[7] = pwmOut;
 assign GPO1[6:0] = 0;

 wire [7:0] pwmCompare;

 PWM pwmModule (Clk, pwmCompare, pwmOut);

 SoftProc MySoftProc (
  .Clk(Clk), // input Clk
  .Reset(Reset), // input Reset
  .UART_Rx(UART_Rx), // input UART_Rx
  .UART_Tx(UART_Tx), // output UART_Tx
  .UART_Interrupt(/*UART_Interrupt*/), // output UART_Interrupt
  .GPO1(pwmCompare), // output [7 : 0] GPO1
  .GPI1(GPI1), // input [7 : 0] GPI1
  .GPI1_Interrupt(/*GPI1_Interrupt*/), // output GPI1_Interrupt
  .INTC_IRQ(/*INTC_IRQ*/) // output INTC_IRQ
 );

endmodule
Now, double-click "Generate Programming File" to resynthesize and create the new bitstream to work with.

Once it is done compiling, we can go back to the SDK environment to write some code to control the PWM module.  Since we connected the compare value to GPO1, writing to it will change the compare value of the PWM.  Since its 8 bits wide, valid values are 0-255.  I wrote a small program that will ramp the value up and down:
#include <xparameters.h>
#include <xiomodule.h>

#define STEP_DELAY 5000

int main()
{
 //Instantiate IOs
 XIOModule gpi;
 XIOModule gpo;

 //Initialize IOs
 XIOModule_Initialize(&gpi, XPAR_IOMODULE_0_DEVICE_ID);
 XIOModule_Start(&gpi);
 XIOModule_Initialize(&gpo, XPAR_IOMODULE_0_DEVICE_ID);
 XIOModule_Start(&gpo);

 int increment = 1;
 int val = 0;

 for (;;)
 {
  //Set the PWM pulse width
  XIOModule_DiscreteWrite(&gpo, 1, val);

  //Determine if we need to switch directions
  if (val == 255)
   increment = -1;
  else if (val == 0)
   increment = 1;

  //Increment/decrement the PWM value
  val += increment;

  //Wait for a bit
  for (int i=0;i<STEP_DELAY;++i);
 }
}
You should be able to build the project and transfer the design to the FPGA.  If all went well and you kept your LED on pin C8 from last time, you should see the LED fade in and out.


If we take a look on the oscilloscope, we can see a square wave of varying duty cycle.


Hope this has been helpful.  Next time, maybe we will try connecting multiple custom peripherals.

EDIT:  Some reddit users have pointed out that connecting the PWM module to the GPO instead of the I/O bus is "awkward" and non-canonical.  I did so in this tutorial just to get up and running quickly.  Perhaps in the future we will cover the I/O bus.

1 comment: