Embedded Systems Development with FPGA boards

In the last few weeks I had the honour to date a Virtex-4™ FX12 LC Development Board provided by Avnet Memec.

The Virtex-4 FX12 LC system board utilizes the Xilinx XC4VFX12-10FF668C FPGA. The board includes 64 MB of DDR SDRAM, 4 MB of Flash, USB-RS232 Bridge, a 10/100/1000 Ethernet PHY, 100 MHz clock source, RS-232 port, and additional user support circuitry to develop a complete system. The board also supports the Memec P160 expansion module standard, allowing application specific expansion modules to be easily added.

The initial goal was to develop an IP core for PWM signal generation, but my HDL and FPGA knowledge is still far too low to reach this target within reasonable time. What I managed to realize though, is an extremely non-optimal, pure software implementation to generate the desired PWM signal.

Making the system run

Anyway, the first challenge was to make my development board, delivered with Xilinx ISE 10.1, compatible with my Windows 7 x64 system. Xilinx ISE 10.1 and Xilinx Platform Studio 10.1 are not really compatible with Windows 7, though they can be successfully installed if you run setup.exe from the root\bin directory. The problem appears when you try to download the bitstream to your board using the JTAG programming cable. Of course, special drivers are needed to make the programming device compatible with the 64-bit system.

It was clear, that the simplest way to the solution is using a virtual machine, so I’ve installed VirtualBox, and tried around all the things that came in my mind, but I could not make the thing work. The Impact x86 which comes with the Xilinx ISE 10.1 pack just would not recognize my JTAG device. I strongly believe that it has nothing to do with  the thing that I was using VirtualBox, more with my incompetence.

So, the final solution that worked for me (I know it’s far not optimal but it works):
  1. Download and install WMvare Player.
  2. Install Windows Xp on your virtual machine.
  3. Install Xilinx ISE 10.1 and XPS 10.1 on your virtual machine.
  4. Install an IMAPCT x64 version on your host machine. (You can install a newer Xilinx ISE pack: e.g. 12.1)
  5. Download the x64 driver from here: http://www.xilinx.com/support/answers/35924.htm
  6. Install the driver.
  7. Now you should be able to work normally with your old Memec board, Xilinx ISE 10.1 and Windows 7 x64 host system.

Create the software for the PWM

Well, as I said before, this was my first real meeting with an FPGA board, so my experience was (is) kind of zero and rather theoretical.

First, I’ve tried to make the timer (and not only) interrupts work, but the examples found on the web didn’t really help me. Fortunately there are some official system design examples, that I’ve found at http://forums.xilinx.com/t5/3rd-Party-Other-Boards-and-Kits/Memec-Virtex-4-Development-Board/td-p/98012.

So, I’ve just taken the design called FX12 LC Interrupt Example Design and started to modify it.

If you check the documentation of this system design and, of course, the system design itself, you will see that it illustrates the handling of multiple interrupts and the associated interrupt service routines.

The system supports three interrupts:

  • OPB Timer Interrupt
  • PowerPC integrated Programmable Interval Timer (PIT) Interrupt
  • UART Interrupt.

When a timer interrupt occurs, software flashes the LEDs and outputs a message through the serial port. The application software also writes a message to the LCD panel on the board when the OPB timer interrupt occurs.

As I wanted to create the simplest system possible for PWM signal generation, I’ve deleted everything from the C source code that had nothing to do with the OPB Timer. I’ve deleted everything related to UART and PIT. (You may download my source code at the and of this article).

I wanted to check the correctness of my generated PWM signal by using a voltmeter. So instead of flashing a LED when an interrupt occurs, I’ve modified the system to set/reset one GPIO pin. To make this change, I’ve simply opened the system.ucf  file of my project and looked for the pin distribution of the four LEDs. I only needed to change the line

Net fpga_0_LEDs_4Bit_GPIO_d_out_pin<1> LOC=D12 | IOSTANDARD = LVCMOS25; 

with

Net fpga_0_LEDs_4Bit_GPIO_d_out_pin<1> LOC=AF18 | IOSTANDARD = LVCMOS33;.

So, now I’ve just needed to change the source code so, that a correct PWM signal is generated on the hardware pin AF18.

When editing the source code, I’ve followed the idea described hereafter:

  • generate a timer interrupt at every clock tick
  • set or reset the selected hardware pin in the interrupt handler routine
Below the source code of the main routine is presented. The duty cycle of the PWM signal is given in the source code as a variable of type char, so that it can be simply written to the LCD.


#define WAIT_BTW_INT 1

int DUTY_CYCLE;
char duty[3] = “079”;

int main() {
lcd_init(XPAR_MYLCD_BASEADDR); // initialize the LCD
DUTY_CYCLE =
(duty[2] – 48) + (duty[1] – 48) * 10 + (duty[0] – 48) * 100;

// write the value of the duty cycle to the LCD
  lcd_write(XPAR_MYLCD_BASEADDR, duty);

Xuint32 i = 0, j = 0;
Xuint32 interrupt_count = 0;

/* Initialize and set the direction of the GPIO */
XGpio_Initialize(&gpio, XPAR_LEDS_4BIT_DEVICE_ID);
XGpio_SetDataDirection(&gpio, 1, 0);

/* Initialize exception handling */
XExc_Init();

/* Register external interrupt handler */
XExc_RegisterHandler(
XEXC_ID_NON_CRITICAL_INT,
(XExceptionHandler)XIntc_LowLevelInterruptHandler,
(void *)0
);

/* Register Timer interrupt handler */
XIntc_RegisterHandler(
XPAR_XPS_INTC_0_BASEADDR,
XPAR_XPS_INTC_0_XPS_TIMER_1_INTERRUPT_INTR,
(XInterruptHandler)timer_int_handler,
(void *)XPAR_XPS_TIMER_1_BASEADDR
);

/* Start the interrupt controller */
XIntc_mMasterEnable(XPAR_XPS_INTC_0_BASEADDR);

/* Set the gpio as output on high 4 bits (LEDs)*/
XGpio_mSetDataDirection(XPAR_LEDS_4BIT_BASEADDR, 1, 0x00);

/* set the number of cycles the timer counts before interrupting */
XTmrCtr_mSetLoadReg(
XPAR_XPS_TIMER_1_BASEADDR,
0,
(timer_count*timer_count+1) * WAIT_BTW_INT
);
XTmrCtr_mSetLoadReg(
XPAR_XPS_TIMER_1_BASEADDR,
1,
(timer_count*timer_count+1) * 10000000
);

/* reset the timers, and clear interrupts */
XTmrCtr_mSetControlStatusReg(
XPAR_XPS_TIMER_1_BASEADDR,
0,
XTC_CSR_ENABLE_TMR_MASK |
XTC_CSR_ENABLE_INT_MASK |
XTC_CSR_AUTO_RELOAD_MASK |
XTC_CSR_DOWN_COUNT_MASK
);
XTmrCtr_mSetControlStatusReg(
XPAR_XPS_TIMER_1_BASEADDR,
1,
XTC_CSR_ENABLE_TMR_MASK |
XTC_CSR_ENABLE_INT_MASK |
XTC_CSR_AUTO_RELOAD_MASK |
XTC_CSR_DOWN_COUNT_MASK
);

/* Enable timer and uart interrupts in the interrupt controller */
XIntc_mEnableIntr(
XPAR_XPS_INTC_0_BASEADDR,
XPAR_XPS_TIMER_1_INTERRUPT_MASK
);

/* Enable PPC non-critical interrupts */
XExc_mEnableExceptions(XEXC_NON_CRITICAL);

/* Wait for interrupts to occur */
while (1) { ; }}

The algorithm to decide when the hardware pin should be on voltage level high and when on voltage level low is the following:
  • use a counter to count from 0 up to 99
  • set the hardware pin (high level = logical “1”) in every cycle when counter <= duty-cycle
  • reset the hardware pin (low level  = logical “0”) in every cycle when duty-cycle < counter <= 99

The interrupt service routine implements the above mentioned algorithm.

void timer_int_handler(void * baseaddr_p) {
Xint32 baseaddr = (int)baseaddr_p;
Xuint32 csr0 = 0;

Xuint32 count0 = 0;

//start timer to keep track of completion time
stop_watch(1, 0);

/* Read timer 0 CSR to see if it raised the interrupt */
csr0 = XTmrCtr_mGetControlStatusReg(baseaddr, 0);

/* See if the timer0 went off */
if (csr0 & XTC_CSR_INT_OCCURED_MASK) {

/* Shift the count */

if (global_count <= DUTY_CYCLE)
{
XGpio_mSetDataReg(XPAR_LEDS_4BIT_BASEADDR, 1, 0x6);
XGpio_mSetDataReg(XPAR_LEDS_4BIT_BASEADDR, 1, 0xf);
}
else
{
XGpio_mSetDataReg(XPAR_LEDS_4BIT_BASEADDR, 1, 0x0);
XGpio_mSetDataReg(XPAR_LEDS_4BIT_BASEADDR, 1, 0x0);
}

if (global_count == 99)
{
global_count = 0;
}
global_count++;

/* Clear the timer interrupt */
XTmrCtr_mSetControlStatusReg(baseaddr, 0, csr0);
}
}

Conclusions

Although the above described solution is far not optimal, it was a nice starting point for me, to see how an FPGA development board works. Working a bit around the system to make it work, I think I’ve understood the main idea behind FPGA based embedded systems.

It was also nice to implement a pure software and only one timer based PWM signal generator. The full system architecture, including hardware configuration files and source codes, is downloadable from here.

Leave a Reply

Your email address will not be published. Required fields are marked *