Embedded Real-Time Systems

Yüklə 80.78 Kb.
ölçüsü80.78 Kb.


Embedded Real-Time Systems

Lab 1: System Call Concepts, Loading Executables, and Code Optimization

Table of Contents

1 Introduction 1

1.1 Goals 1

1.2 Overview 1

2 Development Environment 2

3 Part I: Implementing New SWIs 3

3.1 Introduction 3

3.2 SWI Review 3

3.3 The Installer 1

3.4 The Entry Routine 2

3.5 The Body of the Handler 4

3.6 What to Turn In 4

4 Part II: Loader and Program Launcher 6

4.1 Introduction 6

4.2 Loading ARM Executable Files 6

4.3 What to Turn In 1

5 Part III: JPEG Code Optimization 2

5.1 What to Turn In 2

6 18-349 Project 1 Grading Sheet 3

Related Web Sites




Terminology and Acronyms





This lab provides an introduction to embedded systems programming. The goals of Lab 1 are:

  • Learn how to use the 80200 Big Endian Evaluation Platform tools such as the CodeWarrior IDE and the SDK (which includes the compiler, assembler, and linker), and the simulator.

  • Learn how to program software interrupts (SWIs) and interface C/Assembly.

  • Learn more about object files, executables, and program loading.

  • Learn how to optimize code using the ARM Profiler.


This lab introduces you the ARM programming and development tools. The lab can be roughly divided into two parts:

  • C and assembly code to implement several software-interrupts (SWIs).

  • Two more software interrupts that load and run standard ARM binary files.

  • You will use the ARM profiler to determine the critical sections of a jpeg program and then apply basic optimization techniques to optimize the jpeg program.



What to turn in

Part 1


Basic SWI handlers

demo code with TA

bring a hard copy of the fully commented code .

Parts 2 and 3


Program loader

Program launcher

Optimized jpeg code

submit code via 349 submission directory

code should be fully commented

submit a report on jpeg optimizations.

2Development Environment

No development boards will be used for this lab. All of the code will run on the ARMula­tor, a software simulator of an ARM processor core with a resident operating system called Angel. The simulator runs on the ECE W2K workstations (in the lab only). You will find a description of the tools in the following documents located on the class web page (18-349 Simple How-to).

The web page is on blackboard at:


3Part I: Implementing New SWIs


For Part 1, you will write a program that implements an SWI handler and the SWIs described in the file user_SWIs.h. The code should have three main components:

  • A C function (installSWIhandler()) that installs the new SWI handler.

  • An assembly language routine (SWIhandlerEntry()) that serves as the entry routine for the SWI handlers. This routine will be “installed” using the C function installSWIhandler(). SWIhandlerEntry() must be written in assem­bly because it must save and restore the ARM’s architecture registers.

  • The body of the SWI handler, called SWI_dispatcher. This routine can be written in C and should call the appropriate function based on the SWI number.

3.2SWI Review

The following is a short explanation of how SWIs work. We also strongly recommend you look over Chapter 5 in the Developer Guide book of the ARM Developer Suite. The book provides a more thorough discussion and gives some sample code, but it may also contain a few errors and some information not pertaining to our situation. Some points which you may find handy are summarized in the table below:

Notes on Developer Guide Chapter 5




Pay attention to reentrant handler discussion, because your han­dler must be reentrant.


You needn’t read this section, which describes an assembly handler. The body of your handler will be in C.


BAD - Your installer will actually be somewhat like the second example (example 5-4) but you have to decode the old handler entry first. This is described below.

5.5 onwards

Except 5.5.2 These sections may not be relevant to this project.

As you will recall from the class lecture, SWI is an instruction that causes the processor to jump to memory location 0x08, the SWI entry point and change the execution mode to Supervisor mode. This makes them ideal for implementing system calls into an operating system. The execution of an SWI instruction does the following (all is done in hardware by the proces­sor).

  • Copies the Current Program Status Register (CPSR) into the Supervisor mode Saved Program Status Register (SPSR_SVC), saving a copy for restoration at a later time.

  • Sets the CPSR mode bits to Supervisor mode.

  • Sets the CPSR IRQ disable bit, allowing the SWI handler to execute without ordi­nary interrupts.

  • Stores the value (PC – 4) into LR_SVC. The link register now points to the instruc­tion to be executed when the SWI has been handled.

  • Forces the PC to 0x08, the address of the SWI entry in the vector table.

On our 349 development boards (and the simula­tor), the SWI handler address is stored at a loca­tion we call softvec. The instruction in address 0x08 reads the handler address from softvec and branches to the handler. So when an SWI instruction executes, the ARM jumps to location 0x08, which then reads in the handler address and immediately jumps there. The flow of instruc­tions is:

SWI -> MEM[8] -> MEM[handleraddress]

This method, “LDR pc, softvec”, is used instead of a branch instruction because branch instructions only support an offset of 26 bits (64M addressable bytes)... as opposed to the 32 bit address stored in softvec.

3.3The Installer

The installer, installSWIhandler(), should be implemented as a C function that changes the handler address stored at softvec to the address of the function you pro­vide. The function prototype should look something like:

unsigned int installSWIhandler(int newhandler);

and should return the address of the old SWI handler.

To install your new handler, decode the instruction at address 0x08 to determine the value of softvec. The instruction should be a PC-relative load, i.e.

LDR pc, [pc, offset]

See the ARM Assembler Guide for the format of the load instruction.

Extract the offset using bitwise operations such as & and |. Remember that this is the offset from the program counter, not the address of softvec. To compute the real address of softvec, you need to add the program counter to the offset and then adjust the value by the pre-fetching amount (remember that the PC is used for pre-fetching and is ahead of the current instruction’s memory location).

Once the offset is extracted and the true address of softvec is computed, replace softvec with the address of your new handler. You will do this by first reading in the address currently stored at softvec; this will enable you to branch to the old handler if needed. Then write the address of your handler into softvec; this will setup your han­dler as the current SWI handler. From this point, all future SWIs will go to your handler, not Angel’s. The easiest way to read and write to softvec is to declare softvec as follows:

unsigned int *softvec;

Then, set softvec to equal the address you decoded from the LDR instruction at address 0x08. Now, by dereferencing softvec (i.e. *softvec), you will be able to access the value stored there.

Finally, after you have replaced the value in softvec with your SWI handler, this routine should return the address of the original handler (Your program will need to call the old handler to handle any SWIs not supported by your han­dler).

To test your installer, begin by writing a simple function, simplePrint(void), that prints out the message, “Hello world.” Add a main function that calls your installer with the pointer to simplePrint(void) and then calls the appropriate SWI. Make sure to use an SWI that isn’t already used by Angel. (any number over 0x100 should be fine.) To generate an SWI, you will need to use the __swi compiler directive. For example:

/* map the function SWI_test onto the SWI #0x100 */

void __swi(0x100) SWI_test(void);

void simplePrint(void) {

printf(“Hello world\n”);


void main(void) {


SWI_test(); /* generate SWI 0x100 */


If your test message prints out, your installer worked.

Note that the SWI we test with must be greater than 255, because SWI numbers less than 255 are reserved for Angel’s services. We can’t test the SWI handler with these SWIs, because in the ARMulator Angel SWIs bypass the handler. This is useful since printf and similar functions use Angel SWIs, and hence printf would otherwise break if you messed up the SWI handler. Your handler will have to correctly pass Angel SWIs to the Angel SWI handler. This action is called “chaining” to the Angel handler. While not necessary for the ARMulator, it is necessary for the real ARM/XScale boards (which uses a serial port for printf() output), and we will test to make sure you do it correctly.

3.4The Entry Routine

The entry routine, SWIhandlerEntry(), must be written in assembly code. It is “installed” by your installer, and serves as the “entry point” for your SWI handler. The entry routine must:

  • Store the current program registers including: APCS (APCS stands for ARM Procedure Call Standard. See Chapter 2 in the ARM Developer Guide) nonvolatile registers, the link register, and the SPSR.

  • Determine the SWI instruction that called the handler entry function and decode the SWI instruction to determine the SWI number.

  • Call the actual handler (SWI_dispatcher).

  • Call the original SWI handler if your handler doesn’t support the SWI.

Chapter 5 of the Developer Guide describes how to find the SWI instruction and decode its number. The chapter also describes one method of stacking all the registers and passing your C function (SWI_dispatcher) a pointer to the array. We recommend you follow this method. Make sure that when your entry routine returns, it restores the user’s registers, along with any changes made by the C function.

Our entry routine saves r0-r12, lr, and the SPSR. Why must we save the SPSR, since the SWI handler shouldn’t overwrite that? Because we want our SWI handler to support nested SWI calls. In other words, we would like some SWI handlers to be able to call other SWI handlers. When a nested SWI occurs, the ARM will once again copy the CPSR to the SPSR, overwriting the user program’s saved status register. Thus we must store the SPSR and restore it at the end of the SWI handler. Doing this makes your SWI handler reentrant.

NOTE: It is unwise to write recursive SWI functions. Unlike workstations with virtual mem­ory systems and large stack spaces, the SWI handler has a small, fixed size stack (0x100 bytes). Recursive calls quickly eat this space up.

Your C function should return a flag indicating whether it was able to service the SWI, or if the original handler (i.e., Angel) should handle the SWI. You should also restore all the registers saved on the stack. If your code handled the SWI, you should return to the user program with

MOVS pc, lr

The “S” in the “MOV” instruction restores the SPSR to the CPSR. This is the standard way of returning from SWI and IRQ handlers.

If your code didn’t handle the SWI, jump to the old handler which your installer returned. Before you jump to the old handler, make sure you’ve restored all of the registers to their original values (i.e., when your entry routine started). Angel’s SWI handler thinks it’s being called directly and will return to user mode. It will not return to your handler. Also, do not call Angel’s handler with the MOVS instruction, since this will return the pro­cessor to user mode.

Before you test your entry routine, you should have a good understanding of how C and assembly routines access information. The following symbols are accessed by both the assembly code with the entry routine and the C code with the body of the handler and the installer:

  • The address of the entry routine comes from the assembly file and must be used by the C file where the installer is called.

  • The address of the old SWI handler is returned by the installer and must be called from the entry routine.

  • The address of the C part of the handler must be called from the entry routine.

You can test your entry routine by writing some simple C functions for the entry routine to call. Be sure to test that:

  • SWI number decoding works correctly

  • your code handles the SWI numbers it’s supposed to

  • control is passed to Angel for SWIs you don’t handle

  • nested SWIs work; try a printf in your C handler

  • argument passing and return values work OK

  • the condition codes are restored correctly

You’re now ready to write the custom SWIs for this assignment!

3.5The Body of the Handler

The code that actually does the work for the SWIs should be written in C. For this part of the lab, you will only be implementing simple functions, most of which are already handled. In Lab Project #4, we want you to write you own handlers. The SWIs you will need to imple­ment are listed below.

SWIs to implement for Demo 1

SWI Number




void SWI_printC(char)

Print the character in R0 to standard out.


char SWI_getC(void)

Read a character from standard input.


void SWI_printString(char *)

Print the null terminated string pointed to by R0 to standard out.


void SWI_restore(void)

Restore the original SWI handler.


unsigned int SWI_getClock(void)

Return the value of the system clock in R0.


int SWI_readInt(void)

Read an integer from the standard input stream. You should read a line (25 characters max length) and pass that to the C function atoi.


void SWI_writeInt(int)

Print an integer on the standard output stream. You may not use printf, sprintf, or any other related functions.



This SWI is reserved for future use.


void SWI_peek(unsigned int)

Prints (in decimal) the value at the given memory location. You should print “MISALIGNED” if the memory address is not on a word boundary.


void SWI_poke(unsigned int, unsigned int)

If the memory address in the first argument is word-aligned, set the data at that address to the value in the second argument, other­wise do nothing.

Please understand: you will not write any functions called “void SWI_printC”, etc. Although the calls look like normal functions to C, each gets turned into a single instruc­tion, such as “SWI 0x100.” The code to tell the compiler to do this is in user_SWIs.h. You will write functions to be called by the dispatch function to handle each SWI, but they must have different names than the SWI calls. For instance, your dis­patch function might call “handle_printC” to handle SWI 0x100 requests.

This part of the code will consist of functions, which actually perform the above actions, and a main function, SWI_dispatcher, which dispatches the right function based on the SWI number. As discussed in the previous section, the entry routine will call the main handler with the SWI number, as well as any arguments the SWI was called with. The main handler should in turn return a flag indicating whether it was able to service the SWI.

To implement the required functionality, you will need to call invoke some of the original ANGEL SWIs (which is why your handler needs to be reentrant.) The Semihosting SWIs provided by ANGEL are listed in Section 5.4 on page 5-11 of the Debugger Target Guide (ARM DUI0058D).

3.6What to Turn In

For part 1, you will demo your code for the TAs. You can sign up for a demo later online. At the demo, bring a hardcopy of the fully-commented source code with you so that the TAs can look at the code and provide feedback. You will need to have your SWI handler properly handle requests for SWIs 0x100-0x106 , 0x200 and 0x201. You will have to link your SWI handler code with sample code we will provide at the demo. To make sure your code will link with our demo code, you must provide a function called:

void install_user_SWIs(void);

This function should be a wrapper around installSWIhander to install the handler and the new SWIs which you have created. Our code will call this function to install the SWIs you implement. This function is also prototyped in userSWIs.h.

We will test your code with a file SWI_test.c. At the minidemo, you should be able to show this file working with your code. We will ask you to compile and link other files by copying them over SWI_test.c in your directory and recompiling.

4Part II: Loader and Program Launcher


For the second part of the lab you need to do two things: First, you will write code and add SWIs to load and execute an ARM executable file (the files produced by the linker). Second, you will be given some code and asked to optimize it using the profiling tools provided by the ARMulator.

4.2Loading ARM Executable Files

The files to be loaded will be in the executable ARM Executable Format (AXF). The ARMELF.PDF file in the IDE documents directory explains the structure of the ARM executable file. To load an ARM image file, you must read in the header and decode it, and then load the appropriate segments into memory. Since we are dealing with ARM executable format, you can run the file simply by starting execution at the top of the header.

The header of an AXF file contains all the information you need to load it into memory and execute it.

You will need to use Angel SWIs for file operations, as described in the appendix. NOTE: open the file with the mode “rb”, not “r”.

Once you have opened the file, read in the header. You need to look at several fields to figure out where everything else goes.

The AXF header lists the length of the code area, and the address it was linked at (base address). You have to copy the code from the file to the base address. In executable AXF, the header is considered part of the code, and the code size field includes the size of the header. You must copy the header (which you already read from the disk) to the base address, and then copy the remaining code from disk (it comes right after the header in the file) into memory after the header.

After loading the code into the right place, you must load the data. Although the AIF header contains an entry for the address the data was linked at, it is not used in executable AIF; the data simply follows the code. The data area comes right after the code in the file, fol­lowed by the debug area. The length of each of these areas is specified in the header.

In summary of the last two paragraphs: To load an executable AXF, you must copy (code_size + data_size + debug_size) bytes from the file, including the header, into mem­ory, and place all these bytes in order, starting at the base address specified by the header. Hang on to the base address, because that’s where you’ll start executing the file.

New SWIs

You must create two new SWIs, which are defined in userSWIs.h.

unsigned int __swi(0x108) SWI_loadAXF(char *file);

void __swi(0x109) SWI_run(unsigned int address);

SWI_loadAXF takes as its argument the name of the AXF file to load. This SWI should open the file, read the appropriate parts into RAM, and return to the calling procedure, passing the loaded code’s starting address back to the calling program. You should also make sure that the code to be loaded is linked high enough that it doesn’t write over the loader code or any other SWIs you’ve created.

SWI_run should run the code indicated by its argument. You will pass it the starting address which SWI_loadAXF returns. The loaded code should run in USER mode while the SWIs will run in Supervisor mode. The loaded program will exit with SWI_Exit, which returns control to Angel. So the SWI_run function will never return.

The best way to make sure the Supervisor stack is restored to its original state before run­ning the requested program is to let the SWI return as normal. Since your C handler is passed a pointer to the list of registers which will be restored, it can modify the link register to change the address which the entry routine will return to. This is similar to what you did

to return values to the user program, but you’re modifying the link register instead of the argument-passing registers. Now you must write the actual loader. You should write it to load in the file test_run.axf (we will provide some sample programs for you to load with varying base addresses).

It’s important to note that the loader isn’t a very general model because it won’t relocate the code. The code to be loaded must be placed at the base address specified at link time. The ARM tools default to placing the base address at 0x8000 (32K), but the code that actually does the loading resides there. Instead, link the code so that the base address is greater than 0x8000 (0x12000 or 0x20000 is a good choice). To link code at a specific address, modify the base address option in the compiler settings. Where you link the code to be loaded depends upon the size of your code for doing the loading.

4.3What to Turn In

For part two, you will turn in your code to the directory


See “18-349 Simple Howto” for hand-in and late hand-in procedures. This directory must contain:

  • All the code necessary to link your SWIs (from parts 1 and 2) with the file SWI_test.c

  • All the code necessary to produce the program file loader. Your loader program should load and run the program file load_test, which we provide.

Do yourself a favor and make sure it works on your submission to avoid problems.

Your source code should be documented well. See the handout “Documenting Source Code” which was handed out in class and available on the web page.

5Part III: JPEG Code Optimization

The third part of the lab involves using the supplied JPEG compression code to create a JPEG file from a BMP file. The JPEG code will be provided to you

The code will produce a program file called cjpeg. This program will read in the file testimg.bmp and write the file out.jpeg, which is in JPEG format.

You must use the ARM profiler to optimize the code. Then use the debugger to estimate the run time. The source is not highly optimized for the ARM, so you should be able to do some C/Assembly- level optimization.. In all, you shouldn’t expect to get a speedup of more than 15-20%.

5.1What to Turn In

For Part 3, you will submit your work to the handin directory


See the “349 Simple Howto” for handin and late handin procedures. For part 3 you must hand in:

  • Your optimized JPEG code, along with the makefile used to build it.

  • A report, in Word document format, detailing how you profiled and optimized the JPEG code. The report should describe

  1. The run time (in cycles) for your optimized JPEG code.

  2. The results of profiling the code, including output from the profiler

  3. Any compiler optimizations you used and the speed improvement with results from the debugger

  4. Any code optimizations (either C or assembly) that you performed and the speed improvement with results from armsd. Make sure to carefully explain each optimi­zation why it improved performance, and how much performance improvement was gained.

  • The report should be between 4-6 pages, 12-point Times font, single spaced, left justi­fied, nicely formatted with any tables, figures, or source code fragments clearly labeled.

618-349 Project 1 Grading Sheet



Part 1 (40)





Entry Routine, Dispatcher


printC getC printString, getClock


readInt, writeInt, restore, peek, poke


Part 2 (15)

Loader SWI




Part 3 (35)



Optimized Performance


Coding Style





Verilənlər bazası müəlliflik hüququ ilə müdafiə olunur ©azrefs.org 2016
rəhbərliyinə müraciət

    Ana səhifə