| Deadline |
Monday, October 23, 11:59pm |
| Name on Marmoset | P2 |
| To Submit |
Standard Format: asm.cc OR asm.rktPrescanned Format: asm-prescanned.cc OR asm-prescanned.rkt
|
| Marking Scheme | 30 marks for release tests, 70 marks for secret tests |
Your program should be equivalent in functionality and behaviour to
the course tool
cs241.binasm.
It should produce identical machine language output, and it should detect the same errors
(though the error messages do not have to be identical).
Your assembler should be reasonably efficient. It does not need to be highly optimized but it should not take quadratic time
in the size of the input.
Important Reminders: Secret tests, C++ notes, Racket notes
exit function does not call destructors for stack-allocated objects, which may cause you to leak memory and fail tests. To exit after printing an error, throw an exception and catch it in main, then exit normally with return.
Or if the error happens in main itself, you can just exit directly with return.
error function, and other runtime errors caused by mistakes in your code. To report errors, rather than using error, print to standard error (e.g., with eprintf) and then call exit.
mipsscan,
that is, the input is already scanned for you.
If you did not complete Part A of Project 1, you will need to use the Prescanned Format. Otherwise, you can choose which you prefer. The marking scheme is identical for both formats, and the Standard Format is arguably the more difficult option, since you need to ensure your scanner does not have bugs. The main reasons to choose the Standard Format are:
mipsscan when testing.
The bottom section of this page gives a detailed specification of MIPS assembly language, describing the requirements for a valid MIPS assembly program. Anything that does not follow these requirements is invalid.
ERROR in ALL CAPS to standard error.
Given the following input, in Standard Format:
lis $3 .word 241 jr $31Or in Prescanned Format:
ID lis REGISTER $3 NEWLINE DOTID .word DECINT 241 NEWLINE ID jr REGISTER $31 NEWLINERun your program to generate a file containing the machine language output. (For Racket, replace
./asm with racket asm.rkt.)
./asm < input.asm > output.mipsThe output is machine language, so examining it directly on your terminal (which will try to interpret it as ASCII characters) will not generally be productive. To verify the output is correct, you could use a hex dump command like
xxd,
the course tool
cs241.binview.
a graphical hex editor,
or whatever other method you prefer for examining the binary data in a file.
(Think back to how you solved Question 1.)
Note that you can run cs241.binasm with the Standard Format input
to produce an expected output file:
cs241.binasm < input.asm > expected.mipsYou can then examine this file through whatever means you prefer to see what the output is supposed to look like, or you could directly compare it to the output you produced using the
diff command.
Given the following input, in Standard Format:
jar $31Or in Prescanned Format:
ID jar REGISTER $31 NEWLINEYour program should print an error message containing
ERROR
in ALL CAPS to standard error. For example:
./asm < input.asm > output.mips ERROR: Invalid instruction jarThe error message does not need to be informative, but it is recommended you make them informative, as this will help with debugging. The error message also does not need to match the ones produced by
cs241.binasm. It just needs to contain ERROR
and be printed to standard error.
For error tests, it is okay to produce partial output on standard output
as long as you properly produce an error.
You may find the following steps useful if you are having trouble figuring out where to start.
It is recommended you submit to Marmoset after completing each stepping stone!
In this project, you can earn part marks for an incomplete program.
First, write code to process the scanned input line by line.
If using the Prescanned Format, create a struct or class for tokens that contains a kind and lexeme field.
Read the tokens from the input and separate the input into lines (sequences of tokens).
For example, maybe you could create a vector of vectors of tokens, where the inner vectors are individual
lines, and the outer vector represents the whole program.
When you encounter
a NEWLINE token, this indicates the end of the current line and the start of a new line.
If using the Standard Format together with your own scanner, you have a little more flexibility, but in
either case, set up your program so you can read the input line by line, and
examine the tokens on each line.
Now, implement support for the
For each line, first make sure the line is non-empty (it has at least one token on it). If the line is empty, just skip it.
Otherwise, check
if the first token is a DOTID token with lexeme
If the line is valid, convert the DECINT or HEXINT token into a numeric value and output the
32-bit binary encoding of this value. Repeat until all lines are processed.
Supporting labels requires structuring your assembler to perform two passes over the input,
so it's best to add support for labels early on. This way you won't have to make major structural
changes later on when your assembler is more complicated.
Split up your loop from Step 1 that processes the scanned lines into two loops.
Move the part of the code that checks for syntax errors to the first loop.
Move the part of the code that outputs the 32-bit binary encoding to the second loop.
Now add support for labels in
First implement support for the
The other three instructions all have the same syntax as
Think about how to structure your code to handle these instructions in a uniform
way (as opposed to just copying and pasting your
Remember to do error checking. Each of the instructions should have exactly three operands,
which should be REGISTER tokens,
and the operands should be separated by COMMA tokens.
Any deviation from this is a syntax error.
Once you've figured out how to implement one instruction in your assembler,
most of the remaining instructions will be easier.
However, the branch instructions are a little more difficult to
handle than something like
Implement support for the remaining instructions.
Most of the remaining instructions are similar to
The memory access instructions Step 1: Supporting
.word Directives.word directive, ignoring labels for now
(so only decimal and hexadecimal integers are allowed as the operand).
.word. If so, verify that the line
has exactly one other token on it, and that this token is a DECINT or HEXINT token. If these conditions are not met,
produce an error.
Step 2: Supporting Labels
.word directives.
.word syntax checking to allow ID tokens as operands.
Step 3: The
add, sub, slt, and sltu Instructionsadd instruction.
Check the instruction syntax in the first loop, and output the encoding in the second loop.
add: they take three register operands.
The only difference is that the function bits in the machine language encodings are different
for each instruction.
add code and changing it).
Step 4: The
beq and bne Instructionsadd:
A lot of things are different between add instruction only
allowed REGISTER operands, but in branch instructions,
the third operand can be a
DECINT, HEXINT, or ID (representing a label).
.word directives.
add
and requires using bit masking with bitwise AND.
add and branch instructions.
How much of your code can you reuse between instructions
with different syntax, encodings, and error checking requirements?
Think about the overall design of your assembler before you move on to the last step.
Step 5: The Rest of the Assembler
add,
but with different numbers and positions of the register operands.
Pay attention to the positioning when encoding.
lw and sw
are like beq and bne
in terms of encoding. However, the syntax is totally different.
You fortunately don't need to support label operands for
lw and sw, but you do need to
do range checking on the immediate operand.
A MIPS assembly language program is a text file containing a sequence of lines. Each line has the general format:
labels instruction commentEach of these components –
labels, instruction and comment – is optional; a particular line may have all three, any two, any one, or none at all. The components, if they appear, must appear in the given order, e.g., labels cannot come after the instruction on a line.
A line is called null if the instruction component is absent.
There is a one-to-one correspondence between non-null lines and 32-bit words in the machine language output of the assembler.
That is, each non-null line translates to a word, and nothing is output for null lines.
In the following, we describe what each component looks like after the line has been scanned into tokens. For quick reference, the specification of MIPS tokens is reproduced below.
If a scanning failure or other error occurs when performing
scanning using the simplified maximal munch algorithm and the token
specification below, the MIPS program is invalid.
Restrictions:
MIPS Assembly Token Specification
Kind
Description of Associated Lexemes
ID
Identifiers: Strings starting with an alphabetic character (
a-z or A-Z), followed by zero or more alphanumeric characters (a-z or A-Z or 0-9).
DOTID
Dot-Identifiers: An
. character followed by an ID lexeme.
LABELDEF
Label Definitions: An ID lexeme followed by a
: character.
DECINT
Decimal Integers: Strings which optionally start with a
- character, followed by one or more decimal digits (0-9).
HEXINT
Hexadecimal Integers: Strings starting with
0x, followed by one or more hexadecimal digits (0-9 or a-f or A-F).
REGISTER
Registers: A $ character followed by one or more decimal digits (0-9). The numeric value of the string of decimal digits cannot exceed 31.
COMMA
Comma: A , character.
LPAREN
Left Parenthesis: A ( character.
RPAREN
Right Parenthesis: A ) character.
NEWLINE
Either a line feed character (ASCII 0x0a), or the two-character sequence carriage return, line feed (ASCII 0x0d, ASCII 0x0a).
When printing this token, do not print the lexeme. Print a line containing only the kind.
?WHITESPACE
Whitespace: A sequence of one or more space (ASCII 0x20) or tab (ASCII 0x09) characters.
?COMMENT
Comment: A ; character, followed a sequence of zero or more ASCII characters that does not contain a line feed (ASCII 0x0a) or carriage return (ASCII 0x0d).
(In other words, once a ; character is encountered, everything up to the next line feed or carriage return is a ?COMMENT.)
$ character must not
exceed 31.
label: cs241: HelloWorld:Each LABELDEF token defines a label whose name is everything up to, but not included, the colon
: at the end of the lexeme.
The above example defines three labels called label, cs241, and HelloWorld.
All label names must be unique. The same name cannot be used twice.
; to be or not to beThese tokens are filtered out by the scanner, so after scanning is complete, no extra work is needed to deal with comments.
Each label has an associated address, which is 4 times the number of non-null lines preceding the line with the label definition.
add sub mult multu div divu mfhi mflo lis slt sltu jr jalr beq bne lw swIf it is a DOTID token, the lexeme must be
.word.
Following the first token is a sequence of tokens representing operands for the instruction/directive. Operands in general can be:
The expected sequence of operand and punctuation tokens for each instruction/directive is given below. An expression in square brackets like [DECINT or HEXINT] means a single token is expected, but it can be one of several kinds.
add $d, $s, $t sub $d, $s, $t slt $d, $s, $t sltu $d, $s, $tThe expected sequence of tokens after the ID is: REGISTER COMMA REGISTER COMMA REGISTER
mult $s, $t multu $s, $t div $s, $t divu $s, $tThe expected sequence of tokens after the ID is: REGISTER COMMA REGISTER
mfhi $d mflo $d lis $d jr $s jalr $sThe expected token kind after the ID is: REGISTER
Note that jr and jalr
are encoded differently from mfhi, mflo and lis:
for the jump instructions, the single register operand is $s,
while for the others, it's $d.
beq $s, $t, i bne $s, $t, iThe expected sequence of tokens after the ID is: REGISTER COMMA REGISTER COMMA [DECINT or HEXINT or ID]
If the immediate operand i is an ID, it
must be the name of a label defined elsewhere in the program.
The label is converted to a numeric value as follows: let
address be the address of the label, and let
PC denote the address of the line after the
branch instruction (that is, 4 times the number of non-null
lines up to and including the line with the branch instruction).
Then the numeric value is (address-PC)/4.
The immediate operand i must be encoded as a 16-bit two's complement
integer, so it has the following restrictions:
lw $t, i($s) sw $t, i($s)The expected sequence of tokens after the ID is: REGISTER COMMA [DECINT or HEXINT] LPAREN REGISTER RPAREN
The immediate operand i must be encoded as a 16-bit two's complement
integer, so it has the following restrictions:
.word iThe expected token kind after the DOTID is: [DECINT or HEXINT or ID]
If the immediate operand i is an ID, it
must be the name of a label defined elsewhere in the program.
The numeric value corresponding to the label is simply the address of the label.
Each line containing an instruction should be encoded as a 32-bit word, using the machine language encodings on the MIPS Reference Sheet.
Each line containing a .word directive is encoded a 32-bit word as follows,
depending on the kind of operand.