commit a1a609e40beea80e6207e00e379254735e38cd29
parent 3e942aad1e42e2e9380a7203b6187a827477b1fa
Author: david cochran <about.trout@gmail.com>
Date: Tue, 4 Nov 2025 06:32:44 -0800
add layer6 solution
Diffstat:
| A | cmd/layer6/decode.go | | | 20 | ++++++++++++++++++++ |
| A | tomtel.go | | | 206 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | tomtel_test.go | | | 194 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 420 insertions(+), 0 deletions(-)
diff --git a/cmd/layer6/decode.go b/cmd/layer6/decode.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+ "log"
+ "os"
+
+ "github.com/abtrout/tdo"
+)
+
+func main() {
+ bs, err := tdo.DecodePipedInput()
+ if err != nil {
+ log.Fatalf("Failed to read layer input: %v", err)
+ }
+ t := tdo.NewTomtel(bs)
+ out := t.Run()
+ if _, err := os.Stdout.Write(out); err != nil {
+ log.Fatalf("Failed to write decoded output: %v", err)
+ }
+}
diff --git a/tomtel.go b/tomtel.go
@@ -0,0 +1,206 @@
+package tdo
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "log"
+ "strings"
+)
+
+type Tomtel struct {
+ // 8-bit registers
+ a uint8 // accumulator
+ b uint8 // operand
+ c uint8 // counter/offset
+ d, e uint8 // general purpose
+ f uint8 // flags
+ ptrC uint8 // for MV/MVI
+ // 32-bit registers
+ la, lb, lc, ld uint32 // general purpose
+ ptr uint32 // pointer to memory
+ pc uint32 // program counter
+ // Mutable memory
+ mem []byte
+ // Output buffer
+ outBuf *bytes.Buffer
+}
+
+func NewTomtel(code []byte) *Tomtel {
+ return &Tomtel{
+ mem: code,
+ outBuf: &bytes.Buffer{},
+ }
+}
+
+func (t *Tomtel) String() string {
+ var b strings.Builder
+ b.WriteString("Tomtel{")
+ b.WriteString(fmt.Sprintf("a=%d, ", t.a))
+ b.WriteString(fmt.Sprintf("b=%d, ", t.b))
+ b.WriteString(fmt.Sprintf("c=%d, ", t.c))
+ b.WriteString(fmt.Sprintf("d=%d, ", t.d))
+ b.WriteString(fmt.Sprintf("e=%d, ", t.e))
+ b.WriteString(fmt.Sprintf("f=%d, ", t.f))
+ b.WriteString(fmt.Sprintf("la=%d, ", t.la))
+ b.WriteString(fmt.Sprintf("lb=%d, ", t.lb))
+ b.WriteString(fmt.Sprintf("lc=%d, ", t.lc))
+ b.WriteString(fmt.Sprintf("ld=%d, ", t.ld))
+ b.WriteString(fmt.Sprintf("ptr=%d, ", t.ptr))
+ b.WriteString(fmt.Sprintf("pc=%d, ", t.pc))
+ b.WriteString(fmt.Sprintf("ptrC=%d", t.ptrC))
+ b.WriteString("}")
+ return b.String()
+}
+
+func (t *Tomtel) Run() []byte {
+ for {
+ op := t.mem[t.pc]
+ t.pc += 1
+
+ switch op {
+ case 0xC2: // ADD a <- b
+ t.add()
+ case 0xE1: // APTR imm8
+ imm8 := t.mem[t.pc]
+ t.pc += 1 // advance for uint8 argument
+ t.aptr(imm8)
+ case 0xC1: // CMP
+ t.cmp()
+ case 0x01: // HALT
+ return t.outBuf.Bytes()
+ case 0x21: // JEZ imm32
+ imm32 := binary.LittleEndian.Uint32(t.mem[t.pc:])
+ t.pc += 4 // advance for uint32 argument
+ t.jez(imm32)
+ case 0x22: // JNZ imm32
+ imm32 := binary.LittleEndian.Uint32(t.mem[t.pc:])
+ t.pc += 4 // advance for uint32 argument
+ t.jnz(imm32)
+ case 0x02: // OUT a
+ t.out()
+ case 0xC3: // SUB a <- b
+ t.sub()
+ case 0xC4: // XOR a <- b
+ t.xor()
+ default:
+ if ok, dest, src := parseMv(op); ok {
+ t.mv(dest, src)
+ } else if ok, dest := parseMvi(op); ok {
+ imm8 := t.mem[t.pc]
+ t.pc += 1 // advance for uint8 argument
+ t.mvi(dest, imm8)
+ } else if ok, dest, src := parseMv32(op); ok {
+ t.mv32(dest, src)
+ } else if ok, dest := parseMvi32(op); ok {
+ imm32 := binary.LittleEndian.Uint32(t.mem[t.pc:])
+ t.pc += 4 // advance for uint32 argument
+ t.mvi32(dest, imm32)
+ } else {
+ log.Fatalf("Non instruction byte %X at PC %d", op, t.pc-1)
+ }
+ }
+ }
+}
+
+func parseMv(op uint8) (bool, uint8, uint8) {
+ // 0b01DDDSSS
+ if op&uint8(0b11000000) != 0b01000000 {
+ return false, 0, 0
+ } else if op&uint8(0b00000111) == 0 {
+ return false, 0, 0
+ }
+ dest := (op & uint8(0b00111000)) >> 3
+ src := op & uint8(0b00000111)
+ return true, dest, src
+}
+
+func parseMvi(op uint8) (bool, uint8) {
+ // 0b01DDD000
+ if op&uint8(0b11000111) == 0b01000000 {
+ dest := op & uint8(0b00111000) >> 3
+ return true, dest
+ }
+ return false, 0
+}
+
+func parseMv32(op uint8) (bool, uint8, uint8) {
+ // 0b10DDDSSS
+ if op&uint8(0b11000000) != 0b10000000 {
+ return false, 0, 0
+ } else if op&uint8(0b00000111) == 0 {
+ return false, 0, 0
+ }
+ dest := (op & uint8(0b00111000)) >> 3
+ src := op & uint8(0b00000111)
+ return true, dest, src
+}
+
+func parseMvi32(op uint8) (bool, uint8) {
+ // 0b10DDD000
+ if op&uint8(0b11000111) == 0b10000000 {
+ dest := op & uint8(0b00111000) >> 3
+ return true, dest
+ }
+ return false, 0
+}
+
+func (t *Tomtel) add() { t.a = t.a + t.b }
+
+func (t *Tomtel) aptr(imm8 uint8) { t.ptr += uint32(imm8) }
+
+func (t *Tomtel) cmp() {
+ if t.a == t.b {
+ t.f = 0
+ } else {
+ t.f = 1
+ }
+}
+
+func (t *Tomtel) jez(imm32 uint32) {
+ if t.f == 0 {
+ t.pc = imm32
+ }
+}
+
+func (t *Tomtel) jnz(imm32 uint32) {
+ if t.f != 0 {
+ t.pc = imm32
+ }
+}
+
+func (t *Tomtel) mv(dest, src uint8) {
+ regs := []*uint8{&t.a, &t.b, &t.c, &t.d, &t.e, &t.f}
+ if dest == 7 {
+ t.mem[t.ptr+uint32(t.c)] = *regs[src-1]
+ } else if src == 7 {
+ *regs[dest-1] = t.mem[t.ptr+uint32(t.c)]
+ } else {
+ *regs[dest-1] = *regs[src-1]
+ }
+}
+
+func (t *Tomtel) mvi(dest, imm8 uint8) {
+ regs := []*uint8{&t.a, &t.b, &t.c, &t.d, &t.e, &t.f}
+ if dest == 7 {
+ t.mem[t.ptr+uint32(t.c)] = imm8
+ } else {
+ *regs[dest-1] = imm8
+ }
+}
+
+func (t *Tomtel) mv32(dest, src uint8) {
+ regs := []*uint32{&t.la, &t.lb, &t.lc, &t.ld, &t.ptr, &t.pc}
+ *regs[dest-1] = *regs[src-1]
+}
+
+func (t *Tomtel) mvi32(dest uint8, imm32 uint32) {
+ regs := []*uint32{&t.la, &t.lb, &t.lc, &t.ld, &t.ptr, &t.pc}
+ *regs[dest-1] = imm32
+}
+
+func (t *Tomtel) out() { t.outBuf.WriteByte(t.a) }
+
+func (t *Tomtel) sub() { t.a -= t.b }
+
+func (t *Tomtel) xor() { t.a ^= t.b }
diff --git a/tomtel_test.go b/tomtel_test.go
@@ -0,0 +1,194 @@
+package tdo
+
+import "testing"
+
+func TestParseMv(t *testing.T) {
+ tests := []struct {
+ input uint8
+ wantOk bool
+ wantDest uint8
+ wantSrc uint8
+ }{
+ {0b01011110, true, 0b011, 0b110},
+ {0b01101110, true, 0b101, 0b110},
+ {0b11000000, false, 0, 0},
+ {0b00000000, false, 0, 0},
+ }
+
+ for _, test := range tests {
+ ok, dest, src := parseMv(test.input)
+ if ok != test.wantOk || dest != test.wantDest || src != test.wantSrc {
+ t.Errorf("parseMv(%08b) = %t, %d, %d; want %t, %d, %d", test.input, ok, dest, src, test.wantOk, test.wantDest, test.wantSrc)
+ }
+ }
+}
+
+func TestParseMvi(t *testing.T) {
+ tests := []struct {
+ input uint8
+ wantOk bool
+ wantDest uint8
+ }{
+ {0b01111000, true, 0b111},
+ {0b01101000, true, 0b101},
+ {0b01111001, false, 0},
+ {0b01111111, false, 0},
+ {0b00000000, false, 0},
+ {0b11000000, false, 0},
+ }
+
+ for _, test := range tests {
+ ok, dest := parseMvi(test.input)
+ if ok != test.wantOk || dest != test.wantDest {
+ t.Errorf("parseMvi(%08b) = %t, %d; want %t, %d", test.input, ok, dest, test.wantOk, test.wantDest)
+ }
+ }
+}
+
+func TestParseMv32(t *testing.T) {
+ tests := []struct {
+ input uint8
+ wantOk bool
+ wantDest uint8
+ wantSrc uint8
+ }{
+ {0b10011110, true, 0b011, 0b110},
+ {0b10101110, true, 0b101, 0b110},
+ {0b10111000, false, 0, 0},
+ {0b11111111, false, 0, 0},
+ {0b01000000, false, 0, 0},
+ }
+
+ for _, test := range tests {
+ ok, dest, src := parseMv32(test.input)
+ if ok != test.wantOk || dest != test.wantDest || src != test.wantSrc {
+ t.Errorf("parseMv32(%08b) = %t, %d, %d; want %t, %d, %d", test.input, ok, dest, src, test.wantOk, test.wantDest, test.wantSrc)
+ }
+ }
+}
+
+func TestParseMvi32(t *testing.T) {
+ tests := []struct {
+ input uint8
+ wantOk bool
+ wantDest uint8
+ }{
+ {0b10111000, true, 0b111},
+ {0b10101000, true, 0b101},
+ {0b00000000, false, 0},
+ {0b11000000, false, 0},
+ {0b10111111, false, 0},
+ }
+
+ for _, test := range tests {
+ ok, dest := parseMvi32(test.input)
+ if ok != test.wantOk || dest != test.wantDest {
+ t.Errorf("parseMvi32(%08b) = %t, %d; want %t, %d", test.input, ok, dest, test.wantOk, test.wantDest)
+ }
+ }
+}
+
+func TestTomtel_HelloWorld(t *testing.T) {
+ code := []byte{
+ // 50 48 # MVI b <- 72
+ 0x50, 0x48,
+ // C2 # ADD a <- b
+ 0xC2,
+ // 02 # OUT a
+ 0x02,
+ // A8 4D 00 00 00 # MVI32 ptr <- 0x0000004d
+ 0xA8, 0x4D, 0x00, 0x00, 0x00,
+ // 4F # MV a <- (ptr+c)
+ 0x4F,
+ // 02 # OUT a
+ 0x02,
+ // 50 09 # MVI b <- 9
+ 0x50, 0x09,
+ // C4 # XOR a <- b
+ 0xC4,
+ // 02 # OUT a
+ 0x02,
+ // 02 # OUT a
+ 0x02,
+ // E1 01 # APTR 0x00000001
+ 0xE1, 0x01,
+ // 4F # MV a <- (ptr+c)
+ 0x4F,
+ // 02 # OUT a
+ 0x02,
+ // C1 # CMP
+ 0xC1,
+ // 22 1D 00 00 00 # JNZ 0x0000001d
+ 0x22, 0x1D, 0x00, 0x00, 0x00,
+ // 48 30 # MVI a <- 48
+ 0x48, 0x30,
+ // 02 # OUT a
+ 0x02,
+ // 58 03 # MVI c <- 3
+ 0x58, 0x03,
+ // 4F # MV a <- (ptr+c)
+ 0x4F,
+ // 02 # OUT a
+ 0x02,
+ // B0 29 00 00 00 # MVI32 pc <- 0x00000029
+ 0xB0, 0x29, 0x00, 0x00, 0x00,
+ // 48 31 # MVI a <- 49
+ 0x48, 0x31,
+ // 02 # OUT a
+ 0x02,
+ // 50 0C # MVI b <- 12
+ 0x50, 0x0C,
+ // C3 # SUB a <- b
+ 0xC3,
+ // 02 # OUT a
+ 0x02,
+ // AA # MV32 ptr <- lb
+ 0xAA,
+ // 57 # MV b <- (ptr+c)
+ 0x57,
+ // 48 02 # MVI a <- 2
+ 0x48, 0x02,
+ // C1 # CMP
+ 0xC1,
+ // 21 3A 00 00 00 # JEZ 0x0000003a
+ 0x21, 0x3A, 0x00, 0x00, 0x00,
+ // 48 32 # MVI a <- 50
+ 0x48, 0x32,
+ // 02 # OUT a
+ 0x02,
+ // 48 77 # MVI a <- 119
+ 0x48, 0x77,
+ // 02 # OUT a
+ 0x02,
+ // 48 6F # MVI a <- 111
+ 0x48, 0x6F,
+ // 02 # OUT a
+ 0x02,
+ // 48 72 # MVI a <- 114
+ 0x48, 0x72,
+ // 02 # OUT a
+ 0x02,
+ // 48 6C # MVI a <- 108
+ 0x48, 0x6C,
+ // 02 # OUT a
+ 0x02,
+ // 48 64 # MVI a <- 100
+ 0x48, 0x64,
+ // 02 # OUT a
+ 0x02,
+ // 48 21 # MVI a <- 33
+ 0x48, 0x21,
+ // 02 # OUT a
+ 0x02,
+ // 01 # HALT
+ 0x01,
+ // 65 6F 33 34 2C # non-instruction data
+ 0x65, 0x6F, 0x33, 0x34, 0x2C,
+ }
+
+ tom := NewTomtel(code)
+ out := tom.Run()
+ if string(out) != "Hello, world!" {
+ t.Errorf("Unexpected output: %v", string(out))
+ }
+}