commit:a3cf8b5eb2f6f8f4384153d820702f0401ac2d28
author:Chip
committer:Chip
date:Fri Feb 22 23:35:55 2019 -0600
parents:
Initial commit

Serial, Lua, video output. What more do you need?
diff --git a/.cargo/config b/.cargo/config
line changes: +33/-0
index 0000000..fa909f5
--- /dev/null
+++ b/.cargo/config
@@ -0,0 +1,33 @@
+[target.thumbv7m-none-eabi]
+# uncomment this to make `cargo run` execute programs on QEMU
+# runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"
+
+[target.'cfg(all(target_arch = "arm", target_os = "none"))']
+# uncomment ONE of these three option to make `cargo run` start a GDB session
+# which option to pick depends on your system
+# runner = "arm-none-eabi-gdb -q -x openocd.gdb"
+# runner = "gdb-multiarch -q -x openocd.gdb"
+# runner = "gdb -q -x openocd.gdb"
+
+rustflags = [
+  # LLD (shipped with the Rust toolchain) is used as the default linker
+  "-C", "link-arg=-Tlink.x",
+
+  # if you run into problems with LLD switch to the GNU linker by commenting out
+  # this line
+  # "-C", "linker=arm-none-eabi-ld",
+
+  # if you need to link to pre-compiled C libraries provided by a C toolchain
+  # use GCC as the linker by commenting out both lines above and then
+  # uncommenting the three lines below
+  # "-C", "linker=arm-none-eabi-gcc",
+  # "-C", "link-arg=-Wl,-Tlink.x",
+  # "-C", "link-arg=-nostartfiles",
+]
+
+[build]
+# Pick ONE of these compilation targets
+# target = "thumbv6m-none-eabi"    # Cortex-M0 and Cortex-M0+
+target = "thumbv7m-none-eabi"    # Cortex-M3
+# target = "thumbv7em-none-eabi"   # Cortex-M4 and Cortex-M7 (no FPU)
+# target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)

diff --git a/.gitignore b/.gitignore
line changes: +7/-0
index 0000000..a299401
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+**/*.rs.bk
+.#*
+.gdb_history
+.cargo-ok
+Cargo.lock
+target/
+libs/

diff --git a/Cargo.toml b/Cargo.toml
line changes: +40/-0
index 0000000..870df26
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,40 @@
+[package]
+authors = ["Chip <bytex64@bytex64.net>"]
+edition = "2018"
+readme = "README.md"
+name = "hello-m"
+version = "0.1.0"
+
+[dependencies]
+embedded-hal = "0.2.2"
+cortex-m = { version = "0.5.8", features = [ "const-fn" ] }
+cortex-m-rt = "0.6.6"
+atsam3xa = "0.1.0"
+panic-halt = "0.2.0"
+cty = "0.1.5"
+nb = "0.1.1"
+arraydeque = { version = "0.4.3", default-features = false }
+alloc-cortex-m = "0.3.5"
+r0 = "0.2.2"
+
+[dependencies.atsam3xa-hal]
+version = "0.1.0"
+features = ["rt"]
+
+[patch.crates-io]
+atsam3xa = { path = "../atsam3xa" }
+atsam3xa-hal = { path = "../atsam3xa-hal" }
+cortex-m-rt = { path = "../cortex-m-rt" }
+
+# this lets you use `cargo fix`!
+[[bin]]
+name = "hello-m"
+test = false
+bench = false
+
+[profile.release]
+opt-level = "z"
+codegen-units = 1 # better optimizations
+debug = true # symbols are nice and they don't increase the size on Flash
+incremental = false
+lto = true # better optimizations

diff --git a/README.md b/README.md
line changes: +70/-0
index 0000000..89a53f6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,70 @@
+# `hello-m`
+
+A simple microcontroller hacking environment
+
+## Building
+
+Not gonna lie, this is a terrible mess right now.
+
+Install a copy of the GNU Arm Embedded Toolchain and put it in your
+path. You can get `gcc-arm-none-eabi` on debian-style systems, but I
+recommend getting a newer version.
+
+Run `./fetch-external.sh`. That should retrieve the external
+dependencies and patch them.
+
+Run `./build-external.sh`. This should produce `libc.a`, `libm.a`, and
+`liblua.a` in `libs/`.
+
+Cd to `shell` and run `make`. This will produce `libshell.a`.
+
+Finally, run `cargo build`.  If you want NTSC video, that currently only
+works in release mode with `--release`.
+
+## Using
+
+Flash to your Due and connect to the serial port (the programming port,
+not the native port). You should get a Lua shell.
+
+### peek and poke
+
+Two utility functions are provided, `peek(addr)` and `poke(addr, val)`,
+which will read or write 32-bit memory values.
+
+### memory map
+
+The Due's 96KB is split into a 64KB user segment, and a 32KB kernel
+segment. The user segment is empty and can be fully utilized by
+application software. The kernel segment is further divided into a
+public and private portion. The public portion is accessible to user
+programs and contains data supporting application services like the
+memory allocator state, and publicly accessible service state like video
+RAM. The private portion contains management data for system services
+and the main stack for exception handling.
+
+In user mode, the MPU pokes out several holes in the address space.
+
+0x00000000  +------------------+
+            |       ROM        |
+0x00080000  +------------------+
+                    ...
+0x20000000  +------------------+
+            |     User RAM     |
+0x20010000  +------------------+
+                    ...
+0x2001F000  +------------------+
+            |    User Stack    | (mirrored User RAM)
+0x20020000  +------------------+
+                    ...
+0x20080000  +------------------+
+            |  Public Kernel   |
+0x20084000  +------------------+
+            |  Private Kernel  |
+0x20088000  +------------------+
+                    ...
+0x40000000  +------------------+
+            |   Peripherals    |
+0x60000000  +------------------+
+
+There's of course a lot more to the memory map, and I recommend perusing
+the [ATSAM3X/A datasheet](http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-11057-32-bit-Cortex-M3-Microcontroller-SAM3X-SAM3A_Datasheet.pdf) for more information.

diff --git a/build-external.sh b/build-external.sh
line changes: +25/-0
index 0000000..f687093
--- /dev/null
+++ b/build-external.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+set -e
+set -x
+ROOT=$PWD
+
+mkdir -p libs
+
+cd $ROOT/external/newlib
+mkdir build
+cd build
+../configure --target=arm-none-eabi --enable-newlib-reent-small --disable-newlib-fvwrite-in-streamio --disable-newlib-fseek-optimization --disable-newlib-wide-orient --disable-newlib-unbuf-stream-opt --enable-lite-exit --enable-newlib-global-atexit --enable-newlib-nano-formatted-io --disable-nls --disable-newlib-multithread --disable-multilib --disable-newlib-supplied-syscalls CFLAGS_FOR_TARGET='-g -mthumb -march=armv7-m -DMALLOC_PROVIDED'
+#--enable-target-optspace
+make configure-target-newlib
+# Remove conflicting builtin/rust stdlib symbols
+sed -i.bak -e 's/lib_a-\(memcmp\|memcpy\|memmove\|memset\)\.\$(OBJEXT) //g' arm-none-eabi/newlib/libc/string/Makefile
+sed -i.bak -e 's/lib_a-\(w_fmod\|wf_fmod\)\.\$(OBJEXT) //g' arm-none-eabi/newlib/libm/math/Makefile
+sed -i.bak -e 's/lib_a-memcpy\.\$(OBJEXT) //g' arm-none-eabi/newlib/libc/machine/arm/Makefile
+make -j4 all-target-newlib
+cd arm-none-eabi/newlib
+# copy libc/libc.a, which doesn't include libm.a symbols
+cp libc/libc.a libm.a $ROOT/libs/
+
+cd $ROOT/external/lua-5.3.5/src
+make -j4 CC='arm-none-eabi-gcc -mthumb -march=armv7-m' liblua.a
+cp liblua.a $ROOT/libs/

diff --git a/build.rs b/build.rs
line changes: +32/-0
index 0000000..8cf739c
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,32 @@
+use std::env;
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+use std::process::Command;
+
+fn main() {
+    // Put the linker script somewhere the linker can find it
+    let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
+    File::create(out.join("memory.x"))
+        .unwrap()
+        .write_all(include_bytes!("memory.x"))
+        .unwrap();
+    println!("cargo:rustc-link-search={}", out.display());
+
+    // Build the shell
+    //Command::new("make").args(&["-C", "shell"]).status().unwrap();
+
+    println!("cargo:rerun-if-changed=memory.x");
+    println!("cargo:rerun-if-changed=shell/libshell.a");
+    println!("cargo:rerun-if-changed=libs/libc.a");
+    println!("cargo:rerun-if-changed=libs/libm.a");
+    println!("cargo:rerun-if-changed=libs/liblua.a");
+
+    println!("cargo:rustc-link-search=libs");
+    println!("cargo:rustc-link-search=shell");
+
+    println!("cargo:rustc-link-lib=static=c");
+    println!("cargo:rustc-link-lib=static=m");
+    println!("cargo:rustc-link-lib=static=lua");
+    println!("cargo:rustc-link-lib=static=shell");
+}

diff --git a/crt/include/sysops.h b/crt/include/sysops.h
line changes: +34/-0
index 0000000..2c02779
--- /dev/null
+++ b/crt/include/sysops.h
@@ -0,0 +1,34 @@
+#ifndef __SYSOPS_H
+#define __SYSOPS_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+struct console_operations {
+	int32_t (*readb)();
+	size_t (*read)(uint8_t buf[], size_t len);
+	size_t (*readline)(uint8_t buf[], size_t len);
+	void (*writeb)(uint8_t b);
+	void (*write)(uint8_t a[], size_t len);
+	void (*discard)();
+	void (*pos)(size_t x, size_t y);
+	void (*clear)();
+};
+
+struct alloc_operations {
+	void (*init)(size_t start, size_t size);
+	void * (*alloc)(size_t size, size_t align);
+	void (*dealloc)(void *ptr, size_t size, size_t align);
+	void * (*malloc)(size_t size);
+	void (*free)(void *ptr);
+	void * (*realloc)(void *ptr, size_t size);
+};
+
+struct sys_ops {
+	struct console_operations *console;
+	struct alloc_operations *alloc;
+};
+
+static struct sys_ops *sys_ops __attribute__((section (".rodata"))) = (struct sys_ops *) 0xfc;
+
+#endif //__SYSOPS_H

diff --git a/external/.gitignore b/external/.gitignore
line changes: +2/-0
index 0000000..bdbcd84
--- /dev/null
+++ b/external/.gitignore
@@ -0,0 +1,2 @@
+lua-5.*
+newlib

diff --git a/external/lua.patch b/external/lua.patch
line changes: +37/-0
index 0000000..5471b69
--- /dev/null
+++ b/external/lua.patch
@@ -0,0 +1,37 @@
+diff -ur lua-5.3.5/src/luaconf.h lua-5.3.5.orig/src/luaconf.h
+--- lua-5.3.5/src/luaconf.h	2017-04-19 12:29:57.000000000 -0500
++++ lua-5.3.5.orig/src/luaconf.h	2019-01-10 23:01:29.048803300 -0600
+@@ -33,7 +33,7 @@
+ ** ensure that all software connected to Lua will be compiled with the
+ ** same configuration.
+ */
+-/* #define LUA_32BITS */
++#define LUA_32BITS
+ 
+ 
+ /*
+@@ -782,7 +782,7 @@
+ ** without modifying the main part of the file.
+ */
+ 
+-
++#define l_signalT unsigned int
+ 
+ 
+ 
+Only in lua-5.3.5.orig/src: lundump.o
+Only in lua-5.3.5.orig/src: lutf8lib.o
+Only in lua-5.3.5.orig/src: lvm.o
+Only in lua-5.3.5.orig/src: lzio.o
+diff -ur lua-5.3.5/src/Makefile lua-5.3.5.orig/src/Makefile
+--- lua-5.3.5/src/Makefile	2018-06-25 12:46:36.000000000 -0500
++++ lua-5.3.5.orig/src/Makefile	2019-01-21 23:54:50.177074600 -0600
+@@ -7,7 +7,7 @@
+ PLAT= none
+ 
+ CC= gcc -std=gnu99
+-CFLAGS= -O2 -Wall -Wextra -DLUA_COMPAT_5_2 $(SYSCFLAGS) $(MYCFLAGS)
++CFLAGS= -O2 -g -Wall -Wextra -DLUA_COMPAT_5_2 $(SYSCFLAGS) $(MYCFLAGS)
+ LDFLAGS= $(SYSLDFLAGS) $(MYLDFLAGS)
+ LIBS= -lm $(SYSLIBS) $(MYLIBS)
+ 

diff --git a/external/newlib.patch b/external/newlib.patch
line changes: +11/-0
index 0000000..f72f85e
--- /dev/null
+++ b/external/newlib.patch
@@ -0,0 +1,11 @@
+diff --git a/newlib/libc/stdlib/mallocr.c b/newlib/libc/stdlib/mallocr.c
+index 26d1c89cc..6e4bd78d4 100644
+--- a/newlib/libc/stdlib/mallocr.c
++++ b/newlib/libc/stdlib/mallocr.c
+@@ -1,5 +1,5 @@
+ #ifdef MALLOC_PROVIDED
+-int _dummy_mallocr = 1;
++int __attribute__((weak)) _dummy_mallocr = 1;
+ #else
+ /* ---------- To make a malloc.h, start cutting here ------------ */
+ 

diff --git a/fetch-external.sh b/fetch-external.sh
line changes: +20/-0
index 0000000..75ecc01
--- /dev/null
+++ b/fetch-external.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+set -e
+set -x
+
+cd external
+
+wget -c https://www.lua.org/ftp/lua-5.3.5.tar.gz
+tar xfz lua-5.3.5.tar.gz
+(
+	cd lua-5.3.5
+	patch -p1 <../lua.patch
+)
+
+git clone git://sourceware.org/git/newlib-cygwin.git newlib
+(
+	cd newlib
+	git reset --hard 9fa22dba558f5e3efd0bb719049491edcd7b5e0b
+	patch -p1 <../newlib.patch
+)

diff --git a/memory.x b/memory.x
line changes: +83/-0
index 0000000..e5728d0
--- /dev/null
+++ b/memory.x
@@ -0,0 +1,83 @@
+MEMORY
+{
+  /* NOTE 1 K = 1 KiBi = 1024 bytes */
+  FLASH : ORIGIN = 0x00000000, LENGTH = 512K
+  RAM : ORIGIN = 0x20084000, LENGTH = 16K
+  APPRAM : ORIGIN = 0x20000000, LENGTH = 64K
+  KGRAM : ORIGIN = 0x20080000, LENGTH = 16K
+}
+
+/* This is where the call stack will be allocated. */
+/* The stack is of the full descending type. */
+/* You may want to use this variable to locate the call stack and static
+   variables in different memory regions. Below is shown the default value */
+/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */
+
+/* You can use this symbol to customize the location of the .text section */
+/* If omitted the .text section will be placed right after the .vector_table
+   section */
+/* This is required only on microcontrollers that store some configuration right
+   after the vector table */
+/* _stext = ORIGIN(FLASH) + 0x400; */
+
+/* Example of putting non-initialized variables into custom RAM locations. */
+/* This assumes you have defined a region RAM2 above, and in the Rust
+   sources added the attribute `#[link_section = ".ram2bss"]` to the data
+   you want to place there. */
+/* Note that the section will not be zero-initialized by the runtime! */
+/* SECTIONS {
+     .ram2bss (NOLOAD) : ALIGN(4) {
+       *(.ram2bss);
+       . = ALIGN(4);
+     } > RAM2
+   } INSERT AFTER .bss;
+*/
+
+_appram = ORIGIN(APPRAM);
+_appram_end = ORIGIN(APPRAM) + LENGTH(APPRAM);
+
+SECTIONS {
+  .driver_interfaces : ALIGN(4) {
+    *(.driver_interfaces);
+    . = ALIGN(4);
+  } > FLASH
+
+  .init_array : ALIGN(4) {
+    *(.init_array);
+    . = ALIGN(4);
+  } > FLASH
+
+  PROVIDE(_stext = ADDR(.init_array) + SIZEOF(.init_array));
+} INSERT AFTER .vector_table;
+
+SECTIONS {
+  .kgdata : ALIGN(4) {
+    *(.kgdata);
+    *libc.a*(.data .data.*);
+    *libm.a*(.data .data.*);
+    *liblua.a*(.data .data.*);
+    *libshell.a*(.data .data.*);
+    . = ALIGN(4);
+  } > KGRAM AT >FLASH
+
+  /* .kgdata ROM location */
+  __skgdata = ADDR(.kgdata);
+  __ekgdata = ADDR(.kgdata) + SIZEOF(.kgdata);
+
+  /* .kgdata RAM location */
+  __sikgdata = LOADADDR(.kgdata);
+} INSERT BEFORE .data;
+
+SECTIONS {
+  .kgbss : ALIGN(4) {
+    *(.kgbss);
+    *libc.a*(.bss .bss.* COMMON);
+    *libm.a*(.bss .bss.* COMMON);
+    *liblua.a*(.bss .bss.* COMMON);
+    *libshell.a*(.bss .bss.* COMMON);
+    . = ALIGN(4);
+  } > KGRAM AT >KGRAM  /* Don't ask me why, but simply "> KGRAM" doesn't work */
+
+  __skgbss = ADDR(.kgbss);
+  __ekgbss = ADDR(.kgbss) + SIZEOF(.kgbss);
+} INSERT BEFORE .bss;

diff --git a/shell/.gitignore b/shell/.gitignore
line changes: +2/-0
index 0000000..9eca6c8
--- /dev/null
+++ b/shell/.gitignore
@@ -0,0 +1,2 @@
+*.a
+*.o

diff --git a/shell/Makefile b/shell/Makefile
line changes: +8/-0
index 0000000..77862c9
--- /dev/null
+++ b/shell/Makefile
@@ -0,0 +1,8 @@
+INCLUDES = -I../crt/include -I../external/lua-5.3.5/src
+CFLAGS = -std=c11 -march=armv7-m -mthumb -msingle-pic-base -Os -g -Wall $(INCLUDES)
+
+all: system.o main.o
+	ar cr libshell.a $^
+
+%.o: %.c
+	arm-none-eabi-gcc $(CFLAGS) -c $<

diff --git a/shell/main.c b/shell/main.c
line changes: +137/-0
index 0000000..7d43bfe
--- /dev/null
+++ b/shell/main.c
@@ -0,0 +1,137 @@
+#include <sysops.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+struct MemoryRange {
+	uint32_t start;
+	uint32_t length;
+};
+
+void svc64(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint32_t arg3);
+
+__asm__(
+".global svc64\n"
+".type svc64,function\n"
+"svc64:\n"
+"	svc #64\n"
+"	bx lr\n"
+);
+
+int peek(lua_State *L) {
+	int n_args = lua_gettop(L);
+	if (n_args != 1) {
+		puts("peek(addr)");
+		return 0;
+	}
+	uint32_t addr = lua_tointeger(L, 1);
+	uint32_t data = *(uint32_t *)addr;
+	lua_pushinteger(L, data);
+	return 1;
+}
+
+int poke(lua_State *L) {
+	int n_args = lua_gettop(L);
+	if (n_args != 2) {
+		puts("poke(addr, value)");
+		return 0;
+	}
+	uint32_t addr = lua_tointeger(L, 1);
+	uint32_t data = lua_tointeger(L, 2);
+	*(uint32_t *)addr = data;
+	return 0;
+}
+
+void shell_main(struct MemoryRange *r) {
+	lua_State *L;
+	char buffer[128];
+
+	sys_ops->alloc->init(r->start, r->length);
+
+	L = luaL_newstate();
+	if (L == NULL) {
+		puts("Could not allocate lua state\r\n");
+		exit(EXIT_FAILURE);
+	}
+
+	luaL_openlibs(L);
+
+	lua_pushcfunction(L, peek);
+	lua_setglobal(L, "peek");
+	lua_pushcfunction(L, poke);
+	lua_setglobal(L, "poke");
+
+	puts(LUA_VERSION " Okay!\n");
+
+	while(1) {
+		fputs("> ", stdout);
+		fflush(stdout);
+
+		uint32_t n = sys_ops->console->readline((uint8_t *)buffer, 127);
+		buffer[n] = 0;
+		puts("");
+
+		if (strcmp(buffer, "clear") == 0) {
+			sys_ops->console->clear();
+		} else if (strcmp(buffer, "svcall") == 0) {
+			svc64(42, 69, 88, 64);
+		} else if (strcmp(buffer, "malloc") == 0) {
+			char *s = sys_ops->alloc->malloc(1024);
+			if (!s) {
+				puts("alloc failed");
+				continue;
+			}
+			const char hello[] = "Hello alloc!";
+			for (int i = 0; i < strlen(hello); i++) {
+				s[i] = hello[i];
+			}
+		} else if (strcmp(buffer, "dbz") == 0) {
+			// Enable DIV_0_TRP
+			*(uint32_t*)0xe000ed14 = 0x10;
+			__asm__(
+				"mov r0, #1\n"
+				"mov r1, #0\n"
+				"sdiv r0, r0, r1\n"
+			);
+		} else {
+			luaL_loadstring(L, buffer);
+			int r = lua_pcall(L, 0, LUA_MULTRET, 0);
+			if (r != 0) {
+				fputs("error: ", stderr);
+				switch(r) {
+				case LUA_ERRRUN:
+					break;
+				case LUA_ERRMEM:
+					puts("memory allocation");
+					break;
+				case LUA_ERRERR:
+					puts("message handler");
+					break;
+				case LUA_ERRGCMM:
+					puts("__gc metamethod");
+					break;
+				default:
+					puts("unknown");
+				}
+				int top = lua_gettop(L);
+				puts(luaL_tolstring(L, top, NULL));
+			} else {
+				int top = lua_gettop(L);
+				if (top == 1) {
+					puts(luaL_tolstring(L, 1, NULL));
+				} else {
+					for (int i = 1; i <= top; i++) {
+						printf("%d: %s\n", i, luaL_tolstring(L, i, NULL));
+					}
+				}
+			}
+
+			lua_settop(L, 0);
+		}
+	}
+}

diff --git a/shell/system.c b/shell/system.c
line changes: +118/-0
index 0000000..c77bdac
--- /dev/null
+++ b/shell/system.c
@@ -0,0 +1,118 @@
+#include <sysops.h>
+
+#include <reent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/times.h>
+
+#include <errno.h>
+#undef errno
+extern int errno;
+
+void _exit(int status) {
+	puts("No exit!\r\n");
+	while (1) { }
+}
+
+int _close(int file) {
+	return -1;
+}
+
+int _fstat(int file, struct stat *st) {
+	st->st_mode = S_IFCHR;
+	return 0;
+}
+
+int _gettimeofday(struct timeval *tv, struct timezone *tz) {
+	errno = EINVAL;
+	return -1;
+};
+
+int _isatty(int fd) {
+	if (fd < 3)
+		return 1;
+	errno = EBADF;
+	return 0;
+}
+
+int _getpid(void) {
+	return 1;
+}
+
+int _kill(int pid, int sig) {
+	errno = EINVAL;
+	return -1;
+}
+
+int _link(char *old, char *new) {
+	errno = EMLINK;
+	return -1;
+}
+
+int _lseek(int file, int ptr, int dir) {
+	return 0;
+}
+
+int _open(const char *name, int flags, int mode) {
+	return -1;
+}
+
+int _read(int file, char *ptr, int len) {
+	return 0;
+}
+
+int _times(struct tms *buf) {
+	return -1;
+}
+
+int _unlink(char *name) {
+	errno = ENOENT;
+	return -1; 
+}
+
+int _write(int file, char *ptr, int len) {
+	if (file == 1 || file == 2) {
+		sys_ops->console->write((uint8_t *)ptr, len);
+		return len;
+	} else {
+		errno = EBADF;
+		return -1;
+	}
+}
+
+void * malloc(size_t nbytes) {
+	return sys_ops->alloc->malloc(nbytes);
+}
+
+void * _malloc_r(struct _reent *r, size_t nbytes) {
+	return malloc(nbytes);
+}
+
+void * calloc(size_t n, size_t elem_size) {
+	void *ptr = malloc(n * elem_size);
+	memset(ptr, 0, n * elem_size);
+	return ptr;
+}
+
+void * _calloc_r(struct _reent *r, size_t n, size_t elem_size) {
+	return calloc(n, elem_size);
+}
+
+void free(void *ptr) {
+	sys_ops->alloc->free(ptr);
+}
+
+void _free_r(struct _reent *r, void *ptr) {
+	free(ptr);
+}
+
+void * realloc(void *ptr, size_t nbytes) {
+	return sys_ops->alloc->realloc(ptr, nbytes);
+}
+
+void * _realloc_r(struct _reent *r, void *ptr, size_t nbytes) {
+	return realloc(ptr, nbytes);
+}

diff --git a/src/alloc.rs b/src/alloc.rs
line changes: +187/-0
index 0000000..8599f9e
--- /dev/null
+++ b/src/alloc.rs
@@ -0,0 +1,187 @@
+extern crate alloc;
+use core::alloc::{GlobalAlloc, Layout};
+use alloc::collections::BTreeMap;
+use crate::console::ConsoleOutput;
+use crate::ALLOCATOR;
+
+pub unsafe fn init(start: usize, size: usize) {
+    ALLOCATOR.init(start, size);
+}
+
+/*
+#[cfg(not(alloc_trace))]
+pub unsafe fn alloc(layout: Layout) -> *mut u8 {
+    ALLOCATOR.alloc(layout)
+}
+*/
+
+pub unsafe fn alloc(layout: Layout) -> *mut u8 {
+    let ptr = ALLOCATOR.alloc(layout);
+    #[cfg(alloc_trace)]
+    {
+        crate::serial_console::get_global_console().write("+");
+        crate::util::print_hex_padded(ptr as u32, 8);
+        crate::serial_console::get_global_console().write(" ");
+    }
+    ptr
+}
+
+pub unsafe fn dealloc(ptr: *mut u8, layout: Layout) {
+    #[cfg(alloc_trace)]
+    {
+        crate::serial_console::get_global_console().write("-");
+        crate::util::print_hex_padded(ptr as u32, 8);
+        crate::serial_console::get_global_console().write(" ");
+    }
+    ALLOCATOR.dealloc(ptr, layout);
+}
+
+pub unsafe fn realloc(ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
+    #[cfg(alloc_trace)]
+    {
+        crate::serial_console::get_global_console().write("=");
+        crate::util::print_hex_padded(ptr as u32, 8);
+    }
+    let ptr = ALLOCATOR.realloc(ptr, layout, new_size);
+    #[cfg(alloc_trace)]
+    {
+        crate::serial_console::get_global_console().write("/");
+        crate::util::print_hex_padded(ptr as u32, 8);
+        crate::serial_console::get_global_console().write(" ");
+    }
+    ptr
+}
+
+pub struct RustAllocOperations {
+    pub init: unsafe fn(start: usize, size: usize),
+    pub alloc: unsafe fn(layout: Layout) -> *mut u8,
+    pub dealloc: unsafe fn(ptr: *mut u8, layout: Layout),
+    pub realloc: unsafe fn(ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8,
+}
+
+pub const RUST_ALLOC_OPERATIONS: RustAllocOperations = RustAllocOperations {
+    init,
+    alloc,
+    dealloc,
+    realloc,
+};
+
+pub unsafe extern "C" fn c_init(start: usize, size: usize) {
+    init(start, size)
+}
+
+pub unsafe extern "C" fn c_alloc(size: usize, align: usize) -> *mut u8 {
+    let layout = match Layout::from_size_align(size, align) {
+        Ok(l) => l,
+        Err(_) => return 0 as *mut u8,
+    };
+
+    alloc(layout)
+}
+
+pub unsafe extern "C" fn c_dealloc(ptr: *mut u8, size: usize, align: usize) {
+    let layout = match Layout::from_size_align(size, align) {
+        Ok(l) => l,
+        Err(_) => return,
+    };
+
+    dealloc(ptr, layout)
+}
+
+#[link_section = ".kgbss"]
+static mut MALLOC_MAP: Option<BTreeMap<*mut u8, usize>> = None;
+
+pub unsafe extern "C" fn malloc(size: usize) -> *mut u8 {
+    let layout = match Layout::from_size_align(size, 4) {
+        Ok(l) => l,
+        Err(_) => return 0 as *mut u8,
+    };
+
+    if MALLOC_MAP.is_none() {
+        MALLOC_MAP = Some(BTreeMap::new());
+    }
+
+    let ptr = alloc(layout);
+    if ptr == (0 as *mut u8) {
+        return 0 as *mut u8;
+    }
+
+    let map = MALLOC_MAP.as_mut().unwrap();
+    map.insert(ptr, size);
+
+    ptr
+}
+
+pub unsafe extern "C" fn free(ptr: *mut u8) {
+    if ptr.is_null() {
+        return;
+    }
+
+    if MALLOC_MAP.is_none() {
+        return;
+    }
+
+    let map = MALLOC_MAP.as_mut().unwrap();
+    if map.contains_key(&ptr) {
+        let size = *map.get(&ptr).unwrap();
+        let layout = match Layout::from_size_align(size, 4) {
+            Ok(l) => l,
+            Err(_) => return,
+        };
+        dealloc(ptr, layout);
+        map.remove(&ptr);
+    }
+}
+
+pub unsafe extern "C" fn c_realloc(ptr: *mut u8, nbytes: usize) -> *mut u8 {
+    if MALLOC_MAP.is_none() {
+        MALLOC_MAP = Some(BTreeMap::new());
+    }
+
+    if ptr as u32 == 0 {
+        return malloc(nbytes);
+    } else if nbytes == 0 {
+        free(ptr);
+        return 0 as *mut u8;
+    }
+
+    let map = MALLOC_MAP.as_mut().unwrap();
+    if map.contains_key(&ptr) {
+        let size = *map.get(&ptr).unwrap();
+        let layout = match Layout::from_size_align(size, 4) {
+            Ok(l) => l,
+            Err(_) => return 0 as *mut u8,
+        };
+        let new_ptr = realloc(ptr, layout, nbytes);
+        map.remove(&ptr);
+        map.insert(new_ptr, nbytes);
+        new_ptr
+    } else {
+        0 as *mut u8
+    }
+}
+
+#[repr(C)]
+pub struct CAllocOperations {
+    pub init: unsafe extern "C" fn(start: usize, size: usize),
+    pub alloc: unsafe extern "C" fn(size: usize, align: usize) -> *mut u8,
+    pub dealloc: unsafe extern "C" fn(ptr: *mut u8, size: usize, align: usize),
+    pub malloc: unsafe extern "C" fn(size: usize) -> *mut u8,
+    pub free: unsafe extern "C" fn(ptr: *mut u8),
+    pub realloc: unsafe extern "C" fn(ptr: *mut u8, nbytes: usize) -> *mut u8,
+}
+
+pub const C_ALLOC_OPERATIONS: CAllocOperations = CAllocOperations {
+    init: c_init,
+    alloc: c_alloc,
+    dealloc: c_dealloc,
+    malloc,
+    free,
+    realloc: c_realloc,
+};
+
+#[alloc_error_handler]
+pub fn handle_alloc_error(_layout: Layout) -> ! {
+    crate::serial_console::get_global_console().write("OOM!\r\n");
+    loop{}
+}

diff --git a/src/console.rs b/src/console.rs
line changes: +90/-0
index 0000000..60057e9
--- /dev/null
+++ b/src/console.rs
@@ -0,0 +1,90 @@
+use arraydeque::ArrayDeque;
+use cortex_m::interrupt;
+
+pub trait ConsoleInput {
+    fn readb(&mut self) -> Option<u8>;
+    fn read(&mut self, buf: &mut [u8]) -> usize;
+    fn discard(&mut self);
+}
+
+pub trait ConsoleOutput {
+    fn pos(&mut self, x: usize, y: usize);
+    fn writeb(&mut self, b: u8);
+    fn write<T: AsRef<[u8]>>(&mut self, a: T);
+    fn clear(&mut self);
+}
+
+const BUFFER_SIZE: usize = 16;
+
+pub struct Console {
+    rb: ArrayDeque<[u8; BUFFER_SIZE], arraydeque::Saturating>,
+}
+
+impl Console {
+    pub fn new() -> Console {
+        Console {
+            rb: ArrayDeque::new(),
+        }
+    }
+
+    pub fn reset(&mut self) {
+        interrupt::free(|_| {
+            self.rb.clear();
+        });
+    }
+
+    pub fn pushb(&mut self, b: u8) {
+        interrupt::free(|_| {
+            self.rb.push_back(b).ok();
+        });
+    }
+}
+
+impl ConsoleInput for Console {
+    fn readb(&mut self) -> Option<u8> {
+        interrupt::free(|_| {
+            self.rb.pop_front()
+        })
+    }
+
+    fn read(&mut self, buf: &mut [u8]) -> usize {
+        let len = if buf.len() > self.rb.len() {
+            self.rb.len()
+        } else {
+            buf.len()
+        };
+
+        for i in 0..len {
+            buf[i] = self.readb().unwrap();
+        }
+
+        len
+    }
+
+    fn discard(&mut self) {
+        self.reset();
+    }
+}
+
+pub struct RustConsoleOperations {
+    pub readb: fn() -> Option<u8>,
+    pub read: fn(&mut [u8]) -> usize,
+    pub readline: fn(buf: &mut [u8]) -> usize,
+    pub writeb: fn(b: u8),
+    pub write: fn(a: &[u8]),
+    pub discard: fn(),
+    pub pos: fn(x: usize, y: usize),
+    pub clear: fn(),
+}
+
+#[repr(C)]
+pub struct CConsoleOperations {
+    pub readb: extern "C" fn() -> i32,
+    pub read: extern "C" fn(buf: *mut u8, len: usize) -> usize,
+    pub readline: extern "C" fn(buf: *mut u8, len: usize) -> usize,
+    pub writeb: extern "C" fn(b: u8),
+    pub write: extern "C" fn(a: *const u8, len: usize),
+    pub discard: extern "C" fn(),
+    pub pos: extern "C" fn(x: usize, y: usize),
+    pub clear: extern "C" fn(),
+}

diff --git a/src/fault.rs b/src/fault.rs
line changes: +168/-0
index 0000000..d223b76
--- /dev/null
+++ b/src/fault.rs
@@ -0,0 +1,168 @@
+use cortex_m;
+use cortex_m_rt::exception;
+use cortex_m_rt::ExceptionFrame;
+use crate::serial_console::get_global_console;
+use crate::console::ConsoleOutput;
+use crate::util::print_hex_padded;
+
+fn print_exception_frame(ef: &ExceptionFrame) {
+    let console = get_global_console();
+
+    console.write("PC: ");
+    print_hex_padded(ef.pc, 8);
+    console.write("\r\n");
+
+    console.write("LR: ");
+    print_hex_padded(ef.lr, 8);
+    console.write("\r\n");
+
+    console.write("XPSR: ");
+    print_hex_padded(ef.xpsr, 8);
+    console.write("\r\n");
+
+    console.write("R0: ");
+    print_hex_padded(ef.r0, 8);
+    console.write("\r\n");
+
+    console.write("R1: ");
+    print_hex_padded(ef.r1, 8);
+    console.write("\r\n");
+
+    console.write("R2: ");
+    print_hex_padded(ef.r2, 8);
+    console.write("\r\n");
+
+    console.write("R3: ");
+    print_hex_padded(ef.r3, 8);
+    console.write("\r\n");
+
+    console.write("IP: ");
+    print_hex_padded(ef.r12, 8);
+    console.write("\r\n");
+}
+
+unsafe fn print_stack(ef: &ExceptionFrame) {
+    let console = get_global_console();
+    let sp = ef as *const ExceptionFrame as *const u32;
+
+    console.write("SP: ");
+    print_hex_padded(sp as u32, 8);
+    console.write("\r\n");
+
+    for i in 0..4 {
+        print_hex_padded((sp as u32) + i * 32, 8);
+        console.write(": ");
+        for j in 0..8 {
+            print_hex_padded(*sp.offset((i * 8 + j) as isize), 8);
+            console.write(" ");
+        }
+        console.write("\r\n");
+    }
+}
+
+#[exception]
+unsafe fn HardFault(ef: &ExceptionFrame) -> ! {
+    let console = get_global_console();
+    console.write("\r\nHardFault!\r\n");
+    print_exception_frame(ef);
+    console.write("\r\n");
+
+    print_stack(ef);
+    console.write("\r\n");
+
+    let scb = &(*cortex_m::peripheral::SCB::ptr());
+
+    let hfsr = scb.hfsr.read();
+    console.write("HFSR: ");
+    print_hex_padded(hfsr, 8);
+    if hfsr & 0x00000002 != 0 {
+        console.write(" VECTBL");
+    }
+    if hfsr & 0x40000000 != 0 {
+        console.write(" FORCED");
+    }
+    console.write("\r\n");
+
+    if hfsr & 0x40000000 != 0 {
+        // read all the other fault regs
+        let cfsr = scb.cfsr.read();
+
+        let mmfsr = (cfsr & 0xFF) as u8;
+        console.write("MMFSR: ");
+        print_hex_padded(mmfsr, 8);
+        if mmfsr & 0x01 != 0 {
+            console.write(" IACCVIOL");
+        }
+        if mmfsr & 0x02 != 0 {
+            console.write(" DACCVIOL");
+        }
+        if mmfsr & 0x08 != 0 {
+            console.write(" MUNSTKERR");
+        }
+        if mmfsr & 0x10 != 0 {
+            console.write(" MSTKERR");
+        }
+        console.write("\r\n");
+
+        if mmfsr & 0x80 != 0 {
+            console.write("MMFAR: ");
+            print_hex_padded(scb.mmfar.read(), 8);
+        } else {
+            console.write("MMFAR invalid");
+        }
+        console.write("\r\n");
+
+        let bfsr = ((cfsr & 0xFF00) >> 8) as u8;
+        console.write("BFSR: ");
+        print_hex_padded(bfsr, 2);
+        if bfsr & 0x01 != 0 {
+            console.write(" IBUSERR");
+        }
+        if bfsr & 0x02 != 0 {
+            console.write(" PRECISERR");
+        }
+        if bfsr & 0x04 != 0 {
+            console.write(" IMPRECISERR");
+        }
+        if bfsr & 0x08 != 0 {
+            console.write(" UNSTKERR");
+        }
+        if bfsr & 0x10 != 0 {
+            console.write(" STKERR");
+        }
+        console.write("\r\n");
+
+        if bfsr & 0x80 != 0 {
+            console.write("BFAR: ");
+            print_hex_padded(scb.bfar.read(), 8);
+        } else {
+            console.write("BFAR invalid");
+        }
+        console.write("\r\n");
+
+        let ufsr = ((cfsr & 0xFFFF0000) >> 16) as u16;
+        console.write("UFSR: ");
+        print_hex_padded(ufsr, 4);
+        if ufsr & 0x0001 != 0 {
+            console.write(" UNDEFINSTR");
+        }
+        if ufsr & 0x0002 != 0 {
+            console.write(" INVSTATE");
+        }
+        if ufsr & 0x0004 != 0 {
+            console.write(" INVPC");
+        }
+        if ufsr & 0x0008 != 0 {
+            console.write(" NOCP");
+        }
+        if ufsr & 0x0100 != 0 {
+            console.write(" UNALIGNED");
+        }
+        if ufsr & 0x0200 != 0 {
+            console.write(" DIVBYZERO");
+        }
+        console.write("\r\n");
+    }
+
+    loop {}
+}

diff --git a/src/main.rs b/src/main.rs
line changes: +109/-0
index 0000000..90229e4
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,109 @@
+#![no_std]
+#![no_main]
+#![feature(generators, generator_trait)]
+#![feature(global_asm)]
+#![feature(alloc)]
+#![feature(alloc_error_handler)]
+#![feature(asm)]
+#![feature(const_vec_new)]
+#![feature(const_fn)]
+
+use cortex_m_rt::{entry, pre_init};
+use embedded_hal::watchdog::WatchdogDisable; // REMOVE when embedded-hal watchdog is finalized
+use alloc_cortex_m::CortexMHeap;
+use r0;
+
+mod util;
+mod console;
+mod serial_console;
+mod peripherals;
+mod mpu;
+mod task;
+mod svcall;
+mod alloc;
+mod fault;
+mod panic;
+mod video;
+
+use console::ConsoleOutput;
+use peripherals::Peripherals;
+use task::Task;
+
+#[global_allocator]
+#[link_section = ".kgbss"]
+static ALLOCATOR: CortexMHeap = CortexMHeap::empty();
+
+struct Kernel {
+    peripherals: Peripherals,
+}
+
+impl Kernel {
+    pub fn new() -> Kernel {
+        Kernel {
+            peripherals: Peripherals::new(),
+        }
+    }
+
+    pub fn get_peripherals(&mut self) -> &mut Peripherals {
+        &mut self.peripherals
+    }
+}
+
+extern "C" {
+    fn shell_main() -> u32;
+}
+
+#[allow(dead_code)]
+pub struct RustDriverInterfaces {
+    console: &'static console::RustConsoleOperations,
+    alloc: &'static alloc::RustAllocOperations,
+}
+
+#[link_section = ".driver_interfaces"]
+pub static RUST_DRIVER_INTERFACES: RustDriverInterfaces = RustDriverInterfaces {
+    console: &serial_console::RUST_CONSOLE_OPERATIONS,
+    alloc: &alloc::RUST_ALLOC_OPERATIONS,
+};
+
+#[allow(dead_code)]
+#[repr(C)]
+pub struct CDriverInterfaces {
+    console: &'static console::CConsoleOperations,
+    alloc: &'static alloc::CAllocOperations,
+}
+
+#[link_section = ".driver_interfaces"]
+pub static C_DRIVER_INTERFACES: CDriverInterfaces = CDriverInterfaces {
+    console: &serial_console::C_CONSOLE_OPERATIONS,
+    alloc: &alloc::C_ALLOC_OPERATIONS,
+};
+
+#[pre_init]
+unsafe fn pre_init() {
+    extern "C" {
+        static mut __skgbss: u32;
+        static mut __ekgbss: u32;
+
+        static mut __skgdata: u32;
+        static mut __ekgdata: u32;
+        static __sikgdata: u32;
+    }
+
+    // Initialize KGRAM
+    r0::zero_bss(&mut __skgbss, &mut __ekgbss);
+    r0::init_data(&mut __skgdata, &mut __ekgdata, &__sikgdata);
+}
+
+#[entry]
+fn main() -> ! {
+    let mut kernel = Kernel::new();
+
+    let peripherals = kernel.get_peripherals();
+
+    peripherals.watchdog.disable();
+
+    serial_console::get_global_console().write("Kernel initialized!\r\n");
+
+    let t = Task::default_layout_full(shell_main);
+    t.run()
+}

diff --git a/src/mpu.rs b/src/mpu.rs
line changes: +132/-0
index 0000000..183e1c0
--- /dev/null
+++ b/src/mpu.rs
@@ -0,0 +1,132 @@
+#![allow(dead_code)]
+
+use core::cell::RefCell;
+use cortex_m::interrupt::{self, Mutex};
+use cortex_m::peripheral::MPU;
+
+#[derive(Copy, Clone, Debug)]
+pub enum MemoryPermission {
+    None,
+    RO,
+    RW,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+pub struct MemoryRange {
+    pub start: u32,
+    pub length: u32,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+pub struct MemoryRegion {
+    pub start: u32,
+    pub length: u32,
+    pub permissions: MemoryPermission,
+    pub xn: bool,
+}
+
+static MPU_MANAGER: Mutex<RefCell<Option<MpuManager>>> = Mutex::new(RefCell::new(None));
+
+pub fn init(mpu: MPU) {
+    interrupt::free(|cs| MPU_MANAGER.borrow(cs).replace(Some(MpuManager::new(mpu))));
+}
+
+pub fn add_region(region: &MemoryRegion) {
+    interrupt::free(|cs| {
+        let mut rm = MPU_MANAGER.borrow(cs).borrow_mut();
+        let manager = rm.as_mut().unwrap();
+        unsafe { manager.add_region(&region) };
+    });
+}
+
+pub fn add_regions(regions: &[MemoryRegion]) {
+    interrupt::free(|cs| {
+        let mut rm = MPU_MANAGER.borrow(cs).borrow_mut();
+        let manager = rm.as_mut().unwrap();
+        for region in regions {
+            unsafe { manager.add_region(&region) };
+        }
+    });
+}
+
+pub fn enable() {
+    interrupt::free(|cs| {
+        let mut rm = MPU_MANAGER.borrow(cs).borrow_mut();
+        let manager = rm.as_mut().unwrap();
+        manager.set_mpu_enable(true);
+    });
+}
+
+pub fn disable() {
+    interrupt::free(|cs| {
+        let mut rm = MPU_MANAGER.borrow(cs).borrow_mut();
+        let manager = rm.as_mut().unwrap();
+        manager.set_mpu_enable(false);
+    });
+}
+
+struct MpuManager {
+    mpu: MPU,
+    n_mpu_regions: u32,
+}
+
+impl MpuManager {
+    pub fn new(mpu: MPU) -> MpuManager {
+        // Enable default memory map for privileged software
+        unsafe { mpu.ctrl.write(0x4) };
+
+        MpuManager {
+            mpu,
+            n_mpu_regions: 0,
+        }
+    }
+
+    unsafe fn write_mpu_region(&mut self, start: u32, length: u32, region: &MemoryRegion) {
+        if self.n_mpu_regions == 8 {
+            panic!("Out of MPU regions");
+        }
+
+        // We don't check the start value here. Let the fault handler handle it.
+        self.mpu.rbar.write(start | 0b10000 | self.n_mpu_regions);
+        self.mpu.rasr.write(
+            if region.xn { 1 << 28 } else { 0 } |  // XN
+            match region.permissions {
+                MemoryPermission::None => 0b001,
+                MemoryPermission::RO   => 0b010,
+                MemoryPermission::RW   => 0b011,
+            } << 24 |                              // AP
+            0b001000 << 16 |                       // TEX S C B
+            (length.trailing_zeros() - 1) << 1 |   // SIZE
+            1                                      // ENABLE
+        );
+        self.n_mpu_regions += 1;
+    }
+
+    unsafe fn map_mpu_region(&mut self, start: u32, length: u32, region: &MemoryRegion) {
+        if length.count_ones() > 1 {
+            // The region is not a power of two. Create a MPU region for
+            // the next lowest power of two and then iterate on the
+            // remainder.
+            let nl_length = length.next_power_of_two() >> 1;
+            self.write_mpu_region(start, nl_length, region);
+            self.map_mpu_region(start + nl_length, length - nl_length, region);
+        } else {
+            self.write_mpu_region(start, length, region);
+        }
+    }
+
+    pub unsafe fn add_region(&mut self, region: &MemoryRegion) {
+        if region.length == 0 {
+            return;
+        }
+        self.map_mpu_region(region.start, region.length, region);
+    }
+
+    pub fn set_mpu_enable(&mut self, enable: bool) {
+        unsafe {
+            self.mpu.ctrl.modify(|v| (v & 0xFFFFFFFE) | (enable as u32))
+        };
+    }
+}

diff --git a/src/panic.rs b/src/panic.rs
line changes: +32/-0
index 0000000..412d7e0
--- /dev/null
+++ b/src/panic.rs
@@ -0,0 +1,32 @@
+use core::panic::PanicInfo;
+use crate::serial_console::get_global_console;
+use crate::console::ConsoleOutput;
+use crate::util::print_dec;
+
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+    // Boy if we panic before the console is up, we're just SOL.
+    let console = get_global_console();
+
+    console.write("PANIC!\r\n");
+
+    match info.payload().downcast_ref::<&str>() {
+        Some(p) => console.write(p),
+        None => console.write("no payload"),
+    };
+    console.write("\r\n");
+
+    if let Some(location) = info.location() {
+        console.write("at ");
+        console.write(location.file());
+        console.write(" line ");
+        print_dec(location.line());
+        console.write(":");
+        print_dec(location.column());
+        console.write("\r\n");
+    } else {
+        console.write("Unknown location\r\n");
+    }
+
+    loop{}
+}

diff --git a/src/peripherals.rs b/src/peripherals.rs
line changes: +82/-0
index 0000000..4d15f99
--- /dev/null
+++ b/src/peripherals.rs
@@ -0,0 +1,82 @@
+use cortex_m::peripheral::MPU;
+use atsam3xa_hal::prelude::*;
+use crate::serial_console::SerialConsole;
+use crate::mpu::{self, MemoryRegion, MemoryPermission};
+use crate::video;
+
+pub struct Peripherals {
+    pub led: atsam3xa_hal::pio::Pin,
+    pub watchdog: atsam3xa_hal::watchdog::WDT,
+}
+
+impl Peripherals {
+    pub fn new() -> Peripherals {
+        let mut mp = cortex_m::peripheral::Peripherals::take().unwrap();
+        let pp = atsam3xa::Peripherals::take().unwrap();
+
+        // Initialize flash wait states
+        pp.EFC0.constrain().set_wait_states(4);
+        pp.EFC1.constrain().set_wait_states(4);
+
+        // Initialize clocks
+        let pmc = pp.PMC.constrain();
+        pmc.init_clocks();
+
+        pmc.enable_peripheral_clock(PeripheralID::PIOB);
+        pmc.enable_peripheral_clock(PeripheralID::UART);
+
+        let pio_a = pp.PIOA.constrain();
+        let pio_b = pp.PIOB.constrain();
+
+        let led = pio_b.get_pin(27);
+        led.output_mode();
+
+        let uart = pp.UART.constrain(&pio_a, 115200, ParityType::NO);
+        mp.NVIC.enable(atsam3xa::Interrupt::UART);
+        unsafe { mp.NVIC.set_priority(atsam3xa::Interrupt::UART, 7 << 4) };
+        uart.enable_interrupts();
+        SerialConsole::new(uart);
+        
+        let watchdog = pp.WDT.constrain();
+
+        Self::mpu_setup(mp.MPU);
+
+        video::gen::start(pp.TC0, &mut mp.NVIC, &pmc, &pio_b);
+
+        Peripherals {
+            led,
+            watchdog,
+        }
+    }
+
+    pub fn mpu_setup(mpu: MPU) {
+        mpu::init(mpu);
+
+        mpu::add_regions(&[
+            // ROM
+            MemoryRegion {
+                start: 0x00000000,
+                length: 0x20000000,
+                permissions: MemoryPermission::RO,
+                xn: false,
+            },
+            // Peripherals
+            MemoryRegion {
+                start: 0x40000000,
+                length: 0x20000000,
+                permissions: MemoryPermission::RW,
+                xn: true,
+            },
+            // KGRAM
+            MemoryRegion {
+                start: 0x20080000,
+                length: 0x4000,
+                permissions: MemoryPermission::RW,
+                xn: true,
+            },
+        ]);
+            
+        // Enable MPU
+        mpu::enable();
+    }
+}

diff --git a/src/serial_console.rs b/src/serial_console.rs
line changes: +211/-0
index 0000000..54b2aff
--- /dev/null
+++ b/src/serial_console.rs
@@ -0,0 +1,211 @@
+use crate::console::*;
+
+use core::slice;
+use nb::block;
+use embedded_hal::serial::*;
+use atsam3xa::interrupt;
+
+#[link_section = ".kgbss"]
+static mut G_CONSOLE: Option<SerialConsole> = None;
+
+pub struct SerialConsole {
+    console: Console,
+    uart: atsam3xa_hal::uart::UART,
+}
+
+impl SerialConsole {
+    pub fn new(uart: atsam3xa_hal::uart::UART) {
+        unsafe {
+            G_CONSOLE = Some(SerialConsole {
+                console: Console::new(),
+                uart: uart,
+            });
+        }
+    }
+
+    pub fn readline(&mut self, buf: &mut [u8]) -> usize {
+        let mut c = 0;
+
+        loop {
+            let byte = match self.readb() {
+                Some(b) => b,
+                None => continue,
+            };
+
+            if byte == 0xd {
+                break;
+            } else if byte == 0x8 {
+                if c == 0 {
+                    // buffer empty
+                    continue;
+                }
+                self.write("\x08 \x08");
+                c -= 1;
+                continue;
+            }
+
+            if c == buf.len() {
+                // Don't overflow the buffer
+                continue;
+            }
+
+            self.writeb(byte);
+            buf[c] = byte;
+            c += 1;
+        }
+
+        c
+    }
+
+    fn handle_interrupt(&mut self) {
+        if let Ok(b) = self.uart.read() {
+            self.console.pushb(b);
+        }
+    }
+}
+
+impl ConsoleOutput for SerialConsole {
+    fn pos(&mut self, x: usize, y: usize) {
+    }
+
+    fn writeb(&mut self, b: u8) {
+        block!(self.uart.write(b)).ok();
+    }
+
+    fn write<T: AsRef<[u8]>>(&mut self, a: T) {
+        let bytes = a.as_ref();
+        for b in bytes {
+            self.writeb(*b);
+        }
+    }
+
+    fn clear(&mut self) {
+        self.write(&[0x1b, b'[', b'2', b'J', 0x1b, b'[', b'H']);
+    }
+}
+
+impl ConsoleInput for SerialConsole {
+    fn readb(&mut self) -> Option<u8> {
+        self.console.readb()
+    }
+
+    fn read(&mut self, buf: &mut [u8]) -> usize {
+        self.console.read(buf)
+    }
+
+    fn discard(&mut self) {
+        self.console.discard()
+    }
+}
+
+pub fn get_global_console() -> &'static mut SerialConsole {
+    unsafe {
+        core::intrinsics::transmute(&mut G_CONSOLE)
+    }
+}
+
+#[interrupt]
+fn UART() {
+    get_global_console().handle_interrupt();
+}
+
+
+/* Console driver function table */
+
+/* Rust versions */
+fn rust_readb() -> Option<u8> {
+    get_global_console().readb()
+}
+
+fn rust_read(buf: &mut [u8]) -> usize {
+    get_global_console().read(buf)
+}
+
+fn rust_readline(buf: &mut [u8]) -> usize {
+    get_global_console().readline(buf)
+}
+
+fn rust_writeb(b: u8) {
+    get_global_console().writeb(b)
+}
+
+fn rust_write(a: &[u8]) {
+    get_global_console().write(a)
+}
+
+fn rust_discard() {
+    get_global_console().discard()
+}
+
+fn rust_pos(x: usize, y: usize) {
+    get_global_console().pos(x, y)
+}
+
+fn rust_clear() {
+    get_global_console().clear()
+}
+
+pub const RUST_CONSOLE_OPERATIONS: RustConsoleOperations = RustConsoleOperations {
+    readb: rust_readb,
+    read: rust_read,
+    readline: rust_readline,
+    writeb: rust_writeb,
+    write: rust_write,
+    discard: rust_discard,
+    pos: rust_pos,
+    clear: rust_clear,
+};
+
+/* C versions */
+extern "C" fn c_readb() -> i32 {
+    match rust_readb() {
+        Some(b) => b as i32,
+        None => -1,
+    }
+}
+
+extern "C" fn c_read(buf: *mut u8, len: usize) -> usize {
+    if len == 0 {
+        return 0;
+    }
+
+    let buf = unsafe { slice::from_raw_parts_mut(buf, len) };
+    rust_read(buf)
+}
+
+extern "C" fn c_readline(buf: *mut u8, len: usize) -> usize {
+    let buf = unsafe { slice::from_raw_parts_mut(buf, len) };
+    rust_readline(buf)
+}
+
+extern "C" fn c_writeb(b: u8) {
+    rust_writeb(b)
+}
+
+extern "C" fn c_write(a: *const u8, len: usize) {
+    let a = unsafe { slice::from_raw_parts(a, len) };
+    rust_write(a)
+}
+
+extern "C" fn c_discard() {
+    rust_discard()
+}
+
+extern "C" fn c_pos(x: usize, y: usize) {
+    rust_pos(x, y)
+}
+
+extern "C" fn c_clear() {
+    rust_clear()
+}
+
+pub const C_CONSOLE_OPERATIONS: CConsoleOperations = CConsoleOperations {
+    readb: c_readb,
+    read: c_read,
+    readline: c_readline,
+    writeb: c_writeb,
+    write: c_write,
+    discard: c_discard,
+    pos: c_pos,
+    clear: c_clear,
+};

diff --git a/src/svcall.rs b/src/svcall.rs
line changes: +46/-0
index 0000000..b1de777
--- /dev/null
+++ b/src/svcall.rs
@@ -0,0 +1,46 @@
+use cortex_m_rt::ExceptionFrame;
+use crate::console::ConsoleOutput;
+use crate::serial_console::get_global_console;
+use crate::util::{print_dec, print_hex_padded};
+
+global_asm!(r#"
+    .global SVCall
+    .type SVCall,function
+SVCall:
+    // Store a pointer to registers in r1
+    mrs r1, PSP
+    // find the svc number from the app stack, store it in r0
+    ldr r0, [r1, #24]
+    ldr r0, [r0, #-2]
+    and r0, 0xFF
+    // call service handler
+    b svcall_handler
+"#);
+
+#[no_mangle]
+unsafe extern "C" fn svcall_handler(svc: u8, regs: &mut ExceptionFrame) {
+    match svc {
+        0 => {
+            regs.r0 = &crate::C_DRIVER_INTERFACES as *const crate::CDriverInterfaces as u32
+        },
+        1 => {
+            regs.r0 = &crate::RUST_DRIVER_INTERFACES as *const crate::RustDriverInterfaces as u32
+        },
+        _ => {
+            let console = get_global_console();
+            console.write("unknown svcall ");
+            print_dec(svc);
+            console.write(": ");
+
+            print_hex_padded(regs.r0, 8);
+            console.write(" ");
+            print_hex_padded(regs.r1, 8);
+            console.write(" ");
+            print_hex_padded(regs.r2, 8);
+            console.write(" ");
+            print_hex_padded(regs.r3, 8);
+
+            console.write("\r\n");
+        },
+    }
+}

diff --git a/src/task.rs b/src/task.rs
line changes: +90/-0
index 0000000..0441870
--- /dev/null
+++ b/src/task.rs
@@ -0,0 +1,90 @@
+use core::ptr;
+use crate::mpu::{self, MemoryRegion, MemoryPermission, MemoryRange};
+
+extern "C" {
+    static _appram: u32;
+    static _appram_end: u32;
+    pub fn launch(entry: u32, sp: u32, data_base: u32) -> !;
+}
+
+global_asm!(r#"
+    .global launch
+    .type launch,function
+launch:
+    // Move entry address out of the way
+    mov r3, r0
+    // set up PSP and drop privileges
+    mov r0, 0x3
+    msr CONTROL, r0
+    // set stack pointer and pointer to data region info
+    mov sp, r1
+    mov r0, r1
+    // set up memory region pointer
+    mov r9, r2
+    isb
+    dsb
+    bx r3
+"#);
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+pub struct Task {
+    text: MemoryRegion,
+    data: MemoryRegion,
+    stack: MemoryRegion,
+
+    pc: u32,
+    sp: u32,
+}
+
+impl Task {
+    pub fn default_layout_full(entry: unsafe extern "C" fn() -> u32) -> Task {
+        let appram_base = unsafe { &_appram as *const u32 as u32 };
+        let stack_start = appram_base + 0x10000 + 0xe000;
+        let stack_size = 0x2000;
+        let stack_end = stack_start + stack_size;
+
+        Task {
+            text: MemoryRegion {
+                start: 0x00000000,
+                length: 0,
+                permissions: MemoryPermission::None,
+                xn: false,
+            },
+            data: MemoryRegion {
+                start: appram_base,
+                length: 0x10000,
+                permissions: MemoryPermission::RW,
+                xn: true,
+            },
+            stack: MemoryRegion {
+                start: stack_start,
+                length: stack_size,
+                permissions: MemoryPermission::RW,
+                xn: true,
+            },
+
+            pc: entry as *const () as u32,
+            sp: stack_end,
+        }
+    }
+
+    pub fn run(&self) -> ! {
+        mpu::add_region(&self.text);
+        mpu::add_region(&self.data);
+        mpu::add_region(&self.stack);
+
+        unsafe {
+            let sp = self.sp as *mut MemoryRange;
+            let sp = sp.sub(1);
+            ptr::write(sp, MemoryRange {
+                start: self.data.start,
+                length: self.data.length - self.stack.length,
+            });
+            let sp = sp as u32;
+
+            launch(self.pc, sp, self.data.start)
+        }
+    }
+
+}

diff --git a/src/util.rs b/src/util.rs
line changes: +120/-0
index 0000000..8af31da
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,120 @@
+#![allow(dead_code)]
+use crate::serial_console::get_global_console;
+use crate::console::ConsoleOutput;
+
+pub fn dec_digit(n: u32) -> u8 {
+    b'0' + (n % 10) as u8
+}
+
+pub fn hex_digit(n: u32) -> u8 {
+    let mut n = (n & 0xF) as u8;
+
+    if n > 9 {
+        n += 7;
+    }
+
+    b'0' + n
+}
+
+#[test]
+fn test_hex_digits() {
+    assert!(hex_digit(0) == b'0');
+    assert!(hex_digit(10) == b'A');
+}
+
+pub fn format_int_dec(n: u32, buf: &mut [u8]) -> usize {
+    if n == 0 {
+        buf[buf.len() - 1] = b'0';
+        return 1;
+    }
+
+    let mut n = n;
+    let mut c = buf.len() - 1;
+    loop {
+        buf[c] = dec_digit(n);
+        n /= 10;
+        if c == 0 || n == 0 {
+            break;
+        }
+        c -= 1;
+    }
+
+    buf.len() - c
+}
+
+#[test]
+fn test_format_int_dec() {
+    let mut buf = [0u8; 9];
+
+    let n = format_int_dec(0, &mut buf);
+    assert!(n == 1);
+    assert!(buf[8] == b'0');
+
+    let n = format_int_dec(255, &mut buf);
+    assert!(n == 3);
+    assert!(buf[6..] == [b'2', b'5', b'5']);
+
+    let n = format_int_dec(0x20000000, &mut buf);
+    assert!(n == 9);
+    assert!(buf == "536870912".as_bytes());
+}
+
+pub fn format_int_hex(n: u32, buf: &mut[u8]) -> usize {
+    if n == 0 {
+        buf[buf.len() - 1] = b'0';
+        return 1;
+    }
+
+    let mut n = n;
+    let mut c = buf.len() - 1;
+    loop {
+        buf[c] = hex_digit(n);
+        n >>= 4;
+        if c == 0 || n == 0 {
+            break;
+        }
+        c -= 1;
+    }
+
+    buf.len() - c
+}
+
+#[test]
+fn test_format_int_hex() {
+    let mut buf = [0u8; 8];
+
+    let n = format_int_hex(0, &mut buf);
+    assert!(n == 1);
+    assert!(buf[7] == b'0');
+
+    let n = format_int_hex(0x20000000, &mut buf);
+    assert!(n == 8);
+    assert!(buf == "20000000".as_bytes());
+}
+
+pub fn print_hex<T: Into<u32>>(v: T) {
+    let v: u32 = v.into();
+    let console = get_global_console();
+    let mut buf = [0u8; 8];
+
+    let n = format_int_hex(v, &mut buf);
+    console.write(&buf[8 - n..]);
+}
+
+pub fn print_hex_padded<T: Into<u32>>(v: T, digits: usize) {
+    let v: u32 = v.into();
+    let console = get_global_console();
+    let mut buf = [b'0'; 8];
+
+    format_int_hex(v, &mut buf);
+    console.write(&buf[8 - digits..]);
+}
+
+pub fn print_dec<T: Into<u32>>(v: T) {
+    let v: u32 = v.into();
+    let console = get_global_console();
+    let mut buf = [0u8; 10];
+
+    let n = format_int_dec(v, &mut buf);
+    console.write(&buf[10 - n..]);
+}

diff --git a/src/video/font.rs b/src/video/font.rs
line changes: +2818/-0
index 0000000..030baab
--- /dev/null
+++ b/src/video/font.rs
@@ -0,0 +1,2818 @@
+pub static FONT: [[u8; 8]; 256] = [
+    [
+        0b10101010,
+        0b01010101,
+        0b10101010,
+        0b01010101,
+        0b10101010,
+        0b01010101,
+        0b10101010,
+        0b01010101,
+
+    ],
+    [
+        0b01010101,
+        0b10101010,
+        0b01010101,
+        0b10101010,
+        0b01010101,
+        0b10101010,
+        0b01010101,
+        0b10101010,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00010000,
+        0b00111000,
+        0b00111000,
+        0b00010000,
+        0b00010000,
+        0b00000000,
+        0b00010000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00101000,
+        0b00101000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00100100,
+        0b01111110,
+        0b00100100,
+        0b00100100,
+        0b01111110,
+        0b00100100,
+        0b00000000,
+
+    ],
+    [
+        0b00010000,
+        0b00111000,
+        0b01010000,
+        0b00111000,
+        0b00010100,
+        0b00111000,
+        0b00010000,
+        0b00000000,
+
+    ],
+    [
+        0b01000010,
+        0b10100100,
+        0b01001000,
+        0b00010000,
+        0b00100100,
+        0b01001010,
+        0b10000100,
+        0b00000000,
+
+    ],
+    [
+        0b00010000,
+        0b00101000,
+        0b00010000,
+        0b00101000,
+        0b01001010,
+        0b01000100,
+        0b00111010,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00010000,
+        0b00010000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00001000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00001000,
+        0b00000000,
+
+    ],
+    [
+        0b00010000,
+        0b00001000,
+        0b00001000,
+        0b00001000,
+        0b00001000,
+        0b00001000,
+        0b00010000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00010000,
+        0b01010100,
+        0b00111000,
+        0b01010100,
+        0b00010000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00010000,
+        0b00010000,
+        0b01111100,
+        0b00010000,
+        0b00010000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00110000,
+        0b00110000,
+        0b00010000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b01111100,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00110000,
+        0b00110000,
+        0b00000000,
+
+    ],
+    [
+        0b00000100,
+        0b00001000,
+        0b00001000,
+        0b00010000,
+        0b00010000,
+        0b00100000,
+        0b00100000,
+        0b01000000,
+
+    ],
+    [
+        0b00111000,
+        0b01000100,
+        0b01001100,
+        0b01010100,
+        0b01100100,
+        0b01000100,
+        0b00111000,
+        0b00000000,
+
+    ],
+    [
+        0b00010000,
+        0b00110000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00111000,
+        0b00000000,
+
+    ],
+    [
+        0b00111000,
+        0b01000100,
+        0b00000100,
+        0b00001000,
+        0b00010000,
+        0b00100000,
+        0b01111100,
+        0b00000000,
+
+    ],
+    [
+        0b00111000,
+        0b01000100,
+        0b00000100,
+        0b00011000,
+        0b00000100,
+        0b01000100,
+        0b00111000,
+        0b00000000,
+
+    ],
+    [
+        0b00011000,
+        0b00101000,
+        0b01001000,
+        0b10001000,
+        0b11111100,
+        0b00001000,
+        0b00001000,
+        0b00000000,
+
+    ],
+    [
+        0b01111100,
+        0b01000000,
+        0b01000000,
+        0b01111000,
+        0b00000100,
+        0b01000100,
+        0b00111000,
+        0b00000000,
+
+    ],
+    [
+        0b00111000,
+        0b01000100,
+        0b01000000,
+        0b01111000,
+        0b01000100,
+        0b01000100,
+        0b00111000,
+        0b00000000,
+
+    ],
+    [
+        0b01111100,
+        0b00000100,
+        0b00001000,
+        0b00001000,
+        0b00010000,
+        0b00010000,
+        0b00100000,
+        0b00000000,
+
+    ],
+    [
+        0b00111000,
+        0b01000100,
+        0b01000100,
+        0b00111000,
+        0b01000100,
+        0b01000100,
+        0b00111000,
+        0b00000000,
+
+    ],
+    [
+        0b00111000,
+        0b01000100,
+        0b01000100,
+        0b00111100,
+        0b00000100,
+        0b01000100,
+        0b00111000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00110000,
+        0b00110000,
+        0b00000000,
+        0b00000000,
+        0b00110000,
+        0b00110000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00110000,
+        0b00110000,
+        0b00000000,
+        0b00000000,
+        0b00110000,
+        0b00110000,
+        0b00010000,
+
+    ],
+    [
+        0b00000100,
+        0b00001000,
+        0b00010000,
+        0b00100000,
+        0b00010000,
+        0b00001000,
+        0b00000100,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b01111100,
+        0b00000000,
+        0b00000000,
+        0b01111100,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b01000000,
+        0b00100000,
+        0b00010000,
+        0b00001000,
+        0b00010000,
+        0b00100000,
+        0b01000000,
+        0b00000000,
+
+    ],
+    [
+        0b00010000,
+        0b00101000,
+        0b00001000,
+        0b00010000,
+        0b00010000,
+        0b00000000,
+        0b00010000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b01111000,
+        0b10000100,
+        0b10110100,
+        0b10110100,
+        0b10001100,
+        0b01100000,
+        0b00000000,
+
+    ],
+    [
+        0b00111000,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b01111100,
+        0b01000100,
+        0b01000100,
+        0b00000000,
+
+    ],
+    [
+        0b01111000,
+        0b01000100,
+        0b01000100,
+        0b01111000,
+        0b01000100,
+        0b01000100,
+        0b01111000,
+        0b00000000,
+
+    ],
+    [
+        0b00111000,
+        0b01000100,
+        0b01000000,
+        0b01000000,
+        0b01000000,
+        0b01000100,
+        0b00111000,
+        0b00000000,
+
+    ],
+    [
+        0b01111000,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b01111000,
+        0b00000000,
+
+    ],
+    [
+        0b01111100,
+        0b01000000,
+        0b01000000,
+        0b01110000,
+        0b01000000,
+        0b01000000,
+        0b01111100,
+        0b00000000,
+
+    ],
+    [
+        0b01111100,
+        0b01000000,
+        0b01000000,
+        0b01110000,
+        0b01000000,
+        0b01000000,
+        0b01000000,
+        0b00000000,
+
+    ],
+    [
+        0b00111000,
+        0b01000100,
+        0b01000000,
+        0b01000000,
+        0b01001100,
+        0b01000100,
+        0b00111000,
+        0b00000000,
+
+    ],
+    [
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b01111100,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b00000000,
+
+    ],
+    [
+        0b01111100,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b01111100,
+        0b00000000,
+
+    ],
+    [
+        0b00011100,
+        0b00001000,
+        0b00001000,
+        0b00001000,
+        0b00001000,
+        0b01001000,
+        0b00110000,
+        0b00000000,
+
+    ],
+    [
+        0b01000100,
+        0b01001000,
+        0b01010000,
+        0b01100000,
+        0b01010000,
+        0b01001000,
+        0b01000100,
+        0b00000000,
+
+    ],
+    [
+        0b01000000,
+        0b01000000,
+        0b01000000,
+        0b01000000,
+        0b01000000,
+        0b01000000,
+        0b01111100,
+        0b00000000,
+
+    ],
+    [
+        0b01000100,
+        0b01101100,
+        0b01010100,
+        0b01010100,
+        0b01010100,
+        0b01010100,
+        0b01010100,
+        0b00000000,
+
+    ],
+    [
+        0b01000100,
+        0b01100100,
+        0b01010100,
+        0b01001100,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b00000000,
+
+    ],
+    [
+        0b00111000,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b00111000,
+        0b00000000,
+
+    ],
+    [
+        0b01111000,
+        0b01000100,
+        0b01000100,
+        0b01111000,
+        0b01000000,
+        0b01000000,
+        0b01000000,
+        0b00000000,
+
+    ],
+    [
+        0b00111000,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b01010100,
+        0b01001100,
+        0b00111110,
+        0b00000000,
+
+    ],
+    [
+        0b01111000,
+        0b01000100,
+        0b01000100,
+        0b01111000,
+        0b01010000,
+        0b01001000,
+        0b01000100,
+        0b00000000,
+
+    ],
+    [
+        0b00111000,
+        0b01000100,
+        0b01000000,
+        0b00111000,
+        0b00000100,
+        0b01000100,
+        0b00111000,
+        0b00000000,
+
+    ],
+    [
+        0b01111100,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00000000,
+
+    ],
+    [
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b00111000,
+        0b00000000,
+
+    ],
+    [
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b01000100,
+        0b00101000,
+        0b00101000,
+        0b00010000,
+        0b00000000,
+
+    ],
+    [
+        0b10000010,
+        0b10000010,
+        0b10010010,
+        0b01010100,
+        0b01010100,
+        0b01010100,
+        0b00101000,
+        0b00000000,
+
+    ],
+    [
+        0b01000100,
+        0b01000100,
+        0b00101000,
+        0b00010000,
+        0b00101000,
+        0b01000100,
+        0b01000100,
+        0b00000000,
+
+    ],
+    [
+        0b01000100,
+        0b01000100,
+        0b00101000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00000000,
+
+    ],
+    [
+        0b01111100,
+        0b00000100,
+        0b00001000,
+        0b00010000,
+        0b00100000,
+        0b01000000,
+        0b01111100,
+        0b00000000,
+
+    ],
+    [
+        0b00011100,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00011100,
+        0b00000000,
+
+    ],
+    [
+        0b01000000,
+        0b01000000,
+        0b00100000,
+        0b00100000,
+        0b00010000,
+        0b00010000,
+        0b00001000,
+        0b00001000,
+
+    ],
+    [
+        0b01110000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b01110000,
+        0b00000000,
+
+    ],
+    [
+        0b00010000,
+        0b00101000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b01111100,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b01000000,
+        0b00100000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00111000,
+        0b01001000,
+        0b01001000,
+        0b00110100,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b01000000,
+        0b01000000,
+        0b01110000,
+        0b01001000,
+        0b01001000,
+        0b01110000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00110000,
+        0b01000000,
+        0b01000000,
+        0b00110000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00001000,
+        0b00001000,
+        0b00111000,
+        0b01001000,
+        0b01001000,
+        0b00111000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00110000,
+        0b01111000,
+        0b01000000,
+        0b00110000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00010000,
+        0b00100000,
+        0b00100000,
+        0b01110000,
+        0b00100000,
+        0b00100000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00110000,
+        0b01001000,
+        0b01001000,
+        0b00111000,
+        0b00001000,
+        0b00110000,
+
+    ],
+    [
+        0b00000000,
+        0b01000000,
+        0b01000000,
+        0b01110000,
+        0b01001000,
+        0b01001000,
+        0b01001000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00100000,
+        0b00000000,
+        0b00100000,
+        0b00100000,
+        0b00100000,
+        0b00100000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00010000,
+        0b00000000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b01010000,
+        0b00100000,
+
+    ],
+    [
+        0b00000000,
+        0b01000000,
+        0b01000000,
+        0b01010000,
+        0b01100000,
+        0b01100000,
+        0b01010000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00100000,
+        0b00100000,
+        0b00100000,
+        0b00100000,
+        0b00100000,
+        0b00100000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b01101000,
+        0b01010100,
+        0b01010100,
+        0b01010100,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b01110000,
+        0b01001000,
+        0b01001000,
+        0b01001000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00110000,
+        0b01001000,
+        0b01001000,
+        0b00110000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b01110000,
+        0b01001000,
+        0b01001000,
+        0b01110000,
+        0b01000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00110000,
+        0b01001000,
+        0b01001000,
+        0b00111000,
+        0b00001100,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b01010000,
+        0b01101000,
+        0b01000000,
+        0b01000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b01110000,
+        0b01000000,
+        0b00010000,
+        0b01110000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00100000,
+        0b01110000,
+        0b00100000,
+        0b00100000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b01001000,
+        0b01001000,
+        0b01001000,
+        0b00110000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b01010000,
+        0b01010000,
+        0b01010000,
+        0b00100000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b01000100,
+        0b01000100,
+        0b01010100,
+        0b00101000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b01001000,
+        0b00110000,
+        0b00110000,
+        0b01001000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b01001000,
+        0b01001000,
+        0b00111000,
+        0b00001000,
+        0b00110000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b01111000,
+        0b00010000,
+        0b00100000,
+        0b01111000,
+        0b00000000,
+
+    ],
+    [
+        0b00000100,
+        0b00001000,
+        0b00001000,
+        0b00010000,
+        0b00001000,
+        0b00001000,
+        0b00000100,
+        0b00000000,
+
+    ],
+    [
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00010000,
+        0b00000000,
+
+    ],
+    [
+        0b01000000,
+        0b00100000,
+        0b00100000,
+        0b00010000,
+        0b00100000,
+        0b00100000,
+        0b01000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00100000,
+        0b01010100,
+        0b00001000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00011111,
+        0b00100001,
+        0b01010101,
+        0b10001001,
+        0b01010101,
+        0b00100001,
+        0b00011111,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+    [
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+        0b00000000,
+
+    ],
+];

diff --git a/src/video/gen.rs b/src/video/gen.rs
line changes: +343/-0
index 0000000..4318751
--- /dev/null
+++ b/src/video/gen.rs
@@ -0,0 +1,343 @@
+use cortex_m::asm;
+use cortex_m::peripheral::NVIC;
+use atsam3xa::{TC0};
+use atsam3xa::interrupt;
+use embedded_hal::digital::OutputPin;
+use atsam3xa_hal::prelude::*;
+use atsam3xa_hal::pmc::PMC;
+use atsam3xa_hal::pio::{self, PIOB};
+
+use crate::video::mem::{ROWS, COLS, VIDEO_RAM};
+use crate::video::font::FONT;
+
+#[allow(dead_code)]
+enum VideoStage {
+    EqualizingHead,
+    FieldSync,
+    Equalizing,
+    BlankLine,
+    Line,
+    HalfLineTop,
+    HalfLineBottom,
+}
+
+struct GenOp {
+    stage: VideoStage,
+    count: usize,
+    call_every: usize,
+}
+
+const OPERATIONS: [GenOp; 5] = [
+    // EqualizingHead sets TC period to half a frame for the vblank period
+    GenOp { stage: VideoStage::EqualizingHead, count: 6, call_every: 6 },
+    GenOp { stage: VideoStage::FieldSync, count: 6, call_every: 6 },
+    GenOp { stage: VideoStage::Equalizing, count: 6, call_every: 6 },
+    // BlankLine sets TC period back to a full frame
+    GenOp { stage: VideoStage::BlankLine, count: 11, call_every: 11 },
+    // 242 visible lines(?)
+    GenOp { stage: VideoStage::Line, count: 242, call_every: 1 },
+    /*
+    // HalfLineTop sets TC period back to half a frame before entering vblank on second field
+    GenOp { stage: VideoStage::HalfLineTop, count: 1, call_every: 1 },
+    GenOp { stage: VideoStage::Equalizing, count: 6, call_every: 6 },
+    GenOp { stage: VideoStage::FieldSync, count: 6, call_every: 6 },
+    GenOp { stage: VideoStage::Equalizing, count: 5, call_every: 5 },
+    // HalfLineBottom is the last equalizing cycle, but sets the period back to a full frame
+    GenOp { stage: VideoStage::HalfLineBottom, count: 1, call_every: 1 },
+    // Another set of blank lines (this also resets the cycle, but it doesn't matter)
+    GenOp { stage: VideoStage::BlankLine, count: 11, call_every: 11 },
+    // And 242 lines on the bottom field
+    GenOp { stage: VideoStage::Line, count: 232, call_every: 1 },
+    GenOp { stage: VideoStage::BlankLine, count: 9, call_every: 10 },
+    */
+];
+
+const fn us_to_ticks(us: f64) -> u32 {
+    (TICKS_PER_SECOND as f64 * (us * 1e-6)) as u32
+}
+
+const TICKS_PER_SECOND: u32 = 42_000_000;
+const TICKS_PER_FRAME: u32 = TICKS_PER_SECOND / 30;
+const TICKS_PER_LINE: u32 = TICKS_PER_FRAME / 525;
+const TICKS_PER_HALF_LINE: u32 = TICKS_PER_LINE / 2;
+const PRE_EQ_PULSE: u32 = us_to_ticks(2.3);
+const SYNC_PULSE: u32 = us_to_ticks(4.7);
+const FIELD_SYNC_PULSE: u32 = TICKS_PER_HALF_LINE - SYNC_PULSE;
+const BLANK_TIME: u32 = us_to_ticks(9.2);
+
+const LEFT_MARGIN: u32 = us_to_ticks(3.1);
+
+const HRES: usize = 320;
+const VRES: usize = 240;
+const VSCALE: usize = (240 / VRES);
+
+struct VideoGenerator {
+    tc: TC0,
+    // luma isn't used directly because the pixel loop is assembly
+    #[allow(dead_code)]
+    luma: pio::Pin,
+}
+
+extern "C" {
+    fn pixel_pusher(mem: *const u8);
+    fn write_scanline(linedata: *const u8, char_mem: *const u8, font: *const u8);
+}
+
+global_asm!(r#"
+    .type pixel_pusher,function
+pixel_pusher:
+    // ARGUMENTS
+    // r0 = pointer to scanline data
+    // WORK REGISTERS
+    // r1 = PIOB.26 ODSR bit band address (0x43c20768)
+    // r2 = end address
+    // r3 = work byte
+    mov r1, 0x0768
+    movt r1, 0x43c2
+    add r2, r0, #40
+0:
+    ldr r3, [r0], #4
+
+.rept 31
+    ror r3, r3, 31
+    str r3, [r1]
+    nop
+    nop
+    nop
+    nop
+    nop
+    nop
+    nop
+    nop
+.endr
+    ror r3, r3, 31
+    str r3, [r1]
+
+    cmp r0, r2
+    bne 0b
+
+    // Set output to low at end of line
+    mov r3, #0
+    str r3, [r1]
+
+    bx lr
+
+    .type write_scanline,function
+write_scanline:
+    // ARGUMENTS
+    // r0 = scanline data address
+    // r1 = character memory address
+    // r2 = font memory address (modulo pixel row)
+    // WORK REGISTERS
+    // r3 = byte counter
+    // r4 = 4x character
+    // r5 = output word
+    // r6 = temporary char
+    push {r4-r6}
+    mov r3, 36
+0:
+    mov r5, 0
+    ldr r4, [r1, r3]
+
+    ubfx r6, r4, #0, #8
+    ldrb r6, [r2, r6, lsl #3]
+    orr r5, r5, r6, lsl #24
+
+    ubfx r6, r4, #8, #8
+    ldrb r6, [r2, r6, lsl #3]
+    orr r5, r5, r6, lsl #16
+
+    ubfx r6, r4, #16, #8
+    ldrb r6, [r2, r6, lsl #3]
+    orr r5, r5, r6, lsl #8
+
+    ubfx r6, r4, #24, #8
+    ldrb r6, [r2, r6, lsl #3]
+    orr r5, r5, r6
+
+    str r5, [r0, r3]
+    cbz r3, 1f
+    sub r3, r3, 4
+    b 0b
+
+1:
+    // These nops stabilize the pixel timing somehow.  May require adjustment
+    // if the above changes.
+    nop
+    nop
+    pop {r4-r6}
+    bx lr
+"#);
+
+impl VideoGenerator {
+    fn new(tc: TC0, luma: pio::Pin) -> VideoGenerator {
+        let mut c: u8 = 0;
+        for y in 0..ROWS {
+            for x in 0..COLS {
+                unsafe { VIDEO_RAM.set_cell(x, y, c) };
+                c = c + 1;
+            }
+        }
+
+        VideoGenerator {
+            tc,
+            luma,
+        }
+    }
+
+    fn execute(&mut self, stage: &VideoStage, count: usize) {
+        match stage {
+            VideoStage::EqualizingHead => {
+                self.pre_eq();
+                self.half_line_period();
+            },
+            VideoStage::Equalizing => {
+                self.pre_eq();
+            },
+            VideoStage::FieldSync => {
+                self.field_sync();
+            },
+            VideoStage::BlankLine => {
+                self.line_sync();
+                self.full_line_period();
+            },
+            VideoStage::Line => {
+                if count >= VRES * VSCALE {
+                    return;
+                }
+
+                let pixel_line = count / VSCALE;
+                let char_row = pixel_line / 8;
+                let char_line = pixel_line % 8;
+                let mut linedata : [u8; COLS] = unsafe { core::mem::uninitialized() };
+
+                unsafe {
+                    let char_mem = VIDEO_RAM.row_address(char_row);
+                    // We add the char_line offset here since it's constant for every character
+                    let font_mem = (&FONT[0] as *const u8).offset(char_line as isize);
+                    write_scanline(&linedata as *const u8, char_mem, font_mem);
+                }
+
+                /*
+                let tf = self.tc.cv0.read().cv().bits();
+                if count == 0 {
+                    use crate::console::ConsoleOutput;
+                    crate::util::print_dec(tf as u32);
+                    crate::serial_console::get_global_console().write("\n");
+                }
+                */
+
+                // wait for the sync pulse to finish
+                while self.tc.cv0.read().cv().bits() < BLANK_TIME + LEFT_MARGIN {}
+
+                unsafe {
+                    pixel_pusher(&linedata as *const u8);
+                }
+            },
+            VideoStage::HalfLineTop => {
+                self.half_line_period();
+            },
+            VideoStage::HalfLineBottom => {
+                self.full_line_period();
+            }
+        };
+    }
+
+    fn full_line_period(&self) {
+        self.tc.rc0.write(|w| unsafe { w.bits(TICKS_PER_LINE) });
+    }
+
+    fn half_line_period(&self) {
+        self.tc.rc0.write(|w| unsafe { w.bits(TICKS_PER_HALF_LINE) });
+    }
+
+    fn pre_eq(&self) {
+        // PRE_EQ_PULSE cycles low, then high
+        self.tc.ra0.write(|w| unsafe { w.bits(PRE_EQ_PULSE) });
+    }
+
+    fn field_sync(&self) {
+        // FIELD_SYNC_PULSE cycles low, then high
+        self.tc.ra0.write(|w| unsafe { w.bits(FIELD_SYNC_PULSE) });
+    }
+
+    fn line_sync(&self) {
+        // SYNC_PULSE cycles low, then high
+        self.tc.ra0.write(|w| unsafe { w.bits(SYNC_PULSE) });
+    }
+}
+
+static mut VIDEO_GENERATOR: Option<VideoGenerator> = None;
+
+#[interrupt]
+fn TC0() {
+    static mut OP: usize = 0;
+    static mut COUNT: usize = 0;
+
+    let vg = unsafe { VIDEO_GENERATOR.as_mut().unwrap() };
+    // The status register is irrelevant to us, but we must read it to clear
+    // the interrupt.
+    vg.tc.sr0.read().bits();
+
+    let o = &OPERATIONS[*OP];
+    if *COUNT % o.call_every == 0 {
+        vg.execute(&o.stage, *COUNT);
+    }
+
+    *COUNT += 1;
+    if *COUNT == o.count {
+        *OP = (*OP + 1) % OPERATIONS.len();
+        *COUNT = 0;
+    }
+}
+
+pub fn start(tc: TC0, nvic: &mut NVIC, pmc: &PMC, pio_b: &PIOB) {
+    // Enable peripheral clock
+    pmc.enable_peripheral_clock(PeripheralID::TC0);
+
+    // Set NVIC priority for TC0 interrupt
+    unsafe { nvic.set_priority(atsam3xa::Interrupt::TC0, 0) };
+
+    // Enable TC0 interrupt
+    nvic.enable(atsam3xa::Interrupt::TC0);
+
+    // Configure SYNC output pin on TIOA0
+    let tioa0 = pio_b.get_pin(25);
+    tioa0.peripheral_mode(PeripheralMultiplex::B);
+
+    // set wave mode, up RC mode, and use timer clock 1 (MCLK/2)
+    unsafe {
+        tc.cmr0.cmr0_wave_eq_1.write(|w| {
+            w.wave().set_bit()
+             .wavsel().up_rc()
+             .acpa().set()
+             .acpc().clear()
+             .tcclks().timer_clock1()
+        });
+    }
+    // set RC value (wait one line period before starting for no particular reason)
+    tc.rc0.write(|w| unsafe { w.rc().bits(TICKS_PER_LINE) });
+    // enable interrupt on RC compare
+    tc.ier0.write(|w| w.cpcs().set_bit());
+
+    // Get the digital pin for B/W output
+    let mut luma = pio_b.get_pin(26);
+    luma.output_mode();
+    luma.set_synchronous_mode(true);
+    luma.set_low();
+
+    unsafe {
+        // Store the global VideoGenerator object
+        VIDEO_GENERATOR = Some(VideoGenerator::new(tc, luma));
+    }
+
+    asm::dsb();
+
+    // enable TC0 and start!
+    unsafe {
+        VIDEO_GENERATOR.as_mut().unwrap().tc.ccr0.write(|w| {
+            w.clken().set_bit()
+             .swtrg().set_bit()
+        });
+    }
+}

diff --git a/src/video/mem.rs b/src/video/mem.rs
line changes: +30/-0
index 0000000..d015662
--- /dev/null
+++ b/src/video/mem.rs
@@ -0,0 +1,30 @@
+pub const ROWS: usize = 30;
+pub const COLS: usize = 40;
+pub const CHAR_RAM_SIZE: usize = ROWS * COLS;
+
+pub struct VideoRam {
+    chars: [u8; CHAR_RAM_SIZE],
+}
+
+impl VideoRam {
+    pub const fn new() -> VideoRam {
+        VideoRam {
+            chars: [0; CHAR_RAM_SIZE],
+        }
+    }
+
+    pub fn row_address(&self, y: usize) -> *const u8 {
+        &self.chars[y * COLS] as *const u8
+    }
+
+    pub fn set_cell(&mut self, x: usize, y: usize, c: u8) {
+        self.chars[x + y * COLS] = c;
+    }
+
+    pub fn get_cell(&self, x: usize, y: usize) -> u8 {
+        self.chars[x + y * COLS]
+    }
+}
+
+#[link_section = ".kgbss"]
+pub static mut VIDEO_RAM: VideoRam = VideoRam::new();

diff --git a/src/video/mod.rs b/src/video/mod.rs
line changes: +3/-0
index 0000000..7926ec1
--- /dev/null
+++ b/src/video/mod.rs
@@ -0,0 +1,3 @@
+pub mod gen;
+pub mod mem;
+pub mod font;

diff --git a/src/video/png2font.pl b/src/video/png2font.pl
line changes: +34/-0
index 0000000..bbd1c92
--- /dev/null
+++ b/src/video/png2font.pl
@@ -0,0 +1,34 @@
+#!/usr/bin/perl
+# Input must be 128x128 B/W PNG
+use Image::Magick;
+use warnings;
+use strict;
+
+sub array_split {
+    my $by = shift;
+    my @out;
+    for (my $i = 0; $i < @_; $i += $by) {
+        push @out, [@_[$i..($i + $by - 1)]];
+    }
+    return @out;
+}
+
+my $image = Image::Magick->new;
+$image->Read($ARGV[0]);
+
+print qq{pub static FONT: [[u8; 8]; 256] = [\n};
+for my $y (0..15) {
+    for my $x (0..15) {
+        my @pixels = map {
+            $_->[0];
+        } array_split(4, $image->GetPixels(x => $x * 8, y => $y * 8, width => 8, height => 8));
+        my @rows = array_split(8, @pixels);
+        print qq{    [\n};
+        for my $r (@rows) {
+            my @pv = map { $_ > 0 ? '1' : '0' } @$r;
+            print '        0b', @pv, ",\n";
+        }
+        print qq{\n    ],\n};
+    }
+}
+print qq{];\n};