Simics is a brilliant tool, but what it lacks is a simple way how to make an output from the simulated code. On some simulators, such as MSIM, the simulated code can print a character by writing its code to a special address of memory. It is a priceless feature - debugging output is possible without having to write a driver for a framebuffer or a serial console.

I’ve had some troubles making the graphical console work on the Serengeti machine. On the Simics forum (https://www.simics.net/mwf/forum_show.pl) a guy from Virtutech told me that it is theoretically possible to configure the simulated Simics machine to support a graphical console, but…

First, why do you want the graphical console in the serengeti machine? The Serengeti architecture does not have any serial ports where mouse and keyboards can be connected, i.e. even if you get the graphics card to work, it can only be used to view output. No interactive programs can be run using it. The older SunFire architecture has better support for a graphical device with keyboard/mouse input.

The graphics card was never tested as output device for OBP. Instead the machine was booted as usual and then an X server was started with no mouse/keyboard configured. This required some manual setup and since this was several years ago, I don’t know if it works with more modern Solaris versions.

As HelenOS UltraSPARC port does not support the serial console yet, I will have to write a serial console driver one day. It is, however, difficult to debug a kernel with no possibility of debugging output and I don’t have mood for writing the serial console driver now. So I feel a little bit envious of the ones who can debug their kernels in MIPS-like simulators. Wait a moment… Is it really impossible to use some simple way of output in Simics? With some sort of invention, one can achieve it!

Simics API (see http://www.cs.sfu.ca/~fedorova/Tech/simics-guides-3.0.26/simics-reference-manual-public-all.pdf to get its full description) can be used by Python scripts, which can be included directly in the Simics configuration file.

An API function called SIM_realtime_event exist, via which a Python routine, which will be called in a given number of milliseconds, can be registered. It is therefore possible to regularly run a background Python routine while the simulation is running.

Such a routine can use other Simics API functions, such as SIM_read_register, SIM_write_register, SIM_read_phys_memory or SIM_write_phys_memory to “communicate” with the simulated code. It can also use standard Python output routines to write to the Simics CLI window.

The final solution uses a buffer the simulated code writes to and the Python routine reads from (bytes to which the simulated code is free to write are set to zero). The address of the buffer is transmitted to the Python script by

  • setting the g3 register to the buffer address, and
  • writting a magic value to the g2 register.

The code on the HelenOS side looks like this:

/* all buffer positions are free at the beginning */
uint16_t i;
	for (i = 0; i < BUFSIZE; i++) {
buffer[i] = '\0';
}

/*
 * pass the address of the buffer to the Simics' Python script
 *   - write it to the %g3 register
 *   - write the magic value to the %g2 register
 *     (so that the script knows that the value in %g3 is valid)
 *   - loop until the value is read
 *     (the script notifies us by setting %g2 to 0)
 */
asm volatile (
	"or %0, 0, %%g3\n"

	"set 0x18273645, %%g2\n"

	"0: cmp %%g2, 0\n"
	"bnz 0b\n"
	"nop"
	:: "r" (buffer)
);

And on the Python side:

if ((buf == 0) and (register2Value == 0x18273645)):
	buf = SIM_read_register(SIM_current_processor(), register3Number);
	SIM_write_register(SIM_current_processor(), register2Number, 0);

Once the Python script knows the location of the buffer, the simulated code can write to the buffer…

/** Writes a single character to the Simics CLI.
 *
 * The character is not written immediately, but it is stored to the first free
 * position in the buffer, waiting for Simics' Python routine to pick it
 * and print it.
 */
static void simics_putchar(struct chardev * cd, char c)
{
	/* the first free position in the buffer */
	static uint16_t current = 0;

	/* '\0' terminates a contiguous block of characters to be printed! */
	if (c == '\0')
		return;

	/* wait till buffer is non-full and other processors aren't writing to it */
	while (1) {
		while (buffer[current] != 0)
			;
		if (spinlock_trylock(&simics_buf_lock))
			break;
	}

	buffer[current] = c;

	current = (current + 1) % BUFSIZE;
	membar();

	spinlock_unlock(&simics_buf_lock);
}

… and the Python script can read from the buffer, printing the characters to the CLI window:

byte = SIM_read_phys_memory(SIM_current_processor(), buf + offset, 1);
while byte != 0:
	SIM_putchar(byte);
	SIM_flush();
	SIM_write_phys_memory(SIM_current_processor(), buf + offset, 0, 1);
	offset = (offset + 1) % 512;
	byte = SIM_read_phys_memory(SIM_current_processor(), buf + offset, 1);

This mechanism is easy and significantly faster than writing the output to the graphical console. Moreover, as the Simics CLI runs in the Unix terminal window, the output can be processed by standard Unix means (the tee command, pipes, etc.). It will be necessary to implement a serial console driver in the future, but for the time being, the mechanism described in this post is pretty sufficient.