Exploring the Arrow SoCKit Part X - Sending and Handling Interrupts
Hi everyone! It’s been a long time, but here is another Cyclone V tutorial blog post. This time, we will look at how to send interrupts from the FPGA to the HPS and handle the interrupt in software on the HPS. All hardware descriptions and software programs can be found on Github.
What is an interrupt?
Until now, all of our communication between HPS and FPGA has been initiated by the HPS. In order to detect changes in the state of the FPGA peripherals, the HPS has had to continuously poll the FPGA over the bus. If the state changes infrequently, but we want software to get notified of the change quickly, polling can be rather inefficient. In this case, it would be better if the FPGA could asynchronously notify the HPS of a change.
The way the FPGA can do this is through interrupt. Interrupts are essentially signals going from the FPGA to an interrupt controller on the HPS. The FPGA can make an interrupt request (IRQ) by asserting the interrupt signal high. When an IRQ reaches the HPS, it saves its current state and jumps to an interrupt service routine (ISR). The ISR should service the IRQ by reading or writing some data from the peripheral. Once the ISR has returned, the processor jumps back to its original state.
Creating an Avalon Interrupt Interface
We will create an FPGA peripheral from which we can read the state of the keys and switches attached to the FPGA. The peripheral should send an IRQ when the state changes.
As with other signals sent between FPGA and HPS on the Cyclone V, interrupt signals go through an Avalon interface. The interrupt interface is quite simple, only a single one-bit irq signal is required. However, we also put in a memory-mapped interface so that the state of the inputs can be read.
We pull the state of the keys and switches through two stages of registers.
last_inputs are different, we set the
to high. According to the Avalon interrupt interface specification. The IRQ
signal should not be deasserted until the slave has determined that it has
been serviced. In this case, we consider the IRQ serviced once the input
state is read, so we set
avl_irq back down to 0 if
avl_read is asserted.
We can attach this peripheral to the HPS using Qsys. In Qsys, create a new
component using the verilog module. Make sure to assign
avl_irq to an
“Interrupt Sender” interface and set the signal type to “irq”. Add this
component to the system.
When adding the HPS to the system, make sure to check “Enable FPGA-to-HPS interrupts” in the “Interrupts” section of the “FPGA Interfaces” tab. Connect the clock, reset, and avalon slave interfaces as usual. Then, connect the interrupt line by clicking on the path from FPGA peripheral to HPS in the “IRQ” column. Your final system should look something like the following.
Note the “0” on the interrupt line. This is the interrupt number assigned to this IRQ. It is important, as it determines what interrupt number on the HPS corresponds to this interrupt signal. On the Cyclone V, FPGA interrupts start at IRQ number 72, so our interrupt 0 corresponds to IRQ 72.
At this point you should generate your Qsys system. You will see some warnings about not being able to connect clock or reset for “irq_mapper.sender”. Do not worry about these warnings. The interrupts will still work.
The Linux Kernel Module
In order to be able to handle these interrupts in software, we need to write a linux kernel module which registers an ISR for our interrupt. A basic module would register an ISR that simply reads the input state and returns. Such a module would look something like this.
This isn’t particularly useful, since there is no way to notify userspace of the state changes. In order to do that, we’ll add a read-only sysfs device. Reads on the sysfs file will block until an interrupt occurs. Once this happens, the current state of the inputs is sent to the user.
How do you block the read call? We use a data structure in the kernel called a wait queue. A wait queue can defined like so.
In the “show” function for our sysfs device, we wait until a flag is set by the interrupt controller.
wait_event_interruptible call is what pauses execution of
fpga_uinput_show until an interrupt occurs. If the wait is interrupted
(not by the interrupt we want, but by something like a
SIGINT), it returns
a non-zero value, and we must therefore do some error handling.
If the wait ends successfully, we unset the interrupt flag and copy the input state read from the peripheral to the user.
In our ISR, we must add some code to set the interrupt flag and wake up the processes waiting on the wait queue.
You can find the full code for this kernel module in the Github Repo.
Our userspace program is then pretty simple. All it has to do repeatedly open and read the sysfs file.
Once the userspace code reads the current state, it compares it to the previous state to determine which of the inputs has changed.
So now you know how to handle FPGA interrupts. This will allow you to design much more efficient interfaces between your FPGA hardware peripherals and the CPU.