Minor instruction set update

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 02/19/2024 at 06:440 Comments

After writing some rudimentary assembler routines (with the idea of incorporating them later into a monitor type program), it appears that programming the SIFP is sometimes clunky, sometimes elegant. Lack or registers and absence true base + index addressing modes requires frequent stack saving and retrieving. On the other hand, each register having own flags, and parallelization of some operations in same instruction can offset that inconvenience. To improve the instruction set a bit I implemented following modifications:


SBC is same as 6502-type SBC, except 16-bit instead of 8. AC (accumulator carry) value 1 means "no carry". While XOR and then ADC can substitute SBC, it is inconvenient if one is to implement decimal arithmetic. I plan to do that for SIFC, exactly like 6502, by adding a D (decimal) flag. 

D flag value0 (binary mode)1 (BCD mode)
ADC - second operandno changeno change
SBC - second operand1's complement (XOR with 0xFFFF)9's complement
ResultNot adjustedBCD adjusted


This was actually "RLC" (rotate left through carry), which can be replicated by this simple sequence:



Given the infrequent use of this instruction, trade-off with SBC is probably good choice.


Only the name has changed, it always behaved like a Z80-style RRA. Accumulator and AC flag are treated as one 17-bit register (AC is LSB) and bits in it are rotated towards LSB. 

The new instruction set table now looks like this:


These instructions were always there and working, but I decided to "expose" them using the microcode compiler's primitive ".alias" pragma. From sifp.mcc : 

// Convenient clear operations (internal data bus is 0 by default if not driven)
CLRA	.alias  r_p = NOP, r_a = LDA, r_x = NOX, r_y = NOY, r_s = NOS;	
CLRX	.alias  r_p = NOP, r_a = NOA, r_x = LDX, r_y = NOY, r_s = NOS;	
CLRY	.alias  r_p = NOP, r_a = NOA, r_x = NOX, r_y = LDY, r_s = NOS;	
CLRAX	.alias  r_p = NOP, r_a = LDA, r_x = LDX, r_y = NOY, r_s = NOS;	
CLRXY	.alias  r_p = NOP, r_a = NOA, r_x = LDX, r_y = LDY, r_s = NOS;	
CLRAXY	.alias  r_p = NOP, r_a = LDA, r_x = LDX, r_y = LDY, r_s = NOS;	

 What is going on here?

Each register P, A, X, Y, S has only 1 input. This input is internal data bus, which gets its value by logical OR of all the registers that have output enabled. In case no register is "projecting" data, it the value of the internal data bus is 0. In the processor implementation this is visible here:

-- MUX to allow pushing, storing F to stack memory
int_fdbus <= reg_f when ((i_is_ftos or i_is_pushf) = '1') else int_dbus;

-- internal data bus is logical OR of all registers that project data
int_dbus <= p2d or a2d or x2d or y2d or s2d or d2d;
p2d <= p when (reg_p_d = '1') else X"0000";
a2d <= a when (reg_a_d = '1') else X"0000"; 
x2d <= x when (reg_x_d = '1') else X"0000"; 
y2d <= y when (reg_y_d = '1') else X"0000"; 
s2d <= s when (reg_s_d = '1') else X"0000"; 

When the internal data bus is 0, any register can pick up that value for a convenient "clear", which is somewhat common operation. 

Inherent "superscalar" architecture allows any combination of A, X, Y to be cleared in one instruction. Some of these are exposed as aliases.  It is also possible this way to clear P and S: