From 3c3dbd217507655597d86c87825c5a20f82e80f3 Mon Sep 17 00:00:00 2001 From: Koray Yanik Date: Fri, 13 Oct 2023 20:46:05 +0100 Subject: [PATCH] Starting work on PPU -> display -> VGA --- build/tb_top.Makefile | 9 +- dpi/vgasim.c | 113 +++++++++++++++++++++++ rtl/display.sv | 115 +++++++++++++++++++++++ rtl/gb.sv | 19 +++- rtl/ppu/ppu.sv | 18 +++- sim/tb_top.sv | 90 ++++++++++++++++-- sim/vgasim.sv | 44 +++++++++ src/vgasim | Bin 0 -> 16944 bytes src/vgasim.c | 210 ++++++++++++++++++++++++++++++++++++++++++ synthflow | 2 +- 10 files changed, 600 insertions(+), 20 deletions(-) create mode 100644 dpi/vgasim.c create mode 100644 rtl/display.sv create mode 100644 sim/vgasim.sv create mode 100755 src/vgasim create mode 100644 src/vgasim.c diff --git a/build/tb_top.Makefile b/build/tb_top.Makefile index 6e73ff0..a470ac5 100644 --- a/build/tb_top.Makefile +++ b/build/tb_top.Makefile @@ -1,7 +1,12 @@ TB = tb_top -SOURCES = gb.sv cpu.sv ppu.sv idec.sv ctrl.sv alu.sv alu16.sv regbank.sv rom.sv ram.sv cart.sv tb_top.sv clkgen.sv +SOURCES = gb.sv cpu.sv ppu.sv idec.sv ctrl.sv alu.sv alu16.sv regbank.sv rom.sv ram.sv cart.sv display.sv tb_top.sv vgasim.sv clkgen.sv INCLUDES = cpu_pkg.svh sva_common.svh -PATH_SRC = ../rtl:../rtl/cpu:../rtl/ppu:../sim + +DPI_SOURCES = vgasim.c +DPI_CFLAGS = +DPI_LIBS = + +PATH_SRC = ../rtl:../rtl/cpu:../rtl/ppu:../sim:../dpi DEFINES = SVA_ENABLE diff --git a/dpi/vgasim.c b/dpi/vgasim.c new file mode 100644 index 0000000..f229c4b --- /dev/null +++ b/dpi/vgasim.c @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#define WBUF_SIZE 32 + +static int fifo_fd; + +#define VGASIM_CMD_INIT 0x01 +#define VGASIM_CMD_RESET 0x02 +#define VGASIM_CMD_TICK 0x03 + +int vgasim_init(int screen_h, int screen_w, int screen_bpp) { + int ret; + ssize_t bytes_written, bytes_written_total; + ssize_t pkt_size; + char wbuf[WBUF_SIZE]; + uint16_t *wbuf_ptr; + printf("dpi: vgasim_init: %d x %d x %d\n", screen_h, screen_w, screen_bpp); + + ret = open("/tmp/vgasim", O_WRONLY | O_NONBLOCK); + if (ret == -1) { + printf("dpi: vgasim_init: failed (%d)\n", errno); + return 1; + } + + fifo_fd = ret; + + wbuf_ptr = (uint16_t*)wbuf; + + *wbuf_ptr++ = VGASIM_CMD_INIT; + *wbuf_ptr++ = (uint16_t)screen_h; + *wbuf_ptr++ = (uint16_t)screen_w; + *wbuf_ptr++ = (uint16_t)screen_bpp; + + pkt_size = 4 * sizeof(uint16_t); + + bytes_written_total = 0; + do { + bytes_written = write(fifo_fd, wbuf + bytes_written_total, pkt_size - bytes_written_total); + if (bytes_written < 0) { + printf("dpi: vgasim_init: write error (%d): %s\n", errno, strerror(errno)); + return 2; + } + bytes_written_total += bytes_written; + } while (bytes_written_total < pkt_size); + + return 0; +} + +int vgasim_reset() { + int ret; + ssize_t bytes_written, bytes_written_total; + ssize_t pkt_size; + char wbuf[WBUF_SIZE]; + uint16_t *wbuf_ptr; + + printf("dpi: vgasim_reset\n"); + + wbuf_ptr = (uint16_t*)wbuf; + *wbuf_ptr++ = VGASIM_CMD_RESET; + pkt_size = sizeof(uint16_t); + + bytes_written_total = 0; + do { + bytes_written = write(fifo_fd, wbuf + bytes_written_total, pkt_size - bytes_written_total); + if (bytes_written < 0) { + printf("dpi: vgasim_init: write error (%d): %s\n", errno, strerror(errno)); + return 2; + } + bytes_written_total += bytes_written; + } while (bytes_written_total < pkt_size); + + return 0; +} + +int vgasim_tick(int r, int g, int b, int hsync, int vsync) { + int ret; + ssize_t bytes_written, bytes_written_total; + ssize_t pkt_size; + char wbuf[WBUF_SIZE]; + uint16_t *wbuf_ptr16; + uint8_t *wbuf_ptr8; + + //printf("dpi: vgasim_tick: %X, %X, %X (%s%s)\n", r, g, b, hsync ? "h" : "", vsync ? "v" : ""); + + wbuf_ptr16 = (uint16_t*)wbuf; + *wbuf_ptr16++ = VGASIM_CMD_TICK; + wbuf_ptr8 = (uint8_t*)wbuf_ptr16; + *wbuf_ptr8++ = (uint8_t)r; + *wbuf_ptr8++ = (uint8_t)g; + *wbuf_ptr8++ = (uint8_t)b; + *wbuf_ptr8++ = (uint8_t)((vsync ? 0x02 : 0x00) | (hsync ? 0x01 : 0x00)); + pkt_size = sizeof(uint16_t) + 4 * sizeof(uint8_t); + + bytes_written_total = 0; + do { + bytes_written = write(fifo_fd, wbuf + bytes_written_total, pkt_size - bytes_written_total); + if (bytes_written < 0) { + printf("dpi: vgasim_init: write error (%d): %s\n", errno, strerror(errno)); + return 2; + } + bytes_written_total += bytes_written; + } while (bytes_written_total < pkt_size); + + return 0; +} diff --git a/rtl/display.sv b/rtl/display.sv new file mode 100644 index 0000000..f066fda --- /dev/null +++ b/rtl/display.sv @@ -0,0 +1,115 @@ +module display #( + parameter VGA_DEPTH = 8, + parameter VGA_W = 640, + parameter VGA_H = 480 +) ( + input logic nreset, + + input logic display_enable_i, + + input logic ppu_clk, + input logic [ 1:0] ppu_pixel_i, + input logic ppu_vsync_i, + input logic ppu_hsync_i, + + // RGB ppu_pixel + input logic [ 2:0][VGA_DEPTH-1:0] VGA_PALETTE[3:0], + + input logic vga_clk, + output logic [VGA_DEPTH-1:0] vga_r_o, + output logic [VGA_DEPTH-1:0] vga_g_o, + output logic [VGA_DEPTH-1:0] vga_b_o, + output logic vga_hsync_o, + output logic vga_vsync_o +); + +localparam DISPLAY_W = 160; +localparam DISPLAY_H = 144; +localparam BUFFER_SIZE = DISPLAY_W * DISPLAY_H; + +logic [7:0] ppu_px_r; +logic [7:0] ppu_py_r; +logic [7:0] ppu_px_next; +logic [7:0] ppu_py_next; + +logic [9:0] vga_px_r; +logic [9:0] vga_py_r; +logic [9:0] vga_px_next; +logic [9:0] vga_py_next; + +logic vga_vsync; +logic vga_hsync; + +always_ff @(posedge ppu_clk or negedge nreset) begin + if (!nreset) begin + ppu_px_r <= '0; + ppu_py_r <= '0; + end else begin + ppu_px_r <= ppu_px_next; + ppu_py_r <= ppu_py_next; + end +end +assign ppu_px_next = ppu_hsync_i ? '0 : (ppu_px_r + 8'h01); +assign ppu_py_next = ppu_vsync_i ? '0 : + ppu_hsync_i ? (ppu_py_r + 8'h01) : + (ppu_py_r); + +always_ff @(posedge vga_clk or negedge nreset) begin + if (!nreset) begin + vga_px_r <= '0; + vga_py_r <= '0; + end else begin + vga_px_r <= vga_px_next; + vga_py_r <= vga_py_next; + end +end +assign vga_px_next = vga_hsync ? '0 : (vga_px_r + 8'h01); +assign vga_py_next = vga_vsync ? '0 : + vga_hsync ? (vga_py_r + 8'h01) : + (vga_py_r); + +assign vga_hsync = (vga_px_r == VGA_W); +assign vga_vsync = (vga_py_r == VGA_H); + + +logic [1:0] buffer_r[BUFFER_SIZE-1:0]; +logic [15:0] buffer_raddr; +logic [15:0] buffer_waddr; + +logic buffer_we; + +always_ff @(posedge ppu_clk or negedge nreset) begin + if (!nreset) begin + buffer_r[buffer_waddr] <= '0; + end else if (buffer_we) begin + buffer_r[buffer_waddr] <= ppu_pixel_i; + end +end + +assign buffer_we = ~ppu_vsync_i & ~ppu_hsync_i; + +assign buffer_waddr = DISPLAY_W * ppu_py_r + ppu_px_r; + +logic [1:0] buffer_rval; + +always_ff @(posedge vga_clk or negedge nreset) begin + if (!nreset) begin + buffer_rval <= '0; + end else begin + buffer_rval <= buffer_r[buffer_raddr]; + end +end + +assign buffer_raddr = DISPLAY_W * vga_py_r + vga_py_r; + +logic vga_in_display; +assign vga_in_display = (vga_py_r < DISPLAY_H) & (vga_px_r < DISPLAY_W); + +assign vga_r_o = (display_enable_i & vga_in_display) ? VGA_PALETTE[buffer_rval][0] : '0; +assign vga_g_o = (display_enable_i & vga_in_display) ? VGA_PALETTE[buffer_rval][1] : '0; +assign vga_b_o = (display_enable_i & vga_in_display) ? VGA_PALETTE[buffer_rval][2] : '0; + +assign vga_vsync_o = vga_vsync; +assign vga_hsync_o = vga_hsync; + +endmodule : display diff --git a/rtl/gb.sv b/rtl/gb.sv index d1a3542..55d8595 100644 --- a/rtl/gb.sv +++ b/rtl/gb.sv @@ -10,7 +10,13 @@ module gb ( output wire cart_nwr_o, output wire cart_ncs_o, output wire [15:0] cart_addr_o, - inout wire [ 7:0] cart_data_io + inout wire [ 7:0] cart_data_io, + + // PPU output to display + output logic ppu_display_enable_o, + output logic [ 1:0] ppu_pixel_o, + output logic ppu_vsync_o, + output logic ppu_hsync_o ); @@ -24,13 +30,10 @@ logic [ 7:0] cpu_ppu_rdata; logic rom_enable_r; logic rom_sel; logic [ 7:0] rom_rdata; - logic hiram_sel; logic [ 7:0] hiram_rdata; - logic vram_sel; logic [ 7:0] vram_rdata; - logic cart_sel; logic [ 7:0] cart_rdata; @@ -48,10 +51,16 @@ ppu ppu_inst ( .clk (clk), .nreset (nreset), + .display_enable_o(ppu_display_enable_o), + .cpu_addr_i (cpu_addr), .cpu_rdata_o(cpu_ppu_rdata), .cpu_we_i (cpu_we), - .cpu_wdata_i(cpu_wdata) + .cpu_wdata_i(cpu_wdata), + + .pixel_o (ppu_pixel_o), + .vsync_o (ppu_vsync_o), + .hsync_o (ppu_hsync_o) ); assign rom_enable_r = '1; diff --git a/rtl/ppu/ppu.sv b/rtl/ppu/ppu.sv index 8fd0083..57dcd10 100644 --- a/rtl/ppu/ppu.sv +++ b/rtl/ppu/ppu.sv @@ -4,10 +4,16 @@ module ppu ( input logic clk, input logic nreset, + output logic display_enable_o, + input logic [15:0] cpu_addr_i, output logic [ 7:0] cpu_rdata_o, input logic cpu_we_i, - input logic [ 7:0] cpu_wdata_i + input logic [ 7:0] cpu_wdata_i, + + output logic [ 1:0] pixel_o, + output logic vsync_o, + output logic hsync_o ); `define IOREG_DECL(name) \ @@ -24,7 +30,7 @@ module ppu ( name``_r <= name``_next; \ end \ assign name``_sel = (cpu_addr_i == addr); \ - assign name``_we = name``_we & cpu_we_i; + assign name``_we = name``_sel & cpu_we_i; typedef struct packed { logic lcd_en; @@ -93,6 +99,14 @@ assign cpu_rdata_o = `endif {8{ sy_sel}} & sy_r; +assign pixel_o = ((lx_r < 8'd160) & (ly_r < 8'd144)) ? ly_r[1:0] : // should create a nice line-pattern for now + 2'h00; + +assign vsync_o = ly_r >= 8'd144; +assign hsync_o = lx_r >= 8'd160; + +assign display_enable_o = lcdc_r[7]; + `undef IOREG_DEF `undef IOREG_DECL diff --git a/sim/tb_top.sv b/sim/tb_top.sv index 1740881..3afbd8c 100644 --- a/sim/tb_top.sv +++ b/sim/tb_top.sv @@ -1,6 +1,10 @@ module tb_top; -logic clk; +logic gb_clk; +logic vga_clk; + +logic gb_nreset; +logic vga_nreset; logic nreset; wire gb_cart_clk; @@ -11,13 +15,39 @@ wire gb_cart_ncs; wire [15:0] gb_cart_addr; wire [ 7:0] gb_cart_data; -clkgen clkgen_inst ( - .clk (clk), - .nreset(nreset) +wire display_enable; +wire [ 1:0] ppu_pixel; +wire ppu_hsync; +wire ppu_vsync; + +logic [7:0] vga_r; +logic [7:0] vga_g; +logic [7:0] vga_b; +logic vga_hsync; +logic vga_vsync; + +// 4.19MHz +clkgen #( + .PERIOD_NS (238), + .RESET_DELAY_NS (400) +) gb_clkgen_inst ( + .clk (gb_clk), + .nreset(gb_nreset) ); +// 25MHz +clkgen #( + .PERIOD_NS (40), + .RESET_DELAY_NS (70) +) vga_clkgen_inst ( + .clk (vga_clk), + .nreset(vga_nreset) +); + +assign nreset = gb_nreset & vga_nreset; + gb gb_inst ( - .clk (clk), + .clk (gb_clk), .nreset(nreset), .cart_clk_o (gb_cart_clk), @@ -26,7 +56,12 @@ gb gb_inst ( .cart_nwr_o (gb_cart_nwr), .cart_ncs_o (gb_cart_ncs), .cart_addr_o (gb_cart_addr), - .cart_data_io (gb_cart_data) + .cart_data_io (gb_cart_data), + + .ppu_display_enable_o(display_enable), + .ppu_pixel_o (ppu_pixel), + .ppu_vsync_o (ppu_vsync), + .ppu_hsync_o (ppu_hsync) ); cart cart_inst ( @@ -39,10 +74,45 @@ cart cart_inst ( .data_io (gb_cart_data) ); +display display_inst ( + .nreset (nreset), + .VGA_PALETTE( + // RR GG BB + '{24'h00_00_00, // colour 00 + 24'h60_60_60, // colour 01 + 24'hA0_A0_A0, // colour 10 + 24'hFF_FF_FF} // colour 11 + ), + + .display_enable_i(display_enable), + + .ppu_clk (gb_clk), + .ppu_pixel_i(ppu_pixel), + .ppu_vsync_i(ppu_vsync), + .ppu_hsync_i(ppu_hsync), + + .vga_clk (vga_clk), + .vga_r_o (vga_r), + .vga_g_o (vga_g), + .vga_b_o (vga_b), + .vga_hsync_o(vga_hsync), + .vga_vsync_o(vga_vsync) +); + +vgasim vgasim_inst ( + .clk (vga_clk), + .nreset(nreset), + + .r_i (vga_r), + .g_i (vga_g), + .b_i (vga_b), + .hsync_i (vga_hsync), + .vsync_i (vga_vsync) +); `ifdef SVA_ENABLE `include "sva_common.svh" -`SVA_DEF_CLK(clk); +`SVA_DEF_CLK(gb_clk); `SVA_DEF_NRESET(nreset); logic instr_valid; @@ -70,14 +140,14 @@ assign vram_sel = gb_inst.vram_sel; assign hiram_sel = gb_inst.hiram_sel; assign ppu_sel = gb_inst.ppu_inst.sva_ppu_sel; -always_ff @(posedge clk or negedge nreset) begin +always_ff @(posedge gb_clk or negedge nreset) begin if (!nreset) current_pc <= '0; else if (instr_valid) current_pc <= gb_inst.cpu_inst.pc_r; end -always_ff @(posedge clk or negedge nreset) begin +always_ff @(posedge gb_clk or negedge nreset) begin if (!nreset) begin last_write_address <= '0; last_write_value <= '0; @@ -108,7 +178,7 @@ localparam MAX_COUNT = 10000; logic [15:0] pc_history [$]; int unsigned pc_history_count [$]; -always @(posedge clk iff instr_valid) begin +always @(posedge gb_clk iff instr_valid) begin automatic int unsigned current_pc_count = 0; foreach (pc_history[i]) begin diff --git a/sim/vgasim.sv b/sim/vgasim.sv new file mode 100644 index 0000000..be29bcd --- /dev/null +++ b/sim/vgasim.sv @@ -0,0 +1,44 @@ +module vgasim #( + parameter SCREEN_H = 640, + parameter SCREEN_W = 480, + parameter DEPTH = 8 +) ( + input logic clk, + input logic nreset, + + input logic [DEPTH-1:0] r_i, + input logic [DEPTH-1:0] g_i, + input logic [DEPTH-1:0] b_i, + input logic hsync_i, + input logic vsync_i +); + +import "DPI-C" function int vgasim_init(input int screen_h, input int screen_w, input int screen_bpp); +import "DPI-C" function int vgasim_reset(); +import "DPI-C" function int vgasim_tick(input int r, input int g, input int b, input int hsync, input int vsync); + +initial begin + automatic int ret = vgasim_init(SCREEN_W, SCREEN_H, DEPTH); + assert (ret == 0) else begin + $display($sformatf("[%0t] vgasim init failed: %d", $time, ret)); + $fatal(); + end +end + +always @(negedge nreset) begin + automatic int ret = vgasim_reset(); + assert (ret == 0) else begin + $display($sformatf("[%0t] vgasim reset failed: %d", $time, ret)); + $fatal(); + end +end + +always @(posedge clk iff nreset) begin + automatic int ret = vgasim_tick(r_i, g_i, b_i, hsync_i, vsync_i); + assert (ret == 0) else begin + $display($sformatf("[%0t] vgasim tick failed: %d", $time, ret)); + $fatal(); + end +end + +endmodule : vgasim diff --git a/src/vgasim b/src/vgasim new file mode 100755 index 0000000000000000000000000000000000000000..716ea75a273700c35a7ec36e4af7e137c655b5f2 GIT binary patch literal 16944 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 PM$fc_u*A?bpm7-hS(%o4 literal 0 HcmV?d00001 diff --git a/src/vgasim.c b/src/vgasim.c new file mode 100644 index 0000000..d2bfca8 --- /dev/null +++ b/src/vgasim.c @@ -0,0 +1,210 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include + + +#define RBUF_SIZE 256 + +#define DEF_SCREEN_W 640 +#define DEF_SCREEN_H 480 + +unsigned int pixel_x; +unsigned int pixel_y; +unsigned int screen_w; +unsigned int screen_h; +SDL_Window* sdl_window; +SDL_Renderer* sdl_renderer; +SDL_Texture* sdl_texture; + +Uint32* screen; + +#define VGASIM_CMD_INIT 0x01 +#define VGASIM_CMD_RESET 0x02 +#define VGASIM_CMD_TICK 0x03 + +void vgasim_init(int new_screen_h, int new_screen_w, int bpp) { + printf("vgasim_init: %d x %d x %d\n", new_screen_w, new_screen_h, bpp); + pixel_x = 0; + pixel_y = 0; + + if (screen_h != new_screen_h || screen_w != new_screen_w) { + printf("Need to resize\n"); + } + + screen_h = new_screen_h; + screen_w = new_screen_w; +} + +void vgasim_reset() { + printf("vgasim_reset\n"); + pixel_x = 0; + pixel_y = 0; + SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255); + SDL_RenderClear(sdl_renderer); + SDL_RenderPresent(sdl_renderer); +} + +void vgasim_tick(int r, int g, int b, int hsync, int vsync) { + //printf("vgasim_tick: %X, %X, %X (%s%s)\n", r, g, b, hsync ? "h" : "", vsync ? "v" : ""); + + screen[screen_w * pixel_y + pixel_x] = ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) << 0); + pixel_x++; + if (hsync || pixel_x > screen_w) { + pixel_x = 0; + pixel_y++; + //printf("vgasim_tick: vsync\n"); + SDL_UpdateTexture(sdl_texture, NULL, screen, screen_w * sizeof (Uint32)); + SDL_RenderClear(sdl_renderer); + SDL_RenderCopy(sdl_renderer, sdl_texture, NULL, NULL); + SDL_RenderPresent(sdl_renderer); + } + if (vsync || pixel_y > screen_h) { + pixel_y = 0; + // printf("vgasim_tick: hsync\n"); + } +} + +ssize_t cmd_parse(char *buf, ssize_t bufsize) { + uint8_t *p8; + uint16_t *p16; + uint16_t cmd; + uint8_t r, g, b, hvsync; + uint16_t h, w, bpp; + + if (bufsize < sizeof(uint16_t)) + return 0; + + p16 = (uint16_t*)buf; + cmd = *p16++; + p8 = (uint8_t*)p16; + switch (cmd) { + case VGASIM_CMD_INIT: + if (bufsize < 4 * sizeof(uint16_t)) + return 0; + h = *p16++; + w = *p16++; + bpp = *p16++; + vgasim_init(h, w, bpp); + return 4 * sizeof(uint16_t); + + case VGASIM_CMD_RESET: + vgasim_reset(); + return sizeof(uint16_t); + + case VGASIM_CMD_TICK: + if (bufsize < sizeof(uint16_t) + 4 * sizeof(uint8_t)) + return 0; + r = *p8++; + g = *p8++; + b = *p8++; + hvsync = *p8++; + vgasim_tick(r, g, b, (hvsync & 0x01), ((hvsync & 0x02) >> 1)); + + return sizeof(uint16_t) + 4 * sizeof(uint8_t); + + default: + printf("Unsupported command packet received: %X\n", cmd); + return 0; + } + return 0; +} + +int main (int argc, char **argv) { + int fifo_fd; + char rbuf[RBUF_SIZE]; + ssize_t bytes_read, bytes_read_total; + ssize_t bytes_parsed, bytes_parsed_total; + int ret = mkfifo("/tmp/vgasim", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + + if (ret == -1 && errno != EEXIST) { + perror(argv[0]); + return 1; + } + + ret = SDL_Init(SDL_INIT_VIDEO); + if (ret < 0) { + printf("SDL_Init failed: %d\n", ret); + return 2; + } + + sdl_window = SDL_CreateWindow("vgasim", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DEF_SCREEN_W, DEF_SCREEN_H, 0); + if (sdl_window == NULL) { + printf("SDL_CreateWindow failed\n"); + return 3; + } + sdl_renderer = SDL_CreateRenderer(sdl_window, -1, 0); + if (sdl_renderer == NULL) { + printf("SDL_CreateRenderer failed\n"); + return 4; + } + sdl_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, DEF_SCREEN_W, DEF_SCREEN_H); + if (sdl_texture == NULL) { + printf("SDL_CreateTexture failed\n"); + return 5; + } + SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255); + SDL_RenderClear(sdl_renderer); + SDL_RenderPresent(sdl_renderer); + + screen = malloc(DEF_SCREEN_H * DEF_SCREEN_W * sizeof(Uint32)); + if (screen == NULL) { + return 6; + } + + printf("Listening for incoming connections...\n"); + ret = open("/tmp/vgasim", O_RDONLY); + if (ret == -1) { + perror(argv[0]); + return 2; + } + + fifo_fd = ret; + + bytes_read_total = 0; + do { + + // Read as much bytes as we can into our buffer + bytes_read = read(fifo_fd, rbuf + bytes_read_total, RBUF_SIZE - bytes_read_total); + bytes_read_total += bytes_read; + + bytes_parsed_total = 0; + if (bytes_read > 0) { + do { + + // Parse as many bytes as we can + bytes_parsed = cmd_parse(rbuf + bytes_parsed_total, bytes_read_total - bytes_parsed_total); + bytes_parsed_total += bytes_parsed; + } while (bytes_parsed > 0); + } + + // If there's any data left unparsed, it's the start of a new incomplete packet. + // Copy it to the front of the buffer and restart from there. + // Otherwise we cleared our entire buffer and can start again from the beginning. + if (bytes_read_total > bytes_parsed_total) { + memcpy(rbuf, rbuf + bytes_parsed_total, (bytes_read_total - bytes_parsed_total)); + bytes_read_total = bytes_read_total - bytes_parsed_total; + } else { + bytes_read_total = 0; + } + } while (bytes_read > 0); + + if (bytes_read < 0) { + perror(argv[0]); + return 3; + } + + free(screen); + SDL_DestroyTexture(sdl_texture); + SDL_DestroyRenderer(sdl_renderer); + SDL_DestroyWindow(sdl_window); + SDL_Quit(); + + return 0; +} diff --git a/synthflow b/synthflow index 1486728..cc61998 160000 --- a/synthflow +++ b/synthflow @@ -1 +1 @@ -Subproject commit 1486728ced51acd63f20caf41cc47b44f2efa436 +Subproject commit cc619981f0804ce1da54a12d67ff4009d2df56b9