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
- Click "File->New Project..."
- Enter "ixp_example" as "Project Name"
- Choose a "Location" for the project
- Under "Select chip type", select "IXP2400" and choose revision B0.
- Click "Rename" and name the chip "CSE526_2400"
- 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
- Click "File->New"
- Select "C Source File", click OK
- 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) {
}
- Click File->Save As..., and save the file in your project
directory with the name "sample.c".
- Click "Project->Insert Compiler Source Files..."
- 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
- Click Build->Settings
- Click the "General" tab
- In the Compiler include directories box, add the
%IXARoot%\MicroengineC\include directory. In the Urbauer lab, %IXARoot%
is C:\IXA_SDK_3.1.
- 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
- Click Build->Settings
- Click the "Compiler" tab
- Click "New .list file...", name it "example_uc.list".
- Click "Insert List File".
- Use "Choose source files..." to add sample.c to the list of
files to compile.
- In the "Contexts" group, set "Number" to 1. This option controls
the number of thread contexts to be initialized on the target
microengine.
- 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).
- Click "OK"
Configuring linker
settings
- Click Build->Settings
- Click the "Linker" tab
- Make sure the "Chip" is CSE526_2400
- In the "Microengine 0.0" box, select "example_uc.list"
- Click "OK"
Building the project
- Click "Build->Build" or press F7.
- 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
- Click "File->New"
- Select "Source File", press OK
- 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
- Click "File->Save As", and name your file "sample.uc".
- Click "Project->Insert Assembler Source Files"
- Select "sample.uc", click "Insert". The file is now part of the
project.
Configuring assembler include directories
- Click "Build->Settings", then click the "General" tab
- Add "%IXARoot%\src\library\dataplane_library\microcode\" to the
Assembler include directories box.
Configuring assembler settings
- Click the "Assembler" tab
- Click "New" to create a new .list file named "example_ua.list".
- Select "sample.uc" as the "Root File"
Configuring linker settings
- Click the "Linker" tab
- In the Microengine 0:0 box, select "<none>"
- In the Microengine 0:1 box, select "example_ua.list"
- 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
- Click "Debug->Start Debugging", or click the six-legged bug
icon, or press F12.
Opening debug windows
- 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".
- 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.
- 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").
- 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
- 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.
- 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
- Press the traffic light icon (currently green) to start
simulation.
- 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.
- 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).
- 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
- Click "View->Debug Windows->History". You will likely need
to enlarge this window.
- 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.)
- 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.
- 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.
- Before stopping this simulation, we will take a look at some
statistics.
Measuring Performance
- Click "Simulation->Performance Statistics..."
- The "Summary" tab tells us that Microengine 0:0 (the one we were
using) as only active 4.6% of the time.
- 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).
- 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.