# GXemul: Experimenting with GXemul

Back to the index.

- Hello world
- Test machines
- Experimental devices

### Hello world:

You might want to use the emulator to develop programs on your own, not just run already existing operating system kernels such as NetBSD. To get started, I recommend that you do two things:

- Build and install a cross-compiler for your chosen target, e.g. `mips64-unknown-elf`. 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.

Once you have compiled the Hello World program into a binary, `hello_mips`, start it like this:

    	gxemul -E testmips hello_mips

Then you can experiment using the commands further up on this page.

If you start the emulator in the paused state, or if you press CTRL-C during normal execution, you will end up with a `GXemul>` prompt.

Hopefully this is enough to get you inspired. :-)

### Test machines:

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. This can be used when developing e.g. operating systems, as a complement to targeting real machines.

The name of the test machine modes are `testmips`, `testarm`, etc. Run `gxemul -H` for an up-to-date list.

Here are some examples of guest OSes that use or (have used) the test machine modes:

- HelenOS:
  Old versions of HelenOS can run in GXemul's `testarm` and `testmips` machine modes.

      	ftp http://www.helenos.org/releases/HelenOS-0.5.0-arm32-GXemul.boot
        	gxemul -X -E testarm HelenOS-0.5.0-arm32-GXemul.boot

      	ftp http://www.helenos.org/releases/HelenOS-0.5.0-mips32-GXemul.boot
        	gxemul -X -E testmips HelenOS-0.5.0-mips32-GXemul.boot

- FreeBSD/MIPS:
  For a while, testmips was one of the targets when developing the MIPS port of FreeBSD. See revision 234920 for more details. (The target was removed in December 2018; focus from now on is on the Malta board.)

- Glatt MU/MK:
  One of Glatt MU/MK's targets is `testmips`.

- MINIX 3.x:
  http://yana.jp/20160811/computer/minix.ja.euc.html indicates that an attempt was made to port MINIX 3.x to testarm and testmips.

- YCX5:
  One of YCX5's primary development targets is `testmips`.

### Experimental devices:

The test machines (`testmips`, `testarm`, etc) have the following experimental devices:

`ram`:

The first 256 MB of RAM.
Physical address: 0x00000000

Comment:

`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`
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`
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 on this CPU. 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. Initial resolution is 640 x 480 pixels, with 3 bytes per pixel (red, green, blue, 8 bits each).

Source code: `src/devices/dev_fb.c`

Include file: `dev_fb.h`
Physical address: 0x12000000

Offset: | Effect:
`0x00000000` | Read: read pixel values. Write: write pixel values.
`0x00f00000` | Read/Write: Control ports for changing the resolution at runtime, etc. See `dev_fb.h` and `dev_fbctrl.cc` for more details. If the resolution is read back as X=0 Y=0, then that means that graphics is not used, only serial console.

`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`
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.
`0x0008` | Write: Set the high 32 bits of the offset (in bytes). (*)
`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`
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
`0x4040` | Write: an address, where the emulated MAC address will be copied to.

`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`
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.)

`irqc`:

An Interrupt Controller. (Note: Not used for the MIPS test machine.)

Source code: `src/devices/dev_irqc.c`

Include file: `dev_irqc.h`
Physical address: 0x16000000

Offset: | Effect:
`0x0` | Read: IRQ status as a 32-bit word, one bit per interrupt source.
`0x4` | Write: Mask one interrupt source. Value should be an integer 0..31.
`0x8` | Write: Unmask one interrupt source. Value should be an integer 0..31.

(*) Note: If the emulated architecture has the capability to write 64-bit words atomically, then writing to offset 0x0000 of the `disk` device should be enough to set the offset. For 32-bit architectures, the lowest 32 bits of the disk offset should first be written to the register at offset 0x0000, and the top 32 bits should then be written to the register at offset 0x0008.

The include files for the test machine devices are found in `src/include/testmachine/`.

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`. (Using virtual address `0xffffffff90000000` may work in the emulator, but should be avoided, since devices should in general be accessed in a non-cached manner.)

When using the 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` (as native MIPS interrupts)

IRQ: |  | Used for:
7 |  | MIPS count/compare interrupt
6 |  | `mp` (inter-processor interrupts)
4 |  | `rtc`
3 |  | `ether`
2 |  | `cons`

`testarm` and others (via the `irqc` device)

IRQ: |  | Used for:
6 |  | `mp` (inter-processor interrupts)
4 |  | `rtc`
3 |  | `ether`
2 |  | `cons`