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
make.bashto build. -
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 userun-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.
-
Open a terminal and go to a folder, where git is allowed to create a subfolder called
QNICE-FPGAand entergit clone https://github.com/sy2002/QNICE-FPGA.git -
After that, you should have a folder called
QNICE-FPGA.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
gccandmakewill be used. Open a terminal in the QNICE root folder. Enter the following (it is important, that youcdinto the folder):cd tools ./make-toolchain.shYou will be asked several questions. Answer them using the default answers by pressing
Enterinstead of answering manually by choosingyorn. -
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.shThe resulting
monitor.outfile is what the emulator needs. -
Given, that you are still in the
monitorfolder, enter: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 thatpbcopyis not available. You can savely ignore this and manually copy the filedemos/mandel.outinto your clipboard/pasteboard.
-
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./make.bash. -
Run the emulator and let it instantly load the Monitor:
./qnice ../monitor/monitor.out -
Enter
Mand thenLinto the Monitor window. After that, you should see something likeQMON> MEMORY/LOAD - ENTER ADDRESS/VALUE PAIRS, TERMINATE WITH CTRL-E -
Press
CMD+Vfor PASTING the mandel.out textfile, that should be still in your clipboard, if you followed the above-mentioned steps. -
Press
CTRL-Enow, to go back to the Monitor. You should see theQMON>prompt again. (This mechanism of loading.outfiles into the emulator can also be used while running the below-mentionedqnice-vga, even though it is not explicitly mentioned again there.) -
Enter
Cand thenRand thenA000. -
You should see a textmode rendition of the famous Mandelbrot set.
-
Press
CTRL+Cto leave the Monitor and to return back to theQ>prompt. PressCTRL+Dor enterexitto end the emulator.
-
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 enterFand thenRand thenqbin/q-tris.outto run Q-TRIS. -
Go to the graphical window, press
SPACEand start playing. The emulator automatically attempts to regulate the speed to13.0 MIPS, which is the speed of the original QNICE-FPGA hardware running at 50 MHz. Toggle the MIPS and FPS display usingALT+F. -
Go back to the terminal window where you see
Running...and pressCTRL+Cto end Q-TRIS. -
Enter
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, enterspeedstats onto see how many MIPS this will mean and after that enterrun $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, pressCTRL+Cin the terminal emulation screen to return to the emulator'sQ>prompt. -
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 theloadcommand in theQ>shell:load ../demos/mandel.out -
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 enterrun $a000. You will see the textmode rendition of the Mandelbrot set in the POSIX terminal window. -
The
Q>prompt is replaced by theQMON>prompt because theSYSCALL(exit, 1)command indemos/mandel.asmjumps back to the Monitor. -
Enter
CTRL+Cand then in theQ>shell enterswitch 3and thenrun 0and then switch to the graphical window and enterCandCto clear the screen. Theswitch 3command routed STDIN now to the PS/2 keyboard emulation and STDOUT to the VGA screen. -
While being in the VGA screen, enter
Cand thenRand thena000. The Mandelbrot rendition is shown in the VGA screen. Press theCURSOR 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 theQ>prompt. PressCTRL+Dor enterexitto end 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.
-
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 usingwgetorcurl. 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. -
Open
qnice.cin a text editor and search forsy2002x.de. You should find a line that contains theemscripten_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:emscripten_wget("qnice_disk.img", "qnice_disk.img"); -
Build the WebAssembly/WebGL version of the emulator using
./make-wasm.bash. -
Run a local webserver by entering
python -m SimpleHTTPServer 8080. -
Point your web browser to
http://localhost:8080/qnice.html. -
Enter
Fand thenRand thenqbin/q-tris.outto play Q-TRIS.
-
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.
-
Press
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
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>
-
qniceandqnice-wasmare single-threaded.qnice-vgais multi-threaded. -
qnice.ccontains the main program and the CPU emulation. The functionint 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.hand.cfiles). -
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-vgaandqnice-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.
-
Input/Output is emulated by emulating a serial connection in
uart.c. -
As soon as the emulation runs (e.g. by entering
runin theQ>shell), the POSIX STDIN is switched from the usual line buffer mode where you need to end a line withENTERto an unbuffered mode. This is done inuart.cin the functionuart_hardware_initialization. -
select()andgetchar()are used to read from the keyboard. (Due toselect()having a timeout, this mechanism is not feasable in theqnice-vgamode, because it would significantly slow down the speed and would introduce skew and jitter for any automated MIPS calculation.)
-
Uses six threads. The threading is based on SDL's threading mechanisms for easy portability. Therefore all the threads are started using the function
int vga_create_thread(...)fromvga.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:
int vga_main_loop()invga.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 sameint main_loop(...)function, that also the POSIX terminalqniceis using. -
Various parts of the system need a consistent clock. This is why
int vga_timebase_thread(...)invga.cis updating the global variablegbl$sdl_ticksevery millisecond. -
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 functionvoid 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 ingbl$target_iptms. Due to jitter and skew, this value is only an approximation. Therefore the threadint mips_adjustment_thread(...)compares the actual MIPS with the target MIPS every three seconds and then calculates the adjustment factorgbl$target_iptms_adjustment_factorthat is multiplied withgbl$target_iptms. -
POSIX signal handlers are not working consistently and reliably in multithreaded environments. Therefore
static int signal_handler_ctrl_c_multithreaded(...)usessigwaitto wait for the user to pressCTRL+C. -
The thread
int uart_getchar_thread(...)inuart.cuses thepoll(...)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.
void vga_one_iteration_screen()invga.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.
void vga_render_to_pixelbuffer(...)invga.cis doing the job of modifing the pixel buffer in the arrayscreen_pixels. -
The various overlay windows that are visible in the context of speed adjustments (e.g. by pressing
ALT+vorALT+n) are rendered usingvoid vga_render_speedwin(...)invga.c. -
The keyboard management is currently hardcoded to a German keyboard using large and nested
ifandcasestructures invoid kbd_handle_keydown(...)(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 regularload_binary_file("monitor.out")function call. There is noQ>shell available inqnice-wasm. -
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
emscripten_wget(...)inqnice.c. -
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.emscripten_sleep(...)inqnice.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 fromfifo.cis utilized invga.cso that even if the FPS (aka "iterations per second") are low, no key presses are lost. -
qnice.htmluses the Emscripten Module interface.wasm-shell.htmlcontains the HTML5 and JavaScript code that hosts the WebAssembly/WebGL app. The<canvas>element is used for the WebGL drawing context. ThesetStatusfunction 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. Theemscripten_run_script(...)functions inqnice.care interacting withsetStatusand thestatusElement. -
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 filewasm-shell-release.htmlneeds to be updated manually, after changes inwasm-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.
