Create C++ project called ArmToHack
with files:
ArmToHack.h
: contains class ArmToHack
which has all the Translator functionalitymain.cpp
: contains the code for creating and executing ArmToHack
Translator object.string
and map
Download the following files in the project (Right-Click, Save As). Read the comments inside token_io.h
to get familiar with the API:
token_io.h | token_io.cpp
Make sure to include in your code have the relevant includes:
#include <string>
#include <map>
#include "token_io.h"
programX.arm
, have been provided for testing the code.
test/
and save the examples theremain
execute .translate("test/programX.arm", "test/programX.asm")
test/programX.asm
the code to ensure that the translation was done correctlytest/programX.asm
in CPU Emulator and step through to check that in RAM the cells updated correctlyArmToHack
will have the following data members:
const methods and const& params
Apply
const and const& to methods and parameters, respectively, where appropriate.
|
file streams
File streams for the current input and output programs:
Both streams use the same include:
#include
|
line number
Current line number in Hack program.
|
lookup table 1
Hash Map for associating registers with their addresses in RAM:
Here is an example that shows how to create a map and check if it has a given key:
C++ Hash Map Example: https://en.cppreference.com/w/cpp/container/map/contains A map can be constructed directly in the data members section. |
Here is the list of required methods:
constructor()
Any initialization, if necessary (most can be done directly in the data members section). At this point there is nothing to be done with the two streams.
|
reset()
Clears the relevant data members in preparation for another translation. Still nothing to be done with the two streams.
|
write_line(line)
Writes a single complete line of Hack Assembly.
Important: There should not be any direct writes in the code. All writes to the output should be done by calling this method. (Why? Needs to also do something important that otherwise is easy to overlook.) Writing to a file stream in C++ is the same as writing to the screen: stream << ... << ...;
|
void translate(in-filename, out-filename)
For now mostly calls
translateFirstPass .
|
void translateFirstPass(in-filename, out-filename)
Opens the two streams with the given filenames and carries out the translation.
At this point there will be no branch instructions. Skip empty lines and make sure forget to close the streams. Here is how to process a file line at a time:
myInputStream.open( filename ); while ( myInputStream ) { // get a single line from the input // do something with the line } myInputStream.close(); |
void translate(line)
Simply dispatches to the relevant translator for the given line.
|
void translateXXX(line)
Translates the given line, which is an ARM Assembly instruction, to the sequence of Hack Assembly instructions.
At this point should support the following using only registers (
Note that |
testing
Here are simple test programs:
program1.arm | program2.arm | program3.arm |
Operand2
(see ARM Quick Reference).
You could consider adding a method write_oper2(token)
that writes the corresponding Hack Assembly code for one of the possible tokens:
Rz, #N, #+N, #-N
Recall that the handout code has strip
function that can remove a set of characters, and you can use [i]
to access a character in C++ string.
Here is a simple test program:
program4.arm
lookup table 2
Hash Map for associating ARM
BXX mnemonics with Hack JXX mnemonics.
|
lookup table 3
Hash Map for associating ARM labels with their addresses in Hack programs.
See below for more detailed explanation. |
translateJumps(line)
Translates all
BXX commands (ignore BL for now). Our conventions is to emit the value of D register and use that for the jump decision.
Since we are only handling back jumps the address of the jump is known at the point in the translation process.
|
translateFirstPass(...)
Modify this method so that when labels are encountered they are associated with the corresponding line in the Hack program.
Our convention is that labels are always written on separate lines (only comments allowed). Thus any line that has no second component is considered a label. (There is one exception to the rule above.) |
testing
|
LABEL
in the ARM program, it will have stored map3["LABEL"]=8
to indicate that LABEL
is associated with ADD
which happens to start on line 8 in the Hack program.
Both ARM programs given below should produce the same Hack program. Blank lines are simply ignored and have no effect.
The line numbers are given only for reference. They are not part of the input/output.
ARM Code ----------------- 0: MOV Rx, Ry 1: SUB Rx, Rz, Ry 2: LABEL 3: ADD Rx, Ry, Rz 4: CMP Rx, Ry 5: BGT LABEL Hack Code ---------------- 0: code for MOV 1: code for MOV 2: code for MOV 3: code for SUB 4: code for SUB 5: code for SUB 6: code for SUB 7: code for SUB 8: code for ADD 9: code for ADD 10: code for ADD 11: code for ADD 12: code for ADD 13: code for CMP 14: code for CMP 15: @8 (go back to ADD) 16: code for BGT ARM Code same as above but with blank lines should produce same Hack code ----------------- 0: MOV Rx, Ry 1: 2: SUB Rx, Rz, Ry 3: 4: LABEL 5: 6: ADD Rx, Ry, Rz 7: CMP Rx, Ry 8: 9: BGT LABEL
lookup table 4
: Hash Map for associating Hack lines with ARM labelsHere is the meaning of the new map:
After
ARM Code ----------------- 0: MOV Rx, Ry 1: SUB Rx, Rz, Ry 2: CMP Rx, Rz 3: BEQ LABEL 4: ADD Rx, Ry, Rz LABEL 5: RSB Hack Code --------------- 0: code for MOV 1: code for MOV 2: code for MOV 3: code for SUB 4: code for SUB 5: code for SUB 6: code for SUB 7: code for SUB 8: code for CMP 9: code for CMP 10: @-1 (unknown address) 11: code for JXX 12: code for ADD 13: code for ADD 14: code for ADD 15: code for ADD 16: code for ADD 17: code for RSB 18: code for RSB 19: code for RSB 20: code for RSB 21: code for RSB
translateFirstPass
the maps will have:
map3["LABEL"] = 17
since RSB
starts at line 17 in the Hack programmap4[10] = "LABEL"
since line 10 in the Hack program needs to know where to jump to, i.e. needs to know what LABEL
corresponds to in the Hack program
translateJumps(line)
Modified to load
A register with the not yet known address of the jump. For now:
|
translateSecondPass(in-filename, out-filename)
This method simply reads the input file one line at a time and simply prints each line to the output file.
The only exception are lines whose index appears in the map. The text for these lines will currently be The input file is assumed to be in Hack Assembly, i.e the partially created final output. |
void translate(in-filename, out-filename)
Modify this method to do the following:
translateFirstPass(in-filename, tmp-filename) translateSecondPass(tmp-filename, out-filename)
Here |
testing
Here is a simple test program. It is the same as
program5.arm but uses a while loop, so should see same values in RAM.
program6.arm |
void translateJumps(line)
Handles
BL which is a jump that also stores the address of the next instruction in LR .
Here is a simple test program. It is the same as
program7.arm |
void translateXXX(line)
Update any ARM instruction that has destination register to handle writing to
PC .
These instructions proceed as before but if the destination register is
Consider adding method
Here are sample test programs. The programs should jump back and forth and conclude with
program8.arm | program9.arm | edit for other cases |
When the program is done, should see R5=518
.
Upload a screenshot named 3n1.png of Hardware Simulator / CPU Emulator that shows the contents of the RAM
program10.arm
ArmToHack.h
, main.cpp
, 3n1.png
to the Moodle dropbox.