hypo/cpu/cpu.go

352 lines
5.3 KiB
Go

package cpu
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"git.cyclic.zone/mos/hypo/asm"
)
type Cpu struct {
reg [8]uint32
mem [8192]byte
pc uint32
flags uint32
err error
buf *bytes.Reader
}
func New(buf []byte) (c Cpu, err error) {
c.buf = bytes.NewReader(buf)
hdr := make([]byte, len(asm.Hdr))
if _, err = c.buf.Read(hdr); err != nil {
return c, errors.New("could not read header")
} else if !bytes.Equal(hdr, asm.Hdr) {
return c, errors.New("bad header")
}
return c, nil
}
func (c *Cpu) read(ins any) {
if err := binary.Read(c.buf, binary.LittleEndian, ins); err != nil {
c.err = errors.New("bad read")
}
}
func (c *Cpu) checkReg(r byte) error {
if r > byte(len(c.reg)) {
c.err = fmt.Errorf("invalid register %02x", r)
c.flags |= 1
}
return c.err
}
func (c *Cpu) readReg(r byte) uint32 {
if c.checkReg(r) == nil {
return c.reg[r]
}
return 0
}
func (c *Cpu) writeReg(r byte, i uint32) {
if c.checkReg(r) == nil {
c.reg[r] = i
}
}
func (c *Cpu) readImm(addr uint32) (uint32, error) {
if addr > uint32(len(c.mem)) {
return 0, fmt.Errorf("illegal read %08x", addr)
}
i := c.mem[addr:4]
return uint32(i[3])<<24 | uint32(i[2])<<16 | uint32(i[1])<<8 | uint32(i[0]), nil
}
func (c *Cpu) writeImm(addr, imm uint32) error {
if addr > uint32(len(c.mem)) {
return fmt.Errorf("illegal write %08x (at %08x)", imm, addr)
}
c.mem[addr] = byte(imm)
c.mem[addr+1] = byte(imm >> 8)
c.mem[addr+2] = byte(imm >> 16)
c.mem[addr+3] = byte(imm >> 24)
return nil
}
func (c *Cpu) jump(pc uint32) error {
if _, err := c.buf.Seek(int64(pc+uint32(len(asm.Hdr))), io.SeekStart); err != nil {
return err
}
c.pc = pc
return nil
}
var ops = map[byte]func(*Cpu) int{
asm.OpNop: func(*Cpu) int {
return 0
},
asm.OpLd: func(c *Cpu) int {
var ins struct {
R1, R2 byte
}
if c.read(&ins); c.err != nil {
return 0
}
i, err := c.readImm(c.readReg(ins.R2))
c.err = err
if err != nil {
c.writeReg(ins.R1, i)
}
return 2
},
asm.OpLr: func(c *Cpu) int {
var ins struct {
I uint32
R byte
}
if c.read(&ins); c.err != nil {
return 0
}
c.writeReg(ins.R, ins.I)
return 5
},
asm.OpSt: func(c *Cpu) int {
var ins struct {
R1, R2 byte
}
if c.read(&ins); c.err != nil {
return 0
}
c.err = c.writeImm(c.readReg(ins.R1), c.readReg(ins.R2))
return 2
},
asm.OpAdd: func(c *Cpu) int {
var ins struct {
R1, R2, R3 byte
}
if c.read(&ins); c.err != nil {
return 0
}
c.writeReg(ins.R3, c.readReg(ins.R1)+c.readReg(ins.R2))
return 3
},
asm.OpSub: func(c *Cpu) int {
var ins struct {
R1, R2, R3 byte
}
if c.read(&ins); c.err != nil {
return 0
}
c.writeReg(ins.R3, c.readReg(ins.R1)-c.readReg(ins.R2))
return 3
},
asm.OpAddi: func(c *Cpu) int {
var ins struct {
R1 byte
I uint32
R2 byte
}
if c.read(&ins); c.err != nil {
return 0
}
c.writeReg(ins.R2, c.readReg(ins.R1)+ins.I)
return 6
},
asm.OpSubi: func(c *Cpu) int {
var ins struct {
R1 byte
I uint32
R2 byte
}
if c.read(&ins); c.err != nil {
return 0
}
c.writeReg(ins.R2, c.readReg(ins.R1)-ins.I)
return 6
},
asm.OpP: func(c *Cpu) int {
var R byte
if c.read(&R); c.err != nil {
return 0
}
fmt.Print(string(rune(c.reg[R])))
return 1
},
asm.OpBeq: func(c *Cpu) int {
var ins struct {
R1, R2 byte
I uint32
}
if c.read(&ins); c.err != nil {
return 0
}
if c.readReg(ins.R1) == c.readReg(ins.R2) {
c.jump(ins.I)
return 0
}
return 6
},
asm.OpBne: func(c *Cpu) int {
var ins struct {
R1, R2 byte
I uint32
}
if c.read(&ins); c.err != nil {
return 0
}
if c.readReg(ins.R1) != c.readReg(ins.R2) {
c.jump(ins.I)
return 0
}
return 6
},
asm.OpBgt: func(c *Cpu) int {
var ins struct {
R1, R2 byte
I uint32
}
if c.read(&ins); c.err != nil {
return 0
}
if c.readReg(ins.R1) > c.readReg(ins.R2) {
c.jump(ins.I)
return 0
}
return 6
},
asm.OpBlt: func(c *Cpu) int {
var ins struct {
R1, R2 byte
I uint32
}
if c.read(&ins); c.err != nil {
return 0
}
if c.readReg(ins.R1) < c.readReg(ins.R2) {
c.jump(ins.I)
return 0
}
return 6
},
asm.OpJ: func(c *Cpu) int {
var I uint32
if c.read(&I); c.err != nil {
return 0
}
c.jump(I)
return 0
},
asm.OpJr: func(c *Cpu) int {
var R byte
if c.read(&R); c.err != nil {
return 0
}
c.jump(c.readReg(R))
return 0
},
asm.OpCall: func(c *Cpu) int {
var I uint32
if c.read(&I); c.err != nil {
return 0
}
c.writeReg(3, c.pc+4)
c.jump(I)
return 0
},
asm.OpExit: func(c *Cpu) int {
c.flags |= 1
return 0
},
}
func (c *Cpu) State() bool {
return c.flags != 1
}
func (c *Cpu) Step() error {
var op byte
if c.read(&op); c.err != nil {
return c.err
}
c.pc++
f, ok := ops[op]
if !ok {
c.pc--
return fmt.Errorf("invalid opcode: %02x", op)
}
pc := f(c)
c.pc += uint32(pc)
return c.err
}
func (c *Cpu) WriteTrace(w io.Writer) {
fmt.Fprintln(w, "register trace:")
for i, j := range c.reg {
fmt.Fprintf(w, "%02x: %08x\n", i, j)
}
fmt.Fprintf(w, "pc: %08x\n", c.pc)
fmt.Fprintln(w, "memory trace:")
for i, j := range c.mem {
if i > 0xff {
break
}
if i > 0 && i%16 == 0 {
fmt.Fprintln(w, "")
}
fmt.Fprintf(w, "%02x ", j)
}
fmt.Fprintln(w, "")
}