├── .gitignore ├── LICENSE ├── README.md ├── assemble ├── asm.go ├── asm_test.go ├── assembler │ └── main.go ├── instructions.go ├── labels.go ├── misc.go ├── octavecpu.vim ├── pseudo_asm.go ├── pseudo_asm_test.go └── pseudo_instructions.go ├── brainfuck.oct ├── hello-world.oct ├── octave.go └── run.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | octave 3 | assemble/assembler/assembler 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Cameron Paul, Benjamin Bariteau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # octave 2 | 3 | An 8-bit CPU designed for education 4 | 5 | ## ISA 6 | 7 | ### Jump 8 | 9 | Format | Operation 10 | ---------- | ----------------- 11 | 000 RS NZP | PC <- MEM[RS] NZP 12 | 13 | ### Load Immediate 14 | 15 | Format | Operation 16 | --------- | ------------- 17 | 0010 IIII | R0[HIGH] <- I 18 | 0011 IIII | R0[LOW] <- I 19 | 20 | ### ALU Operations 21 | 22 | Format | Operation 23 | ---------- | ------------- 24 | 0100 RD RS | RD <- RD + RS 25 | 0101 RD RS | RD <- RS 26 | 0110 RD RS | RD <- RD & RS 27 | 0111 RD RS | RD <- RD ^ RS 28 | 29 | ### Memory Operations 30 | 31 | Format | Operation 32 | ---------- | ----------------- 33 | 1000 RX RY | R0 <- MEM[RX, RY] 34 | 1001 RX RY | MEM[RX, RY] <- R0 35 | 36 | ### Stack Operations 37 | 38 | Format | Operation 39 | ---------- | ----------- 40 | 10100000 | add8 41 | 10100001 | sub8 42 | 10100010 | mul8 43 | 10100011 | div8 44 | 10100100 | mod8 45 | 10100101 | neg8 46 | 10100110 | and8 47 | 10100111 | or8 48 | 10101000 | xor8 49 | 10101001 | not8 50 | 10101010 | add16 51 | 10101011 | sub16 52 | 10101100 | mul16 53 | 10101101 | div16 54 | 10101110 | mod16 55 | 10101111 | neg16 56 | 10110000 | and16 57 | 10110001 | or16 58 | 10110010 | xor16 59 | 10110011 | not16 60 | 10110100 | call 61 | 10110101 | trap 62 | 10110110 | ret 63 | 10110111 | iret 64 | 10111000 | int0 enable 65 | 10111001 | int1 enable 66 | 10111010 | int2 enable 67 | 10111011 | int3 enable 68 | 10111100 | int4 enable 69 | 10111101 | int5 enable 70 | 10111110 | int6 enable 71 | 10111111 | int7 enable 72 | 73 | ### Device IO 74 | 75 | Format | Operation 76 | ---------- | ------------- 77 | 110 RD DV | RD <- DEV[DV] 78 | 111 DV RS | DEV[DV] <- RS 79 | 80 | ## Assembly Hello World 81 | 82 | ```assembly 83 | ; Octave CPU - Hello World 84 | 85 | ; R2R1 ← string 86 | LAA R2, R1, string 87 | 88 | loop: 89 | ; R0 ← [R2R1] 90 | LOAD R2, R1 91 | 92 | ; IF R0 == 0, JMP exit 93 | XOR R3, R3 94 | ADD R3, R0 95 | LRA exit 96 | JMP R0 Z 97 | 98 | ; PRINT R3 99 | OUT R3, 1 100 | 101 | ; INC R1 102 | LOADI 0x01 103 | ADD R1, R0 104 | 105 | ; JMP loop 106 | LRA loop 107 | JMP R0 NZP 108 | 109 | exit: 110 | HALT 111 | 112 | string: 113 | BYTES "Hello world!\n" 114 | BYTE 0x00 115 | ``` 116 | -------------------------------------------------------------------------------- /assemble/asm.go: -------------------------------------------------------------------------------- 1 | package assemble 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "regexp" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | var codeToFunc = map[string]func(string) instruction{ 14 | "JMP": assembleJmp, 15 | "LOADIH": assembleLoadi, 16 | "LOADIL": assembleLoadi, 17 | "ADD": assembleTwoRegister, 18 | "MOV": assembleTwoRegister, 19 | "AND": assembleTwoRegister, 20 | "XOR": assembleTwoRegister, 21 | "LOAD": assembleTwoRegister, 22 | "STORE": assembleTwoRegister, 23 | "STACKOP": assembleStackop, 24 | "IN": assembleDeviceIO, 25 | "OUT": assembleDeviceIO, 26 | "BYTE": assembleByte, 27 | "HALT": assembleHalt, 28 | "ADD8": assembleStackopAlias, 29 | "SUB8": assembleStackopAlias, 30 | "MUL8": assembleStackopAlias, 31 | "DIV8": assembleStackopAlias, 32 | "MOD8": assembleStackopAlias, 33 | "NEG8": assembleStackopAlias, 34 | "AND8": assembleStackopAlias, 35 | "OR8": assembleStackopAlias, 36 | "XOR8": assembleStackopAlias, 37 | "NOT8": assembleStackopAlias, 38 | "ADD16": assembleStackopAlias, 39 | "SUB16": assembleStackopAlias, 40 | "MUL16": assembleStackopAlias, 41 | "DIV16": assembleStackopAlias, 42 | "MOD16": assembleStackopAlias, 43 | "NEG16": assembleStackopAlias, 44 | "AND16": assembleStackopAlias, 45 | "OR16": assembleStackopAlias, 46 | "XOR16": assembleStackopAlias, 47 | "NOT16": assembleStackopAlias, 48 | "CALL": assembleStackopAlias, 49 | "TRAP": assembleStackopAlias, 50 | "RET": assembleStackopAlias, 51 | "IRET": assembleStackopAlias, 52 | "INT0E": assembleStackopAlias, 53 | "INT1E": assembleStackopAlias, 54 | "INT2E": assembleStackopAlias, 55 | "INT3E": assembleStackopAlias, 56 | "INT4E": assembleStackopAlias, 57 | "INT5E": assembleStackopAlias, 58 | "INT6E": assembleStackopAlias, 59 | "INT7E": assembleStackopAlias, 60 | "PUSH": assemblePushPop, 61 | "POP": assemblePushPop, 62 | } 63 | 64 | func Assemble(in io.Reader) (bytes []byte, err error) { 65 | /*defer func() { 66 | if r := recover(); r != nil { 67 | err = r.(error) 68 | } 69 | }()*/ 70 | r := bufio.NewReader(in) 71 | instructions, _, labels := convertToInstructions(r) 72 | pc := uint(0) 73 | for _, i := range instructions { 74 | switch i := i.(type) { 75 | case instruction: 76 | bytes = append(bytes, i.assemble()) 77 | pc++ 78 | case pseudoinst: 79 | for _, inst := range i.translate(labels, pc) { 80 | bytes = append(bytes, inst.assemble()) 81 | } 82 | pc += i.size() 83 | } 84 | } 85 | return bytes, nil 86 | } 87 | 88 | func convertToInstructions(in *bufio.Reader) (instructions []interface{}, size uint, labels map[string]uint) { 89 | labels = make(map[string]uint) 90 | for line, err := in.ReadString('\n'); err != io.EOF; line, err = in.ReadString('\n') { 91 | line = strings.TrimSpace(line) 92 | if err != nil { 93 | panic(err) 94 | } 95 | 96 | if len(line) == 0 || line[0] == ';' { 97 | continue 98 | } 99 | fields := strings.Fields(line) 100 | 101 | label := tryLabel(line) 102 | if label != "" { 103 | labels[label] = size 104 | continue 105 | } 106 | 107 | pinst := tryPseudo(line) 108 | if pinst.size() != 0 { 109 | size += pinst.size() 110 | instructions = append(instructions, pinst) 111 | continue 112 | } 113 | 114 | asmFunc, ok := codeToFunc[fields[0]] 115 | if !ok { 116 | panic(errors.New(fmt.Sprintf("'%v' is not a valid assembly instruction", fields[0]))) 117 | } 118 | instructions = append(instructions, asmFunc(line)) 119 | size++ 120 | } 121 | return 122 | } 123 | 124 | func assembleJmp(i string) instruction { 125 | j := jmp{} 126 | re := regexp.MustCompile("JMP R([0-3]) (N|)(Z|)(P|)") 127 | matches := re.FindStringSubmatch(i) 128 | if matches == nil { 129 | panic(errors.New("not a JMP")) 130 | } 131 | 132 | j.register = convertRegisterNum(matches[1]) 133 | 134 | j.negative = matches[2] == "N" 135 | j.zero = matches[3] == "Z" 136 | j.positive = matches[4] == "P" 137 | 138 | return j 139 | } 140 | 141 | func assembleLoadi(i string) instruction { 142 | in := loadi{} 143 | re := regexp.MustCompile("LOADI(L|H) (0x[0-9A-F]|0[0-7]+|[0-9]+)") 144 | matches := re.FindStringSubmatch(i) 145 | if matches == nil { 146 | panic(errors.New("not a LOADI(L|H)")) 147 | } 148 | lr := matches[1] 149 | in.low = (lr == "L") 150 | 151 | nibble, err := strconv.ParseUint(matches[2], 0, 4) 152 | if err != nil { 153 | panic(err) 154 | } 155 | in.nibble = uint8(nibble) 156 | return in 157 | } 158 | 159 | func assembleTwoRegister(i string) instruction { 160 | in := tworeg{} 161 | re := regexp.MustCompile("(ADD|MOV|AND|XOR|LOAD|STORE) R([0-3]), R([0-3])") 162 | matches := re.FindStringSubmatch(i) 163 | if matches == nil { 164 | panic(errors.New(i + " not a (ADD|MOV|AND|XOR|LOAD|STORE)")) 165 | } 166 | in.opcode = strToOpcode[matches[1]] 167 | in.dest = convertRegisterNum(matches[2]) 168 | in.src = convertRegisterNum(matches[3]) 169 | return in 170 | } 171 | 172 | func assembleStackop(i string) instruction { 173 | in := stackop{} 174 | re := regexp.MustCompile("STACKOP ([0-9]{1,2})") 175 | matches := re.FindStringSubmatch(i) 176 | if matches == nil { 177 | panic(errors.New("not a STACKOP")) 178 | } 179 | op, err := strconv.ParseUint(matches[1], 0, 5) 180 | if err != nil { 181 | panic(err) 182 | } 183 | in.op = uint8(op) 184 | return in 185 | } 186 | 187 | func assembleDeviceIO(i string) instruction { 188 | in := devio{} 189 | re := regexp.MustCompile("(IN|OUT) R([0-3]), ([0-7])") 190 | matches := re.FindStringSubmatch(i) 191 | if matches == nil { 192 | panic(errors.New("not a (IN|OUT)")) 193 | } 194 | in.opcode = strToDevioOpcode[matches[1]] 195 | in.register = convertRegisterNum(matches[2]) 196 | in.device = convertDeviceNum(matches[3]) 197 | return in 198 | } 199 | 200 | func assembleByte(i string) instruction { 201 | re := regexp.MustCompile("BYTE (.+)") 202 | matches := re.FindStringSubmatch(i) 203 | if matches == nil { 204 | panic(errors.New("not a BYTE")) 205 | } 206 | val, err := strconv.ParseUint(matches[1], 0, 8) 207 | if err != nil { 208 | panic(err) 209 | } 210 | return rawbyte{uint8(val)} 211 | } 212 | 213 | func assembleHalt(i string) instruction { 214 | return rawbyte{0} 215 | } 216 | 217 | var stackopAliases = map[string]uint8{ 218 | "ADD8": 0, 219 | "SUB8": 1, 220 | "MUL8": 2, 221 | "DIV8": 3, 222 | "MOD8": 4, 223 | "NEG8": 5, 224 | "AND8": 6, 225 | "OR8": 7, 226 | "XOR8": 8, 227 | "NOT8": 9, 228 | "ADD16": 10, 229 | "SUB16": 11, 230 | "MUL16": 12, 231 | "DIV16": 13, 232 | "MOD16": 14, 233 | "NEG16": 15, 234 | "AND16": 16, 235 | "OR16": 17, 236 | "XOR16": 18, 237 | "NOT16": 19, 238 | "CALL": 20, 239 | "TRAP": 21, 240 | "RET": 22, 241 | "IRET": 23, 242 | "INT0E": 24, 243 | "INT1E": 25, 244 | "INT2E": 26, 245 | "INT3E": 27, 246 | "INT4E": 28, 247 | "INT5E": 29, 248 | "INT6E": 30, 249 | "INT7E": 31, 250 | } 251 | 252 | func assembleStackopAlias(line string) instruction { 253 | return stackop{stackopAliases[line]} 254 | } 255 | 256 | func assemblePushPop(line string) instruction { 257 | re := regexp.MustCompile("(PUSH|POP)[ \\t]R([0-3])") 258 | matches := re.FindStringSubmatch(line) 259 | 260 | if matches == nil { 261 | panic(errors.New("not a (PUSH|POP)")) 262 | } 263 | 264 | reg := convertRegisterNum(matches[2]) 265 | if matches[1] == "PUSH" { 266 | return devio{out, reg, 0} 267 | } 268 | return devio{in, reg, 0} 269 | } 270 | -------------------------------------------------------------------------------- /assemble/asm_test.go: -------------------------------------------------------------------------------- 1 | package assemble 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | type testcase struct { 10 | in string 11 | out byte 12 | } 13 | 14 | func TestAssemble(t *testing.T) { 15 | in := ` 16 | ; comment 17 | JMP R3 NZP 18 | LOADIH 0xF 19 | LOADIL 0xF 20 | ADD R0, R0 21 | MOV R3, R3 22 | AND R0, R0 23 | XOR R3, R3 24 | LOAD R0, R0 25 | STORE R0, R0 26 | STACKOP 0 27 | IN R0, 0 28 | OUT R3, 7 29 | ` 30 | expected := []byte{ 31 | binaryHelperByte("00011111"), 32 | binaryHelperByte("00101111"), 33 | binaryHelperByte("00111111"), 34 | binaryHelperByte("01000000"), 35 | binaryHelperByte("01011111"), 36 | binaryHelperByte("01100000"), 37 | binaryHelperByte("01111111"), 38 | binaryHelperByte("10000000"), 39 | binaryHelperByte("10010000"), 40 | binaryHelperByte("10100000"), 41 | binaryHelperByte("11000000"), 42 | binaryHelperByte("11111111"), 43 | } 44 | out, err := Assemble(strings.NewReader(in)) 45 | if err != nil { 46 | t.Error("Unexpected error encountered", err) 47 | } 48 | if !reflect.DeepEqual(out, expected) { 49 | t.Error("Got", out, "expected", expected) 50 | } 51 | } 52 | 53 | func TestAssembleJmp(t *testing.T) { 54 | tests := []testcase{ 55 | testcase{"JMP R3 NZP", binaryHelperByte("00011111")}, 56 | testcase{"JMP R2 NZ", binaryHelperByte("00010110")}, 57 | testcase{"JMP R1 Z", binaryHelperByte("00001010")}, 58 | testcase{"JMP R0 P", binaryHelperByte("00000001")}, 59 | } 60 | for _, test := range tests { 61 | b := assembleJmp(test.in).assemble() 62 | if b != test.out { 63 | t.Error("Got", b, "expected", test.out) 64 | } 65 | } 66 | } 67 | 68 | func TestAssembleLoadi(t *testing.T) { 69 | tests := []testcase{ 70 | testcase{"LOADIL 0xF", binaryHelperByte("00111111")}, 71 | testcase{"LOADIH 017", binaryHelperByte("00101111")}, 72 | testcase{"LOADIH 15", binaryHelperByte("00101111")}, 73 | testcase{"LOADIL 0", binaryHelperByte("00110000")}, 74 | } 75 | for _, test := range tests { 76 | b := assembleLoadi(test.in).assemble() 77 | if b != test.out { 78 | t.Error("Got", b, "expected", test.out) 79 | } 80 | } 81 | } 82 | 83 | func TestAssembleTwoRegister(t *testing.T) { 84 | tests := []testcase{ 85 | testcase{"ADD R0, R0", binaryHelperByte("01000000")}, 86 | testcase{"MOV R1, R1", binaryHelperByte("01010101")}, 87 | testcase{"AND R2, R2", binaryHelperByte("01101010")}, 88 | testcase{"XOR R3, R3", binaryHelperByte("01111111")}, 89 | testcase{"LOAD R0, R3", binaryHelperByte("10000011")}, 90 | testcase{"STORE R1, R2", binaryHelperByte("10010110")}, 91 | } 92 | for _, test := range tests { 93 | b := assembleTwoRegister(test.in).assemble() 94 | if b != test.out { 95 | t.Error("Got", b, "expected", test.out) 96 | } 97 | } 98 | } 99 | 100 | func TestAssembleStackop(t *testing.T) { 101 | tests := []testcase{ 102 | testcase{"STACKOP 10", binaryHelperByte("10101010")}, 103 | } 104 | for _, test := range tests { 105 | b := assembleStackop(test.in).assemble() 106 | if b != test.out { 107 | t.Error("Got", b, "expected", test.out) 108 | } 109 | } 110 | } 111 | 112 | func TestAssembleDeviceIO(t *testing.T) { 113 | tests := []testcase{ 114 | testcase{"IN R0, 0", binaryHelperByte("11000000")}, 115 | testcase{"OUT R3, 7", binaryHelperByte("11111111")}, 116 | } 117 | for _, test := range tests { 118 | b := assembleDeviceIO(test.in).assemble() 119 | if b != test.out { 120 | t.Error("Got", b, "expected", test.out) 121 | } 122 | } 123 | } 124 | 125 | func TestAssembleByte(t *testing.T) { 126 | tests := []testcase{ 127 | testcase{"BYTE 0xFF", binaryHelperByte("11111111")}, 128 | testcase{"BYTE 32", binaryHelperByte("00100000")}, 129 | } 130 | for _, test := range tests { 131 | b := assembleByte(test.in).assemble() 132 | if b != test.out { 133 | t.Error("Got", b, "expected", test.out) 134 | } 135 | } 136 | } 137 | 138 | func TestAssembleStackopAlias(t *testing.T) { 139 | tests := []testcase{ 140 | testcase{"DIV8", binaryHelperByte("10100011")}, 141 | } 142 | for _, test := range tests { 143 | b := assembleStackopAlias(test.in).assemble() 144 | if b != test.out { 145 | t.Error("Got", b, "expected", test.out) 146 | } 147 | } 148 | } 149 | 150 | func TestAssemblePushPop(t *testing.T) { 151 | tests := []testcase{ 152 | testcase{"PUSH R0", binaryHelperByte("11100000")}, 153 | testcase{"POP R3", binaryHelperByte("11011000")}, 154 | } 155 | for _, test := range tests { 156 | b := assemblePushPop(test.in).assemble() 157 | if b != test.out { 158 | t.Error("Got", b, "expected", test.out, "for", test.in) 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /assemble/assembler/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/campaul/octave/assemble" 8 | ) 9 | 10 | func main() { 11 | args := os.Args 12 | if len(args) < 3 { 13 | fmt.Printf("Usage: %v [assembly_file] [output_file]\n", args[0]) 14 | return 15 | } 16 | file, err := os.Open(args[1]) 17 | if err != nil { 18 | fmt.Println("Unable to open file for reading: ", err) 19 | return 20 | } 21 | out, err := assemble.Assemble(file) 22 | if err != nil { 23 | fmt.Println("Error while assembling:", err) 24 | return 25 | } 26 | outfile, err := os.Create(args[2]) 27 | if err != nil { 28 | fmt.Println("Unable to open file for writing: ", err) 29 | return 30 | } 31 | _, err = outfile.Write(out) 32 | if err != nil { 33 | fmt.Println("Error while writing to file") 34 | return 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /assemble/instructions.go: -------------------------------------------------------------------------------- 1 | package assemble 2 | 3 | const ( 4 | add = 0x4 // 0100 5 | mov = 0x5 // 0101 6 | and = 0x6 // 0110 7 | xor = 0x7 // 0111 8 | load = 0x8 // 1000 9 | store = 0x9 // 1001 10 | ) 11 | 12 | const ( 13 | in = 0x6 //110 14 | out = 0x7 //111 15 | ) 16 | 17 | var strToOpcode = map[string]uint8{ 18 | "ADD": add, 19 | "MOV": mov, 20 | "AND": and, 21 | "XOR": xor, 22 | "LOAD": load, 23 | "STORE": store, 24 | } 25 | 26 | var strToDevioOpcode = map[string]uint8{ 27 | "IN": in, 28 | "OUT": out, 29 | } 30 | 31 | type instruction interface { 32 | assemble() byte 33 | } 34 | 35 | type jmp struct { 36 | register uint8 37 | negative bool 38 | zero bool 39 | positive bool 40 | } 41 | 42 | const jmpOpcode = 0x0 // 000 43 | func (j jmp) assemble() (b byte) { 44 | b |= jmpOpcode << 5 45 | b |= j.register << 3 46 | b |= bootToUint8(j.negative) << 2 47 | b |= bootToUint8(j.zero) << 1 48 | b |= bootToUint8(j.positive) 49 | return 50 | } 51 | 52 | func bootToUint8(b bool) uint8 { 53 | if b { 54 | return 1 55 | } 56 | return 0 57 | } 58 | 59 | type loadi struct { 60 | low bool 61 | nibble uint8 62 | } 63 | 64 | const loadiOpcode = 0x1 // 001 65 | func (i loadi) assemble() (b byte) { 66 | b |= loadiOpcode << 5 67 | b |= bootToUint8(i.low) << 4 68 | b |= i.nibble 69 | return 70 | } 71 | 72 | type tworeg struct { 73 | opcode uint8 74 | dest uint8 75 | src uint8 76 | } 77 | 78 | func (i tworeg) assemble() (b byte) { 79 | b |= i.opcode << 4 80 | b |= i.dest << 2 81 | b |= i.src 82 | return 83 | } 84 | 85 | type stackop struct { 86 | op uint8 87 | } 88 | 89 | const stackopOpcode = 0x5 // 101 90 | func (i stackop) assemble() (b byte) { 91 | b |= stackopOpcode << 5 92 | b |= i.op 93 | return 94 | } 95 | 96 | type devio struct { 97 | opcode uint8 98 | register uint8 99 | device uint8 100 | } 101 | 102 | func (i devio) assemble() (b byte) { 103 | b |= i.opcode << 5 104 | switch i.opcode { 105 | case in: 106 | b |= i.register << 3 107 | b |= i.device 108 | case out: 109 | b |= i.device << 2 110 | b |= i.register 111 | } 112 | return 113 | } 114 | 115 | type rawbyte struct { 116 | value uint8 117 | } 118 | 119 | func (i rawbyte) assemble() byte { 120 | return i.value 121 | } 122 | -------------------------------------------------------------------------------- /assemble/labels.go: -------------------------------------------------------------------------------- 1 | package assemble 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | func tryLabel(line string) string { 8 | re := regexp.MustCompile("([A-Za-z]+):") 9 | matches := re.FindStringSubmatch(line) 10 | if matches == nil { 11 | return "" 12 | } 13 | return matches[1] 14 | } 15 | -------------------------------------------------------------------------------- /assemble/misc.go: -------------------------------------------------------------------------------- 1 | package assemble 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | func binaryHelperByte(bitstr string) byte { 8 | num, err := strconv.ParseUint(bitstr, 2, 8) 9 | if err != nil { 10 | panic(err) 11 | } 12 | return byte(num) 13 | } 14 | 15 | func convertRegisterNum(s string) uint8 { 16 | return convertNum(s, 2) 17 | } 18 | 19 | func convertDeviceNum(s string) uint8 { 20 | return convertNum(s, 3) 21 | } 22 | 23 | func convertNum(s string, bitdepth int) uint8 { 24 | reg, err := strconv.ParseUint(s, 10, bitdepth) 25 | if err != nil { 26 | panic(err) 27 | } 28 | return uint8(reg) 29 | } 30 | -------------------------------------------------------------------------------- /assemble/octavecpu.vim: -------------------------------------------------------------------------------- 1 | " Vim syntax file 2 | " Language: octavecpu 3 | " Maintainer: The Octave CPU Team 4 | " Version: $Revision$ 5 | 6 | if version < 600 7 | syntax clear 8 | elseif exists("b:current_syntax") 9 | finish 10 | endif 11 | 12 | 13 | syn case match 14 | 15 | syn keyword octaveInstruction HALT WAITI INTE INTD JMP LJMP 16 | syn keyword octaveInstruction LOADI LOADIL LOADIH 17 | syn keyword octaveInstruction ADD DIV 18 | syn keyword octaveInstruction AND XOR 19 | syn keyword octaveInstruction LOAD STORE 20 | syn keyword octaveInstruction LRA LAA 21 | syn keyword octaveInstruction STACKOP 22 | syn keyword octaveInstruction IN OUT 23 | syn keyword octaveInstruction PUSH MOV 24 | syn keyword octaveInstruction NZP NZ NP ZP N Z P 25 | syn keyword octaveInstruction BYTE BYTES 26 | 27 | syn keyword octaveRegister R0 R1 R2 R3 28 | 29 | syn match octaveComment /;.*$/ 30 | syn match octaveLabelIdentifier /[a-zA-z]/ 31 | syn match octaveLabel /[a-zA-Z]*:/ 32 | 33 | syn match octaveConstant /\d\+/ 34 | syn match octaveConstant /0x\x/ 35 | syn match octaveConstant /0x\x\x/ 36 | 37 | syn region octaveString start=/"/ skip=/\\"/ end=/"/ 38 | 39 | if version >= 508 || !exists("did_c_syn_inits") 40 | if version < 508 41 | let did_c_syn_inits = 1 42 | command -nargs=+ HiLink hi link 43 | else 44 | command -nargs=+ HiLink hi def link 45 | endif 46 | 47 | HiLink octaveInstruction Keyword 48 | HiLink octaveRegister Identifier 49 | HiLink octaveComment Comment 50 | HiLink octaveLabel Label 51 | HiLink octaveConstant Number 52 | HiLink octaveString String 53 | HiLink octaveLabelIdentifier Identifier 54 | 55 | delcommand HiLink 56 | endif 57 | 58 | let b:current_syntax = "octavecpu" 59 | -------------------------------------------------------------------------------- /assemble/pseudo_asm.go: -------------------------------------------------------------------------------- 1 | package assemble 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | var pseudoMap = map[string]func(string) pseudoinst{ 12 | "LOADI": generateLoadi, 13 | "LRA": generateLra, 14 | "LAA": generateLaa, 15 | "BYTES": generateBytes, 16 | "FILL": generateFill, 17 | "LJMP": generateLjmp, 18 | } 19 | 20 | func tryPseudo(line string) pseudoinst { 21 | fields := strings.Fields(line) 22 | if len(fields) == 0 { 23 | return dummy{} 24 | } 25 | f, ok := pseudoMap[fields[0]] 26 | if !ok { 27 | return dummy{} 28 | } 29 | return f(line) 30 | } 31 | 32 | func generateLoadi(line string) pseudoinst { 33 | l := loadimm{} 34 | re := regexp.MustCompile("LOADI (0x[0-9A-F]{1,2}|[0-9]+)") 35 | matches := re.FindStringSubmatch(line) 36 | if matches == nil { 37 | panic(errors.New(fmt.Sprintf("'%v' not a LOADI", line))) 38 | } 39 | val, err := strconv.ParseUint(matches[1], 0, 8) 40 | if err != nil { 41 | panic(err) 42 | } 43 | l.val = uint8(val) 44 | return l 45 | } 46 | 47 | func generateLra(line string) pseudoinst { 48 | l := lra{} 49 | re := regexp.MustCompile("LRA ([A-Za-z]+)") 50 | matches := re.FindStringSubmatch(line) 51 | if matches == nil { 52 | panic(errors.New("not an LRA")) 53 | } 54 | l.label = matches[1] 55 | return l 56 | } 57 | 58 | func generateLaa(line string) pseudoinst { 59 | l := laa{} 60 | re := regexp.MustCompile("LAA R([0-3]), R([0-3]), ([A-Za-z]+)") 61 | matches := re.FindStringSubmatch(line) 62 | if matches == nil { 63 | panic(errors.New("not an LAA")) 64 | } 65 | l.highreg = convertRegisterNum(matches[1]) 66 | l.lowreg = convertRegisterNum(matches[2]) 67 | l.label = matches[3] 68 | return l 69 | } 70 | 71 | func generateBytes(line string) pseudoinst { 72 | re := regexp.MustCompile("BYTES (\".*\")") 73 | matches := re.FindStringSubmatch(line) 74 | if matches == nil { 75 | panic(errors.New("not a BYTES")) 76 | } 77 | s, err := strconv.Unquote(matches[1]) 78 | if err != nil { 79 | panic(err) 80 | } 81 | return rawbytes{[]byte(s)} 82 | } 83 | 84 | func generateFill(line string) pseudoinst { 85 | re := regexp.MustCompile("FILL[ \\t]+([0-9]+)") 86 | matches := re.FindStringSubmatch(line) 87 | if matches == nil { 88 | panic(errors.New("not a FILL")) 89 | } 90 | num, err := strconv.ParseUint(matches[1], 10, 16) 91 | if err != nil { 92 | panic(err) 93 | } 94 | return fill{uint16(num)} 95 | } 96 | 97 | func generateLjmp(line string) pseudoinst { 98 | lj := ljump{} 99 | re := regexp.MustCompile("LJMP ([A-Za-z]+) (N|)(Z|)(P|)") 100 | matches := re.FindStringSubmatch(line) 101 | if matches == nil { 102 | panic(errors.New(fmt.Sprintf("%v, not a LJMP", line))) 103 | } 104 | lj.label = matches[1] 105 | lj.neg = matches[2] == "N" 106 | lj.zero = matches[3] == "Z" 107 | lj.pos = matches[4] == "P" 108 | return lj 109 | } 110 | -------------------------------------------------------------------------------- /assemble/pseudo_asm_test.go: -------------------------------------------------------------------------------- 1 | package assemble 2 | 3 | import ( 4 | "reflect" 5 | "strconv" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | type ptestcase struct { 11 | in string 12 | out []instruction 13 | } 14 | 15 | func TestGenerateLoadi(t *testing.T) { 16 | tests := []ptestcase{ 17 | ptestcase{"LOADI 0xFF", []instruction{loadi{true, 0xF}, loadi{false, 0xF}}}, 18 | ptestcase{"LOADI 0xF0", []instruction{loadi{true, 0x0}, loadi{false, 0xF}}}, 19 | } 20 | for _, test := range tests { 21 | out := generateLoadi(test.in).translate(map[string]uint{}, 0) 22 | if !reflect.DeepEqual(out, test.out) { 23 | t.Error("Got", out, "expected", test.out) 24 | } 25 | } 26 | } 27 | 28 | func bytesToAsm(bytes []byte) string { 29 | str := "[ " 30 | for _, b := range bytes { 31 | str += strconv.FormatUint(uint64(b), 2) + " " 32 | } 33 | str += "]" 34 | return str 35 | } 36 | 37 | func TestBytes(t *testing.T) { 38 | bytes, err := Assemble(strings.NewReader(` 39 | BYTES "butt\n" 40 | `)) 41 | if err != nil { 42 | t.Error("Unexpected error", err) 43 | } 44 | expected := []byte("butt\n") 45 | if !reflect.DeepEqual(bytes, expected) { 46 | t.Error("Got", bytesToAsm(bytes), "expected", bytesToAsm(expected)) 47 | } 48 | } 49 | 50 | func TestFill(t *testing.T) { 51 | bytes, err := Assemble(strings.NewReader(` 52 | FILL 5 53 | `)) 54 | if err != nil { 55 | t.Error("Unexpected error", err) 56 | } 57 | expected := []byte{0, 0, 0, 0, 0} 58 | if !reflect.DeepEqual(bytes, expected) { 59 | t.Error("Got", bytesToAsm(bytes), "expected", bytesToAsm(expected)) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /assemble/pseudo_instructions.go: -------------------------------------------------------------------------------- 1 | package assemble 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | type pseudoinst interface { 9 | translate(labels map[string]uint, pc uint) []instruction 10 | size() uint 11 | } 12 | 13 | type dummy struct{} 14 | 15 | func (d dummy) translate(labels map[string]uint, pc uint) []instruction { 16 | return []instruction{} 17 | } 18 | 19 | func (d dummy) size() uint { 20 | return 0 21 | } 22 | 23 | type loadimm struct { 24 | val uint8 25 | } 26 | 27 | func (i loadimm) translate(labels map[string]uint, pc uint) []instruction { 28 | return []instruction{ 29 | loadi{true, i.val & 0xF}, 30 | loadi{false, (i.val & 0xF0) >> 4}, 31 | } 32 | } 33 | 34 | func (i loadimm) size() uint { 35 | return 2 36 | } 37 | 38 | type lra struct { 39 | dest uint8 40 | label string 41 | } 42 | 43 | func (i lra) translate(labels map[string]uint, pc uint) []instruction { 44 | addr, ok := labels[i.label] 45 | if !ok { 46 | panic(errors.New(fmt.Sprint("%v label not found", i.label))) 47 | } 48 | offset := uint8(int(addr) - int(pc+i.size()+1)) 49 | insts := []instruction{} 50 | insts = append(insts, loadimm{offset}.translate(labels, pc)...) 51 | return insts 52 | } 53 | 54 | func (i lra) size() uint { 55 | return 2 56 | } 57 | 58 | type laa struct { 59 | highreg uint8 60 | lowreg uint8 61 | label string 62 | } 63 | 64 | func (i laa) translate(labels map[string]uint, pc uint) []instruction { 65 | addr, ok := labels[i.label] 66 | if !ok { 67 | panic(errors.New(fmt.Sprint("%v label not found", i.label))) 68 | } 69 | insts := []instruction{} 70 | if i.hasR0() { 71 | if !i.highR0() { 72 | insts = append(insts, loadimm{uint8((addr & 0xFF00) >> 8)}.translate(labels, pc)...) 73 | insts = append(insts, tworeg{mov, i.highreg, 0}) 74 | } 75 | insts = append(insts, loadimm{uint8(addr & 0xFF)}.translate(labels, pc)...) 76 | insts = append(insts, tworeg{mov, i.lowreg, 0}) 77 | if i.highR0() { 78 | insts = append(insts, loadimm{uint8((addr & 0xFF00) >> 8)}.translate(labels, pc)...) 79 | insts = append(insts, tworeg{mov, i.highreg, 0}) 80 | } 81 | } else { 82 | insts = append(insts, loadimm{uint8((addr & 0xFF00) >> 8)}.translate(labels, pc)...) 83 | insts = append(insts, tworeg{mov, i.highreg, 0}) 84 | insts = append(insts, loadimm{uint8(addr & 0xFF)}.translate(labels, pc)...) 85 | insts = append(insts, tworeg{mov, i.lowreg, 0}) 86 | } 87 | return insts 88 | } 89 | 90 | func (i laa) hasR0() bool { 91 | return i.highreg != 0 || i.lowreg != 0 92 | } 93 | 94 | func (i laa) highR0() bool { 95 | return i.highreg == 0 96 | } 97 | 98 | func (i laa) size() uint { 99 | return 2 + 1 + 2 + 1 100 | } 101 | 102 | type rawbytes struct { 103 | bytes []byte 104 | } 105 | 106 | func (i rawbytes) translate(labels map[string]uint, pc uint) (out []instruction) { 107 | for _, b := range i.bytes { 108 | out = append(out, rawbyte{b}) 109 | } 110 | return 111 | } 112 | 113 | func (i rawbytes) size() uint { 114 | return uint(len(i.bytes)) 115 | } 116 | 117 | type fill struct { 118 | num uint16 119 | } 120 | 121 | func (in fill) translate(labels map[string]uint, pc uint) (out []instruction) { 122 | for i := 0; i < int(in.num); i++ { 123 | out = append(out, rawbyte{0}) 124 | } 125 | return 126 | } 127 | 128 | func (i fill) size() uint { 129 | return uint(i.num) 130 | } 131 | 132 | type ljump struct { 133 | label string 134 | neg bool 135 | zero bool 136 | pos bool 137 | } 138 | 139 | func (i ljump) translate(labels map[string]uint, pc uint) (o []instruction) { 140 | addr, ok := labels[i.label] 141 | if !ok { 142 | panic(errors.New(fmt.Sprint("%v label not found", i.label))) 143 | } 144 | 145 | o = append(o, loadimm{7}.translate(labels, pc)...) 146 | if i.isUnconditional() { 147 | o = append(o, jmp{0, !i.neg, !i.zero, !i.pos}) 148 | } 149 | o = append(o, loadimm{uint8(addr & 0xFF)}.translate(labels, pc)...) 150 | o = append(o, devio{out, 0, 0}) 151 | o = append(o, loadimm{uint8((addr & 0xFF00) >> 8)}.translate(labels, pc)...) 152 | o = append(o, devio{out, 0, 0}) 153 | o = append(o, stackop{22}) 154 | return 155 | } 156 | 157 | func (i ljump) size() (s uint) { 158 | s = 2 + 2 + 1 + 2 + 1 + 1 159 | if i.isUnconditional() { 160 | s += 1 161 | } 162 | return 163 | } 164 | 165 | func (i ljump) isUnconditional() bool { 166 | return !(i.neg && i.zero && i.pos) 167 | } 168 | -------------------------------------------------------------------------------- /brainfuck.oct: -------------------------------------------------------------------------------- 1 | LJMP trampoline NZP 2 | 3 | code: 4 | BYTES "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++." 5 | ;BYTES ".[-]>." 6 | ;BYTES ".>.>.>.>.>" 7 | BYTE 0x0 8 | 9 | trampoline: 10 | LJMP start NZP 11 | 12 | data: 13 | BYTE 0x0 14 | BYTE 0x0 15 | BYTE 0x0 16 | BYTE 0x0 17 | BYTE 0x0 18 | BYTE 0x0 19 | BYTE 0x0 20 | BYTE 0x0 21 | BYTE 0x0 22 | BYTE 0x0 23 | BYTE 0x0 24 | BYTE 0x0 25 | BYTE 0x0 26 | BYTE 0x0 27 | BYTE 0x0 28 | BYTE 0x0 29 | BYTE 0x0 30 | BYTE 0x0 31 | BYTE 0x0 32 | BYTE 0x0 33 | BYTE 0x0 34 | BYTE 0x0 35 | 36 | start: 37 | LAA R2, R1, code 38 | LAA R2, R3, data 39 | 40 | ; -1 is 0xFF 41 | ; R2 is program counter 42 | ; R3 is data pointer 43 | ; 0x3E = > Increment data pointer 44 | ; 0x3C = < Decrement data pointer 45 | ; 0x2B = + Increment byte at R3 46 | ; 0x2D = - Decrement byte at R3 47 | ; 0x2E = . OUT [R3], 1 48 | ; 0x2C = , IN [R3], 1 49 | ; 0x5b = [ 50 | ; 0x5d = ] 51 | 52 | bf: 53 | XOR R2, R2 54 | LOAD R2, R1 55 | MOV R2, R0 56 | 57 | LOADI 0xC2 ; -3E 58 | ADD R0, R2 59 | LJMP handleIncData Z 60 | 61 | LOADI 0xC4 ; -3C 62 | ADD R0, R2 63 | LJMP handleDecData Z 64 | 65 | LOADI 0xD2 ; -2E 66 | ADD R0, R2 67 | LJMP handlePrint Z 68 | 69 | LOADI 0xD5 ; -2B 70 | ADD R0, R2 71 | LJMP handleInc Z 72 | 73 | LOADI 0xD3 ; -2B 74 | ADD R0, R2 75 | LJMP handleDec Z 76 | 77 | LOADI 0xA5 ; -5B 78 | ADD R0, R2 79 | LJMP handleOpenBrace Z 80 | 81 | LOADI 0xA3 ; -5D 82 | ADD R0, R2 83 | LJMP handleCloseBrace Z 84 | 85 | XOR R0, R0 ; NUL byte -> exit 86 | ADD R0, R2 87 | LJMP exit Z 88 | 89 | LJMP incProg NZP 90 | 91 | exit: 92 | HALT 93 | 94 | incProg: 95 | LOADI 0x01 96 | ADD R1, R0 97 | 98 | LJMP bf NZP 99 | 100 | handleIncData: 101 | LOADI 0x01 102 | ADD R3, R0 103 | 104 | LJMP incProg NZP 105 | 106 | handleDecData: 107 | LOADI 0xFF 108 | ADD R3, R0 109 | 110 | LJMP incProg NZP 111 | 112 | handleInc: 113 | XOR R2, R2 114 | LOAD R2, R3 115 | 116 | OUT R1, 0 ; Push program counter 117 | 118 | MOV R1, R0 119 | LOADI 0x01 120 | ADD R0, R1 121 | STORE R2, R3 122 | 123 | IN R1, 0 ; Pop program counter 124 | 125 | LJMP incProg NZP 126 | 127 | handleDec: 128 | XOR R2, R2 129 | LOAD R2, R3 130 | 131 | OUT R1, 0 ; Push program counter 132 | 133 | MOV R1, R0 134 | LOADI 0xFF 135 | ADD R0, R1 136 | STORE R2, R3 137 | 138 | IN R1, 0 ; Pop program counter 139 | 140 | LJMP incProg NZP 141 | 142 | handleOpenBrace: 143 | XOR R2, R2 144 | LOAD R2, R3 145 | AND R0, R0 146 | LJMP incProg NP 147 | 148 | OUT R3, 0 149 | ; Find next matching ] 150 | LOADI 0x01 151 | MOV R3, R0 152 | 153 | openBraceLoop: 154 | LOADI 0x01 155 | ADD R1, R0 156 | 157 | XOR R2, R2 158 | LOAD R2, R1 159 | MOV R2, R0 160 | 161 | ; R2 = [R1] 162 | 163 | LOADI 0xA5 ; -5B 164 | ADD R0, R2 165 | LJMP openBraceInc Z 166 | 167 | LOADI 0xA3 ; -5D 168 | ADD R0, R2 169 | LJMP openBraceDec Z 170 | 171 | LJMP openBraceLoop NZP 172 | 173 | openBraceInc: 174 | LOADI 0x01 175 | ADD R3, R0 176 | 177 | LJMP openBraceLoop NZP 178 | 179 | openBraceDec: 180 | LOADI 0xFF 181 | ADD R3, R0 182 | LJMP exitBraceLoop Z 183 | 184 | LJMP openBraceLoop NZP 185 | 186 | exitBraceLoop: 187 | IN R3, 0 188 | LJMP incProg NZP 189 | 190 | handleCloseBrace: 191 | XOR R2, R2 192 | LOAD R2, R3 193 | AND R0, R0 194 | LJMP incProg Z 195 | 196 | OUT R3, 0 197 | ; Find prev matching [ 198 | LOADI 0x01 199 | MOV R3, R0 200 | 201 | closeBraceLoop: 202 | LOADI 0xFF 203 | ADD R1, R0 204 | 205 | XOR R2, R2 206 | LOAD R2, R1 207 | MOV R2, R0 208 | 209 | ; R2 = [R1] 210 | 211 | LOADI 0xA3 ; -5D 212 | ADD R0, R2 213 | LJMP closeBraceInc Z 214 | 215 | LOADI 0xA5 ; -5B 216 | ADD R0, R2 217 | LJMP closeBraceDec Z 218 | 219 | LJMP closeBraceLoop NZP 220 | 221 | closeBraceInc: 222 | LOADI 0x01 223 | ADD R3, R0 224 | 225 | LJMP closeBraceLoop NZP 226 | 227 | closeBraceDec: 228 | LOADI 0xFF 229 | ADD R3, R0 230 | LJMP exitBraceLoop Z 231 | 232 | LJMP closeBraceLoop NZP 233 | 234 | handlePrint: 235 | XOR R2, R2 236 | LOAD R2, R3 237 | OUT R0, 1 238 | 239 | LJMP incProg NZP 240 | -------------------------------------------------------------------------------- /hello-world.oct: -------------------------------------------------------------------------------- 1 | ; Octave CPU - Hello World 2 | 3 | ; R2R1 ← string 4 | LAA R2, R1, string 5 | 6 | loop: 7 | ; R0 ← [R2R1] 8 | LOAD R2, R1 9 | 10 | ; IF R0 == 0, JMP exit 11 | XOR R3, R3 12 | ADD R3, R0 13 | LRA exit 14 | JMP R0 Z 15 | 16 | ; PRINT R3 17 | OUT R3, 1 18 | 19 | ; INC R1 20 | LOADI 0x01 21 | ADD R1, R0 22 | 23 | ; JMP loop 24 | LRA loop 25 | JMP R0 NZP 26 | 27 | exit: 28 | HALT 29 | 30 | string: 31 | BYTES "Hello world!\n" 32 | BYTE 0x00 33 | -------------------------------------------------------------------------------- /octave.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | fmt.Println("Initializing Octave CPU...") 12 | cpu := &CPU{running: true, sp: 0xFFFF} 13 | 14 | file, err := os.Open(os.Args[1]) 15 | 16 | if err != nil { 17 | return 18 | } 19 | 20 | defer file.Close() 21 | 22 | mem, err := ioutil.ReadAll(file) 23 | 24 | for i, val := range mem { 25 | cpu.memory[i] = val 26 | } 27 | 28 | cpu.stack = stack{cpu} 29 | cpu.devices[0] = cpu.stack 30 | cpu.devices[1] = tty{bufio.NewReader(os.Stdin)} 31 | cpu.devices[4] = register{} 32 | cpu.devices[5] = register{} 33 | cpu.devices[6] = register{} 34 | cpu.devices[7] = register{} 35 | 36 | if err != nil { 37 | return 38 | } 39 | 40 | for cpu.running { 41 | inst_byte := fetch(cpu) 42 | inst_func := decode(inst_byte) 43 | inst_func(inst_byte, cpu) 44 | } 45 | } 46 | 47 | type CPU struct { 48 | memory [1 << 16]uint8 49 | registers [4]uint8 50 | pc uint16 51 | sp uint16 52 | running bool 53 | result uint8 54 | devices [8]Device 55 | stack stack 56 | } 57 | 58 | type instruction func(uint8, *CPU) 59 | 60 | type Device interface { 61 | read() uint8 62 | write(uint8) 63 | } 64 | 65 | type tty struct { 66 | reader *bufio.Reader 67 | } 68 | 69 | func (t tty) read() uint8 { 70 | b, _ := t.reader.ReadByte() 71 | return b 72 | } 73 | 74 | func (t tty) write(char uint8) { 75 | fmt.Printf("%c", char) 76 | } 77 | 78 | type stack struct { 79 | cpu *CPU 80 | } 81 | 82 | func (s stack) read() uint8 { 83 | s.cpu.sp++ 84 | value := s.cpu.memory[s.cpu.sp] 85 | return value 86 | } 87 | 88 | func (s stack) write(char uint8) { 89 | s.cpu.memory[s.cpu.sp] = char 90 | s.cpu.sp-- 91 | } 92 | 93 | func fetch(cpu *CPU) uint8 { 94 | inst := cpu.memory[cpu.pc] 95 | cpu.pc = cpu.pc + 1 96 | return inst 97 | } 98 | 99 | type register struct { 100 | value uint8 101 | } 102 | 103 | func (r register) read() uint8 { 104 | return r.value 105 | } 106 | 107 | func (r register) write(char uint8) { 108 | r.value = char 109 | } 110 | 111 | func decode(i uint8) instruction { 112 | inst := illegal 113 | 114 | switch i >> 5 { 115 | case 0: 116 | fmt.Fprint(os.Stderr, "jmp\n") 117 | inst = jmp 118 | case 1: 119 | fmt.Fprint(os.Stderr, "loadi\n") 120 | inst = loadi 121 | case 2: 122 | fmt.Fprint(os.Stderr, "math\n") 123 | inst = math 124 | case 3: 125 | fmt.Fprint(os.Stderr, "logic\n") 126 | inst = logic 127 | case 4: 128 | fmt.Fprint(os.Stderr, "mem\n") 129 | inst = mem 130 | case 5: 131 | fmt.Fprint(os.Stderr, "stack\n") 132 | inst = stacki 133 | case 6: 134 | fmt.Fprint(os.Stderr, "in\n") 135 | inst = in 136 | case 7: 137 | fmt.Fprint(os.Stderr, "out\n") 138 | inst = out 139 | } 140 | 141 | return inst 142 | } 143 | 144 | func jmp(i uint8, cpu *CPU) { 145 | if i == 0 { 146 | cpu.running = false 147 | } 148 | 149 | register := i << 3 >> 6 150 | n := i << 5 >> 7 151 | z := i << 6 >> 7 152 | p := i << 7 >> 7 153 | 154 | if (n == 1 && cpu.result < 0) || (z == 1 && cpu.result == 0) || (p == 1 && cpu.result > 0) { 155 | offset := int8(cpu.registers[register]) 156 | fmt.Fprintf(os.Stderr, "Taking jump to %v\n", offset) 157 | cpu.pc = uint16(int32(cpu.pc) + int32(offset)) 158 | } 159 | } 160 | 161 | func loadi(i uint8, cpu *CPU) { 162 | location := i << 3 >> 7 163 | 164 | if location == 0 { 165 | cpu.registers[0] = (i << 4) | (cpu.registers[0] << 4 >> 4) 166 | } else { 167 | cpu.registers[0] = (i << 4 >> 4) | (cpu.registers[0] >> 4 << 4) 168 | } 169 | } 170 | 171 | func math(i uint8, cpu *CPU) { 172 | operation := i << 3 >> 7 173 | destination := i << 4 >> 6 174 | source := i << 6 >> 6 175 | 176 | if operation == 0 { 177 | cpu.registers[destination] = cpu.registers[destination] + cpu.registers[source] 178 | } else { 179 | cpu.registers[destination] = cpu.registers[source] 180 | } 181 | 182 | cpu.result = cpu.registers[destination] 183 | } 184 | 185 | func logic(i uint8, cpu *CPU) { 186 | operation := i << 3 >> 7 187 | destination := i << 4 >> 6 188 | source := i << 6 >> 6 189 | 190 | if operation == 0 { 191 | cpu.registers[destination] = cpu.registers[destination] & cpu.registers[source] 192 | } else { 193 | cpu.registers[destination] = cpu.registers[destination] ^ cpu.registers[source] 194 | } 195 | 196 | cpu.result = cpu.registers[destination] 197 | } 198 | 199 | func mem(i uint8, cpu *CPU) { 200 | operation := i << 3 >> 7 201 | address_high := i << 4 >> 6 202 | address_low := i << 6 >> 6 203 | address := uint16(cpu.registers[address_high])<<8 + uint16(cpu.registers[address_low]) 204 | 205 | if operation == 0 { 206 | // LOAD 207 | fmt.Fprintf(os.Stderr, "Loading %v\n to R0", address) 208 | cpu.registers[0] = cpu.memory[address] 209 | } else { 210 | // STORE 211 | fmt.Fprintf(os.Stderr, "Storing R0 to %v\n", address) 212 | cpu.memory[address] = cpu.registers[0] 213 | } 214 | } 215 | 216 | func pop16(s stack) uint16 { 217 | byte_1 := s.read() 218 | byte_2 := s.read() 219 | return uint16(byte_1)<<8 + uint16(byte_2) 220 | } 221 | 222 | func pop32(s stack) uint32 { 223 | byte_1 := s.read() 224 | byte_2 := s.read() 225 | byte_3 := s.read() 226 | byte_4 := s.read() 227 | return uint32(byte_1)<<24 + uint32(byte_2)<<16 + uint32(byte_3)<<8 + uint32(byte_4) 228 | } 229 | 230 | func push16(s stack, value uint16) { 231 | byte_1 := uint8(value >> 8) 232 | byte_2 := uint8(value << 8 >> 8) 233 | s.write(byte_2) 234 | s.write(byte_1) 235 | } 236 | 237 | func stacki(i uint8, cpu *CPU) { 238 | stacki := i << 3 >> 3 239 | 240 | switch stacki { 241 | case 0: 242 | // add16 243 | b := cpu.stack.read() 244 | a := cpu.stack.read() 245 | cpu.stack.write(a + b) 246 | case 1: 247 | // sub16 248 | b := cpu.stack.read() 249 | a := cpu.stack.read() 250 | cpu.stack.write(a - b) 251 | case 2: 252 | // mul16 253 | b := cpu.stack.read() 254 | a := cpu.stack.read() 255 | cpu.stack.write(a * b) 256 | case 3: 257 | // div16 258 | b := cpu.stack.read() 259 | a := cpu.stack.read() 260 | cpu.stack.write(a / b) 261 | case 4: 262 | // mod16 263 | b := cpu.stack.read() 264 | a := cpu.stack.read() 265 | cpu.stack.write(a % b) 266 | case 5: 267 | // neg16 268 | a := cpu.stack.read() 269 | cpu.stack.write(uint8(int8(a) * -1)) 270 | case 6: 271 | // and16 272 | b := cpu.stack.read() 273 | a := cpu.stack.read() 274 | cpu.stack.write(a & b) 275 | case 7: 276 | // or16 277 | b := cpu.stack.read() 278 | a := cpu.stack.read() 279 | cpu.stack.write(a | b) 280 | case 8: 281 | // xor16 282 | b := cpu.stack.read() 283 | a := cpu.stack.read() 284 | cpu.stack.write(a ^ b) 285 | case 9: 286 | // not16 287 | a := cpu.stack.read() 288 | cpu.stack.write(^a) 289 | case 10: 290 | case 11: 291 | case 12: 292 | case 13: 293 | case 14: 294 | case 15: 295 | case 16: 296 | case 17: 297 | case 18: 298 | case 19: 299 | case 20: 300 | // Get jump address off the stack 301 | new_pc_high := cpu.devices[0].read() 302 | new_pc_low := cpu.devices[0].read() 303 | new_pc := uint16(new_pc_high)<<8 + uint16(new_pc_low) 304 | 305 | // Push next address to the stack 306 | pc_high := cpu.pc >> 8 307 | pc_low := cpu.pc << 8 >> 8 308 | cpu.devices[0].write(uint8(pc_low)) 309 | cpu.devices[0].write(uint8(pc_high)) 310 | 311 | // Jump 312 | cpu.pc = new_pc 313 | case 21: 314 | // trap 315 | case 22: 316 | // Get return address off the stack 317 | pc_high := cpu.devices[0].read() 318 | pc_low := cpu.devices[0].read() 319 | pc := uint16(pc_high)<<8 + uint16(pc_low) 320 | 321 | // Jump 322 | cpu.pc = pc 323 | case 23: 324 | // iret 325 | default: 326 | // device := stacki - 24 327 | // TODO: enable device 328 | } 329 | } 330 | 331 | func in(i uint8, cpu *CPU) { 332 | device := i << 5 >> 5 333 | destination := i << 3 >> 6 334 | cpu.registers[destination] = cpu.devices[device].read() 335 | } 336 | 337 | func out(i uint8, cpu *CPU) { 338 | device := i << 3 >> 5 339 | source := i << 6 >> 6 340 | cpu.devices[device].write(cpu.registers[source]) 341 | } 342 | 343 | func illegal(i uint8, cpu *CPU) { 344 | } 345 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | stty -icanon; go run octave.go hello.bin 2 | --------------------------------------------------------------------------------