Chris_C

Instruction set design - request for comments

Recommended Posts

Allthough finding ISE an uphill struggle! (but I am getting there) a long term goal is to design and implement my own CPU design.

 

I'm looking to make something small and simple yet also powerful

 

I'd really value peoples thoughts if only just to help get another perspective

 

 

here's my rough design so far...

1   16bit PC4   8bit registers R0 to R3No condition codes1-3 byte instruction codes          IIII RaRb NNNN NNNNCCAAAA AAAA AAAA AAAAinstruction bits    (IIII)Value               (NNNN NNNN)Address             (AAA...) where CC is present AAA... is a signed 14bit offset                    (should address the bus only be 14bit 16kb probably enough?)Ra                  Register R0 to R3Rb                  Register R0 to R3Rb16                value 0 or 2 representing 16bit register (0 = R0,R1) (2 = R2,R3)if an invalid Rb16 value of 1 or 3 is used then no 16 bit value is added tothe address (code 3 possible expansion ?)IIII    Size    Mnemonic                            Description0000    1       NOP                                 spare ?0001    1       NAND    Ra,Rb                       Ra=Ra   NAND    Rb0010    1       XOR     Ra,Rb                       Ra=Ra   XOR     Rb0011    1       AND     Ra,Rb                       Ra=Ra   AND     Rb0100    1       OR      Ra,Rb                       Ra=Ra   OR      Rb0101    1       NOT     Ra,Rb                       Ra=Ra   NOT     Rb0110    1       ADD     Ra,Rb                       Ra=Ra   AND     Rb0111    1       SUB     Ra,Rb                       Ra=Ra   SUB     Rb1000    1       SR      Ra,Rb                       Ra shifted right Rb bits (only bottom 3 bits of Rb used)1001    1       SL      Ra,Rb                       Ra shifted left Rb bits (only bottom 3 bits of Rb used)1010    1       HALT1011    2       SET     Ra,NNNN                     Ra = immidiate 8bit value1100    3       PUT     ( Rb16 + ADDRESS ) = Ra     Ra is stored in address + Rb161101    3       GET     Ra = ( Rb16 + ADDRESS )     Ra = contents of (address + Rb16)1110    3       JMPcc   Ra,Rb,  PC+(A14-A0 signed)  where cc (top 2 bit of address) = eq ne gt lt1111    3       JMP     Rb16 + ADDRESS              PC = address + Rb16Virtual instructionsJMP     ADDRESS         is actually JMPeq R0,R0, ADDRESS

Share this post


Link to post
Share on other sites

I decided that no one could possibly require more than 16kb of data !

 

This allowed me to tidy up some of my addressing ideas and I made invalid index register use clearer

1   16bit PC4   8bit registers R0 to R38 bit data bus14 bit address bus (16kb)No condition codes1-3 byte instruction codes          IIII RaRb NNNN NNNNCCAA AAAA AAAA AAAAinstruction bits    (IIII)Value               (NNNN NNNN)Address             (AAA...) top 2 bits are condition code if needed (eq,ne,gt,lt)Ra                  Register R0 to R3Rb                  Register R0 to R3Rb16                value 0 or 2 representing 16bit register (0 = R0,R1) (2 = R2,R3)rbX                 invalid Rb16 value of 1if an invalid Rb16 value (RbX) of 1 is used then no 16 bit value is added tothe address (code 3 possible expansion ?)IIII    Size    Mnemonic                            Description0000    1       NOP                                 spare ?0001    1       NAND    Ra,Rb                       Ra=Ra   NAND    Rb0010    1       XOR     Ra,Rb                       Ra=Ra   XOR     Rb0011    1       AND     Ra,Rb                       Ra=Ra   AND     Rb0100    1       OR      Ra,Rb                       Ra=Ra   OR      Rb0101    1       NOT     Ra,Rb                       Ra=Ra   NOT     Rb0110    1       ADD     Ra,Rb                       Ra=Ra   AND     Rb0111    1       SUB     Ra,Rb                       Ra=Ra   SUB     Rb1000    1       SR      Ra,Rb                       Ra shifted right Rb bits (only bottom 3 bits of Rb used)1001    1       SL      Ra,Rb                       Ra shifted left Rb bits (only bottom 3 bits of Rb used)1010    1       HALT1011    2       SET     Ra,NNNN                     Ra = immidiate 8bit value1100    3       PUT     ( Rb16 + ADDRESS ) , Ra     Ra is stored in address + Rb161101    3       GET     Ra , ( Rb16 + ADDRESS )     Ra = contents of (address + Rb16)1110    3       JMPcc   Ra,Rb, ADDRESS              where cc (top 2 bit of address) = eq ne gt lt1111    3       JMP     Rb16 + ADDRESS              PC = address + Rb16Virtual instructions        provided byPUT     (ADDRESS),Ra        PUT ( RbX + ADDRESS ), RaGET     Ra, (ADDRESS)       GET Ra, ( RbX + ADDRESS )JMP     ADDRESS             JMP rbX, ADDRESS 

Share this post


Link to post
Share on other sites

Since you asked for comments...

 

1.  I notice that you don't have a mechanism for subroutine calls and returns.

2.  It's hard to get much performance from an 8-bit data path.

3.  You might consider a stack machine.  It's an easy compiler target.

Share this post


Link to post
Share on other sites

These are good points especially subroutines and returns (can't see how to fit it into the current set)

 

I decided to restrict it to an 8 bit bus initially (there may be son and even granson of this cpu!) to reduce complexity and also to ensure it doesn't take up lots of room...

 

While I can see the attraction of stack machines (from a hardware designers point of view) I've never particularly enjoyed programming them, this will be a machine I'll code in assembler and don't have a need for a C compiler

 

 

That said they are all good points especially subroutines / return, thank you very much for your constructive points.

 

 

 

hmmm NOP and SPARE - I did leave myself wiggle room for call / return.... (but this would need a call stack)

Share this post


Link to post
Share on other sites

These are good points especially subroutines and returns (can't see how to fit it into the current set)

 

 

 

AND R0,R0   is equal to  OR R0,R0.  You can get four out of there.

 

What does "Ra = Ra NOT Rb" do? You might be able to get

  Ra = NOT Ra

  Ra = NEG Ra

plus another 8 spare instructions.

Share this post


Link to post
Share on other sites

AND R0,R0 is equal to  OR R0,R0.  You can get four out of there.

Nice spot! I missed that!

What does "Ra = Ra NOT Rb" do?

Ahem its a typo I meant Ra = NOT Rb or possibly Ra = NOT Ra

8 spare? I need to look again when I get home !

Thanks great feedback!

Share this post


Link to post
Share on other sites

Chris,

 

As someone who is also implementing a CPU, some words of advise:

 

a) Don't use variable sized instructions, unless you want to have your fetch unit as big (almost!) as the rest of the design.

B) Get rid of seldom-used instructions, like NEG. It can be accomplished with other instructions.

c) Focus first on basic, then if your ISA space allows, add other instructions. This may come at expense of a larger decode, but pays off.

d) Pay attention to immediates (either for arithmetic or offsets). This is the *most* space consuming, both HDL and code-size.

e) As johnbeetem already said, you need a calling infrastructure, as well as a calling convention. This is of great importance to get right at first attempt.

f) Condition codes are extremely important, and you need them or an indirect way to generate them. They are used for path selection and value setting.

 

We can further discuss this by email, if you like.

 

Alvie

Share this post


Link to post
Share on other sites

Obviously condition codes are useful, but for some reason I was attempting to see if they were really needed...

I've never seen the point of fixed(immediate) offsets ?

I did intially attempt a fixed size of all instructions but kinda seemed to end up with lots of wasted space for a bunch of instructions

I'm trying to weigh up if a single return address is sufficient or if I need to implement a call stack

 

I'll PM you with my email, it may be that we can help each other by batting ideas back and forth!

Share this post


Link to post
Share on other sites

Well, condition codes as such are definitely not needed; many architectures don't have them.  But they have some kind of conditional instruction.  Oh, I could see doing without it but it's more convenient.

 

A couple of approaches I've seen to doing conditionals without a conditional flag:  checking a register directly (jump if Ra > 0); or skipping the next instruction.  Skipping would be made trickier by having variable length instructions, but could still be done.

 

Including a stack in your instruction set isn't necessary, if you're willing to use ordinary address arithmetic and an index register to accomplish it.  Then what you'd really need to add is an instruction to save PC to a general purpose register, either by itself or combined with a jump.

 

One thing if you're using a small data path size like 8 bits, is you'll want a way to build wider operations out of it.  A common way is to use a carry flag.

 

If you really want to do without conditional instructions, I have some idea how it might be accomplished, here's an example:

"if (A >= 0) then go to T else go to E" might be done as:

    shift A right 7 bits // extract the sign bit

    subtract 1 from A // now, if it was negative, we get 0; if positive, we get 255

    build a target pointer P: P = (A & T) | (~A & E)

    and jump to P

 

But conditional logic is such a common thing to program, you'll probably want it to be easier than that.  Maybe a single instruction, "if the last operation's result was in the range 1-127, copy A to B; otherwise do nothing"?

Share this post


Link to post
Share on other sites

yes, condition codes it is! I don't think life without at *least* a carry flag would be much fun!

 

I'm still trying to work out if I really need a stack, while it would make call/return and even parameters easier - I'm still investigating alternatives.

Share this post


Link to post
Share on other sites

When I was playing around with ISAs I found some of these big decisions were greatly influenced around how you intend to handle interrupts and traps. 

 

If you have carry flags, then you need to be able to save the carry flags somewhere, and then you need a way to restore the flags when returning from interrupts.

 

I have programmed Assembler on a DG Eclipse, and really liked that it had no conditional jumps, just the conditional skip next instruction (which sort of like worked as a prefix) and unconditional jumps.

 

For microcontrollers I liked PICs bit-test-and-skip instructions, as it allowed all the flags to be held in the main memory map. So an ADD would update the carry flag, which is bit 0 in address 3, and then a BTFSC 3, 0 could be used to test if the carry flag was set. A few macros for the STATUS register and the 'C' bit made things cleaner, but it is quite a simple implementation.

Start   movlw  b'10000000'      ;set first LED lit        movwf  LEDPORTLoop    bcf    STATUS, C        ;clear carry bit        call   Delay            ;this waits for a while!        rrf    LEDPORT, f        btfss  STATUS, C        ;check if last bit (1 rotated into Carry)        goto   Loop             ;go back and do it again        goto   Start

Share this post


Link to post
Share on other sites

You don't need call return providing you can get access to PC somewhere.

 

Ultimately however all 8bit microprocessor designs that pursue elegance evolve into a 6809 (and I say that as a Z80 fan)

 

 

You don't actually need a lot of instructions to get a pretty effective processor, but how easy it is to program for and how short the code is are rather different questions. The 6502 for example is pretty minimal and quite effective (if a pita to program), while the 8008 is miniscule but does lack a proper stack and arbitrary depth call/return

 

In your instructions set I'd say you can drop NAND (you have NOT and AND), you can drop NOT (XOR). You can in theory even drop SUB as you have ADD and XOR (thus NOT).

 

Some of the bigger machine word systems also didn't have a jump instruction as such, you merely need store-conditional and you can treat program counter as a register. That also makes stacks or register link calls trivial

 

SL/SR can both be replaced with the more useful rotate operation which is as cheap to implement but can do shift left/shift right/ rotate left/rotate right if combined with AND

 

I suspect you can implement call/return and the stack ok as you've got register relative ops so you can use a register of your choice as stack. The only ugly would be that you basically end up doing "load register with constant computed at link time", stick it in (Rstack), Rstack += 2, JMP xx, and your 'RET' is slightly ugly too

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now