Index: emu/all.h
===================================================================
--- emu/all.h	(revision 4f967e8eb19a1addd39ec92272705cffc64c374d)
+++ emu/all.h	(revision 5475ecffc7e57c236384c2d970e1fea0538c9460)
@@ -111,4 +111,6 @@
 extern void lcd_write(uint32_t off, int32_t sz, uint32_t val);
 
+extern void lcd_sdl(void);
+
 extern void ser_init(void);
 extern void ser_quit(void);
Index: emu/lcd.c
===================================================================
--- emu/lcd.c	(revision 4f967e8eb19a1addd39ec92272705cffc64c374d)
+++ emu/lcd.c	(revision 5475ecffc7e57c236384c2d970e1fea0538c9460)
@@ -22,9 +22,330 @@
 #define ver3(...) _ver(lcd_verbose, 2, __VA_ARGS__)
 
+#define WIN_W (1634 * 2 / 3)
+#define WIN_H (342 * 2 / 3)
+
+#define CON_W 86
+#define CON_H 9
+
+#define CON_BGR 0x00000000
+#define CON_BEL 0x00808080
+#define CON_CUR 0x00e87000
+#define CON_FGR ((SDL_Color){ .r = 255, .b = 255, .g = 255, .a = 255 })
+
+#define BEL_CYC 10000
+
+#define	G_INIT		0x40
+#define	G_MWRITE	0x42
+#define	G_MREAD		0x43
+#define	G_SETSAD	0x44
+#define	G_CRSWR		0x46
+#define	G_CRSRD		0x47
+#define	G_CRSMRT	0x4C
+#define	G_CRSMLT	0x4D
+#define	G_CRSMUP	0x4E
+#define	G_CRSMDN	0x4F
+#define	G_ERASE		0x52
+#define	G_SLEEP		0x53
+#define	G_DSPOFF	0x58
+#define	G_DSPON		0x59
+#define	G_HSCRL		0x5A
+#define	G_OVRLAY	0x5B
+#define	G_CGRAM		0x5C
+#define	G_CRSFRM	0x5D
+
+static uint8_t mem[CON_H][CON_W + 1];
+
+static SDL_Window *win;
+static SDL_Renderer *ren;
+static SDL_atomic_t frame;
+
+static uint32_t render = 0;
+
+static TTF_Font *fon;
+static int32_t fon_w, fon_h;
+
+static int32_t sur_w, sur_h;
+static SDL_Surface *sur;
+
+static int32_t cur_x = 0, cur_y = 0;
+static int32_t bel = 0;
+
+static int32_t current_op = 0x00;
+static uint32_t cur_ad = 0;
+static int32_t cur_ad_c = 0;
+
+static uint32_t last_val = 0x00;
+static uint32_t last_off = 0;
+static int32_t last_sz = -1;
+static int32_t mult_val_c = 0;
+
+static void (*cursdir)(void);
+
 int32_t lcd_verbose = 0;
 
+static void scroll(void)
+{
+	memmove(mem, mem + 1, (CON_H - 1) * (CON_W + 1));
+	memset(mem + (CON_H - 1), ' ', CON_W);
+}
+
+static void forw(void)
+{
+	if (cur_x < CON_W - 1) {
+		++cur_x;
+		return;
+	}
+
+	if (cur_y == CON_H - 1) {
+		cur_x = 0;
+		scroll();
+		return;
+	}
+
+	cur_x = 0;
+	++cur_y;
+}
+
+static void back(void)
+{
+	if (cur_x > 0) {
+		--cur_x;
+		return;
+	}
+
+	if (cur_y == 0) {
+		return;
+	}
+
+	cur_x = CON_W - 1;
+	--cur_y;
+}
+
+static void up(void)
+{
+	if (cur_y > 0) {
+		--cur_y;
+		return;
+	}
+
+	// TODO: What happens when cursor out of bounds
+}
+
+static void down(void)
+{
+	if (cur_y < CON_H - 1) {
+		++cur_y;
+		return;
+	}
+
+	// TODO: What happens when cursor out of bounds
+}
+
+static void mvcur(uint32_t cur_addr) {
+	uint32_t n_cur_x = cur_addr % 85;
+	uint32_t n_cur_y = cur_addr / 85;
+
+	ver2("lcd cur x %u\n", n_cur_x);
+	ver2("lcd cur y %u\n", n_cur_y);
+
+	if (n_cur_x < CON_W - 1) {
+		cur_x = (int32_t) n_cur_x;
+	}
+	else {
+		err("invalid x cursor pos in lcd %u:%d", n_cur_x, CON_W);
+	}
+
+	if (n_cur_y < CON_H - 1) {
+		cur_y = (int32_t) n_cur_y;
+	}
+	else {
+		cur_x = 0;
+		cur_y = 9;
+		err("invalid y cursor pos in lcd %u:%d", n_cur_y, CON_H);
+	}
+}
+
+static void echo(uint8_t c)
+{
+	if (c >127) {
+		return;
+	}
+	else if (c < 32) {
+		switch (c) {
+		case '\r':
+			cur_x = 0;
+			break;
+
+		case '\n':
+			down();
+			break;
+
+		case '\b':
+			back();
+			break;
+
+		case '\a':
+			bel = BEL_CYC;
+			break;
+
+		case 0:
+			mem[cur_y][cur_x] = ' ';
+			(*cursdir)();
+			break;
+
+		case 28:
+			// TODO
+			echo('^');
+			echo((uint8_t)(c + '@'));
+			break;
+
+		default:
+			echo('^');
+			echo((uint8_t)(c + '@'));
+			ver2("lcd default case echo %u", c);
+			return;
+		}
+	}
+	else {
+		mem[cur_y][cur_x] = c;
+		(*cursdir)();
+	}
+
+	if (render) {
+		SDL_AtomicAdd(&frame, 1);
+	}
+}
+
+static void clear_mem(void) {
+	for (int32_t y = 0; y < CON_H; ++y) {
+		for (int32_t x = 0; x < CON_W; ++x) {
+			mem[y][x] = ' ';
+		}
+
+		mem[y][CON_W] = 0;
+	}
+}
+
+void lcd_sdl(void)
+{
+	ver3("lcd_sdl()");
+
+	static int32_t last = 0;
+	int32_t now = SDL_AtomicGet(&frame);
+
+	if (last == now) {
+		ver3("no update");
+		return;
+	}
+
+	last = now;
+
+	if (SDL_FillRect(sur, NULL, bel == 0 ? CON_BGR : CON_BEL) < 0) {
+		fail("SDL_FillRect() failed: %s", SDL_GetError());
+	}
+
+	if (SDL_FillRect(sur, &(SDL_Rect){
+		.x = cur_x * fon_w,
+		.y = cur_y * fon_h,
+		.w = fon_w,
+		.h = fon_h
+	}, CON_CUR) < 0) {
+		fail("SDL_FillRect() failed: %s", SDL_GetError());
+	}
+
+	for (int32_t y = 0; y < CON_H; ++y) {
+		char line[CON_W + 1];
+
+		if (SDL_LockMutex(cpu_mutex) < 0) {
+			fail("SDL_LockMutex() failed: %s", SDL_GetError());
+		}
+
+		memcpy(line, mem[y], CON_W + 1);
+
+		if (SDL_UnlockMutex(cpu_mutex) < 0) {
+			fail("SDL_UnlockMutex() failed: %s", SDL_GetError());
+		}
+
+		SDL_Surface *lin = TTF_RenderText_Blended(fon, line, CON_FGR);
+
+		if (lin == NULL) {
+			fail("TTF_RenderText_Blended() failed: %s", TTF_GetError());
+		}
+
+		if (SDL_BlitSurface(lin, NULL, sur, &(SDL_Rect){
+			.x = 0,
+			.y = y * fon_h,
+			.w = CON_W * fon_w,
+			.h = fon_h
+		})) {
+			fail("SDL_BlitSurface() failed: %s", SDL_GetError());
+		}
+
+		SDL_FreeSurface(lin);
+	}
+
+	SDL_Texture *tex = SDL_CreateTextureFromSurface(ren, sur);
+
+	if (tex == NULL) {
+		fail("SDL_CreateTextureFromSurface() failed: %s", SDL_GetError());
+	}
+
+	if (SDL_RenderCopy(ren, tex, NULL, NULL) < 0) {
+		fail("SDL_RenderCopy() failed: %s", SDL_GetError());
+	}
+
+	SDL_DestroyTexture(tex);
+	SDL_RenderPresent(ren);
+}
+
+
 void lcd_init(void)
 {
 	ver("lcd init");
+
+	win = SDL_CreateWindow("Front LCD", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
+			WIN_W, WIN_H, 0);
+
+	if (win == NULL) {
+		fail("SDL_CreateWindow() failed: %s", SDL_GetError());
+	}
+
+	ren = SDL_CreateRenderer(win, -1, 0);
+
+	if (ren == NULL) {
+		fail("SDL_CreateRenderer() failed: %s", SDL_GetError());
+	}
+
+	SDL_AtomicSet(&frame, 1);
+
+	SDL_RWops *ops = SDL_RWFromFile(font, "rb");
+
+	if (ops == NULL) {
+		fail("error while opening font file %s: %s", font, SDL_GetError());
+	}
+
+	fon = TTF_OpenFontRW(ops, 1, 32);
+
+	if (fon == NULL) {
+		fail("error while loading font file %s: %s", font, TTF_GetError());
+	}
+
+	fon_h = TTF_FontLineSkip(fon);
+
+	if (TTF_GlyphMetrics(fon, 'X', NULL, NULL, NULL, NULL, &fon_w) < 0) {
+		fail("error while measuring font width: %s", TTF_GetError());
+	}
+
+	sur_w = CON_W * fon_w;
+	sur_h = CON_H * fon_h;
+
+	sur = SDL_CreateRGBSurface(0, sur_w, sur_h, 32, 0, 0, 0, 0);
+
+	if (sur == NULL) {
+		fail("SDL_CreateRGBSurface() failed: %s", SDL_GetError());
+	}
+
+	clear_mem();
+
 }
 
@@ -32,4 +353,10 @@
 {
 	ver("lcd quit");
+
+	SDL_FreeSurface(sur);
+	TTF_CloseFont(fon);
+
+	SDL_DestroyRenderer(ren);
+	SDL_DestroyWindow(win);
 }
 
@@ -43,9 +370,125 @@
 {
 	ver2("lcd rd %u:%d", off, sz * 8);
-	return 0;
+
+	uint32_t rv;
+
+	switch (current_op) {
+		case G_MREAD:
+			ver2("lcd current op: %d\n", current_op);
+			rv = mem[cur_y][cur_x];
+			break;
+
+		default:
+			rv = 0x00;
+			break;
+	}
+	return rv;
 }
 
 void lcd_write(uint32_t off, int32_t sz, uint32_t val)
 {
-	ver2("lcd wr %u:%d 0x%0*x", off, sz * 8, sz * 2, val);
-}
+
+	if(last_val != val && mult_val_c > 0) {
+		if(mult_val_c > 1) {
+			ver2("lcd wr %u:%d 0x%0*x was called %u more times", last_off, last_sz * 8, last_sz * 2, last_val, mult_val_c);
+		}
+		else {
+			ver2("lcd wr %u:%d 0x%0*x", last_off, last_sz * 8, last_sz * 2, last_val);
+		}
+
+		ver2("lcd wr %u:%d 0x%0*x", off, sz * 8, sz * 2, val);
+		mult_val_c = 0;
+	}
+	else if(last_val == val && mult_val_c >= 0) {
+		++mult_val_c;
+	}
+	else {
+		ver2("lcd wr %u:%d 0x%0*x", off, sz * 8, sz * 2, val);
+	}
+
+	last_val = val;
+	last_off = off;
+	last_sz = sz;
+
+	if (off == 0) {
+		switch (current_op) {
+			case G_MWRITE:
+					echo((uint8_t)val);
+				break;
+
+			case G_CRSWR:
+				if (cur_ad_c == 0) {
+					cur_ad = val;
+					cur_ad_c++;
+				} else if (cur_ad_c == 1) {
+					cur_ad = cur_ad | (val << 8);
+					mvcur(cur_ad);
+					cur_ad_c = 0;
+				}
+				break;
+
+			case G_DSPON:
+				// TODO Configure blinking of cursor(s)
+				break;
+
+			default:
+				break;
+		}
+	}
+	else {
+		switch (val) {
+			case G_MWRITE:
+				current_op = G_MWRITE;
+				break;
+
+			case G_CRSMRT:
+				cursdir = &forw;
+				current_op = G_CRSMRT;
+				break;
+
+			case G_CRSMUP:
+				cursdir = &up;
+				current_op = G_CRSMUP;
+				break;
+
+			case G_CRSMDN:
+				cursdir = &down;
+				current_op = G_CRSMDN;
+				break;
+
+			case G_CRSWR:
+				current_op = G_CRSWR;
+				break;
+
+			case G_MREAD:
+				current_op = G_MREAD;
+				break;
+
+			case G_DSPOFF:
+				if (SDL_SetRenderDrawColor(ren, 0x00, 0x00, 0x00, 0xff) < 0) {
+					fail("SDL_SetRenderDrawColor() failed: %s", SDL_GetError());
+				}
+				if (SDL_RenderClear(ren) < 0) {
+					fail("SDL_RenderClear() failed: %s", SDL_GetError());
+				}
+				SDL_RenderPresent(ren);
+				render = 0;
+				current_op = G_DSPOFF;
+				break;
+
+			case G_DSPON:
+				render = 1;
+				current_op = G_DSPON;
+				break;
+
+			case G_CRSFRM:
+				// TODO Setup Cursor Form...
+				current_op = 0x00;
+				break;
+
+			default:
+				current_op = 0x00;
+				break;
+		}
+	}
+}
Index: emu/sdl.c
===================================================================
--- emu/sdl.c	(revision 4f967e8eb19a1addd39ec92272705cffc64c374d)
+++ emu/sdl.c	(revision 5475ecffc7e57c236384c2d970e1fea0538c9460)
@@ -27,5 +27,5 @@
 
 static sdl_func_t sdl_funcs[] = {
-	ser_sdl, vid_sdl
+	lcd_sdl, ser_sdl, vid_sdl
 };
 
