summaryrefslogtreecommitdiff
path: root/tb/vga.impl.hpp
diff options
context:
space:
mode:
authorAlejandro Soto <alejandro@34project.org>2022-11-14 21:10:40 -0600
committerAlejandro Soto <alejandro@34project.org>2022-11-14 21:10:40 -0600
commit6fb3849e73b797d4610a2b782127f927dec0c9c9 (patch)
tree9d17de8907d860b795761e0644f17d0fd33106de /tb/vga.impl.hpp
parentcad870295dfb741d5c24c25016c5bba878bc37e5 (diff)
Implement VGA simulation
Diffstat (limited to 'tb/vga.impl.hpp')
-rw-r--r--tb/vga.impl.hpp356
1 files changed, 356 insertions, 0 deletions
diff --git a/tb/vga.impl.hpp b/tb/vga.impl.hpp
new file mode 100644
index 0000000..9c3294c
--- /dev/null
+++ b/tb/vga.impl.hpp
@@ -0,0 +1,356 @@
+#ifndef TALLER_VGA_IMPL_HPP
+#define TALLER_VGA_IMPL_HPP
+
+#include <array>
+#include <cassert>
+#include <cmath>
+#include <cstdio>
+#include <cstdio>
+
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_surface.h>
+#include <SDL2/SDL_video.h>
+
+namespace
+{
+ // https://web.mit.edu/6.111/www/s2004/NEWKIT/vga.shtml
+ constexpr std::array<taller::vga::video_mode, 13> MODES
+ {{
+ {25'175'000, { 640, 16, 96, 48}, {480, 11, 2, 31}},
+ {31'500'000, { 640, 24, 40, 128}, {480, 9, 3, 28}},
+ {31'500'000, { 640, 16, 96, 48}, {480, 11, 2, 32}},
+ {36'000'000, { 640, 32, 48, 112}, {480, 1, 3, 25}},
+ {38'100'000, { 800, 32, 128, 128}, {600, 1, 4, 14}},
+ {40'000'000, { 800, 40, 128, 88}, {600, 1, 4, 23}},
+ {50'000'000, { 800, 56, 120, 64}, {600, 37, 6, 23}},
+ {49'500'000, { 800, 16, 80, 160}, {600, 1, 2, 21}},
+ {56'250'000, { 800, 32, 64, 152}, {600, 1, 3, 27}},
+ {65'000'000, {1024, 24, 136, 160}, {768, 3, 6, 29}},
+ {75'000'000, {1024, 24, 136, 144}, {768, 3, 6, 29}},
+ {78'750'000, {1024, 16, 96, 176}, {768, 1, 3, 28}},
+ {94'500'000, {1024, 48, 96, 208}, {768, 1, 3, 36}}
+ }};
+}
+
+namespace taller::vga
+{
+ template<class Crtc>
+ display<Crtc>::display(Crtc &crtc, std::uint32_t clock_hz) noexcept
+ : crtc(crtc),
+ clock_hz(clock_hz)
+ {}
+
+ template<class Crtc>
+ display<Crtc>::~display() noexcept
+ {
+ mode = nullptr;
+ update_window();
+ }
+
+ template<class Crtc>
+ void display<Crtc>::tick(bool clk) noexcept
+ {
+ if(!clk)
+ {
+ return;
+ }
+
+ put_pixel();
+ move_pos();
+ scan_syncs();
+
+ last_hsync = crtc.vga_hsync;
+ last_vsync = crtc.vga_vsync;
+
+ ::SDL_Event event;
+ while(::SDL_PollEvent(&event))
+ {
+ bool value;
+ switch(event.type)
+ {
+ case SDL_KEYDOWN:
+ value = false;
+ break;
+
+ case SDL_KEYUP:
+ value = true;
+ break;
+
+ default:
+ continue;
+ }
+
+ std::size_t index;
+ switch(event.key.keysym.sym)
+ {
+ case SDLK_1:
+ index = 0;
+ break;
+
+ case SDLK_2:
+ index = 1;
+ break;
+
+ case SDLK_3:
+ index = 2;
+ break;
+
+ case SDLK_4:
+ index = 3;
+ break;
+
+ default:
+ continue;
+ }
+
+ keys[index] = value;
+ }
+ }
+
+ template<class Crtc>
+ void display<Crtc>::move_pos() noexcept
+ {
+ if(!mode)
+ {
+ if(found_line && ++col >= hsync_lo + hsync_hi)
+ {
+ col = 0;
+ }
+
+ return;
+ }
+
+ auto dots = mode->h.active + mode->h.front_porch + mode->h.sync + mode->h.back_porch;
+ auto lines = mode->v.active + mode->v.front_porch + mode->v.sync + mode->v.back_porch;
+
+ if((col + 1 != mode->h.sync || (!last_hsync && crtc.vga_hsync)) && ++col >= dots)
+ {
+ col = 0;
+ if(row + 1 != mode->v.sync && ++row >= lines)
+ {
+ row = 0;
+ }
+ }
+
+ if(!last_vsync && crtc.vga_vsync)
+ {
+ ++row;
+ if(window)
+ {
+ ::SDL_UpdateWindowSurface(window);
+ }
+ }
+ }
+
+ template<class Crtc>
+ void display<Crtc>::scan_syncs() noexcept
+ {
+ if(hsync_lo == 0)
+ {
+ hsync_lo += last_hsync && !crtc.vga_hsync;
+ } else if(hsync_hi == 0)
+ {
+ if(crtc.vga_hsync)
+ {
+ scan_vsync();
+ } else
+ {
+ ++hsync_lo;
+ }
+ } else
+ {
+ scan_vsync();
+ }
+ }
+
+ template<class Crtc>
+ void display<Crtc>::scan_vsync() noexcept
+ {
+ if(found_line && crtc.vga_hsync != (col >= hsync_lo))
+ {
+ signal_lost();
+ } else if(!found_line)
+ {
+ if(crtc.vga_hsync)
+ {
+ ++hsync_hi;
+ } else
+ {
+ found_line = true;
+ }
+ } else if(vsync_lo == 0)
+ {
+ vsync_lo += last_vsync && !crtc.vga_vsync;
+ } else if(last_hsync && !crtc.vga_hsync)
+ {
+ if(crtc.vga_vsync)
+ {
+ ++vsync_hi;
+ } else if(vsync_hi == 0)
+ {
+ ++vsync_lo;
+ } else
+ {
+ guess_mode();
+
+ hsync_lo = hsync_hi = vsync_lo = vsync_hi = 0;
+ found_line = false;
+
+ scan_syncs();
+ }
+ }
+ }
+
+ template<class Crtc>
+ void display<Crtc>::put_pixel() noexcept
+ {
+ if(!mode)
+ {
+ return;
+ }
+
+ auto start_h = mode->h.sync + mode->h.back_porch;
+ auto start_v = mode->v.sync + mode->v.back_porch;
+
+ if(col < start_h
+ || col >= start_h + mode->h.active
+ || row < start_v
+ || row >= start_v + mode->v.active)
+ {
+ return;
+ }
+
+ auto *surface = window ? ::SDL_GetWindowSurface(window) : nullptr;
+ if(!window || !surface)
+ {
+ return;
+ }
+
+ assert(surface->format->format == SDL_PIXELFORMAT_RGB888);
+
+ auto *pixels = static_cast<std::uint32_t*>(surface->pixels);
+ auto *pixel = pixels + (row - start_v) * mode->h.active + (col - start_h);
+ *pixel = (crtc.vga_r & 0xff) << 16 | (crtc.vga_g & 0xff) << 8 | (crtc.vga_b & 0xff);
+ }
+
+ template<class Crtc>
+ void display<Crtc>::guess_mode() noexcept
+ {
+ auto timings_match = [this](const video_mode &candidate)
+ {
+ int dots = candidate.h.active + candidate.h.front_porch + candidate.h.back_porch;
+ int lines = candidate.v.active + candidate.v.front_porch + candidate.v.back_porch;
+
+ return std::abs(dots - static_cast<int>(hsync_hi)) <= 2
+ && std::abs(lines - static_cast<int>(vsync_hi)) <= 2
+ && std::abs(static_cast<int>(hsync_lo) - static_cast<int>(candidate.h.sync)) <= 2
+ && std::abs(static_cast<int>(vsync_lo) - static_cast<int>(candidate.v.sync)) <= 2;
+ };
+
+ if(mode != nullptr && timings_match(*mode))
+ {
+ return;
+ }
+
+ std::fprintf
+ (
+ stderr,
+ "[vga] hsync_duty: %u/%u, vsync_duty: %u/%u, pixel_clk: %.2fMHz\n",
+ hsync_lo,
+ hsync_lo + hsync_hi,
+ vsync_lo,
+ vsync_lo + vsync_hi,
+ clock_hz / 1e6
+ );
+
+ mode = nullptr;
+ for(const auto &candidate : MODES)
+ {
+ if(!timings_match(candidate))
+ {
+ continue;
+ }
+
+ float actual_clk_f = clock_hz;
+ float expected_clk_f = candidate.pixel_clk;
+
+ if(std::fabs((expected_clk_f - actual_clk_f) / expected_clk_f) < 0.02)
+ {
+ mode = &candidate;
+ break;
+ }
+ }
+
+ if(mode)
+ {
+ auto width = mode->h.active + mode->h.front_porch + mode->h.sync + mode->h.back_porch;
+ auto height = mode->v.active + mode->v.front_porch + mode->v.sync + mode->v.back_porch;
+ auto rate = static_cast<float>(clock_hz) / (width * height);
+
+ std::fprintf
+ (
+ stderr,
+ "[vga] %ux%u @ %.2fHz\n",
+ mode->h.active,
+ mode->v.active,
+ rate
+ );
+ } else
+ {
+ std::fputs("[vga] failed to guess mode from timings\n", stderr);
+ }
+
+ update_window();
+ if(mode && window)
+ {
+ ::SDL_SetWindowSize(window, mode->h.active, mode->v.active);
+ }
+ }
+
+ template<class Crtc>
+ void display<Crtc>::signal_lost() noexcept
+ {
+ if(mode)
+ {
+ std::fputs("[vga] no signal\n", stderr);
+ mode = nullptr;
+ }
+
+ row = col = 0;
+ hsync_lo = hsync_hi = vsync_lo = vsync_hi = 0;
+ found_line = false;
+
+ update_window();
+ }
+
+ template<class Crtc>
+ void display<Crtc>::update_window() noexcept
+ {
+ if(!mode && window)
+ {
+ ::SDL_DestroyWindow(window);
+ window = nullptr;
+ } else if(mode && !window)
+ {
+ if(!SDL_WasInit(SDL_INIT_VIDEO))
+ {
+ ::SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
+ assert(::SDL_Init(SDL_INIT_VIDEO) >= 0);
+ }
+
+ window = ::SDL_CreateWindow
+ (
+ "VGA Display",
+ SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED,
+ mode->h.active,
+ mode->v.active,
+ 0
+ );
+
+ assert(window);
+ }
+ }
+}
+
+#endif