IXP Tools Tutorial
The purpose of this tutorial is to acquaint you with the IXA SDK
(version 4.3.1) 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.
Obtaining and
Installing the IXP SDK
If you have a copy of the Johnson and Kunze book, the accompanying CD
has a copy of the SDK (version 3.1, I believe). Newer versions can be
be obtained from Intel's
website. While there is no charge, a "Customer Entitlement Request
Form" must be submitted; after a day or so, you should recieve the
download authorization code. Installation is self-explanatory; I highly
recommend the use of default installation directories.
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 the chip ...", select "IXP280X", "IXP2800" and
revision B1.
- Click "Rename" and name the chip "CSE561_2800"
- 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;
}
}
_declspec(shared sram) int data[] =
{10,9,8,7,6,5,4,3,2,1};
void main () {
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 Intel(R) C Compiler ..."
- 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 "Intel(R) C Compiler -explicit mode include directories" box, add the
%IXARoot%\microengineC\include directory.
- 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 "Intel(R) C 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 usage" group, set "Number of contexts" to 1
(leave "Mode" at 8). 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 CSE561_2800
- 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
simulate execution with 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 Status".
- In the "Thread Status" 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
"isort(...", 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
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 'data' within the body of function main(). The GUI is fickle, you do
need to be centered on 'data' to have that option. Right-click and
choose
"Set Data Watch for 'data'". An entry for the variable data now appears in the data
watch
window.
- You can also view this data by examining memory directly. To view
the contents of data in SRAM, add "sram[0x0]"
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[0:43]" to view the entire
array (each address refers to a byte address, and each integer in the
array occupies 4 bytes).
- Now clear all existing breakpoints. Place a new one at line 21
(the end of the isort function). Run the simulator until the breakpoint
is reached.
- Examine the data array. Is something wrong? 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
thread's 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 20510) 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 1 longword (i.e., 1
integer) starting at address 0x4; this is the starting address of
our missing array element. (Note that the addresses listed in the
"Memory Watch" debug window are decimal; you can switch to hex via the
right-click menu.)
- 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 the memory location. So, if you click the "Step" icon 40 times or
so, you should see the correct data value appear.
- To start over and run the sort to completion (don't do this now), clear all breakpoints, and then
set a final breakpoint on line 31 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 30
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 3.45% of the time. (These percentags can vary with
SDK version.)
- The "Microengine" tab gives more detail, showing that cycles are
accounted for as follows: 3.45% executing, 1.91% aborting (i.e., pipeline
squashes), 0% stalling, and 94.64% 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 6.59% (4.43% read, 2.16% write), meaning
that the channel was idle during 93.41% 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@wustl.edu) and
last modified on 1/34/2008. 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.