This assembler is designed for the CS 39 Virtual Machine. It allows you to write mostly-normal assembly code, using labels to specify locations and using symbols to specify register numbers. This page describes the assembly code format and use of the assembler itself.
Below are listed the addressable registers for this architecture and the symbols used to refer to those registers conveniently in assembly code. A description of the typical use for each in assembly programming is provided:
The input format for the assembler is somewhat unusual due to its implementation in Scheme, a derivative of the LISP programming language. The code takes the following format (where the code snippet shown here does nothing interesting, and actually forms an infinite loop):
( (lbl-4 stri %g0 5) ; Set %g0 to 5. (() stri %t2 #x2e30 ; Set %t2 to hold hex value `2e30' (() subr %g1 %g0 %t2) ; %g1 = %g0 - %t2 ; A useless comment for demonstration purposes. (() jmpi lbl-4) ; Jump back to the top and repeat. )
There are a number of elements of this format to observe:
Below is a catalog of opcodes and their associated interpretation of the operands that follow. Specifically, in each description, we will refer to the opcode, as well as operand-0, operand-1, and operand-2. For each instruction, we will provide an example of its use.
This instruction generates an intentional interrupt that forces the processor to trap into the kernel. It is the method by which a program can perform an unusual kind of procedure call, where the procedure is inside the kernel and the arguments are passed in the system registers.
All three operands are unused by this instruction. However, the setting of the system registers are the means of communication to the kernel. The convention for how these registers are used are determine by the kernel itself, which may define how values are passed into and returned from the system call.
Copy a value from one register into another. Set register operand-0 to hold the value contained in register operand-1.
Copy an immediate value into a register. Set register operand-0 to hold the immediate value operand-1.
Copy a value from a main memory location into a register. The main memory address is computed by adding register operand-1 to immediate operand-2. The value at this address is copied into register operand-0. Note that the address must be a word-aligned location.
Copy a value from a register into a main memory location. The main memory address is computed by adding register operand-1 to immediate operand-2. The value in register operand-0 is copied into this address. Note that the address must be a word-aligned location.
Add register operand-1 to register operand-2 and store the result in register operand-0.
Add register operand-1 to immediate operand-2 and store the result in register operand-0.
Subtract register operand-2 from register operand-1 and store the result in register operand-0.
Subtract register operand-2 from immediate operand-1 and store the result in register operand-0.
Multiply register operand-1 with register operand-2 and store the result in register operand-0.
Multiply register operand-1 with immediate operand-2 and store the result in register operand-0.
Divide register operand-1 by register operand-2 and store the result in register operand-0.
Divide register operand-1 by immediate operand-2 and store the result in register operand-0.
Bitwise AND register operand-1 with register operand-2 and store the result in register operand-0.
Bitwise AND register operand-1 with immediate operand-2 and store the result in register operand-0.
Bitwise OR register operand-1 with register operand-2 and store the result in register operand-0. The trailing underscore (_) is critical to ensure a four-byte opcode.
Bitwise OR register operand-1 with immediate operand-2 and store the result in register operand-0. The trailing underscore (_) is critical to ensure a four-byte opcode.
Bitwise NOT register operand-1 and store the result in register operand-0.
Bitwise NOT immediate operand-1 and store the result in register operand-0.
Shift register opcode-1 by the number of bits specified in immediate opcode-2, and store the result in register opcode-0. Note that bits with the value 0 are inserted into the word from the right end.
Shift register opcode-1 by the number of bits specified in immediate opcode-2, and store the result in register opcode-0. Note that bits with the value 0 are inserted into the word from the left end.
Jump to the address in register opcode-0. This instruction causes the virtual machine to set the program counter to the new address in the executable.
Jump to the address in immediate opcode-0. This instruction causes the virtual machine to set the program counter to the new address in the executable.
If register opcode-1 is equal to register opcode-2, jump to the address in immediate opcode-0.
If register opcode-1 is not equal to register opcode-2, jump to the address in immediate opcode-0.
If register opcode-1 is greater than register opcode-2, jump to the address in immediate opcode-0.
If register opcode-1 is less than register opcode-2, jump to the address in immediate opcode-0.
If register opcode-1 is greater than or equal to register opcode-2, jump to the address in immediate opcode-0.
If register opcode-1 is less than or equal to register opcode-2, jump to the address in immediate opcode-0.
Jump to the address in immediate opcode-0. Before jumping, store PC + 32 (that is, the address of the next instruction) into the return address register (%ra).
Use of the VP assembler is reasonably simple. The only difficulty is in handling the errors. But first, a simple example of its use. Assume that we have a file name fib.vma in the current directory that you want to assemble. The following command will assemble it, placing the result in a file name fix.vmx:
assemble.sh fib
If the assembly code is syntactically correct, then it will assemble without any output -- no news is good news. If there is an error in the code, however, you will see one of two kinds of errors:
An error is the number or type of the operands: This kind of error will cause a specific (and somewhat descriptive) error message to be emitted, and the assembly process to terminate, returning you to the shell.
Any other error: All other errors (invalid opcodes, unknown register specifiers, unknown label names, etc.) will result in a more crytpic error message to be emitted. You will be left within the Scheme interpreter, at a single greater-than (>) prompt. The following will allow you to return to the shell:
(exit)
While future versions of the assembler may provide more helpful error messages in these cases. Meanwhile, I hope that the error messages provide some clue as to the error.
Be sure to follow the format of the assembly code (particularly the use of parentheses) described above on this page. Errors in this format may cause some of the most cryptic errors.