├── .gitignore ├── Makefile ├── Projectfile ├── README.adoc ├── samples ├── fib.rcs ├── gcd-fun.rcs ├── gcd.rcs ├── primes.rcs ├── video-opentechschool.rcs └── video.rcs ├── scripts ├── codemotion-logo-bw.png ├── serialize-logo.rb └── soundcloud-logo-bw.png ├── shard.lock ├── shard.yml ├── spec ├── all_spec.cr └── rcpu │ └── emulate │ └── cpu_spec.cr └── src ├── assemble └── main.cr └── emulate ├── cpu.cr ├── main.cr ├── mem.cr ├── reg.cr └── video.cr /.gitignore: -------------------------------------------------------------------------------- 1 | *.rcb 2 | .crystal/ 3 | libs/ 4 | rcpu-assemble 5 | rcpu-emulate 6 | rcpu-spec 7 | README.html 8 | .shards 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCES := $(shell find src -name '*.cr') 2 | SPEC_SOURCES := $(shell find spec -name '*.cr') 3 | 4 | .PHONY: all 5 | all: rcpu-assemble rcpu-emulate 6 | 7 | .PHONY: dependencies 8 | dependencies: .shards 9 | 10 | .PHONY: spec 11 | spec: rcpu-spec 12 | ./rcpu-spec 13 | 14 | .shards: shard.yml 15 | crystal deps 16 | 17 | rcpu-assemble: src/assemble/main.cr $(SOURCES) dependencies 18 | crystal build $< -o $@ 19 | 20 | rcpu-emulate: src/emulate/main.cr $(SOURCES) dependencies 21 | crystal build $< -o $@ 22 | 23 | rcpu-spec: $(SPEC_SOURCES) $(SOURCES) 24 | crystal build spec/all_spec.cr -o $@ 25 | 26 | .PHONY: clean 27 | clean: 28 | rm -rf .crystal 29 | rm -rf .shards 30 | rm -rf libs 31 | rm -f rcpu-assemble 32 | rm -f rcpu-emulate 33 | rm -f rcpu-spec 34 | -------------------------------------------------------------------------------- /Projectfile: -------------------------------------------------------------------------------- 1 | deps do 2 | github "weskinner/crystal-sdl2" 3 | end 4 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = RCPU 2 | :experimental: true 3 | :toc: 4 | :toc-placement!: 5 | 6 | link:http://github.com/ddfreyne/rcpu[_RCPU_] is a virtual machine emulator and assembler written in Crystal. 7 | 8 | CAUTION: This project is very much a work in progress. 9 | 10 | toc::[] 11 | 12 | == Building 13 | 14 | You will need link:http://crystal-lang.org/[Crystal] to build RCPU. Once you have Crystal, run kbd:[make], which will generate the compiled assembler and emulator binaries in the current directory. 15 | 16 | == Usage 17 | 18 | To assemble a file, use _rcpu-assemble_, passing the input filename (with the _rcs_ extension). This will generate a corresponding _rcb_ file. For example: 19 | 20 | % ./rcpu-assemble samples/fib.rcs 21 | 22 | To run a file, use _rcpu-emulate_, passing the input filename (with the _rcb_ extension). For example: 23 | 24 | % ./rcpu-emulate samples/fib.rcb 25 | 26 | The _rcs_ extension is used for **RC**PU **s**ource files, while _rcb_ is for **RC**PU **b**inary (i.e. compiled) files. 27 | 28 | == Tests 29 | 30 | Run kbd:[make spec] to compile and run the tests. 31 | 32 | == Architecture 33 | 34 | === Registers 35 | 36 | [options="header",cols="1,1,1,3"] 37 | |=== 38 | | Name | Code | Size (bytes) | Purpose 39 | | r0 | 0x00 | 4 | general-purpose 40 | | r1 | 0x01 | 4 | general-purpose 41 | | r2 | 0x02 | 4 | general-purpose 42 | | r3 | 0x03 | 4 | general-purpose 43 | | r4 | 0x04 | 4 | general-purpose 44 | | r5 | 0x05 | 4 | general-purpose 45 | | r6 | 0x06 | 4 | general-purpose 46 | | r7 | 0x07 | 4 | general-purpose 47 | | rpc | 0x08 | 4 | program counter (a.k.a instruction pointer) 48 | | rflags | 0x09 | 1 | contains result of `cmp(i)` 49 | | rsp | 0x0a | 4 | stack pointer 50 | | rbp | 0x0b | 4 | base pointer 51 | | rr | 0x0c | 4 | return value 52 | |=== 53 | 54 | === Instruction format 55 | 56 | Instructions are of variable length. 57 | 58 | The first byte is the opcode. Register arguments are one byte long, and label/immediate arguments are four bytes long. 59 | 60 | === Instruction set 61 | 62 | [options="header",cols="1,1,1,2"] 63 | |=== 64 | | Mnemonic | Opcode | Arguments | Effect 65 | 4+h|Function handling 66 | | `call(i)` | 0x01, 0x02 | reg/imm | 67 | | `ret` | 0x03 | (none) | 68 | 4+h|Stack management 69 | | `push(i)` | 0x04, 0x05 | reg/imm | push a0 onto stack 70 | | `pop` | 0x06 | reg | pop into a0 71 | 4+h|Branching 72 | | `j(i)` | 0x07, 0x08 | reg/imm | pc ← a0 73 | | `je(i)` | 0x09, 0x0a | reg/imm | if = then pc ← a0 74 | | `jne(i)` | 0x0b, 0x0c | reg/imm | if ≠ then pc ← a0 75 | | `jg(i)` | 0x0d, 0x0e | reg/imm | if > then pc ← a0 76 | | `jge(i)` | 0x0f, 0x10 | reg/imm | if ≥ then pc ← a0 77 | | `jl(i)` | 0x11, 0x12 | reg/imm | if < then pc ← a0 78 | | `jle(i)` | 0x13, 0x14 | reg/imm | if ≤ then pc ← a0 79 | 4+h|Arithmetic 80 | | `cmp(i)` | 0x15, 0x16 | reg, reg/imm | (see below) 81 | | `mod(i)` | 0x17, 0x18 | reg, reg, reg/imm | a0 ← a1 % a2 82 | | `add(i)` | 0x19, 0x1a | reg, reg, reg/imm | a0 ← a1 + a2 83 | | `sub(i)` | 0x1b, 0x1c | reg, reg, reg/imm | a0 ← a1 - a2 84 | | `mul(i)` | 0x1d, 0x1e | reg, reg, reg/imm | a0 ← a1 * a2 85 | | `div(i)` | 0x1f, 0x20 | reg, reg, reg/imm | a0 ← a1 / a2 86 | | `xor(i)` | 0x21, 0x22 | reg, reg, reg/imm | a0 ← a1 ^ a2 87 | | `or(i)` | 0x23, 0x24 | reg, reg, reg/imm | a0 ← a1 \| a2 88 | | `and(i)` | 0x25, 0x26 | reg, reg, reg/imm | a0 ← a1 & a2 89 | | `shl(i)` | 0x27, 0x28 | reg, reg, reg/imm | a0 ← a1 << a2 90 | | `shr(i)` | 0x29, 0x2a | reg, reg, reg/imm | a0 ← a1 >> a2 91 | | `not` | 0x2b | reg, reg | a0 ← ~a1 92 | 4+h|Register handling 93 | | `mov` | 0x2c | reg, reg | a0 ← a1 94 | | `li` | 0x2d | reg, imm | a0 ← a1 95 | 4+h|Memory handling 96 | | `lw` | 0x2e | reg, imm | (see below) 97 | | `lh` | 0x2f | reg, imm | (see below) 98 | | `lb` | 0x30 | reg, imm | (see below) 99 | | `sw` | 0x31 | reg, imm | (see below) 100 | | `sh` | 0x32 | reg, imm | (see below) 101 | | `sb` | 0x33 | reg, imm | (see below) 102 | 4+h|Special 103 | | `sleep` | 0xfd | imm | sleep for a0 ms 104 | | `prn` | 0xfe | reg | print a0 105 | | `halt` | 0xff | (none) | stops emulation 106 | |=== 107 | 108 | `cmp(i)` updates the `flags` register and sets the 0x01 bit to true if the arguments are equal, and the 0x02 bit to true if the first argument is greater than the second. 109 | 110 | `lw`, `lh` and `lb` load data from memory into a register. `lw` loads a word (4 bytes), `lh` loads a half word (2 bytes) and `lb` loads a byte. Similarly, `sw`, `sh` and `sb` store data from a register into memory. 111 | 112 | Several opcodes have an `(i)` variant. These variants take a four-byte immediate argument (meaning the data is encoded in the instruction) rather than a register name. For opcodes that have immediate variants, the _Opcode_ column contains the non-immediate variant followed by the immediate variant. 113 | 114 | Label arguments are identical to immediate arguments. 115 | 116 | === Memory layout 117 | 118 | [options="header",cols="1,3"] 119 | |=== 120 | | Range | Use 121 | | 0x00 – … | Program code 122 | | … – 0xffff | Stack (grows downward, word-aligned) 123 | | 0x00010000 - 0x00014b00 | Video memory (160 by 120 pixels, 1 byte per pixel) 124 | |=== 125 | 126 | === Assembly language syntax 127 | 128 | A lines can be an instruction line, a label line, or a data directive line. Blank lines are ignored. 129 | 130 | Comments start with the `#` character and can appear anywhere on a line, including a blank line. For example: 131 | 132 | ---- 133 | # load coords 134 | li r2, 0 # x (in px) 135 | li r3, 0 # y (in px) 136 | ---- 137 | 138 | An instruction line starts with a tab character, followed by the instruction mnemonic, and arguments separated by commas. For example: 139 | 140 | ---- 141 | li r3, 0 # y (in px) 142 | jei @print-string-done 143 | addi rsp, rsp, 12 144 | ---- 145 | 146 | Register arguments are indicated with an `r` prefix (e.g. `rsp` or `r0`). 147 | 148 | Immediate values can be given in decimal (e.g. `123`), in hexadecimal (starting with `0x`, e.g. `0xfe`), or in binary (starting with `0b`, e.g. `0b10010000`). 149 | 150 | Label arguments start with the `@` character. 151 | 152 | A label line starts with an identifier, followed by a colon. For example: 153 | 154 | ---- 155 | print-string-loop: 156 | ---- 157 | 158 | A data directive line starts with a period, followed by the directive name, followed by optional arguments. For example: 159 | 160 | ---- 161 | .byte 0x73 # s 162 | .byte 0x6c # l 163 | .byte 0x65 # e 164 | .byte 0x65 # e 165 | .byte 0x70 # p 166 | 167 | .word @char-left-parenthesis # ( 168 | .word @char-right-parenthesis # ) 169 | .word @char-question-mark # * - TODO 170 | .word @char-question-mark # + - TODO 171 | .word @char-comma # , 172 | .word @char-dash # - 173 | .word @char-period # . 174 | .word @char-slash # / 175 | ---- 176 | 177 | The supported data directives are `.byte`, `.half` and `.word`; they insert a byte, a half word (two bytes) or a word (four bytes), respectively. 178 | 179 | See the examples in the _samples_ directory for inspiration. 180 | 181 | == To do 182 | 183 | * Finish implementing all opcodes 184 | * Tests 185 | * Line/column numbers in parser error messages 186 | * RCPU prefix in binaries 187 | -------------------------------------------------------------------------------- /samples/fib.rcs: -------------------------------------------------------------------------------- 1 | start: 2 | li r0, 1 3 | li r1, 0 4 | 5 | loop: 6 | mov r2, r1 7 | add r1, r0, r1 8 | mov r0, r2 9 | prn r0 10 | sleep 200 11 | ji @loop 12 | -------------------------------------------------------------------------------- /samples/gcd-fun.rcs: -------------------------------------------------------------------------------- 1 | pushi 819000 2 | pushi 254163 3 | calli @gcd 4 | prn rr 5 | addi rsp, rsp, 8 6 | 7 | pushi 1470 8 | pushi 1050 9 | calli @gcd 10 | prn rr 11 | addi rsp, rsp, 8 12 | 13 | halt 14 | 15 | gcd: 16 | push rbp 17 | addi rbp, rsp, 0 18 | push r0 19 | push r1 20 | push r2 21 | addi r0, rbp, 8 22 | addi r1, rbp, 12 23 | lw r0, r0 24 | lw r1, r1 25 | 26 | gcd-start: 27 | cmpi r1, 0 28 | jei @gcd-end 29 | addi r2, r1, 0 30 | mod r1, r0, r1 31 | addi r0, r2, 0 32 | ji @gcd-start 33 | 34 | gcd-end: 35 | addi rr, r0, 0 36 | pop r2 37 | pop r1 38 | pop r0 39 | pop rbp 40 | ret 41 | -------------------------------------------------------------------------------- /samples/gcd.rcs: -------------------------------------------------------------------------------- 1 | li r0, 819000 2 | li r1, 254163 3 | 4 | start: 5 | cmpi r1, 0 6 | jei @end 7 | addi r2, r1, 0 8 | mod r1, r0, r1 9 | addi r0, r2, 0 10 | ji @start 11 | 12 | end: 13 | prn r0 14 | halt 15 | -------------------------------------------------------------------------------- /samples/primes.rcs: -------------------------------------------------------------------------------- 1 | start: 2 | li r0, 2 3 | 4 | check-prime: 5 | li r1, 2 6 | 7 | check-factor: 8 | cmp r0, r1 9 | jei @prime-found 10 | mod r2, r0, r1 11 | cmpi r2, 0 12 | jei @next-prime 13 | addi r1, r1, 1 14 | ji @check-factor 15 | 16 | prime-found: 17 | prn r0 18 | 19 | next-prime: 20 | addi r0, r0, 1 21 | cmpi r0, 1000 22 | jli @check-prime 23 | halt 24 | -------------------------------------------------------------------------------- /scripts/codemotion-logo-bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denisdefreyne/rcpu/8c887c9510bbd509dcbd35f2f573da584de8cf59/scripts/codemotion-logo-bw.png -------------------------------------------------------------------------------- /scripts/serialize-logo.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'chunky_png' 4 | 5 | if ARGV.size != 1 6 | $stderr.puts "usage: #{$0} [filename]" 7 | exit 1 8 | end 9 | filename = ARGV[0] 10 | 11 | image = ChunkyPNG::Image.from_file(filename) 12 | puts image.pixels.map { |pixel| pixel <= 255 ? 0 : 1 }.map { |i| ".byte 0x0#{i}" }.join("\n") 13 | -------------------------------------------------------------------------------- /scripts/soundcloud-logo-bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denisdefreyne/rcpu/8c887c9510bbd509dcbd35f2f573da584de8cf59/scripts/soundcloud-logo-bw.png -------------------------------------------------------------------------------- /shard.lock: -------------------------------------------------------------------------------- 1 | version: 1.0 2 | shards: 3 | ssl: 4 | github: weskinner/crystal-sdl2 5 | commit: 2e175a173565f7112114cdc30a4b005abffa3270 6 | 7 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: rcpu 2 | version: 0.1.0 3 | 4 | dependencies: 5 | sdl2: 6 | github: weskinner/crystal-sdl2 7 | 8 | license: MIT 9 | -------------------------------------------------------------------------------- /spec/all_spec.cr: -------------------------------------------------------------------------------- 1 | require "./rcpu/emulate/cpu_spec" 2 | -------------------------------------------------------------------------------- /spec/rcpu/emulate/cpu_spec.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | 3 | require "../../../src/emulate/cpu" 4 | require "../../../src/emulate/mem" 5 | require "../../../src/emulate/reg" 6 | 7 | macro stack_pointer 8 | cpu.reg[Reg::RSP] 9 | end 10 | 11 | macro program_counter 12 | cpu.reg[Reg::RPC] 13 | end 14 | 15 | describe CPU do 16 | 17 | describe "initial CPU state" do 18 | mem = Mem.new 19 | cpu = CPU.new(mem) 20 | 21 | program_counter.should eq(0x0) 22 | stack_pointer.should eq(0xffff) 23 | end 24 | 25 | describe "call" do 26 | it "pushes the return address and jumps" do 27 | mem = Mem.new 28 | mem[0_u32] = CPU::O_CALL 29 | mem[1_u32] = Reg::R3 30 | 31 | cpu = CPU.new(mem) 32 | cpu.reg[Reg::R3] = 0x10204080_u32 33 | 34 | cpu.step 35 | program_counter.should eq(0x10204080) 36 | stack_pointer.should eq(0xfffb) 37 | end 38 | end 39 | 40 | describe "calli" do 41 | it "pushes the return address and jumps" do 42 | mem = Mem.new 43 | mem[0_u32] = CPU::O_CALLI 44 | mem[1_u32] = 1_u8 45 | mem[2_u32] = 2_u8 46 | mem[3_u32] = 3_u8 47 | mem[4_u32] = 4_u8 48 | 49 | cpu = CPU.new(mem) 50 | 51 | cpu.step 52 | program_counter.should eq(0x01020304) 53 | stack_pointer.should eq(0xfffb) 54 | end 55 | end 56 | 57 | describe "ret" do 58 | it "pops the return address and jumps" do 59 | mem = Mem.new 60 | mem[0_u32] = CPU::O_RET 61 | mem[0xfffc_u32] = 5_u8 62 | mem[0xfffd_u32] = 6_u8 63 | mem[0xfffe_u32] = 7_u8 64 | mem[0xffff_u32] = 8_u8 65 | 66 | cpu = CPU.new(mem) 67 | cpu.reg[Reg::RSP] = 0xfffc_u32 68 | 69 | cpu.step 70 | program_counter.should eq(0x05060708) 71 | stack_pointer.should eq(0xffff_u32 + 0x01_u32) 72 | end 73 | end 74 | 75 | describe "push" do 76 | it "advances RSP and stores the value at the given register" do 77 | mem = Mem.new 78 | mem[0x00_u32] = CPU::O_PUSH 79 | mem[0x01_u32] = Reg::R6 80 | 81 | cpu = CPU.new(mem) 82 | cpu.reg[Reg::R6] = 0x05060708_u32 83 | 84 | cpu.step 85 | program_counter.should eq(0x02) 86 | stack_pointer.should eq(0xfffb_u32) 87 | mem[0xfffb_u32].should eq(5_u8) 88 | mem[0xfffc_u32].should eq(6_u8) 89 | mem[0xfffd_u32].should eq(7_u8) 90 | mem[0xfffe_u32].should eq(8_u8) 91 | end 92 | end 93 | 94 | describe "pushi" do 95 | it "advances RSP and stores the immediate value" do 96 | mem = Mem.new 97 | mem[0x00_u32] = CPU::O_PUSHI 98 | mem[0x01_u32] = 5_u8 99 | mem[0x02_u32] = 6_u8 100 | mem[0x03_u32] = 7_u8 101 | mem[0x04_u32] = 8_u8 102 | 103 | cpu = CPU.new(mem) 104 | 105 | cpu.step 106 | program_counter.should eq(0x05) 107 | stack_pointer.should eq(0xfffb_u32) 108 | mem[0xfffb_u32].should eq(5_u8) 109 | mem[0xfffc_u32].should eq(6_u8) 110 | mem[0xfffd_u32].should eq(7_u8) 111 | mem[0xfffe_u32].should eq(8_u8) 112 | end 113 | end 114 | 115 | describe "pop" do 116 | it "pops four bytes" do 117 | mem = Mem.new 118 | mem[0_u32] = CPU::O_POP 119 | mem[1_u32] = 0x05_u8 120 | mem[0xfffc_u32] = 5_u8 121 | mem[0xfffd_u32] = 6_u8 122 | mem[0xfffe_u32] = 7_u8 123 | mem[0xffff_u32] = 8_u8 124 | 125 | cpu = CPU.new(mem) 126 | cpu.reg[Reg::RSP] = 0xfffc_u32 127 | 128 | cpu.step 129 | program_counter.should eq(0x2) 130 | cpu.reg[Reg::R5].should eq(0x05060708) 131 | stack_pointer.should eq(0xffff_u32 + 0x01_u32) 132 | end 133 | end 134 | 135 | describe "j" do 136 | it "jumps to the right address" do 137 | mem = Mem.new 138 | mem[0_u32] = CPU::O_J 139 | mem[1_u32] = Reg::R6 140 | 141 | cpu = CPU.new(mem) 142 | cpu.reg[Reg::R6] = 0x01020304_u32 143 | 144 | cpu.step 145 | program_counter.should eq(0x01020304) 146 | end 147 | end 148 | 149 | describe "ji" do 150 | it "jumps to the right address" do 151 | mem = Mem.new 152 | mem[0_u32] = CPU::O_JI 153 | mem[1_u32] = 1_u8 154 | mem[2_u32] = 2_u8 155 | mem[3_u32] = 3_u8 156 | mem[4_u32] = 4_u8 157 | 158 | cpu = CPU.new(mem) 159 | 160 | cpu.step 161 | program_counter.should eq(0x01020304) 162 | end 163 | end 164 | 165 | describe "je" do 166 | # TODO: implement 167 | end 168 | 169 | describe "jei" do 170 | # TODO: implement 171 | end 172 | 173 | describe "jne" do 174 | # TODO: implement 175 | end 176 | 177 | describe "jnei" do 178 | # TODO: implement 179 | end 180 | 181 | describe "jg" do 182 | # TODO: implement 183 | end 184 | 185 | describe "jgi" do 186 | # TODO: implement 187 | end 188 | 189 | describe "jge" do 190 | # TODO: implement 191 | end 192 | 193 | describe "jgei" do 194 | # TODO: implement 195 | end 196 | 197 | describe "jl" do 198 | # TODO: implement 199 | end 200 | 201 | describe "jli" do 202 | # TODO: implement 203 | end 204 | 205 | describe "jle" do 206 | # TODO: implement 207 | end 208 | 209 | describe "jlei" do 210 | # TODO: implement 211 | end 212 | 213 | describe "cmp" do 214 | it "sets the right flags when equal" do 215 | mem = Mem.new 216 | mem[0_u32] = CPU::O_CMP 217 | mem[1_u32] = Reg::R1 218 | mem[2_u32] = Reg::R2 219 | 220 | cpu = CPU.new(mem) 221 | cpu.reg[Reg::R1] = 0x05_u32 222 | cpu.reg[Reg::R2] = 0x05_u32 223 | 224 | cpu.step 225 | program_counter.should eq(0x03) 226 | cpu.reg[Reg::RFLAGS].should eq(0x01) 227 | end 228 | 229 | it "sets the right flags when greater than" do 230 | mem = Mem.new 231 | mem[0_u32] = CPU::O_CMP 232 | mem[1_u32] = Reg::R1 233 | mem[2_u32] = Reg::R2 234 | 235 | cpu = CPU.new(mem) 236 | cpu.reg[Reg::R1] = 0x07_u32 237 | cpu.reg[Reg::R2] = 0x05_u32 238 | 239 | cpu.step 240 | program_counter.should eq(0x03) 241 | cpu.reg[Reg::RFLAGS].should eq(0x02) 242 | end 243 | 244 | it "sets the right flags when less than" do 245 | mem = Mem.new 246 | mem[0_u32] = CPU::O_CMP 247 | mem[1_u32] = Reg::R1 248 | mem[2_u32] = Reg::R2 249 | 250 | cpu = CPU.new(mem) 251 | cpu.reg[Reg::R1] = 0x03_u32 252 | cpu.reg[Reg::R2] = 0x05_u32 253 | 254 | cpu.step 255 | program_counter.should eq(0x03) 256 | cpu.reg[Reg::RFLAGS].should eq(0x00) 257 | end 258 | end 259 | 260 | describe "cmpi" do 261 | it "sets the right flags when equal" do 262 | mem = Mem.new 263 | mem[0_u32] = CPU::O_CMPI 264 | mem[1_u32] = Reg::R2 265 | mem[2_u32] = 0_u8 266 | mem[3_u32] = 0_u8 267 | mem[4_u32] = 0_u8 268 | mem[5_u32] = 5_u8 269 | 270 | cpu = CPU.new(mem) 271 | cpu.reg[Reg::R2] = 0x05_u32 272 | 273 | cpu.step 274 | program_counter.should eq(0x06) 275 | cpu.reg[Reg::RFLAGS].should eq(0x01) 276 | end 277 | 278 | it "sets the right flags when greater than" do 279 | mem = Mem.new 280 | mem[0_u32] = CPU::O_CMPI 281 | mem[1_u32] = Reg::R2 282 | mem[2_u32] = 0_u8 283 | mem[3_u32] = 0_u8 284 | mem[4_u32] = 0_u8 285 | mem[5_u32] = 5_u8 286 | 287 | cpu = CPU.new(mem) 288 | cpu.reg[Reg::R2] = 0x07_u32 289 | 290 | cpu.step 291 | program_counter.should eq(0x06) 292 | cpu.reg[Reg::RFLAGS].should eq(0x02) 293 | end 294 | 295 | it "sets the right flags when less than" do 296 | mem = Mem.new 297 | mem[0_u32] = CPU::O_CMPI 298 | mem[1_u32] = Reg::R2 299 | mem[2_u32] = 0_u8 300 | mem[3_u32] = 0_u8 301 | mem[4_u32] = 0_u8 302 | mem[5_u32] = 5_u8 303 | 304 | cpu = CPU.new(mem) 305 | cpu.reg[Reg::R2] = 0x03_u32 306 | 307 | cpu.step 308 | program_counter.should eq(0x06) 309 | cpu.reg[Reg::RFLAGS].should eq(0x00) 310 | end 311 | end 312 | 313 | describe "mod" do 314 | # TODO: implement 315 | end 316 | 317 | describe "modi" do 318 | # TODO: implement 319 | end 320 | 321 | describe "add" do 322 | # TODO: implement 323 | end 324 | 325 | describe "addi" do 326 | # TODO: implement 327 | end 328 | 329 | describe "sub" do 330 | # TODO: implement 331 | end 332 | 333 | describe "subi" do 334 | # TODO: implement 335 | end 336 | 337 | describe "mul" do 338 | # TODO: implement 339 | end 340 | 341 | describe "muli" do 342 | # TODO: implement 343 | end 344 | 345 | describe "div" do 346 | # TODO: implement 347 | end 348 | 349 | describe "divi" do 350 | # TODO: implement 351 | end 352 | 353 | describe "xor" do 354 | # TODO: implement 355 | end 356 | 357 | describe "xori" do 358 | # TODO: implement 359 | end 360 | 361 | describe "or" do 362 | # TODO: implement 363 | end 364 | 365 | describe "ori" do 366 | # TODO: implement 367 | end 368 | 369 | describe "and" do 370 | # TODO: implement 371 | end 372 | 373 | describe "andi" do 374 | # TODO: implement 375 | end 376 | 377 | describe "shl" do 378 | # TODO: implement 379 | end 380 | 381 | describe "shli" do 382 | # TODO: implement 383 | end 384 | 385 | describe "shr" do 386 | # TODO: implement 387 | end 388 | 389 | describe "shri" do 390 | # TODO: implement 391 | end 392 | 393 | describe "not" do 394 | # TODO: implement 395 | end 396 | 397 | describe "mov" do 398 | it "copies the register" do 399 | mem = Mem.new 400 | mem[0_u32] = CPU::O_MOV 401 | mem[1_u32] = Reg::R1 402 | mem[2_u32] = Reg::R2 403 | 404 | cpu = CPU.new(mem) 405 | cpu.reg[Reg::R1] = 0x11111111_u32 406 | cpu.reg[Reg::R2] = 0x22222222_u32 407 | 408 | cpu.step 409 | cpu.reg[Reg::R1].should eq(0x22222222_u32) 410 | cpu.reg[Reg::R2].should eq(0x22222222_u32) 411 | end 412 | end 413 | 414 | describe "li" do 415 | # TODO: implement 416 | end 417 | 418 | describe "lw" do 419 | # TODO: implement 420 | end 421 | 422 | describe "lh" do 423 | # TODO: implement 424 | end 425 | 426 | describe "lb" do 427 | # TODO: implement 428 | end 429 | 430 | describe "sw" do 431 | # TODO: implement 432 | end 433 | 434 | describe "sh" do 435 | # TODO: implement 436 | end 437 | 438 | describe "sb" do 439 | # TODO: implement 440 | end 441 | 442 | describe "prn" do 443 | # TODO: implement 444 | end 445 | 446 | describe "halt" do 447 | # TODO: implement 448 | end 449 | 450 | end 451 | -------------------------------------------------------------------------------- /src/assemble/main.cr: -------------------------------------------------------------------------------- 1 | require "option_parser" 2 | 3 | class Arg 4 | def kind 5 | raise "Not implemented" 6 | end 7 | 8 | def valid? 9 | raise "Not implemented" 10 | end 11 | 12 | def bytes(_labels) 13 | raise "Not implemented" 14 | [] of UInt8 15 | end 16 | end 17 | 18 | class RegArg < Arg 19 | MAPPING = { 20 | "r0" => 0_u8, 21 | "r1" => 1_u8, 22 | "r2" => 2_u8, 23 | "r3" => 3_u8, 24 | "r4" => 4_u8, 25 | "r5" => 5_u8, 26 | "r6" => 6_u8, 27 | "r7" => 7_u8, 28 | "rpc" => 8_u8, 29 | "rflags" => 9_u8, 30 | "rsp" => 10_u8, 31 | "rbp" => 11_u8, 32 | "rr" => 12_u8, 33 | } 34 | 35 | getter :name 36 | 37 | def initialize(name : String) 38 | @name = name 39 | end 40 | 41 | def kind 42 | :register 43 | end 44 | 45 | def inspect 46 | "reg(#{@name})" 47 | end 48 | 49 | def valid? 50 | MAPPING.has_key?(name) 51 | end 52 | 53 | def bytes(_labels) 54 | [MAPPING.fetch(name)] 55 | end 56 | end 57 | 58 | class ImmArg < Arg 59 | getter :value 60 | 61 | def initialize(value : Int32) 62 | @value = value 63 | end 64 | 65 | def kind 66 | :immediate 67 | end 68 | 69 | def inspect 70 | "imm(#{@value})" 71 | end 72 | 73 | def valid? 74 | true 75 | end 76 | 77 | def bytes(_labels) 78 | [ 79 | ((value & 0xff000000) >> 0x18).to_u8, 80 | ((value & 0x00ff0000) >> 0x10).to_u8, 81 | ((value & 0x0000ff00) >> 0x08).to_u8, 82 | ((value & 0x000000ff) >> 0x00).to_u8, 83 | ] 84 | end 85 | end 86 | 87 | # Inherits from ImmArg because this is a sort of unresolved immediate arg 88 | class LabelArg < Arg 89 | getter :name 90 | 91 | def initialize(name : String) 92 | @name = name 93 | end 94 | 95 | def kind 96 | :immediate 97 | end 98 | 99 | def inspect 100 | "label(#{@name})" 101 | end 102 | 103 | def valid? 104 | true 105 | end 106 | 107 | def bytes(labels) 108 | if labels 109 | value = labels[name] 110 | [ 111 | ((value & 0xff000000) >> 0x18).to_u8, 112 | ((value & 0x00ff0000) >> 0x10).to_u8, 113 | ((value & 0x0000ff00) >> 0x08).to_u8, 114 | ((value & 0x000000ff) >> 0x00).to_u8, 115 | ] 116 | else 117 | [0x00.to_u8, 0x00.to_u8, 0x00.to_u8, 0x00.to_u8] 118 | end 119 | end 120 | end 121 | 122 | ############################################################################### 123 | 124 | class Instruction 125 | getter :opcode_mnemonic 126 | getter :args 127 | 128 | def initialize(opcode_mnemonic : String, args : Array(Arg)) 129 | @opcode_mnemonic = opcode_mnemonic 130 | @args = args 131 | end 132 | 133 | def inspect 134 | "instr(#{@opcode_mnemonic}, args = #{@args.inspect})" 135 | end 136 | 137 | def instruction? 138 | true 139 | end 140 | 141 | def label? 142 | false 143 | end 144 | 145 | def data? 146 | false 147 | end 148 | end 149 | 150 | class Label 151 | getter :name 152 | 153 | def initialize(name : String) 154 | @name = name 155 | end 156 | 157 | def inspect 158 | "label(#{name.inspect})" 159 | end 160 | 161 | def instruction? 162 | false 163 | end 164 | 165 | def label? 166 | true 167 | end 168 | 169 | def data? 170 | false 171 | end 172 | end 173 | 174 | class DataDirective 175 | getter :length 176 | getter :arg 177 | 178 | def initialize(length : Int32, arg : Arg) 179 | @length = length 180 | @arg = arg 181 | end 182 | 183 | def inspect 184 | "data(arg=#{arg.inspect} length=#{length})" 185 | end 186 | 187 | def instruction? 188 | false 189 | end 190 | 191 | def label? 192 | false 193 | end 194 | 195 | def data? 196 | true 197 | end 198 | 199 | def bytes(labels) 200 | bytes = arg.bytes(labels) 201 | bytes[-length..-1] 202 | end 203 | end 204 | 205 | ############################################################################### 206 | 207 | class Token 208 | property kind 209 | 210 | def initialize 211 | @kind = :unknown 212 | @content_io = MemoryIO.new 213 | end 214 | 215 | def empty 216 | @content_io = MemoryIO.new 217 | end 218 | 219 | def append_char(c) 220 | @content_io << c 221 | end 222 | 223 | def content 224 | @content_io.to_s 225 | end 226 | 227 | def inspect(io) 228 | io << kind.to_s.upcase 229 | 230 | if content.size > 0 231 | io << "(#{content})" 232 | end 233 | end 234 | end 235 | 236 | class Lexer 237 | getter tokens 238 | 239 | def initialize(@input : String) 240 | @index = 0 241 | @tokens = [] of Token 242 | @current_token = Token.new 243 | end 244 | 245 | def run 246 | until @index >= @input.size 247 | char = @input[@index] 248 | @index += 1 249 | 250 | case char 251 | when '@' 252 | @current_token.kind = :at 253 | @current_token.empty 254 | finish_token 255 | when '.' 256 | @current_token.kind = :dot 257 | @current_token.empty 258 | finish_token 259 | when ':' 260 | @current_token.kind = :colon 261 | @current_token.empty 262 | finish_token 263 | when ',' 264 | @current_token.kind = :comma 265 | @current_token.empty 266 | finish_token 267 | when ' ', '\t' 268 | @current_token.kind = :space 269 | @current_token.append_char(char) 270 | consume_space 271 | finish_token 272 | when '\n' 273 | @current_token.kind = :newline 274 | @current_token.empty 275 | finish_token 276 | when '#' 277 | @current_token.kind = :comment 278 | @current_token.append_char(char) 279 | consume_until_newline 280 | finish_token 281 | when '0' .. '9' 282 | @current_token.kind = :number 283 | unread_char # input required for decision making 284 | consume_number 285 | finish_token 286 | when 'a' .. 'z', 'A' .. 'Z' 287 | @current_token.kind = :identifier 288 | @current_token.append_char(char) 289 | consume_identifier 290 | finish_token 291 | else 292 | raise "lexer error before #{char} at #{@index}" 293 | end 294 | end 295 | 296 | if @current_token.content.size > 0 297 | puts "Warning: unfinished token after end of lexing" 298 | finish_token 299 | end 300 | @current_token.kind = :eof 301 | finish_token 302 | end 303 | 304 | def unread_char 305 | @index -= 1 306 | end 307 | 308 | def finish_token 309 | @tokens << @current_token 310 | @current_token = Token.new 311 | end 312 | 313 | def consume_until_newline 314 | until @index >= @input.size 315 | char = @input[@index] 316 | @index += 1 317 | 318 | case char 319 | when '\n' 320 | unread_char 321 | return 322 | else 323 | @current_token.append_char(char) 324 | end 325 | end 326 | end 327 | 328 | def consume_space 329 | until @index >= @input.size 330 | char = @input[@index] 331 | @index += 1 332 | 333 | case char 334 | when ' ', '\t' 335 | @current_token.append_char(char) 336 | else 337 | unread_char 338 | return 339 | end 340 | end 341 | end 342 | 343 | def consume_identifier 344 | until @index >= @input.size 345 | char = @input[@index] 346 | @index += 1 347 | 348 | case char 349 | when 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '-', '_' 350 | @current_token.append_char(char) 351 | else 352 | unread_char 353 | return 354 | end 355 | end 356 | end 357 | 358 | def consume_number 359 | state = :pre 360 | 361 | until @index >= @input.size 362 | char = @input[@index] 363 | @index += 1 364 | 365 | case state 366 | when :pre 367 | case char 368 | when '0' 369 | @current_token.append_char(char) 370 | state = :zero 371 | when '1' .. '9' 372 | @current_token.append_char(char) 373 | state = :body 374 | else 375 | raise "consume_number: inconsistent lexer state" 376 | end 377 | when :zero 378 | case char 379 | when 'x', 'b' 380 | @current_token.append_char(char) 381 | state = :body 382 | when '0' .. '9' 383 | raise "consume_number: lexer error before #{char} at #{@index}" 384 | else 385 | unread_char 386 | return 387 | end 388 | when :body 389 | case char 390 | # TODO: be more strict 391 | when '0' .. '9', 'a' .. 'f', 'A' .. 'F' 392 | @current_token.append_char(char) 393 | else 394 | unread_char 395 | return 396 | end 397 | else 398 | raise "consume_number: inconsistent lexer state" 399 | end 400 | end 401 | end 402 | end 403 | 404 | class Parser 405 | getter statements 406 | 407 | def initialize(@input : Array(Token)) 408 | @index = 0 409 | @statements = [] of Instruction | Label | DataDirective 410 | end 411 | 412 | def run 413 | loop do 414 | case current_token.kind 415 | when :comment 416 | advance 417 | when :identifier # label 418 | consume_label 419 | when :space # instruction 420 | consume_instruction 421 | when :newline # empty line 422 | advance 423 | when :dot # data directive 424 | consume_data_directive 425 | when :eof 426 | break 427 | else 428 | raise "Parser: unexpected #{current_token.kind.to_s.upcase}" 429 | end 430 | end 431 | end 432 | 433 | def consume_data_directive 434 | advance # dot 435 | 436 | directive_kind = consume(:identifier).content 437 | length = 438 | case directive_kind 439 | when "byte" 440 | 1 441 | when "half" 442 | 2 443 | when "word" 444 | 4 445 | else 446 | raise "Parser: invalid data directive kind: #{directive_kind}" 447 | end 448 | 449 | consume(:space) 450 | 451 | arg = 452 | case current_token.kind 453 | when :number 454 | number_token = consume 455 | ImmArg.new(parse_int(number_token.content)) 456 | when :at 457 | advance 458 | label_name = consume(:identifier).content 459 | LabelArg.new(label_name) 460 | else 461 | raise "Parser: unexpected #{current_token.kind.to_s.upcase}" 462 | end 463 | 464 | case current_token 465 | when :newline 466 | advance 467 | when :space 468 | advance 469 | consume(:comment) 470 | consume(:newline) 471 | end 472 | 473 | @statements << DataDirective.new(length, arg) 474 | end 475 | 476 | def consume_label 477 | name = consume(:identifier) 478 | consume(:colon) 479 | 480 | case current_token 481 | when :newline 482 | advance 483 | when :space 484 | advance 485 | consume(:comment) 486 | consume(:newline) 487 | end 488 | 489 | @statements << Label.new(name.content) 490 | end 491 | 492 | def consume_instruction 493 | advance 494 | 495 | if current_token.kind == :comment 496 | advance 497 | consume(:newline) 498 | return 499 | end 500 | 501 | opcode_mnemonic = consume(:identifier).content 502 | case current_token.kind 503 | when :newline 504 | advance 505 | @statements << Instruction.new(opcode_mnemonic, [] of Arg) 506 | when :space 507 | advance 508 | 509 | if current_token == :comment 510 | advance 511 | consume(:newline) 512 | return 513 | end 514 | 515 | args = [] of ImmArg | LabelArg | RegArg 516 | loop do 517 | args << consume_argument 518 | case current_token.kind 519 | when :newline 520 | advance 521 | break 522 | when :space 523 | advance 524 | maybe_consume(:comment) 525 | consume(:newline) 526 | break 527 | when :comma 528 | advance 529 | consume(:space) 530 | end 531 | end 532 | @statements << Instruction.new(opcode_mnemonic, args) 533 | else 534 | raise "Parser: unexpected #{current_token.kind.to_s.upcase}" 535 | end 536 | end 537 | 538 | def parse_int(content) 539 | case content 540 | when /\A0x/ 541 | content[2..-1].to_i(16) 542 | when /\A0b/ 543 | content[2..-1].to_i(2) 544 | else 545 | content.to_i 546 | end 547 | end 548 | 549 | def consume_argument 550 | case current_token.kind 551 | when :number # e.g. 0x123 552 | number_token = consume 553 | ImmArg.new(parse_int(number_token.content)) 554 | when :at # e.g. @foo 555 | advance 556 | name = consume(:identifier) 557 | LabelArg.new(name.content) 558 | when :identifier # e.g. r0 559 | name = consume(:identifier) 560 | RegArg.new(name.content) 561 | else 562 | raise "Parser: unexpected #{current_token.kind.to_s.upcase}" 563 | end 564 | end 565 | 566 | def advance 567 | @index += 1 568 | end 569 | 570 | def unread_token 571 | @index -= 1 572 | end 573 | 574 | def current_token 575 | @input[@index] 576 | end 577 | 578 | def consume 579 | @input[@index].tap { advance } 580 | end 581 | 582 | def consume(kind) 583 | token = @input[@index] 584 | if token.kind == kind 585 | consume 586 | else 587 | raise "Parser: unexpected #{token.kind.to_s.upcase}" 588 | end 589 | end 590 | 591 | def maybe_consume(kind) 592 | token = @input[@index] 593 | if token.kind == kind 594 | @index += 1 595 | end 596 | end 597 | end 598 | 599 | ################################################################################ 600 | 601 | class Assembler 602 | def initialize(@input : String) 603 | end 604 | 605 | def assemble 606 | lexer = Lexer.new(@input) 607 | lexer.run 608 | 609 | parser = Parser.new(lexer.tokens) 610 | parser.run 611 | 612 | statements = parser.statements 613 | labels = collect_labels(statements) 614 | generate_program(statements, labels) 615 | end 616 | 617 | private def collect_labels(statements) 618 | labels = {} of String => UInt32 619 | program = [] of UInt8 620 | statements.each do |statement| 621 | case statement 622 | when Instruction 623 | handle_instruction(statement, program, nil) 624 | when Label 625 | labels[statement.name] = program.size.to_u32 626 | when DataDirective 627 | statement.length.times { program << 0x00.to_u8 } 628 | end 629 | end 630 | labels 631 | end 632 | 633 | private def generate_program(statements, labels) 634 | program = [] of UInt8 635 | statements.each do |statement| 636 | case statement 637 | when Instruction 638 | handle_instruction(statement, program, labels) 639 | when DataDirective 640 | statement.bytes(labels).each { |byte| program << byte } 641 | end 642 | end 643 | program 644 | end 645 | 646 | class IDef 647 | getter :opcode 648 | getter :args 649 | 650 | def initialize(opcode : UInt8, args : Array(Symbol)) 651 | @opcode = opcode 652 | @args = args 653 | end 654 | end 655 | 656 | INSTRUCTION_DEFS = { 657 | # Function handling 658 | "call" => IDef.new(0x01.to_u8, [:register]), 659 | "calli" => IDef.new(0x02.to_u8, [:immediate]), 660 | "ret" => IDef.new(0x03.to_u8, [] of Symbol), 661 | # Stack management 662 | "push" => IDef.new(0x04.to_u8, [:register]), 663 | "pushi" => IDef.new(0x05.to_u8, [:immediate]), 664 | "pop" => IDef.new(0x06.to_u8, [:register]), 665 | # Branching 666 | "j" => IDef.new(0x07.to_u8, [:register]), 667 | "ji" => IDef.new(0x08.to_u8, [:immediate]), 668 | "je" => IDef.new(0x09.to_u8, [:register]), 669 | "jei" => IDef.new(0x0a.to_u8, [:immediate]), 670 | "jne" => IDef.new(0x0b.to_u8, [:register]), 671 | "jnei" => IDef.new(0x0c.to_u8, [:immediate]), 672 | "jg" => IDef.new(0x0d.to_u8, [:register]), 673 | "jgi" => IDef.new(0x0e.to_u8, [:immediate]), 674 | "jge" => IDef.new(0x0f.to_u8, [:register]), 675 | "jgei" => IDef.new(0x10.to_u8, [:immediate]), 676 | "jl" => IDef.new(0x11.to_u8, [:register]), 677 | "jli" => IDef.new(0x12.to_u8, [:immediate]), 678 | "jle" => IDef.new(0x13.to_u8, [:register]), 679 | "jlei" => IDef.new(0x14.to_u8, [:immediate]), 680 | # Arithmetic 681 | "cmp" => IDef.new(0x15.to_u8, [:register, :register]), 682 | "cmpi" => IDef.new(0x16.to_u8, [:register, :immediate]), 683 | "mod" => IDef.new(0x17.to_u8, [:register, :register, :register]), 684 | "modi" => IDef.new(0x18.to_u8, [:register, :register, :immediate]), 685 | "add" => IDef.new(0x19.to_u8, [:register, :register, :register]), 686 | "addi" => IDef.new(0x1a.to_u8, [:register, :register, :immediate]), 687 | "sub" => IDef.new(0x1b.to_u8, [:register, :register, :register]), 688 | "subi" => IDef.new(0x1c.to_u8, [:register, :register, :immediate]), 689 | "mul" => IDef.new(0x1d.to_u8, [:register, :register, :register]), 690 | "muli" => IDef.new(0x1e.to_u8, [:register, :register, :immediate]), 691 | "div" => IDef.new(0x1f.to_u8, [:register, :register, :register]), 692 | "divi" => IDef.new(0x20.to_u8, [:register, :register, :immediate]), 693 | "xor" => IDef.new(0x21.to_u8, [:register, :register, :register]), 694 | "xori" => IDef.new(0x22.to_u8, [:register, :register, :immediate]), 695 | "or" => IDef.new(0x23.to_u8, [:register, :register, :register]), 696 | "ori" => IDef.new(0x24.to_u8, [:register, :register, :immediate]), 697 | "and" => IDef.new(0x25.to_u8, [:register, :register, :register]), 698 | "andi" => IDef.new(0x26.to_u8, [:register, :register, :immediate]), 699 | "shl" => IDef.new(0x27.to_u8, [:register, :register, :register]), 700 | "shli" => IDef.new(0x28.to_u8, [:register, :register, :immediate]), 701 | "shr" => IDef.new(0x29.to_u8, [:register, :register, :register]), 702 | "shri" => IDef.new(0x2a.to_u8, [:register, :register, :immediate]), 703 | "not" => IDef.new(0x2b.to_u8, [:register, :register]), 704 | # Register handling 705 | "mov" => IDef.new(0x2c.to_u8, [:register, :register]), 706 | "li" => IDef.new(0x2d.to_u8, [:register, :immediate]), 707 | # Memory handling 708 | "lw" => IDef.new(0x2e.to_u8, [:register, :register]), 709 | "lh" => IDef.new(0x2f.to_u8, [:register, :register]), 710 | "lb" => IDef.new(0x30.to_u8, [:register, :register]), 711 | "sw" => IDef.new(0x31.to_u8, [:register, :register]), 712 | "sh" => IDef.new(0x32.to_u8, [:register, :register]), 713 | "sb" => IDef.new(0x33.to_u8, [:register, :register]), 714 | # Special 715 | "sleep" => IDef.new(0xfd.to_u8, [:immediate]), 716 | "prn" => IDef.new(0xfe.to_u8, [:register]), 717 | "halt" => IDef.new(0xff.to_u8, [] of Symbol), 718 | } 719 | 720 | def handle_instruction(instr : Instruction, program, labels) 721 | instr_def = INSTRUCTION_DEFS.fetch(instr.opcode_mnemonic) do 722 | raise "Unknown instruction name: #{instr.opcode_mnemonic}" 723 | end 724 | 725 | # Add opcode 726 | opcode = instr_def.opcode 727 | program << opcode 728 | 729 | # Validate args 730 | if instr.args.size != instr_def.args.size 731 | raise "Incorrect argument count (#{instr.inspect}; is #{instr.args.size}, expected #{instr_def.args.size}" 732 | end 733 | instr.args.each_with_index do |arg, i| 734 | arg_def = instr_def.args[i] 735 | unless arg_def == arg.kind 736 | raise "Incorrect argument type (#{instr.inspect}; is #{arg.class}, expected #{arg_def})" 737 | end 738 | unless arg.valid? 739 | raise "Invalid argument (#{instr.inspect}; #{arg.inspect})" 740 | end 741 | end 742 | 743 | # Add args 744 | instr.args.each do |arg| 745 | arg.bytes(labels).each { |b| program << b } 746 | end 747 | end 748 | end 749 | 750 | op = OptionParser.parse! do |opts| 751 | opts.banner = "Usage: rcpu-assemble filename" 752 | opts.on("-h", "--help", "print this help menu") do 753 | puts opts 754 | exit 0 755 | end 756 | end 757 | 758 | if ARGV.size != 1 759 | puts op 760 | exit 1 761 | end 762 | 763 | # Get filenames 764 | input_filename = ARGV[0] 765 | output_filename = 766 | File.join( 767 | File.dirname(input_filename), 768 | File.basename(input_filename, ".rcs") + ".rcb") 769 | 770 | # Read 771 | data = File.read(input_filename) 772 | 773 | # Assemble 774 | bytes = Assembler.new(data).assemble 775 | 776 | # Write 777 | File.open(output_filename, "w") do |io| 778 | bytes.each do |byte| 779 | io.write_byte(byte) 780 | end 781 | end 782 | -------------------------------------------------------------------------------- /src/emulate/cpu.cr: -------------------------------------------------------------------------------- 1 | class CPU 2 | getter running 3 | getter reg 4 | getter mem 5 | setter mem 6 | 7 | O_CALL = 0x01_u8 8 | O_CALLI = 0x02_u8 9 | O_RET = 0x03_u8 10 | O_PUSH = 0x04_u8 11 | O_PUSHI = 0x05_u8 12 | O_POP = 0x06_u8 13 | O_J = 0x07_u8 14 | O_JI = 0x08_u8 15 | O_JE = 0x09_u8 16 | O_JEI = 0x0a_u8 17 | O_JNE = 0x0b_u8 18 | O_JNEI = 0x0c_u8 19 | O_JG = 0x0d_u8 20 | O_JGI = 0x0e_u8 21 | O_JGE = 0x0f_u8 22 | O_JGEI = 0x10_u8 23 | O_JL = 0x11_u8 24 | O_JLI = 0x12_u8 25 | O_JLE = 0x13_u8 26 | O_JLEI = 0x14_u8 27 | O_CMP = 0x15_u8 28 | O_CMPI = 0x16_u8 29 | O_MOD = 0x17_u8 30 | O_MODI = 0x18_u8 31 | O_ADD = 0x19_u8 32 | O_ADDI = 0x1a_u8 33 | O_SUB = 0x1b_u8 34 | O_SUBI = 0x1c_u8 35 | O_MUL = 0x1d_u8 36 | O_MULI = 0x1e_u8 37 | O_DIV = 0x1f_u8 38 | O_DIVI = 0x20_u8 39 | O_XOR = 0x21_u8 40 | O_XORI = 0x22_u8 41 | O_OR = 0x23_u8 42 | O_ORI = 0x24_u8 43 | O_AND = 0x25_u8 44 | O_ANDI = 0x26_u8 45 | O_SHL = 0x27_u8 46 | O_SHLI = 0x28_u8 47 | O_SHR = 0x29_u8 48 | O_SHRI = 0x2a_u8 49 | O_NOT = 0x2b_u8 50 | O_MOV = 0x2c_u8 51 | O_LI = 0x2d_u8 52 | O_LW = 0x2e_u8 53 | O_LH = 0x2f_u8 54 | O_LB = 0x30_u8 55 | O_SW = 0x31_u8 56 | O_SH = 0x32_u8 57 | O_SB = 0x33_u8 58 | O_SLEEP = 0xfd_u8 59 | O_PRN = 0xfe_u8 60 | O_HALT = 0xff_u8 61 | 62 | class OpcodeNotSupportedException < Exception 63 | end 64 | 65 | class InvalidOpcodeException < Exception 66 | end 67 | 68 | def initialize(mem : Mem) 69 | @reg = Reg.new 70 | @mem = mem 71 | @running = true 72 | end 73 | 74 | def run(cycles = -1) 75 | return unless @running 76 | 77 | if cycles == -1 78 | loop do 79 | step 80 | break unless @running 81 | end 82 | else 83 | cycles.times do 84 | step 85 | break unless @running 86 | end 87 | end 88 | end 89 | 90 | def step 91 | opcode = read_byte 92 | 93 | case opcode 94 | # --- FUNCTION HANDLING --- 95 | when O_CALL 96 | new_pc = reg[Reg::RPC] + 1 97 | push(new_pc) 98 | a0 = read_byte 99 | reg[Reg::RPC] = reg[a0] 100 | when O_CALLI 101 | new_pc = reg[Reg::RPC] + 4 102 | push(new_pc) 103 | i = read_u32 104 | reg[Reg::RPC] = i 105 | when O_RET 106 | reg[Reg::RPC] = pop 107 | # --- STACK MANAGEMENT --- 108 | when O_PUSH 109 | a0 = read_byte 110 | raw = reg[a0] 111 | push(raw) 112 | when O_PUSHI 113 | a0 = read_byte 114 | a1 = read_byte 115 | a2 = read_byte 116 | a3 = read_byte 117 | push(a0, a1, a2, a3) 118 | when O_POP 119 | a0 = read_byte 120 | reg[a0] = pop 121 | # --- BRANCHING --- 122 | when O_J 123 | a0 = read_byte 124 | reg[Reg::RPC] = reg[a0] 125 | when O_JI 126 | i = read_u32 127 | reg[Reg::RPC] = i 128 | when O_JE 129 | raise OpcodeNotSupportedException.new(opcode.inspect) 130 | when O_JEI 131 | i = read_u32 132 | reg[Reg::RPC] = i if reg[Reg::RFLAGS] & 0x01 == 0x01 133 | when O_JNE 134 | raise OpcodeNotSupportedException.new(opcode.inspect) 135 | when O_JNEI 136 | i = read_u32 137 | reg[Reg::RPC] = i if reg[Reg::RFLAGS] & 0x01 == 0x00 138 | when O_JG 139 | raise OpcodeNotSupportedException.new(opcode.inspect) 140 | when O_JGI 141 | i = read_u32 142 | reg[Reg::RPC] = i if reg[Reg::RFLAGS] & 0x02 == 0x02 143 | when O_JGE 144 | raise OpcodeNotSupportedException.new(opcode.inspect) 145 | when O_JGEI 146 | i = read_u32 147 | reg[Reg::RPC] = i if reg[Reg::RFLAGS] & 0x03 != 0x00 148 | when O_JL 149 | raise OpcodeNotSupportedException.new(opcode.inspect) 150 | when O_JLI 151 | i = read_u32 152 | reg[Reg::RPC] = i if reg[Reg::RFLAGS] & 0x03 == 0x00 153 | when O_JLE 154 | raise OpcodeNotSupportedException.new(opcode.inspect) 155 | when O_JLEI 156 | i = read_u32 157 | reg[Reg::RPC] = i if reg[Reg::RFLAGS] & 0x02 == 0x00 158 | # --- ARITHMETIC --- 159 | when O_CMP 160 | a0 = read_byte 161 | a1 = read_byte 162 | cmp(reg[a0], reg[a1]) 163 | when O_CMPI 164 | a0 = read_byte 165 | i = read_u32 166 | cmp(reg[a0], i) 167 | when O_MOD 168 | a0 = read_byte 169 | a1 = read_byte 170 | a2 = read_byte 171 | reg[a0] = reg[a1] % reg[a2] 172 | when O_MODI 173 | a0 = read_byte 174 | a1 = read_byte 175 | i = read_u32 176 | reg[a0] = reg[a1] % i 177 | when O_ADD 178 | a0 = read_byte 179 | a1 = read_byte 180 | a2 = read_byte 181 | reg[a0] = (reg[a1] + reg[a2]) 182 | when O_ADDI 183 | a0 = read_byte 184 | a1 = read_byte 185 | i = read_u32 186 | reg[a0] = (reg[a1] + i) 187 | when O_SUB 188 | raise OpcodeNotSupportedException.new(opcode.inspect) 189 | when O_SUBI 190 | a0 = read_byte 191 | a1 = read_byte 192 | i = read_u32 193 | reg[a0] = (reg[a1] - i) 194 | when O_MUL 195 | raise OpcodeNotSupportedException.new(opcode.inspect) 196 | when O_MULI 197 | a0 = read_byte 198 | a1 = read_byte 199 | i = read_u32 200 | reg[a0] = (reg[a1] * i) 201 | when O_DIV 202 | raise OpcodeNotSupportedException.new(opcode.inspect) 203 | when O_DIVI 204 | raise OpcodeNotSupportedException.new(opcode.inspect) 205 | when O_XOR 206 | raise OpcodeNotSupportedException.new(opcode.inspect) 207 | when O_XORI 208 | raise OpcodeNotSupportedException.new(opcode.inspect) 209 | when O_OR 210 | raise OpcodeNotSupportedException.new(opcode.inspect) 211 | when O_ORI 212 | raise OpcodeNotSupportedException.new(opcode.inspect) 213 | when O_AND 214 | raise OpcodeNotSupportedException.new(opcode.inspect) 215 | when O_ANDI 216 | raise OpcodeNotSupportedException.new(opcode.inspect) 217 | when O_SHL 218 | raise OpcodeNotSupportedException.new(opcode.inspect) 219 | when O_SHLI 220 | raise OpcodeNotSupportedException.new(opcode.inspect) 221 | when O_SHR 222 | raise OpcodeNotSupportedException.new(opcode.inspect) 223 | when O_SHRI 224 | a0 = read_byte 225 | a1 = read_byte 226 | i = read_u32 227 | reg[a0] = (reg[a1] >> i) 228 | when O_NOT 229 | raise OpcodeNotSupportedException.new(opcode.inspect) 230 | # --- REGISTER HANDLING --- 231 | when O_MOV 232 | a0 = read_byte 233 | a1 = read_byte 234 | reg[a0] = reg[a1] 235 | when O_LI 236 | a0 = read_byte 237 | a1 = read_u32 238 | reg[a0] = a1 239 | # --- MEMORY HANDLING --- 240 | when O_LW 241 | a0 = read_byte 242 | a1 = read_byte 243 | address = reg[a1] 244 | reg[a0] = 245 | (mem[address + 0].to_u32 << 24) | 246 | (mem[address + 1].to_u32 << 16) | 247 | (mem[address + 2].to_u32 << 8) | 248 | (mem[address + 3].to_u32 << 0) 249 | when O_LH 250 | a0 = read_byte 251 | a1 = read_byte 252 | address = reg[a1] 253 | reg[a0] = 254 | (mem[address + 0].to_u32 << 8) | 255 | (mem[address + 1].to_u32 << 0) 256 | when O_LB 257 | a0 = read_byte 258 | a1 = read_byte 259 | address = reg[a1] 260 | reg[a0] = 261 | (mem[address + 0].to_u32 << 0) 262 | when O_SW 263 | raise OpcodeNotSupportedException.new(opcode.inspect) 264 | when O_SH 265 | raise OpcodeNotSupportedException.new(opcode.inspect) 266 | when O_SB 267 | a0 = read_byte 268 | a1 = read_byte 269 | raw = reg[a1] 270 | mem[reg[a0] + 0] = ((raw & 0x000000ff)).to_u8 271 | # --- SPECIAL --- 272 | when O_SLEEP 273 | i = read_u32 274 | sleep(i.to_f / 1000.0) 275 | when O_PRN 276 | a0 = read_byte 277 | puts "#{reg[a0]}" 278 | when O_HALT 279 | @running = false 280 | advance(-1) 281 | else 282 | raise InvalidOpcodeException.new(opcode.inspect) 283 | end 284 | end 285 | 286 | private def push(int) 287 | push( 288 | ((int & 0xff000000) >> 24).to_u8, 289 | ((int & 0x00ff0000) >> 16).to_u8, 290 | ((int & 0x0000ff00) >> 8).to_u8, 291 | ((int & 0x000000ff)).to_u8, 292 | ) 293 | end 294 | 295 | private def push(a, b, c, d) 296 | reg[Reg::RSP] -= 4_u32 297 | mem[reg[Reg::RSP] + 0] = a 298 | mem[reg[Reg::RSP] + 1] = b 299 | mem[reg[Reg::RSP] + 2] = c 300 | mem[reg[Reg::RSP] + 3] = d 301 | end 302 | 303 | private def pop 304 | reconstruct_int( 305 | mem.fetch(reg[Reg::RSP] + 0), 306 | mem.fetch(reg[Reg::RSP] + 1), 307 | mem.fetch(reg[Reg::RSP] + 2), 308 | mem.fetch(reg[Reg::RSP] + 3) 309 | ).tap do 310 | reg[Reg::RSP] += 4 311 | end 312 | end 313 | 314 | private def cmp(a, b) 315 | reg[Reg::RFLAGS] = 316 | (a == b ? 0x01_u32 : 0x00_u32) | 317 | (a > b ? 0x02_u32 : 0x00_u32) 318 | end 319 | 320 | private def advance(amount) 321 | reg[Reg::RPC] += amount 322 | end 323 | 324 | private def read_byte 325 | mem[reg[Reg::RPC]].tap { advance(1) } 326 | end 327 | 328 | private def read_u32 329 | reconstruct_int(read_byte, read_byte, read_byte, read_byte) 330 | end 331 | 332 | private def reconstruct_int(x, y, z, t) 333 | (x.to_u32 << 24) + (y.to_u32 << 16) + (z.to_u32 << 8) + t.to_u32 334 | end 335 | end 336 | -------------------------------------------------------------------------------- /src/emulate/main.cr: -------------------------------------------------------------------------------- 1 | require "sdl2" 2 | require "option_parser" 3 | 4 | require "./cpu" 5 | require "./mem" 6 | require "./reg" 7 | require "./video" 8 | 9 | enble_video = false 10 | 11 | op = OptionParser.parse! do |opts| 12 | opts.banner = "Usage: rcpu-emulate [options] filename" 13 | opts.on("-V", "--video", "enable video") do 14 | enble_video = true 15 | end 16 | opts.on("-h", "--help", "print this help menu") do 17 | puts opts 18 | exit 0 19 | end 20 | end 21 | 22 | if ARGV.size != 1 23 | puts op 24 | exit 1 25 | end 26 | 27 | # Read instructions into memory 28 | filename = ARGV.first 29 | bytes = [] of UInt8 30 | File.open(filename, "r") do |io| 31 | loop do 32 | byte = io.read_byte 33 | if byte 34 | bytes << byte 35 | else 36 | break 37 | end 38 | end 39 | end 40 | mem = Mem.new 41 | bytes.each_with_index do |byte, index| 42 | mem[index.to_u32] = byte 43 | end 44 | 45 | # Set video memory 46 | 160.times do |x| 47 | 120.times do |y| 48 | index = 0x10000 + (x * 120 + y) 49 | mem[index.to_u32] = 0_u8 50 | end 51 | end 52 | 53 | # Run 54 | cpu = CPU.new(mem) 55 | if enble_video 56 | video = Video.new(mem) 57 | video.run(cpu, 1000) 58 | else 59 | cpu.run 60 | end 61 | -------------------------------------------------------------------------------- /src/emulate/mem.cr: -------------------------------------------------------------------------------- 1 | class Mem 2 | def initialize 3 | @wrapped = {} of UInt32 => UInt8 4 | end 5 | 6 | def [](address) 7 | fetch(address) 8 | end 9 | 10 | def fetch(address) 11 | @wrapped.fetch(address) 12 | end 13 | 14 | def []=(address, value) 15 | @wrapped[address] = value 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /src/emulate/reg.cr: -------------------------------------------------------------------------------- 1 | class Reg 2 | R0 = 0_u8 3 | R1 = 1_u8 4 | R2 = 2_u8 5 | R3 = 3_u8 6 | R4 = 4_u8 7 | R5 = 5_u8 8 | R6 = 6_u8 9 | R7 = 7_u8 10 | RPC = 8_u8 11 | RFLAGS = 9_u8 12 | RSP = 10_u8 13 | RBP = 11_u8 14 | RR = 12_u8 15 | 16 | def initialize 17 | @reg = { 18 | R0 => 0_u32, 19 | R1 => 0_u32, 20 | R2 => 0_u32, 21 | R3 => 0_u32, 22 | R4 => 0_u32, 23 | R5 => 0_u32, 24 | R6 => 0_u32, 25 | R7 => 0_u32, 26 | RPC => 0_u32, 27 | RFLAGS => 0_u32, 28 | RSP => 0xffff_u32, 29 | RBP => 0_u32, 30 | RR => 0_u32, 31 | } 32 | end 33 | 34 | def [](num) 35 | @reg.fetch(num) 36 | end 37 | 38 | def []=(num, value) 39 | unless @reg.has_key?(num) 40 | raise "Unknown register: #{num}" 41 | end 42 | 43 | @reg[num] = value 44 | end 45 | 46 | def each 47 | @reg.each { |r| yield(r) } 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /src/emulate/video.cr: -------------------------------------------------------------------------------- 1 | require "sdl2" 2 | 3 | include SDL2 4 | 5 | @[Link("SDL2_gfx")] 6 | lib LibSDL2_GFX 7 | fun box_color = boxColor(dst : Void*, x1 : Int16, y1 : Int16, x2 : Int16, y2 : Int16, color : UInt32) : Int32 8 | end 9 | 10 | # ABGR 11 | ON = 0xFF005CFF_u32 12 | OFF = 0xFF301E0E_u32 13 | 14 | class Graphics 15 | property render 16 | 17 | def initialize(title : String, width : Int32, height : Int32) 18 | @render_changed = false 19 | 20 | # Create window 21 | @window = LibSDL2.create_window( 22 | title, 23 | LibSDL2::WINDOWPOS_CENTERED, 24 | LibSDL2::WINDOWPOS_CENTERED, 25 | width, 26 | height, 27 | Window::Flags::SHOWN) 28 | unless @window 29 | raise "SDL_CreateWindow Error: #{SDL2.error}" 30 | end 31 | 32 | # Create renderer 33 | @render = LibSDL2.create_renderer( 34 | @window, 35 | -1, 36 | Renderer::Flags::ACCELERATED | Renderer::Flags::PRESENTVSYNC | Renderer::Flags::TARGETTEXTURE) 37 | unless @render 38 | raise "SDL_CreateRenderer Error: #{SDL2.error}" 39 | end 40 | 41 | LibSDL2.set_render_draw_blend_mode(@render, LibSDL2::BlendMode::BLEND) 42 | 43 | # Create texture 44 | @texture = LibSDL2.create_texture( 45 | @render, 46 | 0x16462004_u32, 47 | LibSDL2::TextureAccess::TARGET, 48 | width, 49 | height) 50 | 51 | LibSDL2.set_render_target(@render, @texture) 52 | end 53 | 54 | def update_render 55 | if @render_changed 56 | LibSDL2.set_render_target(@render, nil) 57 | LibSDL2.render_copy(@render, @texture, nil, nil) 58 | LibSDL2.render_present(@render) 59 | @render_changed = false 60 | end 61 | end 62 | 63 | def set_render_changed 64 | @render_changed = true 65 | end 66 | 67 | def clear_background 68 | LibSDL2.set_render_draw_color( 69 | @render, 70 | (OFF & 0x000000ff) >> 0, 71 | (OFF & 0x0000ff00) >> 8, 72 | (OFF & 0x00ff0000) >> 16, 73 | (OFF & 0xff000000) >> 24, 74 | ) 75 | LibSDL2.render_clear(@render) 76 | end 77 | 78 | def draw_block(x, y, width, height, color) 79 | LibSDL2_GFX.box_color(@render.as(Void*), x.to_i16, y.to_i16, x.to_i16 + width, y.to_i16 + width, color) 80 | 81 | set_render_changed 82 | end 83 | 84 | def render 85 | LibSDL2.set_render_target(@render, @texture) 86 | clear_background 87 | yield 88 | update_render 89 | end 90 | 91 | def finalize 92 | LibSDL2.destroy_renderer(@render) 93 | LibSDL2.destroy_window(@window) 94 | end 95 | end 96 | 97 | EMPTY = 0xFF4f4c42_u32 98 | TEAL = 0xFFFFDB7F_u32 99 | BLUE = 0xFFD97400_u32 100 | ORANGE = 0xFF1B85FF_u32 101 | YELLOW = 0xFF00DCFF_u32 102 | GREEN = 0xFF40CC2E_u32 103 | PURPLE = 0xFF4B1485_u32 104 | RED = 0xFF4B59F2_u32 105 | 106 | class Video 107 | PADDING = 16 108 | 109 | def initialize(mem : Mem) 110 | @mem = mem 111 | end 112 | 113 | def draw_once(graphics) 114 | row_length_in_bytes = 160 115 | num_rows = 120 116 | 117 | num_bytes = num_rows * row_length_in_bytes 118 | 119 | num_bytes.times do |index| 120 | x = (index % row_length_in_bytes) 121 | y = (index / row_length_in_bytes) 122 | 123 | byte = @mem.fetch(0x10000 + index) 124 | color = (byte != 0x00) ? ON : OFF 125 | graphics.draw_block(PADDING + x * 4, PADDING + y * 4, 4, 4, color) 126 | end 127 | end 128 | 129 | def draw 130 | SDL2.run(SDL2::INIT::EVERYTHING) do 131 | graphics = Graphics.new("RCPU 3000", 2 * PADDING + 640, 2 * PADDING + 480) 132 | 133 | yield(graphics) 134 | end 135 | end 136 | 137 | def update(graphics) 138 | graphics.render do 139 | draw_once(graphics) 140 | end 141 | end 142 | 143 | def get_input 144 | while LibSDL2.poll_event(out e) == 1 145 | case e.type 146 | when EventType::QUIT 147 | return :quit 148 | end 149 | end 150 | return :none 151 | end 152 | 153 | def run(cpu, num_cycles) 154 | draw do |g| 155 | loop do 156 | case get_input 157 | when :quit 158 | break 159 | end 160 | 161 | cpu.run(num_cycles) 162 | update(g) 163 | break unless cpu.running 164 | end 165 | end 166 | end 167 | end 168 | --------------------------------------------------------------------------------