Simulating the Instruction Set

A project log for Suite-16

Suite-16 is a 16-bit cpu built entirely from TTL. It is a personal exploration of how hardware and software interact.

monsonitemonsonite 10/16/2019 at 11:220 Comments

Simulating the Instruction Set

Simulation is a powerful tool, and for a simple processor it is relatively quick to right some C code to give you confidence that your instruction set is doing what it is supposed to.

A simple processor may be simulated in fewer than a hundred lines of C code - and once you have implemented the basic model, you can begin to optimise the instruction set, adding and removing instructions until you think you have a useful set.

The most basic of simulators sets up an array of 16-bit unsigned integers in memory and this is where you need to place your instructions, one after the other, as they would exist in the program memory of the simulated processor.  I have chosen to express this array as hexadecimal digits, because it's so much easier to work with hex instruction mnemonics on Suite-16.

#define MEMSIZE  1024
int M[MEMSIZE] = {
        0x1100,     // SET R1, 0x10
        0x2100,     // LD AC, R1
        0xD000,     // DEC AC
        0x0103,     // BNZ 03
        0x1000,     // SET AC, 0x20
        0xD000,     // DEC AC
        0x0107,     // BNZ 07
        0x0000,     // BRA 00

This  very simple example executes a couple of down counters, testing the DEC instruction and conditional branching BNZ

Hand assembling anything more than a few instructions at a time can get tedious - so my next software task will be to create a tool, similar to an assembler which will make this process a lot easier.

I have chosen to simulate the Suite-16 cpu using an MSP430 microcontroller.  

Firstly because it is a 16-bit cpu with a very orthogonal instruction set. Secondly it can easily be programmed using a derivative of the Arduino IDE called Energia, and thirdly it gives me access to 256kbytes of non-volatile FRAM memory and a microSD card.  I view the Suite-16 cpu as a very much cut down version of the MSP430 but sharing a similar instruction set. Until I have actually built the TTL computer, the MSP430 might have to stand in as a hardware equivalent.

As Energia is a similer IDE package to Arduino, it should be possible to port the Suite-16 simulator to any microcontroller supported by Arduino. This includes everything from the humble ATtiny to the 400MHz STM32H743.

The simulation model is built around a simple  switch-case structure - switching on the various values that form the op-code field.

 /* Opcode Execute */
    switch (op) {
    case 0x0:   break ; 
    case 0x1:   R[n]= M[PC] ; PC++     ; break ; /* SET */      
    case 0x2:   R[0] = R[n]            ; break ; /* LD */
    case 0x3:   R[n] = R[0]            ; break ; /* ST */
    case 0x4:   R[0] = M[pointer]      ; break ; /* LD@ */
    case 0x5:   M[pointer] = R[0]      ; break ; /* ST@ */
    case 0x6:   R[0] = M[pointer]      ; R[n]= R[n]-1 ; break ; /* POP */
    case 0x7:   M[pointer] = R[0]      ; R[n]= R[n]+1 ; break ; /* PSH */  
    case 0x8:   R[0] &= R[n]           ; break ; /* AND */
    case 0x9:   R[0] |= R[n]           ; break ; /* OR */
    case 0xA:   R[0] += R[n]           ; break ; /* ADD */     
    case 0xB:   R[0] -= R[n]           ; break ; /* SUB */
    case 0xC:   R[n] != R[n]           ; break ; /* COM */
    case 0xD:   R[n] = R[n]-1          ; break ; /* DEC */
    case 0xE:   R[n] = R[n]+1          ; break ; /* INC */
    case 0xF:   R[0] ^= R[n]           ; break ; /* XOR */        

The code snippet above shows the first draft.  REG[0] is the accumulator and R[n] is any of the general purpose registers R1 to R15.

Memory and Register Operations 

Instructions 01 to 07 handle the loading and storing of the selected register from and to the accumulator, both with direct and indirect addressing.  For the direct load and store operations the Accumulator can be loaded with the value in the designated register Rn, or the contents of the Accumulator can be stored in the register Rn.  

Indirect addressing uses the contents of the register Rn to form the memory address for the word in memory M[pointer].

Push and Pop allow the accumulator to be deposited or retrieved from a stack structure addressed by the selected register Rn - which acts as a stack-pointer, and is automatically incremented or decremented after each stack operation.

ALU Operations.

The remainder of the operations 8x to Fx are arithmetical or logical instructions and will thus involve the ALU.

They will either be a two-operand instruction involving the accumulator and a register - such as ADD, SUB, CPR, AND, OR and XOR

or they will be a one-operand operation where the ALU is used to increment or decrement the value within a given register.

Class 0x Instructions

These are reserved for the program flow instructions such as Call, Return, JMP and the conditional branches. 

The conditional branches can be handled with the following bit of code:

 /* Conditional Branches */
    A = REG[0] ;
    if (op == 0) {      // It's an 0x branch - so test the accumulator against the following conditions
                        // then load the PC with the address in the address field
    unsigned int  cond = (IR & 0xf00) >> 8 ; // Extract the condition field

switch (cond) {

       case 0x0:  PC = addr ;              break ;   // BRA Branch Always
       case 0x1:  if(A>0)  { PC = addr ; } break ;   // BGT Branch if Greater
       case 0x2:  if(A<0)  { PC = addr ; } break ;   // BLT Branch if Less Than
       case 0x3:  if(A>=0) { PC = addr ; } break ;   // BGE Branch if Greater or Equal
       case 0x4:  if(A<=0) { PC = addr ; } break ;   // BLE Branch if Less Than or Equal
       case 0x5:  if(A!=0) { PC = addr ; } break ;   // BNE Branch if Not Equal to zero
       case 0x6:  if(A==0) { PC = addr ; } break ;   // BEQ Branch if Equal to zero
       case 0x7:  break ;
       case 0x8:  break ;
       case 0x9:  break ;
       case 0xA:  break ;
       case 0xB:  break ;
       case 0xC:  break ;
       case 0xD:  break ;
       case 0xE:  break ;
       case 0xF:  break ;