Section 4.3. A Minimal Impulse C Program


4.3. A Minimal Impulse C Program

In their classic book C Programming Language, authors Brian Kernighan and Dennis Ritchie began by presenting a simple example called "Hello World." In honor of Kernighan and Ritchie's book (which was first published in 1978 by Prentice Hall) we now present a minimal Impulse C application that we call "Hello FPGA." This will allow us to demonstrate how data moves in and out of Impulse C processes, as well as providing a quick introduction to the Impulse C library.

If you are familiar with the "Hello World" example presented by Kernighan and Ritchie, you may initially be dismayed at the complexity of this purportedly simple example. Don't worry, though; the concepts are straightforward, and you can use this basic example as a template for any number of Impulse C examples, large and small.

An important aspect of Impulse C highlighted by this simple example is the creation of a software test bench, which will provide you with your first look at how parallelism is expressed as well as demonstrating how to test streams-oriented hardware and software processes. The relationship of the software test bench to the hardware process is diagrammed in Figure 4-2. As shown in the diagram, the goal of this example is to demonstrate moving data from the software test bench to the hardware process and back. (We'll cover the concept of software test benches more fully in later chapters.)

Figure 4-2. HelloFPGA hardware process and software test bench.


The "Hello FPGA" example includes two source files; let's examine them in detail.

The Software Source File: HelloFPGA_sw.c

The file HelloFPGA_sw.c (see Figure 4-3) contains those portions of the application that represent software running on a traditional processor, which may be an embedded processor that is part of the target system or (as in this case) may be used only as a software test bench during desktop simulation. This part of the application is written using standard C, with few (if any) constraints being placed on the kind of C statements used.

Examining this source file section by section and starting at the top, we find:

  • A comment header, which in this example is limited to one line for the sake of brevity. As in traditional C programming, you should make extensive use of comments for internal documentation as well as for revision control. This is particularly important if your application makes certain assumptions about the target platform or relies on specific tool settings.

  • #include statements referencing the Impulse C co.h file as well as the standard C library stdio.h:

     #include "co.h" #include <stdio.h> 

    The co.h file (which is located in the Impulse C installation directories in the /include subdirectory) contains declarations and macros representing the Impulse C function library. Functions in this library are prefixed with the co_ character combination.

  • An extern declaration for the function co_initialize:

     extern co_architecture co_initialize(int param); 

    The co_initialize function is a special function that you must include in your application, typically in the same file containing your application's configuration function (which we will describe in a moment). The extern declaration for co_initialize in HelloFPGA_sw.c is required so that co_initialize may be referenced in the application's main function.

Figure 4-3. HelloFPGA software test bench functions (HelloFPGA_sw.c).
    // HelloWorld_sw.c: Software processes to be executed on the CPU. #include "co.h" #include <stdio.h> extern co_architecture co_initialize(int param); void Producer(co_stream output_stream) {   int32 i;   static char HelloWorldString[] = "Hello FPGA!";   char *p;   co_stream_open(output_stream, O_WRONLY, CHAR_TYPE);   p = HelloWorldString;   while (*p) {     printf("Producer writing output_stream with: %c\ n", *p);     co_stream_write(output_stream, p, sizeof(char));     p++;   }   co_stream_close(output_stream); } void Consumer(co_stream input_stream) {   char c;   co_stream_open(input_stream, O_RDONLY, CHAR_TYPE);   while (co_stream_read(input_stream, &c, sizeof(char)) == co_err_none ) {      printf("Consumer read %c from input stream\ n", c);   }   co_stream_close(input_stream); } int main(int argc, char *argv[]) {     int param = 0;     co_architecture my_arch;     printf("HelloFPGA starting...\ n");     my_arch = co_initialize(param);     co_execute(my_arch);     printf("HelloFPGA complete.\ n");     return(0); } 

  • A Producer process run function, which has been described as a C function with a void return value:

     void Producer(co_stream output_stream) { 

    This process run function represents the input side of a software test bench. The code in this process generates some test data (in this case, a brief stream of characters spelling out "Hello FPGA!") to test the hardware module that is described in HelloFPGA_hw.c. The process itself consists of some declarations and an inner code loop (a while loop) that iterates through the input string and writes characters to a stream declared as output_stream. Note the use of co_stream_open, co_stream_write, and co_stream_close in this process:

     co_stream_open(output_stream, O_WRONLY, CHAR_TYPE); p = HelloWorldString; while (*p) {      printf("Producer writing output_stream with: %c\ n", *p);      co_stream_write(output_stream, p, sizeof(char));      p++; } co_stream_close(output_stream); 

    As you will see, Impulse C separates the definition of a process run function such as this one from its actual implementation, or instantiation, in hardware or software. Process run functions (which are technically procedures, not functions, because they have no return value) are described in more detail later in this chapter.

  • A Consumer process run function, which has also been described as a C function with a void return value:

     void Consumer(co_stream input_stream) { 

    This process represents the output side of our software test bench. Like the Producer process, this process interacts with other parts of the application (in particular, the hardware module being tested) via a data stream. In this case the data stream is incoming rather than outgoing, as indicated by the O_RDONLY mode specified in the call to co_stream_open:

     co_stream_open(input_stream, O_RDONLY, CHAR_TYPE); 

    As in the Producer process, this process includes an inner code loop (which is again a while loop) that causes characters to be read from the input stream (which is declared here as a co_stream argument named input_stream) as long as characters are being generated by the module under test:

     co_stream_open(input_stream, O_RDONLY, CHAR_TYPE); while (co_stream_read(input_stream, &c, sizeof(char)) == co_err_none ) {   printf("Consumer read %c from input stream\ n", c); } co_stream_close(input_stream); 

    The consumer process uses a simple printf statement to display the test results to the console.

  • A main function. The main function simply displays console messages (using printf) and, most importantly, launches the application using calls to the Impulse C functions co_initialize and co_execute. The co_initialize function is a required part of every Impulse C application, and is described in a later section. The co_execute function is an Impulse C library function that launches the application and its constituent processes:

     int main(int argc, char *argv[]) {    int param = 0;    co_architecture my_arch;    printf("HelloFPGA starting...\ n");    my_arch = co_initialize(param);    co_execute(my_arch);    printf("HelloFPGA complete.\ n");    return(0); } 

The Hardware Source File: HelloFPGA_hw.c

The file HelloFPGA_hw.c (see Figure 4-4) contains those portions of the application that represent hardware running on the FPGA. This file (and any other files containing additional hardware processes) is analyzed by the Impulse C compiler for the purpose of hardware generation. Examining this source file section by section and starting at the top, we find:

  • A comment header.

  • An include statement referencing the co.h include file containing Impulse C macros and function declarations:

     #include "co.h" 

    Figure 4-4. HelloFPGA hardware process and configuration function (HelloFPGA_hw.c).
     // HelloWorld_hw.c: Hardware processes and configuration. #include "co.h" extern void Consumer(co_stream input_stream); extern void Producer(co_stream output_stream); // Hardware process void DoHello(co_stream input_stream, co_stream output_stream) {   char c;   co_stream_open(input_stream, O_RDONLY, CHAR_TYPE);   co_stream_open(output_stream, O_WRONLY, CHAR_TYPE);   while (co_stream_read(input_stream, &c, sizeof(char)) ==   co_err_none ) {     // Do something with the data stream here     co_stream_write(output_stream,&c,sizeof(char));   }   co_stream_close(input_stream);   co_stream_close(output_stream); } void config_hello(void *arg) {  // Configuration function   co_stream s1,s2;   co_process producer, consumer;   co_process hello;   s1 = co_stream_create("Stream1", CHAR_TYPE, 2);   s2 = co_stream_create("Stream2", CHAR_TYPE, 2);   producer = co_process_create("Producer",                 (co_function) Producer, 1, s1);   hello = co_process_create("DoHello",                 (co_function) DoHello, 2, s1, s2);   consumer = co_process_create("Consumer",                 (co_function) Consumer, 1, s2);   co_process_config(hello, co_loc, "PE0");  // Assign to PE0 } co_architecture co_initialize(int param) {   return(co_architecture_create("HelloArch","generic",                                config_hello,(void *)param)); } 

  • Extern declarations for the Producer and Consumer processes defined in HelloFPGA_sw.c:

     extern void Consumer(co_stream input_stream); extern void Producer(co_stream output_stream); 

    These are required here because the configuration function (described in a moment) must reference these processes.

  • A process run function named DoHello:

     void DoHello(co_stream input_stream, co_stream output_stream) { 

    This process run function accepts as its arguments two stream objects. One of these streams (input_stream) represents an incoming stream of 8-bit character values. This stream will be connected to the single output of the Producer process. The other stream (output_stream) represents the processed data, which is also composed of 8-bit character values. This stream will be connected to the input stream of the Consumer process. As in the Consumer process, this process uses an inner code loop (represented by a while loop) that operates on the input stream for as long as data appears on the inputs. The stream operations are described using the Impulse C functions co_stream_open, co_stream_read, co_stream_write, and co_stream_close:

     co_stream_open(input_stream, O_RDONLY, CHAR_TYPE); co_stream_open(output_stream, O_WRONLY, CHAR_TYPE); while (co_stream_read(input_stream, &c, sizeof(char)) == co_err_none ){   // Do something with the data stream here   co_stream_write(output_stream,&c,sizeof(char)); } co_stream_close(input_stream); co_stream_close(output_stream); 

    The co_process_create function call detailed later in this section creates one instance of this process and indicates the name of that process instance, as well as defining the external stream connections. Note that this process does not actually do anything with the incoming data; instead, each value appearing on the input stream is immediately written (via co_stream_write) to the output stream.

  • A configuration subroutine named config_hello:

     void config_hello(void *arg){  // Configuration function 

    The configuration subroutine is a required part of every Impulse C application. The configuration subroutine defines the structure of your application in terms of the processes that are to be used and how they are to be interconnected. This configuration subroutine uses the co_process_create function to create three process instances, which are given the names producer, consumer, and hello:

     s1 = co_stream_create("Stream1", CHAR_TYPE, 2); s2 = co_stream_create("Stream2", CHAR_TYPE, 2); producer = co_process_create("Producer", (co_function) Producer, 1, s1); hello = co_process_create("DoHello", (co_function) DoHello, 2, s1, s2); consumer = co_process_create("Consumer", (co_function) Consumer, 1, s2); 

    We have also used the function co_stream_create to create the two streams that will carry data from producer to hello and from hello to consumer, respectively. And lastly, we have used the process configuration function co_process_config to specify that one of the three processes, hello, is to be assigned to a hardware resource called PE0, which represents the target FPGA:

     co_process_config(hello, co_loc, "PE0");  // Assign to PE0 

    This single source file line, the call to co_process_config, is the only clue we have in this application that the target of compilation is an FPGA.

  • After the configuration subroutine we find a definition for the co_initialize function that was referenced from our main function (in file HelloFPGA_sw.c):

     co_architecture co_initialize(int param) {   return(co_architecture_create("HelloArch","generic",                                config_hello,(void *)param)); } 

    As with the configuration subroutine, one such function (named co_initialize) is required in every Impulse C application. Within this function there is a single call to function co_architecture_create, which associates the configuration function we previously defined with the application's target architecture, which in this case is a generic hardware/software platform. (The specific programmable platform that will be the target of compilation is defined outside of the application source files, as part of the hardware compiler settings.)

You have now seen a complete Impulse C application that includes both a hardware process (DoHello) and two software test bench processes (Consumer and Producer). We have also examined how these three processes (which are more formally defined as process run functions) are instantiated (to form three process instances) and are interconnected by data channels called streams. The following sections describe these various Impulse C elements in more detail.



    Practical FPGA Programming in C
    Practical FPGA Programming in C
    ISBN: 0131543180
    EAN: 2147483647
    Year: 2005
    Pages: 208

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net