Compare commits

..

2 Commits

Author SHA1 Message Date
Vinicius Silva 0424fa56b1
Merge pull request #4 from spytheman/improvements
Fix compilation with latest V, separate updates from the display refresh rate, improve keyboard handling, run `v fmt -w .`, enable running on macos and windows.
2025-04-08 08:53:19 -03:00
Delyan Angelov 961212808d
Fix compilation with latest V, separate updates from the display refresh rate, improve keyboard handling, run `v fmt -w .`, enable running on macos and windows. 2025-04-08 12:32:47 +03:00
3 changed files with 198 additions and 283 deletions

View File

@ -1,38 +1,26 @@
import rand
const keyboard := {
0x1 : 0x1 //1 - 1
0x2 : 0x2 //2 - 2
0x3 : 0x3 //3 - 3
0x4 : 0x43 //4 - C
0x51 : 0x4 //Q - 4
0x71 : 0x4 //q - 4
0x57 : 0x5 //W - 5
0x77 : 0x5 //w - 5
0x45 : 0x6 //E - 6
0x65 : 0x6 //e - 6
0x52 : 0x44 //R - D
0x72 : 0x44 //r - D
0x41 : 0x7 //A - 7
0x61 : 0x7 //a - 7
0x53 : 0x8 //S - 8
0x73 : 0x8 //s - 8
0x44 : 0x9 //D - 9
0x64 : 0x9 //d - 9
0x46 : 0x45 //F - E
0x66 : 0x45 //f - E
0x5A : 0x41 //Z - A
0x7A : 0x41 //z - A
0x58 : 0x30 //X - 0
0x78 : 0x30 //x - 0
0x43 : 0x42 //C - B
0x63 : 0x42 //c - B
0x56 : 0x46 //V - F
0x76 : 0x46 //v - F
const keyboard = {
0x31: 0x01 // keycode 1 - 1
0x32: 0x02 // keycode 2 - 2
0x33: 0x03 // keycode 3 - 3
0x34: 0x0C // keycode 4 - C
0x51: 0x04 // keycode Q - 4
0x57: 0x05 // keycode W - 5
0x45: 0x06 // keycode E - 6
0x52: 0x0D // keycode R - D
0x41: 0x07 // keycode A - 7
0x53: 0x08 // keycode S - 8
0x44: 0x09 // keycode D - 9
0x46: 0x0E // keycode F - E
0x5A: 0x0A // keycode Z - A
0x58: 0x00 // keycode X - 0
0x43: 0x0B // keycode C - B
0x56: 0x0F // keycode V - F
}
const font := [
[u8(0xF0), u8(0x90), u8(0x90), u8(0x90), u8(0xF0)], // 0
const font = [
[u8(0xF0), u8(0x90), u8(0x90), u8(0x90), u8(0xF0)], // 0
[u8(0x20), u8(0x60), u8(0x20), u8(0x20), u8(0x70)], // 1
[u8(0xF0), u8(0x10), u8(0xF0), u8(0x80), u8(0xF0)], // 2
[u8(0xF0), u8(0x10), u8(0xF0), u8(0x10), u8(0xF0)], // 3
@ -46,40 +34,39 @@ const font := [
[u8(0xE0), u8(0x90), u8(0xE0), u8(0x90), u8(0xE0)], // B
[u8(0xF0), u8(0x80), u8(0x80), u8(0x80), u8(0xF0)], // C
[u8(0xE0), u8(0x90), u8(0x90), u8(0x90), u8(0xE0)], // D
[u8(0xF0), u8(0x80), u8(0xF0), u8(0x80), u8(0xF0)], // E
[u8(0xF0), u8(0x80), u8(0xF0), u8(0x80), u8(0x80)], // F
[u8(0xF0), u8(0x80), u8(0xF0), u8(0x80), u8(0xF0)], // E
[u8(0xF0), u8(0x80), u8(0xF0), u8(0x80), u8(0x80)], // F
]
struct Screen{
pub:
display_width int = 64
display_height int = 32
mut:
display [32][64]u8
struct Screen {
pub:
display_width int = 64
display_height int = 32
mut:
display [32][64]u8
}
const mem_size = 4096
const mem_size = 4096
const num_of_registers = 16
const f = 15
const f = 15
struct Chip8{
struct Chip8 {
cpu_clock u8 = 9
pub mut:
ram [mem_size]u8
v [num_of_registers]u8
screen Screen
pc u16 = 0x200
i u16
stack Stack
delay_timer u8
sound_timer u8
is_draw bool
key u8
cycles u8
pub mut:
ram [mem_size]u8
v [num_of_registers]u8
screen Screen
pc u16 = 0x200
i u16
stack Stack
delay_timer u8
sound_timer u8
is_draw bool
key u8
cycles u8
}
fn (mut chip Chip8) start_cpu(){
fn (mut chip Chip8) start_cpu() {
chip.screen = Screen{}
// load font in the memory
@ -88,89 +75,79 @@ fn (mut chip Chip8) start_cpu(){
}
}
fn (mut chip Chip8) run(){
fn (mut chip Chip8) run() {
mut instruction := chip.fetch()
chip.decode_and_run(instruction)
}
@[direct_array_access]
fn (mut chip Chip8) set_ram(instructions []u8, index u16) {
mut j := index
for i := 0; i < instructions.len; i++ {
chip.ram[j] = instructions[i]
j++
}
}
}
fn (mut chip Chip8) fetch() u16{
@[direct_array_access]
fn (mut chip Chip8) fetch() u16 {
mut instruction := u16(0x00)
mut half_instruction := chip.ram[chip.pc]
instruction = instruction | half_instruction
instruction = instruction << 8
half_instruction = chip.ram[chip.pc + 1]
instruction = instruction | half_instruction
return instruction
}
@[direct_array_access]
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
mut is_jump := false
chip.is_draw = false
if chip.delay_timer > 0 { chip.delay_timer-- }
if chip.sound_timer > 0 { chip.sound_timer-- }
match opcode_msb{
if chip.delay_timer > 0 {
chip.delay_timer--
}
if chip.sound_timer > 0 {
chip.sound_timer--
}
match opcode_msb {
0x0000 {
match opcode_lsb {
0xEE {
chip.pc = chip.stack.pop() or { panic(err) }
// Returns from a subroutine
}
0xE0 {
chip.is_draw = true
for i := 0; i < chip.screen.display_height; i++{
for i := 0; i < chip.screen.display_height; i++ {
for j := 0; j < chip.screen.display_width; j++ {
chip.screen.display[i][j] = 0
}
}
}
//0NNN {
// 0NNN {
// nnn = instruction & 0x0FFF
// Calls machine code routine
// Calls machine code routine
//}
else{
else {
nnn = instruction & 0x0FFF
//panic('Invalid instruction! 0x${instruction.hex()}')
// panic('Invalid instruction! 0x${instruction.hex()}')
}
}
}
0x1000 {
nnn = instruction & 0x0FFF
chip.pc = u16(nnn)
is_jump = true
// Jumps to address NNN
}
0x2000 {
nnn = instruction & 0x0FFF
chip.stack.push(chip.pc)
@ -178,47 +155,47 @@ fn (mut chip Chip8) decode_and_run(instruction u16) {
is_jump = true
// Calls subroutine at NNN
}
0x3000 {
x = (instruction & 0x0F00) >> 8
x = (instruction & 0x0F00) >> 8
nn = instruction & 0x00FF
if chip.v[x] == nn { chip.pc += 2 }
if chip.v[x] == nn {
chip.pc += 2
}
// Skips the next instruction if VX equals NN
}
0x4000 {
x = (instruction & 0x0F00) >> 8
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
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
if chip.v[x] == chip.v[y] {
chip.pc += 2
}
// Skips the next instruction if VX equals VY
}
0x6000 {
x = (instruction & 0x0F00) >> 8
x = (instruction & 0x0F00) >> 8
nn = instruction & 0x00FF
chip.v[x] = u8(nn)
// Sets VX to NN
}
0x7000 {
x = (instruction & 0x0F00) >> 8
x = (instruction & 0x0F00) >> 8
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
@ -229,125 +206,105 @@ fn (mut chip Chip8) decode_and_run(instruction u16) {
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]
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{
} else {
chip.v[f] = 0
}
chip.v[x] = (xy & 0xFF)
// Adds VY to VX. VF is set to 1 when there's an overflow, and to 0 when there is not.
}
0x05 {
if chip.v[x] > chip.v[y] {
chip.v[f] = 1
}else{
} else {
chip.v[f] = 0
}
chip.v[x] -= chip.v[y]
// 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 {
if chip.v[x] % 2 == 1 {
chip.v[f] = 1;
}
else {
chip.v[f] = 0;
}
chip.v[f] = 1
} else {
chip.v[f] = 0
}
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{
} 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 {
if (chip.v[x] & 10000000) == 1 {
chip.v[f] = 1;
}
else {
chip.v[f] = 0;
}
if (chip.v[x] & 10000000) == 1 {
chip.v[f] = 1
} else {
chip.v[f] = 0
}
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{
else {
panic('Invalid instruction! 0x${instruction.hex()}')
}
}
}
0x9000 {
x = (instruction & 0x0F00) >> 8
y = (instruction & 0x00F0) >> 4
if chip.v[x] != chip.v[y] { chip.pc += 2 }
if chip.v[x] != chip.v[y] {
chip.pc += 2
}
}
0xA000 {
nnn = instruction & 0x0FFF
chip.i = u16(nnn)
}
0xB000 {
nnn = instruction & 0x0FFF
chip.pc = u16(nnn + chip.v[0])
is_jump = true
}
0xC000 {
x = (instruction & 0x0F00) >> 8
nn = instruction & 0x00FF
x = (instruction & 0x0F00) >> 8
nn = instruction & 0x00FF
randint := rand.intn(256) or { panic(err) }
chip.v[x] = u8(randint & nn)
}
0xD000 {
chip.is_draw = true
x = (instruction & 0x0F00) >> 8
y = (instruction & 0x00F0) >> 4
@ -359,115 +316,106 @@ fn (mut chip Chip8) decode_and_run(instruction u16) {
chip.v[f] = 0
for y_coord := 0; y_coord < n; y_coord++ {
pixel := chip.ram[chip.i + y_coord]
for x_coord := 0; x_coord < 8; x_coord++ {
if (regvy + y_coord) < chip.screen.display_height && (regvx + x_coord) < chip.screen.display_width {
if (regvy + y_coord) < chip.screen.display_height
&& (regvx + x_coord) < chip.screen.display_width {
if (pixel & (0x80 >> x_coord)) != 0 {
if chip.screen.display[regvy + y_coord][regvx + x_coord] == 1 {
chip.v[f] = 1
}
chip.screen.display[regvy + y_coord][regvx + x_coord] ^= 1
}
}
}
}
}
0xE000 {
x = (instruction & 0x0F00) >> 8
opcode_lsb = instruction & 0x00FF
match opcode_lsb {
0x9E {
if chip.key == chip.v[x] { chip.pc += 2 }
if chip.key == chip.v[x] {
chip.pc += 2
}
}
0xA1 {
if chip.key != chip.v[x] { chip.pc += 2 }
if chip.key != chip.v[x] {
chip.pc += 2
}
}
else{
else {
panic('Invalid instruction 0x${instruction.hex()}')
}
}
}
0xF000 {
x = (instruction & 0x0F00) >> 8
opcode_lsb = instruction & 0x00FF
match opcode_lsb {
0x07{
0x07 {
chip.v[x] = chip.delay_timer
}
0x0A{
0x0A {
chip.v[x] = chip.key
}
0x15{
0x15 {
chip.delay_timer = chip.v[x]
}
0x18{
0x18 {
chip.sound_timer = chip.v[x]
}
0x1E{
0x1E {
chip.i += chip.v[x]
}
0x29{
0x29 {
chip.i = u16(chip.v[x] * 0x5)
}
0x33{
0x33 {
chip.ram[chip.i] = u8(chip.v[x] / 100)
chip.ram[chip.i + 1] = u8((u8(chip.v[x] / 10)) % 10)
chip.ram[chip.i + 2] = u8(chip.v[x] % 100) % 10
}
0x55{
0x55 {
for i := chip.v[0]; i <= x; i++ {
chip.ram[chip.i + i] = chip.v[i]
}
chip.i = u16(x + 1)
}
0x65{
0x65 {
for i := chip.v[0]; i <= x; i++ {
chip.v[x] = chip.ram[chip.i + i]
}
chip.i = u16(x + 1)
}
else {
panic('Invalid instruction! 0x${instruction.hex()}')
}
}
}
else {
panic('Invalid instruction! 0x${instruction.hex()}')
}
}
if !is_jump { chip.pc += 2 }
if !is_jump {
chip.pc += 2
}
}
fn (mut chip Chip8) update_timers(){
if chip.delay_timer > 0 { chip.delay_timer-- }
if chip.sound_timer > 0 { chip.sound_timer-- }
fn (mut chip Chip8) update_timers() {
if chip.delay_timer > 0 {
chip.delay_timer--
}
if chip.sound_timer > 0 {
chip.sound_timer--
}
}
fn (mut chip Chip8) set_key(key int){
@[direct_array_access]
fn (mut chip Chip8) set_key(key int) {
chip.key = u8(keyboard[key])
}
}

View File

@ -3,107 +3,85 @@ module main
import os
import gg
import gx
import time
struct Emulator{
pub mut:
chip8 Chip8
graphic &gg.Context = unsafe { nil }
is_graphic bool = is_graphic()
struct Emulator {
pub mut:
chip8 Chip8
graphic &gg.Context = unsafe { nil }
}
fn (mut emulator Emulator) draw_block(i f32, j f32) {
emulator.graphic.draw_rect_filled(j,i, f32(20), f32(20), gx.rgb(0,255,0))
emulator.graphic.draw_rect_filled(j, i, f32(20), f32(20), gx.rgb(0, 255, 0))
}
fn (mut emulator Emulator) load_rom() !{
arguments := os.args.clone()
if arguments.len > 1 {
mut file := os.open(arguments[1])!
defer { file.close() }
println(' Loading ROM in the memory...\n')
load_animate()
mut instructions := file.read_bytes(1024)
mut index := u16(0x200)
emulator.chip8.set_ram(instructions, index)
println('ROM successfully loaded into memory!')
}else{
panic('ROM path not especified!')
fn (mut emulator Emulator) load_rom() ! {
if os.args.len <= 1 {
return error('ROM path not especified!')
}
mut file := os.open(os.args[1])!
defer { file.close() }
println('Loading ROM in the memory...')
mut instructions := file.read_bytes(1024)
mut index := u16(0x200)
emulator.chip8.set_ram(instructions, index)
println('ROM successfully loaded into memory!')
}
fn frame(mut emulator Emulator){
fn frame(mut emulator Emulator) {
emulator.graphic.begin()
emulator.chip8.run()
emulator.chip8.cycles++;
emulator.chip8.update_timers()
display_height := emulator.chip8.screen.display_height
display_width := emulator.chip8.screen.display_width
for y := 0; y < display_height; y++ {
for x := 0; x < display_width; x++ {
for y in 0 .. emulator.chip8.screen.display_height {
for x in 0 .. emulator.chip8.screen.display_width {
pixel := emulator.chip8.screen.display[y][x]
if pixel == 1 {
emulator.draw_block(f32((y)*20), f32((x)*20))
emulator.draw_block(f32(y * 20), f32(x * 20))
}
}
}
if emulator.chip8.cpu_clock == emulator.chip8.cycles {
emulator.chip8.cycles = 0
}
emulator.graphic.end()
}
fn (mut emulator Emulator) show_display(){
emulator.graphic.run()
fn (mut emulator Emulator) run(ms_per_tick int) {
for {
emulator.chip8.run()
emulator.chip8.cycles++
emulator.chip8.update_timers()
if emulator.chip8.cpu_clock == emulator.chip8.cycles {
emulator.chip8.cycles = 0
}
time.sleep(ms_per_tick * time.millisecond)
}
}
fn is_graphic() bool{
return os.environ()['DISPLAY'] != ''
fn on_event(e &gg.Event, mut emulator Emulator) {
if e.typ == .key_down {
x := int(e.key_code)
// eprintln('>>> e.typ: ${e.typ} | e.key_code: ${e.key_code} | x: ${x} | x.hex(): ${x.hex()}')
emulator.chip8.set_key(x)
}
if e.typ == .key_up {
emulator.chip8.set_key(0)
}
}
fn main() {
mut emulator := &Emulator{
chip8 : Chip8{}
}
if emulator.is_graphic {
emulator.load_rom()!
emulator.chip8.start_cpu()
emulator.graphic = gg.new_context(
bg_color: gx.rgb(0, 0, 0)
width: 1280
height: 640
window_title: 'V CHIP-8 Emulator'
user_data: emulator
frame_fn : frame
event_fn: on_event
)
emulator.show_display()
}else{
panic('System is not graphic!')
chip8: Chip8{}
}
emulator.load_rom()!
emulator.chip8.start_cpu()
emulator.graphic = gg.new_context(
bg_color: gx.rgb(0, 0, 0)
width: 1280
height: 640
window_title: 'V CHIP-8 Emulator'
user_data: emulator
frame_fn: frame
event_fn: on_event
)
// Ensure a constant rate of updates to the emulator, no matter
// what the refresh rate is, by running the updates in a separate
// independent thread:
spawn emulator.run(8)
emulator.graphic.run()
}
fn on_event(e &gg.Event, mut emulator Emulator){
emulator.chip8.set_key(e.char_code)
}

View File

@ -1,40 +1,29 @@
module main
import time
struct Stack{
pub mut:
addresses [16]u16
i_control int
struct Stack {
pub mut:
addresses [16]u16
i_control int
}
fn (stack Stack) is_empty() bool{
fn (stack Stack) is_empty() bool {
return stack.i_control == 0
}
fn (mut stack Stack) push(address u16){
@[direct_array_access]
fn (mut stack Stack) push(address u16) {
stack.addresses[stack.i_control] = address
stack.i_control++
}
fn (mut stack Stack) pop() !u16{
@[direct_array_access]
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
} 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 < 4000; i++ {
print('[${bars[i%4]}]\r ')
time.sleep(400000)
}
}