QNICE - an Elegant 16 Bit Processor QNICE is a rather simple, yet powerful 16 bit processor architecture which is a result of some research done in the late 1990s and the following years. The goal was to create a processor architectur which is as orthogonal as possible without complicating a hardware implementation unnecessarily. Since the design borrows heavily from the NICE processor which featured a 32 bit architecture, the resulting architecture was called QNICE for "Quite NICE". The architecture is very well suited for teaching the basics of computer architecture and hardware implementation. Furthermore programming the QNICE processor is really easy and thus the architecture already served as a training device for some lectures about programming as well as computer architecture. This introductory presentation contains valuable insights. QNICE at a Glance 16 bit architecture with RISC like instruction set (18 instructions so far): MOVE src, dst : dst := src ADD src, dst : dst := dst + src ADDC src, dst : dst := dst + src + C SUB src, dst : dst := dst - src SUBC src, dst : dst := dst - src - C SHL src, dst : dst << src , fill with X bit, shift to C bit SHR src, dst : dst >> src , fill with C bit, shift to X bit SWAP src, dst : dst := ((src << 8) & 0xFF00 | ((src >> 8) & 0xFF NOT src, dst : dst := !src AND src, dst : dst := dst & src OR src, dst : dst := dst | src XOR src, dst : dst := dst ^ src CMP src, dst : dst := -src HALT : Halt the processor ABRA src, [!]cond : Absolute branch, conditional ASUB src, [!]cond : Absolute subroutine call, conditional RBRA src, [!]cond : Relative branch, conditional RSUB src, [!]cond : Relative subroutine call, conditional

Four addressing modes (register direct, register indirect, register indirect with post increment and register indirect with pre decrement): Rxx , @Rxx , @Rxx++ and @--Rxx .

, , and . Each instruction occupies one 16 bit word and has a fixed instruction format (excluding branches/jumps) thus decoding instructions is easy:

Branch and subroutine call instructions share a common instruction format, too:

16 registers divided into to areas: R0 to R7 are in fact a window to a register bank containing 256 times 8 registers while R8 to R15 are fixed. This architecture makes subroutine calls and saving registers very easy (just increment/decrement the register bank pointer which is part of the status register). All in all QNICE features 256 * 8 + 8 = 2056 registers.

Special registers: R13 serves as a stack pointer (used by the xSUB instructions), R14 is the status register which also controls the register bank windows described above while R15 is the program counter.

Branch/subroutinecall instructions can be executed conditionally, controlled by the status bits of the status register.

Address space is 64 kWords of 16 bits each.

Memory mapped IO. Examples

Summing 0F80 0000 MOVE 0x0000, R0 0F84 1000 MOVE 0x1000, R1 1100 LOOP ADD R1, R0 3F84 0001 SUB 0x0001, R1 FF8B 0004 ABRA LOOP, !Z E000 HALT The QNICE assembler program shown on the left sums all values from 0x0000 to 0x1000. This example is really simple - the main idea is to count backwards from 0x1000 to 0x0000 and using the Z (zero) flag of the status register R14 as a control condition for the central loop. Due to the possibility of making all branches and subroutine calls conditional by prefixing them with the name of one of the eight status register status bits the loop effectively consists of only two instructions, a subtraction and an absolute branch ( ABRA ). Prefixing the flag controlling the instruction with an exclamation mark inverts the flag (without modifying the status register's contents) prior to testing thus the instruction ABRA LOOP, !Z will perform an absolute branch to the location labeled LOOP if the zero flag is not set.

Subroutine Calls The code example below is a bit more complex and shows a subroutine which performs a string comparison (like the C standard library function strcmp). This function expects two pointers to the strings to be compared in the registers R9 and R10 and will return the result of the comparison in R8. Since the routine needs some registers for temporary storage of data is has to make sure that the contents of these registers are somehow saved at the entry of the subroutine and restored just before jumping back to the calling program. In traditional architectures this would involve pushing the registers to a stack (either explicitly, register by register, or controlled by a bit mask). Since QNICE features a register bank for its registers R0 to R7 saving and restoring the contents of these eight registers just makes it necessary to increment the register bank pointer which is made up by the eight upper bits of the status register R14 thus giving the routine access to its "own" set of registers R0 to R7. Before returning to the calling program the register bank pointer will be decremented making sure that the original register contents will be accessible again. Incrementing the register bank pointer is done by performing the instruction ADD 0x0100, R14 , decrementing it by one is done at the label STR$_STRCMP_EXIT using the subtraction SUB 0x0100, R14 . ; ;*********************************************************************** ;* STR$STRCMP compares two strings ;* ;* R9: Pointer to the first string (S0), ;* R10: Pointer to the second string (S1), ;* ;* R8: negative if (S0 < S1), zero if (S0 == S1), positive if (S0 > S1) ; ;* The contents of R8 and R9 are being preserved during the run of ;* this function ;*********************************************************************** ; STR$STRCMP ADD 0x0100, R14 ; Get a new register page MOVE R9, R0 ; Save arguments MOVE R10, R1 STR$_STRCMP_LOOP MOVE @R0, R8 ; while (*s1 == *s2++) MOVE @R1++, R2 SUB R8, R2 RBRA STR$_STRCMP_END, !Z MOVE @R0++, R8 ; if (*s1++ == 0) RBRA STR$_STRCMP_EXIT, Z ; return 0; RBRA STR$_STRCMP_LOOP, 1 ; end-of-while-loop STR$_STRCMP_END MOVE @--R1, R2 ; return (*s1 - (--*s2)); SUB R2, R8 STR$_STRCMP_EXIT SUB 0x0100, R14 ; Restore previous register page MOVE @R13++, R15 ; and return to calling program part ; A typical call to this routine would look like this: MOVE QMON$COMMAND, R9 MOVE QMON$CMD_HALT, R10 RSUB STR$STRCMP, 1 ; Was a halt command issued? Contents of the Distribution Kit The historical QNICE distribution kit is available here and currently contains the following items (the most recent distribution kit is available here): A short documentation of the processor architecture in form of a slide show prepared for a talk at a German DECUS symposium.

The source code of a QNICE cross assembler written entirely in pure C (this has been proven to run on LINUX, Mac OS X, OpenVMS and Windows).

The source code of the QNICE emulator, written in pure C, too. This emulator not only emulates the processor itself but also a 2681 UART (serial input/output is read from stdin/written to stdout) as well as a simple IDE disk. Both devices are memory mapped in the last 1 k Block of memory.

Some introductory examples like the summation program shown above and the first couple of routines for a monitor program. Current state of the project The project is currently in a solid working state. We moved to GitHub and merged the repository with the QNICE-FPGA implementation. You find the repository here: https://github.com/sy2002/QNICE-FPGA. The following features are available: Assembler

C Compiler including a C standard library

Emulator: Available in three flavors: POSIX command line (simulating an UART), windowed version that runs on macOS, Linux, Windows and a WebAssembly/WebGL version that you can try online

Monitor ("operating system") with FAT32 support

FPGA Implementation featuring 32k words of ROM and RAM, UART, VGA, PS/2 keyboard, SD card

Atmel AVR implementation Currently our main goal is to interest people in the QNICE architecture in general and in the implementation of the processor in hardware. If you are interested in joining the project, please contact Bernd Ulmann at ulmann@vaxman.de .