Instruction Set
There are 18 instructions in the Spew CPU. The instructions are always 16-bits wide (see Table 2).
instr. |
mnemonic |
Description |
00xx |
OSCALL xx |
Use an OS call (int 21h service) |
0xxx |
JP xxx |
Jump to new PC address |
1000 |
RETURN |
Return from a GOSUB call |
1xxx |
GOSUB xxx |
Go (call) a sub-routine |
2xxx |
PUSHB [xxx] |
Push a byte onto the stack |
3xxx |
POPB [xxx] |
Pop a byte from the stack |
4xxx |
LDA [xxx] |
Load A (accumulator) from memory |
5xxx |
STA [xxx] |
Store A into memory |
6xxx |
RDI [(xxx)] |
Read an indirect byte using mem-ptr |
7xxx |
WRI [(xxx)] |
Write an indirect byte using mem-ptr |
8xxx |
RDSYS [0000:0xxx] |
Read byte from system ram[0000:0xxx] |
9xxx |
ADDW [xxx],A |
Add sign-extended A to word variable |
Acpp |
JPcc +pp |
Jump if the condition is true |
Bxxx |
ADCA [xxx] |
A + mem[xxx] + CF |
Cxxx |
SBBA [xxx] |
A - mem[xxx] - CF |
Dxxx |
ORA [xxx] |
A OR mem[xxx] |
Exxx |
ANDA [xxx] |
A AND mem[xxx] |
Fxxx |
XORA [xxx] |
A XOR mem[xxx] |
The OSCALL instruction was created so that you could call the host with limited I/O support such as character in/out, terminate application, etc. You simply used the AH value of the corresponding INT 21h service as the second byte of the instruction. Only a limited number of services were allowed.
For example, to print a string of characters to the Host screen, you would use the following example:
@label1 gosub @writeit db 'This is a test program for SPU.COM ' db #0000 @label3 oscall #0000 @writeit popb @Ptr ; pop pointer popb @Ptr1 ; @NextChar rdi @Ptr ; get char pushb %A lda #0001 ; move pointer addw @Ptr popb %A pushb #0000 ; test if 0 popb %status ; sbba #0000 ; jpz @Done oscall #0006 ; write jp @NextChar @Done pushb @Ptr1 ; push pointer pushb @Ptr ; return @Ptr db #0000 @Ptr1 db #0000
Notice that when the code makes it to @writeit, it pops off the return value as the string pointer. It then reads the string, sending a character at a time to the host's screen using OSCALL #0006, until a null character is found. The code then places a new return value back on the stack and does a return. The new position for the return is now the next instruction after the string declaration, which is at @label3.
The code must do two pushes and pops since it can only push and pop a byte at a time, since the stack pointer is 16-bits wide. The ADDW instruction, however, can sign add to a word value, so the two bytes in memory to store the current PC value, PTR and PTR1, must be in consecutive bytes and in the correct order.
The SPEW CPU does not have a CLC instruction like the 80x86 to clear the carry flag, so to clear out the zero flag, it pushes a value of zero into the status register. Since there is no "dangerous" bits in the status register, like the Direction Flag of the 80x86, it is safe to write all zeros to the register. Please note that the JPZ instruction is not "jump if parity zero", it is the "JumP if Zero" instruction.
Another example of one of the instructions is the ability to read the hosts memory from 0x00000 to 0x00FFF. This was added so that a SPU app could read certain parts of the hosts BIOS data for things like the current shift state, master clock count, current screen mode, etc. This instruction, called the RDSYS instruction, is read only and does not allow you to write to this area.
Since an instruction is 16-bits wide and a memory access is only 12-bits wide, most instructions use the high nibble as the instruction and the remaining 12-bits for the memory operand.
The Jump if Condition instruction uses the high nibble for the instruction, the next nibble for the condition, and the lower byte for the signed relative displacement to jump to. This instruction works very similar to the 80x86 instruction, where as a displacement of 0x00 jumps to the next instruction. This can be used as a NOP instruction. The assembler described below allows the NOP mnemonic and simply output a JPZ 0x00 instruction.
The Assembler
To simplify the assembler, it requires all operands to start with a specific character to indicate what type of operand it is. For example, a label must start with the @ character, while an immediate value must start with the # character and be four digits long, and a register to start with the % character.
The assembler does not check for errors. It assumes that all code is valid. It also only allows 255 characters per line, 50 16-byte symbols (labels), and the total source file must be 32k or less.
Math equations are not allowed.
The following line should not be used. Multiple values on a DB line also should not be used.
db #0000+#0001
I am sure that it would be a simple task to expand these limitations, but for simplicity, I have left them as so. If you improve upon the assembler's functionality, please contact me. I would like to see your work.
As with most assemblers, the semicolon is used for comments.
Since the emulator overwrites the first 256 bytes of the SPU image, the assembler writes a comment to these 256 bytes, with the last character being an EOF character (0x1A). The reason is so that you can use the DOS TYPE command and view the comment up to the EOF character. This way, you have 255 bytes to create a specific comment for each of your SPU files. In some cases, the source code to the SPU image fits in this 255-byte area. The assembler is currently hard-coded to a specific comment, but modifying it to ask for the comment, either from stdin or a filename on the command line, shouldn't be difficult.
To use the assembler, the name of the source file must be in the second character position after the SPU on the command line; for instance, the assembler assumes the filename starts at the first byte in the command line, offset 0x82 in the PSP. The assembler also assumes the .SAM extension for the source file and the .SPU extension for the output image file. These extensions should not be used. The assembler's usage is:
SPU demo
This takes demo.sam, assembles it, and produces demo.spu.
The Emulator
One of the members of our team also included DICE, a really nice emulator/debugger that lets you single step through your code. Its usage is:
DICE /s demo.spu
The /s parameter is used to simply emulate the demo.spu image. Remove the /s parameter to use the debugger.
Conclusion
We had a lot of fun coding for this competition. It was exciting to see what we could come up with. To get the results and source code to this competition, visit http://www.hugi.scene.org/compo/compoold.htm#compo9. You can also view other competitions we had on that same page.
And again, you can get the SPEW assembler, source code, the debugger, and detailed information here and at www.frontiernet.net/~fys/spu.htm.