diff --git a/Programs/print.asm b/Programs/print.asm index 1caa5c1..3dbb132 100644 --- a/Programs/print.asm +++ b/Programs/print.asm @@ -5,55 +5,94 @@ ; 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. + BRI start ; Branch immediately to the start of the program. 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. + INIA 0x0A ; Load the character for linefeed into A. + OUTA 0x00 ; Output it. + RET ; Return to the caller. -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. - POPD ; Restore the Data Pointer. - RET ; Return to the caller. +printString: ; Expects Data Pointer to be set to the beginning of the string to be printed. + LDA ; Move the first character of the string into A. + BRA printDone ; If A is NULL, the string is finished, so return. + OUTA 0x00 ; Output the character. + INCD ; Increment Data Pointer to the next character. + BRI printString ; Branch to the beginning of the loop. + printDone: + 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. +; Expects Data Pointer to be set to the one byte integer value to be printed in decimal form. +printDecimal: + LDA ; Load the value into A. + PSHA ; Save it onto the stack. + ; Clear the buffer we're going to write into. + SETD DecimalValue ; Set the Data Pointer to the buffer. + RSTB ; Set B to 0. + STB ; Store it in the buffer. + INCD ; Increment to the tens place. + STB ; Store 0 in it. + INCD ; Increment to the hundred's place. + STB ; Store 0 in it. + INIB 0d10 ; Load 10 into B. + printDecimalLoopStart: + POPA ; Pop the value to A from the stack. + CCF ; Clear the Carry Flag. + SUB ; Subtract 10 from the value in A. + BRC getOnes ; If 10 is bigger than A, We're done looping and A contains the ones. + BRA getOnes ; If the value 0, we're also done. + ; Otherwise, increment the tens place. + SETD DecimalValue ; Set the Data Pointer to the buffer. + INCD ; Increment to the tens place. + PSHQ ; Save the new value onto the stack. + LDA ; Load the number of tens into A. + INCA ; Increment it. + ; Now check to see if there are ten tens and we need to carry to the hundreds. + CCF ; Clear the Carry Flag. + SUB ; Subtract 10 from A. + BRQ incrementHundreds ; If Q is 0, set the tens to 0 and increment the hundreds place. + STA ; Otherwise, store the value back to the tens place. + BRI printDecimalLoopStart ; and loop again. + incrementHundreds: + STQ ; Store 0 in the tens place. + INCD ; Increment the Data Pointer to the hundreds place. + LDA ; Load it into A. + INCA ; Increment it. + STA ; Store it back again. + BRI printDecimalLoopStart ; and Loop again. + getOnes: + SETD DecimalValue ; Set the Data Pointer to the ones place. + STA ; Store A in it. + ; At this point we're done. We just need to print the digits with the printDecimalDigit subroutine. + INCD INCD ; Increment the Data Pointer twice to the hundreds place. + decimalCheckLoop: + LDA ; Load the value into A. + BRA checkTens ; If the hundreds are 0, skip printing their digit. + CALL printDecimalDigit ; Otherwise, print it. + DECD + CALL printDecimalDigit ; And the tens place, too. + BRI printOnes ; And finally, the ones. + checkTens: + DECD ; Decrement to the tens place. + LDA ; Load it into A. + BRA printOnes ; If the tens are zero, skip printing their digit, too. + CALL printDecimalDigit ; Otherwise, print it. + printOnes: + DECD ; Decrement to the ones place. + CALL printDecimalDigit ; Print it no matter what. + RET ; We're done, return to the caller. -Return: - CALL restoreRegisters ; Restore the registers. - RET ; Return to the caller. +printDecimalDigit: ; Expects Data Pointer to be set to the value to print. + LDA ; Load the value into A. + INIB 0x30 ; Load ASCII offset for numbers into B. + CCF ; Clear the Carry Flag. + ADD ; Add it to A. + OUTQ 0x00 ; Send it to the output. + RET ; Return from subroutine. #Data -Registers: ; This is where saveRegisters stores the registers temporarily. - 0x00 ; Q Register - 0x00 ; A Register - 0x00 ; B Register +; This is where printDecimal stores its result. +DecimalValue: + 0x00 ; The ones place. + 0x00 ; The tens place. + 0x00 ; The hundreds place. diff --git a/Programs/printDecimalTest.asm b/Programs/printDecimalTest.asm new file mode 100644 index 0000000..6558479 --- /dev/null +++ b/Programs/printDecimalTest.asm @@ -0,0 +1,31 @@ +; Tests for the printDecimal subroutine. + +#Include print.asm + +#Program + +start: + INIB 0xFF ; Load 255 into B. + loop: + SETD MyDecimal ; Set D to our decimal. + SUB + CCF ; Clear the Carry Flag. + CALL printDecimal ; Print the value. + LDA ; Load the value into A. + INCA ; Increment it. + STA ; Store it again. + SETD Space + CALL printString ; Print a space between the the numbers. + BRQ end ; If Q is zero, we're done. + BRI loop ; Branch back to the start of the loop. + end: + CALL lineFeed + HALT + +#Data + +MyDecimal: + 0x00 + +Space: + " " diff --git a/Programs/printDigitTest.asm b/Programs/printDigitTest.asm new file mode 100644 index 0000000..9c3dc2f --- /dev/null +++ b/Programs/printDigitTest.asm @@ -0,0 +1,24 @@ +#Include print.asm + +#Program + +start: + INIB 0d10 + SETD byte + loop: + CALL printDecimalDigit + LDA + INCA + STA + SUB + BRQ end + CALL lineFeed + BRI loop + end: + CALL lineFeed + HALT + +#Data + +byte: +0x00 diff --git a/Programs/printHello.asm b/Programs/printHello.asm index cb518ad..452dda5 100644 --- a/Programs/printHello.asm +++ b/Programs/printHello.asm @@ -8,13 +8,25 @@ #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. + +start: + SETD MyString ; Set the Data Pointer to the start of MyString. + CALL printString ; Call the string printing subroutine. + CALL lineFeed ; Call the line feed subroutine to end the line. + SETD MyDecimal ; Set the Data Pointer to the value to print as a decimal. + CALL printDecimal ; Call the decimal printing subroutine. + SETD MyMessage ; Set the Data Pointer to another string. + CALL printString ; Call the string printing subroutine again. + CALL lineFeed ; Call the line feed subroutine to end the line. + HALT ; Stop the program. #Data -myString: - "Hello, World!" +MyString: + "Hello, World! " + +MyDecimal: + 0d42 + +MyMessage: + " is the great answer." diff --git a/Source/Assembler/assembly.c b/Source/Assembler/assembly.c index ed2b7d9..b08c6d6 100644 --- a/Source/Assembler/assembly.c +++ b/Source/Assembler/assembly.c @@ -29,6 +29,7 @@ Instruction instruction_set[] = { {0x13, "BRB"}, {0x14, "CALL"}, {0x15, "RET"}, + {0x16, "BRC"}, // Register Operations: {0x20, "RSTA"}, {0x21, "RSTB"}, @@ -40,6 +41,7 @@ Instruction instruction_set[] = { {0x27, "LDB"}, {0x28, "INIA"}, {0x29, "INIB"}, + {0x2F, "CCF"}, // Stack Operations: {0x30, "PSHQ"}, {0x31, "PSHA"}, diff --git a/Source/Emulator/cpu.c b/Source/Emulator/cpu.c index d4c67ea..389048e 100644 --- a/Source/Emulator/cpu.c +++ b/Source/Emulator/cpu.c @@ -121,16 +121,55 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) { cpu->StackPointer--; cpu->Data[cpu->StackPointer] = (cpu->ProgramCounter >> 8) & 0xFF; cpu->StackPointer--; + // Push the Data Pointer to the Stack. + // Order, low byte, high byte + cpu->Data[cpu->StackPointer] = cpu->DataPointer & 0xFF; + cpu->StackPointer--; + cpu->Data[cpu->StackPointer] = (cpu->DataPointer >> 8) & 0xFF; + cpu->StackPointer--; + // Push Q to the Stack. + cpu->Data[cpu->StackPointer] = cpu->Q; + 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); break; case 0x15: - // RET - Return from subroutine, restore the Program Counter from the Stack. - // POPP - Pop Data to the Program Counter + // 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 Q from the Stack. + cpu->StackPointer++; + cpu->Q = cpu->Data[cpu->StackPointer]; + // Pop the Data Pointer from the Stack. + cpu->StackPointer++; + cpu->DataPointer = (uint16_t)cpu->Data[cpu->StackPointer] << 8; + cpu->StackPointer++; + cpu->DataPointer |= (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]; - cpu->ProgramCounter += 2; // Because it needs to skip over the address when it returns. + // Add 2 to the Program Counter to skip over the address when it returns. + cpu->ProgramCounter += 2; + break; + case 0x16: + // BRC - Do an immediate branch is the Carry Flag is set. + if (cpu->Status & 0x01) { + genericBranch(cpu); + } else { + cpu->ProgramCounter+=2; + } break; // // 2x - Register Operations: @@ -177,6 +216,10 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) { cpu->ProgramCounter++; cpu->B = cpu->Program[cpu->ProgramCounter]; break; + case 0x2F: + // CCF - Clear the Carry Flag. + cpu->Status &= ~0x01; + break; // // 3x - Stack Operations: // @@ -195,9 +238,8 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) { cpu->Data[cpu->StackPointer] = cpu->B; cpu->StackPointer--; break; - break; case 0x33: - // PSHD - Push the Data Pointer to the Stack. + // PSHD - Push the Data Pointer Address to the Stack. // Order, low byte, high byte cpu->Data[cpu->StackPointer] = cpu->DataPointer & 0xFF; cpu->StackPointer--; @@ -205,18 +247,18 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) { cpu->StackPointer--; break; case 0x34: - // POPA - Pop Data to A. + // POPA - Pop A from the Stack. cpu->StackPointer++; cpu->A = cpu->Data[cpu->StackPointer]; break; case 0x35: - // POPB - Pop Data to B. + // POPB - Pop B from the Stack. cpu->StackPointer++; cpu->B = cpu->Data[cpu->StackPointer]; break; case 0x36: - // POPD - Pop Data to the Data Pointer + // POPD - Pop Data Address from the Stack. cpu->StackPointer++; cpu->DataPointer = (uint16_t)cpu->Data[cpu->StackPointer] << 8; cpu->StackPointer++; diff --git a/SplitBit Assembler Manual.md b/SplitBit Assembler Manual.md index c42ee70..6fbe203 100644 --- a/SplitBit Assembler Manual.md +++ b/SplitBit Assembler Manual.md @@ -16,11 +16,11 @@ The assembler will accept: Labels may be a string of up to 32 alphanumeric characters that must end with a semicolon, ':'. ``` -ProgramStart: +programStart: -LoopStart: +loopStart: -ErrorHandler01: +errorHandler01: ``` @@ -36,25 +36,28 @@ SplitBit programs may have a Data Segment. You may define the start of the data ## An Example SplitBit Assembly Program: ``` -; 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. +; This is a slightly more advanced hello world program that demonstrates some SplitBit programming conventions. #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. +start: ; By uninforced convention, Program Labels start with a lowercase letter. + SETD HelloString ; Set the Data Pointer to the address of the string. + CALL printString ; Call the string printing subroutine. + HALT ; End the program. -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. +; This is a reusable subroutine that could be includedd in other programs. +printString: ; Expects Data Pointer to be set to the beginning of the string to be printed. + LDA ; Move the first character of the string into A. + BRA printDone ; If A is NULL, the string is finished, so return. + OUTA 0x00 ; Output the character. + INCD ; Increment Data Pointer to the next character. + BRI printString ; Branch to the beginning of the loop. + printDone: + RET ; Return to the caller. #Data +HelloString: ; By uninforced convention, Data Labels start with a capital letter. "Hello, World!" ``` diff --git a/SplitBit Programming Manual.md b/SplitBit Programming Manual.md index 17d1616..d10c46e 100644 --- a/SplitBit Programming Manual.md +++ b/SplitBit Programming Manual.md @@ -29,17 +29,18 @@ Hex Code | Mnemonic | Description 07 | NOTA | Bitwise inversion of A, the result is stored in Q. 08 | NOTB | Bitwise inversion of B, the result is stored in Q. -### Branch Operations: 4 Instructions +### Branch Operations: 7 Instructions Hex Code | Mnemonic | Description -- | -- | -- 10 | BRI | Branch Immediately. Loads the immediate next two bytes of Program Memory into the Program Counter, first the most significant byte, then the least. 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. +14 | CALL | Stores all the registers to the Stack, A, B, Q, the Data Pointer, and the Program Counter, then performs an immediate branch. +15 | RET | Restores all registers from the Stack, then immediately branches to the Return Address by setting the Program Counter to the next instruction after the last CALL. +16 | BRC | Branch if Carry is set. -### Register Operations: 6 Instructions +### Register Operations: 11 Instructions | Hex Code | Mnemonic | Description | | -------- | -------- | ------------------- | | 20 | RSTA | Resets A to 0. | @@ -52,8 +53,9 @@ Hex Code | Mnemonic | Description | 27 | LDB | Loads Data to B via the Data Pointer. | 28 | INIA | Loads the next byte of Program Memory to A. | | 29 | INIB | Loads the next byte of Program Memory to B. | +| 2F | CCF | Clears the Carry Flag. | -### Stack Operations: 9 Instructions +### Stack Operations: 7 Instructions Hex Code | Mnemonic | Description -- | -- | -- 30 | PSHQ | Stores Q into Data Memory at the location referenced by the Stack Pointer then decrements the Stack Pointer. @@ -64,7 +66,7 @@ Hex Code | Mnemonic | Description 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 +### Data Operations: 8 Instructions Hex Code | Mnemonic | Description -- | -- | -- 40 | INCD | Increments the Data Pointer. @@ -103,38 +105,26 @@ The current implementation has Input 0 and Output 0 hooked to stdout and stdin r ### Example Program: Hello World ``` -; 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. +; 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. -; 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. +#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!" ``` ### Structure of a SplitBit Binary File: @@ -143,5 +133,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 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 +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 ```