Wednesday 14 September 2016

The bits are almost all in place

We have seen the ALU, the addressing unit, and the way the registers will work, but we have not yet considered how we might join all the bits together.

But we don't yet have a programmer's model, and without that, we don't have a processor - since we can't write a program. Let's consider the sort of things we want to do, and then we can look at the way we'll implement them...

The instruction pointer

The processor has to know which instruction it is supposed to be executing. No matter how the actual execution is implemented, the first part of any instruction is going to be a memory read with the first part of the opcode, and that's going to have to be remembered.

So, we implement a register for this. It's different from many of the other registers in that it is effectively write-only; the processor will never need to read from it (though it will need to act upon its contents). It will hold the opcode throughout the complete execution of the instruction, which may be several system clocks.

Memory access

It also needs to be able to talk to the external memory; without that it's just spinning its wheels. Again, we implement this the same way as we have done the internal registers; but rather than latching the data in the register, we pass the data through to the external memory along with an address, and it latches the data in the desired memory location. Similarly, when we want to read from external memory, we put the desired address on the bus and the memory register allows the data through to the internal memory bus.

Together, the instruction register and the memory 'register' almost complete the full set; the only one remaining is a temporary register which will be used in some of the more complex memory addressing modes.

Internal registers

Here's the list of the internal registers, along with their internal addresses:

0 - ACC - the accumulator
1 - B - one input to the alu
2 - ALU - the other alu input, and the alu output
3 - TEMP - temporary storage
4 - MEM - memory gateway
5 - IR - the instruction register
6 -
7 -
8 - PCL - program counter low
9 - PCH - program counter high
a - SPL - stack pointer low
b - SPH - stack pointer high
c - ADDL - address pointer low
d - ADDH - address pointer high
e - Y - the y register
f - X - the x register

Execution

All instructions follow the same general process; items are moved from one register to another until the instruction is complete. However, it's not quite that simple - let's look at a few of the load instructions...
  • LDA # - load the accumulator with an immediate value held in the next memory location:
    • the program counter is pointing at the instruction, so read the memory (the instruction) and store it in IR; incrementing the program counter
    • read the memory (the immediate value), incrementing the program counter, and store the result in ACC
  • LDA abcd - load the accumulator with the contents of the memory location at abcd:
    • the program counter is pointing at the instruction, so read the memory (the instruction) and store it in IR; incrementing the program counter
    • read the memory (the low byte of the address), incrementing the program counter, and store the result in ADDL
    • read the memory (the high byte of the address), incrementing the program counter, and store the result in ADDH; place the address pointer on the address bus
    • read the memory (the desired data), storing it in ACC, and place the program counter back on the address bus
  • LDA [xy]+k - load the accumulator with the byte pointed to by the XY pair, plus a constant k (of two bytes)
    • the program counter is pointing at the instruction, so read the memory (the instruction) and store it in IR; incrementing the program counter
    • read the memory (the low byte of the offset), incrementing the program counter, and store the result in ADDL
    • read the memory (the high byte of the offset), incrementing the program counter, and store the result in ADDH; place the XY pointer on the address bus (which automatically adds ADD)
    • read the memory (the byte we actually want), storing it in ACC, and put the PC back on the address bus
All instructions start the same way; the data at the current program counter - the next operand - is loaded from memory into the instruction register, and the program counter is incremented.

If there are further data associated with the instruction, these are grabbed next, again incrementing the program counter (PC). Load and store instructions are all similar, with the main difference being the source and destination. Logic and arithmetic instructions work the same way, with an extra stage to move the operand into the B register if required:
  •  ADC # - add the immediate value, plus the carry, to the accumulator.
    • move the instruction to the IR, incrementing the PC
    • move the data to the B register, incrementing the PC
    • set ACC as the other input to the ALU, select 'add' and latch the result
    • move ALU output to the ACC
  •  INC A - increment the contents of the ACC
    • move the instruction to the IR, incrementing the PC
    • move the ACC to the ALU input, select 'inc' and latch the result
    • move ALU output to the ACC

Implementation

So how do we organise things? It is simple to arrange that a reset signal should clear program counter to zero, and cause the first instruction to be available, but what then?

Somehow, we need to arrange that the right things happen at the right times, in the right order. We need a state machine... I'm going to implement it in a rather unusual way, with a fistful of eeproms. This has the disadvantage that I have to program them, but the great advantage that they can be reprogrammed very easily if I decide I need a new instruction, or I program a bug in.

The idea is that the eeprom has a number of inputs: eight bits of the instruction register, two bits of status (which only makes a difference for conditional jumps, but has to be done somehow!), and three or four bits of state counter. I hope that all the instructions can be implemented in fewer than eight states, so let's go for three. That means thirteen address bits for the eeprom - 16k.

As for outputs... hmmm. There are quite a lot of those:
  • source register - 4 bits
  • destination register - 4 bits
  • address selection - 2 bits
  • stack_enable - 1 bit (allow the stack to inc or decrement)
  • up/~down - 1 bit (tell the stack which way to go)
  • pc_enable - 1 bit (allow the PC to increment)
  • alu_cmd - 3 bits (which logic/arithmetic operation?)
  • clc - 1 bit (clear the carry)
  • sec - 1 bit (set the carry)
  • compare - 1 bit (perform a compare)
That's nineteen control bits, plus one more which we will use to indicate that we've finished the instruction. So, three 16k x 8 bit eeproms? Sounds good to me.

Now I need to think about the instructions I want...

No comments:

Post a Comment