There are three flavors of QNICE emulators:
POSIX Terminal: Emulation of a QNICE system that only offers a serial connection for input/output. Runs in any POSIX terminal. Use
SDL OpenGL Window: Full QNICE-FPGA emulation including the VGA screen and the PS/2 keyboard. Opens two windows: One using the POSIX terminal for emulating a serial connection and in parallel a graphics window for the VGA output. Needs SDL2 to compile. Use
make-vga.bashto build or use
run-vga.bashto automatically build, download a disk image and run.
WebAssembly/WebGL: Running in any modern web browser, the WebAssembly/WebGL flavor of the emulator is extremely easy to use and very portable. Needs Emscripten and SDL2 to compile. Use
make-wasm.bashto build. Try a prebuilt version online here, which mounts a FAT32 disk image that contains among other things also Q-TRIS.
Build the Toolchain, the Monitor and the Demos
Open a terminal and go to a folder, where git is allowed to create a subfolder called
git clone https://github.com/sy2002/QNICE-FPGA.git
After that, you should have a folder called
cdinto that folder now. From now, all instructions will be relative to this folder.
Compile the toolchain: You need to have the GNU compiler toolchain installed, particularly
makewill be used. Open a terminal in the QNICE root folder. Enter the following (it is important, that you
cdinto the folder):
cd tools ./make-toolchain.sh
You will be asked several questions. Answer them using the default answers by pressing
Enterinstead of answering manually by choosing
Compile the Monitor, which is akin to an operating system for QNICE-FPGA. If you are still in the
toolsfolder, then enter:
cd ../monitor ./compile_and_distribute.sh
monitor.outfile is what the emulator needs.
Given, that you are still in the
cd ../demos ../assembler/asm mandel.asm ../assembler/asm q-tris.asm cat mandel.out|pbcopy
On macOS, you now have an ASCII file in the clipboard/pasteboard that starts with the line
0xA000 0x0F80. On other operating systems, you might see an error message, stating that
pbcopyis not available. You can savely ignore this and manually copy the file
demos/mandel.outinto your clipboard/pasteboard.
POSIX Terminal: Emulation using Serial I/O
Make sure you are not overwriting your clipboard contents (which should contain
mandel.out) by typing the following commands manually instead of copy pasting them from here.
Navigate to the
emulatorfolder and compile the emulator using
Run the emulator and let it instantly load the Monitor:
Linto the Monitor window. After that, you should see something like
QMON> MEMORY/LOAD - ENTER ADDRESS/VALUE PAIRS, TERMINATE WITH CTRL-E
CMD+Vfor PASTING the mandel.out textfile, that should be still in your clipboard, if you followed the above-mentioned steps.
CTRL-Enow, to go back to the Monitor. You should see the
QMON>prompt again. (This mechanism of loading
.outfiles into the emulator can also be used while running the below-mentioned
qnice-vga, even though it is not explicitly mentioned again there.)
You should see a textmode rendition of the famous Mandelbrot set.
CTRL+Cto leave the Monitor and to return back to the
exitto end the emulator.
SDL OpenGL Window: Emulation of the VGA Screen and the PS/2 Keyboard
You need libsdl for compiling.
If you are connected to the Internet, then enter
./run-vga.bashto download a disk image with demo content, compile the SDL OpenGL version of the emulator (aka qnice-vga) and run it while mounting the disk image.
As soon as qnice-vga runs, an additional window will open, so that the emulator now has two windows open: The POSIX terminal window, that shows the
QMON>prompt and a blinking cursor; this is the emulation of the serial I/O. Furthermore, an additional graphical window is open. It is mainly black and shows a blinking cursor.
Go to the
QMON>prompt in the terminal window and enter
qbin/q-tris.outto run Q-TRIS.
Go to the graphical window, press
SPACEand start playing. The emulator automatically attempts to regulate the speed to
13.0 MIPS, which is the speed of the original QNICE-FPGA hardware running at 50 MHz. Toggle the MIPS and FPS display using
Go back to the terminal window where you see
CTRL+Cto end Q-TRIS.
helpto see the various Monitor commands and keyboard shortcuts.
You can for example use the
mips maxcommand to set the emulation speed to the maximum speed that your computer can support. After that, enter
speedstats onto see how many MIPS this will mean and after that enter
run $8000to restart Q-TRIS. You should now see the new MIPS at the top right corner of the VGA screen. After having seen the new speed, press
CTRL+Cin the terminal emulation screen to return to the emulator's
Instead of loading a file from the virtual FAT32 formatted SD Card that is located in the file
qnice_disk.img(downloaded when you first run
./run-vga.bash), you can also directly load something into the emulator's memory using the
loadcommand in the
And instead of using the Monitor to run something, you can also point the emulator directly to a certain memory address and execute. The Mandelbrot demo is at
$a000, so enter
run $a000. You will see the textmode rendition of the Mandelbrot set in the POSIX terminal window.
Q>prompt is replaced by the
QMON>prompt because the
SYSCALL(exit, 1)command in
demos/mandel.asmjumps back to the Monitor.
CTRL+Cand then in the
switch 3and then
run 0and then switch to the graphical window and enter
Cto clear the screen. The
switch 3command routed STDIN now to the PS/2 keyboard emulation and STDOUT to the VGA screen.
While being in the VGA screen, enter
a000. The Mandelbrot rendition is shown in the VGA screen. Press the
CURSOR UPkey three times to see the rendition nicely centered.
Go to the POSIX terminal window, press
CTRL+Cto leave the Monitor and to return back to the
exitto end the emulator.
How to mount FAT32 devices and files in the Emulator
The Emulator is able to mount raw image files that are made from appropriate media and expose it to QNICE-FPGA via the SD Card interface, so that the Monitor "thinks" this is a FAT32/MBR formatted SD card. As devices behave like files on Unix-like systems, on these systems you can also directly mount a device.
Read the file doc/emumount.txt to learn more.
WebAssembly/WebGL in a Web Browser: Emulation of the VGA Screen and the PS/2 Keyboard
You can try it out online. The following steps show how to build and run a local version.
If you followed all above-mentioned instructions in sequence, then you have downloaded the file
qnice_disk.img, which contains a FAT32 disk image with demo programs. If you jumped directly to this section, then you need to download it using
curl. Enter the following two lines:
DISK_IMAGE=http://sy2002x.de/hwdp/qnice_disk.img wget $DISK_IMAGE || curl -O $DISK_IMAGE
You need to build the toolchain and the Monitor as described above and you need the Emscripten SDK. Activate the Emscripten SDK using the
source emsdk_env.shcommand while being in the Emscripten SDK home folder.
qnice.cin a text editor and search for
sy2002x.de. You should find a line that contains the
emscripten_wget()function. Change that line so that it looks like this, so that your local FAT32 disk image is used instead of the online version:
Build the WebAssembly/WebGL version of the emulator using
Run a local webserver by entering
python -m SimpleHTTPServer 8080.
Point your web browser to
qbin/q-tris.outto play Q-TRIS.
Adjusting the Emulation Speed
In contrast to the native qnice-vga version of the emulator, the WebAssembly/WebGL version is not capable to automatically regulate the speed to match the
13.0 MIPSof the FPGA hardware that runs at 50 MHz. Probably your computer will be faster, so you will need to slow down the emulation speed.
The emulation speed depends on how many QNICE CPU instructions the emulator is performing per frame and on the amount of frames per second that your hardware is able to draw while calculating the before-mentioned amount of instructions.
ALT+fto toggle between showing and hiding the MIPS (million instructions per second) and the FPS (frames per second). The numbers are displayed at the top-right corner of the screen and the display stays on, until toggled again.
ALT+vto see, how many instructions per frame are currently being executed. The amount is displayed in a window in the middle of the screen, which disappears after about three seconds. The speed change windows, that are described in the following bullet points, are disappearing after about three seconds after the last keypress as well.
ALT+SHIFT+n: decrease instructions per frame (IPF) by 100,000
ALT+n: decrease IPS by 2,500
ALT+SHIFT+m: increase IPF by 100,000
ALT+m: increase IPF by 2,500
Using GZIP on the Web Server
Due to the fact that the minimum disk image size of FAT32 images is 32MB, it makes sense to have the web server GZIP the file before sending it to the browser. This will greatly increase the download speed and can be done on Apache web servers using the following commands in the .htaccess file:
<IfModule mod_deflate.c> AddOutputFilter DEFLATE img </IfModule>
For other web servers there are similar mechanisms available. If the download server is on another domain than the WASM file, then due to CORS, the following needs to be added to the .htaccess file:
<IfModule mod_headers.c> Header set Access-Control-Allow-Origin "*" </IfModule>
qnice.ccontains the main program and the CPU emulation. The function
int execute()is the core of the emulation as it executes a single QNICE instruction and updates the whole state machine.
QNICE-FGA uses memory mapped I/O, so does the emulator. This is why the memory access is funneled through the function
unsigned int access_memory(...)that explicitly routes certain memory reads or writes through the register access functions of the emulated hardware (IDE, SD card, UART, VGA in the respective
The FAT32 emulation is part of the Monitor, so that the SD card emulation of the emulator is nothing more than a buffered file access.
qnice-wasmneed a FIFO for their keyboard input, albeit at completely different spots in their logic.
fifo.cis a simple but yet thread-safe implementation of such a FIFO.
POSIX Terminal (
Input/Output is emulated by emulating a serial connection in
As soon as the emulation runs (e.g. by entering
Q>shell), the POSIX STDIN is switched from the usual line buffer mode where you need to end a line with
ENTERto an unbuffered mode. This is done in
uart.cin the function
getchar()are used to read from the keyboard. (Due to
select()having a timeout, this mechanism is not feasable in the
qnice-vgamode, because it would significantly slow down the speed and would introduce skew and jitter for any automated MIPS calculation.)
SDL OpenGL Window (
Uses six threads. The threading is based on SDL's threading mechanisms for easy portability. Therefore all the threads are started using the function
vga.cwhich encapsulates the appropriate SDL functions. All threads use global variables for synchronization. All these global variables are written and read carefully, so that no mutex or semaphores are necessary and race-conditions are still being avoided.
The main thread runs the SDL event loop and therefore reads the keyboard and updates the screen:
vga.c. It is noteworthy that the screen refresh speed is throttled to 60 FPS, which greatly reduces system strain. The CPU emulation is decoupled from drawing the screen, so more FPS do not lead to more MIPS.
The CPU emulation is in a separate thread, so that modern multi-core systems can play to their strengths and maximize emulation performance. The function
static int emulator_main_loop(...)is just an encapsulation of the same
int main_loop(...)function, that also the POSIX terminal
Various parts of the system need a consistent clock. This is why
vga.cis updating the global variable
The original QNICE-FPGA hardware performs
13.0 MIPSwhile running at 50 MHz. Most modern systems will emulate QNICE-FPGA much faster. The speed regulation mechanism is implemented in the function
void run()by calculating how many QNICE instructions per 10 milliseconds need to be performed to match the original hardware's speed. The value is stored in
gbl$target_iptms. Due to jitter and skew, this value is only an approximation. Therefore the thread
int mips_adjustment_thread(...)compares the actual MIPS with the target MIPS every three seconds and then calculates the adjustment factor
gbl$target_iptms_adjustment_factorthat is multiplied with
POSIX signal handlers are not working consistently and reliably in multithreaded environments. Therefore
static int signal_handler_ctrl_c_multithreaded(...)uses
sigwaitto wait for the user to press
poll(...)function with a 5 millisecond timeout to read keys from the keyboard into the FIFO. The read-access via emulated UART registers happens in parallel and in high-speed in the CPU thread. Consequently,
fifo.cuses SDL's Mutex mechanism for ensuring thread-safety.
The emulated VGA screen is an OpenGL streaming texture: A pixel buffer in main memory that represents the screen is repeatedly copied ("streamed") into a texture buffer in the GPU's RAM and from there copied to the screen.
vga.cshows this mechanism.
For maximizing the VGA screen's performance, the pixel buffer is modified one character at a time in contrast to re-rendering it for each frame. This corresponds to the way how QNICE-FPGA's VGA hardware works: Also there, you can always only modify one character at a time in VRAM, because the VRAM is not mapped to QNICE-FPGA's RAM, but only accessible via memory mapped registers.
vga.cis doing the job of modifing the pixel buffer in the array
The various overlay windows that are visible in the context of speed adjustments (e.g. by pressing
ALT+n) are rendered using
The keyboard management is currently hardcoded to a German keyboard using large and nested
vga.c). Future versions of the emulator might want to utilize more flexible mechanisms.
The following files are constituting the executables of the WebAssembly version of the emulator:
qnice.html qnice.js qnice.wasm
Emscripten offers a virtual file system that is linked during compile time. When an Emscripten app starts, the appropriate data file is automatically loaded and the virtual file system is immediatelly available via the C standard library functions. The file
qnice.datacontains the Monitor in such a virtual file system, so that the Monitor can be loaded immediatelly after startup using the regular
load_binary_file("monitor.out")function call. There is no
Q>shell available in
The minimum file size of a FAT32 disk image is 32MB. Emscripten cannot package files that big into the virtual file system. This is why the disk image is loaded from a server using
At the time of writing
qnice-wasm, WebAssembly only supports single-threaded apps, which are forced to yield CPU cycles back to the browser in the sense of cooperative multitasking.
qnice.cperforms this task.
Speed regulation is done by defining an amount of QNICE instructions that shall be executed in each "iteration". One iteraton (in pseudocode) looks like this:
Perform the amount of QNICE instructions defined in gbl$instructions_per_iteration Yield CPU cycles back to the browser Read keys from the keyboard Update the screen
Depending on the setting of
gbl$instructions_per_iteration, the interval between two keyboard buffer reads might be quite high, this is why the FIFO buffer from
fifo.cis utilized in
vga.cso that even if the FPS (aka "iterations per second") are low, no key presses are lost.
qnice.htmluses the Emscripten Module interface.
<canvas>element is used for the WebGL drawing context. The
setStatusfunction contains the code that makes sure that the user receives status update while the app itself and the 32 MB disk image is being loaded. The
qnice.care interacting with
The GitHub web pages that host
qnice-wasmare available at qnice-fpga.com and are rendered using Jekyll. Switch to the branch gh-pages to learn more. The way the Jekyll template is built does not allow to import a fully fledged HTML5 file but only the inner part of the
<body>tag. Therefore the file
wasm-shell-release.htmlneeds to be updated manually, after changes in
wasm-shell.htmlhave been made. This is why you need to call
./make-wasm.bash RELEASE, if you want to use the resulting files to update the GitHub web pages.