Section 5.3. The Software Test Bench

5.3. The Software Test Bench

To verify the correct function of this FIR filter prior to attempting any optimizations, we have written a simple software test bench that reads coefficient and source data from files, using the standard C fopen and related functions, and presents these values to the FIR filter process during desktop simulation. The producer and consumer processes representing the software test bench are shown in Figures 5-2 and 5-3, respectively. The configuration subroutine that ties these two test bench processes to the FIR filter process is presented in Figure 5-4.

How fast will it run?

What you see in Figure 5-1 is all that is required to define the operation of a generic 51-tap FIR filter.

What you might be asking yourself at this time (and any time you create a new Impulse C process of your own) is how the process will perform when generated as FPGA hardware. What kind of latencies and/or throughput can be expected? How large will the resulting logic be? These questions can be answered relatively quickly by doing some initial compilations with the C-to-hardware compiler and optimizer, which reports information on loop delays (measured as stages), pipeline latency and rate, and other information useful for determining cycle counts and a rough size of the generated logic. More precise information regarding performance can be obtained by performing hardware simulations (using an HDL simulator) on the generated outputs and by synthesizing the generated logic to obtain an FPGA netlist.

This design has some clear trade-offs that can be made either by doing some simple recoding (for example, by manually unrolling the inner code loops) or by using compiler pragmas provided with Impulse C to perform such unrolling automatically and, if desired, to generate a pipeline.

As written, this FIR filter will compile to produce a rather slow implementation of a FIR filter, but one that is reasonably compact in terms of the generated hardware. The filter will be slow (when measured in terms of clock cycles to complete one filter operation) because, by default, the Impulse C compiler will not attempt to unroll the inner code loops or pipeline the statements of the main processing loop.

Armed with your unique knowledge of the sytem requirements, you may choose to have these optimizations performed, and you might insert instructions to that effect in the code, in the form of compiler pragmas. You must, however, always remain cognizant of the likely results and learn how to apply such optimizations intelligently to balance size and speed. This balancing of goals is covered to some extent in Chapter 6 and is covered in much greater depth in Chapters 7 and 10.

Figure 5-2. Producer process for the FIR filter software test bench.
 void Producer(co_stream waveform_raw) {     FILE *coefFile, *inFile;    const char *CoefFileName = "coef.dat";    const char *FileName = "waveform.dat";    int32 coefValue, rawValue, coefcount = 0;    cosim_logwindow log = cosim_logwindow_create("Producer");    coefFile = fopen(CoefFileName, "r");    if ( coefFile == NULL ) {         fprintf(stderr, "Error opening file %s\n", CoefFileName);        exit(-1);    }    // Read and write the coefficient data...    co_stream_open(waveform_raw, O_WRONLY, INT_TYPE(32));    cosim_logwindow_write(log, "Sending coefficients...\n");    while (coefcount < TAPS) {      if (fscanf(coefFile,"%d",&coefValue) < 1) {          break;     }      coefcount++;     co_stream_write(waveform_raw, &coefValue, sizeof(int32));     cosim_logwindow_fwrite(log, "Coef: %x\n", coefValue);    }     fclose(coefFile);    inFile = fopen(FileName, "r");    if ( inFile == NULL ) {         fprintf(stderr, "Error opening file %s\n", FileName);        exit(-1);    }    // Now read and write the waveform data...    co_stream_open(waveform_raw, O_WRONLY, INT_TYPE(32));    cosim_logwindow_write(log, "Sending waveform...\n");    while (1) {       if (fscanf(inFile,"%d",&rawValue) < 1)          break;      co_stream_write(waveform_raw, &Value, sizeof(int32));      cosim_logwindow_fwrite(log, "Value: %x\n", rawValue);    }     cosim_logwindow_write(log, "Finished writing waveform.\n");    co_stream_close(waveform_raw);    fclose(inFile); } 

Figure 5-3. Consumer process and main function for the FIR filter software test bench.
 void Consumer(co_stream waveform_filtered) {     int32 waveformValue;    unsigned int waveformCount = 0;    const char * FileName = "results.dat";    FILE * outFile;    cosim_logwindow log = cosim_logwindow_create("Consumer");    outFile = fopen(FileName, "w");    if ( outFile == NULL ) {         fprintf(stderr, "Error opening file %s for writing\n",                   FileName);        exit(-1);    }     co_stream_open(waveform_filtered, O_RDONLY, INT_TYPE(32));    cosim_logwindow_write(log, "Consumer reading data...\n");    while ( co_stream_read(waveform_filtered, &Value,                   sizeof(int32)) == co_err_none ) {      fprintf(outFile, "%d\n", waveformValue);     cosim_logwindow_fwrite(log, "Value: %x\n", waveformValue);     waveformCount++;    }    cosim_logwindow_fwrite(log, "Consumer read %d datapoints.\n",                 waveformCount);    co_stream_close(waveform_filtered);    fclose(outFile); }  int main(int argc, char *argv[]) {     co_architecture my_arch;    void *param = NULL;    printf("FIR filter software test bench.\n");    my_arch = co_initialize(param);    co_execute(my_arch);    printf("\n\nTest complete.\n");    return(0); } 

Use the full power of C and C++ for test benches

There is no reason to limit yourself to ANSI C when writing Impulse C software test benches. If you wish, you can create a large, complex test bench using C++ and any number of useful class libraries, including the MinGW library, Microsoft MFC or ATL, or even SystemC. From an Impulse C processing standpoint, all that is required is that you maintain your co_initialize function, your configuration subroutine, and your hardware and software processes that are to be implemented in the actual target in distinct source files that can be analyzed by the Impulse C compiler. Also, be sure to restrict yourself to ANSI C syntax (or use ifdef statements as appropriate) to distinguish those portions that must be analyzed by the Impulse C hardware compiler.

The producer and consumer processes for this example are very much like the equivalent producer and consumer processes presented in Chapter 4, with the notable difference that they read test data from files (coef.dat and waveform.dat) and write the results of the test to file results.dat.

Because Impulse C is standard ANSI C (extended with C-compatible libraries), you can make use of the full power of C programming in your test bench. For example, you can do a direct, value-by-value comparison of the output values with a known-good set of results read by the test bench from another input file. For brevity, however, we have shown a minimal test bench that simply reads input values from one set of files and writes the filtered results to the output file indicated.

Let's look at the test bench processes (which are summarized in Figure 5-5) in more detail.

Figure 5-5. FIR filter test bench block diagram.

The Producer Process

The Producer process generates a stream of outputs representing (in sequence) the FIR filter coefficients and the data to be filtered (the input waveform). Starting at the top of this process, we find the following:

  • C declarations, including declarations of the two input files containing the sample coefficients and the waveform values. These values may have been generated by some other test-related software, or they may be actual sampled data from some kind of instrument. Whatever their source, the values in these files are formatted quite simply, as one value (in decimal integer format) per file line.

    Figure 5-4. Configuration subroutine for the FIR filter software test bench.
     extern void Producer(co_stream waveform_raw); extern void Consumer(co_stream waveform_filtered); void config_fir(void *arg) {     co_stream waveform_raw;    co_stream waveform_filtered;    co_process producer_process;    co_process fir_process;    co_process consumer_process;    IF_SIM(cosim_logwindow_init();)    waveform_raw = co_stream_create("waveform_raw",                      INT_TYPE(32), BUFSIZE);    waveform_filtered = co_stream_create("waveform_filtered",                      INT_TYPE(32), BUFSIZE);    producer_process = co_process_create("producer_process",                      (co_function)test_producer,                      1, waveform_raw);    fir_process = co_process_create("filter_process",                      (co_function)fir,                      2, waveform_raw, waveform_filtered);    consumer_process = co_process_create("consumer_process",                      (co_function)test_consumer,                      1, waveform_filtered);     // Assign FIR process to hardware element     co_process_config(fir_process, co_loc, "PE0");   }  co_architecture co_initialize(int param) {    return(co_architecture_create("fir_arch","Generic_VHDL",                      config_fir,(void *)param)); } 

  • A call to the cosim_logwindow_create function. This function call results in an Application Monitor window being created at runtime (during software simulation) and associated with the indicated name and local pointer (of type cosim_logwindow).

  • A call to the standard C fopen function, followed by a loop that iteratively reads the coefficient values from the file coef.dat. These values are written to the filter's input stream (which in the Producer process is called waveform_raw) using co_stream_write. Notice the use of cosim_logwindow_fwrite to pass formatted text messages to the previously opened log window. The formatting supported in this function is identical to the formatting available in the standard C fprintf function.

  • After all 51 coefficients have been read from the file and passed into the stream, the input file is closed (using fclose) but the stream is left open for the next step.

  • The second Producer loop (which appears after the second call to fopen to open the waveform.dat file) follows the same format and outputs the test waveform data to the filter via the waveform_raw data stream. This loop continues as long as data remains in the input file.

The Consumer Process

The Consumer process accepts outputs from the FIR filter on a stream named waveform_filtered and writes these values both to an output file (again using one value per line) and to a log window. Examining this process in more detail, we see the following:

  • A declaration of the output file (results.dat) and a call to the cosim_logwindow_create function.

  • A co_stream_open Impulse C function call followed by a main processing loop. This loop iteratively reads values from the waveform_filtered stream and writes these values both to the results.dat file and to the log window using cosim_logwindow_fwrite.

  • When the FIR filter stream has been closed, the Consumer function cleans up by closing the output file and writing a final count of the number of values read to the log window.

  • Following the Consumer process is a main function for this application. When combined with the configuration function and the co_initialize function, this represents a complete C program ready for compilation for the purpose of software (or "desktop") simulation.

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

    Similar book on Amazon © 2008-2017.
    If you may any questions please contact us: