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:

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

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)
3 ether
2 cons

Other machines: TODO