# HG changeset patch # User Steve Losh # Date 1482683422 18000 # Node ID ef37b9f3e3980a8f1e7727205dfa140e624323c6 # Parent f0833ca00887672adb31bdbba6e8ff55dd65f24d Start debugging entry diff -r f0833ca00887 -r ef37b9f3e398 content/blog/2016/12/chip8-debugging-infrastructure.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/blog/2016/12/chip8-debugging-infrastructure.markdown Sun Dec 25 11:30:22 2016 -0500 @@ -0,0 +1,198 @@ ++++ +title = "CHIP-8 in Common Lisp: Debugging Infrastructure" +snip = "Let's figure out what the hell is going on." +date = 2016-12-31T14:50:00Z +draft = true + ++++ + +In the previous posts we looked at how to emulate a [CHIP-8][] CPU with Common +Lisp. After adding a screen, input, and sound the core of the emulator is +essentially complete. + +I've been guiding you through the code step by step and it might look pretty +simple, but that's only because I went down all the dead ends myself first. In +practice, when you're writing an emulator for a system you'll need a way to +debug the execution of code, so let's look at how to add some debugging +capabilities to our simple CHIP-8 emulator. + +The full series of posts so far: + +1. [CHIP-8 in Common Lisp: The CPU](http://stevelosh.com/blog/2016/12/chip8-cpu/) +2. [CHIP-8 in Common Lisp: Graphics](http://stevelosh.com/blog/2016/12/chip8-graphics/) +3. [CHIP-8 in Common Lisp: Input](http://stevelosh.com/blog/2016/12/chip8-input/) + +The full emulator source is on [BitBucket][] and [GitHub][]. + +[CHIP-8]: https://en.wikipedia.org/wiki/CHIP-8 +[BitBucket]: https://bitbucket.org/sjl/cl-chip8 +[GitHub]: https://github.com/sjl/cl-chip8 + +
+ +## Disassembling + +The first thing we'll need is a way to take an instruction like `#x8055` and +turn it into something we can read. The easiest way to do this seemed to be to +copy the dispatch loop from the CPU emulator and turn it into a disassembly +function: + +```lisp +(defun disassemble-instruction (instruction) + (flet ((v (n) (symb 'v (format nil "~X" n)))) + (let ((_x__ (ldb (byte 4 8) instruction)) + (__x_ (ldb (byte 4 4) instruction)) + (___x (ldb (byte 4 0) instruction)) + (__xx (ldb (byte 8 0) instruction)) + (_xxx (ldb (byte 12 0) instruction))) + (case (logand #xF000 instruction) + (#x0000 (case instruction + (#x00E0 '(cls)) + (#x00EE '(ret)))) + (#x1000 `(jp ,_xxx)) + (#x2000 `(call ,_xxx)) + (#x3000 `(se ,(v _x__) ,__xx)) + (#x4000 `(sne ,(v _x__) ,__xx)) + (#x5000 (case (logand #x000F instruction) + (#x0 `(se ,(v _x__) ,(v __x_))))) + (#x6000 `(ld ,(v _x__) ,__xx)) + (#x7000 `(add ,(v _x__) ,__xx)) + (#x8000 (case (logand #x000F instruction) + (#x0 `(ld ,(v _x__) ,(v __x_))) + (#x1 `(or ,(v _x__) ,(v __x_))) + (#x2 `(and ,(v _x__) ,(v __x_))) + (#x3 `(xor ,(v _x__) ,(v __x_))) + (#x4 `(add ,(v _x__) ,(v __x_))) + (#x5 `(sub ,(v _x__) ,(v __x_))) + (#x6 `(shr ,(v _x__) ,(v __x_))) + (#x7 `(subn ,(v _x__) ,(v __x_))) + (#xE `(shl ,(v _x__) ,(v __x_))))) + (#x9000 (case (logand #x000F instruction) + (#x0 `(sne ,(v _x__) ,(v __x_))))) + (#xA000 `(ld i ,_xxx)) + (#xB000 `(jp ,(v 0) ,_xxx)) + (#xC000 `(rnd ,(v _x__) ,__xx)) + (#xD000 `(drw ,(v _x__) ,(v __x_) ,___x)) + (#xE000 (case (logand #x00FF instruction) + (#x9E `(skp ,(v _x__))) + (#xA1 `(sknp ,(v _x__))))) + (#xF000 (case (logand #x00FF instruction) + (#x07 `(ld ,(v _x__) dt)) + (#x0A `(ld ,(v _x__) k)) + (#x15 `(ld dt ,(v _x__))) + (#x18 `(ld st ,(v _x__))) + (#x1E `(add i ,(v _x__))) + (#x29 `(ld f ,(v _x__))) + (#x33 `(ld b ,(v _x__))) + (#x55 `(ld (mem i) ,_x__)) + (#x65 `(ld ,_x__ (mem i))))))))) +``` + +There are a lot of other ways we could have done this, like making a proper +parser or adding functionality to `define-opcode`, but since there's not that +many instructions I think this is pretty reasonable. Now we can pass in a raw, +two-byte instruction and get out something readable: + +``` +[SBCL] CHIP8> (disassemble-instruction #x8055) +(SUB V0 V5) + +[SBCL] CHIP8> (disassemble-instruction #x4077) +(SNE V0 119) +``` + +Disassembling a single instruction will be useful, but it would also be nice to +disassemble an entire ROM at once to see what its code looks like. Let's make +a little helper function to handle that: + +```lisp +(defun dump-disassembly (array &optional (start 0) (end (length array))) + (iterate + (for i :from start :below end :by 2) + (print-disassembled-instruction array i) + (sleep 0.001))) +``` + +The `sleep` is there because Neovim's terminal seems to shit the bed if you dump +too much text at it at once. Computers are garbage. + +Other that than, `dump-disassembly` is pretty straightforward: just iterate +through the array of instructions two bytes at a time and print the information. +Let's look at the printing function now: + +```lisp +(defun print-disassembled-instruction (array index) + (destructuring-bind (address instruction disassembly) + (instruction-information array index) + (let ((*print-base* 16)) + (format t "~3,'0X: ~4,'0X ~24A~%" + address + instruction + (or disassembly ""))))) +``` + +Once again we'll delegate to a helper function. +`print-disassembled-instruction` just handles the string formatting to dump an +instruction to the screen. Running it for a single instruction would print +something like: + +``` +Address Disassembly + | | + v v +200: 8055 (SUB V0 V5) + ^ + | + Raw instruction +``` + +The helper function `instruction-information` is simple, but we'll be using it +in the future for something else, so it's nice to have: + +```lisp +(defun instruction-information (array index) + (let ((instruction (retrieve-instruction array index))) + (list index + instruction + (disassemble-instruction instruction)))) +``` + +`retrieve-instruction` is simple (for now): + +```lisp +(defun retrieve-instruction (array index) + (cat-bytes (aref array index) + (aref array (1+ index)))) +``` + +These functions *could* be combined into a single, bigger function, but I'm +a strong believer in having each function do exactly one thing only. And as +we'll see, each of these "simple" tasks is going to get more complicated in the +real world. + +```lisp +``` + +```lisp +``` + +```lisp +``` + +```lisp +``` + +```lisp +``` + +```lisp +``` + +```lisp +``` + +```lisp +``` + +```lisp +```