Quick Guide


 

Linking

The first step if you want to compile an application making use of the ARM9Core library is to link with it. In our example, we will use a static link. First, we must include the header file into our code:

#include "ARM9Core.h"

For the compiler to use the library, we also need to add it to its linking options. In our case, we add "ARM9Core.lib" to the list of files the compiler will link with.

Depending of which compiler we are using, it could also be necessary to configure the header files folder (.h) and libraries files folder (.lib) adding the folders where the ARM9Core files are located, for it to find those files.

Now, we can start using the ARM9Core in our program.

 

Classes and Structures

These are the main classes and structures you will be using:

 

Initialization

To inicializate the emulator we first create a instance of the ARM9Core class and then use the following methods:

ARM9Core core;

//We will only emulate a single core system
core.setCores(1);

//Set the memory regions
core.addMemoria (nRegions, memoryRegions);

//Set the exception handlers
core.setExcepHandler (EXCEP_SWI, &swi);
core.setExcepHandler (EXCEP_UNDEFINED, &undef);
core.setExcepHandler (EXCEP_DATA_ABORT, &abort);
core.setExcepHandler (EXCEP_PREFETCH_ABORT, &abort);

//Set the external function handler
core.setFuncionExterna (&fexternal);

//We will be emulating at a 50.0 Mhz frequency
core.setFrecuenciaARM (50.0);

//Lastly, we reset the core and leave it ready for emulation
core.reset ();

 

Memory Regions

The first complex data we need to give to the ARM9Core is the memory region list. This is done by using the addMemoria method. The first parameter is the total number of memory regions, the second is the list (array) of memory identifiers. The method returns "true" if the initialization was correct, or "false" if it detects any error in the given list (for example, overlapping regions).

Each memory identifier is an ARM9MemRegion structure containing both the start and end address of the block, as well as pointers to either the function handlers, or the memory buffer, depending of which method we are using. It also contains all the information about the access permissions. For example, the following code initializes a region from address 0 to 0x8000, and asociates it to a data buffer, giving it all access permissions:

memoryRegions[0].inferior = 0; //Lower address
memoryRegions[0].superior = 0x7FFF; //Upper address
memoryRegions[0].buffer = (uint8*)buffer;

//We won't be using any function handler
memoryRegions[0].handlerRead32 =
     memoryRegions[0].handlerRead16 =
     memoryRegions[0].handlerRead8 = NULL;
memoryRegions[0].handlerWrite32 =
     memoryRegions[0].handlerWrite16 =
     memoryRegions[0].handlerWrite8 = NULL;
	 
memoryRegions[0].permisos = USR_READ_PERM |
                             USR_WRITE_PERM |
                             PRIV_READ_PERM |
                             PRIV_WRITE_PERM;

If you want to use a function pointer instead of a data buffer, set the buffer pointer to NULL, and set the different handlers with the corresponding function pointers. There are 3 read handlers and 3 write handlers, each with a different data width (32, 16, and 8 bits). These handlers are defined as pointers to the functions of the following types:

typedef uint32 (*readHandler32)(uint32 dir);
typedef uint32 (*readHandler16)(uint32 dir);
typedef uint32 (*readHandler8)(uint32 dir);
typedef void (*writeHandler32)(uint32 datos, uint32 dir);
typedef void (*writeHandler16)(uint16 datos, uint32 dir);
typedef void (*writeHandler8)(uint8 datos, uint32 dir);

While most of the times using a data buffer will be enough and faster than a function handler, these allow you to have more control over the memory system. For example, you might want to link a memory region to an input device, so that when the ARM code reads from that address, it polls the status of the device. In this case, you could write a function handler. The ARM9Core will invoke the function whenever it detects a read access to that address, and the handler can then poll the status of the real device.

 

Exceptions

ARM9Core offers support for all of the ARM exception types. These exceptions might be "internal" (signalled by the own ARM9Core), such as a data abort, or external, such as an IRQ. You can signal any external exception by calling the following method in the ARM9Core class:

void marcarExcepcion (ARM9Exception exception, int param = 0);

By default, when the ARM9Core detects an exception it changes the execution mode and jumps to the interrupt vector address, as specified in the ARM architecture documentation. However, ARM9Core offers support for exception function handlers. If there's a function handlers associated to the detected exception type, the library will invoke that function and keep executing from the next instruction in the program.

These function handlers are often faster and easier to work with than the real machine behaviour, so we really encourage you to use them. To link a handler, use the method:

void setExcepHandler (ARM9Exception ex, void (*handler)(int n));

The first parameter is the identifier for the exception type. The second is a pointer to the function that will handle that exception type. For example, a valid SWI handler would look like:

void swi (int n) {
	if (n == 0x123456) {
		//ANGEL protocol
		angelSyscall();
	} else {
		printf ("Detected SWI %X\n", n);
	}
}

The "n" parameter depends on the exception types. Its value can be:

If you go back to the initialization code, you might notice there are no handlers for the RESET, IRQ, and FIQ exceptions. The Reset exeption is emulated just by a call to the reset () method, so you can't set a function handler for it. And while you can set function handlers for IRQ and FIQ exceptions, it's not recommended to do so, but to emulate them in the "external function".

 

Slices and External Function

The ARM9Core emulates by executing instructions in groups. These groups of instructions are called "slices", and you can set its size in cycles. Once a slice is fully executed the emulator performs the synchronization to the specified speed, and a call to the external function.

The external function is yet another function handler specified by the user. This call allows the emulator to give the control of the program back to your application, so it can perform its own tasks. We recommend you implement the emulation of IRQ and FIQ exceptions by yourself in this function, as it is faster than using the standard exception system from the library.

The external function has no input and no output, and is set by calling the following method:

void setFuncionExterna (void (*func)(void));

As we said, the emulator executes groups of instructions (slices) at a time for performance reasons. You can specify how large these slices are by giving their size in cycles. The ARM9Core then will execute instructions until it runs out of cycles, when it will perform the synchronization and call to the external function, and start working in the next slice.

A larger slice value means more performance when emulating, but also less frequently calls to the external function (which could result in less precise emulation of IRQ and FIQ, or bottlenecks in your application's code), while a smaller slice value means less performance when emulating. We suggest you use your most frequent exception as a guide for the slice's size.

 

Running

To run the emulator, you just need to call:

core.run (nCycles);

That call starts the emulator using a slice size of "nCycles". The emulation keeps going until you call to:

core.stop ();

If you need to debug, you might find the step-by-step execution mode useful. This method will only execute the next instruction, and then return:

core.step ();

Please note that the "run" method will block execution and will not return until a call to "stop" has been made. That means the "stop" call must be either in one of your defined function handlers, or in the external function. Otherwise, you might not be able to stop the core. The "stop" call does not reset the machine's state, so you can call to "run" after it and keep emulating from the same point.