v1.899 is now out and about.
I changed a little the design philosophy to enable us to have better synchronisation among components.Specifically, the control bus now is composed of 9 lines. The upper 5 bits, enumerate operands and registers within our cpu, the 2 next bits are for passing parameters, the 8th line is for internal interrupts and the 9th (the one that has the jagged shape) is the clock
Now, as of the current state of the machine, we don't yet fetch opcodes from memory, although we are in a state that we could do so. That is, because i haven't programmed yet all the procedures. But then again, programming the procedures is more time consuming rather than brain consuming. Let's see how it's done
Step one would be to fetch an instruction from the ram address, pointed to by the program counter. (The fetch structure is existant, but not yet utilised and i haven't programmed the procedure as of yet).
Next step is to decode the instruction into internal procedures. Those internal procedures are like a hardcoded 1-4 step algorithm that tells the machine what to do exactly. And it makes constructing a mnemonic table, more intuitive.
Instructions are 8 bit words.
MOV instructions start with a 00. The next 3 bit's correspond to the enumerated source register and the last 3 bits correspond to the enumerated destination register. The register enumeration, is as following:
ENUM || REGISTER
00 000 NULL
00 001 ALL FLAGS
00 010 AX
00 011 BX
00 100 Σ (sigma accumulator)
00 101 IObuf (Not a register, just denotes direction of the data bus as input/output from/to ram and peripherals)
00 110 IP(index pointer)
00 111 NULL (reserved for SP)
10 00X PC*
*Note: This is a special register. We cannot address it directly with a mov command. When fully implemented in the future, it's default behaviour will be to increase it's value by one before every fetch step, except if the last instruction was a conditional jump that succeded, in which case we load the value from the IP to which we have loaded the address beforehand. The full functionality is not yet implemented, because i have to consider some small timing details.
Behavioural of registers:
A register that is to be a source, should be set so,during negative clock. At the positive edge of clk, it will enable output to the data bus and will disable itself at the next positive edge.
A register that is to be a destination, should be set so during positive clock. At the negative edge of clk, it will write into itself whatever value it get's from the data bus. Unlike being a source, the procedure of writing lasts one tick instead of the input being enabled untill the next negative edge
To set anything as source/dest, set your enumerator, and set parameter1 to '1' for dest or '0' for source. Give it 3 ticks to settle
In the AX/BX register, set parameter0 to '1' to set them as sources to DATA bus. Also the behaviour of setting them as sources for the ALU is inverted. The output is normally enabled. If you set parameter1 to '0', the output will be disabled untill the next positive edge. This is to output only one register in some specific instances.
Components inside the ALU, are also enumerated in the same way registers are enumerated
ENUM || FUNCTION
01 00X "CONCATENATED NOT'S" (NOT A/NOT B/ INTERNAL NOT A/INTERNAL NOT B)
01 010 ADD
01 011 CMP (as numeric)
01 100 BITWISE
01 101 BITSHIFT/BITROTATE
01 110 NULL (reserved for special operands in future)
01 111 NULL (reserved for special operands in future)
Components in the ALU are ALWAYS destinations, since the input to all of them is automatically AX and BX. Remember the part of the extremely fast unconcious brain? Well, this is the ALU. It knows everything beforehand and it's up to us which answer we want to route to AX/BX or Σ. Ιν μοστ ψασεσ, ςε ςαντ το λοαδ ιντο ΑΧ ορ ΒΧ, βθτ ςηεν ςε ηαωε α ΞΜΠ φθνψτιον ιν ηανδ, ςε ςαντ το λοαδ ιτ ιντο τηε S register so as to set our flags.
This is also the difference between Comparing to get a numeric answer and comparing to prepare a jump.
For adding and comparing, we just need to set parameter1 to '1' (no need to normally, but some error correction never hurt anyone ) and the ouput will be enabled from positive edge until the next positive edge, just like being a destination register.
For bitwise operations, param1 and param0 select the operation. 00 is null, 01 is or, 10 is XOR and 11 is AND
For the bitshift operation, param1 selects wether we Roll or Shift and param0 selects direction left/right. The bitshifter is set so that it bitshifts directly the value of A by the last 3 bits of B (much faster than shifting one by one for x times). When it's down, it will output an internal interrupt to let the controller know it's done.
Note:The jmp struct and it's mnemonics, will be more thoroughly explained in next update were it will be acctually implemented, but you can use it right now if you can write the procedure for it!
If you follow the output path of the flag's, you'll see it ends up in some gates and then in a bunch more cables that end up in the program counter part
Zero flag raised = Lower than
Zero or Equality flag raised = Lower than or Equal
Equality Flag = Equal
Not Equality Flag = Not Equal
Equality or Greater Flag raised = Greater than or Equal
Greater Flag raised = Greater than
Carry flag raised = Jump Carry (to implement Jump Not Carry, what we do is to Jump Carry back to the start of the function and if there isn't carry and we can continue, we load another value in the IP and jump uncoditionally in the next step. For Now, because later on i'll make a direct Jump not carry opcode)
Unconditional Jump = Always '1'.
FETCH AND PROCEDURE DECODING
Let's assume that we just fetched an instruction from RAM. How do we decode it?
Locate this part. The fetch register isn't connected to anything yet so it'll never interact with the data bus. You can set the hypothetical opcode values through those switches. You can also see the "MOV" procedure. The logic behind the procedures, is that we feed a 'step' line with 'HIGH' for 4 ticks and at the 3rd tick we toggle the state of clk so that the enumeration decoders have ample time to be set and can start routing the way we instruct them to route. 4 ticks is the minimal set time to have certainty, but you can increase it. Just append more OR's on every collumn of the "PLL"
The way we generate the clock and the proc line will change, because there will be some cases were we need to slack it to have information certainty, but the way it is now, it'll give you the rough idea of our timing constraints. (Every enumeration decoder can, and should be isochronous with a set time of less than 4 ticks. Just something to keep in mind if you decide to change the enum decoders around.)
But in the case depicted, we don't decode to MOV (all MOV's start with 00. Also 00000000 decodes to moving null to null and essentially means NOP).In this case, we decode for INC, which is this proc:
We get 0101010X as fetched instruction. This decodes to INC. X is the register to put the answer to (0 = AX, 1 = BX)
The first row contains two AND gates with one leg normally on. When they receive a signal that it's their step, they'll output '1' to Bit 2 and 0 of the control bus. This means 00001-01 which translates to (ENUM:All flags, PARAM: Raise Carry flag.)
The next step is to 0001(NOT X)-01 which translates to (ENUM:The other register, PARAM: Disable output to internals)
Next up is 0101010 which translates to (ENUM:ADD, PARAM:Enable)
And lastly 0001( X )-10 which translates to (ENUM: The defined reguster, PARAM:WRITE).
After we execute those steps, we are supposed to increase the program counter and fetch a new thing, but this is not yet implemented as i said before. So here you have it, The INC demo that we started with, but in it's final form.
This is not the final form of the controller though. We need a better clock generator, because in some cases that have to do with arithmetic/logic functions, we need to give some more time for them to set. In edge cases, the ripple adder and the comparator, will need 7 ticks to set to the correct value and the bitshifter will do it's own thing and tell us when it's done. So we will need to 'slack' the clock in those steps. But this is for another time.
That's all the new stuff i added. Those might seem more confusing than when we started, but trust me, it will all make sense in the end.
Now, i will be kinda busy for the forseeable future (like 1-2 weeks) so i'll have little to no time to work on the cpu, but what can we do... If i want to continue doing cool stuff in ONI, i must secure food on my plate first. But by all means, you can add your own opcode decoders if you understood how to program a procedure. Hell, even post it here if you verified that it'll work. I'll be more than happy to credit you even for the smallest contribution. We are after all at the phase where everything is easy but just tedious and time consuming.
This for now. See you later
Arn Vortex v1.899.sav