commit fa7998f2764cdfb59d9288073cd4405401c0322f Author: Nicholas Orlowsky Date: Tue Jan 30 19:54:35 2024 -0600 graphics working diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..697cf4f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +obj_dir/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef07fcd --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Yet Another Yet Another Chip-8 Emulator (yayacemu) + +[Because one just wasn't enough](https://github.com/nickorlow/yacemu). + +A Chip-8 emulator (interpreter) written in SystemVerilog with I/O simulated with C++ also depends on SDL2. + +### Building & Testing + +In order to run yayacemu, you must have sdl2, verilator, and make installed. + +Once you have the dependencies installed, you can build the emulator with: +```shell +make build +``` + +You can build start the emulator with: +```shell +make run ROM_FILE=[PATH_TO_YOUR_ROM] +``` + + +### Running + +If you have a binary, you can run it with the following: + +```shell +yayacemu [PATH_TO_YOUR_ROM] +``` + +### Todo +- [x] Graphics +- [ ] Corax+ Required Instructions +- [ ] Proper Flag Handling +- [ ] Working Input +- [ ] More Instructions +- [ ] Tetris Working Running +- [ ] Pass Quirks Test (DispQuirk is a bit touchy) +- [ ] Add beeper (instead of sound, screen becomes red) +- [ ] Code cleanup +- [ ] All Instructions + +### Screenshots + +![Chip 8 Logo Demo](https://github.com/nickorlow/yayacemu/blob/main/screenshots/chip8-logo.png?raw=true) +![IBM Logo Demo](https://github.com/nickorlow/yayacemu/blob/main/screenshots/ibm-logo.png?raw=true) diff --git a/makefile b/makefile new file mode 100644 index 0000000..d821024 --- /dev/null +++ b/makefile @@ -0,0 +1,16 @@ +SDL_CFLAGS = `sdl2-config --cflags` +SDL_LDFLAGS = `sdl2-config --libs` + +lint: + verilator --lint-only --timing yayacemu.sv rom_loader.sv + +build: lint + verilator --cc --exe --build --timing -j 0 yayacemu.sv rom_loader.sv yayacemu.cpp -CFLAGS "${SDL_CFLAGS}" -LDFLAGS "${SDL_LDFLAGS}" && clear + +run: build + obj_dir/Vyayacemu ${ROM_FILE} + +clean: + rm -rf obj_dir + + diff --git a/rom_loader.sv b/rom_loader.sv new file mode 100644 index 0000000..237d98f --- /dev/null +++ b/rom_loader.sv @@ -0,0 +1,23 @@ +module rom_loader ( + output bit [7:0] memory [0:4095], + output logic rom_ready + ); + + import "DPI-C" function int load_rom(); + import "DPI-C" function bit [7:0] get_next_instr(); + import "DPI-C" function void close_rom(); + + int rom_size; + int i; + + initial begin + rom_size = load_rom(); + $display("HW : ROM size is %0d bytes (%0d bits) (%0d instructions)", rom_size, rom_size * 8, rom_size / 2); + for (i = 0; i < rom_size; i++) begin + memory[i] = get_next_instr(); + end + close_rom(); + $display("HW : ROM loaded successfully"); + rom_ready = 1; + end +endmodule diff --git a/screenshots/chip8-logo.png b/screenshots/chip8-logo.png new file mode 100644 index 0000000..0b69ff9 Binary files /dev/null and b/screenshots/chip8-logo.png differ diff --git a/screenshots/ibm-logo.png b/screenshots/ibm-logo.png new file mode 100644 index 0000000..f50da02 Binary files /dev/null and b/screenshots/ibm-logo.png differ diff --git a/tests/1-chip8-logo.ch8 b/tests/1-chip8-logo.ch8 new file mode 100644 index 0000000..8f83105 Binary files /dev/null and b/tests/1-chip8-logo.ch8 differ diff --git a/tests/2-ibm-logo.ch8 b/tests/2-ibm-logo.ch8 new file mode 100644 index 0000000..d60dac8 Binary files /dev/null and b/tests/2-ibm-logo.ch8 differ diff --git a/yayacemu.cpp b/yayacemu.cpp new file mode 100644 index 0000000..1b5c7d4 --- /dev/null +++ b/yayacemu.cpp @@ -0,0 +1,105 @@ +#include "svdpi.h" +#include "Vyayacemu__Dpi.h" +#include +#include + +#include "Vyayacemu.h" +#include "verilated.h" + +#include +#include +#include +#include +#include + +#define SCREEN_WIDTH 64 +#define SCREEN_HEIGHT 32 + +FILE *rom_file; +SDL_Window *window; +SDL_Renderer *renderer; +SDL_Texture *texture; +char* rom_name; + +void init_screen() { + SDL_Init(SDL_INIT_EVERYTHING); + window = + SDL_CreateWindow("Yet Another Yet Another Chip-8 Emulator", // creates a window + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + SCREEN_WIDTH * 10, SCREEN_HEIGHT * 10, 0); + renderer = + SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_STREAMING, + SCREEN_WIDTH, SCREEN_HEIGHT); + printf("INF_EMU: Screen initialized\n"); +} + +void draw_screen(const svLogicVecVal* vram) { + uint32_t *screen = (uint32_t*) malloc(SCREEN_WIDTH*SCREEN_HEIGHT*32); + for(int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) { + screen[i] = vram[i].aval; + } + SDL_UpdateTexture(texture, NULL, screen, + sizeof(screen[0]) * SCREEN_WIDTH); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + printf("INF_EMU: Drawing Frame\n"); +} + +int load_rom() { + printf("INF_EMU: Loading ROM %s\n", rom_name); + + rom_file = fopen(rom_name, "r"); + + if (rom_file == NULL) { + printf("INF_EMU: Error reading ROM file. Panicing.\n"); + exit(1); + } + + fseek(rom_file, 0L, SEEK_END); + int rom_size = ftell(rom_file); + fseek(rom_file, 0L, SEEK_SET); + printf("INF_EMU: ROM size is %d bytes\n", rom_size); + + return rom_size; +} + +svBitVecVal get_next_instr() { + return (uint8_t) fgetc(rom_file); +} + +void close_rom() { + fclose(rom_file); +} + +int get_num() { + return 1; +} + +int main(int argc, char** argv) { + if (argc < 2) { + printf("Use: yayacemu [ROM_NAME]"); + exit(1); + } + rom_name = argv[1]; + VerilatedContext* contextp = new VerilatedContext; + contextp->commandArgs(argc, argv); + Vyayacemu* top = new Vyayacemu{contextp}; + //while (!contextp->gotFinish()) { + top->pc_out = 0; + while (top->pc_out < 100) { + top->clk_in ^= 1; + top->pc_in = top->pc_out; + top->eval(); + usleep(500); + } + printf("TB : Testbench has reached end of simulation. Pausing for 10 seconds before exiting"); + fflush(stdout); + usleep(10000000); + delete top; + delete contextp; + return 0; +} diff --git a/yayacemu.out b/yayacemu.out new file mode 100755 index 0000000..a31c378 --- /dev/null +++ b/yayacemu.out @@ -0,0 +1,71 @@ +#! /usr/bin/vvp +:ivl_version "12.0 (stable)" "(v12_0-dirty)"; +:ivl_delay_selection "TYPICAL"; +:vpi_time_precision + 0; +:vpi_module "/usr/lib/ivl/system.vpi"; +:vpi_module "/usr/lib/ivl/vhdl_sys.vpi"; +:vpi_module "/usr/lib/ivl/vhdl_textio.vpi"; +:vpi_module "/usr/lib/ivl/v2005_math.vpi"; +:vpi_module "/usr/lib/ivl/va_math.vpi"; +:vpi_module "/usr/lib/ivl/v2009.vpi"; +S_0x5617a32fb170 .scope package, "$unit" "$unit" 2 1; + .timescale 0 0; +S_0x5617a32fb300 .scope module, "chip8_processor" "chip8_processor" 3 1; + .timescale 0 0; + .port_info 0 /INPUT 16 "opcode"; +o0x7f10d36a7018 .functor BUFZ 16, C4; HiZ drive +v0x5617a32fb490_0 .net "opcode", 15 0, o0x7f10d36a7018; 0 drivers +v0x5617a33439a0_0 .var "opcode2", 15 0; + .scope S_0x5617a32fb300; +T_0 ; + %wait E_0x0; + %pushi/vec4 4064, 0, 16; + %store/vec4 v0x5617a33439a0_0, 0, 16; + %load/vec4 v0x5617a33439a0_0; + %dup/vec4; + %pushi/vec4 224, 0, 16; + %cmp/z; + %jmp/1 T_0.0, 4; + %dup/vec4; + %pushi/vec4 238, 0, 16; + %cmp/z; + %jmp/1 T_0.1, 4; + %dup/vec4; + %pushi/vec4 0, 4095, 16; + %cmp/z; + %jmp/1 T_0.2, 4; + %dup/vec4; + %pushi/vec4 4096, 4095, 16; + %cmp/z; + %jmp/1 T_0.3, 4; + %dup/vec4; + %pushi/vec4 8192, 4095, 16; + %cmp/z; + %jmp/1 T_0.4, 4; + %jmp T_0.5; +T_0.0 ; + %vpi_call/w 3 9 "$display", "CLS" {0 0 0}; + %jmp T_0.5; +T_0.1 ; + %vpi_call/w 3 10 "$display", "RET" {0 0 0}; + %jmp T_0.5; +T_0.2 ; + %vpi_call/w 3 11 "$display", "SYS" {0 0 0}; + %jmp T_0.5; +T_0.3 ; + %vpi_call/w 3 12 "$display", "JP" {0 0 0}; + %jmp T_0.5; +T_0.4 ; + %vpi_call/w 3 13 "$display", "CALL" {0 0 0}; + %jmp T_0.5; +T_0.5 ; + %pop/vec4 1; + %vpi_call/w 3 15 "$display", "yayacemu" {0 0 0}; + %jmp T_0; + .thread T_0; +# The file index is used to find the file name in the following table. +:file_names 4; + "N/A"; + ""; + "-"; + "yayacemu.sv"; diff --git a/yayacemu.sv b/yayacemu.sv new file mode 100644 index 0000000..f78c297 --- /dev/null +++ b/yayacemu.sv @@ -0,0 +1,115 @@ +module yayacemu ( + input wire clk_in, + input int pc_in, + output int pc_out + ); + import "DPI-C" function void init_screen(); + + wire [31:0] vram [0:2047]; + bit [7:0] memory [0:4095]; + wire [7:0] registers [0:15]; + logic [7:0] index_reg; + logic rom_ready; + int pc; + + rom_loader rl (memory, rom_ready); + chip8_cpu cpu (memory, clk_in, vram, index_reg, registers, pc_in, pc_out); + chip8_gpu gpu (vram); + + initial begin + init_screen(); + end + +endmodule + +module chip8_cpu( + input bit [7:0] memory [0:4095], + input wire clk_in, + output wire [31:0] vram [0:2047], + output wire [7:0] index_reg, + output wire [7:0] registers [0:15], + input int pc_in, + output int pc_out + ); + + logic [15:0] opcode; + logic [15:0] scratch; + + logic [31:0] x_cord; + logic [31:0] y_cord; + logic [7:0] size; + logic [31:0] screen_pixel; + logic [7:0] sprite_pixel; + + int i; + + int r; + int c; + always_ff @(posedge clk_in) begin + opcode = {memory[pc_in], memory[pc_in+1]}; + $display("HW : opcode is 0x%h (%b)", opcode, opcode); + $display("HW : PC %0d", pc_in); + + casez(opcode) + 'h00E0: begin + $display("HW : INSTR CLS"); + for(i = 0; i < 2048; i++) begin + vram[i] = 0; + end + end + 'h00EE: $display("HW : INSTR RET"); + 'h0???: $display("HW : INSTR SYS addr"); + 'h1???: $display("HW : INSTR JP addr"); + 'h2???: $display("HW : INSTR CALL addr"); + 'h6???: begin + $display("HW : INSTR LD Vx, byte"); + scratch = (opcode & 'h00FF); + registers[(opcode & 'h0F00) >> 8] = scratch[7:0]; + end + 'h7???: begin + $display("HW : INSTR ADD Vx, byte"); + scratch = (opcode & 'h00FF); + registers[(opcode & 'h0F00) >> 8] += scratch[7:0]; + end + 'hA???: begin + $display("HW : INSTR LD I, addr"); + scratch = (opcode & 'h0FFF); + index_reg = scratch[7:0]; + end + 'hD???: begin + $display("HW : INSTR DRW Vx, Vy, nibble"); + x_cord = {24'h000000, registers[(opcode & 'h0F00) >> 8]}; + y_cord = {24'h000000, registers[(opcode & 'h00F0) >> 4]}; + + scratch = (opcode & 'h000F); + size = scratch[7:0]; + + for (r = 0; r < size; r++) begin + for ( c = 0; c < 8; c++) begin + screen_pixel = vram[((r + y_cord) * 64) + (x_cord + c)]; + sprite_pixel = memory[{24'h000000, index_reg} + r] & ('h80 >> c); + + if (|sprite_pixel) begin + if (screen_pixel == 32'hFFFFFFFF) begin + registers[15] = 1; + end + vram[((r + y_cord) * 64) + (x_cord + c)] = screen_pixel ^ 32'hFFFFFFFF; + end + end + end + end + default: $display("HW : ILLEGAL INSTRUCTION"); + endcase + + pc_out <= pc_in + 2; + end +endmodule + +module chip8_gpu ( + input wire [31:0] vram [0:2047] + ); + import "DPI-C" function void draw_screen(logic [31:0] vram [0:2047]); + always_comb begin + draw_screen(vram); + end +endmodule