Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
uasm
uvm
*.bin
*.hex
*.rom

19 changes: 15 additions & 4 deletions asm/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand All @@ -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?
Expand Down
21 changes: 16 additions & 5 deletions asm/syntax.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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
Expand All @@ -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
Expand Down
49 changes: 49 additions & 0 deletions assets/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>WebSocket Example</title>
</head>
<body>
<img style="width: 800px; height: 600px;" id="video1"/>

<script type="text/javascript">
function swapImage(imgElement, newImageUrl) {
// Create a new Image object for preloading
const tempImage = new Image();

// Set the `onload` event to update the `src` of the target image
tempImage.onload = function () {
imgElement.src = newImageUrl; // Swap image content after preload
};

// Set the `src` of the temporary image to start preloading
tempImage.src = newImageUrl;
}

(function() {
var conn = new WebSocket("ws://localhost:8080/ws");
conn.binaryType = 'arraybuffer';
const i = document.getElementById("video1");

conn.onclose = function(evt) {
console.log("closed.");
}
conn.onopen = function() {
console.log("ws: opened");
}
conn.onerror = (error) => {
console.error('WebSocket error:', error);
};

conn.onmessage = function(evt) {
if (event.data instanceof ArrayBuffer) {
// console.log('got update');
const blob = new Blob([event.data], { type: 'image/png' });
const imageUrl = URL.createObjectURL(blob);
swapImage(i, imageUrl);
}
}
})();
</script>
</body>
</html>
14 changes: 5 additions & 9 deletions cmd/uvm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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()
}
151 changes: 102 additions & 49 deletions cpu/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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
Expand Down Expand Up @@ -122,16 +138,53 @@ 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})
}
}

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("=============================================")
}
Loading