syscall in the Playground
The overview of this topic is in Assembly basics. The same topic in M68K, RISC-V, Z80, x86-64.
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:
- put the service number in
$v0; - put the service's inputs in the registers listed for that service;
- execute
syscall.
These five services are enough for the examples and exercises on this page:
| service | action | input | result |
|---|---|---|---|
| 1 | print a signed integer | value in $a0 | — |
| 4 | print a zero-terminated string | address in $a0 | — |
| 5 | read an integer | — | $v0 |
| 10 | end the program | — | — |
| 11 | print one character | low 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:
$a0chooses 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.$a1gives the duration in milliseconds. For example, 300 lasts three tenths of a second, and 1000 lasts one second.$a2chooses 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.$a3sets 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
0is 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
0x11chooses 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
0x12sets the overall volume, from 0 to 127. Larger values are louder. - the byte at
0x13sets 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
0x10holds 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
| service | action | input | formatting |
|---|---|---|---|
| 1 | signed decimal integer | $a0 | no padding |
| 2 | float | $f12 | |
| 3 | double | $f12 / $f13 | the pair holds one 64-bit value |
| 4 | string | address in $a0 | stops at the zero byte |
| 11 | character | low byte of $a0 | |
| 34 | hexadecimal integer | $a0 | 0x followed by eight hexadecimal digits |
| 35 | binary integer | $a0 | exactly 32 binary digits |
| 36 | unsigned decimal | $a0 | ordinary 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
| service | action | input | result |
|---|---|---|---|
| 5 | read integer | — | $v0 |
| 6 | read float | — | $f0 |
| 7 | read double | — | $f0 / $f1 |
| 8 | read string | buffer address in $a0, buffer size in $a1 | memory |
| 12 | read 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
$a0and the high 32 bits in$a1. Service 32 takes milliseconds in$a0and 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 at0x10040000; 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$a0with a signed integer; service 42 replaces it with an integer in the range0 <= value < $a1, so$a1must 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