tdo

Dicing the onion
git clone git@abtrout.com:tdo.git
Log | Files | Refs | README

commit a1a609e40beea80e6207e00e379254735e38cd29
parent 3e942aad1e42e2e9380a7203b6187a827477b1fa
Author: david cochran <about.trout@gmail.com>
Date:   Tue,  4 Nov 2025 06:32:44 -0800

add layer6 solution

Diffstat:
Acmd/layer6/decode.go | 20++++++++++++++++++++
Atomtel.go | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atomtel_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)) + } +}