|
|
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.
-
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:
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 .
|
|