diff --git a/Programs/print.asm b/Programs/print.asm new file mode 100644 index 0000000..dcdd254 --- /dev/null +++ b/Programs/print.asm @@ -0,0 +1,60 @@ +; print.asm + +; Some simple SplitBit subroutines for printing. +; Anachronaut +; 10/27/2024 + +#Program + BRI Start ; Branch immediately to the start of the program. + +printString: + CALL saveRegisters ; Store the registers so we can restore them later. +printLoop: + LDA ; Move the first character of the string into A. + BRA Return ; If A is NULL, the string is finished, so return. + OUTA 0x00 ; Output the character. + INCD ; Increment Data Pointer to the next character. + BRI printLoop ; Branch to the beginning of the loop. + +lineFeed: + CALL saveRegisters ; Save the registers so they can be restored. + INIA 0x0A ; Load the character for linefeed into A. + OUTA 0x00 ; Output it. + CALL restoreRegisters + BRI Return ; We're done. + +saveRegisters: ; Subroutine for storing the registers. + PSHD ; Store the Data Pointer, push it to the stack. + SETD Registers ; Set the Data Pointer to the address for the registers. + STQ ; Store Q. + INCD ; Move to the next address. + STA ; Store A. + INCD ; Move to the next address. + STB ; Store B. + INCD ; Move to the next address. + POPD ; Restore the Data Pointer. + RET ; Return to the caller. + +restoreRegisters: + PSHD ; Save the Data Pointer. + SETD Registers ; Set the Data Pointer to the registers address. + RSTB ; Set B to 0. + LDA ; Load the value for Q into A. + ADD ; Add 0 to move the value to Q. + INCD ; Move to the next register value. + LDA ; Load A with its value. + INCD ; Move to the next register value. + LDB ; Load B with its value. + POPD ; Restore the Data Pointer. + RET ; Return to the caller. + +Return: + CALL restoreRegisters ; Restore the registers. + RET ; Return to the caller. + +#Data + +Registers: ; This is where saveRegisters stores the registers temporarily. + 0x00 ; Q Register + 0x00 ; A Register + 0x00 ; B Register diff --git a/Programs/printHello.asm b/Programs/printHello.asm new file mode 100644 index 0000000..720b1e6 --- /dev/null +++ b/Programs/printHello.asm @@ -0,0 +1,20 @@ +; printHello.asm + +; As a demonstration of the new print routines, here's a new implementation for hello.asm +; Anachronaut +; 10/27/2024 + +; Include the subroutine file. +#Include print.asm + +#Program +Start: +SETD myString ; Set the Data Pointer to the string we want to print. +CALL printString ; Call the subroutine. +CALL lineFeed ; Call the linefeed subroutine. +HALT ; Stop the CPU. + +#Data + +myString: + "Hello, World!" diff --git a/Source/Assembler/Assembler.c b/Source/Assembler/Assembler.c index 76acc9b..8c2bbdd 100644 --- a/Source/Assembler/Assembler.c +++ b/Source/Assembler/Assembler.c @@ -71,11 +71,14 @@ void assemblerCleanup(intermediateElement *intermediateArray, int arraySize, cha } // Free the intermediateArray itself. free(intermediateArray); + // Free the list of included files. freeIncludeList(); + // Free the list of labels. freeLabelList(); - // Free the output file name. + + // Free the output file name free(outputFileName); } diff --git a/Source/Assembler/assembly.c b/Source/Assembler/assembly.c index 05b1d4f..ed2b7d9 100644 --- a/Source/Assembler/assembly.c +++ b/Source/Assembler/assembly.c @@ -27,6 +27,8 @@ Instruction instruction_set[] = { {0x11, "BRQ"}, {0x12, "BRA"}, {0x13, "BRB"}, + {0x14, "CALL"}, + {0x15, "RET"}, // Register Operations: {0x20, "RSTA"}, {0x21, "RSTB"}, @@ -42,12 +44,10 @@ Instruction instruction_set[] = { {0x30, "PSHQ"}, {0x31, "PSHA"}, {0x32, "PSHB"}, - {0x33, "PSHP"}, - {0x34, "PSHD"}, - {0x35, "POPA"}, - {0x36, "POPB"}, - {0x37, "POPP"}, - {0x38, "POPD"}, + {0x33, "PSHD"}, + {0x34, "POPA"}, + {0x35, "POPB"}, + {0x36, "POPD"}, // Data Operations: {0x40, "INCD"}, {0x41, "DECD"}, diff --git a/Source/Assembler/secondPass.c b/Source/Assembler/secondPass.c index 309e99c..19ec427 100644 --- a/Source/Assembler/secondPass.c +++ b/Source/Assembler/secondPass.c @@ -112,9 +112,9 @@ void populateOutputBuffers(intermediateElement *intermediateArray, int arraySize Program[(*programCount)++] = intermediateArray[i].byteValue; // Check if it's a branch instruction. - if ((intermediateArray[i].byteValue & 0xF0) == 0x10) { + if (((intermediateArray[i].byteValue & 0xF0) == 0x10) && (intermediateArray[i].byteValue != 0x15)){ if (intermediateArray[i + 1].type != LABEL) { - fprintf(stderr, "Error: Branch without label.\n"); + fprintf(stderr, RED "Error: Branch without label.\n" RESET); printf("File: %s at line %d.\n", intermediateArray[i].fileName, intermediateArray[i].lineNumber); exit(1); } diff --git a/Source/Emulator/cpu.c b/Source/Emulator/cpu.c index 16776a1..d4c67ea 100644 --- a/Source/Emulator/cpu.c +++ b/Source/Emulator/cpu.c @@ -114,6 +114,24 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) { cpu->ProgramCounter+=2; } break; + case 0x14: + // CALL - Push the Program Counter to the Stack, and perform an immediate branch. + // Order, low byte, high byte + cpu->Data[cpu->StackPointer] = cpu->ProgramCounter & 0xFF; + cpu->StackPointer--; + cpu->Data[cpu->StackPointer] = (cpu->ProgramCounter >> 8) & 0xFF; + cpu->StackPointer--; + genericBranch(cpu); + break; + case 0x15: + // RET - Return from subroutine, restore the Program Counter from the Stack. + // POPP - Pop Data to the Program Counter + cpu->StackPointer++; + cpu->ProgramCounter = (uint16_t)cpu->Data[cpu->StackPointer] << 8; + cpu->StackPointer++; + cpu->ProgramCounter = cpu->ProgramCounter | (uint16_t)cpu->Data[cpu->StackPointer]; + cpu->ProgramCounter += 2; // Because it needs to skip over the address when it returns. + break; // // 2x - Register Operations: // @@ -177,15 +195,8 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) { cpu->Data[cpu->StackPointer] = cpu->B; cpu->StackPointer--; break; - case 0x33: - // PSHP - Push the Program Counter to the Stack. - // Order, low byte, high byte - cpu->Data[cpu->StackPointer] = cpu->ProgramCounter & 0xFF; - cpu->StackPointer--; - cpu->Data[cpu->StackPointer] = (cpu->ProgramCounter >> 8) & 0xFF; - cpu->StackPointer--; break; - case 0x34: + case 0x33: // PSHD - Push the Data Pointer to the Stack. // Order, low byte, high byte cpu->Data[cpu->StackPointer] = cpu->DataPointer & 0xFF; @@ -193,26 +204,18 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) { cpu->Data[cpu->StackPointer] = (cpu->DataPointer >> 8) & 0xFF; cpu->StackPointer--; break; - case 0x35: + case 0x34: // POPA - Pop Data to A. cpu->StackPointer++; cpu->A = cpu->Data[cpu->StackPointer]; break; - case 0x36: + case 0x35: // POPB - Pop Data to B. cpu->StackPointer++; cpu->B = cpu->Data[cpu->StackPointer]; break; - case 0x37: - // POPP - Pop Data to the Program Counter - cpu->StackPointer++; - cpu->ProgramCounter = (uint16_t)cpu->Data[cpu->StackPointer] << 8; - cpu->StackPointer++; - cpu->ProgramCounter = cpu->ProgramCounter | (uint16_t)cpu->Data[cpu->StackPointer]; - cpu->ProgramCounter += 3; // Because it needs to skip over the subsequent branch instruction on return. - break; - case 0x38: + case 0x36: // POPD - Pop Data to the Data Pointer cpu->StackPointer++; cpu->DataPointer = (uint16_t)cpu->Data[cpu->StackPointer] << 8; diff --git a/SplitBit Programming Manual.md b/SplitBit Programming Manual.md index 1cb22f4..17d1616 100644 --- a/SplitBit Programming Manual.md +++ b/SplitBit Programming Manual.md @@ -36,6 +36,8 @@ Hex Code | Mnemonic | Description 11 | BRQ | Branch on Q. If Q is zero, loads the immediate next two bytes of Program Memory into the Program Counter. 12 | BRA | Branch on A. If A is zero, loads the immediate next two bytes of Program Memory into the Program Counter. 13 | BRB | Branch on B. If B is zero, loads the immediate next two bytes of Program Memory into the Program Counter. +14 | CALL | Push the Program Counter to the Stack, loads the immediate next two bytes of Program Memory into the Program Counter. +15 | RET | Restores the Program Counter from the Stack, used to return from subroutines. ### Register Operations: 6 Instructions | Hex Code | Mnemonic | Description | @@ -57,12 +59,10 @@ Hex Code | Mnemonic | Description 30 | PSHQ | Stores Q into Data Memory at the location referenced by the Stack Pointer then decrements the Stack Pointer. 31 | PSHA | Stores A into Data Memory at the location referenced by the Stack Pointer then decrements the Stack Pointer. 32 | PSHB | Stores B into Data Memory at the location referenced by the Stack Pointer then decrements the Stack Pointer. -33 | PSHP | Stores the Program Counter to the next two bytes in the stack, decrements the Stack Pointer by two. -34 | PSHD | Stores the Data Pointer to the next two bytes in the stack, decrements the Stack Pointer by two. -35 | POPA | Reads the location referenced by the Stack Pointer from Data Memory into A then increments the Stack Pointer. -36 | POPB | Reads the location referenced by the Stack Pointer from Data Memory into B then increments the Stack Pointer. -37 | POPP | Restores the Program Counter from the top two bytes in the stack, increments the Stack Pointer by two. -38 | POPD | Restores the Program Counter from the top two bytes in the stack, increments the Stack Pointer by two. +33 | PSHD | Stores the Data Pointer to the next two bytes in the stack, decrements the Stack Pointer by two. +34 | POPA | Reads the location referenced by the Stack Pointer from Data Memory into A then increments the Stack Pointer. +35 | POPB | Reads the location referenced by the Stack Pointer from Data Memory into B then increments the Stack Pointer. +36 | POPD | Restores the Program Counter from the top two bytes in the stack, increments the Stack Pointer by two. ### Data Operations: 7 Instructions Hex Code | Mnemonic | Description @@ -103,26 +103,38 @@ The current implementation has Input 0 and Output 0 hooked to stdout and stdin r ### Example Program: Hello World ``` -; This is a basic hello world program for the SplitBit CPU. -; We'll create a loop that outputs each byte of our string to Output 0, the text console. +; Hello World for SplitBit CPU +; First, we define the string in Data Memory. +Data: +0000 0x48 ; 'H' +0001 0x65 ; 'e' +0002 0x6C ; 'l' +0003 0x6C ; 'l' +0004 0x6F ; 'o' +0005 0x2C ; ',' +0006 0x20 ; ' ' +0007 0x57 ; 'W' +0008 0x6F ; 'o' +0009 0x72 ; 'r' +000A 0x6C ; 'l' +000B 0x64 ; 'd' +000C 0x32 ; '!' +000D 0x0A ; This is a linefeed, it's equivalent to putting '\n' in a string in C. +000E 0x00 ; Zero terminates the string. -#Program - -Start: - LDA ; Load a byte of the string into A. - BRA End ; If A is zero, branch out of the loop. - OUTA 0x00 ; Output the value in A to Port 0, the text console. - INCD ; Increment the Data Pointer to the next byte of the string. - BRI Start ; Branch immediately to the start of the loop. - -End: - INIA 0x0A ; We'll load a linefeed into A and output it to make it look nice. - OUTA 0x00 ; Output it to the text console. - HALT ; Terminate the program. - -#Data - -"Hello, World!" +; Next, we'll create a loop that outputs each byte of our string to Output 0, the text console. +Program: +0000 LDA 0x42 ; Load the first byte of the string into A. +0001 BRA 0x12 ; If A is zero, branch out of the loop. +0002 0x00 0x00 ; The high byte of the branch. +0003 0x0A 0x0A ; The low byte of the branch. +0004 OUTA 0xD1 ; Output the value in A. +0005 0x01 0x00 ; The Output Port to use. +0006 INCD 0x40 ; Increment the Data Pointer to the next byte of the string. +0007 BRI 0x10 ; Branch immediately to the start of the loop. +0008 0x00 0x00 ; The high byte of the branch address. +0009 0x00 0x00 ; The low byte of the branch address. +000A HALT 0xFF ; The end of the program. ``` ### Structure of a SplitBit Binary File: @@ -131,5 +143,5 @@ The Program and Data values are both stored in a single file for loading into th Here's an example hex dump of the hello world program stored in the proper format: ``` -50 52 47 00 0F 26 12 00 0A D1 00 40 10 00 00 28 0A D1 00 FF 44 41 54 00 0E 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 00 +50 52 47 00 0A 42 12 00 0A D1 00 40 10 00 00 FF 44 41 54 00 0E 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A 00 ```