12 uCore Scaling

uCore 1.66
open,
scalable,
dual Stack,
Harvard Processor
for embedded Control
[email protected]
Using an open VHDL based extensible and scalable processor core as the foundation of a control
system that easily fits into contemporary FPGAs eventually frees the "core aided programmer" from
the limitations of any static processor architecture, be it CISC, RISC, WISC, FRISC or otherwise.
No more programming around known hardware bugs. A choice can be made as to whether a needed
functionality should be implemented in hardware or software; simply, the least complex, most
energy efficient solution can be realized while working on a specific application. Building on an
FPGA, time critical and perhaps complex functions can be realized in hardware in exactly the way
needed by the application offloading the processor from sub-optimal inner loops.
The FPGA approach also makes the user independent from product discontinuity problems that
haunt the hi-rel industry since the dawn of the silicon age. Finally: putting the core into an FPGA
puts an end to one of the high-level programming language paradigms, namely the aspect of (hopedfor) portability. Once you can realize your own instruction set, you are no longer confronted with
the need to port the application to any different architecture and henceforth, the only reason to
adhere to a conventional programming style is the need to find maintenance programmers.
Remains the need for a vendor independent hardware description language to be portable w.r.t. any
specific FPGA vendor and family. To date, uCore has been realized in VHDL, using the ModelTech
simulator and the Synplify synthesizer targeting Xilinx, Lattice, Altera, and Actel FPGAs.
A novel feature of uCore are its two different inputs to react to external events:
Interrupt: An event did happen that was not expected by the software.
Exception: An event did not happen that was expected by the software.
uCore is not confined to executing Forth programs but it is rooted in the Forth virtual machine.
uCore has been designed to support Forth as its Assembler. And it supports C's way of using the
return stack for local storage.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
1 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
Table of content
1
Design Philosophy ............................................................................ 3
1.1 Evolution............................................................................................................... 4
2
3
Hadware Architecture ..................................................................... 6
Instruction Architecture .................................................................. 8
3.1 Lit/Op Bit .............................................................................................................. 8
3.2 Type field .............................................................................................................. 9
3.3 Stack field ............................................................................................................. 9
3.4 Group field ............................................................................................................ 9
Instruction set overview .............................................................................................. 10
4
Instruction semantics ..................................................................... 11
4.1
4.2
4.3
4.4
5
Internal Registers ........................................................................... 20
5.1
5.2
5.3
5.4
5.5
5.6
5.7
6
TOS..................................................................................................................... 20
NOS .................................................................................................................... 20
TOR .................................................................................................................... 20
STATUS ............................................................................................................. 20
RSP ..................................................................................................................... 20
DSP ..................................................................................................................... 21
TASK .................................................................................................................. 21
Memory Mapped Registers ........................................................... 21
6.1
6.2
6.3
6.4
6.5
6.6
6.7
7
BRAnches ........................................................................................................... 11
ALU .................................................................................................................... 14
MEMory ............................................................................................................. 17
USeR ................................................................................................................... 19
int_reg (-1) .......................................................................................................... 21
flag_reg (-2) ........................................................................................................ 22
task_reg (-3) ........................................................................................................ 22
version_reg (-4)................................................................................................... 22
debug_reg (-5) .................................................................................................... 22
ctrl_reg (-6) ......................................................................................................... 22
timer (-7) ............................................................................................................. 23
Booting............................................................................................. 23
7.1 Boot Loader ........................................................................................................ 23
8
Interrupts ........................................................................................ 24
8.1 The Interrupt Mechanism .................................................................................... 24
8.2 Handling Multiple Interrupt Sources .................................................................. 24
9
Exceptions ....................................................................................... 25
9.1 EXCEPTION Signal ........................................................................................... 25
10 Data Memory Access ...................................................................... 26
10.1 Memory Map ...................................................................................................... 26
11 uCore Instruction Cycle ................................................................. 27
12 uCore Scaling .................................................................................. 27
12.1 Synthesizer Switches ........................................................................................... 27
12.2 Clock Issues ........................................................................................................ 28
12.3 Vector Widths ..................................................................................................... 28
13 Software Development ................................................................... 30
13.1 Forth Cross-Compiler ......................................................................................... 30
13.2 C Cross-Compiler ............................................................................................... 30
14 Project Status .................................................................................. 31
14.1 Products/projects................................................................................................. 31
14.2 FPGA families .................................................................................................... 31
15 Licensing ......................................................................................... 31
16 Acknowledgements ......................................................................... 31
17 Bibliography ................................................................................... 32
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
2 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
1 Design Philosophy
uCore's top priorities are portability, extensibility, and simplicity. uCore is rooted in the Forth
language but it is not confined to execute Forth programs – it is a pretty good general purpose
processor and indexed addressing into the return stack allows easy compilation of C programs that
execute efficiently.
Yet its design approach has been different from most other processors: Its assembler was first and it
realises 26 Forth primitives and another 9 as 2 cycle macros. Whereas most other processors attempt
to give you the utmost in instruction diversity from a minimum of hardware, you will find some
very specialised instructions of high semantic content in uCore, because 40 years of Forth
experience have proven that these are useful. Nevertheless, most instructions executes in one cycle
with at most one memory access and uCore is interruptible between any two instructions.
uCore is not the only architecture that uses Forth as its assembler. I took Chuck Moores NC4000 as
a model, enhanced it to become the FRP1600, which never made it beyond second silicon and its
vectored interrupt bug. A fresh approach was the realisation of the "Fieldbus Processor IX1" in the
1990s that still sells in industrial automation. To my knowledge, the IX1 was the first Forth
processor to use a Harvard Architecture.
uCore utilises two inventions of the Transputer:
1.) The way literal values are realized in the instruction stream. Literal "nibbles" may be
concatenated to form higher precision literals followed by the instruction that consumes it.
This is the enabling technology for the scalable data path width.
2.) The EXCEPTION input signal. A hardware mechanism to deal with resource scheduling, which
is the enabling technology for efficient and easy to use multi-tasking.
uCore attempts to be an optimum in terms of hardware complexity versus instruction semantics.
Internal state machines are kept to a minimum. There are other architectures that are simpler at the
expense of instruction semantics. But then you have to execute more of those simpler instructions to
get a certain job done, which leads to higher power consumption because, after all, fetching
instructions from program memory tends to be the major source of power consumption in
microprocessor systems.
Due to Forth's extensibility - i.e. the openness of its unconventional compiler for user modifications
and extensions - the Forth standardisation process is a slow consensus building process in a large
community compared to the compiler writer specialists' circles of most other languages. Forth
words could be thought of as the micro cells of a software system that has been realized in order to
solve a specific problem. In much the same way application specific instructions can be added to
uCore's open VHDL code to realize a more specific and complex piece of hardware. uCore's VHDL
code structure makes it simple to add registers and instructions.
In releasing uCore to the public, I hope that it will become a catalyst to spawn additional peripheral
functions, or MicroCells, placed under the same license conditions. As the Original Developer of
uCore I reserve the right to certify conformance of Derived Work with the Original Code. This is
my only business interest in uCore besides using it myself in embedded systems development. In
that respect, the uCore licensing terms are equivalent to the freeBSD terms.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
3 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
1.1 Evolution
1.1.1
Hardware
At first, ucore.vhd was not an atomic entity but it interconnected a plethora of sub-entities (dstack,
alu, rstack, sequencer, interrupt, uBus). Every instruction had to be defined in each of these subentities minutely. In July 2009 I finally dissolved all these sub-entities into one single behavioural
entity ucore.vhd. Since then each instruction is defined in one single place. Giving up hiearchy also
gave room for more direct connections between the core's sub-systems. In essence: The code
became more efficient and easier to maintain.
This led to a rapid evolution of the code structure and several additional Forth instructions have
been realised (e.g. ROT, -ROT), which are indeed possible in one cycle. In August 2009 I learned
how to do two-cycle un-interruptible instructions. Eventually, the "real" Forth ?BRANCH could be
implemented as one instruction, which simplified the cross-compiler as well.
The semaphor register could be abandoned, because its functionality can be realized by writing to
the flag register. As a consequence of a port to an Altera prototyping board with slow external
RAM, a general purpose wait state generator has been added that allows to stretch the execution
phase of each instruction cycle by 1-4 clock cycles.
Branch instruction design could now be driven
by syntax. The Forth phrases in the table each
have a unique branch opcode. Note: IF stands
for WHILE and UNTIL as well.
Mastering the two-cycle technology opened the
door for indivisible instructions. +! became
two- instruction macro +st drop. It can not
be interrupted and therefore, it can be used to
synchronize interrupts and tasks.
Forth instructions < and > are non-trivial to
implement and therefore, instruction less?
has been added for two-cycle < macro
- less?. drol, dror, and I have been
added as single cycle instructions.
Forth syntax
Instruction Mnemonic
BRANCH branch
IF z-branch
0= IF nz-branch
?dup IF ?dup-branch
0< IF ns-branch
0< 0= IF s-branch
carry? IF nc-branch
ovfl? IF no-branch
NEXT tor-branch
In March 2010 I debugged overflow and carry, and as a consequence I mopped-up the division
primitives. Both unsigned and signed (floored) division steps are implemented and each division
takes data_width+2 cycles to execute. 4-quadrant signed floored division takes a lot of
resources to implement and therefore, in October 2010 I implemented division primitives to divide a
singed dividend by an unsigned divisor.
Another consequence of debugging overflow is saturation arithmetic. It returns deterministic results
in overflow conditions (smallest, largest number or zero). Saturation arithmetic can be activated
with configuration constant sat_arith in CONSTANTS.VHD.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
4 of 32
uCore 1.66
1.1.2
scalable, Dual Stack, Harvard Processor
Software
In February 2009 the multitasker was ported to uCore, and the scheduler was simplified in April
2011. For a discussion of its principles and data structures see the multitasker chaper in
"uForth_166.pdf".
In May 2009 Ulrich Hoffmann released a re-implementation of the debugger on top of gforth, which
up until that time was a piece of C code. This was a real "version 2." breakthrough, because it
opened the door to interactively define colon-definitions while debugging.
Also in May 2009 I added early binding objects with encapsulation and polymorphic inheritance to
the uForth compiler based on concepts developed by Manfred Mahlow.
Another breakthrough happened in February 2010. Since then, a simple VHDL interpreter allows
the cross-compiler to load CONSTANTS.VHD, which defines hardware features and opcode
mnemonics. uCore/uForth became a co-design environment. Now every change in the hardware is
unambiguously brought forward to the cross-compiler. It is guaranteed by design that names of
hardware characteristics are spelled the same in VHDL and in Forth, and that they have identical
values.
The latter feature allows easy optimization of a uCore instantiation. If an instruction is not needed,
the definition of its mnemonic in CONSTANTS.VHD can be deleted. From this point on, both the
VHDL simulator and synthesizer as well as the cross-compiler will bark at you when you try to use
the abandoned instruction nevertheless. If needed, the former instruction must be defined as a
macro, a soft call or a subroutine in the cross-compiler, and everything works again except for
simpler hardware.
Given uCore's complexity and the resources of contemporary FPGAs, this usually is no problem in
single processor projects. You just synthesize a "full instruction set" and the software uses whatever
it needs. But it will be appreciated in multi-core applications.
This possibility, to easily mold a specific instruction set will have important ramifications on the
"instruction inventory": Specialized instructions can be added/removed easily and hopefully, an
inventory of re-usable instructions will grow over time.
Finally, in September 2010 the "bit wise write" mechanism was improved. The sign bit determines
whether '0's are anded or '1's are ored to the register content. E.g. #power_ctrl Ctrl_reg !
sets and #power_ctrl invert Ctrl_reg ! resets a "power control"-bit.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
5 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
2 Hadware Architecture
uCore is a dual-stack, Harvard architecture with three memory areas that can be accessed in parallel:
Data-stack (RAM), Data-memory and return-stack (RAM), and Program-memory (ROM).
All instructions without exception are 8 bits wide, and they are stored in the program-memory
ROM. Due to the way literal values can be concatenated from sequences of literal instructions, all
data-paths and memories are scalable to any word width as long as your needed address room can be
represented.
The data path is made up of the Data-Stack, the ALU and of the Data-Memory and Return-Stack.
Registers TOS (Top-Of-Stack) and NOS (Next-Of-Stack) feed the ALU. TOR (Top-Of-Returnstack) allows for a decrement-and-branch instruction that can be nested and it allows complex math
instructions like multiply, divide or vector multiply (FIR filters). Adding the TASK register will
support a multi-tasking OS with a base-index addressing mode into the task control block. Indexed
addressing into the return-stack gives single cycle access to local variables.
The data-stack is realized by a single-port RAM used as Stack under control of the Data-StackPointer DSP, and the topmost stack item is held in the TOS register. Typically the size of the Data
Stack memory needed will be small enough to fit inside the FPGA.
IO is data-memory mapped and the most significant address bit selects the external world when set.
In addition, program memory may be mapped into the lower part of the IO address space for van
Neumann style read and write access in two cycles during the development phase. If the most
significant address bit is not set, data-memory and return-stack RAM is selected. The return-stack
grows towards lower addresses and typically occupies the upper end of Data Memory under control
of Return-Stack-Pointer RSP. Both Data and Program Memory may use internal FPGA block-RAM
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
6 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
as "caches" and therefore, uCore can run as a "single chip controller" inside an FPGA without any
external memory needs.
Several Data Memory access instructions are available:

Absolute addressing with the address in the TOS register and a three bit signed preincrement/decrement in the instruction (ld, st),

Indexed addressing relative to the Return Stack Pointer for local variable access (lld, lst),

Indexed addressing relative to the TASK register (tld, tst).
After each memory access, the absolute memory address that had been accessed will remain in TOS.
Data transfer takes place between the second data stack element NOS and memory/IO.
The Sequencer generates the Program Memory address for the next instruction, which can have a
number of sources:

The Program Counter PC for a sequential instruction,

the ALU for a relative branch or call,

the TOS register for an absolute branch or call,

the TOR register (Top-Of-Return-stack) for a return instruction,

the INSTruction register for an immediate call (soft instruction),

the fixed Interrupt Service Routine address ISR as part of an interrupt acknowledge cycle,

the fixed Exception Service Routine address ESR during an exception cycle, or

the fixed Overflow Service Routine address OSR for a conditional service routine on overflow.
In the code, uBus has been defined as a record of signals, which are needed in several entities: The
data memory and I/O signals, a selection address for the internal registers, an array of all register
outputs so as to simplify adding application specific registers.
Instruction decoding and status register bit processing since version 1.50 has been centralized in a
single file uCore.vhd, because this way it is easier to modify and maintain.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
7 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
3 Instruction Architecture
An instruction is always 8 bits wide. Scalability is achieved on the object code level because all
literal values are composed of literal instructions that can be concatenated. Refer to [3 instruction
structures] for a discussion of the literal representation used, which is characterised by its "prefix"
nature called "Vertical instruction set with literal prefixes" in the paper. To my knowledge, this type
of code has been invented by David May for the Transputer.
It has two advantages and one drawback compared to other instruction set structures:
Each instruction is "self contained" and therefore, this type of code can be interrupted between
any two instructions, simplifying interrupt hardware and minimising interrupt latency.
Long literals are composed of a sequence of literal instructions that are concatenated in the TOS
register. Therefore, this type of instruction architecture is independent of the data word width.
Prefix code has the highest instruction fetch rate compared to the two other instruction types
discussed in the paper. Therefore, it is not really the technology of choice for demanding realtime applications.
Keeping in mind that uCore is about putting a processor into FPGAs for complex embedded
control, the latter drawback is tolerable because the instruction fetch delay, even using external
ROM, barely dominates total cycle delay, because all processor logic is synthesized into an FPGA
and therefore, it will be slower than an ASIC implementation anyway. The potential high semantic
content of an application specific instruction compensates this drawback in comparison with pure
RISC machines.
The instruction
7
$80
Lit/Op
6
$40
5
$20
Type
4
$10
3
$8
Stack
2
$4
1
$2
0
$1
Group
3.1 Lit/Op Bit
1: 7-bit Literal (signed)
0: 7-bit Opcode
The Lit/Op field is a semantic switch:

When set, the remaining 7 bits are interpreted as a literal nibble and transferred to the Top-ofStack (TOS) register. When the previous instruction had been an opcode, the literal nibble is
sign-extended and pushed on the stack. If its predecessor was a literal nibble as well (LIT-bit of
the status register), the 7 bits are shifted into TOS from the right. Therefore, the number of
literal nibbles needed to represent a number depends on its absolute magnitude.
The LIT-bit in the status register will be set.

When not set, the remaining 7 bits are interpreted as an opcode. Opcodes are composed of three
sub-fields: Type, Stack, and Group.
The LIT-bit in the status register will be reset.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
8 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
3.2 Type field
Code
00
01
10
11
Name
BRA
ALU
MEM
USR
Action
Branches, Calls and Returns
Binary and Unary Operators
Data-Memory and Register access
User instructions / immediate calls
BRAnches are conditioned on the group field and they consume the content of TOS, using either
TOS or TOS+PC as destination address. In order to implement Forth's IF, WHILE, UNTIL,
?EXIT and 0=EXIT easily, the relevant branches are 2 cycle instructions with an additional drop
of the flag.
CALL has been moved to the MEM types and it pushes the content of the PC on the return-stack
while branching. Exit (return) pops the return-stack using it as the address of the next instruction.
ALU instructions use the stack as source and destination for arithmetic operations. Unary operations
only use TOS, binary operations use TOS and Next-of-Stack (NOS) storing the result in TOS.
Interruptible complex math or signal processing step instructions can use TOS, NOS and TOR
for arguments.
MEMory instructions refer to the data memory when the most significant bit of TOS, which holds
the address, is not set. When set, it refers to input/output operations with the external world. The
return-stack resides in the data-memory and the Program Memory may be accessed through the
I/O space if van Neumann addressing has been enabled for debugging. Internal registers
STATUS, DSP and RSP can be accessed immediately.
26 USeR instructions are free for any application specific function, which may be needed to achieve
total system throughput. The first USeR instructions coincide with uCore's hardware vectors ISR
(InterruptServiceRoutine), ESR (ExceptionServiceRoutine), and OSR (OverflowServiceRoutine)
and two division steps. As default, the remaining 26 instructions perform a call to 26 trap vector
addresses that have room for a sequence of instructions to be used to e.g. emulate multi-cycle
instructions.
3.3 Stack field
Code
00
01
10
11
Name
NONE
POP
PUSH
BOTH
Action
Type dependent
Stack->NOS->TOS
TOS->NOS->Stack
Type dependent
As a rule, POP pops and PUSH pushes the data stack. The stack semantics of the remaining states
NONE and BOTH depend on type and on external signals INTERRUPT and EXCEPTION. This is
where the opcode fields are non-orthogonal creating instruction decoding complexity, which is
gracefully hidden by the synthesizer.
3.4 Group field
Each group is a collection of eight instructions, which have similar stack behaviour and, if possible,
are related semantically.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
9 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
3.5 Instruction set overview
Each instruction is preceeded by 2, and in the case of double-cycle instructions by 3 symbols, which
describe its stack behaviour. For single cycle instructions, the first symbol relates to the data stack,
the second to the return stack. For double-cycle instructions (2c), the first two symbols relate to the
data stack (one for each cycle), and the third to the return stack.
- stack drop
+ stack push
0 no change
? conditional
BRA
NONE
0
1
2
3
4
5
6
7
00
00
00
00
0000-
nop
rot
-rot
less?
sdivl
udivl
rdrop
exit
POP
-0
?-0
--0
--0
--0
--0
-0
-0
PUSH
branch
?dup-branch
s-branch
ns-branch
z-branch
nz-branch
no-branch
nc-branch
2c
2c
2c
2c
2c
+0
?0
+0
+0
+0
+0
+0
+0
BOTH
dup
?dup
unpack
under
tuck
ovfl?
carry?
I
-0
-0
-0
-0
-?
-?
-?
--
drop
times
pack
multl
z-exit
nz-exit
tor-branch
iret
ALU
NONE
0
1
2
3
4
5
6
7
00
00
00
00
00
00
0-0
00
umult
smult
div
time?
drol
dror
+st 2c
swap
POP
-0
-0
-0
-0
-0
-0
-0
-0
PUSH
+
+c
swapand
or
xor
nip
+0
+0
+0
+0
+0
+0
+0
+0
BOTH
2dup
2dup
2dup
2dup
2dup
2dup
2dup
over
+
+c
swapand
or
xor
00
00
00
00
00
00
00
00
invert
2*
2/
u2/
ror
rol
0=
0<
MEM
NONE
0
1
2
3
4
5
6
7
-0
-0
-0
-+
-0
-0
-0
-+
status!
st_set
lst
>r
rsp!
dsp!
tst
call
POP
-0
-0
-0
-0
-0
-0
-0
-0
ST
1 +
2 +
3 +
4 3 2 1 -
PUSH
ST
ST
ST
ST
ST
ST
ST
+0
+0
+0
+0
+0
+0
+0
+0
LD
1 +
2 +
3 +
4 3 2 1 -
BOTH
LD
LD
LD
LD
LD
LD
LD
+0
+0
+0
++0
+0
+0
status@
r@
lld
r>
rsp@
dsp@
tld
USR
NONE
0
1
2
3
4
-+
++
0+
0?
0+
udivs
int
exc
?ovfl
break
POP
PUSH
BOTH
-+ sdivs
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
10 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
4 Instruction semantics
In the following detailed description of instruction semantics, each instruction is at first
characterized by its stack behaviour as specified in ANS Forth (X3.215-1994).
4.1 BRAnches
NONE
0
1
2
3
4
5
6
7
4.1.1
0
00
00
00
00
0000-
nop
rot
-rot
less?
sdivl
udivl
rdrop
exit
POP
-0
?-0
--0
--0
--0
--0
-0
-0
branch
?dup-branch
s-branch
ns-branch
z-branch
nz-branch
no-branch
nc-branch
PUSH
2c
2c
2c
2c
2c
+0
?0
+0
+0
+0
+0
+0
+0
dup
?dup
unpack
under
tuck
ovfl?
carry?
I
BOTH
-0
-0
-0
-0
-?
-?
-?
--
drop
times
pack
multl
z-exit
nz-exit
tor-branch
iret
NONE
nop ( -- )
no operation. The processor state does not change
1
rot ( u1 u2 u3 -- u2 u3 u1 )
2
-rot ( u1 u2 u3 -- u3 u1 u2 )
3
less? ( diff -- f )
Primitive to realize Forth words < and > as two cycle macros.
4
sdivl ( n1 n2 -- n3 n4 ) (R: n5 -- )
CO
The third and last "cleanup" instruction for signed division. See div instruction.
5
udivl ( u1 u2 -- u3 u4 ) (R: u5 -- )
CO
The third and last "cleanup" instruction for unsigned division. See div instruction.
6
rdrop ( -- ) (R: u -- )
Return stack drop.
7
exit ( -- ) (R: addr -- )
Branch to the address on top of the return stack. Subroutine return.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
11 of 32
uCore 1.66
4.1.2
scalable, Dual Stack, Harvard Processor
POP
The following group of instructions are (conditional) branches. Depending on the LIT-bit in the
status register, TOS is interpreted differently:
LIT=0 TOS holds an absolute destination address.
LIT=1 TOS holds a signed address offset relative to the address of the PC.
0
branch ( dest -- )
Unconditional branch.
1
?dup-branch 2c ( n dest -- n ) or ( 0 dest -- )
If the number in NOS is zero, the branch ist taking an both TOS and NOS are discarded.
If the number in NOS is not zero, the branch is not taken and only TOS is discarded.
2
s-branch 2c ( n dest -- )
If n is negative, the branch is taken. Both TOS and NOS are discarded.
3
ns-branch 2c ( n dest -- )
If n is not negative (positive or zero), the branch is taken. Both TOS and NOS are discarded.
4
z-branch 2c ( u dest -- )
If u is zero, the branch is taken. Both TOS and NOS are discarded.
5
nz-branch 2c ( u dest -- )
If u is non zero, the branch is taken. Both TOS and NOS are discarded.
6
no-branch ( dest -- )
If the overflow flag in the status register is not set, the branch is taken. TOS is discarded.
7
nc-branch ( dest -- )
If the carry flag in the status register is not set, the branch is taken. TOS is discarded.
4.1.3
PUSH
0
dup ( u -- u u )
1
?dup ( u -- u u ) or ( 0 -- 0 )
2
unpack ( u1 -- u2 8b )
8b, the 8 least significant bits of u1, are pushed on the stack. u2 is the "remainder" of u1
shifted 8 bits to the right filling the 8 most significant bits with zeros.
3
under ( u1 u2 -- u1 u1 u2 )
4
tuck ( u1 u2 -- u2 u1 u2 )
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
12 of 32
uCore 1.66
5
scalable, Dual Stack, Harvard Processor
ovfl? ( -- f )
Depending on the overflow flag of the status register, a flag is pushed on the stack. A flag is
either 0 or -1.
6
carry? ( -- f )
Depending on the carry flag of the status register, a flag is pushed on the stack. A flag is either
0 or -1.
7
I ( -- n ) (R: addr count -- addr count )
The Forth loop index is pushed on the stack. Used between DO ... LOOP. n = addr-count.
4.1.4
BOTH
0
drop ( n -- )
1
times ( n -- )
the following instruction is executed n times. It is not executed at all, when n is zero.
2
pack ( u1 8b -- u2 )
u1 is shifted 8 bits to the left and the vacated least significant bits are filled with 8b.
3
multl ( ud -- n )
CO
It discards the most significant part of a product. The overflow flag is set, when the signed
product can not be represented by its least significant item on the stack alone.
4
z-exit ( n -- ) (R: addr -- ) or (R: addr -- addr )
When n is zero, exit is executed. Otherwise execution continues with the next sequential
instruction.
5
nz-exit ( n -- ) (R: addr -- addr ) or (R: addr -- )
When n is not zero, exit is executed. Otherwise execution continues with the next
sequential instruction.
6
tor-branch ( dest -- ) (R: count -- count-1 ) or (R: 0 -- )
When TOR is non zero, TOR is decremented by one and the branch is taken. When TOR is
zero, execution continues with the next sequential instruction and the return stack is popped.
7
iret ( status -- ) (R: addr -- )
CO
"Interrupt return". Execution continues at addr and the status register is restored from the data
stack.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
13 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
4.2 ALU
NONE
0
1
2
3
4
5
6
7
4.2.1
0
00
00
00
00
00
00
0-0
00
umult
smult
div
time?
drol
dror
+st 2c
swap
POP
-0
-0
-0
-0
-0
-0
-0
-0
+
+c
swapand
or
xor
nip
PUSH
+0
+0
+0
+0
+0
+0
+0
+0
2dup
2dup
2dup
2dup
2dup
2dup
2dup
over
BOTH
+
+c
swapand
or
xor
00
00
00
00
00
00
00
00
invert
2*
2/
u2/
ror
rol
0=
0<
NONE
umult ( u1 u2 -- ud )
CO
When synthesizer switch WITH_MULT=1, it is a single cycle unsigned multiply.
Otherwise, it is a multiply step instruction, which has to be repeated once for each data bit,
after setting up the parameters in TOS; NOS, and TOR.
1
smult ( n1 n2 -- d )
CO
Single cycle signed multiply. This instruction is only defined when synthesizer switch
WITH_MULT=1.
2
div ( u1 u2 -- u3 u4 ) (R: u5 -- u6 )
CO
Division primitive. It has to be repeated once for every bit.
For unsigned division, it has to be preceded by udivs and succeeded by udivl.
For signed division, it has to be preceded by sdivs and succeeded by sdivl.
3
time? ( ticks -- f )
C
Comparison against the timer register. As soon as the timer is greater than ticks, a true flag
will be left, a false flag otherwise.
4
drol ( ud1 -- ud2 )
C
Double precision rotate left. C is shifted into the least significant bit of NOS, its most
significant bit is shifted into TOS and the most significant bit of TOS is shifted into C.
5
dror ( ud1 -- ud2 )
C
Double precision rotate right. C is shifted into the most significant bit of TOS, its least
significant bit is shifted into NOS and the least significant bit of NOS is shifted into C.
6
+st 2c ( n addr -- addr )
Signed increment of the memory cell at addr by n. This double cycle instruction is uninterruptible and therefore, it can be used for interrupt/task synchronization.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
14 of 32
uCore 1.66
7
4.2.2
scalable, Dual Stack, Harvard Processor
swap ( u1 u2 -- u2 u1 )
POP
0
+ ( n1 n2 -- n3 )
CO
1
+c ( n1 n2 -- n3 )
CO
2s-complement add with carry, for extended precision arithmetic.
2
- ( n1 n2 -- n1-n2 )
CO
3
swap- ( n1 n2 -- n2-n1 )
CO
SWAP - as single cycle instruction.
4
and ( u1 u2 -- u3 )
5
or ( u1 u2 -- u3 )
6
xor ( u1 u2 -- u3 )
7
nip ( u1 u2 -- u2 )
4.2.3
PUSH
0
2dup + ( n1 n2 -- n1 n2 n3 )
CO
1
2dup +c ( n1 n2 -- n1 n2 n3 )
CO
2
- ( n1 n2 -- n1 n2 n3 )
CO
3
2dup swap- ( n1 n2 -- n1 n2 n3 )
CO
4
2dup and ( u1 u2 -- u1 u2 u3 )
CO
5
2dup or ( u1 u2 -- u1 u2 u3 )
CO
6
2dup xor ( u1 u2 -- u1 u2 u3 )
CO
7
over ( u1 u2 -- u1 u2 u1 )
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
15 of 32
uCore 1.66
4.2.4
scalable, Dual Stack, Harvard Processor
BOTH
0
invert ( n1 -- n2 )
1
2* ( u1 -- u2 )
C
Arithmetic/Logical shift left. 0 is shifted into the least significant bit and the most significant
bit is shifted into C.
2
2/ ( n1 -- n2 )
C
Arithmetic shift right. The sign bit is kept and shifted into the most but one significant bit.
The least significant bit is shifted into C.
3
u2/ ( u1 -- u2 )
C
Logical shift right. 0 is shifted into the most significant bit, and the least significant bit is
shifted into C.
4
ror ( u1 -- u2 )
C
Rotate right. C is shifted into the most significant bit, and the least significant bit is shifted
into C.
5
rol ( u1 -- u2 )
C
Rotate left. C is shifted into the least significant bit, and the most significant bit is shifted into
C.
6
0= ( u -- f )
7
0< ( n -- f )
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
16 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
4.3 MEMory
NONE
0
1
2
3
4
5
6
7
4.3.1
0
-0
-0
-0
-+
-0
-0
-0
-+
POP
status!
st_set
lst
>r
rsp!
dsp!
tst
call
-0
-0
-0
-0
-0
-0
-0
-0
ST
1 +
2 +
3 +
4 3 2 1 -
PUSH
ST
ST
ST
ST
ST
ST
ST
+0
+0
+0
+0
+0
+0
+0
+0
LD
1 +
2 +
3 +
4 3 2 1 -
LD
LD
LD
LD
LD
LD
LD
BOTH
+0
+0
+0
++0
+0
+0
status@
r@
lld
r>
rsp@
dsp@
tld
NONE
status! ( u -- )
CO
The bit pattern of u is stored into the status register.
1
st_set ( mask -- )
CO
When the sign bit of mask is not set, those bits in mask, which are set to 1 will be set to 1 in
the status register. Those bits set to 0 will not change their corresponding bit in the status
register.
When the sign bit of mask is set, those bits in the mask, which are set to 0 will be reset to 0 in
the status register. Those bits set to 1 will not change their corresponding bit in the status
register.
Examples:
: set_carry
( -- )
: reset_carry ( -- )
2
#carry st_set ;
#carry invert st_set ;
lst ( u i -- addr ) (R: ni .. n0 -- ui .. n0 )
"local store" stores u at the (i+1)th element of the return stack leaving addr, the data
memory address of ui on the stack. The return stack grows towards lower memory addresses
and therefore, addr+1 points to older element ui+1 etc.
Note: 0 for i produces an erroneous result, because TOR holds the first element of the
return stack, not data memory.
3
>r ( u -- ) (R: -- u )
4
rsp! ( n -- )
"return stack pointer store" sets the rsp register.
5
dsp! ( n -- ? )
"data stack pointer store" sets the dsp register.
6
tst ( u i -- addr )
"task store" stores u at data memory address (task_register+i), leaving the address.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
17 of 32
uCore 1.66
7
scalable, Dual Stack, Harvard Processor
call ( dest -- ) (R: -- addr )
branch to code at dest leaving addr as return address to be used by exit later on.
For the computation of dest see 4.1.2.
4.3.2
0
POP
ST ( u addr -- addr+ )
At first, the group field is used as a 3-bit 2s-complement pre-increment (-4 .. 3) added to
addr to yield effective address addr+. Then u is stored at addr+ in data memory.
addr+ is left on the stack. ST is also used to store into memory mapped registers.
4.3.3
0
PUSH
LD ( addr -- u addr+ )
At first, the group field is used as a 3-bit 2s-complement pre-increment (-4 .. 3) added to
addr to yield effective address addr+. Then u is read from addr+ in data memory.
addr+ is left on the stack as well. LD is also used to read memory mapped registers.
4.3.4
0
BOTH
status@ ( -- status )
CO
The status register is pushed on the stack.
1
r@ ( -- u ) (R: u -- u )
2
lld ( i -- ui addr ) (R: ui .. u0 -- ui .. u0 )
"local load" loads the (i+1)th element of the return stack leaving it on the stack under the
effective data memory address addr. The return stack grows towards lower memory
addresses and therefore, addr+1 points to older element ui+1 etc.
Note: 0 for i leaves an ambiguous result, because TOR holds the first element of the return
stack, not data memory.
3
r> ( -- u ) (R: u -- )
4
rsp@ ( -- n )
"return stack pointer fetch" reads the return stack pointer.
5
dsp@ ( -- n )
"data stack pointer fetch" reads the data stack pointer.
6
tld ( i -- u addr )
"task load" reads u from data memory address (task_register+i), leaving effective address
addr on top of u.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
18 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
4.4 USeR
NONE
0
1
2
3
4
4.4.1
0
-+
++
0+
0?
0+
udivs
int
exc
?ovfl
break
POP
-+ sdivs
NONE
udivs ( complex stack shuffling )
The first "parameter setup" instruction for unsigned division. See div instruction.
1
int ( -- status ) (R: -- addr )
INTerrupt instruction. It is forced into the instruction register as a result of an interrupt.
The status register is pushed on the stack, and the address of the next instruction to be
executed is pushed on the return stack. Execution continues at the interrupt vector. Its physical
address depends on usr_vect_width in CONSTANTS.VHD.
2
exc (R: -- addr )
EXCeption instruction. It is forced into the instruction register as a result of raising an
exception. The address of the next instruction following the instruction, which had just been
aborted due to the exception signal is pushed on the return stack. Execution continues at the
exception vector. Its physical address depends on usr_vect_width in CONSTANTS.VHD.
3
?ovfl (R: -- adddr ) or (R: -- )
When the overflow flag is not set, execution continues with the next sequential instruction.
When the overflow flag ist set, a call to the overflow vector address is executed. Its physical
address depends on usr_vect_width in CONSTANTS.VHD.
4
break (R: -- addr )
A call to the break vector address is executed. Its physical address depends on
usr_vect_width in CONSTANTS.VHD. The break instruction is used for the single step
debugger to halt execution of a word at a predetermined point.
4.4.2
0
POP
sdivs ( complex stack shuffling )
The first "parameter setup" instruction for signed division. See div instruction.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
19 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
5 Internal Registers
5.1 TOS
The "Top-Of-Stack" is held in the TOS register.
5.2 NOS
The "Next-Of-Stack" (2nd item on the stack) is held in the NOS register.
5.3 TOR
Top-Of-Return-stack. TOR is needed for the tor-branch instruction and the division and multiply
step instructions.
5.4 STATUS
Bit Name Access Description
0
C
R/W The Carry flag reflects the result of the most recent +, +c, -, 2*, 2/, u2/,
ROR, and ROL instructions. When subtracting, it is the complement of the
borrow bit.
1 OVFL R/W The OVerFLow flag reflects the result of the most recent +, +c, -, /
instruction.
2
IE
R/W Interrupt-Enable-Flag. When IE is not set, interrupts will be disabled.
3
IIS
R/W The Interrupt-In-Service flag is set when jumping to the interrupt service
address. It will be reset when IRET (Interrupt-RETurn) restores the status
register at the end of the interrupt service routine . When IIS is set, interrupts
are disabled.
4
LIT
R
The LITeral flag keeps the most significant bit of the previous instruction.
5
N
R
The Negative flag reflects the content of the most significant bit of TOS or of
NOS when LIT=1.
6
Z
R
The Zero flag reflects the content of TOS or of NOS when LIT=1
7
DIV
R
DIVision sign bit used by signed division
8+ TIMES
W
times_width number of bits, serving as counter for the TIMES instruction.
As long as the TIMES field is not zero, the following instruction will be
repeated. During each repetition TIMES will be decremented by 1. The
TIMES repeat mechanism may be interrupted at any time.
3 TIMES 2* will shift TOS three times to the left.
Z and N reflect the actual state of the top "number" on the stack. This may be in TOS (when LIT=0)
or in NOS (when LIT=1) because e.g. a target address may be in TOS.
5.5 RSP
Return-Stack-Pointer. It is used to implement the return-stack that is located in the data memory and
it can be read and written to support multi-tasking and stack-frame linkage. The return stack pointer
width is defined by rs_addr_width in CONSTANTS.VHD. For multi-tasking support, a task prefix
address is appended to the left of the return-stack-pointer itself, which is tasks_addr_width wide.
The topmost address of the return stack memory area is defined by rs_base_width. The return stack
grows from higher towards lower memory addresses.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
20 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
5.6 DSP
Data-Stack-Pointer. It is used to implement the data-stack and it can be read and written to support
multi-tasking. The data stack pointer width is defined by ds_addr_width in CONSTANTS.VHD.
For multi-tasking support, a task prefix address is appended to the left of the data-stack-pointer
itself, which is tasks_addr_width wide.
5.7 TASK
The TASK register itself can be read and written via memory mapped I/O (address = -3). In a multitasking environment it would hold an address pointing to the Task Description Block (TDB) of the
active task. The implementation of the multitasking mechanism is operating system dependent.
Variables that are local to a task can be accessed via the tld and tst instructions. The data
memory address is the sum of TASK+TOS and after the memory access the effective memory
address remains in TOS.
If the TASK register is not used for multitasking support, it constitutes a general purpose base
register for a pre-incrementing base-offset addressing mode.
6 Memory Mapped Registers
6.1 int_reg (-1)
This is a pair of registers – int_reg @ (Macro ints@) for reading, and int_reg !
(Macro ie!) for writing.
An interrupt condition exists as long as any bit in int_reg is set whose corresponding bit has been
set previously by ie!. Interrupt processing will be performed when the processor is not already
executing an interrupt (IIS status bit not set) and interrupts are enabled (IE status bit set).
Typically, at the beginning of interrupt processing (after calling the hard-wired interrupt handler
address ISR) int_reg will be read. One specific bit is associated with each potential interrupt source.
When a certain interrupt has been asserted, its associated bit will be set. All interrupts are static and
therefore, it is the responsibility of the interrupt service routine of a specific interrupt to reset the
interrupt signal of the external hardware before the end of interrupt processing..
The interrupt enable register, which can be written by storing to the int_reg address, holds one
enable bit for each interrupt source. Setting or resetting interrupt enable bits is done in a peculiar
way, which could be called "bit-wise writing":
When ie! is executed, the sign bit (most significant bit) determines whether individual enable
bits will be set or reset without affecting other enable bits.
When the sign bit is not set, those bits, which are set to 1 will be set in the interrupt enable register.
Those bits set to 0 will not change their corresponding bit in the interrupt enable register.
When the sign bit is set, those bits, which are set to 0 will be reset in the interrupt enable register.
Those bits set to 1 will not change their corresponding bit in the interrupt enable register.
This way individual interrupt enable bits can be modified without affecting other enable bits and
without the need to use a "shadow variable".
Constant INTERRUPTS in CONSTANTS.VHD determines the number of active interrupt inputs. If
set to zero, the interrupt logic and int_reg will not be instantiated at all.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
21 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
6.2 flag_reg (-2)
The flag register has a dual functionality depending on whether it is read or written.
Upon reading (Macro flags@), a copy of all interrupt flags as well as additional flags, which do
not generate interrupts is returned. flag_reg reads the interrupt flags irrespective of the setting of the
interrupt enable register, i.e. even if a certain interrupt flag may be disabled, its physical state can be
read by flags@.
Upon writing (Macro pass), an exception will be raised when a '1' is written to a flag bit, which is
not set. Using this mechanism, the flag bits can be used as hardware semaphors.
Constant FLAG_WIDTH in CONSTANTS.VHD holds the total number of interrupt and flag bits.
6.3 task_reg (-3)
The task register can be read (Macro task@) and written (Macro task!) at address -3. The task
register defines the base address for the tst and the tld instructions.
task_reg and its associated instructions will only be instantiated when WITH_TASKS is set to '1' in
CONSTANTS.VHD.
6.4 version_reg (-4)
The version register can only be read and it returns the version number of the FPGA configuration
that is defined as constant VERSION in CONSTANTS.VHD.
6.5 debug_reg (-5)
This register serves as umbilical interface for the debug monitor to communicate with the debugger
on the host computer. Reading debug_reg will raise an exception when no information is available.
Writing debug_reg will raise an exception when the output buffer is not empty.
For this mechanism to work, an appropriate exception service routine has to be present. In its
simplest form this is just:
#esr USR: esr
r> 1- BRANCH ;USR
This exception handler will re-execute the fetch instruction, which raised the exception, until the
exception condition is no longer valid.
6.6 ctrl_reg (-6)
The control register is a collection of individual bits, whose current status may be read, and which
can be individually set to '1' or '0' depending on the sign bit:
When the sign bit is not set, those bits, which are set to 1 will be set in the control register. Those
bits set to 0 will not change their corresponding bit in the control register.
When the sign bit is set, those bits, which are set to 0 will be reset in the control register. Those bits
set to 1 will not change their corresponding bit in the control register.
This way individual control bits can be modified without affecting the other control bits.
Constant CTRL_WIDTH in CONSTANTS.VHD determines the number of control bits. If set to
zero, ctrl_reg will not be instantiated at all.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
22 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
6.7 timer (-7)
This register allows to specify timed delays in software. It is a free running counter, which may be
read any time to return the current number of "ticks". Constant TICKS_PER_MS in
CONSTANTS.VHD sets the counter speed in "ticks per millisecond".
The time? instruction can be used to compare the timer against a number on the stack.
7 Booting
Given uCore's hardware architecture, this is very simple:
A RESET signal resets all registers to zero. Because the code for a NOP { BRA NONE NEVER }
happens to be all zeros, the processor just fetches the instruction pointed to by the PC register
(which had also been reset to zero) in the first cycle. Therefore, the reset vector happens to be at
memory address zero.
7.1 Boot Loader
After FPGA configuration, internal flip-flop BOOTING_N will be set to '0' and the program
specified in BOOTLOAD.VHD will be executed. It will e.g. copy an external flashROM into the
program memory proper. An absolute branch to zero (the reset location) will toggle BOOTING_N
and start fetching instructions from the program memory proper. Depending on the length of the
boot loader, BOOT_ADDR_WIDTH has to be set appropriately in CONSTANTS.VHD. The boot
loader will be statically synthesized into the FPGA configuration as a ROM.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
23 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
8 Interrupts
8.1 The Interrupt Mechanism
Don't forget: At first, interrupt requests have to be synchronised or else you might run into
metastability troubles!
In the succeeding cycle(s) the following mechanism will unfold by hardware design:
1st cycle:
The current program memory address will be loaded into the PC un-incremented.
The int instruction will be loaded into the instruction register instead of the output of the
program memory.
2nd cycle:
Now, the int instruction will be executed, which performs a call to the fixed Interrupt
Service Routine (ISR) address, and the status register is pushed on the data stack at the same
time.
Therefore, only the first interrupt cycle must be implemented by hardware. The second cycle is
realised by executing the int instruction.
8.2 Handling Multiple Interrupt Sources
Whenever an interrupt source whose corresponding interrupt enable bit is set in the interrupt enable
register (see: 6.1) is asserted, its associated bit in the int_reg will be set and an interrupt condition
exists. An interrupt acknowledge cycle will be executed when the processor is not currently
executing an interrupt (IIS status bit not set) and interrupts are globally enabled (IE status bit set).
Note: Neither the call to the ISR address nor reading int_reg will clear the interrupt flag. It is the
responsibility of an interrupt server to reset its interrupt signal in the external hardware as part of its
specific interrupt service routine.
uCore has only one central interrupt service routine and therefore, int_reg has to be fetched after
entering the service routine, anding individual interrupt flags one at a time in order to determine the
interrupt source(s).
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
24 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
9 Exceptions
The Exception signal complements interrupts:
An interrupt is an event that has not been anticipated by the software currently being executed.
An exception is an event that has not (yet) happened although anticipated by the software.
Therefore, the processor has to wait or - in the case of a multi-tasking environment - it could process
another task.
To my knowledge, the Transputer has been the first processor to realize an exception mechanism in
hardware, which was used to perform a task switch that was entirely realized in hardware. Nice as
this feature and the underlying philosophy of its programming language Occam may be, it crippled
the transputer for traditional programming languages. This in turn did make the transputer difficult
to understand and market. It never became really popular although its users were happy with it.
Nevertheless, hardware support for multitasking seems to be an attractive feature greatly simplifying
software engineering for complex systems. Analysing the real needs w.r.t. multitasking support it
became clear that a full-blown task switch mechanism in hardware is not really needed. Instead, a
mechanism that would allow to access resources that may not be ready using fetch and store without
the need to explicitly query associated status flags beforehand is all that is needed to hide
multitasking pains from the application programmer.
Therefore, uCore has an Exception mechanism to support multitasking or, to be less ambitious, to
deal with busy resources. Fortunately, it turned out that the implementation of this mechanism in
uCore comes almost for free and therefore, it is build into the core from the very beginning.
9.1 EXCEPTION Signal
An additional external control input has been added: EXCEPTION. When the processor intends to
access a resource, the resource may not be ready yet. In such an event, it can assert the
EXCEPTION signal before the end of the current instruction execution cycle. This disables storing
the next processor state into any register but the instruction register, which loads the exc
instruction instead of the next instruction from program memory.
In the next processor cycle, exc will be executed calling the Exception Service Routine (ESR) at
its fixed address.
The ESR address will typically hold a branch to code that performs an operating system dependent
task switch. Please note that the return address pushed on the return stack is the address of the
instruction following the one that caused the exception. Therefore, before re-activating the excepted
task again, the return address on the return stack must be decremented by one prior to executing an
exit instruction in order to re-execute the instruction, which caused the exception previously.
Please note that no other parameter reconstruction operation prior to re-execution is needed, because
the EXCEPTION cycle fully preserves all registers but the instruction register.
The EXCEPTION mechanism is independent from the interrupt mechanism. It adds one cycle of
delay to an interrupt acknowledge when both an interrupt request and an EXCEPTION signal
coincide.
In essence, the EXCEPTION mechanism allows to access external resources without having to
query status bits to ascertain the availability/readyness of a resource. This greatly simplifies the
software needed for e.g. serial channels for communicating with external devices or processes.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
25 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
10 Data Memory Access
On a data memory access, TOS holds the base address and NOS holds/receives the value to be
exchanged with memory. Pre-incrementing access operators ld and st have been defined. The
group field is used as a signed increment spanning the range from -4 .. 3 and after the memory
access, the incremented address remains in TOS.
Relative addressing into the return-stack can be done using instructions lld and lst. The actual
memory address is the sum of the base address in RSP and the index in TOS. After the memory
access, TOS holds the effective address in the data memory. Please note that an index of zero should
address the element in TOR. uCore does not support this and therefore, compilers have to allocate
an additional dummy element on the return stack if this feature is needed in a stack frame.
Relative addressing into the data memory uses the tld and tst instructions. Prior to the
memory access the effective address as sum of the base address in the task register and an index in
TOS is computed. The task register typically points at the Task Descriptor Block of the running
task. After the memory access, TOS holds the effective address.
10.1 Memory Map
Addresses are shown for a 16 bit data path width by way of example.
$FFFF
2reg_addr_width
internal registers
memory mapped
external I/O
"van Neumann"
program memory
2prog_ram_width
8 bit program memory cells
$7FFF
Data memory
2rs_base_width
Return Stack
(growing towards
lower addresses)
2data_addr_width
Data memory
$0000
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
26 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
11 uCore Instruction Cycle
Due to the utilisation of synchronous blockRAM for the stacks and data and program memory
"caches", one basic uCore instruction cycle consists of two phases and therefore, at least two input
clock cycles (CLK) are needed for each instruction execution cycle.
Phase_1
Phase_2
CLK
Instruction
execution
cycle
Data memory and return stack, program
memory address computation.
Write to and read from memories,
instruction fetch, latching of results
Since version 1.3 uCore is a fully synchronous design with one single clock and clock_enable
signals as needed. Therefore, two constants are defined in CONSTANTS.VHD: PHASE_1 and
PHASE_2. PHASE_1 specifies the number of CLK cycles for the first phase, PHASE_2 specifies
the number of CLK cycles for the second phase of each instruction execution cycle. At the end of
PHASE_1, the addresses for the synchronous blockRAMs are latched. After PHASE_2, the results
of the execution cycle are latched into all registers.
For the central uBus, either a tri-state or a multiplexed implementation may be chosen depending on
constant TRI_STATE in CONSTANTS.VHD. In the tri-state case, signal uBus.en is used to maskoff bus contention situations due to differences in bus enable signal latencies.
12 uCore Scaling
Setting constants in CONSTANTS.VHD allows to instantiate different configurations, design
alternatives, and computational capabilities. The default values are shown in each header line.
12.1 Synthesizer Switches
CONSTANT simulation
: STD_LOGIC := '0';
Set to '1' during simulation. It allows to speed up timers and to initialise counters such that
they can be easily observed during simulation.
CONSTANT tri-state
: STD_LOGIC := '1';
When the FPGA technology used allows the use of tri-state buffers this constant can be set to
'1'. If set to '0', multiplexers will be used to implement the uCore bus. A tri-state implementation usually uses fewer logic and routing resources. uBus.en is used to prevent bus contention.
CONSTANT keeper
: STD_LOGIC := '0';
When the FPGA technology used allows for KEEPER circuits on output or tri-state pads this
constant can be set to '1'. If set to '0', latches will be instantiated on outputs as e.g. IO_ADDR
or IO_DATA in order to keep those outputs from toggling when no external circuits are
actually accessed.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
27 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
CONSTANT syn_stackram
: STD_LOGIC := '1';
When set to '1', the stack_ram will be realized as synchronous blockRAM. Otherwise, it will
be realized as asynchronous RAM, which may be internal or external to the FPGA.
CONSTANT with_mult
: STD_LOGIC := '0';
The constant may be set to '1' if the FPGA used contains multiply hardware resources,
generating code for a one cycle multiply. Otherwise, a multiply takes data_width+4 cycles.
CONSTANT with_tasks
: STD_LOGIC := '1';
When set to '1', the task register and instructions tst and tld will be instantiated. For
multi-tasking, tasks_addr_width (see below) has to be set appropriately as well.
CONSTANT sat_arith
: STD_LOGIC := '0';
When set to '1', well defined results will be returned in case of an overflow. If the result would
be positive, the largest positive number will be returned. If the result would be negative, the
smallest negative number will be returned. Zero will be returned when dividing zero by zero.
12.2 Clock Issues
These clock constants allow to adapt uCore to any system clock even if its cylce time is less than the
physical delay of one uCore cycle. Please note that one uCore cycle is at least two system clock
cycles long, because of the internal FPGA block-RAM, which needs an extra clock transition in
order to latch the address prior to a memory read.
CONSTANT sys_frequency
: NATURAL := 12288000;
Frequency of the external system clock fed into the FPGA. Needs to be known in order to
compute baud rate delays.
CONSTANT phase_1
: NATURAL := 1;
Number of external system clock cycles for the address computation phase. At the end of
phase_1, the address is latched into block-RAM memory if the current instruction performs an
internal memory access.
CONSTANT phase_2
: NATURAL := 1;
Number of external system clock cycles for the memory access and operand execution phase.
CONSTANT baudrate
: NATURAL := 115200;
Defines the effective baud rate of the UART. Only needed if a UART is used as umbilical
interface to the debugger on the host computer.
12.3 Vector Widths
CONSTANT data_width
: NATURAL := 16;
This defines the data path width and therefore, the magnitude of the numbers that may be
processed.
CONSTANT prog_addr_width
: NATURAL := 12;
Program memory address width sets the size of the program memory. It can be at most
data_width wide because all program addresses must fit on the return stack as return address.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
28 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
CONSTANT boot_addr_width
: NATURAL := 12;
Specifies the number of address bits needed for the boot loader program that is statically
synthesized into the design. It has to adapt to the length of the chosen boot program such that
(2boot_addr_width) >= boot_program_length. The boot loader is active after FPGA configuration
and can be used to load the application program off e.g. a HDD or flash memory.
CONSTANT pcache_addr_width : NATURAL := 8;
Number of address bits of the program memory area that uses internal synchronous
blockRAM. When pcache_addr_width=0, no internal RAM is used; when
pcache_addr_width=prog_addr_width, no external RAM is used.
CONSTANT prog_ram_width
: NATURAL := 8;
Number of address bits that may be used to modify the program memory van Neumann style.
If set to zero, the program memory operates as a canonical Harvard Architecture ROM. It may
be at most data_width-2 wide and the program memory is mapped into the address space that
has the most significant bit of the address set. Please note that the internal registers, which are
located at the top end of the address space, take precedence over a potential program memory
access in case the address spaces overlap.
CONSTANT ds_addr_width
: NATURAL := 5;
Number of address bits for the data stack memory. The memory space needed for the data
stack is 2(ds_addr_width+tasks_addr_width) data-words long.
CONSTANT tasks_addr_width
: NATURAL := 3;
Number of address bits for the task address. 2tasks_addr_width copies of the data and the return
stack will be allocated. The task address is added to the left of both the current ds_address and
rs_address, which may have different widths.
CONSTANT data_addr_width
: NATURAL := 12;
This sets the address range for the data memory, which can be data_width-1 wide at most,
because the upper half of the address range is used for memory mapped I/O and internal
registers.
CONSTANT dcache_addr_width : NATURAL := 8;
Number of address bits of the data memory area that uses internal synchronous blockRAM.
When dcache_addr_width=0, no internal RAM is used, when
dcache_addr_width=data_addr_width, no external RAM is used.
CONSTANT rs_base_width
: NATURAL := 12;
The return stack is a data memory area. It extends downwards towards lower addresses
starting at 2rs_base_width, which is exactly one address above the return-stack area.
CONSTANT rs_addr_width
: NATURAL := 5;
Number of address bits for the return stack memory. The memory space needed for the return
stack is 2(rs_addr_width+tasks_addr_width) data-words long.
CONSTANT usr_vect_width
: NATURAL := 3;
The call destination addresses for two adjacent USR instructions will be 2usr_vect_width apart
from each other. "Adjacent USR instructions" are those, whose group field difference has a
magnitude of 1.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
29 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
CONSTANT reg_addr_width
: NATURAL := 5;
Number of address bits reserved for internal memory mapped registers that reside at the upper
end of the address space.
13 Software Development
An interactive software development environment for uCore is rather straightforward and it has
been realized under Linux and Windows on top of gforth under GNU GPL conditions.
A "debuggable uCore" has an additional umbilical interface that can be controlled by a two wire
UART (RxD, TxD) interface on the PC. The program memory, which must be realized as a RAM,
can be loaded through this interface. After loading the application, a small debug monitor (see:
DEBUGGER.FS) takes control exchanging messages with the host. The following tasks can be
done under control of a host PC:

Loading a program into uCore's program memory

Resetting uCore

Single-step debugging uCore with breakpoints

Observing variables, buffer areas, semaphores, and the task list.
For details see the chapter on the debugger in the uForth_166 document.
13.1 Forth Cross-Compiler
It loads on top of gforth (Windows and Linux), because these are free 32-bit systems. It can produce
a binary image and a symbol table file for the debugger, a VHDL file for simulation, and a MEM
file for FPGA blockRAM configuration.
Because the Forth systems are 32 bit systems, the cross-compiler only supports numbers up to 32
bits signed magnitude. For larger data path widths, the cross compiler has to be extended
accordingly, if larger numbers need to be compiled.
It is a short but rather complex piece of code and my 4th iteration on implementing a Forth crosscompiler in Forth. The most challenging aspect was compiling uCore's branches, which, as relative
branches, are preceded by a variable number of literal nibbles. The cross-compiler at first tries to get
away with one literal nibble for the branch offset. If it turns out that this is not sufficient space for
the branch offset at the closing ELSE, THEN, UNTIL, REPEAT, NEXT, or LOOP, the source code
is re-compiled again, leaving ample space for the required number of literal nibbles in front of the
branch opcode.
Several peep-hole optimizations have been implemented and the cross-compiler is of production
quality.
13.2 C Cross-Compiler
Not realised yet although uCore will be able to execute compiled C-code efficiently.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
30 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
14 Project Status
14.1 Products/projects
Seismic data-logger Geolon-MCS:
A 32-bit implementation with all options enabled consumes 40% of the resources of a Xilinx
XC2S200 FPGA. With a 25 MHz clock it executes one instruction every 120 ns (PHASE_1=2,
PHASE_2=1), using external memories for data/return-stack and program.
Seismic deep towed streamer controller:
A 24-bit implementation with all options enabled consumes 25% of the SLICEs of a Lattice XP2-8E
FPGA. Using all blockRAM resources for internal memories allows for 4 kWords of data/returnstack memory, 16 kBytes of program memory and 1 kWords of data stack memory, which is
sufficient to support 8 concurrent tasks. With a 25 MHz clock it executes one instruction every 80
ns (PHASE_1=1, PHASE_2=1).
GPS to DCF77 pulse converter:
A 16-bit implementation consumes 39% of the SLICEs of a Lattice XP-6C FPGA. Using all
blockRAM resources for internal memories allows for 2 kWords of data/return-stack memory, and
4 kBytes of program memory and one single data stack of 64 elements in logic SLICEs used as
asynchronous memory. With a 25 MHz clock it executes one instruction every 120 ns
(PHASE_1=2, PHASE_2=1).
14.2 FPGA families
uCore has been instantiated on the following FPGA families (in historical order):
Xilins, Lattice, Altera, and Actel.
15 Licensing
uCore will be licensed in the spirit of the licensing terms of the free BSD license applied to a
hardware design. Everybody will be free to use uCore even for proprietary designs without license
fee. The only exclusive right I reserve to myself is the right to verify conformance of derived work
with the original uCore model.
See " uCore_Public_License.pdf" for the licensing terms.
At present, the open license will be restricted to what I call an "exploratory" license (see:
16 Acknowledgements
I would like to thank the following people, without whom uCore would be different or not exist at
all, namely:
Chuck Moore, who invented Forth and pioneered Forth hardware with the design of the NC4000.
Norbert Ellenberger, who backed the design of the FRP1600 that paved the way for the IX1.
Christophe Lavarenne who introduced the Transputer innovations to me.
Adolf Krüger, without whom the literal accumulator would probably still be in a separate register
instead of on the stack.
Ulrich Hoffmann, who ported the debugger to gforth from an earlier C version, which boosted its
capabilities, because it includes the cross-compiler and disassembler in an integrated environment.
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
31 of 32
uCore 1.66
scalable, Dual Stack, Harvard Processor
17 Bibliography
[3 instruction structures]
Xiaoming Fan, Holger Heitsch, Tomasz Malitka, Bernd Rosenthal,
and Klaus Schleisiek "Three Instruction Set Structures for a Stack Processor", Proceedings
euroForth 1995, mail to: [email protected]
© Klaus.Schleisiek, uCore 1.66, 22-June-2011
32 of 32