# Play a Song

• Due: 5 pm Friday 8 April 2022

• Summary: You will write MIPS assembly to control a speaker and play a song.

• Collaboration: You will work during lab in randomly assigned pairs. You must complete the work together in these groups, whether during or after the scheduled lab time.

• Submitting: After completing each exercise, show your completed work to the instructor or a course mentor. If you are unable to complete the lab during class time, schedule a time during office hours to demonstrate each of your solutions.

## Overview

In this lab, you will wire a speaker to the PIC32 and play a song. Along the way, you will write functions in assembly code, work with arrays stored in memory, and use instructions for multiplication and division.

### Background

As you might guess, multiplication and division are more complicated than arithmetic (addition and subtraction) and logic. Hence, they happen in a separate part of the chip called the MDU (multiplication and division unit), rather than the ALU. The multiplication and division instructions bring their own set of intricacies and complications that we must understand to use them correctly.

Let us recall that with addition, the only bit of “extra space” (beyond the storage capacity of each register) that we might have to worry about is the carry out—a single bit. However, if you recall your grade-school multiplication algorithm, for every digit (or bit, in our case) in the multiplier, we must shift the product with that digit further and further to the left. Thus, a product could take twice as many bits as the multiplicand and multiplier. To handle this situation, the MIPS ISA stores the resulting product in two special, reserved registers called `Hi` and `Lo` from which you must copy into a general purpose register using instructions `mflo` (for the lower bits) or `mfhi` (for the higher bits).

Fortunately, the PIC32 architecture can do this multiplication fairly quickly—though not always in a single “cycle,” the architecture handles the messy details for you, so that you never have to worry about any delay, the way you do with jumps and branches. If you want to read more about efficient multiplication, consult section 3.3 of Patterson and Hennessy.

Unfortunately, division is not quite so clean, both from an algorithmic standpoint and an implementation standpoint. Division takes much longer, but (fortunately) these details are again hidden from you. Much like multiplication, the quotient can be copied from `Lo` using instruction `mflo`, and the remainder can be copied from `Hi` using instruction `mfhi`. Unfortunately, we have detected a side-effect in the division instructions that seems to put the quotient into the dividend register, as the following code snippet demonstrates.

``````li     \$t0, 60     # t0 = 60
li     \$t1, 18     # t1 = 18
divu   \$t0, \$t1    # Calculate t0/t1
mflo   \$t2         # Put the quotient (3) into t2 for later use
mfhi   \$t3         # Put the remainder (6) into t3 for later use``````

Contrary to our expectations about the MIPS ISA and the description on the Green Card, at the end of this code sequence we find `\$t0` also contains 3 with our Microstick PIC32s. For now, this is simply a fact you will have to be aware of and program around.

There is an additional caveat: The two instructions following an `mflo` or `mfhi` instruction must not modify the HI register. That precludes multiplication and division instructions from immediately following either of these moves.

## Part A: Debugging an arithmetic function

### Background

Recall that our `delayloop` function from the previous lab takes as its parameter a number of loop iterations to execute. As a first step to playing musical notes, we’d like to be able to convert units of time into delay loop iterations. For example, consider the following function in C:

``````#define CLOCK_RATE_HZ       4000000 // Cycles per second for this CPU
#define CPI                 1       // Cycles per instruction
#define IC                  3       // Instructions per delay loop
#define MICROSEC_PER_SEC    1000000 // Microseconds per second
int
time_to_iters (int microsec) {
return (microsec * CLOCK_RATE_HZ) / (MICROSEC_PER_SEC * IC * CPI);
}``````

(Note that we use C-style comments in the `#define` lines because these are consumed by the preprocessor, which is operating in the C programming language.)

I translated this C code into assembly. To test my implementation, I wrote a simple program that calls `time_to_iters` and `delayloop` to blink the onboard LED at a rate of 1 Hz. To test my program, I simultaneously pushed the PIC32 reset button and started a 60 second timer. I verified that the blinking stopped after the 60 second timer was complete.

Here is my test program:

``````# timetest.S
# Test harness for the time_to_iters function
# Written by Janet Davis, 23 October 2013
# Last revised by Janet Davis, 24 October 2013

#define OFF                 0x0
#define ON                  0x1
#define CLOCK_RATE_HZ       4000000 // Cycles per second for this CPU
#define CPI                 1       // Cycles per instruction
#define IC                  3       // Instructions per delay loop
#define MICROSEC_PER_SEC    1000000 // Microseconds per second

.set noreorder          # Avoid reordering instructions
.text                   # Start generating instructions
.globl main             # The label should be globally known
.ent main               # The label marks an entry point

# Compute number of delay loops in a given period of time
# (measured in microseconds)
time_to_iters:
# \$t0 = time * CLOCK_RATE_HZ
li       \$t2, CLOCK_RATE_HZ  # Load CLOCK_RATE_HZ constant
multu    \$a0, \$t2            # Multiply argument with CLOCK_RATE_HZ
mflo     \$t0                 # Copy result from lo to \$t0

# \$t1 = MICROSEC_PER_SEC * IC * CPI
li       \$t2, MICROSEC_PER_SEC # Load MICROSEC_PER_SEC constant
li       \$t3, IC             # Load IC constant
# NB: At least two instructions occur between the mflo above and multu below
multu    \$t2, \$t3            # Multiply IC * MICROSEC_PER_SEC
mflo     \$t1                 # Copy result from lo to \$t1
li       \$t4, CPI            # Load CPI constant
nop                          # Ensure delay between mflo and multu (2 instr)
multu    \$t1, \$t4            # Multiply previous result by CPI
mflo     \$t1                 # Copy result from lo to \$t1

# result = (time * CLOCK_RATE_HZ) / (MICROSEC_PER_SEC * IC * CPI)
nop                          # Ensure delay betweeen mflo and divu (2 instr)
nop                          #  [continue delay]
divu     \$t0, \$t1            # Divide \$t0 by \$t1
mflo     \$v0                 # Copy quotient from lo to return val reg

nop                          # Delay slot

# Delay for the given number of loop iterations (3 cycles/iteration)
# Precondition: \$a0 > 0
delayloop:
bnez    \$a0, delayloop
nop
jr      \$ra
nop

# Main program.
# Test time_to_iters computation.
# The light should blink at a rate of 1 Hz.
main:
# Set up port A for output
li      \$t0, 0x0000         # Output on all pins
sw      \$t0, 0(\$s0)         # Write to TRISA

# Compute number of delayloop iterations for blinking at 1Hz
li      \$a0, 500000         # Half a second, in microsec
jal     time_to_iters       # Call time_to_iters procedure.
nop                         # Delay slot
add     \$s1, \$v0, \$zero     # Store result in \$s1

# Blink on and off 60 times; should take 60 seconds to finish blinking.
li      \$s2, 60             # Set countdown to 60.
loop:
li      \$t0, ON             # Turn LED on.
sw      \$t0, 0(\$s0)
move    \$a0, \$s1            # Call delayloop on the computed value.
jal     delayloop
nop                         # Delay slot
li      \$t0, OFF            # Turn LED off.
sw      \$t0, 0(\$s0)
move    \$a0, \$s1            # Call delayloop on the computed value.
jal     delayloop
nop                         # Delay slot
addi    \$s2, \$s2, -1        # Decrement countdown
bnez    \$s2, loop           # Loop while countdown > 0
nop                         # Delay slot
forever:
j forever                   # Infinite loop that does nothing.
nop
.end main                       # Marks the end of the program``````

Unfortunately, I made a significant error when writing the `time_to_iters` function; the blinking doesn’t even start. It’s your job to identify and fix my bug. (Note: It does not have to do with violating the `mflo` delay; however, any purported fix must be sure to respect this condition.)

Note: You may NOT change any of the pre-`#define`d constants.

1. What return value should the call `time_to_iters` produce in the given program?
2. Create a new project (remember if MPLAB complains about an invalid folder name, you need to restart your machine).
3. Copy and paste the code above into a new assembly file, `timetest.S`
4. Run the program and verify that it does not work.
5. Using whatever means you deem appropriate, identify the bug in this program.
6. Revise the `time_to_iters` function so it works correctly.

When you have completed this task, explain your fix and demonstrate the program to the mentor or instructor.

## Part B: Playing a song

Next, you will use your debugged `time_to_iters` function to play a song. The overall algorithm is as follows:

1. Read a note and duration from memory
2. Call a function to play that note for the specified duration
3. Move on to the next note and repeat until there are no more notes to play

### Hardware setup

Get a couple of long wires, one red and one black. Connect the red wire to `RA0` (pin 2) and the black wire to `VSS` (pin 8). Connect the other end of one wire to the top row of holes in the protoboard’s speaker connection, and the other wire to the bottom row. It does not matter which wire goes in which spot.

If you run the `timetest.S` program now, you will hear a series of quiet, dull clicks. This is because 1Hz is much too slow an oscillation for the human mind to perceive as a tone. If you change the time passed in to `time_to_iters` so it is much shorter, you will hear a tone.

### Assembly code

Use this assembly program as a starting point for your work:

``````# song.S
# Plays a song
# Written by Janet Davis, 23 October 2013
# Last revised by YOUR NAME(S), THE DATE

#define OFF                 0x0
#define ON                  0x1
#define CLOCK_RATE_HZ       4000000 // Cycles per sec for this CPU
#define CPI                 1       // Cycles per instruction
#define IC                  3       // Instructions per loop
#define MICROSEC_PER_SEC    1000000 // Microseconds per second

# C-major scale
# Reference: http://en.wikipedia.org/wiki/Piano_key_frequencies
#define REST        0       // Use 0 to represent rests (no sound played)
#define C           3822    // Period in microseconds for middle C
#define CSHRP       3608    // Period in microseconds for middle C#
#define D           3405    // Period in microseconds for D above middle C
#define DSHRP       3214    // Period in microseconds for D# above middle C
#define E           3034    // Period in microseconds for E above middle C
#define F           2863    // Period in microseconds for F above middle C
#define FSHRP       2703    // Period in microseconds for F# above middle C
#define G           2551    // Period in microseconds for G above middle C
#define GSHRP       2408    // Period in microseconds for G# above middle C
#define A           2273    // Period in microseconds for A above middle C
#define Bee         2025    // Period in microseconds for B above middle C
#define CC          1911    // Period in microseconds for C above middle C
// (Note this is half of middle C!)
# Durations
#define WHOLE       2000000  // Whole note (microseconds)
#define DTHALF      1500000  // Dotted half note
#define HALF        1000000  // Half note
#define DTQUART      750000  // Dotted quarter note
#define QUARTER      500000  // Quartner note
#define EIGHTH       250000  // Eighth note
#define SIXTEENTH    125000  // Sixteenth note
#define BREATH        10000  // A breath between phrases

# The song (Rodgers & Hammerstein, 1959)
# notes and durations are arrays stored in memory.
# songlength, also stored in memory, gives the size of the arrays.
# Reference for data segment format:
# http://web.archive.org/web/20091112173310/http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/dataseg.html
.data
songlength:
.word 65

notes:
.word C,       REST,    D,       E,       REST, \
C,       E,       C,       E,       REST, \
D,       E,       F,       F,       E,       D,       F,       REST, \
E,       F,       G,       E,       G,       E,       G,       REST, \
F,       G,       A,       A,       G,       F,       A,       REST, \
G,       C,       D,       E,       F,       G,       A,       REST, \
A,       D,       E,       FSHRP,   G,       A,       Bee,     REST, \
Bee,     E,       FSHRP,   GSHRP,   A,       Bee,     CC,      REST, \
F,       F,       A,       F,       Bee,     G,       CC

durations:
.word QUARTER, EIGHTH,  EIGHTH,  QUARTER, EIGHTH, \
EIGHTH,  QUARTER, QUARTER, HALF,    BREATH, \
DTQUART, EIGHTH,  EIGHTH,  EIGHTH,  EIGHTH,  EIGHTH,  WHOLE,   BREATH, \
DTQUART, EIGHTH,  DTQUART, EIGHTH,  QUARTER, QUARTER, HALF,    BREATH, \
DTQUART, EIGHTH,  EIGHTH,  EIGHTH,  EIGHTH,  EIGHTH,  WHOLE,   BREATH, \
DTQUART, EIGHTH,  EIGHTH,  EIGHTH,  EIGHTH,  EIGHTH,  WHOLE,   BREATH, \
DTQUART, EIGHTH,  EIGHTH,  EIGHTH,  EIGHTH,  EIGHTH,  WHOLE,   BREATH, \
DTQUART, EIGHTH,  EIGHTH,  EIGHTH,  EIGHTH,  EIGHTH,  DTHALF,  BREATH, \
EIGHTH,  EIGHTH,  QUARTER, QUARTER, QUARTER, QUARTER, DTHALF

# The program starts here
.set noreorder          # Avoid reordering instructions
.text                   # Start generating instructions
.globl main             # The label should be globally known
.ent main               # The label marks an entry point

# Play the specified note for the specified length of time
# \$a0: period in microseconds for the note (0 specifies a rest)
# \$a1: duration in microseconds to play the note
playnote:

# Compute number of delay loops in a given period of time
# (measured in microseconds)
time_to_iters:

# Delay for the given number of loop iterations (3 cycles/iteration)
# Precondition: \$a0 > 0
delayloop:
bnez    \$a0, delayloop
nop
jr      \$ra
nop

# Main program
# Plays the first few notes of the song stored in memory.
# TODO: PLAY THE ENTIRE SONG
main:
# Set port A for output.
li      \$t0, 0x0000         # Output on all pins
sw      \$t0, 0(\$s0)         # Write to TRISA

# Pause for a moment so that the song does not begin playing while
# MPLAB is programming the microprocessor.
li      \$a0, REST
li      \$a1, WHOLE
jal     playnote
nop

la      \$s5, songlength
la      \$s6, notes
la      \$s7, durations

# Play the first five notes/rests of the song.
# TODO: CHANGE THIS TO A LOOP
lw      \$a0, 0(\$s6)         # Play the first note or rest.
lw      \$a1, 0(\$s7)
jal     playnote
nop                         # Delay slot
lw      \$a0, 4(\$s6)         # Play the second note or rest.
lw      \$a1, 4(\$s7)
jal     playnote
nop                         # Delay slot
lw      \$a0, 8(\$s6)         # Play the third note or rest.
lw      \$a1, 8(\$s7)
jal     playnote
nop                         # Delay slot
lw      \$a0, 12(\$s6)        # Play the fourth note or rest.
lw      \$a1, 12(\$s7)
jal     playnote
nop                         # Delay slot
lw      \$a0, 16(\$s6)        # Play the fifth note or rest.
lw      \$a1, 16(\$s7)
jal     playnote
nop                         # Delay slot
forever:
j forever                   # Infinite loop that does nothing.
nop                         # Delay slot
.end main                       # Marks the end of the program``````

### Step 1: Setup

• Create a new project.
• Copy and paste the code above into a new assembly file `song.S`.
• Fill in the `time_to_iters` function with your corrected implementation.

### Step 2: Playing a note

Implement the `playnote` function. To help you get started, here is a C function that does the same thing:

``````/*
* Play a musical note for a specified duration.
* period:
*    The period of the note (1/frequency), in microseconds.
*    0 indicates a rest (silence).
* duration:
*    The duration to play the note, in microseconds.
* Preconditions:
*    period >= 0, duration >= 0
*    Port A, pin 0 is configured for output, and is connected to a speaker.
*    Port A, pin 0 is off (0).
* Postconditions:
*    The note (or rest) has been played.
*    Port A, pin 0 is off (0).
*/
void
playnote (int period, int duration)
{
int * p_lata = LATA;
int delay, count;
if (period == 0) {
delay = time_to_iters (duration);
delayloop (delay);
} else {
count = duration/period;
delay = time_to_iters(period/2);
while (count > 0) {
*p_lata = ON;
delayloop (delay);
*p_lata = OFF;
delayloop (delay);
count--;
} // while
} // if
} // playnote``````

Test your program. You should hear the first few notes of a song.

### Step 3: Looping over notes

Revise the body of the main program so it will play the entire song instead of just the first few notes. It may be helpful to write the loop in C before translating it to assembly.

### Step 4: A new song (optional)

If you have time, revise the data segment to play a different song.

## Before You Finish

So that others are not startled or confused when they connect your MIPS processor, please re-program your chip with a different program that does not do anything useful. While one solution would be to program your chip with a project from an earlier lab, the easiest thing to do might be to change the first few lines of the `main` procedure to be an infinite loop, like follows:

``````main:
j main    # Infinitely do nothing
nop``````

After you make the change to your source code, remember to build the project and program it to your chip.

Copyright © 2018, 2019, 2020, 2022 Marge Coahran, Charlie Curtsinger, Janet Davis, and Jerod Weinman