diff --git a/.gitignore b/.gitignore index f6696641..3264c80e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,12 @@ assembler/qasm assembler/qasm2rom register_file.sym emulator/qnice +emulator/qnice-vga +emulator/qnice.js +emulator/qnice.html +emulator/qnice.wasm +emulator/qnice.data +emulator/qnice_disk.img *.bin *.lis *.out diff --git a/README.md b/README.md index 132e3d31..700e98bc 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,10 @@ Getting Started --------------- If you do have FPGA hardware, then read on to learn how to get started. If -you don't, then you might want to use the emulator to experience QNICE. - -Switch to the [VGA Emulator Branch](https://github.com/sy2002/QNICE-FPGA/tree/develop-vga-emu) -to use the emulator without hardware and have a look at the README.md in -that branch to learn more. +you don't, then you might want to use the emulator to experience QNICE: +[Try it online](https://qnice-fpga.com/emulator.html) or have a look at +[emulator/README.md](emulator/README.md) to learn how to build and run your +own emulator. Get started using actual FPGA hardware: @@ -45,7 +44,8 @@ Get started using actual FPGA hardware: the fact, that there is an underlying git repository.) * Hardware: Currently, we develop QNICE-FPGA on a Nexys 4 DDR development - board, so if you own one, the fastest way of getting started is to + board. Nexys updated and renamed it to Nexys A7. + So, if you own one of those, the fastest way of getting started is to download the bitstream file `dist_kit/qnice-v141.bit` on a microSD card or a USB stick, insert it into the Nexys board and set the jumpers to read the FPGA configuration from the SD card or USB stick. Do not copy more than one @@ -54,7 +54,7 @@ Get started using actual FPGA hardware: copies, so that the Nexys board does not accidentally read the `*.bit` from your trash instead of the recent one. -* If you do not own a Nexys 4 DDR board, then use your VHDL development +* If you do not own a Nexys 4 DDR or A7 board, then use your VHDL development environment to synthesize QNICE-FPGA. The root file for the system is `vhdl/env1.vhdl`. Make sure that you connect at least the IO pins for PS2, VGA, UART and the two switches. In the file `nexys4ddr/env1.ucf` @@ -303,10 +303,12 @@ Acknowledgements * [sy2002](http://www.sy2002.de): Creator and maintainer of QNICE-FPGA: hardware development (VHDL), FAT32 library, additional Monitor libraries and - functions, Q-TRIS, additional QNICE specific vbcc toolchain. + functions, Q-TRIS, additional QNICE specific vbcc toolchain, + VGA and WebAssembly versions of the emulator. * [vaxman](http://www.vaxman.de): Inventor of the [QNICE ISA](http://qnice.sourceforge.net): - system architect, assembler, emulator, Monitor and Monitor libraries, tools. + system architect, assembler, original POSIX version of the emulator, + Monitor and Monitor libraries, tools. * [Volker Barthelmann](http://www.compilers.de): vbcc compiler system, QNICE specific vbcc backend incl. standard C library. diff --git a/TODO.txt b/TODO.txt index 24694aa8..97d1cc87 100644 --- a/TODO.txt +++ b/TODO.txt @@ -102,17 +102,52 @@ ASSEMBLER STANDARD LIBs (WITHIN MONITOR): EMULATOR: -* Emulate the VGA output and the USB keyboard (maybe using something like - LIBSDL? SFML? GLFW?). Optimally, the whole thing would by totally - dynamically loaded so that the emulator continues to run on text only - systems and on systems that don't have a multimedia library installed. - If this does not work, then we should just work with #defines within - the Emulator's code and compile two versions, like "qnice" and "qnice_vga". +* Use defines from ../dist_kit/sysdef.h instead of redefining them in qnice.c + +* Test, if compilation and running works under Linux (e.g. Ubuntu) + +* Test, if compilation and running works under Windows + +* Emulate all VGA register commands + +* WASM: Investigate if switching the WebGL context to high performance mode + makes sense; currently we run on the low performance mode on most browsers, + which means on Mac Books that the low performance GPU is used. Currently it + looks like this works absolutely well enough, but nevertheless investigating + this topic might be worth a try. + + Reference Emscripten Changelog.md: 1.39.9 searcch for #10505 + + https://www.khronos.org/registry/webgl/specs/latest/1.0/ + and search for "powerPreference" + +* macOS only: Make file option for creating a installable/clickable Mac + executable (learn how to do that first and also how to sign). Maybe the + demo disk image from qnice-fpga.com should also be mounted there by default + (but still other images should be mountable, too) + +* U.S. keyboard support + +* Homogeneous code style like vaxmans original style + (pointers, indentation, etc.) => try to find and use a coding style + modification tool and throughly check that no bugs are introduced + +* Better support for window resizing: offer some presets incl. aspect ratios + (see above) using Q> commands and maybe additionally also using ALT+ + combinations or a small "windowed menu" building on the speed regulator + window "system" . Optionally, as a bonus: Research menu items in SDL and + offer this via menus (seems to need very platformm specific code; + plugins/open source available?) + +* Improve the Full Screen mode by allowing various aspect ratios such as + 1.8:1 which is optimal for Q-TRIS, or standards like 4:3 and 16:9. + +* Support High-DPI Mode (needs to be signed on Mac?) NATIVE TOOLCHAIN: ASSEMBLER: - +* Get rid of hardcoded gcc in the assembler/asm script for precompiling. + Use tools/detect.include. But this needs thorough testing, if for example + the precompiler invokation works the same in clang vs. gcc. VBCC TOOLCHAIN: C COMPILER: @@ -156,7 +191,7 @@ DEMOS: * Forth Interpreter -* VGA Textmode games: Snake, Pac Man, 2048 +* VGA Textmode games: BreakOut, Snake, Pac Man, 2048 DOCUMENTATION: diff --git a/VERSIONS.txt b/VERSIONS.txt index 9e2e5595..b4700ec4 100644 --- a/VERSIONS.txt +++ b/VERSIONS.txt @@ -1,3 +1,12 @@ +Version 1.5 March, 22 2020 +============================== + +This version greatly enhances the capabilities and the stability of the +emulator. Detailed description: emulator/README.md + +* qnice-vga: VGA screen and PS/2 keyboard emulation +* qnice-wasm: WebAssembly/WebGL version that runs in the web browser + Version 1.41 December, 30 2016 ================================= diff --git a/doc/emumount.txt b/doc/emumount.txt index 353970c9..0966c311 100644 --- a/doc/emumount.txt +++ b/doc/emumount.txt @@ -24,7 +24,6 @@ For macOS: Here is an example, how you can add the folders "test_programs" and "qbin" from the QNICE-FPGA root folder to a newly created raw image file. - 1. hdiutil create -megabytes 33 -fs MS-DOS -fsargs "-F 32" -volname myimage -srcfolder qbin -srcfolder test_programs -o myimage.dmg 2. hdiutil convert myimage.dmg -format UDTO -o myimage @@ -35,6 +34,12 @@ IMPORTANT: hdiutil is only able to create FAT32 images, when the size of the image (the "-megabytes" parameter) is larger than 32, so we use 33 in the above-mentioned example. +IMPORTANT: In 2016, above-mentioned step (1) worked on my then current macOS +and machine. In 2020, step (1) strangely enough led to 100% CPU usage without +any file being created. Workaround: Remove the two "-srcfolder" statements, +let hdiutil create an empty DMG and then mount it using Finder. Then manually +copy the files you need. Then unmount and proceed with step (2). + Creating a raw image file from an existing device ------------------------------------------------- @@ -59,7 +64,7 @@ to do it on a Mac: Mounting a raw image file ------------------------- -Use the new command like parameter -a to mount a far image file: +Use the new command line parameter -a to mount a raw image file: ./qnice -a @@ -99,3 +104,4 @@ Example of how to do it on a Mac: written by sy2002 in December 2016 +latest update by sy2002 in February 2020 diff --git a/doc/github/vgaemu.jpg b/doc/github/vgaemu.jpg new file mode 100644 index 00000000..4957c8e0 Binary files /dev/null and b/doc/github/vgaemu.jpg differ diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 00000000..947e36ba --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,7 @@ +Minimum requirements to work with QNICE-FPGA: + +* bash +* CC or GCC +* perl +* If you want to synthesize your own bitstream (.bit): Xilinx ISE + diff --git a/emulator/README.md b/emulator/README.md new file mode 100644 index 00000000..958a3e38 --- /dev/null +++ b/emulator/README.md @@ -0,0 +1,430 @@ +QNICE Emulator +============== + +![QNICE_VGA_EMU_Img](../doc/github/vgaemu.jpg) + +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.bash` + to 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.bash` to build or use `run-vga.bash` to + 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.bash` + to build. + Try a [prebuilt version online here](http://qnice-fpga.com/emulator.html), + which mounts a FAT32 disk image that contains among other things + also Q-TRIS. + +Getting Started +--------------- + +### 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 `QNICE-FPGA` and enter + `git clone https://github.com/sy2002/QNICE-FPGA.git` + +* After that, you should have a folder called `QNICE-FPGA`. `cd` into 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 `gcc` and `make` will be used. Open a terminal in + the QNICE root folder. Enter the following (it is important, that you `cd` + into the folder): + ``` + cd tools + ./make-toolchain.sh + ``` + You will be asked several questions. Answer them using the default answers + by pressing `Enter` instead of answering manually by choosing `y` or `n`. + +* Compile the Monitor, which is akin to an operating system for QNICE-FPGA. If + you are still in the `tools` folder, then enter: + ``` + cd ../monitor + ./compile_and_distribute.sh + ``` + The resulting `monitor.out` file is what the emulator needs. + +* Given, that you are still in the `monitor` folder, 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 that `pbcopy` is not available. You can savely + ignore this and manually copy the file `demos/mandel.out` into 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 `emulator` folder and compile the emulator + using `./make.bash`. + +* Run the emulator and let it instantly load the Monitor: + `./qnice ../monitor/monitor.out` + +* Enter `M` and then `L` into the Monitor window. After that, you should + see something like `QMON> MEMORY/LOAD - ENTER ADDRESS/VALUE PAIRS, + TERMINATE WITH CTRL-E` + +* Press `CMD+V` for PASTING the mandel.out textfile, that should be still + in your clipboard, if you followed the above-mentioned steps. + +* Press `CTRL-E` now, to go back to the Monitor. You should see the `QMON>` + prompt again. (This mechanism of loading `.out` files into the emulator can + also be used while running the below-mentioned `qnice-vga`, even though + it is not explicitly mentioned again there.) + +* Enter `C` and then `R` and then `A000`. + +* You should see a textmode rendition of the famous Mandelbrot set. + +* Press `CTRL+C` to leave the Monitor and to return back to the `Q>` prompt. + Press `CTRL+D` or enter `exit` to end the emulator. + +### SDL OpenGL Window: Emulation of the VGA Screen and the PS/2 Keyboard + +* You need [libsdl](https://www.libsdl.org/) for compiling. + +* If you are connected to the Internet, then enter `./run-vga.bash` to + 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 `F` and then + `R` and then `qbin/q-tris.out` to run Q-TRIS. + +* Go to the graphical window, press `SPACE` and start playing. The emulator + automatically attempts to regulate the speed to `12.9 MIPS`, which is + the speed of the original QNICE-FPGA hardware running at 50 MHz. Toggle + the MIPS and FPS display using `ALT+F`. + +* Go back to the terminal window where you see `Running...` and press + `CTRL+C` to end Q-TRIS. + +* Enter `help` to see the various Monitor commands and keyboard shortcuts. + +* You can for example use the `mips max` command to set the emulation speed + to the maximum speed that your computer can support. After that, enter + `speedstats on` to see how many MIPS this will mean and after that enter + `run $8000` to 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+C` in the terminal emulation screen to return to the + emulator's `Q>` 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 the `load` command in the `Q>` 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 enter `run $a000`. You will see the textmode + rendition of the Mandelbrot set in the POSIX terminal window. + +* The `Q>` prompt is replaced by the `QMON>` prompt because the + `SYSCALL(exit, 1)` command in `demos/mandel.asm` jumps back to the Monitor. + +* Enter `CTRL+C` and then in the `Q>` shell enter `switch 3` and then `run 0` + and then switch to the graphical window and enter `C` and `C` to clear + the screen. The `switch 3` command routed STDIN now to the PS/2 keyboard + emulation and STDOUT to the VGA screen. + +* While being in the VGA screen, enter `C` and then `R` and then `a000`. + The Mandelbrot rendition is shown in the VGA screen. Press the + `CURSOR UP` key three times to see the rendition nicely centered. + +* Go to the POSIX terminal window, press `CTRL+C` to leave the Monitor + and to return back to the `Q>` prompt. Press `CTRL+D` or enter `exit` to + end the emulator. + +WebAssembly/WebGL in a Web Browser: Emulation of the VGA Screen and the PS/2 Keyboard +------------------------------------------------------------------------------------- + +* You can [try it out online](http://qnice-fpga.com/emulator.html). + 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 `wget` or `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](https://emscripten.org/docs/getting_started/downloads.html). + Activate the Emscripten SDK using the `source emsdk_env.sh` command while + being in the Emscripten SDK home folder. + +* Open `qnice.c` in 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: + ``` + 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 `F` and then `R` and then `qbin/q-tris.out` to 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 `12.9 MIPS` of 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+f` to 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+v` to 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: + +``` + +AddOutputFilter DEFLATE img + +``` + +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: + +``` + +Header set Access-Control-Allow-Origin "*" + +``` + +Emulator Architecture +--------------------- + +* `qnice` and `qnice-wasm` are single-threaded. `qnice-vga` is multi-threaded. + +* `qnice.c` contains 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 `.h` and `.c` files). + +* 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-vga` and `qnice-wasm` need a FIFO for their keyboard input, albeit + at completely different spots in their logic. `fifo.c` is a simple + but yet thread-safe implementation of such a FIFO. + +### POSIX Terminal (`qnice`) Specifics + +* Input/Output is emulated by emulating a serial connection in `uart.c`. + +* As soon as the emulation runs (e.g. by entering `run` in the `Q>` shell), + the POSIX STDIN is switched from the usual line buffer mode where you + need to end a line with `ENTER` to an unbuffered mode. This is done in + `uart.c` in the function `uart_hardware_initialization`. + +* `select()` and `getchar()` are used to read from the keyboard. (Due to + `select()` having a timeout, this mechanism is not feasable in the + `qnice-vga` mode, because it would significantly slow down the speed + and would introduce skew and jitter for any automated MIPS calculation.) + +### SDL OpenGL Window (`qnice-vga`) Specifics + +* 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(...)` from `vga.c` which 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()` in `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 + `qnice` is using. + +* Various parts of the system need a consistent clock. This is why + `int vga_timebase_thread(...)` in `vga.c` is updating the global variable + `gbl$sdl_ticks` every millisecond. + +* The original QNICE-FPGA hardware performs `12.9 MIPS` while 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_factor` + that is multiplied with `gbl$target_iptms`. + +* POSIX signal handlers are not working consistently and reliably in + multithreaded environments. Therefore + `static int signal_handler_ctrl_c_multithreaded(...)` uses `sigwait` to + wait for the user to press `CTRL+C`. + +* The thread `int uart_getchar_thread(...)` in `uart.c` uses the `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.c` uses + 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()` in `vga.c` shows 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(...)` in `vga.c` is doing the job of + modifing the pixel buffer in the array `screen_pixels`. + +* The various overlay windows that are visible in the context of speed + adjustments (e.g. by pressing `ALT+v` or `ALT+n`) are rendered using + `void vga_render_speedwin(...)` in `vga.c`. + +* The keyboard management is currently hardcoded to a German keyboard using + large and nested `if` and `case` structures in + `void kbd_handle_keydown(...)` (`vga.c`). Future versions of the emulator + might want to utilize more flexible mechanisms. + +### WebAssembly/WebGL (`qnice-wasm`) Specifics + +* 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.data` + contains 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 `qnice-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(...)` in `qnice.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(...)` + in `qnice.c` performs 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.c` is utilized in `vga.c` so that even if the + FPS (aka "iterations per second") are low, no key presses + are lost. + +* `qnice.html` uses the Emscripten + [Module interface](https://emscripten.org/docs/api_reference/module.html). + `wasm-shell.html` contains the HTML5 and JavaScript code that hosts + the WebAssembly/WebGL app. The `` element is used for the WebGL + drawing context. The `setStatus` function 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 `emscripten_run_script(...)` functions + in `qnice.c` are interacting with `setStatus` and the `statusElement`. + +* The GitHub web pages that host `qnice-wasm` are available at + [qnice-fpga.com](https://qnice-fpga.com) and are rendered using + [Jekyll](https://jekyllrb.com/). Switch to the branch + [gh-pages](https://github.com/sy2002/QNICE-FPGA/tree/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 `` tag. + Therefore the file `wasm-shell-release.html` needs to be updated + manually, after changes in `wasm-shell.html` have 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. + + diff --git a/emulator/fifo.c b/emulator/fifo.c new file mode 100644 index 00000000..7b66a22b --- /dev/null +++ b/emulator/fifo.c @@ -0,0 +1,93 @@ +/* +** Very simple FIFO +** +** Originally intended use-case is keyboard FIFO for the UART in VGA mode and for SDL keyboard +** +** done by sy2002 in February 2020 +** +*/ + +#include +#include + +#include "fifo.h" + +fifo_t* fifo_init(unsigned int size) +{ + fifo_t* fifo = malloc(sizeof(fifo_t)); + if (fifo && (fifo->data = malloc(size * sizeof(int)))) + { +#ifndef __EMSCRIPTEN__ + fifo->mutex = SDL_CreateMutex(); +#endif + fifo->size = size; + fifo_clear(fifo); + return fifo; + } + else + { + printf("Out of memory error (fifo.c: fifo_init)\n"); + exit(1); + return 0; + } +} + +void fifo_free(fifo_t* fifo) +{ +#ifndef __EMSCRIPTEN__ + SDL_DestroyMutex(fifo->mutex); +#endif + free(fifo->data); + free(fifo); +} + +void fifo_clear(fifo_t* fifo) +{ +#ifndef __EMSCRIPTEN__ + SDL_LockMutex(fifo->mutex); + fifo->head = fifo->tail = fifo->count = 0; + SDL_UnlockMutex(fifo->mutex); +#else + fifo->head = fifo->tail = fifo->count = 0; +#endif +} + +void fifo_push(fifo_t* fifo, int data) +{ +#ifndef __EMSCRIPTEN__ + SDL_LockMutex(fifo->mutex); +#endif + if (fifo->count < fifo->size) + { + fifo->data[fifo->head] = data; + fifo->count++; + if (fifo->head < (fifo->size - 1)) + fifo->head++; + else + fifo->head = 0; + } +#ifndef __EMSCRIPTEN__ + SDL_UnlockMutex(fifo->mutex); +#endif +} + +int fifo_pull(fifo_t* fifo) +{ +#ifndef __EMSCRIPTEN__ + SDL_LockMutex(fifo->mutex); +#endif + int retval = 0; + if (fifo->count) + { + retval = fifo->data[fifo->tail]; + fifo->count--; + if (fifo->tail < (fifo->size - 1)) + fifo->tail++; + else + fifo->tail = 0; + } +#ifndef __EMSCRIPTEN__ + SDL_UnlockMutex(fifo->mutex); +#endif + return retval; +} diff --git a/emulator/fifo.h b/emulator/fifo.h new file mode 100644 index 00000000..d218b6aa --- /dev/null +++ b/emulator/fifo.h @@ -0,0 +1,36 @@ +/* +** Header file for the FIFO +** +** done by sy2002 in February 2020 +** +*/ + +#ifndef _QEMU_FIFO_H +#define _QEMU_FIFO_H + +#ifndef __EMSCRIPTEN__ +# include "SDL.h" +#endif + +struct fifo_type_s +{ + unsigned int size; //overall size of FIFO < sizeof (unsigned int) + unsigned int count; //amount of data + unsigned int head; //position where the next push puts data to + unsigned int tail; //position where the net pull gets data from + int* data; //data buffer + +#ifndef __EMSCRIPTEN__ + SDL_mutex* mutex; //avoid race conditions: push vs. pull +#endif +}; + +typedef struct fifo_type_s fifo_t; + +fifo_t* fifo_init(unsigned int size); +void fifo_free(fifo_t* fifo); +void fifo_clear(fifo_t* fifo); +void fifo_push(fifo_t* fifo, int data); +int fifo_pull(fifo_t* fifo); + +#endif \ No newline at end of file diff --git a/emulator/make-vga.bash b/emulator/make-vga.bash new file mode 100755 index 00000000..0828a3bf --- /dev/null +++ b/emulator/make-vga.bash @@ -0,0 +1,47 @@ +#!/bin/bash + +if [ ! -f qnice-vga ]; then + echo "Build the QNICE Emulator with VGA and PS/2 (USB) keyboard support." + echo "" + echo "Some hints:" + echo "* You need to have SDL2 installed for compiling." + echo "* We are linking static libs, so SDL2 does not need to be installed" + echo " on the end user's machine." + echo "* Run the executable with ./qnice-vga" + echo "" + read -p "Press ENTER to continue or CTRL+C to quit." +fi + +source ../tools/detect.include + +SDL2_CFLAGS=`sdl2-config --cflags` + +if [ -z "$SDL2_CFLAGS" ]; then + echo "" + echo "error: SDL2 not found!" + echo "" + if [ "$OSTP" = "OSX" ]; then + echo "On OSX we suggest using the Homebrew package manager. Install it " + echo "via http://brew.sh and then install SDL2 by entering: brew install sdl2" + echo "Alternatively, go to https://www.libsdl.org" + + else + echo "Use your favorite package manager or go to https://www.libsdl.org" + fi + exit +fi + +if [ $OSTP = "OSX" ]; then + #On OSX sdl2-config is not returning the right string to build statically, so we + #need to create it manually by finding out the path to the SDL2 library and then + #by replacing the lSDL2 part in the output of sdl2-config + PATH_TO_SDL2LIB=$(sdl2-config --static-libs | perl -pe 's|(-L/.+?\s).*|\1|' | cut -c 3- | rev | cut -c 2- | rev)"/libSDL2.a" + SDL2_LIBS=$(sdl2-config --static-libs | sed 's|-lSDL2|'$PATH_TO_SDL2LIB'|') +else + SDL2_LIBS=`sdl2-config --static-libs` +fi + +FILES="qnice.c fifo.c sd.c uart.c vga.c" +DEF_SWITCHES="-DUSE_SD -DUSE_UART -DUSE_VGA" +UNDEF_SWITCHES="-UUSE_IDE -U__EMSCRIPTEN__" +$COMPILER $FILES -O3 $DEF_SWITCHES $UNDEF_SWITCHES $SDL2_CFLAGS $SDL2_LIBS -o qnice-vga diff --git a/emulator/make-wasm.bash b/emulator/make-wasm.bash new file mode 100755 index 00000000..297a1520 --- /dev/null +++ b/emulator/make-wasm.bash @@ -0,0 +1,58 @@ +#!/bin/bash + +if [[ ! -f qnice.js ]] || [[ ! -f qnice.wasm ]] || [[ ! -f qnice.html ]] || [[ ! -f qnice.data ]]; then + echo "Build the QNICE Emulator with VGA and PS/2 (USB) keyboard support" + echo "for the WebAssembly/WebGL target using the Emscripten toolchain" + echo "" + echo "Some hints:" + echo "* Emscripten is a dependency: https://emscripten.org/" + echo "* The Emscripten environment needs to be active: source emsdk_env.sh" + echo "* You need to have SDL2 installed for compiling." + echo "* A FAT32 SD Card image named qnice_disk.img needs to be present" + echo " (read ../doc/emumount.txt to learn how to create one)" + echo "* The monitor needs to be present at ../monitor/monitor.out" + echo "* The resulting executables are qnice.wasm, qnice.js and qnice.html" + echo " and qnice.data contains the Monitor (operating system)" + echo "* If you want to create an embeddable release version of qnice.html" + echo " then run this script having RELEASE as parameter: ./make-wasm.bash RELEASE" + echo "* If you are developing the release version, then use the parameter DEVELOP-RELEASE" + echo "* Use for example Python's minimal webserver to serve the executables:" + echo " python -m SimpleHTTPServer 8080" + echo "" + read -p "Press ENTER to continue or CTRL+C to quit." +fi + +command -v emcc >/dev/null 2>&1 || { + echo >&2 "" + echo >&2 "emcc from Emscripten toolchain not found." + echo >&2 "Activate it with: source /emsdk_env.sh" + echo "" + exit 1 +} + +EMCC_VERSION=`emcc --version|grep emcc|egrep -o "([0-9]{1,}\.)+[0-9]{1,}"` +if [[ $EMCC_VERSION != "1.39.10" ]]; then + echo "Warning: Emscripten SDK other than 1.39.10 might lead to errors." + echo "(see also https://github.com/emscripten-core/emscripten/issues/10746)" +fi + +if [[ ! -f qnice_disk.img ]]; then + echo "Warning: qnice_disk.img not found. You can still compile the emulator." +fi + +FILES="qnice.c fifo.c sd.c vga.c" +DEF_SWITCHES="-DUSE_SD -DUSE_VGA" +UNDEF_SWITCHES="-UUSE_IDE -UUSE_UART" +PRELOAD_FILES="--preload-file monitor.out" + +if [ "$1" == "DEVELOP-RELEASE" ]; then + SHELL_FILE="--shell-file wasm-shell.html" +fi + +if [ "$1" == "RELEASE" ]; then + SHELL_FILE="--shell-file wasm-shell-release.html" +fi + +cp ../monitor/monitor.out . +emcc $FILES -O3 -s ASYNCIFY -s ASYNCIFY_IGNORE_INDIRECT -s USE_SDL=2 $SHELL_FILE $DEF_SWITCHES $UNDEF_SWITCHES $PRELOAD_FILES -o qnice.html +rm monitor.out diff --git a/emulator/make.bash b/emulator/make.bash index 60859900..6c0a13ad 100755 --- a/emulator/make.bash +++ b/emulator/make.bash @@ -1,2 +1,6 @@ #!/bin/bash -cc qnice.c ide_simulation.c uart.c sd.c -o qnice +source ../tools/detect.include +FILES="qnice.c uart.c sd.c" +DEF_SWITCHES="-DUSE_SD -DUSE_UART" +UNDEF_SWITCHES="-UUSE_VGA -UUSE_IDE -U__EMSCRIPTEN__" +$COMPILER $FILES -O3 $DEF_SWITCHES $UNDEF_SWITCHES -o qnice diff --git a/emulator/qnice.c b/emulator/qnice.c index 0679ce5b..12d3bfe5 100644 --- a/emulator/qnice.c +++ b/emulator/qnice.c @@ -1,35 +1,59 @@ /* -** QNICE emulator -- this emulator was written as a proof of concept for the -** QNICE processor. +** QNICE emulator -- this emulator was written as a proof of concept for the QNICE processor. ** ** B. Ulmann, 16-AUG-2006...03-SEP-2006...04-NOV-2006...29-JUN-2007... ** 16-DEC-2007...03-JUN-2008...28-DEC-2014... ** xx-AUG-2015...xx-MAY-2016... ** 28-DEC-2016, 29-DEC-2016 ** +** sy2002, on-again-off-again 2017 and 2020: vga emulator and emscripten +** +** The following defines are available: +** +** USE_IDE (currently always undefined) +** USE_SD +** USE_UART +** USE_VGA +** +** The different make scripts "make.bash", "make-vga.bash" and "make-emscripten.bash" +** are defining these. The emscripten environment is automatically defining __EMSCRIPTEN__. */ -#define USE_UART -#define USE_SD -#undef USE_IDE +#undef USE_IDE +#include #include #include #include -#include #include -#include -#ifdef USE_UART -# include "uart.h" +#include "../dist_kit/sysdef.h" + +#ifdef USE_IDE +# include "ide_simulation.h" #endif #ifdef USE_SD # include "sd.h" #endif -#ifdef USE_IDE -# include "ide_simulation.h" +#ifdef USE_UART +# include "uart.h" +#endif + +#ifdef USE_VGA +# include "vga.h" +# ifndef __EMSCRIPTEN__ +# include +# include +# include +# endif +#endif + +#ifdef __EMSCRIPTEN__ +# include "emscripten.h" +#else +# include #endif /* @@ -77,11 +101,15 @@ #define GENERIC_BRANCH_OPCODE 0xf /* All branches share this common opcode */ +#ifdef USE_UART +uart gbl$first_uart; +#endif + typedef struct statistic_data { - unsigned int instruction_frequency[NO_OF_INSTRUCTIONS], /* Count the number of executions per instruction */ - addressing_modes[2][NO_OF_ADDRESSING_MODES], /* 0 -> read, 1 -> write */ - memory_accesses[2]; /* 0 -> read, 1 -> write */ + unsigned long long instruction_frequency[NO_OF_INSTRUCTIONS], /* Count the number of executions per instruction */ + addressing_modes[2][NO_OF_ADDRESSING_MODES], /* 0 -> read, 1 -> write */ + memory_accesses[2]; /* 0 -> read, 1 -> write */ } statistic_data; int gbl$memory[MEMORY_SIZE], gbl$registers[REGMEM_SIZE], gbl$debug = FALSE, gbl$verbose = FALSE, @@ -92,25 +120,120 @@ int gbl$memory[MEMORY_SIZE], gbl$registers[REGMEM_SIZE], gbl$debug = FALSE, gbl$ unsigned long long gbl$cycle_counter = 0l; /* This cycle counter is effectively an instruction counter... */ char *gbl$normal_mnemonics[] = {"MOVE", "ADD", "ADDC", "SUB", "SUBC", "SHL", "SHR", "SWAP", - "NOT", "AND", "OR", "XOR", "CMP", "rsrvd", "HALT"}, + "NOT", "AND", "OR", "XOR", "CMP", "rsvd", "HALT"}, *gbl$branch_mnemonics[] = {"ABRA", "ASUB", "RBRA", "RSUB"}, *gbl$sr_bits = "1XCZNVIM", *gbl$addressing_mnemonics[] = {"rx", "@rx", "@rx++", "@--rx"}; statistic_data gbl$stat; -#ifdef USE_UART -uart gbl$first_uart; +bool gbl$cpu_running = false; //thread-sync: is the CPU currently running? +bool gbl$shutdown_signal = false; //thread-sync: shut down the emulator when set to true +bool gbl$initial_run = true; //thread-sync: is the current run() the very first one? + +#if defined(USE_VGA) && !defined(__EMSCRIPTEN__) +sigset_t gbl$sigset; //multithreaded signal handling +pthread_t ctrlc_thread_id = 0; //used for killing the signal handler thread +#endif + +#ifdef USE_VGA +float gbl$mips = 0; //actual MIPS (measured each second) +unsigned long gbl$mips_inst_cnt = 0; //amount of instructions in the current second +Uint32 gbl$mips_tick_cnt = 0; //used to measure a second (1000 ticks == 1000 ms == 1s) +extern unsigned long gbl$sdl_ticks; //global timer in milliseconds +bool gbl$speedstats = false; //show MIPS and FPS in VGA window + +#ifdef __EMSCRIPTEN__ +/* one iteration in emscripten mode means: + 1. execute n instructions, where n == gbl$instructions_per_iteration + 2. hand back control to the browser (cooperative multitasking) + 3. handle keyboard + 4. draw screen +*/ +const unsigned long gbl$ipi_default = 500000; +unsigned long gbl$instructions_per_iteration = gbl$ipi_default; +#endif + +/* According to ../test_programs/mandel_perf_test.asm, the current QNICE hardware, + which runs at 50 MHz performs at 12.93 MIPS. + The speed regulation occurs every 10 milliseconds, which is why we introduce + a "target instructions per 10-milliseconds" measurement using gbl$target_iptms. + Since there is system jitter, the gbl$target_iptms needs to be adjusted, + which is done by sampling the actual MIPS every 3 seconds and calculating + the gbl$target_iptms_adjustment_factor in the thread "mips_adjustment_thread" */ +const float gbl$qnice_mips = 12.93; +const float gbl$max_mips = INFINITY; +float gbl$target_mips = gbl$qnice_mips; +unsigned long gbl$target_iptms = ((gbl$qnice_mips * 1e6) / 1e3) * 10; +float gbl$target_iptms_adjustment_factor = 1.0; +const unsigned long gbl$target_sampling_s = 3; +bool mips_adjustment_thread_running = false; +bool gbl$target_mips_changed = false; + +void gbl_set_target_mips(float new_mips) +{ + if (new_mips != gbl$max_mips) + { + gbl$target_mips = new_mips > 0 ? new_mips : gbl$qnice_mips; + gbl$target_iptms = ((gbl$target_mips * 1e6) / 1e3) * 10; + } + else + { + gbl$target_mips = gbl$max_mips; + gbl$target_iptms = 1000; + } +} + +void gbl_change_target_mips(float delta) +{ + if (gbl$target_mips == gbl$max_mips) + gbl_set_target_mips(gbl$mips + delta); + else + gbl_set_target_mips(gbl$target_mips + delta); +} #endif /* -** +** QNICE VGA wordexp stub for emscripten +** some versions of emscripten might not supoprt wordexp +** see also https://github.com/emscripten-core/emscripten/issues/10403 +** if your version does not support it, then activate this stub +** by commenting in the following section +*/ + +/* +#ifdef __EMSCRIPTEN__ +int wordexp(const char *s, wordexp_t *p, int flags) +{ + p->we_wordv[0] = (char*) s; + return 0; +} +#endif +*/ + +/* +** use CTRL+c to pause emulation and to return back to the emulator's console */ static void signal_handler_ctrl_c(int signo) { gbl$ctrl_c = TRUE; } +#if defined(USE_VGA) && !defined(__EMSCRIPTEN__) +static int signal_handler_ctrl_c_multithreaded(void* param) +{ + ctrlc_thread_id = pthread_self(); + while (!gbl$shutdown_signal) + { + int signum; + sigwait(&gbl$sigset, &signum); + gbl$ctrl_c = TRUE; + } + ctrlc_thread_id = 0; + return 0; +} +#endif + /* ** upstr converts a string into upper case. */ @@ -118,8 +241,7 @@ void upstr(char *string) { while (*string) { - if (*string >= 'a' && *string <= 'z') - *string += -'a' + 'A'; + *string = (char) toupper(*string); string++; } } @@ -289,6 +411,12 @@ unsigned int access_memory(unsigned int address, unsigned int operation, unsigne else if (address >= UART0_BASE_ADDRESS && address < UART0_BASE_ADDRESS + 8) /* Some UART0 operation */ value = uart_read_register(&gbl$first_uart, address - UART0_BASE_ADDRESS); #endif +#ifdef USE_VGA + else if (address >= VGA_STATE && address <= VGA_OFFS_RW) /* VGA register */ + value = vga_read_register(address); + else if (address >= IO_KBD_STATE && address <= IO_KBD_DATA) + value = kbd_read_register(address); +#endif #ifdef USE_IDE else if (address >= IDE_BASE_ADDRESS && address < IDE_BASE_ADDRESS + 16) /* Some IDE operation */ value = readIDEDeviceRegister(address - IDE_BASE_ADDRESS); @@ -358,6 +486,12 @@ unsigned int access_memory(unsigned int address, unsigned int operation, unsigne uart_write_register(&gbl$first_uart, address - UART0_BASE_ADDRESS, value & 0xff); } #endif +#ifdef USE_VGA + if (address >= VGA_STATE && address <= VGA_OFFS_RW) /* VGA register */ + vga_write_register(address, value); + if (address >= IO_KBD_STATE && address <= IO_KBD_DATA) + kbd_write_register(address, value); +#endif #ifdef USE_IDE if (address >= IDE_BASE_ADDRESS && address < IDE_BASE_ADDRESS + 16) /* Some IDE operation */ writeIDEDeviceRegister(address - IDE_BASE_ADDRESS, value); @@ -394,6 +528,11 @@ void reset_machine() gbl$stat.addressing_modes[0][i] = gbl$stat.addressing_modes[1][i] = 0; gbl$stat.memory_accesses[0] = gbl$stat.memory_accesses[1] = 0; + /* Route use the USB keyboard emulation for stdin and VGA for stdout */ +#if defined(__EMSCRIPTEN__) || (defined(USE_VGA) && !defined(USE_UART)) + gbl$memory[IO_SWITCH_REG] = 3; +#endif + if (gbl$debug || gbl$verbose) printf("\treset_machine: done\n"); } @@ -455,7 +594,7 @@ void disassemble(unsigned int start, unsigned int stop) { if (opcode == 0xd) /* This one is reserved for future use! */ { - strcpy(mnemonic, "RSRVD"); + strcpy(mnemonic, "RSVD"); *operands = (char) 0; } else @@ -613,6 +752,17 @@ int execute() int condition, cmp_0, cmp_1; +#ifdef USE_VGA + /* global instruction counter for MIPS calcluation; slightly different semantics than gbl$cycle_counter++ */ + gbl$mips_inst_cnt++; + if (gbl$sdl_ticks - gbl$mips_tick_cnt > 1000) + { + gbl$mips = (float) gbl$mips_inst_cnt / (float) 1000000; + gbl$mips_inst_cnt = 0; + gbl$mips_tick_cnt = gbl$sdl_ticks; + } +#endif + if (gbl$cycle_counter_state & 0x0002) gbl$cycle_counter++; /* Increment cycle counter which is an instruction counter in the emulator as opposed to the hardware. */ @@ -805,8 +955,59 @@ int execute() return FALSE; /* No HALT instruction executed */ } +#if defined(USE_VGA) && !defined(__EMSCRIPTEN__) +int mips_adjustment_thread(void* param) +{ + mips_adjustment_thread_running = true; + usleep(1e6); + float samples = 0; + int sample_count = 0; + while (!gbl$shutdown_signal) + { + usleep(1e6); + + if (gbl$cpu_running) + { + samples += gbl$mips; + sample_count++; + } + + if (sample_count == gbl$target_sampling_s) + { + float avg_mips = samples / (float) gbl$target_sampling_s; + /* avg_mips > 0: avoiding division by zero after restarting after CTRL+C + checking gbl$target_mips_changed: if the mips value recently was changed in the other thread using the MIPS command, + then the avg_mips value contains nonsense and the whole process of regulating speed would take longer than necessary, + since a few more iterations of this adjustment cycle would need to run; this is why we avoid this by checking */ + if (avg_mips > 0 && !gbl$target_mips_changed && gbl$target_mips != gbl$max_mips) + { + gbl$target_iptms_adjustment_factor *= (gbl$target_mips / avg_mips); // "*=" because the factor itself needs to be adjusted + + // make sure that the value of the factor is not going off-limits in edge-cases + if (gbl$target_iptms_adjustment_factor < 0.25) + gbl$target_iptms_adjustment_factor = 0.25; + else if (gbl$target_iptms_adjustment_factor > 3) + gbl$target_iptms_adjustment_factor = 3; + } + else + { + gbl$target_iptms_adjustment_factor = 1.0; + if (gbl$target_mips_changed) + gbl$target_mips_changed = false; + } + samples = 0; + sample_count = 0; + } + } + mips_adjustment_thread_running = false; + return 1; +} +#endif + void run() { + if (gbl$initial_run) + gbl$initial_run = false; gbl$ctrl_c = FALSE; #ifdef USE_UART @@ -814,7 +1015,31 @@ void run() #endif gbl$gather_statistics = TRUE; - while (!execute() && !gbl$ctrl_c); + gbl$cpu_running = true; + +#if defined(USE_VGA) && !defined(__EMSCRIPTEN__) + unsigned long instruction_counter = gbl$target_iptms; + struct timespec tstart, tend; + clock_gettime(CLOCK_REALTIME, &tstart); +#endif + + while (!execute() && !gbl$ctrl_c && !gbl$shutdown_signal) + { +#if defined(USE_VGA) && !defined(__EMSCRIPTEN__) + if (gbl$target_mips != gbl$max_mips) + if (!--instruction_counter) + { + clock_gettime(CLOCK_REALTIME, &tend); + unsigned long delta_us = ((tend.tv_sec * 1e9 + tend.tv_nsec) - (tstart.tv_sec * 1e9 + tstart.tv_nsec)) / 1000.0f; + if (delta_us < 10e3) + usleep(10e3 - delta_us); + instruction_counter = (unsigned long) ((float) gbl$target_iptms * (float) gbl$target_iptms_adjustment_factor); + clock_gettime(CLOCK_REALTIME, &tstart); + } +#endif + } + + gbl$cpu_running = false; if (gbl$ctrl_c) printf("\n\tAborted by CTRL-C!\n"); gbl$gather_statistics = FALSE; @@ -822,23 +1047,30 @@ void run() #ifdef USE_UART uart_run_down(); #endif + +#if defined(USE_VGA) && defined(USE_UART) && !defined(__EMSCRIPTEN__) + /* wait until the the asynchronous (non-blocking) keyboard input thread ended + (the end signal is setting gbl$cpu_running to false) */ + while (uart_getchar_thread_running) + usleep(10000); +#endif } void print_statistics() { - unsigned int i, value; + unsigned long long i, value; for (i = value = 0; i < NO_OF_INSTRUCTIONS; value += gbl$stat.instruction_frequency[i++]); if (!value) printf("No statistics have been gathered so far!\n"); else { - printf("\n%d memory reads, %d memory writes and\n%d instructions have been executed so far:\n\n\ -INSTR ABSOLUTE RELATIVE INSTR ABSOLUTE RELATIVE\n\ ------------------------------------------------\n", + printf("\n%llu memory reads, %llu memory writes and\n%llu instructions have been executed so far:\n\n\ +INSTR ABSOLUTE RELATIVE INSTR ABSOLUTE RELATIVE\n\ +---------------------------------------------------------------\n", gbl$stat.memory_accesses[READ_MEMORY], gbl$stat.memory_accesses[WRITE_MEMORY], value); for (i = 0; i < NO_OF_INSTRUCTIONS; i++) - printf("%s%-4s: %8d (%5.2f%%)\t", + printf("%s%-4s: %16llu (%5.2f%%)\t", !(i & 1) && i ? "\n" : "", /* New line every second round */ i < GENERIC_BRANCH_OPCODE ? gbl$normal_mnemonics[i] : gbl$branch_mnemonics[i - GENERIC_BRANCH_OPCODE], @@ -851,11 +1083,11 @@ INSTR ABSOLUTE RELATIVE INSTR ABSOLUTE RELATIVE\n\ printf("\n\nThere have not been any memory references so far!\n"); else { - printf("\n\n READ ACCESSES WRITE ACCESSES\n\ -MODE ABSOLUTE RELATIVE MODE ABSOLUTE RELATIVE\n\ ------------------------------------------------------------\n"); + printf("\n\n READ ACCESSES WRITE ACCESSES\n\ +MODE ABSOLUTE RELATIVE MODE ABSOLUTE RELATIVE\n\ +------------------------------------------------------------------------\n"); for (i = 0; i < NO_OF_ADDRESSING_MODES; i++) - printf("%-5s: %8d (%5.2f%%) \t%-5s: %8d (%5.2f%%)\n", + printf("%-5s: %16llu (%5.2f%%) \t%-5s: %16llu (%5.2f%%)\n", gbl$addressing_mnemonics[i], gbl$stat.addressing_modes[0][i], (float) (100 * gbl$stat.addressing_modes[0][i]) / (float) value, gbl$addressing_mnemonics[i], gbl$stat.addressing_modes[1][i], @@ -929,57 +1161,28 @@ void dump_registers() printf("\n\n"); } -int main(int argc, char **argv) +int main_loop(char **argv) { char command[STRING_LENGTH], *token, *delimiters = " ,", scratch[STRING_LENGTH]; unsigned int start, stop, i, j, address, value, last_command_was_step = 0; wordexp_t expanded_filename; FILE *handle; - signal(SIGINT, signal_handler_ctrl_c); - reset_machine(); - -#ifdef USE_IDE - initializeIDEDevice(); -#endif - - if (*++argv) /* At least one argument */ + if (*argv) { - if (!strcmp(*argv, "-h")) - { - printf("\nUsage:\n\ - \"qnice\" without arguments will start an interactive session\n\ - \"qnice -h\" will print this help text\n\ - \"qnice -a \" will attach an SD-card image file\n\ - \"qnice -a \" attaches an images and runs a file\n\ - \"qnice \" will run in batch mode and print statistics\n\n"); - return 0; - } -#ifdef USE_SD - else if (!strcmp(*argv, "-a")) /* We will try to attach an SD-disk image... */ - { - if (!*++argv) /* No more arguments! */ - { - printf("Expected a filename after -a but none found.\n"); - return -1; - } - - sd_attach(*argv++); - } -#endif - - if (*argv) - { if (load_binary_file(*argv)) return -1; run(); print_statistics(); - } } for (;;) { +#ifdef USE_VGA + gbl$mips_inst_cnt = 0; + gbl$mips = 0; +#endif printf("Q> "); fgets(command, STRING_LENGTH, stdin); chomp(command); @@ -1120,13 +1323,60 @@ int main(int argc, char **argv) printf("Switch register contains: %04X\n", access_memory(SWITCH_REG, READ_MEMORY, 0)); } +#if defined(USE_VGA) && defined(USE_UART) && !defined(__EMSCRIPTEN__) + else if (!strcmp(token, "MIPS")) + { + if ((token = tokenize(NULL, " "))) + { + upstr(token); + if (!strcmp(token, "MAX")) + { + gbl_set_target_mips(gbl$max_mips); + printf("QNICE hardware MIPS is %.2f\nNew target MIPS is MAXIMUM\n", gbl$qnice_mips); + } + else + { + gbl_set_target_mips(atof(token)); + printf("QNICE hardware MIPS is %.2f\nNew target MIPS is %.2f\n", gbl$qnice_mips, gbl$target_mips); + } + gbl$target_mips_changed = true; + } + else + { + if (gbl$target_mips == gbl$max_mips) + printf("QNICE hardware MIPS is MAXIMUM\n"); + else + printf("QNICE hardware MIPS is %.2f\nCurrent target MIPS is %.2f\n", gbl$qnice_mips, gbl$target_mips); + } + } + else if (!strcmp(token, "SPEEDSTATS")) + { + if ((token = tokenize(NULL, " "))) + { + upstr(token); + if (!strcmp(token, "ON")) + gbl$speedstats = true; + else if (!strcmp(token, "OFF")) + gbl$speedstats = false; + else + printf("Illegal switch. Use ON or OFF. SPEEDSTATS is currently %s\n", gbl$speedstats ? "ON": "OFF"); + } + else + printf("Show MIPS and FPS in VGA window is currently %s\n", gbl$speedstats ? "ON": "OFF"); + } +#endif else if (!strcmp(token, "RUN")) { if ((token = tokenize(NULL, delimiters))) write_register(15, str2int(token)); +#if defined(USE_VGA) && defined(USE_UART) && !defined(__EMSCRIPTEN__) + if (!gbl$initial_run) + vga_create_thread(uart_getchar_thread, "thread: uart_getchar", NULL); +#endif run(); } else if (!strcmp(token, "HELP")) + { printf("\n\ ATTACH Attach a disk image file (only with SD-support)\n\ CB Clear Breakpoint\n\ @@ -1135,14 +1385,24 @@ DETACH Detach a disk image file\n\ DIS , Disassemble a memory region\n\ DUMP , Dump a memory area, START and STOP can be\n\ hexadecimal or plain decimal\n\ -LOAD Loads a binary file into main memory\n\ +LOAD Loads a binary file into main memory\n"); +#if defined(USE_VGA) && defined(USE_UART) && !defined(__EMSCRIPTEN__) + printf("\ +MIPS [ | MAX] Displays/sets the emulator's speed in MIPS\n"); +#endif + printf("\ QUIT/EXIT Stop the emulator and return to the shell\n\ RESET Reset the whole machine\n\ RDUMP Print a register dump\n\ RUN [] Run a program beginning at ADDR\n\ -SET Either set a register or a memory cell\n\ +SET Either set a register or a memory cell\n\ SAVE Create a loadable binary file\n\ -SB Set breakpoint to an address\n\ +SB Set breakpoint to an address\n"); +#if defined(USE_VGA) && defined(USE_UART) && !defined(__EMSCRIPTEN__) + printf("\ +SPEEDSTATS [ON | OFF] Set the display of MIPS and FPS in VGA window\n"); +#endif + printf("\ STAT Displays some execution statistics\n\ STEP [] Executes a single instruction at address\n\ ADDR. If not address is specified the current\n\ @@ -1152,8 +1412,165 @@ STEP [] Executes a single instruction at address\n\ SWITCH [] Set the switch register to a value\n\ VERBOSE Toggle verbosity mode\n\ "); + +#if defined(USE_VGA) && defined(USE_UART) && !defined(__EMSCRIPTEN__) + printf("\n\ +Keyboard shortcuts for the VGA window:\n\n\ +ALT+f Toggle SPEEDSTATS\n\ +ALT[+SHIFT]+m Increase MIPS by 0.1 [or 1.0]\n\ +ALT[+SHIFT]+n Decrease MIPS by 0.1 [or 1.0]\n\ +ALT+c Set to QNICE hardware speed (%.1f MIPS)\n\ +ALT+v Set to maximum hardware speed\n\ +", gbl$qnice_mips); +#endif + } else printf("main: Unknown command >>%s<<\n", token); } } } + +#ifdef USE_VGA +static int emulator_main_loop(void* param) +{ + int retval = main_loop((char**) param); + gbl$shutdown_signal = true; + return retval; +} +#endif + +int main(int argc, char **argv) +{ + /* CTRL+C can be used in the terminal window to stop a running program + (e.g. the Monitor) and to return back to the Q> shell. + + * In Emscripten, this is not needed, because we do not have a Q> shell. + * In the single threaded POSIX shell environment, a simple signal handler can + be used by the signal() library function. + * In multithreaded environments, signal() is not working reliably. + On macOS 10.14 for example, we had the behaviour, that CTRL+C only worked, if a + command line parameter such as "../monitor/monitor.out" has been given before but not + when for example the monitor was loaded using the "Q> load ../monitor/monitor.out" + command. This is why we implemented the reliable solution described here, that + uses a separate thread and sigwait(): + https://thomastrapp.com/blog/signal-handler-for-multithreaded-c++/ + */ +#ifndef __EMSCRIPTEN__ +# ifndef USE_VGA + signal(SIGINT, signal_handler_ctrl_c); +# else + //block CTRL+C in the main thread and wait for it in signal_handler_ctrl_c_multitheaded() instead + sigemptyset(&gbl$sigset); + sigaddset(&gbl$sigset, SIGINT); + pthread_sigmask(SIG_BLOCK, &gbl$sigset, NULL); +# endif +#endif + + reset_machine(); + +#ifdef USE_IDE + initializeIDEDevice(); +#endif + + if (*++argv) /* At least one argument */ + { + if (!strcmp(*argv, "-h")) + { + printf("\nUsage:\n\ + \"qnice\" without arguments will start an interactive session\n\ + \"qnice -h\" will print this help text\n\ + \"qnice -a \" will attach an SD-card image file\n\ + \"qnice -a \" attaches an images and runs a file\n\ + \"qnice \" will run in batch mode and print statistics\n\n"); + return 0; + } +#ifdef USE_SD + else if (!strcmp(*argv, "-a")) /* We will try to attach an SD-disk image... */ + { + if (!*++argv) /* No more arguments! */ + { + printf("Expected a filename after -a but none found.\n"); + return -1; + } + + sd_attach(*argv++); + } +#endif + } + +/* ----------------------------------------------------------------------------------------- + Standard environment emulating an UART on a POSIX terminal + ----------------------------------------------------------------------------------------- */ +#ifndef USE_VGA + return main_loop(argv); +#else + +/* ----------------------------------------------------------------------------------------- + WebAssembly/WebGL environment + ----------------------------------------------------------------------------------------- */ +# ifdef __EMSCRIPTEN__ + + /* The --preload-file switch used in make-wasm.bash lets emscripten build a virtual file + system that contains the "operating system" in the file "monitor.out" */ + if (load_binary_file("monitor.out")) + return -1; + + /* If GZIP is enabled on the web server, then the download is quick, due to a lot of zeros + inside the largely empty disk image file. But just in case: Show a "Please wait: ..." message + and remove it after the download finished */ + emscripten_run_script("Module.setStatus('Please wait: Downloading 32MB SD card disk image...');"); + emscripten_wget("https://sy2002x.de/hwdp/qnice_disk.img", "qnice_disk.img"); + emscripten_run_script("statusElement.style.display = 'none';"); + sd_attach("qnice_disk.img"); + + vga_init(); + while (1) + { + for (unsigned long i = 0; i < gbl$instructions_per_iteration; i++) + execute(); + + emscripten_sleep(0); //cooperative multitasking, otherwise the browser kills the emulator + vga_one_iteration_keyboard(); + vga_one_iteration_screen(); + } + +/* ----------------------------------------------------------------------------------------- + Multithreaded local environment + ----------------------------------------------------------------------------------------- */ +# else + +# ifdef USE_UART + uart_fifo_init(); +# endif + + if (vga_init() && + vga_create_thread(emulator_main_loop, "thread: main_loop", (void*) argv) && + vga_create_thread(vga_timebase_thread, "thread: vga_timebase", NULL) && + vga_create_thread(mips_adjustment_thread, "thread: mips_adjustment", NULL) && + vga_create_thread(signal_handler_ctrl_c_multithreaded, "thread: ctrl_c_handler", NULL) && +# ifdef USE_UART + vga_create_thread(uart_getchar_thread, "thread: uart_getchar", NULL) && +# endif + vga_main_loop()) + { + gbl$shutdown_signal = true; + pthread_kill(ctrlc_thread_id, SIGINT); + while (gbl$cpu_running || vga_timebase_thread_running || mips_adjustment_thread_running || (ctrlc_thread_id != 0)) + usleep(10000); + vga_shutdown(); +# ifdef USE_UART + if (uart_status == uart_init) + { + uart_run_down(); + uart_fifo_free(); + } + printf("\n"); +# endif + return 0; + } + else + return -1; + +# endif +#endif +} diff --git a/emulator/qnice.css b/emulator/qnice.css new file mode 100644 index 00000000..bddcd1ce --- /dev/null +++ b/emulator/qnice.css @@ -0,0 +1,24 @@ +body { + margin: 0; + padding: none; + } + + .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } + div.emscripten { text-align: left; } + /* the canvas *must not* have any border or padding, or mouse coords will be wrong */ + canvas.emscripten { border: 0px none; background-color: black; } + + #status { + display: inline-block; + vertical-align: top; + margin-top: 30px; + margin-left: 20px; + font-weight: bold; + color: rgb(120, 120, 120); + } + + #progress { + height: 20px; + width: 300px; + } + \ No newline at end of file diff --git a/emulator/run-vga.bash b/emulator/run-vga.bash new file mode 100755 index 00000000..f1a94d75 --- /dev/null +++ b/emulator/run-vga.bash @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +DISK_IMAGE=http://sy2002x.de/hwdp/qnice_disk.img + +if [ ! -f ../assembler/qasm ] || [ ! -f ../assembler/qasm2rom ]; then + source ../tools/detect.include + cd .. + $COMPILER assembler/qasm.c -o assembler/qasm + $COMPILER assembler/qasm2rom.c -o assembler/qasm2rom -std=c99 + cd emulator +fi + +if [ ! -f ../monitor/monitor.out ]; then + cd ../monitor + ../assembler/asm monitor.asm + cd ../emulator +fi + +if [ ! -f qnice-vga ]; then + ./make-vga.bash +fi + +if [ ! -f qnice_disk.img ]; then + wget $DISK_IMAGE || curl -O $DISK_IMAGE +fi + + +if [ ! ./qnice-vga ] || [ ! ../monitor/monitor.out ] || [ ! -f qnice_disk.img ]; then + echo "" + echo "ERROR: Something went wrong." + echo "" + exit 1 +fi + +./qnice-vga -a qnice_disk.img ../monitor/monitor.out diff --git a/emulator/sd.c b/emulator/sd.c index ce05117d..40d9a394 100644 --- a/emulator/sd.c +++ b/emulator/sd.c @@ -7,7 +7,7 @@ #include "sd.h" #include #include -#include +#include #undef DEBUG #define VERBOSE @@ -51,6 +51,8 @@ void sd_attach(char *filename) void sd_detach() { fclose(image); + image = 0; + memset(sd_data, 0, SD_SECTOR_SIZE); } void sd_write_register(unsigned int address, unsigned int value) @@ -78,8 +80,11 @@ void sd_write_register(unsigned int address, unsigned int value) if ((value & 0xffff) == 1) /* Read 512 bytes from the block addressed by the current LBA. */ { block_start_address = ((sd_addr_lo & 0xffff) | ((sd_addr_hi & 0xfff) << 16)) * SD_SECTOR_SIZE; - fseek(image, block_start_address, SEEK_SET); - fread(sd_data, SD_SECTOR_SIZE, 1, image); + if (image) + { + fseek(image, block_start_address, SEEK_SET); + fread(sd_data, SD_SECTOR_SIZE, 1, image); + } #ifdef DEBUG printf("SD: Read block %08X.\n", (sd_addr_lo & 0xffff) | ((sd_addr_hi & 0xfff) << 16)); dump_sd_buffer(); @@ -91,8 +96,11 @@ void sd_write_register(unsigned int address, unsigned int value) #ifdef DEBUG printf("SD: Write block %08X.\n", block_start_address); #endif - fseek(image, block_start_address, SEEK_SET); - fwrite(sd_data, SD_SECTOR_SIZE, 1, image); + if (image) + { + fwrite(sd_data, SD_SECTOR_SIZE, 1, image); + fseek(image, block_start_address, SEEK_SET); + } } break; default: diff --git a/emulator/uart.c b/emulator/uart.c index a6b729e7..5489f3c2 100644 --- a/emulator/uart.c +++ b/emulator/uart.c @@ -1,24 +1,40 @@ /* -** This module implements the emulation of a generic UART which will be eventually used in the actual hardware implementation -** of QNICE. +** This module implements the emulation of a generic UART which will be eventually used +** in the actual hardware implementation of QNICE. ** ** 02-JUN-2008, B. Ulmann fecit. ** 03-AUG-2015, B. Ulmann Changed from curses to select-calls. ** 28-DEC-2015, B. Ulmann Adapted to the current FPGA-implementation. +** FEB-2020, sy2002 added non-blocking multithreaded version for the VGA emulator */ #undef TEST /* Define to perform stand alone test */ #define VERBOSE -#include "uart.h" #include - -#include #include #include +#include + +#include "uart.h" + +#ifdef USE_VGA +# include +# include "fifo.h" +fifo_t* uart_fifo; +bool uart_getchar_thread_running; //flag to safely free the FIFO's memory +extern bool gbl$cpu_running; //the getchar thread stops when the CPU stops + +/* Needs to be as large as the maximum amount of words that can be pasted while doing + copy/paste in the M/L mode. Reason: The uart thread might pick up the data slower, + than the operating systemm is pasting the data into the window. For being on the + safe side, we chose double the size of the current size of 32k words */ +const unsigned int uart_fifo_size = 2*32*1024; +#endif /* Ugly global variable to hold the original tty state in order to restore it during rundown */ struct termios tty_state_old, tty_state; +enum uart_status_t uart_status = uart_undef; unsigned int uart_read_register(uart *state, unsigned int address) { @@ -41,6 +57,7 @@ unsigned int uart_read_register(uart *state, unsigned int address) value = state->mr1a; break; case SRA: +#ifndef USE_VGA /* Check if there is a character in the input buffer */ if ((ret_val = select(1, &fd, NULL, NULL, &tv)) == -1) { @@ -50,13 +67,19 @@ unsigned int uart_read_register(uart *state, unsigned int address) state->sra &= 0xfe; /* Do not touch the transmit-ready bit! */ else /* Data available */ state->sra |= 1; - +#else + if (uart_fifo->count) + state->sra |= 1; + else + state->sra &= 0xfe; +#endif value = state->sra; break; case BRG_TEST: value = state->brg_test; break; case RHRA: +#ifndef USE_VGA if ((ret_val = select(1, &fd, NULL, NULL, &tv)) == -1) { /* Don't stop here as it might be caused by a catched CTRL-C signal! */ @@ -65,7 +88,12 @@ unsigned int uart_read_register(uart *state, unsigned int address) state->rhra = 0; else /* Data available */ state->rhra = getchar() & 0xff; - +#else + if (uart_fifo->count) + state->rhra = fifo_pull(uart_fifo); + else + state->rhra = 0; +#endif value = state->rhra; break; case IPCR: @@ -169,6 +197,38 @@ void uart_write_register(uart *state, unsigned int address, unsigned int value) } } +#ifdef USE_VGA +void uart_fifo_init() +{ + uart_fifo = fifo_init(uart_fifo_size); +} + +void uart_fifo_free() +{ + fifo_free(uart_fifo); +} + +int uart_getchar_thread(void* param) +{ + //wait until CPU is running (it is started in main thread after uart_getchar_thread_running = true) + while (!gbl$cpu_running) + usleep(10000); + + struct pollfd fds = {.fd = 0, .events = POLLIN}; // 0 means STDIN + int ret_val; + + uart_getchar_thread_running = true; + while (gbl$cpu_running) + { + ret_val = poll(&fds, 1, 5); //timeout = 5ms + if (ret_val) + fifo_push(uart_fifo, getchar() & 0xff); + } + uart_getchar_thread_running = false; + return 1; +} +#endif + void uart_hardware_initialization(uart *state) { /* Turn off buffering on STDIN and save original state for later */ @@ -194,12 +254,15 @@ void uart_hardware_initialization(uart *state) state->x_x_test = state->rhrb = state->input_ports = state->start_counter = state->stop_counter = state->csra = state->cra = state->thra = state->acr = state->imr = state->crur = state->ctlr = state->csrb = state->crb = state->thrb = state->opcr = state->set_output_port = state->reset_output_port = (unsigned int) 0; + + uart_status = uart_init; } void uart_run_down() { /* Reset the terminal to its original settings */ tcsetattr(STDIN_FILENO, TCSANOW, &tty_state_old); + uart_status = uart_rundown; } /* diff --git a/emulator/uart.h b/emulator/uart.h index d59ced9b..2bf98741 100644 --- a/emulator/uart.h +++ b/emulator/uart.h @@ -3,8 +3,11 @@ ** ** 02-JUN-2008, B. Ulmann fecit ** 28-DEC-2016, B. Ulmann Cleanup... +** FEB-2020, sy2002 added non-blocking multithreaded version for the VGA emulator */ +#include + #define UART0_BASE_ADDRESS 0xff20 /* This structure contains all register data of the emulated UART */ @@ -50,7 +53,17 @@ typedef struct uart #define SET_OUTPUT_PORT 14 #define RESET_OUTPUT_PORT 15 +//flag to ensure restoring a working terminal when closing the emulator by closing the SDL window +enum uart_status_t {uart_undef, uart_init, uart_rundown} uart_status; + unsigned int uart_read_register(uart *, unsigned int); void uart_write_register(uart *, unsigned int, unsigned int); void uart_hardware_initialization(uart *); void uart_run_down(); + +#ifdef USE_VGA +int uart_getchar_thread(void* param); +bool uart_getchar_thread_running; +void uart_fifo_init(); +void uart_fifo_free(); +#endif diff --git a/emulator/vga.c b/emulator/vga.c new file mode 100644 index 00000000..15268227 --- /dev/null +++ b/emulator/vga.c @@ -0,0 +1,712 @@ +/* +** QNICE VGA and PS2/USB keyboard Emulator +** +** done by sy2002 in December 2016 .. January 2017 +** emscripten/WebGL version in February and March 2020 +** +** Known harmless race-conditions: +** In multithreaded native VGA mode, this codes contains some possibilities for +** harmless race-conditions: Registers are being read or written by the CPU thread +** where in parallel the SDL thread accesses the same memory for the screen or +** for the keyboard. The consequences are minor and cannot be observed by humans +** since their timespan of occurrence is too small and the situation heals itself. +** In this context and for better code readability, we did not prevent these +** harmless race-conditions. If this changes one day, here are the sensitive areas: +** kbd_state, kbd_data, vga_state, vga_x, vga_y, vga_offs_display, +** vga_offs_rw, vram, screen_pixels +*/ + +#include +#include +#include + +#include "fifo.h" +#include "vga.h" +#include "vga_font.h" + +#include "../dist_kit/sysdef.h" + +//in native VGA mode (no emscripten): stabilize display thread to ~60 FPS +const unsigned long stable_fps_ms = 16; + +static Uint16 vram[65535]; +static Uint16 vga_state; +static Uint16 vga_x; +static Uint16 vga_y; +static Uint16 vga_offs_display; +static Uint16 vga_offs_rw; + +static Uint16 kbd_state; +static Uint16 kbd_data; +const Uint16 kbd_fifo_size = 100; +fifo_t* kbd_fifo; + +#ifdef __EMSCRIPTEN__ +const Uint16 display_dx = 960; //the hardware runs at a 1.8 : 1 ratio, see screenshots on GitHub +const Uint16 display_dy = 534; +//const Uint16 display_dx = 960; +//const Uint16 display_dy = 600; +#else +const Uint16 display_dx = 1280; //1.8 : 1 ratio +const Uint16 display_dy = 712; +//const Uint16 display_dx = 1280; +//const Uint16 display_dy = 800; +#endif +const Uint16 render_dx = 640; +const Uint16 render_dy = 480; +const Uint16 screen_dx = 80; +const Uint16 screen_dy = 40; +const Uint16 font_dx = QNICE_FONT_CHAR_DX_BITS; +const Uint16 font_dy = QNICE_FONT_CHAR_DY_BYTES; +const float zoom_x = (float) display_dx / (float) render_dx; +const float zoom_y = (float) display_dy / (float) render_dy; +static Uint32 font[font_dx * font_dy * QNICE_FONT_CHARS]; + +static bool cursor = false; +static float cursor_fx, cursor_fy; //compensation factors for non-propotionally resized window + +//data structures for rendering on screen +SDL_Window* win; +SDL_Renderer* renderer; +SDL_Texture* screen_texture; +Uint32* screen_pixels; + +SDL_Event event; +bool event_quit; + +unsigned long gbl$sdl_ticks; +unsigned long sdl_ticks_prev; +unsigned long sdl_ticks_curr; +unsigned long fps_framecounter; +unsigned int fps; +char fps_print_buffer[screen_dx]; +Uint16 fps_background_save[19]; + +extern float gbl$mips; +extern unsigned long gbl$mips_inst_cnt; +extern bool gbl$shutdown_signal; +extern bool gbl$speedstats; +bool speedstats_rendered; + +const unsigned int speed_change_timer_duration = 3000; //display duration of speed change in ms +unsigned int speed_change_timer = 0; +char speed_change_msg[screen_dx]; + +#ifndef __EMSCRIPTEN__ +bool vga_timebase_thread_running = false; +extern void gbl_set_target_mips(float new_mips); +extern void gbl_change_target_mips(float delta); +extern float gbl$qnice_mips; +extern float gbl$max_mips; +extern float gbl$target_mips; +#else +extern unsigned long gbl$ipi_default; +extern unsigned long gbl$instructions_per_iteration; +#endif + +unsigned int kbd_read_register(unsigned int address) +{ + switch (address) + { + case IO_KBD_STATE: + return kbd_state; + + case IO_KBD_DATA: +#ifndef __EMSCRIPTEN__ + kbd_state &= 0xFFFC; //clear new key indicators + return kbd_data; +#else + if (kbd_fifo->count) + { + //no more keys after this key? + if (kbd_fifo->count == 1) + kbd_state &= 0xFFFC; + return fifo_pull(kbd_fifo); + } +#endif + } + + return 0; +} + +void kbd_write_register(unsigned int address, unsigned int value) +{ + switch (address) + { + case IO_KBD_STATE: kbd_state = value; break; + case IO_KBD_DATA: kbd_data = value; break; + } +} + +void kbd_handle_keydown(SDL_Keycode keycode, SDL_Keymod keymod) +{ + bool shift_pressed; + bool ctrl_pressed; + bool alt_pressed; + + if (keymod & KMOD_SHIFT || keymod & KMOD_CAPS) + { + kbd_state |= KBD_SHIFT; + shift_pressed = true; + } + else + { + kbd_state &= ~KBD_SHIFT; + shift_pressed = false; + } + + if (keymod & KMOD_CTRL) + { + kbd_state |= KBD_CTRL; + ctrl_pressed = true; + } + else + { + kbd_state &= ~KBD_CTRL; + ctrl_pressed = false; + } + + if (keymod & KMOD_ALT) + { + float delta, sign; + + kbd_state |= KBD_ALT; + alt_pressed = true; + + const char* hotkeys = "fcvnm"; + if (strchr(hotkeys, keycode)) + { + switch (keycode) + { + case 'f': + gbl$speedstats = !gbl$speedstats; + return; + +#ifndef __EMSCRIPTEN__ + case 'c': + gbl_set_target_mips(gbl$qnice_mips); + sprintf(speed_change_msg, "Set target MIPS to QNICE hardware (%.1f MIPS)", gbl$qnice_mips); + break; + + case 'v': + gbl_set_target_mips(gbl$max_mips); + sprintf(speed_change_msg, "Set target MIPS to MAXIMUM"); + break; + + case 'n': + case 'm': + delta = shift_pressed ? 1.0 : 0.1; + sign = keycode == 'n' ? -1.0 : 1.0; + gbl_change_target_mips(sign * delta); + sprintf(speed_change_msg, "Set target MIPS to %.1f MIPS", gbl$target_mips); + break; +#else + case 'c': + gbl$instructions_per_iteration = gbl$ipi_default; + sprintf(speed_change_msg, "Set instructions per frame to default (%lu)", gbl$ipi_default); + break; + + case 'v': + sprintf(speed_change_msg, "Current instructions per frame: %lu", gbl$instructions_per_iteration); + break; + + case 'n': + case 'm': + delta = shift_pressed ? 100000 : 2500; + if (keycode == 'n') + { + if (gbl$instructions_per_iteration > delta) + gbl$instructions_per_iteration -= delta; + else + gbl$instructions_per_iteration = 0; + } + else + gbl$instructions_per_iteration += delta; + sprintf(speed_change_msg, "Set instructions per frame to %lu", gbl$instructions_per_iteration); + break; +#endif + } + keycode = 0; + vga_refresh_rendering(); + speed_change_timer = gbl$sdl_ticks; + } + } + else + { + kbd_state &= ~KBD_ALT; + alt_pressed = false; + } + + if ((keycode > 0 && keycode < 128) || keycode == 60 || keycode == 94 || keycode == 223 || keycode == 228 || keycode == 246 || keycode == 252) + { + kbd_data = keycode; + + if (shift_pressed) + { + if (keycode >= 'a' && keycode <= 'z') + kbd_data -= 32; //to upper + else switch (keycode) + { + //whole mapping table is currently DE keyboard specific + case '1': kbd_data = '!'; break; + case '2': kbd_data = '"'; break; + case '3': kbd_data = 0xA7; break; + case '4': kbd_data = '$'; break; + case '5': kbd_data = '%'; break; + case '6': kbd_data = '&'; break; + case '7': kbd_data = '/'; break; + case '8': kbd_data = '('; break; + case '9': kbd_data = ')'; break; + case '0': kbd_data = '='; break; + case ',': kbd_data = ';'; break; + case '.': kbd_data = ':'; break; + case '-': kbd_data = '_'; break; + case '+': kbd_data = '*'; break; + case '#': kbd_data = 0x27; break; + + case 60: kbd_data = 0xB0; break; + case 94: kbd_data = 0x3E; break; + case 223: kbd_data = '?'; break; + case 228: kbd_data = 0xC4; break; + case 246: kbd_data = 0xD6; break; + case 252: kbd_data = 0xDC; break; + } + } + else switch(keycode) + { + case 60: kbd_data = 0x5E; break; + case 94: kbd_data = 0x3C; break; + case 223: kbd_data = 0xDF; break; + } + + //CTRL + "overwrites" any other behaviour to 1 .. 26 + if (ctrl_pressed && keycode >= 'a' && keycode <= 'z') + kbd_data = keycode - 96; // a = 1, b = 2, ... + +#ifdef __EMSCRIPTEN__ + fifo_push(kbd_fifo, kbd_data); +#endif + + /* For avoiding race conditions, this needs to be the last statement + within this if branch and this is also the reason, why in the + following else branch the KBD_NEW_SPECIAL assignment is done like + it is done: As soon as the flag is set, + the CPU in the parallel thread is likely to read the data. */ + kbd_state |= KBD_NEW_ASCII; + } + else + { + switch (keycode) + { + case SDLK_UP: kbd_data = KBD_CUR_UP; break; + case SDLK_DOWN: kbd_data = KBD_CUR_DOWN; break; + case SDLK_LEFT: kbd_data = KBD_CUR_LEFT; break; + case SDLK_RIGHT: kbd_data = KBD_CUR_RIGHT; break; + case SDLK_PAGEUP: kbd_data = KBD_PG_UP; break; + case SDLK_PAGEDOWN: kbd_data = KBD_PG_DOWN; break; + case SDLK_HOME: kbd_data = KBD_HOME; break; + case SDLK_END: kbd_data = KBD_END; break; + case SDLK_INSERT: kbd_data = KBD_INS; break; + case SDLK_DELETE: kbd_data = KBD_DEL; break; + + default: + if (keycode >= SDLK_F1 && keycode <= SDLK_F12) + kbd_data = (keycode - 0x4000003A + 1) << 8; + else + return; + } + +#ifdef __EMSCRIPTEN__ + fifo_push(kbd_fifo, kbd_data); +#endif + + //see description above at KBD_NEW_ASCII + kbd_state |= KBD_NEW_SPECIAL; + } +} + +unsigned int vga_read_register(unsigned int address) +{ + switch (address) + { + case VGA_STATE: return vga_state; + case VGA_OFFS_DISPLAY: return vga_offs_display; + case VGA_OFFS_RW: return vga_offs_rw; + + /* The hardware is currently as of hardware revision V1.41 returning only part of the + register bits, so we are emulating this here. See "read_vga_registers" in + the file "vga_textmode.vhd". */ + case VGA_CR_X: return vga_x & 0x00FF; + case VGA_CR_Y: return vga_y & 0x007F; + case VGA_CHAR: return vram[((vga_y * screen_dx + vga_x) & 0x0FFF) + vga_offs_rw] & 0x00FF; + } + + return 0; +} + +void vga_write_register(unsigned int address, unsigned int value) +{ + switch (address) + { + case VGA_STATE: + vga_state = (value & ~VGA_BUSY); //bit #9 is read-only, so mask it + if (value & VGA_CLR_SCRN) + vga_clear_screen(); + break; + + case VGA_OFFS_RW: + vga_offs_rw = value; + break; + + case VGA_OFFS_DISPLAY: + vga_offs_display = value; + vga_refresh_rendering(); + break; + + /* As you can see in "write_vga_registers" in file "vga_textmode.vhd" of hardware + revision V1.41, there are some distinct - and from my today's one year later view + *strange* - things happening when it comes to handling coordinate overflows. + To be truly compatible to the hardware, we need to emulate this behaviour. */ + case VGA_CR_X: + vga_x = value & 0x00FF; + break; + + case VGA_CR_Y: + vga_y = value & 0x007F; + break; + + case VGA_CHAR: + //store character to video ram (vram) + vram[((vga_y * screen_dx + vga_x) & 0x0FFF) + vga_offs_rw] = value; + //make sure that the to-be-printed char is within the visible window + unsigned int print_addr = vga_offs_rw + vga_y * screen_dx + vga_x; + if (print_addr >= vga_offs_display && print_addr < vga_offs_display + screen_dy * screen_dx) + vga_render_to_pixelbuffer(vga_x, vga_y, (Uint8) value); + break; + } +} + +int vga_init() +{ +#ifndef __EMSCRIPTEN__ + SDL_SetMainReady(); +#endif + + if (SDL_Init(SDL_INIT_VIDEO) != 0) + { + printf("\nUnable to initialize SDL: %s\n", SDL_GetError()); + return 0; + } + + vga_state = vga_x = vga_y = vga_offs_display = vga_offs_rw = 0; + + kbd_state = KBD_LOCALE_DE; //for now, we hardcode german keyboard layout + kbd_data = 0; + + cursor_fx = cursor_fy = 1.0; + +#ifdef __EMSCRIPTEN__ + gbl$sdl_ticks = SDL_GetTicks(); //in non-emscripten mode done by vga_timebase_thread +#endif + fps = fps_framecounter = 0; + speedstats_rendered = gbl$speedstats; + + kbd_fifo = fifo_init(kbd_fifo_size); + + unsigned long pixelheap = render_dx * render_dy * sizeof(Uint32); + if ((screen_pixels = malloc(pixelheap)) == 0) + { + printf("Out of memory. Need %lu bytes of heap.", pixelheap); + return 0; + } + + Uint32 create_win_flags = SDL_WINDOW_OPENGL; +#ifndef __EMSCRIPTEN__ + create_win_flags |= SDL_WINDOW_RESIZABLE; +#endif + + win = SDL_CreateWindow("QNICE Emulator", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, display_dx, display_dy, create_win_flags); + if (win) + { + renderer = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED); + if (renderer) + { + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 0); + screen_texture = SDL_CreateTexture( renderer, + SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, + render_dx, + render_dy); + if (!screen_texture) + { + printf("Unable to screen texture: %s\n", SDL_GetError()); + return 0; + } + } + else + { + printf("Unable to create renderer: %s\n", SDL_GetError()); + return 0; + } + } + else + { + printf("Unable to create window: %s\n", SDL_GetError()); + return 0; + } + + vga_create_font_cache(); + vga_clear_screen(); + return 1; +} + +void vga_create_font_cache() +{ + const Uint32 green = 0x0000ff00; + for (int i = 0; i < QNICE_FONT_CHARS; i++) + for (int char_y = 0; char_y < font_dy; char_y++) + for (int char_x = 0; char_x < font_dx; char_x++) + font[i * font_dx * font_dy + char_y * font_dx + char_x] = qnice_font[i * font_dy + char_y] & (128 >> char_x) ? green : 0; +} + +void vga_shutdown() +{ + fifo_free(kbd_fifo); + free(screen_pixels); + SDL_DestroyTexture(screen_texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(win); + SDL_Quit(); +} + +int vga_create_thread(vga_tft thread_func, const char* thread_name, void* param) +{ + SDL_Thread* mlt = SDL_CreateThread(thread_func, thread_name, param); + if (mlt) + { + SDL_DetachThread(mlt); + return 1; + } + else + { + printf("\nUnable to create main thread.\n"); + return 0; + } +} + +void vga_clear_screen() +{ + vga_state |= VGA_BUSY | VGA_CLR_SCRN; + for (Uint32 i = 0; i < 65535; i++) + vram[i] = ' '; + for (Uint32 i = 0; i < render_dx * render_dy; i++) + screen_pixels[i] = 0; + vga_state &= ~(VGA_BUSY | VGA_CLR_SCRN); +} + +/* Prints to pixel buffer without modifying the video ram (vram). + This means that vga_print must be called upon each render iteration, otherwise nothing is visible */ +void vga_print(int x, int y, char* s) +{ + for (int i = 0; i < strlen(s); i++) + vga_render_to_pixelbuffer(x + i, y, s[i]); +} + +/* For performance reasons, during normal operation, the vram is not completely rendered, but only the + region that changed while writing a char using the respective registers. + vga_refresh_rendering is used to restore the vram on screen (inside the pixelbuffer), e.g. to restore + the background after having shown the speed change window or the speedstats */ +void vga_refresh_rendering() +{ + for (int y = 0; y < screen_dy; y++) + for (int x = 0; x < screen_dx; x++) + vga_render_to_pixelbuffer(x, y, vram[y * screen_dx + x + vga_offs_display]); +} + +void vga_render_to_pixelbuffer(int x, int y, Uint8 c) +{ + if (x < 0 || x >= screen_dx || y < 0 || y >= screen_dy) + return; + + unsigned long scr_offs = y * font_dy * render_dx + x * font_dx; + unsigned long fnt_offs = font_dx * font_dy * c; + for (int char_y = 0; char_y < font_dy; char_y++) + { + for (int char_x = 0; char_x < font_dx; char_x++) + screen_pixels[scr_offs + char_x] = font[fnt_offs + char_x]; + scr_offs += render_dx; + fnt_offs += font_dx; + } +} + +void vga_render_cursor() +{ + static Uint32 milliseconds; + + if (vga_state & VGA_EN_HW_CURSOR) + { + if (gbl$sdl_ticks > milliseconds + VGA_CURSOR_BLINK_SPEED) + { + milliseconds = gbl$sdl_ticks;; + cursor = !cursor; + } + + if (cursor) + { + SDL_Rect cursor_rect = { + vga_x * font_dx * zoom_x * cursor_fx, + vga_y * font_dy * zoom_y * cursor_fy, + font_dx * zoom_x * cursor_fx, + font_dy * zoom_y * cursor_fy + }; + SDL_RenderFillRect(renderer, &cursor_rect); + } + } +} + +void vga_one_iteration_keyboard() +{ + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_KEYDOWN) + { + SDL_Keycode keycode = ((SDL_KeyboardEvent*) &event)->keysym.sym; + SDL_Keymod keymod = SDL_GetModState(); + kbd_handle_keydown(keycode, keymod); + } + + else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + cursor_fx = event.window.data1 / (float) display_dx; + cursor_fy = event.window.data2 / (float) display_dy; + } + + else if (event.type == SDL_QUIT) + event_quit = true; + } +} + +void vga_render_speedwin(const char* message) +{ + unsigned int x, y, dx, dy; + char win[screen_dx]; + char spaces[screen_dx]; + + dx = strlen(message) + 6; + dy = 5; + x = screen_dx / 2 - dx / 2; + y = screen_dy / 2 - dy / 2; + + for (int i = 0; i < dx; spaces[i++] = 0x20); + spaces[dx] = 0; + vga_print(x, y, spaces); + + win[0] = 0x20; + win[1] = 0x86; + for (int i = 0; i < dx - 3; win[2 + i++] = 0x8A); + win[dx - 2] = 0x8C; + win[dx - 1] = 0x20; + win[dx] = 0; + vga_print(x, y+1, win); + + win[0] = 0x20; + win[1] = 0x85; + win[2] = 0x20; + for (int i = 0; i < dx; i++) + win[3 + i] = i < strlen(message) ? message[i] : 0x20; + win[dx - 2] = 0x85; + win[dx - 1] = 0x20; + win[dx] = 0; + vga_print(x, y+2, win); + + win[0] = 0x20; + win[1] = 0x83; + for (int i = 0; i < dx - 3; win[2 + i++] = 0x8A); + win[dx - 2] = 0x89; + win[dx - 1] = 0x20; + win[dx] = 0; + vga_print(x, y+3, win); + vga_print(x, y+4, spaces); +} + +void vga_one_iteration_screen() +{ + SDL_RenderClear(renderer); + + //calculate FPS + fps_framecounter++; +#ifdef __EMSCRIPTEN__ + gbl$sdl_ticks = SDL_GetTicks(); +#endif + if (gbl$sdl_ticks - sdl_ticks_prev > 1000) + { + sdl_ticks_prev = gbl$sdl_ticks; + fps = fps_framecounter; + fps_framecounter = 0; + } + + //show MIPS and FPS + if (gbl$speedstats) + { + sprintf(fps_print_buffer, " %.1f MIPS @ %d FPS", gbl$mips, fps); + vga_print(screen_dx - strlen(fps_print_buffer), 0, fps_print_buffer); + speedstats_rendered = true; + } + else if (speedstats_rendered) + { + vga_refresh_rendering(); + speedstats_rendered = false; + } + + //show speed change window + if (speed_change_timer > 0) + { + if (gbl$sdl_ticks - speed_change_timer < speed_change_timer_duration) + vga_render_speedwin(speed_change_msg); + else + { + vga_refresh_rendering(); + speed_change_timer = 0; + } + } + + //high-performance way of displaying the screen using streaming textures + SDL_UpdateTexture(screen_texture, NULL, screen_pixels, render_dx * sizeof(Uint32)); + SDL_RenderCopy(renderer, screen_texture, NULL, NULL); + vga_render_cursor(); + SDL_RenderPresent(renderer); +} + +#ifndef __EMSCRIPTEN__ +int vga_main_loop() +{ + event_quit = false; + while (!event_quit && !gbl$shutdown_signal) + { + unsigned long elapsed_ms = SDL_GetTicks(); + + vga_one_iteration_keyboard(); + vga_one_iteration_screen(); + + /* do not waste performance by displaying too many FPS: the real performance bottleneck + is the emulation of the system (i.e. the MIPS), maximizing FPS does not make any sense */ + elapsed_ms = SDL_GetTicks() - elapsed_ms; + if (elapsed_ms < stable_fps_ms) + SDL_Delay(stable_fps_ms - elapsed_ms); + } + return 1; +} + +int vga_timebase_thread(void* param) +{ + vga_timebase_thread_running = true; + while (!gbl$shutdown_signal) + { + gbl$sdl_ticks = SDL_GetTicks(); + SDL_Delay(1); + } + vga_timebase_thread_running = false; + return 1; +} +#endif diff --git a/emulator/vga.h b/emulator/vga.h new file mode 100644 index 00000000..300c5964 --- /dev/null +++ b/emulator/vga.h @@ -0,0 +1,43 @@ +/* +** Header file for the VGA and PS2/USB keyboard emulation. +** +** done by sy2002 in December 2016 .. January 2017 +** emscripten/WebGL version in February and March 2020 +*/ + +#ifndef _QEMU_VGA +#define _QEMU_VGA + +#include +#include "SDL.h" + +#define VGA_CURSOR_BLINK_SPEED 500 //milliseconds between cursor on/off + +typedef int (vga_tft)(void*); + +unsigned int vga_read_register(unsigned int address); +void vga_write_register(unsigned int address, unsigned int value); + +unsigned int kbd_read_register(unsigned int address); +void kbd_write_register(unsigned int address, unsigned int value); + +int vga_init(); +void vga_shutdown(); +void vga_create_font_cache(); +int vga_create_thread(vga_tft thread_func, const char* thread_name, void* param); +void vga_clear_screen(); +void vga_refresh_rendering(); +void vga_render_to_pixelbuffer(int x, int y, Uint8 c); +void vga_render_cursor(); +void vga_render_speedwin(const char* message); +void vga_print(int x, int y, char* s); +void vga_one_iteration_keyboard(); +void vga_one_iteration_screen(); + +#if defined(USE_VGA) && !defined(__EMSCRIPTEN__) +int vga_main_loop(); +bool vga_timebase_thread_running; +int vga_timebase_thread(void* param); +#endif + +#endif \ No newline at end of file diff --git a/emulator/vga_font.h b/emulator/vga_font.h new file mode 100644 index 00000000..651e14de --- /dev/null +++ b/emulator/vga_font.h @@ -0,0 +1,3092 @@ +/* +** QNICE Standard Font +** +** done by sy2002 in Januar 2017 +*/ + +#ifndef _QEMU_VGA_FONT +#define _QEMU_VGA_FONT + +// 8x12 font with 256 characters +#define QNICE_FONT_CHAR_DX_BITS 8 +#define QNICE_FONT_CHAR_DY_BYTES 12 +#define QNICE_FONT_CHARS 256 +#define QNICE_FONT_SIZE (QNICE_FONT_CHAR_DY_BYTES * 256) + +static unsigned char qnice_font[QNICE_FONT_SIZE] = +{ + 0x7E, + 0xC3, + 0x99, + 0x99, + 0xF3, + 0xE7, + 0xE7, + 0xFF, + 0xE7, + 0xE7, + 0x7E, + 0x00, + 0x00, + 0x00, + 0x00, + 0x76, + 0xDC, + 0x00, + 0x76, + 0xDC, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6E, + 0xD8, + 0xD8, + 0xD8, + 0xD8, + 0xDE, + 0xD8, + 0xD8, + 0xD8, + 0x6E, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6E, + 0xDB, + 0xDB, + 0xDF, + 0xD8, + 0xDB, + 0x6E, + 0x00, + 0x00, + 0x00, + 0x00, + 0x10, + 0x38, + 0x7C, + 0xFE, + 0x7C, + 0x38, + 0x10, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x38, + 0x6C, + 0x38, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xF0, + 0xF0, + 0xF0, + 0xF0, + 0xF0, + 0xF0, + 0xF0, + 0xF0, + 0xF0, + 0xF0, + 0xF0, + 0xF0, + 0x0F, + 0x0F, + 0x0F, + 0x0F, + 0x0F, + 0x0F, + 0x0F, + 0x0F, + 0x0F, + 0x0F, + 0x0F, + 0x0F, + 0x80, + 0x80, + 0x80, + 0x80, + 0xF8, + 0x00, + 0x3E, + 0x20, + 0x38, + 0x20, + 0x20, + 0x00, + 0x88, + 0x88, + 0x50, + 0x50, + 0x20, + 0x00, + 0x3E, + 0x08, + 0x08, + 0x08, + 0x08, + 0x00, + 0xF8, + 0x80, + 0xE0, + 0x80, + 0x80, + 0x00, + 0x3E, + 0x20, + 0x38, + 0x20, + 0x20, + 0x00, + 0x78, + 0x80, + 0x80, + 0x80, + 0x78, + 0x00, + 0x3C, + 0x22, + 0x3E, + 0x24, + 0x22, + 0x00, + 0x22, + 0x88, + 0x22, + 0x88, + 0x22, + 0x88, + 0x22, + 0x88, + 0x22, + 0x88, + 0x22, + 0x88, + 0x55, + 0xAA, + 0x55, + 0xAA, + 0x55, + 0xAA, + 0x55, + 0xAA, + 0x55, + 0xAA, + 0x55, + 0xAA, + 0xEE, + 0xBB, + 0xEE, + 0xBB, + 0xEE, + 0xBB, + 0xEE, + 0xBB, + 0xEE, + 0xBB, + 0xEE, + 0xBB, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x06, + 0x0C, + 0x18, + 0x30, + 0x7E, + 0x00, + 0x7E, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x60, + 0x30, + 0x18, + 0x0C, + 0x7E, + 0x00, + 0x7E, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x06, + 0x0C, + 0xFE, + 0x38, + 0xFE, + 0x60, + 0xC0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x02, + 0x0E, + 0x3E, + 0x7E, + 0xFE, + 0x7E, + 0x3E, + 0x0E, + 0x02, + 0x00, + 0x00, + 0x00, + 0x80, + 0xE0, + 0xF0, + 0xFC, + 0xFE, + 0xFC, + 0xF0, + 0xE0, + 0x80, + 0x00, + 0x00, + 0x00, + 0x18, + 0x3C, + 0x7E, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x7E, + 0x3C, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x0C, + 0xFE, + 0x0C, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x30, + 0x60, + 0xFE, + 0x60, + 0x30, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x3C, + 0x7E, + 0x18, + 0x18, + 0x18, + 0x7E, + 0x3C, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x28, + 0x6C, + 0xFE, + 0x6C, + 0x28, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x06, + 0x06, + 0x36, + 0x66, + 0xFE, + 0x60, + 0x30, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xC0, + 0x7C, + 0x6E, + 0x6C, + 0x6C, + 0x6C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x3C, + 0x3C, + 0x3C, + 0x18, + 0x18, + 0x00, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x36, + 0x36, + 0x14, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6C, + 0x6C, + 0xFE, + 0x6C, + 0x6C, + 0x6C, + 0xFE, + 0x6C, + 0x6C, + 0x00, + 0x00, + 0x10, + 0x7C, + 0xD6, + 0x70, + 0x38, + 0x1C, + 0xD6, + 0x7C, + 0x10, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x62, + 0x66, + 0x0C, + 0x18, + 0x30, + 0x66, + 0xC6, + 0x00, + 0x00, + 0x00, + 0x38, + 0x6C, + 0x38, + 0x38, + 0x76, + 0xF6, + 0xCE, + 0xCC, + 0x76, + 0x00, + 0x00, + 0x1C, + 0x1C, + 0x0C, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x30, + 0x30, + 0x30, + 0x30, + 0x30, + 0x18, + 0x0C, + 0x00, + 0x00, + 0x00, + 0x30, + 0x18, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x18, + 0x30, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6C, + 0x38, + 0xFE, + 0x38, + 0x6C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x7E, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0C, + 0x0C, + 0x0C, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFE, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x06, + 0x0C, + 0x18, + 0x30, + 0x60, + 0xC0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xD6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x18, + 0x78, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x7E, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0x0C, + 0x18, + 0x30, + 0x60, + 0xC6, + 0xFE, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0x06, + 0x06, + 0x3C, + 0x06, + 0x06, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x0C, + 0x1C, + 0x3C, + 0x6C, + 0xCC, + 0xFE, + 0x0C, + 0x0C, + 0x0C, + 0x00, + 0x00, + 0x00, + 0xFE, + 0xC0, + 0xC0, + 0xC0, + 0xFC, + 0x06, + 0x06, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC0, + 0xC0, + 0xFC, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0xFE, + 0xC6, + 0x0C, + 0x18, + 0x30, + 0x30, + 0x30, + 0x30, + 0x30, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0x7E, + 0x06, + 0x06, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0C, + 0x0C, + 0x00, + 0x00, + 0x0C, + 0x0C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0C, + 0x0C, + 0x00, + 0x00, + 0x0C, + 0x0C, + 0x0C, + 0x18, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x30, + 0x60, + 0xC0, + 0x60, + 0x30, + 0x18, + 0x0C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFE, + 0x00, + 0xFE, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x60, + 0x30, + 0x18, + 0x0C, + 0x06, + 0x0C, + 0x18, + 0x30, + 0x60, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0x0C, + 0x18, + 0x18, + 0x00, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xDE, + 0xDE, + 0xDE, + 0xDC, + 0xC0, + 0x7E, + 0x00, + 0x00, + 0x00, + 0x38, + 0x6C, + 0xC6, + 0xC6, + 0xC6, + 0xFE, + 0xC6, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x00, + 0xFC, + 0x66, + 0x66, + 0x66, + 0x7C, + 0x66, + 0x66, + 0x66, + 0xFC, + 0x00, + 0x00, + 0x00, + 0x3C, + 0x66, + 0xC0, + 0xC0, + 0xC0, + 0xC0, + 0xC0, + 0x66, + 0x3C, + 0x00, + 0x00, + 0x00, + 0xF8, + 0x6C, + 0x66, + 0x66, + 0x66, + 0x66, + 0x66, + 0x6C, + 0xF8, + 0x00, + 0x00, + 0x00, + 0xFE, + 0x66, + 0x60, + 0x60, + 0x7C, + 0x60, + 0x60, + 0x66, + 0xFE, + 0x00, + 0x00, + 0x00, + 0xFE, + 0x66, + 0x60, + 0x60, + 0x7C, + 0x60, + 0x60, + 0x60, + 0xF0, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC0, + 0xC0, + 0xCE, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xFE, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x00, + 0x3C, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x00, + 0x3C, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0xD8, + 0xD8, + 0x70, + 0x00, + 0x00, + 0x00, + 0xC6, + 0xCC, + 0xD8, + 0xF0, + 0xF0, + 0xD8, + 0xCC, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x00, + 0xF0, + 0x60, + 0x60, + 0x60, + 0x60, + 0x60, + 0x62, + 0x66, + 0xFE, + 0x00, + 0x00, + 0x00, + 0xC6, + 0xC6, + 0xEE, + 0xFE, + 0xD6, + 0xD6, + 0xD6, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x00, + 0xC6, + 0xC6, + 0xE6, + 0xE6, + 0xF6, + 0xDE, + 0xCE, + 0xCE, + 0xC6, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0xFC, + 0x66, + 0x66, + 0x66, + 0x7C, + 0x60, + 0x60, + 0x60, + 0xF0, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xD6, + 0x7C, + 0x06, + 0x00, + 0x00, + 0xFC, + 0x66, + 0x66, + 0x66, + 0x7C, + 0x78, + 0x6C, + 0x66, + 0xE6, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC0, + 0x60, + 0x38, + 0x0C, + 0x06, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x7E, + 0x5A, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x00, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x6C, + 0x38, + 0x10, + 0x00, + 0x00, + 0x00, + 0xC6, + 0xC6, + 0xD6, + 0xD6, + 0xD6, + 0xFE, + 0xEE, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x00, + 0xC6, + 0xC6, + 0x6C, + 0x38, + 0x38, + 0x38, + 0x6C, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x00, + 0x66, + 0x66, + 0x66, + 0x66, + 0x3C, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x00, + 0xFE, + 0xC6, + 0x8C, + 0x18, + 0x30, + 0x60, + 0xC2, + 0xC6, + 0xFE, + 0x00, + 0x00, + 0x00, + 0x7C, + 0x60, + 0x60, + 0x60, + 0x60, + 0x60, + 0x60, + 0x60, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xC0, + 0x60, + 0x30, + 0x18, + 0x0C, + 0x06, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x18, + 0x3C, + 0x66, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x1C, + 0x1C, + 0x18, + 0x0C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x78, + 0x0C, + 0x7C, + 0xCC, + 0xDC, + 0x76, + 0x00, + 0x00, + 0x00, + 0xE0, + 0x60, + 0x60, + 0x7C, + 0x66, + 0x66, + 0x66, + 0x66, + 0xFC, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC0, + 0xC0, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x1C, + 0x0C, + 0x0C, + 0x7C, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0x7E, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xFE, + 0xC0, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x1C, + 0x36, + 0x30, + 0x30, + 0xFC, + 0x30, + 0x30, + 0x30, + 0x78, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x76, + 0xCE, + 0xC6, + 0xC6, + 0x7E, + 0x06, + 0xC6, + 0x7C, + 0x00, + 0xE0, + 0x60, + 0x60, + 0x6C, + 0x76, + 0x66, + 0x66, + 0x66, + 0xE6, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x00, + 0x38, + 0x18, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0C, + 0x0C, + 0x00, + 0x1C, + 0x0C, + 0x0C, + 0x0C, + 0xCC, + 0xCC, + 0x78, + 0x00, + 0xE0, + 0x60, + 0x60, + 0x66, + 0x6C, + 0x78, + 0x6C, + 0x66, + 0xE6, + 0x00, + 0x00, + 0x00, + 0x38, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6C, + 0xFE, + 0xD6, + 0xD6, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xDC, + 0x66, + 0x66, + 0x66, + 0x66, + 0x66, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xDC, + 0x66, + 0x66, + 0x66, + 0x7C, + 0x60, + 0x60, + 0xF0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x76, + 0xCC, + 0xCC, + 0xCC, + 0x7C, + 0x0C, + 0x0C, + 0x1E, + 0x00, + 0x00, + 0x00, + 0x00, + 0xDC, + 0x66, + 0x60, + 0x60, + 0x60, + 0xF0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0x70, + 0x1C, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x30, + 0x30, + 0x30, + 0xFC, + 0x30, + 0x30, + 0x30, + 0x36, + 0x1C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0x76, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xC6, + 0xC6, + 0xC6, + 0x6C, + 0x38, + 0x10, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xC6, + 0xC6, + 0xD6, + 0xD6, + 0xFE, + 0x6C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xC6, + 0x6C, + 0x38, + 0x38, + 0x6C, + 0xC6, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xC6, + 0xC6, + 0xC6, + 0xCE, + 0x76, + 0x06, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFE, + 0x8C, + 0x18, + 0x30, + 0x62, + 0xFE, + 0x00, + 0x00, + 0x00, + 0x0E, + 0x18, + 0x18, + 0x18, + 0x70, + 0x18, + 0x18, + 0x18, + 0x0E, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x70, + 0x18, + 0x18, + 0x18, + 0x0E, + 0x18, + 0x18, + 0x18, + 0x70, + 0x00, + 0x00, + 0x00, + 0x76, + 0xDC, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x66, + 0x66, + 0x00, + 0x66, + 0x66, + 0x66, + 0x3C, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x1F, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x1F, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x1F, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x1F, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xF8, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0xF8, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xF8, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0xF8, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0xFF, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3F, + 0x30, + 0x3F, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6F, + 0x60, + 0x7F, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7F, + 0x60, + 0x6F, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6F, + 0x60, + 0x6F, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFC, + 0x0C, + 0xFC, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0xEC, + 0x0C, + 0xFC, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0xEF, + 0x00, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFC, + 0x0C, + 0xEC, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0xEC, + 0x0C, + 0xEC, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x00, + 0xEF, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0xEF, + 0x00, + 0xEF, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x6C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x82, + 0xFE, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x00, + 0x18, + 0x18, + 0x3C, + 0x3C, + 0x3C, + 0x18, + 0x00, + 0x00, + 0x10, + 0x7C, + 0xD6, + 0xD0, + 0xD0, + 0xD6, + 0x7C, + 0x10, + 0x00, + 0x00, + 0x00, + 0x38, + 0x6C, + 0x60, + 0x60, + 0xF0, + 0x60, + 0x66, + 0xF6, + 0x6C, + 0x00, + 0x00, + 0x00, + 0x3C, + 0x62, + 0x60, + 0xF8, + 0x60, + 0xF8, + 0x60, + 0x62, + 0x3C, + 0x00, + 0x00, + 0x00, + 0x66, + 0x66, + 0x3C, + 0x18, + 0x7E, + 0x18, + 0x3C, + 0x18, + 0x18, + 0x00, + 0x00, + 0x6C, + 0x38, + 0x00, + 0x7C, + 0xC6, + 0xC0, + 0x7C, + 0x06, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0x60, + 0x7C, + 0xC6, + 0xC6, + 0x7C, + 0x0C, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x6C, + 0x38, + 0x00, + 0x7C, + 0xC6, + 0x70, + 0x1C, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x7E, + 0x81, + 0x99, + 0xA5, + 0xA1, + 0xA1, + 0xA5, + 0x99, + 0x81, + 0x7E, + 0x00, + 0x00, + 0x3C, + 0x6C, + 0x6C, + 0x3E, + 0x00, + 0x7E, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x36, + 0x6C, + 0xD8, + 0x6C, + 0x36, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7E, + 0x06, + 0x06, + 0x06, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7E, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7E, + 0x81, + 0xB9, + 0xA5, + 0xA5, + 0xB9, + 0xA5, + 0xA5, + 0x81, + 0x7E, + 0x00, + 0x00, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x38, + 0x6C, + 0x38, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x7E, + 0x18, + 0x18, + 0x00, + 0x7E, + 0x00, + 0x00, + 0x00, + 0x38, + 0x6C, + 0x18, + 0x30, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x38, + 0x6C, + 0x18, + 0x6C, + 0x38, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6C, + 0x38, + 0x00, + 0xFE, + 0xC6, + 0x8C, + 0x38, + 0x62, + 0xC6, + 0xFE, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xF6, + 0xC0, + 0xC0, + 0x00, + 0x7F, + 0xDB, + 0xDB, + 0xDB, + 0x7B, + 0x1B, + 0x1B, + 0x1B, + 0x1B, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6C, + 0x38, + 0x00, + 0xFE, + 0x8C, + 0x18, + 0x30, + 0x62, + 0xFE, + 0x00, + 0x00, + 0x00, + 0x30, + 0x70, + 0x30, + 0x30, + 0x78, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x38, + 0x6C, + 0x6C, + 0x38, + 0x00, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xD8, + 0x6C, + 0x36, + 0x6C, + 0xD8, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6E, + 0xDF, + 0xD8, + 0xDE, + 0xDE, + 0xD8, + 0xD8, + 0xDF, + 0x6E, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6C, + 0xDA, + 0xDE, + 0xD8, + 0xDA, + 0x6C, + 0x00, + 0x00, + 0x66, + 0x66, + 0x00, + 0x66, + 0x66, + 0x3C, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x30, + 0x30, + 0x00, + 0x30, + 0x30, + 0x60, + 0xC6, + 0xC6, + 0x7C, + 0x30, + 0x18, + 0x00, + 0x38, + 0x6C, + 0xC6, + 0xC6, + 0xFE, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x18, + 0x30, + 0x00, + 0x38, + 0x6C, + 0xC6, + 0xC6, + 0xFE, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x38, + 0x6C, + 0x38, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xFE, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x76, + 0xDC, + 0x00, + 0x38, + 0x6C, + 0xC6, + 0xC6, + 0xFE, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x6C, + 0x6C, + 0x00, + 0x38, + 0x6C, + 0xC6, + 0xC6, + 0xFE, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x38, + 0x6C, + 0x38, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xFE, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x7E, + 0xD8, + 0xD8, + 0xD8, + 0xD8, + 0xFE, + 0xD8, + 0xD8, + 0xD8, + 0xDE, + 0x00, + 0x00, + 0x00, + 0x3C, + 0x66, + 0xC0, + 0xC0, + 0xC0, + 0xC6, + 0x66, + 0x3C, + 0x18, + 0xCC, + 0x38, + 0x18, + 0x0C, + 0x00, + 0xFE, + 0x66, + 0x60, + 0x7C, + 0x60, + 0x66, + 0xFE, + 0x00, + 0x00, + 0x18, + 0x30, + 0x00, + 0xFE, + 0x66, + 0x60, + 0x7C, + 0x60, + 0x66, + 0xFE, + 0x00, + 0x00, + 0x38, + 0x6C, + 0x00, + 0xFE, + 0x66, + 0x60, + 0x7C, + 0x60, + 0x66, + 0xFE, + 0x00, + 0x00, + 0x6C, + 0x6C, + 0x00, + 0xFE, + 0x66, + 0x60, + 0x7C, + 0x60, + 0x66, + 0xFE, + 0x00, + 0x00, + 0x18, + 0x0C, + 0x00, + 0x3C, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x18, + 0x30, + 0x00, + 0x3C, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x3C, + 0x66, + 0x00, + 0x3C, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x66, + 0x66, + 0x00, + 0x3C, + 0x18, + 0x18, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x00, + 0xF8, + 0x6C, + 0x66, + 0x66, + 0xF6, + 0x66, + 0x66, + 0x6C, + 0xF8, + 0x00, + 0x00, + 0x76, + 0xDC, + 0x00, + 0xC6, + 0xE6, + 0xF6, + 0xDE, + 0xCE, + 0xC6, + 0xC6, + 0x00, + 0x00, + 0x30, + 0x18, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x18, + 0x30, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x38, + 0x6C, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x76, + 0xDC, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x6C, + 0x6C, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6C, + 0x38, + 0x38, + 0x6C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7E, + 0xC6, + 0xCE, + 0xDE, + 0xD6, + 0xF6, + 0xE6, + 0xC6, + 0xFC, + 0x00, + 0x00, + 0x30, + 0x18, + 0x00, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x18, + 0x30, + 0x00, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x38, + 0x6C, + 0x00, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x6C, + 0x6C, + 0x00, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x00, + 0x66, + 0x66, + 0x66, + 0x3C, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x00, + 0xF0, + 0x60, + 0x7C, + 0x66, + 0x66, + 0x66, + 0x7C, + 0x60, + 0xF0, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xCC, + 0xC6, + 0xC6, + 0xD6, + 0xDC, + 0x80, + 0x00, + 0x60, + 0x30, + 0x18, + 0x00, + 0x78, + 0x0C, + 0x7C, + 0xCC, + 0xDC, + 0x76, + 0x00, + 0x00, + 0x18, + 0x30, + 0x60, + 0x00, + 0x78, + 0x0C, + 0x7C, + 0xCC, + 0xDC, + 0x76, + 0x00, + 0x00, + 0x30, + 0x78, + 0xCC, + 0x00, + 0x78, + 0x0C, + 0x7C, + 0xCC, + 0xDC, + 0x76, + 0x00, + 0x00, + 0x00, + 0x76, + 0xDC, + 0x00, + 0x78, + 0x0C, + 0x7C, + 0xCC, + 0xDC, + 0x76, + 0x00, + 0x00, + 0x00, + 0x6C, + 0x6C, + 0x00, + 0x78, + 0x0C, + 0x7C, + 0xCC, + 0xDC, + 0x76, + 0x00, + 0x00, + 0x38, + 0x6C, + 0x38, + 0x00, + 0x78, + 0x0C, + 0x7C, + 0xCC, + 0xDC, + 0x76, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7E, + 0xDB, + 0x1B, + 0x7F, + 0xD8, + 0xDB, + 0x7E, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7C, + 0xC6, + 0xC0, + 0xC0, + 0xC6, + 0x7C, + 0x18, + 0x6C, + 0x38, + 0x30, + 0x18, + 0x0C, + 0x00, + 0x7C, + 0xC6, + 0xFE, + 0xC0, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x30, + 0x00, + 0x7C, + 0xC6, + 0xFE, + 0xC0, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x10, + 0x38, + 0x6C, + 0x00, + 0x7C, + 0xC6, + 0xFE, + 0xC0, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x6C, + 0x6C, + 0x00, + 0x7C, + 0xC6, + 0xFE, + 0xC0, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x60, + 0x30, + 0x18, + 0x00, + 0x38, + 0x18, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x30, + 0x00, + 0x38, + 0x18, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x18, + 0x3C, + 0x66, + 0x00, + 0x38, + 0x18, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x00, + 0x6C, + 0x6C, + 0x00, + 0x38, + 0x18, + 0x18, + 0x18, + 0x18, + 0x3C, + 0x00, + 0x00, + 0x78, + 0x30, + 0x78, + 0x0C, + 0x7E, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x76, + 0xDC, + 0x00, + 0xDC, + 0x66, + 0x66, + 0x66, + 0x66, + 0x66, + 0x00, + 0x00, + 0x60, + 0x30, + 0x18, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x30, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x10, + 0x38, + 0x6C, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x76, + 0xDC, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x6C, + 0x6C, + 0x00, + 0x7C, + 0xC6, + 0xC6, + 0xC6, + 0xC6, + 0x7C, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x18, + 0x00, + 0x7E, + 0x00, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7E, + 0xCE, + 0xDE, + 0xF6, + 0xE6, + 0xFC, + 0x00, + 0x00, + 0xC0, + 0x60, + 0x30, + 0x00, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0x76, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x30, + 0x00, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0x76, + 0x00, + 0x00, + 0x30, + 0x78, + 0xCC, + 0x00, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0x76, + 0x00, + 0x00, + 0x00, + 0xCC, + 0xCC, + 0x00, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0x76, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x30, + 0x00, + 0xC6, + 0xC6, + 0xC6, + 0xCE, + 0x76, + 0x06, + 0xC6, + 0x7C, + 0x00, + 0xF0, + 0x60, + 0x60, + 0x78, + 0x6C, + 0x6C, + 0x6C, + 0x78, + 0x60, + 0x60, + 0xF0, + 0x00, + 0xC6, + 0xC6, + 0x00, + 0xC6, + 0xC6, + 0xC6, + 0xCE, + 0x76, + 0x06, + 0xC6, + 0x7C +}; + +#endif \ No newline at end of file diff --git a/emulator/wasm-shell-release.html b/emulator/wasm-shell-release.html new file mode 100644 index 00000000..6ca2b736 --- /dev/null +++ b/emulator/wasm-shell-release.html @@ -0,0 +1,85 @@ + + +
QNICE-FPGA Emulator
+ +
+ +
+ + + + + {{{ SCRIPT }}} diff --git a/emulator/wasm-shell.html b/emulator/wasm-shell.html new file mode 100644 index 00000000..e958f046 --- /dev/null +++ b/emulator/wasm-shell.html @@ -0,0 +1,94 @@ + + + + + + QNICE-FPGA Emulator + + + + + +
QNICE-FPGA Emulator
+ +
+ +
+ + + + + {{{ SCRIPT }}} + + diff --git a/qbin/TESTCRLF.txt b/qbin/TESTCRLF.txt deleted file mode 100755 index 33952754..00000000 --- a/qbin/TESTCRLF.txt +++ /dev/null @@ -1,5 +0,0 @@ -This file is CR/LF (both). -Another line -line: 3 -line: 4 -line: 5 \ No newline at end of file diff --git a/qbin/fread_multi_testfile1.txt b/qbin/fread_multi_testfile1.txt deleted file mode 100755 index 86cc4dbf..00000000 --- a/qbin/fread_multi_testfile1.txt +++ /dev/null @@ -1,3 +0,0 @@ -A1A2A3A4A5A6A7A8A9AAABACADAEAFAGAHAIAJAKALAMANAOAP -AQARASATAUAVAWAXAYAZAaAbAcAdAeAfAgAhAiAjAkAlAmAnAo -ApAqArAsAtAuAvAwAxAyAz \ No newline at end of file diff --git a/qbin/fread_multi_testfile2.txt b/qbin/fread_multi_testfile2.txt deleted file mode 100755 index 356ede73..00000000 --- a/qbin/fread_multi_testfile2.txt +++ /dev/null @@ -1 +0,0 @@ -BaBbBcBdBeBfBgBhBiBjBkBlBmBnBoBpBqBrBsBtBuBvBwBxByBz \ No newline at end of file diff --git a/qbin/fread_multi_testfile3.txt b/qbin/fread_multi_testfile3.txt deleted file mode 100755 index 05872309..00000000 --- a/qbin/fread_multi_testfile3.txt +++ /dev/null @@ -1 +0,0 @@ -CDCECFCGCHCICJCKCLCMCNCOCPCQCRCSCTCUCVCWCXCYCZ \ No newline at end of file diff --git a/qbin/test-cr.txt b/qbin/test-cr.txt deleted file mode 100755 index 233ff9f9..00000000 --- a/qbin/test-cr.txt +++ /dev/null @@ -1 +0,0 @@ -This file is CR only. 2 3 4 5 6 7 8 9 \ No newline at end of file diff --git a/qbin/test-lf.TXT b/qbin/test-lf.TXT deleted file mode 100755 index dd148f1f..00000000 --- a/qbin/test-lf.TXT +++ /dev/null @@ -1,15 +0,0 @@ -This file is LF only. -2 -3 -4 -5 -6 -7 -8 -9 -A -B -C -D -E -F \ No newline at end of file diff --git a/test_programs/hello.asm b/test_programs/hello.asm index 6d34859f..faab6cea 100644 --- a/test_programs/hello.asm +++ b/test_programs/hello.asm @@ -1,7 +1,7 @@ ; Classical "Hello World!" example for getting started coding ; QNICE assembler using the native toolchain: First, it prints "Hello World!" ; and then it counts from 1 to 10 and prints "Count #x", where x is decimal -; and goes from 1 to 10. So besides the simple "Hellow World!" printing, this +; and goes from 1 to 10. So besides the simple "Hello World!" printing, this ; example also shows how to use "operating system" functions and how to do ; sub routine calls. ; diff --git a/test_programs/teleball.asm b/test_programs/teleball.asm new file mode 100644 index 00000000..b22b541f --- /dev/null +++ b/test_programs/teleball.asm @@ -0,0 +1,50 @@ +; 8x8 Playfield of TeleBall +; can be used as an inspiration of actually doing an 8x8 TeleBall on QNICE :-) +; needs VGA +; done by sy2002 in January 2017 + +#include "../dist_kit/sysdef.asm" +#include "../dist_kit/monitor.def" + + .ORG 0x8000 + + SYSCALL(vga_cls, 1) + MOVE VGA$STATE, R0 + MOVE VGA$CR_X, R1 + MOVE VGA$CR_Y, R2 + MOVE VGA$CHAR, R3 + + MOVE VGA$EN_HW_CURSOR, R4 ; switch off cursor + NOT R4, R4 + AND R4, @R0 + + MOVE 10, @R2 ; y pos + MOVE TELEBALL, R6 + +Y_LOOP MOVE 20, @R1 ; x pos +X_LOOP MOVE @R6++, @R3 ; print char + ADD 1, @R1 + CMP @R1, 30 + RBRA X_LOOP, !Z + ADD 1, @R2 + CMP @R2, 20 + RBRA Y_LOOP, !Z + + SYSCALL(getc, 1) ; wait for a key + + NOT R4, R4 ; switch on cursor + OR R4, @R0 + + SYSCALL(exit, 1) + +TELEBALL +.DW 0x86, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8C +.DW 0x85, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x85 +.DW 0x85, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x85 +.DW 0x85, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x85 +.DW 0x85, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x85 +.DW 0x85, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x85 +.DW 0x85, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6F, 0x20, 0x20, 0x85 +.DW 0x85, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x85 +.DW 0x85, 0x20, 0x20, 0x9A, 0x9A, 0x20, 0x20, 0x20, 0x20, 0x85 +.DW 0x83, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x89 diff --git a/tools/detect.include b/tools/detect.include new file mode 100644 index 00000000..34873c98 --- /dev/null +++ b/tools/detect.include @@ -0,0 +1,26 @@ +# meant to be included in various QNICE bash scripts +# done by sy2002 in December 2016 + +case "$OSTYPE" in + solaris*) OSTP="SOLARIX" ;; + darwin*) OSTP="OSX" ;; + linux*) OSTP="LINUX" ;; + bsd*) OSTP="BSD" ;; + *) OSTP="unknown" ;; +esac + +if [ $OSTP = "unknown" ]; then + echo "" + echo "error: unsupported OS type:" $OSTYPE + exit +fi + +if hash cc 2>/dev/null; then + COMPILER=cc +elif hash gcc 2>/dev/null; then + COMPILER=gcc +else + echo "" + echo "error: neither cc nor gcc has been found!" + exit +fi; diff --git a/tools/gource.sh b/tools/gource.sh new file mode 100755 index 00000000..1a6ea2e8 --- /dev/null +++ b/tools/gource.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +cd .. +gource -s 1 -a 1 diff --git a/tools/make-toolchain.sh b/tools/make-toolchain.sh index c0e1ff42..7323e66f 100755 --- a/tools/make-toolchain.sh +++ b/tools/make-toolchain.sh @@ -1,8 +1,10 @@ #!/usr/bin/env bash +source ./detect.include + cd .. -gcc assembler/qasm.c -o assembler/qasm -gcc assembler/qasm2rom.c -o assembler/qasm2rom -std=c99 +$COMPILER assembler/qasm.c -o assembler/qasm +$COMPILER assembler/qasm2rom.c -o assembler/qasm2rom -std=c99 cd emulator ./make.bash @@ -12,4 +14,7 @@ source setenv.source ./make-vasm.sh ./make-vlink.sh ./make-vbcc.sh -gcc qnice/qniceconv.c -o qnice/qniceconv +$COMPILER qnice/qniceconv.c -o qnice/qniceconv + +echo "QNICE: Toolchain successfully made, if you do not see any error messages above." +echo "(Outputs like \"mkdir: config: File exists\" are OK.)" diff --git a/tools/mini_terminal.py b/tools/mini_terminal.py new file mode 100755 index 00000000..fa24b21b --- /dev/null +++ b/tools/mini_terminal.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +# Mini Terminal for connecting to QNICE-FPGA +# +# Setup: +# 1. Install dependencies: pip install -r mini_terminal.txt +# 2. Set the QNICE_FPGA_PORT variable (below) +# 3. Run via python3 mini_terminal.py or by directly executing ./mini_terminal.py +# +# Exit using CTRL+Q +# +# done by sy2002 on 20th of July 2019 + +# on macOS and Linux enter "ll -l /dev/cu*" in terminal to find out where to connect to +# the following port is the one on sy2002's computer; you'll probabily need to adjust it to yours +QNICE_FPGA_PORT = '/dev/cu.usbserial-2102927424241' + +import readchar +import serial +import threading +import time +import sys + +print("\nQNICE Mini-Terminal V1.0, done by sy2002 on 20th of July 2019") +print("=============================================================\n") +print("press CTRL+Q to exit\n") + +try: + ser = serial.Serial( port='/dev/cu.usbserial-2102927424241', + baudrate=115200, + bytesize=8, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, + rtscts=True, + timeout=0) +except: + print("ERROR: SERIAL PORT CANNOT BE OPENED.") + sys.exit(1) + +CONTINUE_RUNNING = True # if set to False, the threads end + +def read_thread(ser): + global CONTINUE_RUNNING + while CONTINUE_RUNNING: + time.sleep(0.01) # sleep 10ms, free CPU cycles, keep CPU usage low + amount = ser.in_waiting # amount of chars in serial read buffer + input_str = "" + if amount > 0: + ser_in = ser.read(amount) # due to "timeout=0" this is a non-blocking serial read + input_str = ser_in.decode("ASCII") # interpret byte stream as ASCII and convert to python string + print(input_str, end="") # print without a standard CR/LF + sys.stdout.flush() # necessary to make sure we see the printed strint immediatelly + +def write_thread(ser): + global CONTINUE_RUNNING + while CONTINUE_RUNNING: + ch = readchar.readchar() # blocking call + try: + send_str = ch.encode("ASCII") # convert python string to byte stream + ser.write(send_str) # send non-blocking due to "timeout=0" in serial.Serial(...) + except: + pass + + if ord(ch) == 17: # CTRL+Q ends the program + CONTINUE_RUNNING = False + +# The serial interface is full duplex and therefore reading and writing operations occur concurrently. +# "join()" means: wait until the thread ends +t1 = threading.Thread(target=read_thread, args=[ser]) +t2 = threading.Thread(target=write_thread, args=[ser]) +t1.start() +t2.start() +t1.join() +t2.join() +ser.close() +print("\n\nCONNECTION CLOSED!\n") diff --git a/tools/mini_terminal.txt b/tools/mini_terminal.txt new file mode 100644 index 00000000..b7cde16b --- /dev/null +++ b/tools/mini_terminal.txt @@ -0,0 +1,2 @@ +pyserial==3.4 +readchar==2.0.1 diff --git a/vhdl/vga_textmode.vhd b/vhdl/vga_textmode.vhd index d5a92b12..9ee697b3 100644 --- a/vhdl/vga_textmode.vhd +++ b/vhdl/vga_textmode.vhd @@ -32,7 +32,7 @@ -- register 5: vga read/write offset register used for accessing the whole vram (0..63999) -- -- this component uses Javier Valcarce's vga core --- http://www.javiervalcarce.eu/wiki/VHDL_Macro:_VGA80x40 +-- http://www.javiervalcarce.eu/html/vhdl-vga80x40-en.html -- how to make fonts, see http://nafe.sourceforge.net/ -- then use the psf2coe.rb and then coe2rom.pl toolchain to generate .rom files