P, A, X, Y, S: more than a just another register

A project log for SIFP - Single Instruction Format Processor

A super-scalar, reduced instruction set processor where microcode and machine code are the same thing!

zpekiczpekic 12/13/2023 at 06:500 Comments

Most CPUs contain some number of internal registers, a subset of which are available for programs to use. These are often not much more than very fast internal memory connected to internal bus(es) and ALU(s), meaning that the the transformation of bits crucial for program execution is happening elsewhere and not within logical confines of the register. 

SIFP-16 somewhat extends the concept of register to a "processing element" - each of these in addition to fast value storage is also given own simple ALU, flags inputs and outputs, as well as part of control over CPU data and address paths to make it a self-contained unit of data processing.  

All 5 processing elements present in SIFP-16 have the following basic internal structure:

 Main elements:

Processing element operations are declared in the include file that defines the instruction field bits. This way the microcode source becomes a CPU assembly source file, and assembly instructions can use:

P (program counter)

All program control depends on the operations that P supports. It is important to note that at time of execution of these operations P has been incremented and is pointing to next location in program memory, which can be used either as data (destination for jump, branch offset, immediate) or next instruction.

// 16-bit program counter (has no flags, always increment during fetch phase, changes as below during execute phase)
r_p    .valfield 4 values
    NOP,            // continue (increment P during next fetch phase)
    M[IMM],            // output value as address, increment (used for immediate values)
    BRANCH|IF_TRUE,        // unconditional branch (add word pointed by P)
    JUMP|GOTO,        // unconditional jump (load with word pointed by P)
    LDP,            // load from incoming data (mostly from stack, for return)
    STP4,            // put P + 4 to internal data bus
    STP2,            // put P + 2 to internal data bus
    STP,            // put P to internal data bus (mostly to output during trace mode)
    BAC|IF_AC|IF_A_GE,    // if A flag C set add word pointed by P (branch), else increment P
    BAZ|IF_AZ|IF_A_EQ,    // if A flag Z set add word pointed by P (branch), else increment P
    default NOP;

A (accumulator)

With only 5 remaining operations (after no-op, load and store are used), the dilemma was which others to implement:

X, Y (index / base pointers / counter)

These two are identical in every way. Main uses are indirect and base + index addressing, counters, temporary storage. 

// 16-bit index register X (has own carry (XC) and zero (XZ) flags, changes only during execute phases)
r_x	.valfield 3 values
	NOX,	// no operation on X register 
	CPX,	// compare with incoming data (XC, XZ)
	INX,	// increment by 1	(XC, XZ)
	DEX,	// decrement by 1	(XC, XZ)
	LDX,	// load with incoming data (XZ)
	ADX,	// add incoming data (XC, XZ)
	M[X],	// output value as address 
	STX|X	// output value as data
				default NOX;

S (stack pointer)

A very "classic" stack pointer, growing from high address towards lower. However the S points to TOS (top of stack) not the first free location on stack. So M[S] allows direct read and modification of the TOS. It is initialized at 0 after RESET, so first push will write to location 0xFFFF which is expected to be RAM (ROM is expected at 0x0000, i8080 style)

ADS and LDS allows (with some difficulty) implementation of stack frames and overflow checks. 

// 16-bit stack pointer S (has own carry (SC) and zero (SZ) flags, changes only during execute phases)
r_s	.valfield 3 values
	NOS,	// no operation on S register 
	CPS,	// compare with incoming data (SC, SZ)
	M[POP],	// address is S, then increment
	M[PUSH],// address is S - 1, then decrement
	LDS,	// load with incoming data (SZ)
	ADS,	// add incoming data (SC, SZ)
	M[S],	// output value as address (use to access stack top in memory) 
	STS|S	// output value as data
	default NOS;

Processing elements recap:

Processing elementInstruction bit fieldNumber of instructionsFlags (carry, zero)Project dataProject address
P15..1316(only consumes flags from A, X, Y, S)yes (multiple)yes (multiple)
A12..108ac, azyesno
X9..08xc, xzyesyes
Y6..48yc, yzyesyes
S3..08sc, szyesyes (multiple)