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
© Copyright 2026 Paperzz