Gavare's eXperimental Emulator:
Experimenting with GXemul
|
|
Back to the index
Experimenting with GXemul
Hello world:
You might want to use the emulator to develop programs on your own,
not just run precompiled kernels such as NetBSD. To get started, I recommend
that you do two things:
- Build and install a cross-compiler for your chosen target.
GCC is usually a good compiler choice, because it is portable
and in wide-spread use. (Other compilers should work too.)
- Compile the Hello World demo program for your chosen target, and run
it in the emulator.
The Hello World demo program is included in the GXemul source
code distribution, in the demos/hello/
subdirectory. The README files in the demo directories have several
examples of how the demo programs can be built.
Hopefully this is enough to get you inspired. :-)
Experimental devices:
The emulator has several modes where it doesn't emulate any real machine.
It can either run in "bare" mode, where no devices are included by default
(just the CPU), or in a "test" mode where some simple devices are
emulated.
The test machines (testmips, testppc, etc) have the
following experimental devices:
cons:
A simple console device, for writing
characters to the controlling terminal
and receiving keypresses.
Source code: src/devices/dev_cons.c
Include file: dev_cons.h
Default physical address:  0x10000000
|
|
Offset: |
Effect: |
0x00 |
Read: getchar() (non-blocking; returns
0 if no char was available)
Write: putchar(ch) |
0x10 |
Read or write: halt()
(Useful for exiting the emulator.) |
|
|
mp:
This device controls the behaviour of CPUs in an emulated
multi-processor system.
Source code: src/devices/dev_mp.c
Include file: dev_mp.h
Default physical address:  0x11000000
|
|
Offset: |
Effect: |
0x0000 |
Read: whoami().
Returns the id of the CPU doing the read. |
0x0010 |
Read: ncpus().
Returns the number of CPUs in the system. |
0x0020 |
Write: startupcpu(i).
Starts CPU i. It begins execution at the address
set by a write to startupaddr (see below). |
0x0030 |
Write: startupaddr(addr).
Sets the starting address for CPUs. |
0x0040 |
Write: pause_addr(addr).
Sets the pause address. (NOTE: This is not
used anymore.) |
0x0050 |
Write: pause_cpu(i).
Pauses all CPUs except CPU i. |
0x0060 |
Write: unpause_cpu(i).
Unpauses CPU i. |
0x0070 |
Write: startupstack(addr).
Sets the startup stack address. (CPUs started with
startupcpu() above will have their stack pointer
set to this value.) |
0x0080 |
Read: hardware_random().
This produces a "random" number. |
0x0090 |
Read: memory().
Returns the number of bytes of RAM in the system. |
0x00a0 |
Write: ipi_one((nr << 16) + cpuid).
Sends IPI nr to a specific CPU. |
0x00b0 |
Write: ipi_many((nr << 16) + cpuid).
Sends IPI nr to all CPUs except
the specified one. |
0x00c0 |
Read: ipi_read().
Returns the next pending IPI. 0 is returned if there is no
pending IPI (so 0 shouldn't be used for valid IPIs).
Hardware int 6 is deasserted when the IPI queue is empty.
Write: ipi_flush().
Clears the IPI queue, discarding any pending IPIs. |
0x00d0 |
Read: ncycles().
Returns approximately the number of cycles executed.
Note: this value is not updated for every instruction,
so it cannot be used for small measurements. |
|
|
fb:
A simple linear framebuffer, for graphics output.
640 x 480 pixels, 3 bytes per pixel (red, green, blue, 8 bits each).
Source code: src/devices/dev_fb.c
Include file: dev_fb.h
Default physical address:  0x12000000
|
|
Offset: |
Effect: |
0x00000- 0xe0fff |
Read: read pixel values.
Write: write pixel values. |
|
|
disk:
Disk controller, which can read from and write
to emulated IDE disks. It does not use interrupts; read and
write operations finish instantaneously.
Source code: src/devices/dev_disk.c
Include file: dev_disk.h
Default physical address:  0x13000000
|
|
Offset: |
Effect: |
0x0000 |
Write: Set the offset (in bytes) from the beginning
of the disk image. This offset will be used for the next read/write operation. |
0x0010 |
Write: Select the IDE ID to be used in the next
read/write operation. |
0x0020 |
Write: Start a read or write operation.
(Writing 0 means a Read operation, a 1 means a
Write operation.) |
0x0030 |
Read: Get status of the last operation.
(Status 0 means failure, non-zero means success.) |
0x4000- 0x41ff |
Read/Write: 512 bytes data buffer. |
|
|
ether:
A simple ethernet controller, enough to send
and receive packets on a simulated network.
Source code: src/devices/dev_ether.c
Include file: dev_ether.h
Default physical address:  0x14000000
|
|
Offset: |
Effect: |
0x0000- 0x3fff |
Read/write buffer for the packet to be sent/received. |
0x4000 |
Read: status word, one or more of these:
0x01 = something was received (because of
the last command)
0x02 = more packets are available
NOTE: Whenever the status word is non-zero,
an interrupt is asserted. Reading the status word
clears it, and deasserts the interrupt. |
0x4010 |
Read: get the Length of the received packet
Write: set the Length of the next packet to transmit |
0x4020 |
Write: command:
0x00: receive a packet
0x01: send a packet |
|
|
rtc:
A Real-Time Clock, used to retrieve the current time
and to cause periodic interrupts.
Source code: src/devices/dev_rtc.c
Include file: dev_rtc.h
Default physical address:  0x15000000
|
|
Offset: |
Effect: |
0x0000 |
Read or Write: Trigger a clock update (a gettimeofday() on the host). |
0x0010 |
Read: Seconds since 1st January 1970 |
0x0020 |
Read: Microseconds |
0x0100 |
Read: Get the current
timer interrupt frequency. Write: Set the timer
interrupt frequency. (Writing 0 disables the timer.) |
0x0110 |
Read or Write: Acknowledge
one timer interrupt. (Note that if multiple interrupts
are pending, only one is acknowledged.) |
|
While these devices may resemble real-world hardware, they are
intentionally made simpler to use. (An exception is the framebuffer;
some machines actually have simple linear framebuffers like this.)
If the physical address is 0x10000000, then for MIPS that
means that it can be accessed at virtual address
0xffffffffb0000000. (Actually it can be accessed at
0xffffffff90000000 too, but devices should usually be accessed in
a non-cached manner.)
When using the Alpha, ARM, or PPC test machines, the addresses are
0x10000000, 0x11000000 etc., so no need to add any
virtual displacement.
The mp, disk, and ether devices are agnostic
when it comes to word-length. For example, when reading offset
0x0000 of the mp device, you may use any kind of read
(an 8-bit read will work just as well as a 64-bit read, although the value
will be truncated to 8 bits in the first case). You can not,
however, read one byte from 0x0000 and one from 0x0001,
and combine the result. The read from 0x0001 will be invalid.
The cons device should be accessed using 8-bit reads
and writes. Doing a getchar() (ie reading from offset 0x00)
returns 0 if no character was available. Whenever a character is
available, the cons device' interrupt is asserted. When there are
no more available characters, the interrupt is deasserted. (Remember that
the interrupt has to be unmasked to be able to actually cause an
interrupt.)
IPIs (inter-processor interrupts) are controlled by the mp
device. Whenever an IPI is "sent" from a source to one or more target
CPUs, the interrupt is asserted on the target CPUs, and the IPI number is
added last in the IPI queue for each of the target CPUs. It is then up to
those CPUs to individually read from offset 0x00c0, to figure out
what kind of IPI it was.
Interrupt mappings are as follows:
testmips
|
IRQ: | |
Used for: |
7 | |
MIPS count/compare interrupt |
6 | |
mp (inter-processor interrupts) |
4 | |
rtc |
3 | |
ether |
2 | |
cons |
|
Other machines: TODO