(reference_desing documentation here)
Creating an application using GeMRTOS follows a process familiar to developers experienced with uniprocessor projects in Intel’s Quartus Prime and Platform Designer tools. The journey begins by setting up a Quartus Prime project, using the “New Project Wizard…” to simplify configuration and define essential parameters such as the target FPGA device, project directory, and top-level entity.
Alternatively, developers can leverage an existing project provided with the target FPGA development board, streamlining the setup further. This tutorial will modify an existing Platform Designer project based on Nios II or Nios V, included with the FPGA board.
The following steps assume the prerequisites are met:
- Quartus Prime installed.
- Platform Designer project with Nios II or Nios V processors targeted for the FPGA board.
These prerequisites ensure the development environment is properly set up for working with GeMRTOS and Intel’s design tools. It is important to note that configuring a system for any board simply requires specific information about that board. To avoid error-prone configurations, it is best to start development from a pre-configured design aimed at the specific board, including all necessary settings in the project.
Step 1: Check for the existence of a Quartus Prime project folder containing the GeMRTOS IP components. #
To begin the design of a GeMRTOS application using Intel’s Platform Designer within the Quartus Prime project directory, the following files and directories must be included:
The /ip subfolder contains the necessary directories including the GeMRTOS IP components required by Platform Designer (The GeMRTOS component and license can be freely downloaded here).
The essential Quartus Prime project and Platform Designer project files include:
- <quartus_project_name>.qpf: The Quartus Project file.
- <quartus_project_name>.qsf: The Quartus Project Settings file.
- <top_level_entity_file>.v or .vhd: The hardware description file containing the design description. This file can be added after the project has been created, but its association must be established during the generating process. It is typical to instantiate the embedded system created using the Platform Designer tool, including the GeMRTOS IP component, within this file.
- <platform_designer_project_name>.qsys: The Platform Designer project file.
Step 2: Modify a Platform Designer project, incorporating the GeMRTOS IP component. #
While the Quartus Prime project is active, navigate to Tools -> Platform Designer to initiate the Intel’s Platform Designer tool. This utility facilitates the creation or modification of an embedded system tailored for Intel’s FPGA devices.
Note: It is imperative that the Quartus Prime project remains open within Quartus Prime to establish the working directory as the current Quartus Prime project directory. This ensures that the Platform Designer project will be located in the designated folder. This same principle applies to executing commands in the command shell: all commands should be executed from the Quartus Prime project directory.
When “Platform Designer” is open, the GeMRTOS_Multiprocessor component is available in the GeMRTOS group in the IP Catalog Library. This integration streamlines the process and empowers you to harness the capabilities of GeMRTOS effortlessly.
The GeMRTOS_Multiprocessor component simplifies the configuration of multiprocessor system features. Aspects such as the number of processors, processor type, and processor parameters easily converge within the GeMRTOS component’s user-friendly GUI parameter setup. This consolidation captures all of the complicated features inherent in the design of a sophisticated multiprocessor system, effectively addressing problems that would otherwise necessitate much knowledge and effort.
Note: The fields labeled as "Quartus Prime project name" and "Platform Designer project name" enable the selection of default values for constructing the entire design flow using the gemrtos_build.sh script. Therefore, it is advisable to initially save the Platform Designer project and subsequently verify the chosen project names to ensure accuracy in the process.
Within the “System Contents” window, a distinctive characteristic of the GeMRTOS_Multiprocessor becomes evident. It is seamlessly interconnected, much like a “conventional” processor, featuring multiple memory buses that establish connections to various peripherals within the system. This integration facilitates a smooth and familiar interaction with peripherals, despite the underlying complexity of the GeMRTOS_Multiprocessor’s functionality.
Note: The GeMRTOS IP component offers the flexibility of utilizing two different clocks. It's crucial to ensure that all components connected to the GeMRTOS IP component share the same clock, which is connected to the "clk_external_bus" port. However, it is possible to connect the GeMRTOS processors to a different clock through the "clk_processors" port, providing an additional layer of configurability.
The GeMRTOS framework smoothly integrates interrupt handling, matching the approach of a normal uniprocessor architecture. This unique perspective enables GeMRTOS to simplify the development of multiprocessor architectures within FPGA devices, providing an intuitive avenue for implementation.
For a more beginner-friendly introduction to GeMRTOS, a direct port can be effortlessly exported to connect inputs and outputs directly to the GeMRTOS memory map registers. These outputs can serve practical purposes, such as controlling LEDs to visually represent the execution of system tasks. This approach obviates the need for intricate PIO device configuration.
To bring the entire system to life,it is imperative to initiate the generation of the “Platform Designer” project. This can be achieved by selecting the “Generate HDL…” button located in the bottom-left corner of the “System Contents” window or selecting “Generate ->Generate HDL…” from the Platform Designer menu. This project generation culminates in the creation of a component that can be seamlessly instantiated within your Quartus Prime hardware project, solidifying the integration of GeMRTOS into your FPGA design. The instantiation template for the Platform Designer system can be readily obtained by navigating to “Generate -> Show Instantiation Template…” from the Platform Designer menu.
When the Platform Designer project containing the GeMRTOS IP component is generated, the gemrtos_build.sh script is automatically generated and placed within the Quartus Prime project directory.
Step 3: Instantiate the GeMRTOS Platform Designer component into the existing Quartus Prime project. #
To prepare for hardware compilation, the Platform Designer Instantiation Template must be instantiated in the Quartus Prime project. This stage should be carried out in the same way as any other Platform Designer project to ensure a smooth integration into the Quartus Prime project.
The exported signals from the Platform Designer component must be meticulously linked to the appropriate signals in the Quartus Prime project, aligning them with the specific FPGA pins connected to the corresponding on-board devices. For instance, memory signals should be accurately assigned to the FPGA pins that interface with the memory device on the board. This meticulous signal mapping is vital to ensure proper functionality and connectivity with the hardware.
Once the Platform Designer component has been instantiated, the Quartus Prime project can be compiled by selecting “Processing -> Start Compilation” from the Quartus Prime menu. Upon completion of the compilation process, the <quartus_project_name>.sof file is generated. This file serves as the configuration hardware file for the FPGA device.
Note: To ensure the successful operation of the gemrtos_build.sh script, it is imperative to specify the output file directory as "<quartus_project_name>/output_files" subdirectory. This configuration can be achieved through the "Assignments -> Settings..." menu, where you should set the "Directory name" field to "output_files" within the "Compilation Process Settings" category in the settings window. This precise configuration is vital for the script's functionality.
Step 4: Build all the application through the gemrtos_build.sh script. #
A Platform Designer project’s generation process includes multiple configuration steps. A GeMRTOS-based system’s build flow is similar to that of any other Platform Designer project, with the addition of configuring reset and exception memory settings for the system processors. This particular setting is required to ensure appropriate operation.
As a result of the introduction of the Nios V command shell, which is based on the Windows Console, all the design flow for Nios V processor requires executing the gemrtos_build.bat script. #
The gemrtos_build.sh script automates all of the essential operations for building a GeMRTOS application, which simplifies these stages, especially for those new to GeMRTOS, and improves efficiency during the development of GeMRTOS applications. This automation simplifies the procedure and decreases the complexity of manual configuration.
The gemrtos_build.sh should be executed from the command line of the Nios Command shell from the <quartus_prime_directory_name> directory.
XXXX@XXXX:/mnt/<quartus_prime_directory_name>$ bash gemrtos_build.sh
The “gemrtos_build.sh” file is a bash script that executes all the essential steps to build a GeMRTOS application via the command line in the command shell. By default, it is set up with the parameters derived from the GeMRTOS module configuration.
- Platform Designer Project Name: The GeMRTOS component should be integrated into a Platform Designer project, and the associated .qsys file must reside in the main directory of the Quartus Prime project. The Platform Designer project name may be specified through the “Platform Designer project name” parameter in the GeMRTOS IP component’s parameter window.
- Quartus Prime Project Name: This designation identifies the Quartus Prime project in which the Platform Designer project, containing the GeMRTOS IP component, is instantiated. The Quartus Prime project name can be designated via the “Quartus Prime project name” parameter in the GeMRTOS IP component’s parameter window.
- Application project name: This determines the name of the application that the GeMRTOS multiprocessor architecture will execute. By default, a “hellogemrtos” project is created or considered within the “./software” directory, using a “hellogemrtos.c” template.
- BSP Project Name: This establishes the name for the Board Support Package (BSP) associated with the application. By default, it is produced and stored in the “hellogemrtos_bsp” subdirectory within the “./software” directory.
The execution of the “gemrtos_build” script encompasses the following steps:
- Generates the Platform Designer system, incorporating the GeMRTOS component.
- Compiles the Quartus Prime project that instantiates the Platform Designer system, leading to the creation of the “<quartus_project_name>.sof” file.
- Generates the BSP setting file and the BSP project directory, configuring memory sections for GeMRTOS processors.
- Establishes the “./software/hellogemrtos” directory, and if absent, includes the “hellogemrtos.c” template file.
- Compiles both the BSP and application projects, resulting in the creation of “hellogemrtos.elf.”
- Transfers the “<quartus_project_name>.sof” file to the FPGA utilizing the Quartus Prime Programmer tool.
- Opens terminals to establish connections with the GeMRTOS JTAG UARTs within the GeMRTOS component, encompassing a default STDIO, a default STDERR, and a JTAG UART for each system processor.
- Downloads and initiates the execution of “hellogemrtos.elf” on the target hardware.
Following these procedures, a multiprocessor application commences its execution on the FPGA device, and the connected terminals begin displaying welcome messages.
Congratulations! Your first GeMRTOS implementation is up and running successfully. This achievement marks a significant milestone in your project. If you have any further questions or require assistance with anything else, please feel free to ask.
Some final remarks: the application code. #
By default, the gemrtos_build.sh script generates two projects: “hellogemrtos” and “hellogemrtos_bsp.” These projects are located within the /software subdirectory of the <quartus_project_directory_name> directory.
The “hellogemrtos_bsp” project includes the Board Support Package (BSP) required to build the system’s Hardware Abstraction Layer (HAL).
The “hellogemrtos” project, on the other hand, is started with a template C code file called “hellogemrtos.c.” This code can be changed to meet the specific functionality of your project and serves as a starting point for an initial GeMRTOS application. You are free to edit this source file to meet your needs.
The original code within this template source file contains the following program:
/******************************************************************************
* *
* License Agreement *
* Copyright (c) Ricardo L. Cayssials *
* All rights reserved. *
* *
******************************************************************************/
/* Include the GeMRTOS library */
#include <gemrtos.h>
/******************************************************************************
* Defining system parameters *
******************************************************************************/
#define number_EDF_lists 2
#define number_FP_lists 2
#define tasks_per_list 15
/******************************************************************************
* Global structure for task information storage *
* Declaring structures to hold data for easy displaying *
******************************************************************************/
struct task_information {
G_INT32 task_index; // Index to the task pointer in task array
};
/******************************************************************************
* Global structure for jtag uart information storage *
* Declaring structures to hold data for jtag_uart device *
******************************************************************************/
struct jtag_uart_info {
unsigned int trigger;
G_RCB *pqueue;
unsigned int buffer_length;
unsigned int jtag_base;
};
/* Semaphore to exclude LEDs variable access */
t_semaphore_resource *leds_sem;
/* LEDs variable retains the current value written to GeMRTOS outputs */
volatile unsigned int leds;
/* Producer consumer queue for jtag_uart_0 */
G_RCB *pqueue;
/* Trigger for jtag_output_0 server */
G_RCB *jtag_uart_0;
/******************************************************************************
* Signal function when task is aborted (the deadline is missed) *
******************************************************************************/
void sig_aborted_task_generic(int pdata)
{
gu_fprintf("\nTASK %d ABORTED\n", pdata);
}
/******************************************************************************
* generic task function *
******************************************************************************/
void task_generic(void* pdata)
{
struct task_information *ptask_info = (struct task_information *) pdata;
/* get the pointer to the TCB of the current task */
GS_TCB *ptcb = gu_GetCurrentTCB();
/* get the led_sem semaphore to exclude reading leds variable */
int sem = (int) gu_sem_wait(leds_sem, (gt_priority) 0, (int) G_TRUE);
/* if semaphore is granted, change the leds value */
if (sem == G_TRUE) {
leds = leds ^ (unsigned int) ptask_info->task_index;
/* write the value in leds variable to the GeMRTOS output port */
gu_write_outputs(leds);
/* release the led_sem semaphore */
gu_sem_post((G_RCB *) leds_sem);
}
/* print to STDOUT the task description and the processor ID */
gu_printf("%s by processor %d\n", ptcb->TCB_description, (int) GRTOS_CMD_PRC_ID);
/* print to the system JTAG-UART as through the consumer-producer queue */
gu_queue_printf(pqueue,"%s by processor %d\n", ptcb->TCB_description, (int) GRTOS_CMD_PRC_ID);
}
/******************************************************************************
* trigger enable function for jtag_uart write server *
******************************************************************************/
void *GRTOS_IRQ_WRITE_SERVER_ENABLE(void * pdata)
{
// Enable interrupt in JTAG UART component
IOWR_ALTERA_AVALON_JTAG_UART_CONTROL(JTAG_UART_0_BASE, 0x00000002);
return ((void *) 0);
}
/******************************************************************************
* trigger disable function for jtag_uart write server *
******************************************************************************/
void *GRTOS_IRQ_WRITE_SERVER_DISABLE(void *pdata)
{
// Disable interrupt in JTAG UART component
IOWR_ALTERA_AVALON_JTAG_UART_CONTROL(JTAG_UART_0_BASE, 0x00000000);
return ((void *) 0);
}
/******************************************************************************
* jtag_uart write server task *
******************************************************************************/
void GRTOS_IRQ_WRITE_SERVER(void *pdata)
{
struct jtag_uart_info * jtag_info = (struct jtag_uart_info *) pdata;
/* Get a buffer to store the messaje to send to the jtag-uart */
void *mem = malloc(jtag_info->buffer_length);
char *buffer_msg = (char *) mem;
int size;
int spc_fifo;
int index_msg;
/* Get the pointer to the TCB of the current task */
GS_TCB *ptcb = gu_PCB_GetCurrentTCB();
/* Define this task as an ISR associated to trigger jtag_uart_0 */
gu_trigger_register_task(ptcb, (G_INT32) jtag_info->trigger);
while (1) {
/* receive next message from producer/consumer queue */
size = gu_queue_receive(jtag_info->pqueue, (void *) buffer_msg,
jtag_info->buffer_length);
/* Iterate for each character of the message, but the ending '\0' */
index_msg = 0;
while (index_msg < size-1)
{
/* Get the available space in jtag uart buffer */
spc_fifo = (((G_INT32) IORD_ALTERA_AVALON_JTAG_UART_CONTROL(jtag_info->jtag_base))\
& ((G_INT32) ALTERA_AVALON_JTAG_UART_CONTROL_WSPACE_MSK )) >> \
ALTERA_AVALON_JTAG_UART_CONTROL_WSPACE_OFST;
/* Send as many characters up to the end of message or as possible to jtag uart */
while (spc_fifo > 0 && index_msg < size-1)
{
/* write next message character to jtag uart output fifo */
IOWR_ALTERA_AVALON_JTAG_UART_DATA(jtag_info->jtag_base, buffer_msg[index_msg]);
/* Update iteration variables */
index_msg++;
spc_fifo--;
}
/* There exist more character to send, so wait to next trigger from jtag device */
if (index_msg < size-1) {
gu_trigger_wait();
}
}
};
}
/******************************************************************************
* This function is the ISR for the jtag uart named "jtag_uart_0" *
******************************************************************************/
void jtag_irq_manager(int pdata)
{
/* Read the state of interrupt flags from the JTAG UART status register */
G_INT32 uart_ctrl_register = (G_INT32) IORD_ALTERA_AVALON_JTAG_UART_CONTROL(JTAG_UART_0_BASE);
/* Get the state of the write interrupt (output buffer empty) */
G_INT32 write_interrupt = (uart_ctrl_register & \
((G_INT32) ALTERA_AVALON_JTAG_UART_CONTROL_WI_MSK )) >> \
ALTERA_AVALON_JTAG_UART_CONTROL_WI_OFST;
/* Get the state of the read interrupt (input buffer full) */
G_INT32 read_interrupt = (uart_ctrl_register & \
((G_INT32) ALTERA_AVALON_JTAG_UART_CONTROL_RI_MSK )) >> \
ALTERA_AVALON_JTAG_UART_CONTROL_RI_OFST;
/* If write interrupt is on, the release the output server trigger */
if (write_interrupt == (G_INT32) 1) gu_trigger_release ((int) jtag_uart_0);
/* Read ISR should be defined for input reading */
}
/******************************************************************************
* main code where system is configured *
******************************************************************************/
int main(void)
{
/* ################################################### */
/* main variable definition */
/* ################################################### */
GS_TCB *ptcb;
GS_LCB *plcb;
int i, j;
/* Create a semaphore for LEDS */
leds_sem = gu_sem_create((int) 1);
/* Create a message queue for JTAG-UART jtag_uart_0 */
pqueue = gu_queue_create();
struct task_information *ptask_info; // pointer to task information structure
for (j = 0; j < ((G_INT32) number_FP_lists + (G_INT32) number_EDF_lists); j++)
{
if (j < number_FP_lists) {
plcb = gu_Get_LCB((G_INT32) GS_LCBType_FP);
} else {
plcb = gu_Get_LCB((G_INT32) GS_LCBType_EDF);
}
// Assigning all the processors to the list
for (i = 1; i <= G_NUMBER_OF_PCB; i++) {
gu_LCB_Associate_PCB((GS_LCB *) plcb,
(G_INT32) i,
(G_INT32) (j+i+G_NUMBER_OF_PCB-1)%G_NUMBER_OF_PCB);
}
/* Create all the tasks using the same generic task code */
for (i=0; i < tasks_per_list; i++)
{
/* Get structure to hold task information */
ptask_info = malloc(sizeof(struct task_information));
/* assigned an consecutive task index */
ptask_info->task_index = (j * tasks_per_list) + i;
/* Create a new Task */
ptcb = gu_GetTask((void *) task_generic, // Pointer to the task code
(void *) ptask_info, // Pointer to the argument of first call
"task %d", ptask_info->task_index);
if (ptcb != (void *) 0) { /* Task was created successfully */
/* Assigning the task to the scheduling list plcb */
gu_SetTaskList(ptcb,(struct gs_lcb *) plcb);
/* Set the task as periodic */
gu_SetTaskType(ptcb, G_TCBType_PERIODIC);
/* Set the task priority equal to the task index plus one */
gu_SetTaskReadyPriority(ptcb, (long long) ptask_info->task_index+1);
gu_SetTaskRunPriority(ptcb,(long long) ptask_info->task_index+1);
/* Set the period and deadline equal to the task index plus one in seconds */
gu_SetTaskDeadline(ptcb, 0, 0, 1, ptask_info->task_index * 300);
gu_SetTaskPeriod(ptcb, 0, 0, 1, ptask_info->task_index * 300);
/* Set the task with abort when deadline */
gu_SetTaskAbortwhenDeadline(ptcb, 0);
/* Start execution of task with an offset equal to five seconds */
gu_StartTaskwithOffset(ptcb, 0, 0, 5, 0);
gu_signal_create(G_SCBType_TCB_ABORTED, 0, (void *) ptcb,
(void *) sig_aborted_task_generic,
(void *) i); // Abort when deadline
} else {
gu_printf("Error when creating task");
while(1);
}
}
}
/* Create a trigger for JTAG UART writting */
/* -1 because it is not associated with hardware interrupt */
jtag_uart_0 = gu_trigger_create(-1);
/* create a jtag_uart_info structure to store the information for output server */
struct jtag_uart_info *jtag_info = \
(struct jtag_uart_info *) malloc(sizeof(struct jtag_uart_info));
/* set the parameter for jtag output server configuration.
The pointer to this structure is pass as reference to the task */
jtag_info->pqueue = pqueue;
jtag_info->trigger = (unsigned int) jtag_uart_0;
jtag_info->buffer_length = 100;
jtag_info->jtag_base = JTAG_UART_0_BASE;
/* Creatse task for jtag output server */
ptcb = gu_GetTask((void *) GRTOS_IRQ_WRITE_SERVER,
(void *) jtag_info,
"GRTOS_IRQ_WRITE_SERVER");
if (ptcb != (void *) 0)
{
gu_SetTaskType(ptcb, G_TCBType_UCOS);
gu_SetTaskReadyPriority(ptcb, 0);
gu_SetTaskRunPriority(ptcb, 0);
gu_SetTaskDeadline(ptcb, 0, 0, 1, 1);
gu_SetTaskPeriod(ptcb, 0, 0, 1, 1);
gu_SetTaskAbortwhenDeadline(ptcb, 0);
/* Start execution of task with an offset */
gu_StartTaskwithOffset(ptcb, 0, 0, 5, 0);
}
/* suscribe task as consumer to the consumer/producer queue */
gu_queue_subscribe(ptcb, pqueue);
/* Set the jtag output server hooks */
gu_trigger_enable_hook((int) jtag_uart_0,
(void *) GRTOS_IRQ_WRITE_SERVER_ENABLE, (void *) NULL);
gu_trigger_disable_hook((int) jtag_uart_0,
(void *) GRTOS_IRQ_WRITE_SERVER_DISABLE, (void *) NULL);
// #################################
/* Create a ISR task for jtag uart hardware interrupt */
ptcb = gu_GetTask((void *) jtag_irq_manager,
(void *) 5,
"jtag_irq_manager");
if (ptcb != (void *) 0)
{
gu_SetTaskType(ptcb, G_TCBType_UCOS);
gu_SetTaskReadyPriority(ptcb, 0);
gu_SetTaskRunPriority(ptcb, 0);
gu_SetTaskDeadline(ptcb, 0, 0, 1, 1);
gu_SetTaskPeriod(ptcb, 0, 0, 1, 1);
gu_SetTaskAbortwhenDeadline(ptcb, 0);
}
/* Associate the task to be triggered with the jtag uart interrupt */
gu_trigger_register_task(ptcb, (int) JTAG_UART_0_IRQ);
gu_trigger_enable((int) JTAG_UART_0_IRQ);
return (0);
}