A6: Assembly Programming

Instructions: Remember, all assignments in CS 3410 are individual. You must submit work that is 100% your own. Remember to ask for help from the CS 3410 staff in office hours or on Ed! If you discuss the assignment with anyone else, be careful not to share your actual work, and include an acknowledgment of the discussion in a collaboration.txt file along with your submission.

The assignment is due via Gradescope at 11:59pm on the due date indicated on the schedule.

Submission Requirements

You will submit the following files to Gradescope.

From Part I:

From Part II:

GenAI Survey:

Getting Started

There is no starter code for this assignment.

However, we still encourage you to use Git to keep track of your solution. An assignment repository has been created for you on GitHub:

$ git clone git@github.coecis.cornell.edu:cs3410-2025fa-student/<NETID>_asm.git

Replace <NETID> with your NetID. All the letters in your NetID should be in lowercase.

Overview

This assignment will level-up your skills as an assembly language programmer by reading and writing RISC-V assembly.

Part I: From C to RISC-V

In this first part, you’ll translate three C programs written to RISC-V assembly. Consider trying out your implementations using the online RISC-V simulator to check that it behaves like the original C.

Array Accesses

Imagine we have variables of these types:

int x;   // x10
int y;   // x11
int* A;  // x12
int* B;  // x13

Assume that the two pointer variables, A and B, point to large arrays of ints. The code you need to translate is:

x += (x + y) * 2 - A[4];
B[3] = x;

Assume:

  • x is stored in register x10
  • y is in x11
  • the base address of array A is in register x12
  • B is in x13

Use x5 and x6 (and no more) as the temporary registers. Write your assembly code in a file named arrays.s.

Multiplication

Let’s implement the integer multiplication instruction in RISC-V using other instructions! The instruction mul rd, rs1, rs2 multiplies rs1 and rs2 and stores the result in rd. Here is an implementation in C for 64-bit integers:

unsigned long intmul(unsigned long rs1, unsigned long rs2) {
  unsigned long rd = 0;
  for (int i = 0; i < 64; i++) {
    if (rs2 & 0x1) {
      rd += rs1;
    }
    rs1 <<= 1;
    rs2 >>= 1;
  }
  return rd;
}

Translate the above code to assembly. Do not use the mul instruction. Assume:

  • the variable rs1 is stored in register a0
  • rs2 is in register a1
  • the return value rd goes in t0

Use t0, t1, and t2 for any temporary values. Please name your submission file mult.s.

Primality Test

The following function prime gives a rudimentary algorithm for checking whether a number (p) is prime:

bool prime(int p) {
  if (p < 2) {
    return false;
  }

  for (int i = 2; i < p; i++) {
    int rem = p % i;
    if (rem == 0) {
      return false;
    }
  }
  return true;
}

Translate this function to RISC-V. Submit your file as prime.s.

Please label the entry block to your assembly with .prime.

Imagine that there are two labels .ret_tru and .ret_fls that already exist; translate the return true and return false lines into jumps to these labels.

Assume p is stored in a2 (a.k.a. x12).

To implement the % operation, you will need to use mul and div instructions. Please use t3t6 (a.k.a. x28x31) for temporary values, and try to minimize how many of these you use.

Part II: Mysterious RISC-V

Your friend, Sia, is a great C programmer. But she doesn’t understand RISC-V assembly, unfortunately. She is trying to understand some mysterious RISC-V programs so she comes to find you, a RISC-V assembly programmer, to help her translate those RISC-V programs to C so that she can understand what they do.

Mysterious Function 1

Here’s one assembly program Sia is trying to understand:

loop:
  loop:
  lw   x5, 0(x11)
  add  x5, x5, x15
  lw   x6, 0(x12)
  mul  x6, x6, x5
  sw   x6, 0(x13)
  addi x11, x11, 4
  addi x12, x12, 4
  addi x13, x13, 4
  addi x14, x14, -1
  bne  x14, x0, loop
  ret

Sia has already written a function signature:

void mystery1(int *arr1, int *arr2, int *arr3, int size, int num) {
  // ???
}

Assume that the function arguments are in registers x11 through x15, a.k.a. a1 through a5. Also assume that any array length given as an input is greater than zero. Complete this C function so it behaves the same way as the above assembly.

Follow these guidelines in your translation:

  • Prioritize readability. Comments are optional, but use them if you think it makes the code easier to understand.
  • Do not use goto. Use C’s if, for, while, etc. instead.
  • Prefer for loops over while loops. It is always possible to use while to implement any loop, but we want you to use for if the control flow fits the typical for (i = 0; i < max; i++) pattern.

It is possible to implement this function in only 2 lines of straightforward, readable C. Your solution does not need to be that short, but try to make it reasonably compact and understandable. (Sia will be grateful!)

Submit your completed implementation of the mystery1 function in mystery1.c.

Hint

Once you have a working C program, consider writing some tests for it. You can write a main function that calls the mystery1 function a few times on different inputs, for example, so you can compare the results to running the original RISC-V code. But please only submit the mystery1 function alone.

Mysterious Function 2

Sia asks you about a second mysterious assembly program:

addi x10, x0, 0

loop:
  lw x6, 0(x11)
  bne x6, x0, foo
  j bar

foo:
  sw x6, 0(x12)
  addi x12, x12, 4
  addi x10, x10, 1

bar:
  addi x11, x11, 4
  addi x13, x13, -1
  bne x13, x0, loop

ret

She already has this function signature:

int mystery2(int* arr1, int* arr2, int size) {
  // ...
}

The function arguments are again in registers a1 through a3 (a.k.a. x11 through x13). Register x10 is used to store the result of mystery2. Complete this function body. Use the same guidelines as in the previous part. You can also assume that any array length given as an input is greater than zero. It is possible to implement this code in about 6 lines of readable C but, again, your solution does not need to be that short.

Submit your solution in a file named mystery2.c.

GenAI Survey Screenshot Submission

To help you remember to submit the Gen AI survey along with the assignment, you will now submit a screenshot called genai_survey.png. All you need to do is submit the A6 AI Survey on Gradescope, take a screenshot (all it needs to show is that you submitted it), rename the file to genai_survey.png, and include the file along with your code. You do not need to do this until you plan on making your final submission. The autograder will work correctly without it. The failed test that appears if you do not submit the file is simply to act as a reminder. Submitting this screenshot is worth 1 point.

Rubric

We will test all submitted code by running it on several test cases to check that it behaves correctly, i.e., equivalent to the original code. We will also manually read to the assembly code to check that the required registers are used, and we’ll read the C to see that it obeys the guidelines.

  • genai_survey.png: 1 point
  • arrays.s: 16 points
  • mult.s: 16 points
  • prime.s: 16 points
  • mystery1.c: 16 points
  • mystery2.c: 16 points