Close

We can perform simple additions!

A project log for TMS9900 compatible CPU core in VHDL

Retro challenge 2017/04 project to create a TMS9900 compatible CPU core. Again in a month... Failure could be an option...

erik-piehlErik Piehl 04/12/2017 at 01:080 Comments

Now the design implements a few more instructions, totalling five:

LI Rx,imm
AI Rx,imm
LWPI imm
LIMI imm4
JMP offset8
These instructions all have immediate operands and are two words long, except the JMP which a single word instruction.

Above imm is a 16 bit immediate value, imm4 a four bit immediate value, Rx designates a register R0-R15, and offset8 a 8-bit signed offset.

The TMS9900 is an unusual processor in that it only has three registers directly accessible for the programmer, yet the programming model provides the programmer with 16 registers R0-R15. This is done by means of indirection: the register W points to a word aligned region of memory, where the 16 "workspace registers" are kept, taking 32 bytes. The hardware registers are:

PC program counter

W workspace pointer

ST status register

This architecture means that the memory bus gets very busy when executing instructions. The most advanced instruction I have implemented so far is AI (add immediate) where a constant immediate number is simply added to a workspace register. For example AI R3,1 would add the number 1 to workspace register R3. Simple, right? It is, but when you implement this part of the microprocessor core, a whole lot of states are needed, the VHDL code does roughly the following:

1. Fetch state, initiate the opcode fetch from address pointed to by the PC register

2. Start the memory cycle, also increment PC by 2 to point to next opcode

3. Wait for the memory cycle to finish

4. Decode state, examine the opcode that was fetched and write it to the instruction register IR. Here we see that the instruction is AI and go to the first state of AI processing

5. Immediate operand fetch, the AI instruction is a two word instruction, so at this point another fetch from PC is prepared.

6. Do the memory cycle, also increment PC by 2 to point to next opcode. Similar to steps 2&3.

7. Execute step starts: the AI instruction needs the old value of R3. For that we need to first calculate where R3 is. Thus we initiate an ALU cycle to add W and 3*2 (registers are 16 bits, i.e. two bytes).

8. The ALU has done the addition for the address of R3, so we can initiate a memory cycle from that address to fetch old contents of R3. The address of R3 is stored for later.

9. Once the R3 fetch is complete, another ALU cycle is initiated. This time it is the actual addition operation, so the contents of R3 and the immediate operand are forwarded to the ALU input registers. Also the ALU is configured for an add operation.

10. Finally a memory write cycle is started, to store the result value from the previous state 9 to the address calculated in step 8. The outgoing databus is driven with the data (embedded cores do not have three state databuses, instead there is a separate output bus and another input bus.

11. Wait for memory cycle completion. Once that is done, go back to state 1 for the next instruction.

In order to perform the above operations, an ALU also needed to be added. The ALU is not yet complete, it just does a few operations and does not compute all the status flags.

I have successfully simulated the following program, and that proves that the LI, AI and JMP instructions work. TMS 9900 assemblers typically implement the NOP (no-operation) as a JMP to the next instruction, there is no bespoke opcode for that. The program below does not show the reset vectors.

* Erik Piehl (C) 2017 April
* test9900.asm
*
* Test program sequences to test drive the TMS9900 VHDL core.
*

	IDT 'TEST9900'

BOOT
	NOP
	LI R3,>ED07
LOOPPI
	AI	R3,>0001
	JMP LOOPPI

SLAST  END  BOOT
When compiled, the following VHDL code implements the ROM memory containing the above program for simulation purposes:
        -- Program ROM
        type pgmRomArray is array(0 to 11) of STD_LOGIC_VECTOR (15 downto 0);
        constant pgmRom : pgmRomArray := (
                x"8300", -- initial W
                x"0008", -- initial PC
                x"BEEF",
                x"BEEF",
                x"1000",                                -- BOOT: NOP
                x"02E0", x"83E0", -- LWPI >83E0
                x"0203", x"ED07", -- LI R3,>ED07
                                                                -- LOOPPI
                x"0223", x"0001", -- AI R3,>0001
                x"10FD"                         -- JMP LOOPPI
        );

Discussions