syscall in the Playground

Until now, a program's result has stayed in a register or in memory. To show text in the console, read what someone types or end a run, a program asks the Playground to perform a service. The instruction that makes the request is syscall.

Choose a service, then call it

In this Playground, we write syscall with no operands. Each request follows the same pattern:

  1. put the service number in $v0;
  2. put the service's inputs in the registers listed for that service;
  3. execute syscall.

These five services are enough for the examples and exercises on this page:

serviceactioninputresult
1print a signed integervalue in $a0—
4print a zero-terminated stringaddress in $a0—
5read an integer—$v0
10end the program——
11print one characterlow byte of $a0—

The registers are part of the Playground's contract for each service. Other services use different registers. Floating-point services, for example, use $f12 for an input and $f0 for a result.

In the MIPS architecture, syscall deliberately raises a synchronous exception: an event caused by the instruction being executed. The Playground handles that event by performing the selected service and then lets the program continue. The later Exceptions and Coprocessor 0 lecture explains the exception mechanism; the service convention is all you need here.

Printing and exiting

.data
message: .asciiz "Hello, world!\n"

.text
.globl main
main:
    li $v0, 4               # service 4: print a zero-terminated string
    la $a0, message
    syscall

    li $v0, 1               # service 1: print a signed integer
    li $a0, 42
    syscall

    li $v0, 11              # service 11: print one character
    li $a0, '\n'
    syscall

    li $v0, 10              # service 10: end the program
    syscall

The console shows:

Hello, world!
42

Service 4 starts at the address in $a0 and prints bytes until it reaches a zero byte. That is why the example uses .asciiz: the final z means the assembler adds the zero terminator. Service 1 interprets the 32 bits in $a0 as a signed integer, so the same service also prints negative numbers correctly.

Printing does not add a line break. The \n in the string prints the first newline, and service 11 prints the second one from the character literal '\n'. Every example on this page finishes by selecting service 10 and executing syscall.

Reading an integer

Service 5 waits for a decimal integer and returns it in $v0:

.data
prompt: .asciiz "Give me a number: "
answer: .asciiz "\nTwice that is "

.text
.globl main
main:
    li $v0, 4
    la $a0, prompt
    syscall

    li $v0, 5               # $v0 contains 5 before the call
    syscall                 # the result replaces that 5 in $v0
    move $t0, $v0           # preserve the result before choosing another service

    li $v0, 4
    la $a0, answer
    syscall

    add $a0, $t0, $t0
    li $v0, 1
    syscall

    li $v0, 10
    syscall

Press Run, type a number in the console and press Enter. The program pauses at service 5 until the input arrives, then continues from the instruction after syscall.

Notice what happened to $v0: it held the service number 5 before syscall, and the read service replaced it with the integer that was entered. Any service result replaces the previous value in its result register. Playground services preserve all other general-purpose and floating-point registers; a service with a documented memory result can still change that memory. Move a register result somewhere safe before loading another service number into $v0.

Making sound

Services 31 and 33 play a musical note. Both take the same four inputs:

  • $a0 chooses the pitch, or how high or low the note sounds. Use a number from 0 to 127. The example starts at 60, a note called middle C. Adding 1 moves to the next higher note; subtracting 1 moves to the next lower note. Each of these small steps is called a semitone.
  • $a1 gives the duration in milliseconds. For example, 300 lasts three tenths of a second, and 1000 lasts one second.
  • $a2 chooses the instrument, using a number from 0 to 127 in the General MIDI instrument list. The example uses 0 for an acoustic grand piano; try changing this number to hear other instrument sounds.
  • $a3 sets the volume, from 0 to 127. Larger values are louder; the example uses 100.

Service 33 waits for the note to end before running the next instruction. Service 31 continues without waiting. If you use service 31 again while a previous note is still sounding, the notes overlap.

.text
.globl main
main:
    li $a1, 300             # each note lasts 300 milliseconds
    li $a2, 0               # instrument 0, the acoustic grand piano
    li $a3, 100             # volume 100

    li $v0, 33              # service 33: play a note, then wait for it to end
    li $a0, 60              # middle C
    syscall
    li $a0, 64              # E
    syscall
    li $a0, 67              # G
    syscall

    li $v0, 31              # service 31: play a note and go on at once
    li $a1, 1000
    li $a0, 60              # C, E and G again, together: a chord
    syscall
    li $a0, 64
    syscall
    li $a0, 67
    syscall

    li $v0, 10
    syscall

The first three calls use service 33 to play the notes C, E and G one after another. The next three calls use service 31 to start those same notes within a few instructions of each other. They sound together as a chord and finish their one-second duration even though the program has already ended.

Neither sound service changes a register, so $v0 and the other arguments keep their values between calls. That is why the example sets the instrument and volume only once. If a pitch, instrument or volume is outside 0 to 127, the service uses middle C, the piano or volume 100, respectively.

Once a program has made a sound, a Sound button in the execution dock turns sound off and on. Testcases run silently, and service 33 finishes immediately without a real delay. The testcase still counts the note's duration as elapsed program time.

Waveforms on the Synth

Services 31 and 33 play recorded instrument sounds. The editor also has a Synth: a sound chip like the ones inside 1980s game consoles and home computers, which the program controls directly.

The Synth's controls are device registers: each has a memory address, and storing a value there changes a sound setting. The example puts the base address 0xffff0100 in the CPU register $s0, then uses sh and sb with offsets from that address to reach the device registers.

The Synth has eight channels, each playing one sound at a time. The example uses channel 0 and these controls:

  • the halfword at offset 0 is the frequency in Hz (hertz), how many times a second the sound repeats. Middle C, named by pitch number 60 in the previous example, is about 262 Hz here.
  • the byte at 0x11 chooses the waveform, the shape that repeats: 0 is a pulse, 1 a triangle, 2 a sawtooth, 3 a sine and 4 noise.
  • the byte at 0x12 sets the overall volume, from 0 to 127. Larger values are louder.
  • the byte at 0x13 sets the sustain, the level held while the sound continues, from 0 to 127. Sustain 127 keeps the sound at the chosen volume; lower values reduce that level further.
  • the byte at 0x10 holds the gate: storing 1 starts a note, and storing 0 ends it.

The example sets volume 100 and sustain 127 so the tone holds at volume 100 until the gate goes off. To leave time to hear each pitch, it uses service 32: put 32 in $v0, the wait in milliseconds in $a0, then execute syscall. The stores control the sound, and the service supplies the pauses.

.text
.globl main
main:
    li $s0, 0xffff0100     # base address of Synth channel 0
    li $t0, 1
    sb $t0, 0x11($s0)     # waveform 1: a triangle
    li $t0, 100
    sb $t0, 0x12($s0)     # volume 100
    li $t0, 127
    sb $t0, 0x13($s0)     # sustain 127: hold at the chosen volume

    li $t0, 262            # middle C, in Hz
    sh $t0, 0($s0)
    li $t0, 1
    sb $t0, 0x10($s0)     # gate on: the note starts
    li $v0, 32             # service 32: wait
    li $a0, 400            # for 400 milliseconds
    syscall

    li $t0, 330            # E: change pitch while the gate stays on
    sh $t0, 0($s0)
    syscall                 # wait again: $v0 and $a0 still ask for it
    li $t0, 392            # G
    sh $t0, 0($s0)
    syscall

    sb $zero, 0x10($s0)   # gate off: the note ends
    li $v0, 10
    syscall

The gate stays on while the frequency changes from 262 to 330 to 392 Hz. You hear C, E and G as one continuing tone that changes pitch, with 400 milliseconds at each pitch. Change the waveform number at 0x11 to hear how the shape changes the sound.

The Synth is an extension of this editor; MARS does not provide these device controls. The Synth chapter of the Documentation has more controls and examples.

More Playground services

You do not need to memorize this reference. Use it to find the registers for a service before you write the request.

Console output

serviceactioninputformatting
1signed decimal integer$a0no padding
2float$f12
3double$f12 / $f13the pair holds one 64-bit value
4stringaddress in $a0stops at the zero byte
11characterlow byte of $a0
34hexadecimal integer$a00x followed by eight hexadecimal digits
35binary integer$a0exactly 32 binary digits
36unsigned decimal$a0ordinary decimal digits, with no padding

Services 1 and 36 can print the same bits differently. If $a0 contains -1, service 1 prints -1, while service 36 treats those bits as an unsigned value and prints 4294967295.

Console input

serviceactioninputresult
5read integer—$v0
6read float—$f0
7read double—$f0 / $f1
8read stringbuffer address in $a0, buffer size in $a1memory
12read character—$v0

For service 8, the size in $a1 includes the final zero byte. A buffer of size n therefore holds at most n - 1 input characters. When the entered line and its newline fit within that limit, the service stores the newline and then the zero terminator. If they do not fit, it stores as much of the line as fits, followed by the terminator. With size 1 it writes only the terminator; with a size less than 1 it writes nothing.

Program control, time and less common services

  • Service 10 ends the run. Service 17 also ends it and takes an exit code in $a0.
  • Service 30 returns elapsed Playground program time in milliseconds, with the low 32 bits in $a0 and the high 32 bits in $a1. Service 32 takes milliseconds in $a0 and waits before the next instruction. The Playground stays responsive, and the service avoids a busy-wait loop that would repeatedly spend the program's instruction budget. In a testcase, the clock starts at zero and service 32 advances it immediately without a real delay.
  • Services 31 and 33 play a note, as shown above.
  • Service 9 allocates bytes from the heap and returns their address in $v0. Under the Playground's default memory layout, that heap begins at 0x10040000; a different memory layout can place it elsewhere.
  • Services 13–16 open, read, write and close files in the Playground project's Files. They do not expose arbitrary files from the computer running the browser.
  • Services 41–44 take a generator ID in $a0. Service 41 replaces $a0 with a signed integer; service 42 replaces it with an integer in the range 0 <= value < $a1, so $a1 must be positive. Service 43 returns a float in $f0, and service 44 returns a double in $f0 / $f1. This Playground does not provide service 40 for setting a seed.
  • Services 50–59 provide dialog-style input and output through the Playground interface.

Other service numbers are unsupported and end the run with an error. The MIPS syscall documentation page lists the full register contract for each supported service.

Some devices use memory-mapped I/O instead: the program loads and stores at special memory addresses. That is a different interface from placing a service number in $v0 and executing syscall.

Two output services

Print exactly The answer is 42, with no newline or other text. Use two output syscalls—one for the string and one for the number—then use the exit syscall.

.data
message: .asciiz "The answer is "

.text
.globl main
main:
    # your code here
Show solution
.data
message: .asciiz "The answer is "

.text
.globl main
main:
    li $v0, 4
    la $a0, message
    syscall

    li $v0, 1
    li $a0, 42
    syscall

    li $v0, 10
    syscall

Read, square and print twice

Read two integers. Print the square of the first, one space, and the square of the second, with nothing else in the output. Assume both squares fit in a signed 32-bit value. The supplied inputs check a positive number and a negative number.

.text
.globl main
main:
    # your code here
Show solution
.text
.globl main
main:
    li $v0, 5
    syscall
    move $t0, $v0

    li $v0, 5
    syscall
    move $t1, $v0

    mul $a0, $t0, $t0
    li $v0, 1
    syscall

    li $v0, 11
    li $a0, ' '
    syscall

    mul $a0, $t1, $t1
    li $v0, 1
    syscall

    li $v0, 10
    syscall

Instructions in this lesson

lilasyscallmoveaddsbshmul