Files
SplitBit-Emulator/Source/Emulator/cpu.c
T

426 lines
14 KiB
C

// cpu.c
// SplitBit CPU Emulator Core
// Written by Anachronaut
// 10/16/2024
#include "cpu.h"
#include "io.h"
uint16_t shiftRegister;
void initializeCPU(CPURegisters *cpu, uint8_t *programMemory, uint8_t *dataMemory) {
cpu->A = 0;
cpu->B = 0;
cpu->Q = 0;
cpu->Status = 0;
cpu->ProgramCounter = 0x0000;
// Every Data Pointer starts at the bottom of Data Memory.
for (int i = 0; i < DATA_POINTERS; i++) {
cpu->DataPointer[i] = 0x0000;
}
cpu->StackPointer = 0xFFFF;
cpu->Program = programMemory;
cpu->Data = dataMemory;
}
void genericBranch(CPURegisters *cpu){
// Load the next two bytes from program memory into the Program Counter.
// Byte order is imporant. Most Significant first, then Least Significant.
cpu->ProgramCounter++; // Move to the next byte. (MSB)
uint16_t DestinationAddress;
DestinationAddress = (uint16_t)cpu->Program[cpu->ProgramCounter] << 8; // Cast the 8 bit value to a 16 bit value and shifts it up to the high byte.
cpu->ProgramCounter++; // Move to the next byte. (LSB)
DestinationAddress = DestinationAddress | (uint16_t)cpu->Program[cpu->ProgramCounter]; // Cast the 8 bit value to a 16 bit value and or it to add it to the desination.
cpu->ProgramCounter = DestinationAddress-1;
}
void genericCall(CPURegisters *cpu){
// Order, low byte, high byte
cpu->Data[cpu->StackPointer] = cpu->ProgramCounter & 0xFF;
cpu->StackPointer--;
cpu->Data[cpu->StackPointer] = (cpu->ProgramCounter >> 8) & 0xFF;
cpu->StackPointer--;
// Push the preserved Data Pointers to the Stack, lowest numbered first.
// Order within each one, low byte, high byte.
// The pointers above PRESERVED_DATA_POINTERS are deliberately left alone, so a
// subroutine can use one to hand an address back to whoever called it.
for (int i = 0; i < PRESERVED_DATA_POINTERS; i++) {
cpu->Data[cpu->StackPointer] = cpu->DataPointer[i] & 0xFF;
cpu->StackPointer--;
cpu->Data[cpu->StackPointer] = (cpu->DataPointer[i] >> 8) & 0xFF;
cpu->StackPointer--;
}
// Push B to the Stack.
cpu->Data[cpu->StackPointer] = cpu->B;
cpu->StackPointer--;
// Push A to the Stack.
cpu->Data[cpu->StackPointer] = cpu->A;
cpu->StackPointer--;
// Perform a Generic Branch to the Address.
genericBranch(cpu);
}
uint16_t *selectDataPointer(CPURegisters *cpu) {
// Every instruction that works through a Data Pointer names which one in the
// byte immediately after the opcode. Out of range selectors are masked down
// rather than rejected, the way a narrow field in hardware would be. It is the
// assembler's job to refuse to emit one in the first place.
cpu->ProgramCounter++;
return &cpu->DataPointer[cpu->Program[cpu->ProgramCounter] & (DATA_POINTERS - 1)];
}
uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
switch(Instruction) {
// 0x - Arithmetic and Logic Operations.
case 0x00:
// ADD - A + B + Carry -> Q
uint16_t result = (uint16_t)cpu->A + (uint16_t)cpu->B + (cpu->Status & 0x01);
if (result > 255) {
cpu->Status |= 0x01;
} else {
cpu->Status &= ~0x01;
}
cpu->Q = result & 0xFF;
break;
case 0x01:
// SUB - A - B - Carry -> Q
result = (uint16_t)cpu->A - (uint16_t)cpu->B - (cpu->Status & 0x01);
if (result > 255) {
cpu->Status |= 0x01;
} else {
cpu->Status &= ~0x01;
}
cpu->Q = result & 0xFF;
break;
case 0x02:
// AND - A and B -> Q
cpu->Q = cpu->A&cpu->B;
break;
case 0x03:
// OR - A or B -> Q
cpu->Q = cpu->A|cpu->B;
break;
case 0x04:
// XOR - A xor B -> Q
cpu->Q = cpu->A^cpu->B;
break;
case 0x05:
// NOTA - not A -> Q
cpu->Q = ~cpu->A;
break;
case 0x06:
// NOTB - not B -> Q
cpu->Q = ~cpu->B;
break;
case 0x07:
// SHL - Shift AB left.
shiftRegister = ((uint16_t)cpu->A << 8) | cpu->B;
shiftRegister = (shiftRegister << 1) | (shiftRegister >> 15);
cpu->A = shiftRegister >> 8;
cpu->B = shiftRegister & 0xFF;
break;
case 0x08:
// SHR - Shift AB right.
shiftRegister = ((uint16_t)cpu->A << 8) | cpu->B;
shiftRegister = (shiftRegister >> 1) | (shiftRegister << 15);
cpu->A = shiftRegister >> 8;
cpu->B = shiftRegister & 0xFF;
break;
//
// 1x - Branch Operations:
//
case 0x10:
// BRI - Branch Immediately
genericBranch(cpu);
break;
case 0x11:
// BRQ - Branch if Q = 0
if(cpu->Q == 0) {
genericBranch(cpu);
} else {
cpu->ProgramCounter+=2;
}
break;
case 0x12:
// BRA - Branch if A = 0
if(cpu->A == 0) {
genericBranch(cpu);
} else {
cpu->ProgramCounter+=2;
}
break;
case 0x13:
// BRB - if B = 0
if(cpu->B == 0) {
genericBranch(cpu);
} else {
cpu->ProgramCounter+=2;
}
break;
case 0x14:
// BRC - Do an immediate branch if the Carry Flag is set.
if (cpu->Status & 0x01) {
genericBranch(cpu);
} else {
cpu->ProgramCounter+=2;
}
break;
case 0x17:
// CALL - Push the Program Counter to the Stack, and perform an immediate branch.
genericCall(cpu);
break;
case 0x1F:
// RET - Return from subroutine, restore the registers and set the Program Counter to the Return Address.
// Pop A from the Stack.
cpu->StackPointer++;
cpu->A = cpu->Data[cpu->StackPointer];
// Pop B from the Stack.
cpu->StackPointer++;
cpu->B = cpu->Data[cpu->StackPointer];
// Pop the preserved Data Pointers from the Stack. This walks the pointers
// in the opposite order to genericCall, and takes the high byte before the
// low byte, so that it exactly mirrors the way they were pushed.
for (int i = PRESERVED_DATA_POINTERS - 1; i >= 0; i--) {
cpu->StackPointer++;
cpu->DataPointer[i] = (uint16_t)cpu->Data[cpu->StackPointer] << 8;
cpu->StackPointer++;
cpu->DataPointer[i] |= (uint16_t)cpu->Data[cpu->StackPointer];
}
// Pop the Return Address from the Stack.
cpu->StackPointer++;
cpu->ProgramCounter = (uint16_t)cpu->Data[cpu->StackPointer] << 8;
cpu->StackPointer++;
cpu->ProgramCounter = cpu->ProgramCounter | (uint16_t)cpu->Data[cpu->StackPointer];
// Add 2 to the Program Counter to skip over the address when it returns.
cpu->ProgramCounter += 2;
break;
//
// 2x - Register Operations:
//
case 0x20:
// RSTA - Reset A to 0.
cpu->A = 0;
break;
case 0x21:
// RSTB - Reset B to 0.
cpu->B = 0;
break;
case 0x22:
// INCA - Add 1 to A.
// Set the Carry Flag if the register overflows.
if (cpu->A == 0xFF) {
cpu->Status |= 0x01;
} else {
cpu->Status &= ~0x01;
}
cpu->A++;
break;
case 0x23:
// INCB - Add 1 to B.
// Set the Carry Flag if the register overflows.
if (cpu->B == 0xFF) {
cpu->Status |= 0x01;
} else {
cpu->Status &= ~0x01;
}
cpu->B++;
break;
case 0x24:
// DECA - Subtract 1 from A.
// Set the Carry Flag if the register underflows.
if (cpu->A == 0x00) {
cpu->Status |= 0x01;
} else {
cpu->Status &= ~0x01;
}
cpu->A--;
break;
case 0x25:
// DECB - Subtract 1 from B.
// Set the Carry Flag if the register underflows.
if (cpu->B == 0x00) {
cpu->Status |= 0x01;
} else {
cpu->Status &= ~0x01;
}
cpu->B--;
break;
case 0x26:
// INIA - Initialize A Immediately from Program Memory.
cpu->ProgramCounter++;
cpu->A = cpu->Program[cpu->ProgramCounter];
break;
case 0x27:
// INIB - Initialize A Immediately from Program Memory.
cpu->ProgramCounter++;
cpu->B = cpu->Program[cpu->ProgramCounter];
break;
case 0x28:
// CCF - Clear the Carry Flag.
cpu->Status &= ~0x01;
break;
//
// 3x - Stack Operations:
//
case 0x30:
// PSHQ - Push Q to the Stack.
cpu->Data[cpu->StackPointer] = cpu->Q;
cpu->StackPointer--;
break;
case 0x31:
// PSHA - Push A to the Stack.
cpu->Data[cpu->StackPointer] = cpu->A;
cpu->StackPointer--;
break;
case 0x32:
// PSHB - Push B to the Stack.
cpu->Data[cpu->StackPointer] = cpu->B;
cpu->StackPointer--;
break;
case 0x33: {
// PSHD - Push the selected Data Pointer Address to the Stack.
// Order, high byte, low byte
// This ordering makes it easier to add offsets with register math.
uint16_t pushed = *selectDataPointer(cpu);
cpu->Data[cpu->StackPointer] = (pushed >> 8) & 0xFF;
cpu->StackPointer--;
cpu->Data[cpu->StackPointer] = pushed & 0xFF;
cpu->StackPointer--;
}
break;
case 0x34:
// POPA - Pop A from the Stack.
cpu->StackPointer++;
cpu->A = cpu->Data[cpu->StackPointer];
break;
case 0x35:
// POPB - Pop B from the Stack.
cpu->StackPointer++;
cpu->B = cpu->Data[cpu->StackPointer];
break;
case 0x36: {
// POPD - Pop a Data Address from the Stack into the selected Data Pointer.
uint16_t *popped = selectDataPointer(cpu);
cpu->StackPointer++;
*popped = (uint16_t)cpu->Data[cpu->StackPointer];
cpu->StackPointer++;
*popped |= (uint16_t)cpu->Data[cpu->StackPointer] << 8;
}
break;
//
// 4x - Data Operations:
//
case 0x40:
// INCD - Increment the selected Data Pointer.
(*selectDataPointer(cpu))++;
break;
case 0x41:
// DECD - Decrement the selected Data Pointer.
(*selectDataPointer(cpu))--;
break;
case 0x42:
// LDA - Load A from Data.
cpu->A = cpu->Data[*selectDataPointer(cpu)];
break;
case 0x43:
// LDB - Load B from Data.
cpu->B = cpu->Data[*selectDataPointer(cpu)];
break;
case 0x44:
// STQ - Store Q into Data.
cpu->Data[*selectDataPointer(cpu)] = cpu->Q;
break;
case 0x45:
// STA - Store A into Data.
cpu->Data[*selectDataPointer(cpu)] = cpu->A;
break;
case 0x46:
// STB - Store B into Data.
cpu->Data[*selectDataPointer(cpu)] = cpu->B;
break;
case 0x47: {
// SETD - Set the selected Data Pointer.
uint16_t *destination = selectDataPointer(cpu);
cpu->ProgramCounter++;
uint16_t Address;
Address = (uint16_t)cpu->Program[cpu->ProgramCounter] << 8; // Cast the 8 bits to a 16 bit value and shift them to the high byte.
cpu->ProgramCounter++;
Address |= (uint16_t)cpu->Program[cpu->ProgramCounter];
*destination = Address;
}
break;
case 0x48: {
// DPUP - Offset the selected Data Pointer up by the value of the next byte of Program Memory.
uint16_t *target = selectDataPointer(cpu);
cpu->ProgramCounter++;
*target += cpu->Program[cpu->ProgramCounter];
}
break;
case 0x49: {
// DPDN - Offset the selected Data Pointer down by the value of the next byte of Program Memory.
uint16_t *target = selectDataPointer(cpu);
cpu->ProgramCounter++;
*target -= cpu->Program[cpu->ProgramCounter];
}
break;
//
// Dx - Output Operations:
//
case 0xD0:
// OUTQ - Write the value of Q to an output port.
cpu->ProgramCounter++;
OutputHandler(cpu->Q, cpu->Program[cpu->ProgramCounter]);
break;
case 0xD1:
// OUTA - Write the value of A to an output port.
cpu->ProgramCounter++;
OutputHandler(cpu->A, cpu->Program[cpu->ProgramCounter]);
break;
case 0xD2:
// OUTB - Write the value of B to an output port.
cpu->ProgramCounter++;
OutputHandler(cpu->B, cpu->Program[cpu->ProgramCounter]);
break;
//
// Ex - Input Operations:
//
case 0xE0:
// INA - Read an Input to A.
cpu->ProgramCounter++;
cpu->A = InputHandler(cpu->Program[cpu->ProgramCounter]);
break;
case 0xE1:
// INB - Read an Input to B.
cpu->ProgramCounter++;
cpu->B = InputHandler(cpu->Program[cpu->ProgramCounter]);
break;
//
// Fx - Special Operations:
//
case 0xF0:
// NOP - Do nothing.
break;
case 0xFF:
// HALT - Set the Halt Bit of the Status Register.
cpu->Status |= 0x80;
break;
default:
// Unknown Instruction.
return 1;
}
return 0;
}
void stepCPU(CPURegisters *cpu) {
if (!(cpu->Status & 0x80)) {
// The CPU is not halted, so do a cycle.
executeOperation(cpu->Program[cpu->ProgramCounter], cpu);
cpu->ProgramCounter++;
}
}