Interfacing the Altera Nios CPU core with custom VHDL designs

... brought to you by the MAIDEN Project Team ... written by Horace Chan

  1. Interfacing the Altera Nios CPU core with custom VHDL design
    1. Overview:
    2. Hardware and software tools used:
    3. Procedure:
      1. Generating required parallel inputs and outputs to the Nios core:
      2. Input datapath:
        1. Sample timing characteristics:
      3. Output datapath :
        1. Sample timing characteristics:

Overview:

As field programable gate arrays (FPGA) increase in size and logic power, it becomes possible to integrate entire systems that once spanned a board full of separate integrated circuit (IC) chips onto one system on a chip (SOC).  These systems are growing in complexity everyday, and it is not uncommon to incorporate entire CPUs or DSPs into an application specific system.  The Nios core is an embedded user configurable 16 or 32-bit RISC CPU that is made available with the Altera Nios development kit (Apex Edition).  In order to make the CPU core useful in an application specific design, it must be interfaced with custom HDL datapaths efficiently.  Our design utilized parallel inputs and outputs added to the 32-bit Nios core through the Megafunction Wizard found in Quartus II v1.1; these added input and output ports function as the datain/dataout datapaths coming and going to external hardware, and all associated handshaking signals.  A study of this interface scheme, including sample C code fragment follows.

Hardware and software tools used:

Procedure:

Generating required parallel inputs and outputs to the Nios core:

The physical connection between the Nios cpu core and any external component will be done through user added parallel inputs and outputs (PIOs).  If many external VHDL components are required to communicate with the CPU core, it may be advantages to implement a bi-directional bus into Nios via bi-directional PIOs (not much unlike how the CPU core interfaces with the external RAM and FLASH memory).  To reduce the complexity of our design (both in terms of hardware synchronization and software programming) dedicated input and output PIOs were used for each external component.  

 
Signal
Direction
Description
Signals implementing input data path (N bit wide)
data_in[N-1..0]
input
  • N bit wide data input to CPU
  • rising edge sensitive data capture PIO (design decision)
  • the CPU only register/captures these values when data_in_valid is high
data_in_valid
input
  • one bit input data handshaking signal
  • IRQ sensitive
  • rising edge sensitive data capture PIO (design decision)
  • the external component sets this signal HIGH when the data_in contains a valid value to be captured by the CPU
data_in_request
output
  • one bit output data handshaking signal
  • the CPU sets this high when it is ready for new input data
Signals implementing output data path (M bit wide)
data_out[M-1..0]
output
  • M bit wide data input to CPU
data_out_valid
output
  • one bit output data handshaking signal
  • the CPU sets this signal HIGH when the data_out contains a valid value ready to be captured by external component
data_out_request
input
  • one bit input data handshaking signal
  • rising edge sensitive data capture PIO (design decision)
Table 1: Summary of input output signals

Input datapath:

Sample timing characteristics:


 
Fig. 1: Sample waveform of input datapath timing


Timing Event
Description
A
When the CPU captures a data_in_valid high signal (IRQ routine), it sets global flag to trigger the data_in capture function at a later time, but immediately sets data_in_request low to stall incoming data
B
CPU data_in capture function finished capturing data, sets data_in_request signal high, this triggers the external component to prepare new data (while the external component is preparing the new data, data_in_valid) is set low
C
New data ready from external component, data_in_valid set high again
Table 2: Summary of data input timing events

The external component is expected to have the timing scheme shown above.  The component provides new data ONLY when it asserts that the CPU data_in_request is set high, allowing the CPU to stall incoming data when it cannot process any more.  Note that the CPU immediately sets the data_in_request low once The CPU captures  new data from the data_in only after it asserts that the incoming data is valid data. 

Implementing software to control input data capture, including simple Nios interrupt service routine:

To implement the above timing scheme in the CPU, an interrupt service routine for the data_in_valid signal is used to trigger the capture of the data_in signal.  The data_in is not captured within the interrupt service routine to minimize the time spent in the ISR.  Instead, the ISR sets a flag, and the data_in is captured when the CPU checks for this flag (so that the CPU is not interrupted from it's other tasks for extended periods while capturing the data_in).   The sample code fragment below implements the ISR and the data_in capture function. 

Note: the PIO pointers (e.g. na_data_in_valid) point to defined data structures at specified memory locations, these macros can be found in the nios_map.h file in the [project dir]/[project name]sdk/inc/ for more information, please refer to the nios_software_development_reference.pdf from Altera

#include "nios_peripherals.h"
#include "nios.h"
#include "util.h"

int data_in_ready;
char data_in;

/******************************************************
void data_in_valid_ISR( int context )
-- services the interrupt datain_valid
-- sets flag to trigger capture of data_in at later
    time to minimize time in interrupt handler
*******************************************************/
void data_in_valid_ISR( int context ) {
   
    // reset the edge capture to exit the interrupt
    na_data_in_valid->np_pioedgecapture = 0;

    /* stall the input of new data while the input data
         has not been buffered */
    na_data_in_request->np_piodata = 0;

    // set flag to trigger capture_data_in function
    ++data_in_ready;
}


/******************************************************
void capture_data_in()

-- captures input data from the 8-bit data_in PIOs
*******************************************************/
void capture_data_in() {
   
    // when there is valid data at inputs
    if ( data_in_ready ) {
        // capture data from input PIO
        data_in = na_data_in->np_piodata;
       
        // request new data from external component
        na_data_in_request->np_piodata = 1;   
       
        // reset data_in_ready flag
        data_in_ready = 0;
    }               
}           

int main( void ) {

    /* install ISR and map it to IRQ number */
    nr_installuserisr( na_data_in_valid_irq , &lcd_data_in_ISR, 0 );
   
    // enable IRQ mask for data_in_valid
    na_data_in_valid->np_piointerruptmask = 0xFF;

    datain_start = 0; 

    while ( 1 ) { // infinite loop
   
        // processor does other work here ...
       
        capture_data_in(); // checks if there is new data to capture
    }


    return 0;
}

Output datapath :

Sample timing characteristics:



Fig. 2 Sample waveform of output datapth timing

Timing Event
Description
A
External component ready fro new data from CPU
B
CPU sends out new data, and holds this data at output until a new data_out_request is recieved
C
New data_out_request recieved, CPU prepares new data for data_out
D
data_out from CPU is ready to be read, CPU sets data_out_valid high
Table 2: Summary of data input timing events

The external component is expected to have data input timing characteristics as shown above.  In this CPU asserts that the external component is ready for new data by asserting that the data_out_request is high, only then will it begin the procedure of placing a new data_out value on the data_out PIO.  The C code to implement this scheme in the Nios core is trivial and therefore not included as a sample. 




Last Revised by Horace Chan April 4th, 2003