diff --git a/.gitignore b/.gitignore index ec5789e..2cca2f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ uasm uvm *.bin +*.hex +*.rom diff --git a/asm/compiler.go b/asm/compiler.go index 210da72..7de8ecb 100644 --- a/asm/compiler.go +++ b/asm/compiler.go @@ -15,6 +15,18 @@ type ( nodeType uint8 ) +// Size is operand size in bytes +func (ot OperandType) Size() int { + switch ot { + case OperandReg, OperandValue: + return 1 + case OperandAddr: + return 2 + default: + panic("unknown operand " + ot) + } +} + const ( OperandReg OperandType = "reg" OperandValue OperandType = "val" @@ -136,9 +148,8 @@ func assemble(ins string, ops []string) []uint8 { } // Compile compiles program loaded from reader (usually strings.Reader or os.File) -func Compile(textReader io.Reader) [1 << 16]uint8 { - - bin := [defines.ROMSize]uint8{} +func Compile(textReader io.Reader) []uint8 { + bin := make([]byte, 1<<16) offset := uint16(0x00) lineNum := 0 @@ -165,7 +176,7 @@ func Compile(textReader io.Reader) [1 << 16]uint8 { // clean-up operands for i := 0; i < len(ops); i++ { - ops[i] = strings.Replace(strings.TrimSpace(ops[i]), ",", "", -1) + ops[i] = strings.ReplaceAll(strings.TrimSpace(ops[i]), ",", "") } // decide on what we're looking right now - operator or macro? diff --git a/asm/syntax.go b/asm/syntax.go index 271a44c..3a5ea22 100644 --- a/asm/syntax.go +++ b/asm/syntax.go @@ -2,20 +2,25 @@ package asm const ( OpNOP = 0x00 - OpJUMP = 0x01 OpPUSH = 0x02 OpPOP = 0x03 OpCLEAR = 0x04 OpINC = 0x05 OpHALT = 0x09 + OpCP = 0x0A + OpCPI = 0x0B + + OpJUMP = 0x50 + OpJUMPIF_EQ = 0x51 + OpJUMPIF_NE = 0x52 + OpADDRegReg = 0x10 OpADDRegVal = 0x11 OpMOVRegReg = 0x20 OpMOVRegVal = 0x21 - OpLPM = 0x22 OpLOAD = 0x30 OpSTORE = 0x40 ) @@ -30,13 +35,21 @@ asm syntax help // Syntax maps instruction name to opcodes with operands var Syntax = map[string]map[uint8][]OperandType{ "NOP": {OpNOP: {}}, - "JUMP": {OpJUMP: {OperandAddr}}, "PUSH": {OpPUSH: {OperandReg}}, "POP": {OpPOP: {OperandReg}}, "CLEAR": {OpCLEAR: {OperandReg}}, "INC": {OpINC: {OperandReg}}, "HALT": {OpHALT: {}}, + "CP": { + OpCP: {OperandReg, OperandReg}, + OpCPI: {OperandReg, OperandValue}, + }, + + "JUMP": {OpJUMP: {OperandAddr}}, + "JE": {OpJUMPIF_EQ: {OperandAddr}}, + "JNE": {OpJUMPIF_NE: {OperandAddr}}, + "ADD": { OpADDRegReg: {OperandReg, OperandReg}, // do reg1 + reg2 and store the result in reg1 OpADDRegVal: {OperandReg, OperandValue}, // do reg1 + value and store the result in reg1 @@ -46,8 +59,6 @@ var Syntax = map[string]map[uint8][]OperandType{ OpMOVRegVal: {OperandReg, OperandValue}, // move value to reg1 immediately }, - "LPM": {OpLPM: {OperandReg, OperandAddr}}, // load ROM value at given addr to reg - // external memory "LOAD": {OpLOAD: {OperandReg, OperandAddr}}, // load register with a value stored at addr "STORE": {OpSTORE: {OperandAddr, OperandReg}}, // store reg's value at address diff --git a/assets/index.html b/assets/index.html new file mode 100644 index 0000000..80840e3 --- /dev/null +++ b/assets/index.html @@ -0,0 +1,49 @@ + + + + WebSocket Example + + + + + + + diff --git a/cmd/uvm/main.go b/cmd/uvm/main.go index 4c8f01b..44d6fb1 100644 --- a/cmd/uvm/main.go +++ b/cmd/uvm/main.go @@ -3,11 +3,9 @@ package main import ( "flag" "fmt" - "io/ioutil" "os" "github.com/sshaman1101/uvm/cpu" - "github.com/sshaman1101/uvm/defines" ) var usageFunc = func() { @@ -27,22 +25,20 @@ func main() { } romFile := os.Args[1] - image, err := ioutil.ReadFile(romFile) + image, err := os.ReadFile(romFile) if err != nil { fmt.Printf("ERR: Failed to load ROM file from %s: %v", romFile, err) os.Exit(1) } - if len(image) > defines.ROMSize { + romSize := 1 << 16 + if len(image) > romSize { fmt.Printf("WARN: ROM image does not fits into memory "+ "(size = %d, but %d bytes available).\n"+ - "Image will be truncated.\n", len(image), defines.ROMSize) + "Image will be truncated.\n", len(image), romSize) } - var rom = [defines.ROMSize]uint8{} - copy(rom[:], image) - uCPU := cpu.NewCPU() - uCPU.ROM = rom + uCPU.LoadROM(image) uCPU.Run() } diff --git a/cpu/cpu.go b/cpu/cpu.go index b897e70..df59d02 100644 --- a/cpu/cpu.go +++ b/cpu/cpu.go @@ -2,7 +2,9 @@ package cpu import ( "fmt" + "strings" + colors "github.com/nikonov1101/colors.go" "github.com/sshaman1101/uvm/asm" "github.com/sshaman1101/uvm/defines" ) @@ -18,74 +20,88 @@ func (f *flags) String() string { } type CPU struct { - ROM [defines.ROMSize]uint8 - RAM [defines.RAMSize]uint8 - - registers [defines.RegisterCount]uint8 - stack *stack - flags *flags - - // program counter - pc uint16 + flags *flags + // general-purpose registers are not memory-mapped (yet). + generalPurposeReg [defines.RegisterCount]uint8 + // pc actually 24 bits wide + pc uint32 + // stack pointer, 24 bits wide as well + sp uint32 + + // address is 24 bit wide, first 8 bits are pointed by segmentSelectorReg + // (inspired by 8088), next 16 bits are pointed by the address operand of + // an instruction, thus: + // MOV r1 $abcd + // moves value at address sreg+0xabcd into r1. + segmentSelectorReg uint8 + + // all addressable memory + mem [1 << defines.AddressWidth]uint8 } +const ( + startSegment = 0 + startAddress = 0 +) + func NewCPU() *CPU { return &CPU{ - ROM: [defines.ROMSize]uint8{}, - RAM: [defines.RAMSize]uint8{}, - registers: [defines.RegisterCount]uint8{}, - stack: newStack(defines.StackDepth), - flags: &flags{}, - pc: 0, + flags: &flags{}, + sp: defines.StackInitialAddr, // very end of the memory + pc: startSegment, + segmentSelectorReg: startSegment, + } +} + +func (cpu *CPU) LoadROM(rom []byte) { + if len(rom) == 0 { + panic("empty ROM given") } + + romStart := 0x00FFFFFF & (uint32(startSegment)<<16 | uint32(startAddress)) + copy(cpu.mem[romStart:], rom) } func (cpu *CPU) Run() { for { // load next value from mem, // must be an instruction - v := cpu.ROM[cpu.pc] + v := cpu.mem[cpu.pc] - // decode instruction + // STAGE 1: decode instruction // note: panics on invalid input next := cpu.decodeInstruction(v) + var pcOffset uint32 = 0 + + // STAGE 2: fetch the operands from memory + for i := range next.operands { + // calculate next address + pcOffset++ - // load operands - for i := 0; i < next.operandCount; i++ { - // calculate next mem address - cpu.pc++ - // fetch memory - // todo: do it via something like MAR/MDR, as real hardware do - // or just emulate it, at least it will looks hacky. - given := cpu.ROM[cpu.pc] + // fetch next operand from the memory + given := cpu.mem[cpu.pc+pcOffset] expected := next.operands[i] // sanity check - opName := checkOperand(given, expected.opType) + checkOperand(given, expected.opType) // XXX debug - fmt.Printf(" operand %s loaded\n", opName) + // fmt.Printf(" operand %s loaded\n", opName) - // store within instruction + // store operand *data* within instruction next.operands[i].value = given } - // XXX print instruction with operators loaded - fmt.Printf("at PC = %d (0x%02x) -> RUN %s\n", cpu.pc, cpu.pc, next) - - next.execute(cpu) - - // dump CPU state after the each instruction - fmt.Println("======== CPU state ========") - fmt.Printf("PC = %d\n", cpu.pc) - fmt.Printf("flags:\n %v\n", cpu.flags) - fmt.Printf("registers:\n ") - for i := 0; i < defines.RegisterCount; i++ { - fmt.Printf("r%d = %02x", i, cpu.registers[i]) - if i+1 != defines.RegisterCount { - fmt.Printf(" | ") - } - } - fmt.Printf("\n===========================\n\n") + cpu.debugPre(next) + // update PC with a number operands fetched, + // do this before the actual execution, so + // JUMP instructions may override the PC + cpu.pc += pcOffset + 1 + + // STAGE 3: execute the instruction + cpu.execute(next) + + // XXX debug state on the fly + cpu.debugPost() if cpu.flags.halt { return @@ -122,6 +138,7 @@ func (cpu *CPU) decodeInstruction(opcode uint8) instruction { // note: just a dirty crutch to add two address bytes for instruction. // Need to find a smarter way to handle such situation. if op == asm.OperandAddr { + // XXX why??? instructionOperands = append(instructionOperands, operand{opType: asm.OperandAddr}, operand{opType: asm.OperandAddr}) } else { instructionOperands = append(instructionOperands, operand{opType: op}) @@ -129,9 +146,45 @@ func (cpu *CPU) decodeInstruction(opcode uint8) instruction { } return instruction{ - name: mnemonic, - opCode: opcode, - operandCount: len(instructionOperands), - operands: instructionOperands, + name: mnemonic, + opCode: opcode, + operands: instructionOperands, } } + +func (cpu *CPU) debugPre(next instruction) { + pc := colors.Cyan(fmt.Sprintf("0x%06X", cpu.pc)) + fmt.Printf("PC: %s @ %v\n", pc, next.String()) +} + +func (cpu *CPU) debugPost() { + var regs []string + for i, r := range cpu.generalPurposeReg { + rs := fmt.Sprintf("0x%02X", r) + if r > 0 { + rs = colors.Yellow(rs) + } + regs = append(regs, fmt.Sprintf("r%d: %s", i, rs)) + } + + zs := "Z: false" + cs := "C: false" + hs := "H: false" + if cpu.flags.zero { + zs = fmt.Sprintf("Z: %s", colors.Green("TRUE")) + } + if cpu.flags.carry { + cs = fmt.Sprintf("C: %s", colors.Green("TRUE")) + } + if cpu.flags.halt { + hs = colors.Red("HALT: TRUE") + } + + flags := fmt.Sprintf("%s | %s | %s", zs, cs, hs) + pc := colors.Cyan(fmt.Sprintf("0x%06X", cpu.pc)) + sp := colors.Magenta(fmt.Sprintf("0x%06X", cpu.sp)) + + fmt.Printf("\tPC: %s | SP: %v | flags: %s\n", pc, sp, flags) + fmt.Printf("\t%s\n", strings.Join(regs, " ")) + fmt.Println("=============================================") +} diff --git a/cpu/instruction.go b/cpu/instruction.go index 02cc26a..e896aab 100644 --- a/cpu/instruction.go +++ b/cpu/instruction.go @@ -2,7 +2,9 @@ package cpu import ( "fmt" + "strings" + "github.com/nikonov1101/colors.go" "github.com/sshaman1101/uvm/asm" "github.com/sshaman1101/uvm/defines" "github.com/sshaman1101/uvm/math" @@ -27,71 +29,97 @@ func checkOperand(v uint8, typ asm.OperandType) string { } return fmt.Sprintf("r%02d", v) case asm.OperandAddr: - if int(v) >= defines.RAMSize { - panic("mem address operand is out of memory") - } - return fmt.Sprintf("$%04X", v) + return fmt.Sprintf("$%02X", v) default: panic("operand type must be defined") } } -// calculateAddress calculates 16-bit address using -// LO and HI 8-bit address parts -func calculateAddress(lo, hi uint8) uint16 { - // shift HI by 8 bits, then OR with LO to fill the rest - return uint16(hi)<<8 | uint16(lo) -} - type instruction struct { // just a name, like MOV, XOR, JUMP name string // what to do opCode uint8 - // how many operands we need to fetch from memory - operandCount int - // on which data we need to preform operation + // on which data we need to preform operation, + // note: when the only instruction fetched, we know the + // operand **types** from the instruction code itself. + // the actual value of operands (register names, address values) + // will be fetched from a memory during the instruction fetch cycle. operands []operand } +func (in instruction) OperandSize() int { + size := 0 + for _, op := range in.operands { + size += op.opType.Size() + } + return size +} + func (in instruction) String() string { - ops := "" + var ops []string for _, o := range in.operands { - ops += fmt.Sprintf("%s %0X, ", o.opType, o.value) + switch o.opType { + case asm.OperandReg: + ops = append(ops, colors.Green(fmt.Sprintf("reg%d", o.value))) + case asm.OperandValue: + ops = append(ops, colors.Magenta(fmt.Sprintf("val 0x%02X", o.value))) + case asm.OperandAddr: + ops = append(ops, fmt.Sprintf("addr 0x%02X", o.value)) + } } - return in.name + " " + ops + name := colors.Yellow(strings.ToUpper(in.name)) + return name + " " + strings.Join(ops, "; ") } // asAddress returns 16 bit address from given operand indexes -func (in *instruction) asAddress(loOp, hiOp int) uint16 { - return calculateAddress(in.operands[loOp].value, in.operands[hiOp].value) +func (in instruction) asAddress(seg uint8, loOp, hiOp int) uint32 { + lo := in.operands[loOp].value + hi := in.operands[hiOp].value + return uint32(seg)<<16 | uint32(hi)<<8 | uint32(lo) } // execute the instruction // can touch: -// * registers -// * flag register -// * program counter -// note that in must increase PC by one -// if it's regular instruction (not JUMP) -// TODO: seems like the dependency must be inverted: -// the CPU executes the instruction, not vise versa. -func (in *instruction) execute(cpu *CPU) { +// - registers +// - flag register +// - program counter (when jumps) +func (cpu *CPU) execute(in instruction) { switch in.opCode { case asm.OpNOP: // just do nothing case asm.OpJUMP: - // go to address, DO NOT increment PC by one - cpu.pc = in.asAddress(0, 1) - return + cpu.pc = in.asAddress(cpu.segmentSelectorReg, 0, 1) + + case asm.OpJUMPIF_EQ: + if cpu.flags.zero { + cpu.pc = in.asAddress(cpu.segmentSelectorReg, 0, 1) + } + + case asm.OpJUMPIF_NE: + if !cpu.flags.zero { + cpu.pc = in.asAddress(cpu.segmentSelectorReg, 0, 1) + } + + case asm.OpCP: + r0 := cpu.generalPurposeReg[in.operands[0].value] + r1 := cpu.generalPurposeReg[in.operands[1].value] + cpu.flags.zero = r0 == r1 + cpu.flags.carry = false + + case asm.OpCPI: + r0 := cpu.generalPurposeReg[in.operands[0].value] + v1 := in.operands[1].value + cpu.flags.zero = r0 == v1 + cpu.flags.carry = false case asm.OpADDRegReg: r0 := in.operands[0].value r1 := in.operands[1].value - result, carry := math.Add8(cpu.registers[r0], cpu.registers[r1]) - cpu.registers[r0] = result + result, carry := math.Add8(cpu.generalPurposeReg[r0], cpu.generalPurposeReg[r1]) + cpu.generalPurposeReg[r0] = result cpu.flags.zero = result == 0 cpu.flags.carry = carry @@ -100,8 +128,8 @@ func (in *instruction) execute(cpu *CPU) { reg := in.operands[0].value value := in.operands[1].value - result, carry := math.Add8(cpu.registers[reg], value) - cpu.registers[reg] = result + result, carry := math.Add8(cpu.generalPurposeReg[reg], value) + cpu.generalPurposeReg[reg] = result cpu.flags.zero = result == 0 cpu.flags.carry = carry @@ -109,74 +137,64 @@ func (in *instruction) execute(cpu *CPU) { case asm.OpMOVRegVal: reg := in.operands[0].value val := in.operands[1].value - cpu.registers[reg] = val - // todo: flags? + cpu.generalPurposeReg[reg] = val case asm.OpMOVRegReg: dstReg := in.operands[0].value srcReg := in.operands[1].value - cpu.registers[dstReg] = cpu.registers[srcReg] - // todo: flags on dstReg value? - - case asm.OpLPM: // load from program memory - // calculate 16 bit address in ROM - addr := in.asAddress(1, 2) - // load value in the given register - cpu.registers[in.operands[0].value] = cpu.ROM[addr] - // todo: flags? - - case asm.OpLOAD: - addr := in.asAddress(1, 2) + cpu.generalPurposeReg[dstReg] = cpu.generalPurposeReg[srcReg] + + case asm.OpLOAD: // LOAD reg <- $mem + addr := in.asAddress(cpu.segmentSelectorReg, 1, 2) + val := cpu.mem[addr] + reg := in.operands[0].value - val := cpu.RAM[addr] - cpu.registers[reg] = val - cpu.flags.zero = val == 0 + cpu.generalPurposeReg[reg] = val - case asm.OpSTORE: // addr, reg - addr := in.asAddress(0, 1) + case asm.OpSTORE: // STORE $mem <- reg reg := in.operands[2].value - val := cpu.registers[reg] - cpu.RAM[addr] = val + val := cpu.generalPurposeReg[reg] + + addr := in.asAddress(cpu.segmentSelectorReg, 0, 1) + cpu.mem[addr] = val case asm.OpHALT: cpu.flags.halt = true - return case asm.OpPUSH: reg := in.operands[0].value - val := cpu.registers[reg] - cpu.stack.push(val) + val := cpu.generalPurposeReg[reg] + sp := 0x00FFFFFF & cpu.sp + cpu.mem[sp] = val + // TODO(nikonov): any kind of stack guards, maybe? + // at least .SP > .PC + cpu.sp-- case asm.OpPOP: + cpu.sp++ + sp := 0x00FFFFFF & cpu.sp + val := cpu.mem[sp] + // TODO(nikonov): any kind of stack guards, maybe? + reg := in.operands[0].value - val := cpu.stack.pop() - cpu.registers[reg] = val - // todo: flags for val? + cpu.generalPurposeReg[reg] = val case asm.OpCLEAR: reg := in.operands[0].value - cpu.registers[reg] = 0 + cpu.generalPurposeReg[reg] = 0 cpu.flags.zero = true + cpu.flags.carry = false case asm.OpINC: reg := in.operands[0].value - val := cpu.registers[reg] + val := cpu.generalPurposeReg[reg] result, carry := math.Add8(val, 1) - cpu.registers[reg] = result - cpu.flags.zero = reg == 0 + cpu.generalPurposeReg[reg] = result + cpu.flags.zero = result == 0 cpu.flags.carry = carry default: panic(fmt.Sprintf("dunno how to execute instruction %2x (%s)", in.opCode, in.name)) } - - // todo: math - // maybe it should be like: - // res = cpu.add(v1, v2) - // where res is a 8bit value, and flags are set by the method accordingly? - - // go to next instruction. - // todo: something is wrong with this design. - cpu.pc++ } diff --git a/cpu/video.go b/cpu/video.go new file mode 100644 index 0000000..fed7e1a --- /dev/null +++ b/cpu/video.go @@ -0,0 +1,215 @@ +package cpu + +import ( + "image" + "image/color" + "image/png" + "log" + "net/http" + "os" + "time" + + "github.com/gorilla/websocket" + "github.com/sshaman1101/uvm/defines" +) + +type videoCard struct { + mem [defines.VideoWidth * defines.VideoHeight]uint8 + + display *image.NRGBA + redrawn chan struct{} +} + +// todo: +// it just a POC, more to fix and reconsider here: +// * docstrings +// * do not touch host's file system +// * we need mem-mapped IO, so videoCard.mem have to be refactored anyway + +func newVideo() *videoCard { + if err := os.RemoveAll("/tmp/video/"); err != nil { + panic(err) + } + if err := os.Mkdir("/tmp/video/", 0o755); err != nil { + panic(err) + } + + buf := image.NewNRGBA(image.Rect(0, 0, defines.VideoWidth, defines.VideoHeight)) + + black := color.NRGBA{ + A: 255, + R: 0, + G: 0, + B: 0, + } + + for y := 0; y < defines.VideoHeight; y++ { + for x := 0; x < defines.VideoWidth; x++ { + buf.Set(x, y, black) + } + } + + video := &videoCard{ + display: buf, + mem: [defines.VideoWidth * defines.VideoHeight]uint8{}, + redrawn: make(chan struct{}), + } + + go video.startWebSocket() + return video +} + +func (v *videoCard) render() { + for x := 0; x < defines.VideoWidth; x++ { + for y := 0; y < defines.VideoHeight; y++ { + color8 := v.mem[x*defines.VideoWidth+y] + v.point(x, y, color8) + } + } +} + +const lastFrameAt = "/tmp/video/last.png" + +var map3 = [8]uint8{ + 0, 37, 74, 110, 146, 183, 219, 255, +} + +var map2 = [4]uint8{ + 0, 85, 170, 255, +} + +func (v *videoCard) point(x, y int, color8 uint8) { + r := (color8 >> 5) & 0x07 + r = map3[r] + + g := (color8 >> 2) & 0x07 + g = map3[g] + + b := color8 & 0x03 + b = map2[b] + + v.display.Set(x, y, color.NRGBA{ + A: 255, R: r, G: g, B: b, + }) +} + +func (v *videoCard) writeFrame() { + // it very unlikely will cause any performance issues, + // but consider encode to a bytes buffer once, + // then write such buffer to each file. + fd, err := os.Create(lastFrameAt) + if err != nil { + log.Fatal(err) + } + + if err := png.Encode(fd, v.display); err != nil { + fd.Close() + log.Fatal(err) + } + + if err := fd.Close(); err != nil { + log.Fatal(err) + } + + select { + case v.redrawn <- struct{}{}: + default: + } +} + +// below is a websocket-as-a-display implementation + +const ( + pongWait = 60 * time.Second + pingPeriod = (pongWait * 9) / 10 + filePeriod = 20 * time.Millisecond + writeWait = filePeriod +) + +func (v *videoCard) reader(ws *websocket.Conn) { + defer ws.Close() + ws.SetReadLimit(512) + ws.SetReadDeadline(time.Now().Add(pongWait)) + ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + for { + _, _, err := ws.ReadMessage() + if err != nil { + break + } + } +} + +func (v *videoCard) writer(ws *websocket.Conn) { + pingTicker := time.NewTicker(pingPeriod) + fileTicker := time.NewTicker(2 * time.Second) // alpha-like frame? + defer func() { + pingTicker.Stop() + fileTicker.Stop() + ws.Close() + }() + readAndSend := func() { + bs, err := os.ReadFile(lastFrameAt) + if err != nil { + log.Printf("failed to read last frame: %v", err) + return + } + + ws.SetWriteDeadline(time.Now().Add(writeWait)) + if err := ws.WriteMessage(websocket.BinaryMessage, bs); err != nil { + log.Printf("failed to write: %v", err) + return + } + } + for { + select { + case <-fileTicker.C: + readAndSend() + case <-v.redrawn: + readAndSend() + case <-pingTicker.C: + ws.SetWriteDeadline(time.Now().Add(writeWait)) + if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil { + return + } + } + } +} + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +func (v *videoCard) serveWs(w http.ResponseWriter, r *http.Request) { + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + if _, ok := err.(websocket.HandshakeError); !ok { + log.Println(err) + } + return + } + + go v.writer(ws) + v.reader(ws) +} + +func (v *videoCard) serveIndex(w http.ResponseWriter, r *http.Request) { + // TODO(nikonov): use embed + // TODO(nikonov): implement websocket reconnet on a client + bs, err := os.ReadFile("assets/index.html") + if err != nil { + panic(err) + } + + w.Write(bs) +} + +func (v *videoCard) startWebSocket() { + http.HandleFunc("/", v.serveIndex) + http.HandleFunc("/ws", v.serveWs) + + log.Printf("starting websocket server at :8080") + if err := http.ListenAndServe(":8080", nil); err != nil { + log.Fatal(err) + } +} diff --git a/defines/defines.go b/defines/defines.go index 62e89e9..1e37283 100644 --- a/defines/defines.go +++ b/defines/defines.go @@ -1,8 +1,10 @@ package defines const ( - ROMSize = 1 << 16 - RAMSize = 1 << 10 - RegisterCount = 8 - StackDepth = 32 + RegisterCount = 8 + AddressWidth = 24 + StackInitialAddr = (1 << AddressWidth) - 1 + + VideoWidth = 800 + VideoHeight = 600 ) diff --git a/go.mod b/go.mod index 14a9dd8..5dbb6f3 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,17 @@ module github.com/sshaman1101/uvm -go 1.13 +go 1.22.1 + +toolchain go1.22.6 require ( + github.com/gorilla/websocket v1.5.3 github.com/stretchr/testify v1.4.0 - gopkg.in/yaml.v2 v2.2.7 +) + +require ( + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/nikonov1101/colors.go v0.0.0-20241018142902-837c37d48f63 + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v2 v2.2.7 // indirect ) diff --git a/go.sum b/go.sum index 4005ec1..9e407d9 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,15 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/nikonov1101/colors.go v0.0.0-20241018142902-837c37d48f63 h1:UMaWSI0awqe9dVkgy4Ki7T9Y3IzVwoAXcIpFHG5F2sY= +github.com/nikonov1101/colors.go v0.0.0-20241018142902-837c37d48f63/go.mod h1:PWytY7Q83xkVQbHTlwGpe+vTlSWoLUi8sJTNXzVACg0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= diff --git a/test.asm b/test.asm index d489c74..bd08267 100644 --- a/test.asm +++ b/test.asm @@ -12,7 +12,30 @@ NOP ; check pop POP r5 ; check mem load -MOV r3, $0101 +LOAD r3 $1a2b + +MOV r2 #aa +STORE $1a2c r2 + +;test conditionals +MOV r3 #1 +MOV r4 #a1 +CP r3 r4 +JNE $001A + +NOP +NOP +NOP +NOP +NOP + +.text $001A +INC r3 +INC r3 +CP r3 #3 +JE $01FF + + NOP ; check jump JUMP $00FF @@ -22,6 +45,12 @@ JUMP $00FF .text $00FF HALT +.text $01FF + HALT + +.text $02FF + HALT + ; place random value at $0100 ; check that we can compile .byte's .byte $0101 #42