diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..01072ca --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.v] +indent_style = tab diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f4011a7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +* text=auto eol=lf +*.bat eol=crlf + +**/*.v linguist-language=V +**/*.vv linguist-language=V +**/*.vsh linguist-language=V +**/v.mod linguist-language=V diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ae7f786 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Binaries for programs and plugins +main +vchip8 +*.exe +*.exe~ +*.so +*.dylib +*.dll + +# Ignore binary output folders +bin/ + +# Ignore common editor/system specific metadata +.DS_Store +.idea/ +.vscode/ +*.iml + +# ENV +.env + +# vweb and database +*.db +*.js diff --git a/CHIP8_GUIDE.pdf b/CHIP8_GUIDE.pdf new file mode 100644 index 0000000..2fdf995 Binary files /dev/null and b/CHIP8_GUIDE.pdf differ diff --git a/CHIP8_WIKI.pdf b/CHIP8_WIKI.pdf new file mode 100644 index 0000000..3756323 Binary files /dev/null and b/CHIP8_WIKI.pdf differ diff --git a/Display.pdf b/Display.pdf new file mode 100644 index 0000000..f2a848f Binary files /dev/null and b/Display.pdf differ diff --git a/chip8ref.pdf b/chip8ref.pdf new file mode 100644 index 0000000..bd9e65e Binary files /dev/null and b/chip8ref.pdf differ diff --git a/src/chip8.v b/src/chip8.v new file mode 100644 index 0000000..ff3868e --- /dev/null +++ b/src/chip8.v @@ -0,0 +1,377 @@ + + +import rand + +const font := [ + [0xF0, 0x90, 0x90, 0x90, 0xF0], // 0 + [0x20, 0x60, 0x20, 0x20, 0x70], // 1 + [0xF0, 0x10, 0xF0, 0x80, 0xF0], // 2 + [0xF0, 0x10, 0xF0, 0x10, 0xF0], // 3 + [0x90, 0x90, 0xF0, 0x10, 0x10], // 4 + [0xF0, 0x80, 0xF0, 0x10, 0xF0], // 5 + [0xF0, 0x80, 0xF0, 0x90, 0xF0], // 6 + [0xF0, 0x10, 0x20, 0x40, 0x40], // 7 + [0xF0, 0x90, 0xF0, 0x90, 0xF0], // 8 + [0xF0, 0x90, 0xF0, 0x10, 0xF0], // 9 + [0xF0, 0x90, 0xF0, 0x90, 0x90], // A + [0xE0, 0x90, 0xE0, 0x90, 0xE0], // B + [0xF0, 0x80, 0x80, 0x80, 0xF0], // C + [0xE0, 0x90, 0x90, 0x90, 0xE0], // D + [0xF0, 0x80, 0xF0, 0x80, 0xF0], // E + [0xF0, 0x80, 0xF0, 0x80, 0x80], // F +] + +struct Screen{ + pub mut: + display_width int = 64 + display_height int = 32 + display [64][32]u8 +} + +const mem_size = 4096 +const num_of_registers = 16 +const f = 15 + +struct Chip8{ + pub mut: + ram [mem_size]u8 + v [num_of_registers]u8 + screen Screen + pc u16 + i int + stack Stack + delay_timer u8 +} + +fn (mut chip Chip8) start_cpu(){ + + // load font in the memory + for sprite in font { + for byte_sprite in sprite { + chip.set_ram(u8(byte_sprite)) + } + } +} + +fn (mut chip Chip8) run(){ + instruction := chip.fetch() + chip.decode_and_run(instruction) +} + +fn (mut chip Chip8) set_ram(instruction u8) { + chip.ram[chip.pc] = instruction +} + +fn (chip Chip8) get_instruction() u8{ + instruction := chip.ram[chip.pc] + return instruction +} + +fn (chip Chip8) fetch() u16{ + + mut instruction := u16(0x00) + mut half_instruction := chip.get_instruction() + + instruction = instruction | half_instruction + instruction = instruction << 8 + half_instruction = chip.get_instruction() + instruction = instruction | half_instruction + + return instruction +} + +fn (mut chip Chip8) decode_and_run(instruction u16) { + + mut nnn, mut nn, mut n, mut x, mut y := 0x00, 0x00, 0x00, 0x00, 0x00 + + mut opcode_msb := instruction & 0xF000 + mut opcode_lsb := instruction & 0x00FF + + match opcode_msb{ + + 0x0000 { + + match opcode_msb { + 0x00EE { + chip.pc = chip.stack.pop() or { panic(err) } + // Returns from a subroutine + } + + 0x00E0 { + + for i in 0..chip.screen.display_width { + for j in 0..chip.screen.display_height { + chip.screen.display[i][j] = 0 + } + } + } + + else { + nnn = instruction & 0x0FFF + // Calls machine code routine + } + } + } + + 0x1000 { + nnn = instruction & 0x0FFF + chip.pc = u8(nnn) + // Jumps to address NNN + } + + 0x2000 { + + nnn = instruction & 0x0FFF + + chip.stack.push(chip.pc) + chip.pc = u16(nnn) + // Calls subroutine at NNN + } + + 0x3000 { + x = (instruction & 0x0F00) >> 8 + nn = instruction & 0x00FF + + if chip.v[x] == nn { chip.pc += 2 } + // Skips the next instruction if VX equals NN + } + + 0x4000 { + x = (instruction & 0x0F00) >> 8 + nn = instruction & 0x00FF + + if chip.v[x] != nn { chip.pc += 2 } + // Skips the next instruction if VX does not equal NN + } + + 0x5000 { + x = (instruction & 0x0F00) >> 8 + y = (instruction & 0x00F0) >> 4 + + if chip.v[x] == chip.v[y] { chip.pc += 2 } + // Skips the next instruction if VX equals VY + } + + 0x6000 { + x = instruction & 0xF00 + nn = instruction & 0x00FF + + chip.v[x] = u8(nn) + // Sets VX to NN + } + + 0x7000 { + x = instruction & 0xF00 + nn = instruction & 0x00FF + + chip.v[x] += u8(nn) + // Adds NN to VX (carry flag is not changed) + } + + 0x8000 { + x = (instruction & 0x0F00) >> 8 + y = (instruction & 0x00F0) >> 4 + opcode_lsb = instruction & 0x000F + + match opcode_lsb { + 0x00 { + chip.v[x] = chip.v[y] + // Sets VX to the value of VY + } + + 0x01 { + chip.v[x] |= chip.v[y] + // Sets VX to VX or VY. (bitwise OR operation). + } + + 0x02 { + chip.v[x] &= chip.v[y] + // Sets VX to VX and VY. (bitwise AND operation) + } + + 0x03 { + chip.v[x] ^= chip.v[y] + // Sets VX to VX xor VY + } + + 0x04 { + + xy := chip.v[x] + chip.v[y] + + if xy > 255 { + chip.v[f] = 1 + }else{ + chip.v[f] = 0 + } + + chip.v[x] = xy + // Adds VY to VX. VF is set to 1 when there's an overflow, and to 0 when there is not. + } + + 0x05 { + xy := chip.v[x] - chip.v[y] + + if chip.v[x] >= chip.v[y] { + chip.v[f] = 1 + }else{ + chip.v[f] = 0 + } + + chip.v[x] = xy + // VY is subtracted from VX. VF is set to 0 when there's an underflow, and 1 when there is not. (i.e. VF set to 1 if VX >= VY and 0 if not) + } + + 0x06 { + chip.v[f] = (chip.v[x] & 0xF0) >> 7 + chip.v[x] = chip.v[x] >> 1 + // Stores the least significant bit of VX in VF and then shifts VX to the right by 1 + } + + 0x07 { + + xy := chip.v[y] - chip.v[x] + + if chip.v[y] >= chip.v[x] { + chip.v[f] = 1 + }else{ + chip.v[f] = 0 + } + + chip.v[x] = xy + // Sets VX to VY minus VX. VF is set to 0 when there's an underflow, and 1 when there is not. (i.e. VF set to 1 if VY >= VX). + } + + 0x0E { + + chip.v[f] = (chip.v[x] & 0xF0) >> 7 + + chip.v[x] = chip.v[x] >> 1 + // Stores the most significant bit of VX in VF and then shifts VX to the left by 1. + } + + else{ + panic('Invalid instruction!') + } + } + + } + + 0x9000 { + x = (instruction & 0x0F00) >> 8 + y = (instruction & 0x00F0) >> 4 + + if chip.v[x] != chip.v[y] { chip.pc += 2 } + } + + 0xA000 { + nnn = instruction & 0x0FFF + + chip.i = nnn + } + + 0xB000 { + nnn = instruction & 0x0FFF + + chip.pc = u16(nnn + chip.v[0]) + } + + 0xC000 { + x = (instruction & 0x0F00) >> 8 + nn = instruction & 0x00FF + + randint := rand.intn(256) or { panic(err) } + + chip.v[x] = u8(randint & nn) + } + + 0xD000 { + x = (instruction & 0x0F00) >> 8 + y = (instruction & 0x00F0) >> 4 + n = (instruction & 0x000F) + + chip.v[f] = 0 + + for y_coord := 0; y_coord < n; y_coord++ { + + regvy := chip.v[y + y_coord] & chip.screen.display_height-1 + + for x_coord := 0; x_coord < 8; x_coord++ { + + regvx := chip.v[x + x_coord] & chip.screen.display_width-1 + + if chip.screen.display[regvx + y_coord][regvy + x_coord] == 1 { + chip.v[f] = 1 + chip.screen.display[regvx + y_coord][regvy + x_coord] = 0 + } + + chip.screen.display[regvx + y_coord][regvy + x_coord] = chip.ram[chip.i + y_coord] + } + } + } + + //0xE000 { + // x = (opcode & 0x0F00) >> 8 + // s_opcode = opcode & 0x00FF +// + // match s_opcode { +// + // 0x9E { +// + // }, +// + // 0xA1 { +// + // }, + // } + //}, +// + //0xF000 { + // x = (opcode & 0x0F00) >> 8 + // s_opcode = opcode & 0x00FF +// + // match s_opcode { + // 0x07{ +// + // }, +// + // 0x0A{ +// + // }, +// + // 0x15{ +// + // }, +// + // 0x18{ +// + // }, +// + // 0x1E{ + // chip.i += chip.v[x] + // }, +// + // 0x29{ +// + // }, +// + // 0x33{ +// + // }, +// + // 0x55{ +// + // }, +// + // 0x65{ +// + // }, + // } + //}, + + else { + panic('Invalid instruction!') + } + } + +} + diff --git a/src/main.v b/src/main.v new file mode 100644 index 0000000..ff0eda0 --- /dev/null +++ b/src/main.v @@ -0,0 +1,175 @@ +module main + +import os +import gg +import gx + +struct Emulator{ + + pub mut: + chip8 Chip8 + graphic &gg.Context = unsafe { nil } + is_graphic bool = is_graphic() +} + +fn (emulator Emulator) draw_block(i int, j int, mut ctx gg.Context) { + emulator.graphic.draw_rect_filled(f32((j - 1) * 20) + 1, f32((i - 1) * 20), f32(20 - 1), f32(20 - 1), gx.rgb(255,255,255)) +} + + +fn (mut emulator Emulator) load_rom() !{ + + arguments := os.args.clone() + + if arguments.len > 1 { + + mut file := os.open(arguments[2])! + defer { file.close() } + + println(' Loading ROM in the memory...\n') + load_animate() + + for instruction in file.read_bytes_at(1024, 0) { + emulator.chip8.set_ram(u8(instruction)) + } + + println(' ROM successfully loaded into memory!') + + }else{ + panic('ROM path not especified!') + } +} + +fn (mut emulator Emulator) draw_screen(){ + + emulator.graphic.begin() + + display_height := emulator.chip8.screen.display_height + display_width := emulator.chip8.screen.display_width + + mut buf := []u8{len: display_height*display_width} + mut i := 0 + + for y := 0; y < emulator.chip8.screen.display_height; y++ { + for x := 0; x < emulator.chip8.screen.display_width; x++ { + + pixel := emulator.chip8.screen.display[y][x] + + buf[i] = u8((0xFFFFFF00 * pixel) | 0x000000FF); + i++ + } + } + + emulator.graphic.create_image_from_memory(unsafe{ &buf[0] }, buf.len) or { panic(err) } + emulator.graphic.end() + +} + +fn (mut emulator Emulator) show_display(){ + emulator.graphic.run() +} + +//fn (emulator Emulator) keyboard(input string) !string{ +// +// match input { +// '1' { +// return 0x0001 +// }, +// +// '2' { +// +// }, +// +// '3' { +// +// }, +// +// '4' { +// +// }, +// +// 'Q', 'q' { +// +// }, +// +// 'W', 'w' { +// +// }, +// +// 'E', 'e' { +// +// }, +// +// 'R', 'r' { +// +// }, +// +// 'A', 'a' { +// +// }, +// +// 'S', 's' { +// +// }, +// +// 'D', 'd' { +// +// }, +// +// 'F', 'f' { +// +// }, +// +// 'Z', 'z' { +// +// }, +// +// 'X', 'x' { +// +// }, +// +// 'C', 'c' { +// +// }, +// +// 'V', 'v' { +// +// }, +// +// else { +// panic('Invalid key!') +// } +// } +// +//} + +fn is_graphic() bool{ + return os.environ()['DISPLAY'] != '' +} + +fn main() { + + mut emulator := Emulator{ + + chip8 : Chip8{} + + graphic : gg.new_context( + bg_color: gx.rgb(0, 0, 0) + width: 1280 + height: 640 + window_title: 'V CHIP-8 Emulator' + ) + } + + if emulator.is_graphic { + + emulator.load_rom()! + emulator.chip8.start_cpu() + emulator.chip8.run() + + emulator.draw_screen() + emulator.show_display() + }else{ + panic('System is not graphic!') + } +} diff --git a/src/utils.v b/src/utils.v new file mode 100644 index 0000000..59a49c8 --- /dev/null +++ b/src/utils.v @@ -0,0 +1,40 @@ +module main + +import time + +struct Stack{ + pub mut: + addresses [16]u16 + i_control int +} + +fn (stack Stack) is_empty() bool{ + return stack.i_control == 0 +} + +fn (mut stack Stack) push(address u16){ + stack.addresses[stack.i_control] = address + stack.i_control++ +} + +fn (mut stack Stack) pop() !u16{ + if stack.is_empty() { + return error('Stack is empty!') + }else{ + val := stack.addresses[stack.i_control-1] + stack.addresses[stack.i_control-1] = 0 + stack.i_control-- + return val + } +} + + +fn load_animate() { + + mut bars := ['|','/','-','\\'] + + for i := 0; i < 10000; i++ { + print('[${bars[i%4]}]\r ') + time.sleep(400000) + } +} \ No newline at end of file diff --git a/v.mod b/v.mod new file mode 100644 index 0000000..f28e414 --- /dev/null +++ b/v.mod @@ -0,0 +1,7 @@ +Module { + name: 'vchip8' + description: 'CHIP8 emulator written in V programming language' + version: '0.0.0' + license: 'MIT' + dependencies: [] +}