Outline

  • Due date: 27 May 2022, 11:59 pm (Friday Evening, Week 12)
  • Mark weighting: 30% of the grade
  • Submission: Submit your assignment through GitLab (full instructions below)
  • Policies: For late policies, plagiarism policies, etc, see the policies page

You should at least complete the first two C labs (Lab 8 and Lab 9) before starting this assignment. Labs 10 and 11 will help with the last two assignment tasks.

Background

Building a new CPU in real life is a massive investment of time and money. Typically, companies such as Intel, AMD, and Apple, first develop emulators and simulators of the CPU they wish to build. An emulator faithfully emulates the instruction set architecture and the behavior of the CPU. For example, after executing an instruction, an emulator reports the side effects, such as the register file’s state. On the other hand, a simulator builds a simulation model of the CPU. It reports the execution time (or the total number of cycles) a program (sequence of instructions) takes to finish.

You have already built a simulation model of the QuAC CPU in Digital. Unfortunately, building a CPU in a logic simulator, such as Digital, is hugely time-consuming. It is much easier to write a C program that emulates and simulates the processor. The resulting emulator/simulator is also much more versatile, and it is easy to introduce new features.

Your task in this assignment is to write a C program that emulates the QuAC CPU. An emulator does not need to model all of the CPU’s internal components faithfully - it should only model the behavior of the CPU. More specifically, the emulator should keep track of the architectural state, i.e., the registers and memory, and their contents should match the actual CPU’s behavior instruction-wise.

Assignment Tasks

The emulator template we provide loads a binary file into emulated memory at startup. We have already included the code for loading the program in memory. We have also declared memory and registers for your convenience. The registers are all initialised to 0.

The next action the emulator takes depends on the user input. We ask you to implement the following features. Each feature carries a separate weight.

To get help about the commands you need to implement, you can type ? after starting the emulator code we provide as a template.

Instruction Execution (30%)

Your emulator should execute an instruction from the address pointed to by the program counter. As mentioned above, the program counter is initialised to 0. We have added an empty function called exec_inst. Your first task is to write the function to emulate the execution of an instruction by the QuAC CPU.

  • You will first need to read the next instruction from ram.
  • You then need to decode the instruction. Specifically, you will need to find out the relevant opcode, z bit, rd, ra, rb, and imm8 values (depending on the particular instruction).
  • Finally, using the decoded values, you will need to execute the instruction, which will generally involve changing the contents of the register file or memory.

Finish writing the function called exec_inst in emulator.c.

You should note in emulator.c that the exec_inst() function is called each time the user types s at the command prompt. The s tells the emulator to step to the next instruction. Now, compile and run the emulator to check the functionality of your emulator.

Register Viewer (15%)

When the user presses r at the command prompt (terminal), the emulator displays the contents of the registers. There is no prescribed format, but make sure it is obvious what value each register has.

Memory Viewer (15%)

When the user presses m, the emulator displays the contents of memory in the range of memory addresses as specified by the user. The code to accept the address range is already included in the emulator template we provide. Again, there is no prescribed format - carefully consider how you can structure your output to make finding and reading data an easy task.

Disassembly (15%)

The next task is to implement a function that translates and prints the current 16-bit QuAC instruction to a string representing the QuAC assembly instruction. This translation is essentially the reverse of the action the assembler performs. You should implement the function in the disasm() function we have already declared for your convenience. Note that you may structure this function as you see fit - you will almost certainly need to change the return and/or argument types.

Finish writing the function disasm in emulator.c. Integrate your function by disassembling instructions in step view (exec_inst) and the memory viewer.

Your disassembler should recognize movl r0, 0 as the pseudo-instruction nop and behave accordingly. All other pseudo-instructions are not mandatory to implement, however feel free to do so.

Breakpoints (15%)

We now ask you to implement the capability of having a breakpoint in the emulator. A breakpoint makes your emulator stop whenever the configured instruction address is encountered. Otherwise, the emulator keeps executing instructions without stopping. It only stops when the breakpoint is reached. You can add a breakpoint when the user types b. The code for receiving the breakpoint information is already included in the template. When the user types c, the emulator continues to execute (emulate) instructions until the breakpoint is reached.

Add breakpoint support. Running the b command toggles a breakpoint at an address (adds if not already there, removes if already there). Running the c command continues emulation until a breakpoint is reached.

Execution Trace (10%)

The last task we ask you to implement is to save a copy of the CPU’s architectural state with each instruction executed. To accomplish this task you will need to save a copy of the CPU’s registers and the instruction being executed at each step. This task does not include tracking the contents of memory. Your trace should be able to handle the execution of an arbitrarily large number of instructions - you will need to use some kind of dynamically allocated data structure to solve this part of the assignment.

When the user types t, the emulator should display (print) the execution trace on the screen.

Deliverables

We provide the file emulator.c as a template, which contains the variable and function declarations mentioned above. The code we provide also includes the ability to receive input from the user for each of the tasks we ask you to implement. You should implement the full emulator in this file. We also provide a simple test program, test.bin. The test.bin program is the same as the one we used in Exercise 3 in Lab 5.

Marking Criteria

We have already provided the breakdown of marks for each feature/task we ask you to implement. We will take the following criteria into account during the marking of the assignment:

  • Correctness of functionality
  • Modularity
    • proper use of functions and function headers when needed
    • avoid needless code duplication
  • Code Clarity
    • write readable code with proper indentation
    • avoid unnecessary complexity
  • Documentation and comment
    • do not provide a comment for every line you write
    • 1–2 lines per function and per loop or if/else statement about what is its purpose

Testing and Validation

We provide two test cases for you to validate the implementation of your emulator. The first test in the repository is test.bin. When you run your newly-built emulator with this test program, the assembly instruction should match the assembly code from Exercise 3 of Lab 5. The assembly of the test programs is provided in the FAQ below.

A CI job is also run every time you push to check that your statement of originality is signed, and that your code compiles. Check the results instead of assuming you did not make any errors.

Printing and Formatting

For the register viewer, memory viewer, and execution trace, we expect to see well-formatted output. Example: the register viewer should have register identifiers which provide a clear association between printed values and the register to which they belong. It should be similarly easy to determine the memory address of a memory cell printed by the memory viewer.

Working on Your Assignment

Please follow the process below for working on your assignment submission.

  1. One group member forks the assignment 2 template repository
  2. On the forked project, go to ‘Members’ in the left side panel
  3. Add the other group member
    1. Search for their UID
    2. Change role to ‘Maintainer’
    3. Click ‘Invite’
    4. Check they appear as a maintainer in the members list at the bottom. You will also see a marker bot account down here; leave this as it is. GitLab members
  4. Both members can now clone the forked project to their computer. I.e., the one with the forking group member’s UID in the URL. Do not clone the template repository.
  5. Regularly commit and push your changes to the GitLab server
  6. The last commit that you push to GitLab before the submission deadline will count as your submission. Please push your work regularly.

Submission Checklist

  1. Our emulator in emulator.c implements at least the ability to execute instructions.
  2. Our statement.txt file includes all the necessary references/acknowledgements, and everything not mentioned in the statement is our groups original work.
  3. We have pushed all our work to GitLab, and we can see all of our work on GitLab.
  4. We have one or two assembly programs that can be run on our emulator.
  5. The CI is passing

FAQ

We welcome questions on Piazza. However, we will add answers to frequently asked questions here.

How do I run the emulator?

After compiling the emulator with make, you can run it with the command ./emulator. Note that you will also need to provide an argument - the program you want to load. The test programs are in the programs folder, so usually you will run something like ./emulator programs/test.bin.

What is the contents of the test programs?

test.bin This program is identical to the exercise 3 program from lab 5.

0. movl r1, 0x9    ; 0x0109
1. movl r2, 1
2. ldr r3, [r1]
3. add r3, r3, r2
4. str r3, [r1]
5. .word 0
6. .word 0
7. .word 0
8. .word 0
9. .word 0xFF

recursiontest.bin This program recursively sums an array of numbers, and returns the result in r2. Set a breakpoint on the nop instruction under the rest_of_prog label to run the entire program.

; Stack is dec before store
;          inc after load

mov r4, rz ; init sp
movl r1, array ; get pointer to length of array
ldr r2, [r1] ; load length of array into r2
movl r3, 1
add r1, r3 ; inc r1 to point to first elem

sub r4, r3 ; dec sp
movl r3, rest_of_prog ; return address into r3
str r3, [r4] ; push return address

; mem adr of element in r1
; count in r2
; scratch r3
; return sum in r2
func:
    cmp r2, rz ; base case test
    ldrz pc, [r4] ; if base case pop return into pc

    ; organise stack frame
    movl r3, 1
    sub r4, r3
    str r1, [r4] ; push mem adr to stack
    
    ; load the element at the mem adr
    ldr r1, [r1]

    ; push to stack
    sub r4, r3
    str r1, [r4]

    ; dec count
    sub r2, r3

    ; set up func call
    add r1, r4, r3 ; loc in stack where mem adress is stored
    ldr r1, [r1] ; load mem address back to r1
    add r1, r3 ; inc pointer into array

    sub r4, r3
    movl r3, func_ret
    str r3, [r4] ; store the return point on the stack

    ; call func
    jp func

    ; return from func (sum so far will be in r2)
func_ret:
    movl r3, 1
    add r4, r3 ; inc sp after function return

    ; pop elem from stack
    ldr r1, [r4] ; load elem from stack
    ; do sum
    add r2, r1 ; add elem to count so far
    ; ret
    movl r3, 2
    add r4, r3 ; increment stack pointer by 2 to remove elem and mem adr from stack
    ldr pc, [r4]


rest_of_prog:
    nop

array:
.word 8 ; length of array
.word 15
.word 4
.word 5
.word 6
.word 7
.word 8
.word 9
.word 10

Can I make my own test programs?

Absolutely! The process is the same as running a program on your CPU in digital. Open a lab or your assignment, and run the assembly code with the QuAC debugger. Digital doesn’t need to be running to do this - the part you need is the program.bin file that the debugger generates. Move this in to your emulator’s programs folder and start the emulator with your freshly assembled program.

Do I need to implement my assignment 1 extensions?

No. The emulator will only implement the standard QuAC instruction set, with no additional extensions or features.

Is there a report or written component?

No. The only written component to this assignment is your code documentation and commenting.

Updated:    07 Feb 2023 / Responsible Officer:    Director, School of Computing / Page Contact:    Shoaib Akram