diff --git a/Makefile b/Makefile
index 94f754b..b610fcd 100644
--- a/Makefile
+++ b/Makefile
@@ -20,11 +20,14 @@ hello.elf: hello.o
 calc.elf: mem.o hex.o debug.o math.o calc.o
 	$(LD) -m elf32lriscv -T link.ld $^ -o $@
 
-tests: tests/btohex.elf tests/tohex.elf tests/math_add64.elf
+tests: tests/btohex.elf tests/tohex.elf tests/math_add64.elf tests/math_mul.elf
 
 tests/math_add64.elf: hex.o math.o tests/math_add64.o
 	$(LD) -m elf32lriscv -T link.ld $^ -o $@
 
+tests/math_mul.elf: hex.o math.o tests/math_mul.o
+	$(LD) -m elf32lriscv -T link.ld $^ -o $@
+
 tests/btohex.elf: mem.o hex.o debug.o tests/btohex.o
 	$(LD) -m elf32lriscv -T link.ld $^ -o $@
 
diff --git a/math.s b/math.s
index 2389918..9e2b2d1 100644
--- a/math.s
+++ b/math.s
@@ -1,4 +1,5 @@
 .globl add64
+.globl mul
 
 .text
 
@@ -24,3 +25,28 @@ add64:
     add  a1, t0, t1  # upper 32 bits of answer (upper sum + carry bit)
     ret
 
+# 32-bit shift-add multiplication
+#    arguments:
+#        a0: multiplicand
+#        a1: multiplier
+#    return:
+#        a0 = a0 × a1
+#
+mul:
+    mv      t0, a1          # Save multiplier in t0
+    li      a1, 0           # Initialize product in a1
+
+multiply_loop:
+    beqz    t0, done        # If multiplier is 0, we're done
+    andi    t1, t0, 1       # Check least significant bit
+    beqz    t1, shift       # If LSB is 0, skip addition
+    add     a1, a1, a0      # Add multiplicand to product
+
+shift:
+    slli    a0, a0, 1       # Shift multiplicand left
+    srli    t0, t0, 1       # Shift multiplier right
+    j       multiply_loop   # Continue loop
+
+done:
+    mv      a0, a1          # Move product to return register
+    ret
diff --git a/tests/test_math.sh b/tests/test_math.sh
index 56020e4..9897d7c 100644
--- a/tests/test_math.sh
+++ b/tests/test_math.sh
@@ -2,8 +2,10 @@
 
 test_add64() {
   result=$("${QEMU}" -B 0x80000000 -s 2k tests/math_add64.elf)
-  # 3.5
-  # 3.75
+  #  3.5
+  #  3.75
+  #  1.25
+  # -1.25
   expected=$(cat << END
 00000003.80000000
 00000003.C0000000
@@ -14,3 +16,18 @@ END
   # different inputs.
   test $? -eq 0 && test "$result" = "$expected"
 }
+
+test_mul() {
+  result=$("${QEMU}" -B 0x80000000 -s 2k tests/math_mul.elf)
+  expected=$(cat << END
+0000000F
+0000003F
+0063FF9C
+FFFFFFEB
+END
+)
+
+  # TODO: Ideally this test would allow calling the binary repeatedly with
+  # different inputs.
+  test $? -eq 0 && test "$result" = "$expected"
+}