IXP Tools Tutorial

The purpose of this tutorial is to acquaint you with the IXA SDK version 3.1 (and 3.5)  integrated development environment (IDE) and simulator. You probably have experience with other IDEs, and therefore know about project-oriented development, but the IXA specifics are likely to be new.

Creating and Building a Project

We first must create a project.

Creating a project

  1. Click "File->New Project..."
  2. Enter "ixp_example" as "Project Name"
  3. Choose a "Location" for the project
  4. Under "Select chip type", select "IXP2400" and choose revision B0.
  5. Click "Rename" and name the chip "CSE526_2400"
  6. Click "OK"
Next, we will add a source file and configure the IDE to compile, link and build our target correctly.

Adding and compiling a microengine C source file

  1. Click "File->New"
  2. Select "C Source File", click OK
  3. Copy and paste the following insertion sort code (it is important to copy and paste as specific line numbers are later referenced):
    void isort(_declspec(shared sram) int *A, int l, int r) {
      int i,j;
      int tmp;
      for (i=l; i<=r; i++) {
        //if A[l] > A[i], swap them
        if (A[i] < A[l]) {
          tmp = A[l];
          A[l] = A[i];
          A[i] = tmp;
        }
      }
      for (i=l+1; i<=r; i++) {
        j = i;
        tmp = A[i];
        while (tmp < A[j-1]) {
          A[j] = A[j-1];
          j--;
        }
        A[j] = tmp;
      }
    }


    void main () {

        _declspec(shared sram) int data[] = {10,9,8,7,6,5,4,3,2,1};

        isort(data, 0, sizeof(data)/sizeof(int)-1);

    }
    void exit (unsigned int arg) {
    }
  4. Click File->Save As..., and save the file in your project directory with the name "sample.c".
  5. Click "Project->Insert Compiler Source Files..."
  6. Select "sample.c", click Insert. Your source file is now part of the project, and is listed under the "Compiler Source Files" on the right hand side of the window (the "FileView" tab).

Configuring include directories

  1. Click Build->Settings
  2. Click the "General" tab
  3. In the Compiler include directories box, add the %IXARoot%\MicroengineC\include directory. In the Urbauer lab, %IXARoot% is C:\IXA_SDK_3.1.
  4. Click "OK"
In addition to telling the compiler where to find our source files, we must also instruct the compiler and linker to map our program onto the appropriate microengine(s) and thread context(s). This is achieved via ".list" files -- source files within a project get compiled into one or more .list files, and each microengine is assigned zero or one .list files. All the list files, and their assignments to microengines, are used to generate the binary image which is handed to the XScale processor, which in turn populates the microengine control stores.

Configuring compiler settings

  1. Click Build->Settings
  2. Click the "Compiler" tab
  3. Click "New .list file...", name it "example_uc.list".
  4. Click "Insert List File".
  5. Use "Choose source files..." to add sample.c to the list of files to compile.
  6. In the "Contexts" group, set "Number" to 1. This option controls the number of thread contexts to be initialized on the target microengine.
  7. Note that all parameters in this dialog box influence the command line parameters shown in the "Parameters used to invoke compiler" box at the bottom. All of these parameters can be specified in scripts as well (a fact we will eventually exploit).
  8. Click "OK"

Configuring linker settings

  1. Click Build->Settings
  2. Click the "Linker" tab
  3. Make sure the "Chip" is CSE526_2400
  4. In the "Microengine 0.0" box, select "example_uc.list"
  5. Click "OK"

Building the project

  1. Click "Build->Build" or press F7.
  2. Examine the build results in the status frame at the bottom of the window. There should be no errors or warnings.
The project is now built, and you are ready to run the program in the simulator. However, in addition to microengine C source files, you can also (and in all likelihood will) use assembler source files.

Adding an assembler source file

  1. Click "File->New"
  2. Select "Source File", press OK
  3. Copy and paste the following code (adapted from IXP2XXX Programming, Johnson and Kunze):
    // This gives us some basic macros
    #include "stdmac.uc"
    #include "xbuf.uc"

    #macro reverse_array_simple[in_old, in_new, in_size]
    .begin
        .reg entries_left current_old_entry
        .reg current_new_entry
        .reg $array_data

        // Set up a count of remaining entries and SRAM
        // pointers
        move(entries_left, in_size)
        // Set up a pointer to the current array entry in
        // the old array
        move(current_old_entry, in_old)

        // Set up a pointer to the current array entry in
        // the new array.  This works out to be
        // in_new + (in_size * 4) - 4, because the code
        // starts at the back of the new array, and because
        // it operates with 4-byte longwords.
        move(current_new_entry, in_new)
        add_shf_left(current_new_entry, current_new_entry,
                     in_size, 2)
        sub(current_new_entry, current_new_entry, 4)

        // Now loop one longword at a time and copy the
        // array
        .while (entries_left != 0)
            // Need a signal for SRAM accesses
            .sig sram_sig
            // Read the old array from SRAM
            sram[read, $array_data, current_old_entry,
                 0, 1], ctx_swap[sram_sig]
           
            // Move the data from the read side of the
            // transfer register to the write side
            move($array_data, $array_data)

            // Write the new array
            sram[write, $array_data, current_new_entry,
                 0, 1], ctx_swap[sram_sig]
           
            // Update the counter and the pointers
            sub(entries_left, entries_left, 1)
            add(current_old_entry, current_old_entry, 4)
            sub(current_new_entry, current_new_entry, 4)
        .endw
    .end
    #endm

    // Allocate space for the initial array and the new array
    .global_mem old_array     SRAM0 40
    .global_mem new_array     SRAM0 40
    .global_mem new_array_adv SRAM0 40

    // Initialize the arrays
    .init old_array        1 2 3 4 5 6 7 8 9 10
    .init new_array        0 0 0 0 0 0 0 0 0 0

    // This is the main part of the program
    .begin
        // Only do this on one thread
        .if(ctx() == 0)
            ctx_arb[voluntary]
            // Call the reverse_array macro
            start_simple_reverse#:
            reverse_array_simple(old_array, new_array, 10)
            end_simple_reverse#:
        .endif
        ctx_arb[kill]
    .end
    nop

  4. Click "File->Save As", and name your file "sample.uc".
  5. Click "Project->Insert Assembler Source Files"
  6. Select "sample.uc", click "Insert". The file is now part of the project.

Configuring assembler include directories

  1. Click "Build->Settings", then click the "General" tab
  2. Add "%IXARoot%\src\library\dataplane_library\microcode\" to the Assembler include directories box.

Configuring assembler settings

  1. Click the "Assembler" tab
  2. Click "New" to create a new .list file named "example_ua.list".
  3. Select "sample.uc" as the "Root File"

Configuring linker settings

  1. Click the "Linker" tab
  2. In the Microengine 0:0 box, select "<none>"
  3. In the Microengine 0:1 box, select "example_ua.list"
  4. Click OK
As currently configured, the project is ready to build and execute the assembler source file. For the rest of this tutorial, however, we will simulated the C source file. So, change the linker settings to load "example_uc.list" in Microengine 0:0, and "<none>" in Microengine 0:1.

Simulating Your Program

Starting simulation

  1. Click "Debug->Start Debugging", or click the six-legged bug icon, or press F12.

Opening debug windows

  1. You won't see much unless you open some debug windows (which may or may not already be open.) Use "View->Debug Windows" to open "Data Watch", "Memory Watch" and "Thread History".
  2. In the "Thread History" window, expand the "Microengine 0:0" list by clicking the + box on the left. Double click "Thread0" to open a simulator-annotated, "active" version of your source code window.
  3. In this new source code window, in the gray margin to the left of "void main () {", notice the six overlapped triangles. Each triangle represents one of the 6 processor pipeline stages in the microengine. When simulating, only one of these will be highlighted; by default, the execute stage (the fifth arrow) at the given PC address will be highlighted (this is configurable via "Simulation->Options").
  4. At the top toolbar of this window, there is a button with green and yellow pages, with arrows pointing between them. This button will toggle the view between the C source and the compile assembly language. Toggle the view and peruse the assembly. Toggle back to the C view before continuing.

Setting breakpoints

  1. In the active source code window, place your mouse on line 4 (line numbers are shown at the bottom-right of the IDE window) at the start of the first for loop, right-click and select "Insert/Remove Breakpoint". You can also place the cursor at that line and click the white hand icon on the toolbar.
  2. This breakpoint will work more or less like any other you have used before. When simulation reaches that point in the code, it will stop and pass control to you.

Watching data values

  1. Press the traffic light icon (currently green) to start simulation.
  2. Once your breakpoint is reached, place your mouse over the variable 'A' (e.g., on line 8, A[l] = A[i];). The GUI is fickle, you do need to be centered on 'A' to have that option. Right-click and choose "Set Data Watch for 'A'". An entry for A now appears in the data watch window (value should be 0x28). A is a pointer, so its value is a memory location,  an SRAM memory location in particular.
  3. To view the contents of SRAM that A points to, add "sram[0x28]" to the SRAM section of the Memory Watch window (double click an empty entry in the left-most column, and type in that string). You will get an expected message about adjusting for longword alignment. You can also view memory ranges here, add "sram[40:80]" to view the entire array (each address refers to a byte address, and each integer in the array occupies 4 bytes).
  4. Examine the array. Is something missing? Is this a bug? If the answer is not clear to you, we can examine the Thread History, and it will become clear.

Thread history

  1. Click "View->Debug Windows->History". You will likely need to enlarge this window.
  2. This view presents an illustrated and annotated history of each threads activities through time (the cycles are recorded along the top). If you click the "Legend..." button on the tool bar, you will see how to interpret the figure. The blue solid bar indicates an idle thread, a black solid bar indicates thread execution, and so on.
    Beneath this main bar, you can see a white line -- this, as the legend indicates, signifies an SRAM event. Place your mouse over the last white segment (it begins near cycle 560) and leave it there; a hint window will appear and show you the details of the SRAM event. As you can see, this event is an SRAM write, of two 2 longwords (i.e., 2 integers) starting at address 0x48 (which is the starting address of our first missing array element.)
  3. The mystery of the missing data is this: the SRAM completion signal is sent to the microengine a few cycles before the SRAM unit actually updates memory. So, if you click the "Step" icon 15 times or so, you should see the last two array elements appear.
  4. To run the sort to completion, clear all breakpoints, and then set a final breakpoint on line 32 inside the exit function. Then continue by clicking the green traffic light. When the breakpoint is reached, you will find a mostly sorted array. If you step another 10 cycles or so (for the same reasons as above), the final SRAM event should complete, and the array will be sorted.
  5. Before stopping this simulation, we will take a look at some statistics.

Measuring Performance

  1. Click "Simulation->Performance Statistics..."
  2. The "Summary" tab tells us that Microengine 0:0 (the one we were using) as only active 4.6% of the time.
  3. The "Microengine" tab gives more detail, showing that cycles are accounted for as follows: 4.6% executing, 2.4% aborting (i.e., pipeline squashes), 0% stalling, and 93% idle (i.e., waiting for memory).
  4. The "All" tab gives access to all statistics recorded by the simulator. For example, "SRAM Channel #0 Utilization" was 4.1%, meaning that the channel was idle during 95.9% of the SRAM unit simulation cycles.

Other Documentation

There is considerably more information to be found in the IXA SDK documentation. Many of these documents are linked from the "InfoView" tab on the right-hand side of the screen.


This document was created by Patrick Crowley (pcrowley@cse.wustl.edu) and last modified on 2/9/2004. All comments, suggestions and corrections are welcome. Much of this tutorial was inspired by the Johnson and Kunze IXP programming books, where I first learned to use the IXA SDK tools.