summaryrefslogtreecommitdiff
path: root/tb
diff options
context:
space:
mode:
Diffstat (limited to 'tb')
-rw-r--r--tb/avalon.hpp56
-rw-r--r--tb/avalon.impl.hpp22
-rw-r--r--tb/mem.cpp55
-rw-r--r--tb/mem.hpp42
-rw-r--r--tb/mem.impl.hpp81
-rw-r--r--tb/null.hpp31
-rw-r--r--tb/platform.sv8
-rwxr-xr-xtb/sim/sim.py2
-rw-r--r--tb/top/conspiracion.cpp35
-rw-r--r--tb/vga.hpp72
-rw-r--r--tb/vga.impl.hpp356
-rw-r--r--tb/vga_domain.sv35
-rw-r--r--tb/window.hpp37
13 files changed, 727 insertions, 105 deletions
diff --git a/tb/avalon.hpp b/tb/avalon.hpp
index a00ced0..8134677 100644
--- a/tb/avalon.hpp
+++ b/tb/avalon.hpp
@@ -1,7 +1,9 @@
-#ifndef AVALON_HPP
-#define AVALON_HPP
+#ifndef TALLER_AVALON_HPP
+#define TALLER_AVALON_HPP
+#include <cassert>
#include <cstdint>
+#include <cstdio>
#include <vector>
namespace taller::avalon
@@ -9,11 +11,57 @@ namespace taller::avalon
class slave
{
public:
- virtual std::uint32_t base_address() noexcept = 0;
- virtual std::uint32_t address_mask() noexcept = 0;
+ inline slave(std::uint32_t base, std::uint32_t size, std::size_t word_size)
+ : base(base),
+ mask(~(size - 1)),
+ word(log2i(word_size))
+ {
+ assert(!((word_size - 1) & word_size));
+ assert(!(base & word_mask()) && !(size & word_mask()) && !((size - 1) & size));
+ }
+
+ inline std::uint32_t base_address() noexcept
+ {
+ return base;
+ }
+
+ inline std::uint32_t address_mask() noexcept
+ {
+ return mask;
+ }
+
+ inline std::uint32_t word_mask() noexcept
+ {
+ return (1 << word) - 1;
+ }
+
+ inline std::size_t word_size() noexcept
+ {
+ return 1 << word;
+ }
+
+ inline unsigned word_bits() noexcept
+ {
+ return word;
+ }
+
+ inline std::uint32_t address_span() noexcept
+ {
+ return ~mask + 1;
+ }
virtual bool read(std::uint32_t addr, std::uint32_t &data) = 0;
virtual bool write(std::uint32_t addr, std::uint32_t data, unsigned byte_enable) = 0;
+
+ private:
+ std::uint32_t base;
+ std::uint32_t mask;
+ unsigned word;
+
+ static inline int log2i(int i)
+ {
+ return sizeof(int) * 8 - __builtin_clz(i) - 1;
+ }
};
template<class Platform>
diff --git a/tb/avalon.impl.hpp b/tb/avalon.impl.hpp
index f701e73..03685d0 100644
--- a/tb/avalon.impl.hpp
+++ b/tb/avalon.impl.hpp
@@ -1,5 +1,5 @@
-#ifndef AVALON_IMPL_HPP
-#define AVALON_IMPL_HPP
+#ifndef TALLER_AVALON_IMPL_HPP
+#define TALLER_AVALON_IMPL_HPP
#include <cassert>
#include <cstdio>
@@ -61,11 +61,9 @@ namespace taller::avalon
avl_byteenable = plat.avl_byteenable;
assert(!avl_read || !avl_write);
-
- if(avl_address & 0b11)
+ if(!avl_read && !avl_write)
{
- fprintf(stderr, "[avl] unaligned address: 0x%08x\n", avl_address);
- assert(false);
+ return;
}
for(auto &binding : devices)
@@ -83,13 +81,21 @@ namespace taller::avalon
fprintf(stderr, "[avl] attempt to %s memory hole at 0x%08x\n", op, avl_address);
assert(false);
}
+
+ if(avl_address & active->word_mask())
+ {
+ fprintf(stderr, "[avl] unaligned address: 0x%08x\n", avl_address);
+ assert(false);
+ }
}
- auto pos = (avl_address & ~active->address_mask()) >> 2;
+ auto pos = (avl_address & ~active->address_mask()) >> active->word_bits();
if(avl_read)
{
- plat.avl_waitrequest = !active->read(pos, plat.avl_readdata);
+ std::uint32_t readdata;
+ plat.avl_waitrequest = !active->read(pos, readdata);
+ plat.avl_readdata = readdata;
} else if(avl_write)
{
plat.avl_waitrequest = !active->write(pos, avl_writedata, avl_byteenable);
diff --git a/tb/mem.cpp b/tb/mem.cpp
deleted file mode 100644
index 6eeb7df..0000000
--- a/tb/mem.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-#include <cassert>
-#include <cstdint>
-#include <memory>
-
-#include "mem.hpp"
-
-namespace taller::avalon
-{
- mem::mem(std::uint32_t base, std::uint32_t size)
- : base(base), mask(~(size - 1)),
- block(std::make_unique<std::uint32_t[]>(size >> 2))
- {
- assert(!(size & 0b11) && !((size - 1) & size));
- }
-
- bool mem::read(std::uint32_t addr, std::uint32_t &data)
- {
- data = block[addr];
- return ready();
- }
-
- bool mem::write(std::uint32_t addr, std::uint32_t data, unsigned byte_enable)
- {
- std::uint32_t bytes = 0;
-
- if(byte_enable & 0b1000)
- {
- bytes |= 0xff << 24;
- }
-
- if(byte_enable & 0b0100)
- {
- bytes |= 0xff << 16;
- }
-
- if(byte_enable & 0b0010)
- {
- bytes |= 0xff << 8;
- }
-
- if(byte_enable & 0b0001)
- {
- bytes |= 0xff;
- }
-
- block[addr] = (data & bytes) | (block[addr] & ~bytes);
- return ready();
- }
-
- bool mem::ready() noexcept
- {
- count = count > 0 ? count - 1 : 2;
- return count == 0;
- }
-}
diff --git a/tb/mem.hpp b/tb/mem.hpp
index a76780d..0943530 100644
--- a/tb/mem.hpp
+++ b/tb/mem.hpp
@@ -1,5 +1,5 @@
-#ifndef MEM_HPP
-#define MEM_HPP
+#ifndef TALLER_MEM_HPP
+#define TALLER_MEM_HPP
#include <cstdint>
#include <memory>
@@ -8,54 +8,30 @@
namespace taller::avalon
{
+ template<typename Cell>
class mem : public slave
{
public:
mem(std::uint32_t base, std::uint32_t size);
- virtual inline std::uint32_t base_address() noexcept final override
- {
- return base;
- }
-
- virtual inline std::uint32_t address_mask() noexcept final override
- {
- return mask;
- }
-
virtual bool read(std::uint32_t addr, std::uint32_t &data) final override;
+
virtual bool write
(
std::uint32_t addr, std::uint32_t data, unsigned byte_enable = 0b1111
) final override;
template<typename F>
- void load(F loader, std::size_t addr = 0);
+ void load(F loader, std::size_t offset = 0);
private:
- std::unique_ptr<std::uint32_t[]> block;
- std::uint32_t base;
- std::uint32_t mask;
- unsigned count = 0;
+ std::unique_ptr<Cell[]> block;
+ unsigned count = 0;
bool ready() noexcept;
};
-
- template<typename F>
- void mem::load(F loader, std::size_t addr)
- {
- std::size_t size = mask + 1;
- while(addr < size)
- {
- std::size_t read = loader(&block[base + addr], size - addr);
- if(read == 0)
- {
- break;
- }
-
- addr += read;
- }
- }
}
+#include "mem.impl.hpp"
+
#endif
diff --git a/tb/mem.impl.hpp b/tb/mem.impl.hpp
new file mode 100644
index 0000000..f7bb424
--- /dev/null
+++ b/tb/mem.impl.hpp
@@ -0,0 +1,81 @@
+#ifndef TALLER_MEM_IMPL_HPP
+#define TALLER_MEM_IMPL_HPP
+
+#include <cassert>
+#include <cstdint>
+#include <memory>
+
+namespace taller::avalon
+{
+ template<typename Cell>
+ mem<Cell>::mem(std::uint32_t base, std::uint32_t size)
+ : slave(base, size, sizeof(Cell)),
+ block(std::make_unique<Cell[]>(size >> word_bits()))
+ {}
+
+ template<typename Cell>
+ template<typename F>
+ void mem<Cell>::load(F loader, std::size_t offset)
+ {
+ auto base = base_address();
+ auto bits = word_bits();
+ std::size_t size = address_span();
+ std::size_t addr = base_address() + offset;
+
+ while(addr >= base && addr < base + size)
+ {
+ std::size_t read = loader(&block[(addr - base) >> bits], (base + size - addr) >> bits);
+ if(read == 0)
+ {
+ break;
+ }
+
+ addr += read << bits;
+ }
+ }
+
+ template<typename Cell>
+ bool mem<Cell>::read(std::uint32_t addr, std::uint32_t &data)
+ {
+ data = block[addr];
+ return ready();
+ }
+
+ template<typename Cell>
+ bool mem<Cell>::write(std::uint32_t addr, std::uint32_t data, unsigned byte_enable)
+ {
+ std::uint32_t bytes = 0;
+
+ if(byte_enable & 0b1000)
+ {
+ bytes |= 0xff << 24;
+ }
+
+ if(byte_enable & 0b0100)
+ {
+ bytes |= 0xff << 16;
+ }
+
+ if(byte_enable & 0b0010)
+ {
+ bytes |= 0xff << 8;
+ }
+
+ if(byte_enable & 0b0001)
+ {
+ bytes |= 0xff;
+ }
+
+ block[addr] = (data & bytes) | (block[addr] & ~bytes);
+ return ready();
+ }
+
+ template<typename Cell>
+ bool mem<Cell>::ready() noexcept
+ {
+ count = count > 0 ? count - 1 : 2;
+ return count == 0;
+ }
+}
+
+#endif
diff --git a/tb/null.hpp b/tb/null.hpp
new file mode 100644
index 0000000..37de464
--- /dev/null
+++ b/tb/null.hpp
@@ -0,0 +1,31 @@
+#ifndef TALLER_NULL_HPP
+#define TALLER_NULL_HPP
+
+#include <cstdint>
+#include <memory>
+
+#include "avalon.hpp"
+
+namespace taller::avalon
+{
+ class null : public slave
+ {
+ public:
+ using slave::slave;
+
+ inline virtual bool read(std::uint32_t addr, std::uint32_t &data) final override
+ {
+ return true;
+ }
+
+ inline virtual bool write
+ (
+ std::uint32_t addr, std::uint32_t data, unsigned byte_enable = 0b1111
+ ) final override
+ {
+ return true;
+ }
+ };
+}
+
+#endif
diff --git a/tb/platform.sv b/tb/platform.sv
index cbf31b0..5def85f 100644
--- a/tb/platform.sv
+++ b/tb/platform.sv
@@ -1,6 +1,3 @@
-
-
-
module platform
(
input wire clk_clk, // clk.clk
@@ -77,4 +74,9 @@ module platform
.*
);
+ vga_domain vga
+ (
+ .*
+ );
+
endmodule
diff --git a/tb/sim/sim.py b/tb/sim/sim.py
index bef1b73..0c8b023 100755
--- a/tb/sim/sim.py
+++ b/tb/sim/sim.py
@@ -249,7 +249,7 @@ mem_dumps = module_get('mem_dumps', [])
if init := module_get('init'):
init()
-exec_args = [verilated, '--cycles', str(cycles), '--dump-regs']
+exec_args = [verilated, '--headless', '--cycles', str(cycles), '--dump-regs']
for rng in mem_dumps:
length = rng.stop - rng.start
diff --git a/tb/top/conspiracion.cpp b/tb/top/conspiracion.cpp
index ef9235d..2840b15 100644
--- a/tb/top/conspiracion.cpp
+++ b/tb/top/conspiracion.cpp
@@ -12,6 +12,7 @@
#include "Vconspiracion_arm810.h"
#include "Vconspiracion_conspiracion.h"
#include "Vconspiracion_platform.h"
+#include "Vconspiracion_vga_domain.h"
#include "Vconspiracion_core_control.h"
#include "Vconspiracion_core_psr.h"
#include "Vconspiracion_core_regs.h"
@@ -21,6 +22,9 @@
#include "../avalon.hpp"
#include "../mem.hpp"
+#include "../null.hpp"
+#include "../window.hpp"
+#include "../vga.hpp"
namespace
{
@@ -113,6 +117,7 @@ namespace
int main(int argc, char **argv)
{
using namespace taller::avalon;
+ using namespace taller::vga;
Verilated::commandArgs(argc, argv);
@@ -138,6 +143,11 @@ int main(int argc, char **argv)
parser, "dump-regs", "Dump all registers", {"dump-regs"}
);
+ args::Flag headless
+ (
+ parser, "headless", "Disable video output", {"headless"}
+ );
+
args::ValueFlag<unsigned> cycles
(
parser, "cycles", "Number of core cycles to run", {"cycles"}, 256
@@ -184,10 +194,27 @@ int main(int argc, char **argv)
}
interconnect<Vconspiracion_platform> avl(*top.conspiracion->plat);
- mem hps_ddr3(0x0000'0000, 512 << 20);
+ interconnect<Vconspiracion_vga_domain> avl_vga(*top.conspiracion->plat->vga);
+
+ mem<std::uint32_t> hps_ddr3(0x0000'0000, 512 << 20);
+ mem<std::uint16_t> vram(0x3800'0000, 64 << 20);
+ null vram_null(0x3800'0000, 64 << 20, 2);
+ window vram_window(vram, 0x0000'0000);
+ display<Vconspiracion_vga_domain> vga(*top.conspiracion->plat->vga, 25'175'000);
+
+ bool enable_video = !headless;
avl.attach(hps_ddr3);
+ if(enable_video)
+ {
+ avl.attach(vram);
+ avl_vga.attach(vram_window);
+ } else
+ {
+ avl.attach(vram_null);
+ }
+
FILE *img_file = std::fopen(image->c_str(), "rb");
if(!img_file)
{
@@ -216,7 +243,13 @@ int main(int argc, char **argv)
{
top.clk_clk = !top.clk_clk;
top.eval();
+
avl.tick(top.clk_clk);
+ if(enable_video)
+ {
+ avl_vga.tick(top.clk_clk);
+ vga.tick(top.clk_clk);
+ }
if(enable_trace)
{
diff --git a/tb/vga.hpp b/tb/vga.hpp
new file mode 100644
index 0000000..92446ca
--- /dev/null
+++ b/tb/vga.hpp
@@ -0,0 +1,72 @@
+#ifndef LIBTALLER_VGA_HPP
+#define LIBTALLER_VGA_HPP
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+
+#include <SDL2/SDL_surface.h>
+#include <SDL2/SDL_video.h>
+
+namespace taller::vga
+{
+ struct timings
+ {
+ unsigned active;
+ unsigned front_porch;
+ unsigned sync;
+ unsigned back_porch;
+ };
+
+ struct video_mode
+ {
+ std::uint32_t pixel_clk;
+ timings h;
+ timings v;
+ };
+
+ template<class Crtc>
+ class display
+ {
+ public:
+ display(Crtc &crtc, std::uint32_t clock_hz) noexcept;
+
+ ~display() noexcept;
+
+ void tick(bool clk) noexcept;
+
+ inline bool key(std::size_t index)
+ {
+ return keys.at(index);
+ }
+
+ private:
+ Crtc& crtc;
+ SDL_Window *window = nullptr;
+ const video_mode *mode = nullptr;
+ unsigned row = 0;
+ unsigned col = 0;
+ unsigned hsync_lo = 0;
+ unsigned hsync_hi = 0;
+ unsigned vsync_hi = 0;
+ unsigned vsync_lo = 0;
+ bool last_hsync = false;
+ bool last_vsync = false;
+ bool found_line = false;
+ const std::uint32_t clock_hz;
+
+ std::array<bool, 4> keys = {};
+
+ void move_pos() noexcept;
+ void scan_syncs() noexcept;
+ void scan_vsync() noexcept;
+ void put_pixel() noexcept;
+ void guess_mode() noexcept;
+ void signal_lost() noexcept;
+ void update_window() noexcept;
+ };
+}
+
+#include "vga.impl.hpp"
+
+#endif
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
diff --git a/tb/vga_domain.sv b/tb/vga_domain.sv
new file mode 100644
index 0000000..d3ffbc0
--- /dev/null
+++ b/tb/vga_domain.sv
@@ -0,0 +1,35 @@
+module vga_domain
+(
+ input logic clk_clk,
+ reset_reset_n /*verilator public*/
+);
+
+ logic[25:0] avl_address /*verilator public*/;
+ logic avl_read /*verilator public*/;
+ logic avl_write /*verilator public*/;
+ logic[15:0] avl_readdata /*verilator public_flat_rw @(negedge clk_clk)*/;
+ logic[31:0] avl_writedata /*verilator public*/;
+ logic avl_waitrequest /*verilator public_flat_rw @(negedge clk_clk)*/;
+ logic avl_readdatavalid;
+ logic[3:0] avl_byteenable /*verilator public*/;
+
+ assign avl_write = 0;
+ assign avl_readdatavalid = avl_read && !avl_waitrequest;
+
+ logic vga_clk /*verilator public*/;
+ logic vga_hsync /*verilator public*/;
+ logic vga_vsync /*verilator public*/;
+ logic vga_blank_n /*verilator public*/;
+ logic vga_sync_n /*verilator public*/;
+ logic[7:0] vga_r /*verilator public*/;
+ logic[7:0] vga_g /*verilator public*/;
+ logic[7:0] vga_b /*verilator public*/;
+
+ vga crtc
+ (
+ .clk(clk_clk),
+ .rst_n(reset_reset_n),
+ .*
+ );
+
+endmodule
diff --git a/tb/window.hpp b/tb/window.hpp
new file mode 100644
index 0000000..181f3c1
--- /dev/null
+++ b/tb/window.hpp
@@ -0,0 +1,37 @@
+#ifndef TALLER_wINDOW_HPP
+#define TALLER_wINDOW_HPP
+
+#include <cstdint>
+#include <memory>
+
+#include "avalon.hpp"
+
+namespace taller::avalon
+{
+ class window : public slave
+ {
+ public:
+ inline window(slave &downstream, std::uint32_t base)
+ : slave(base, downstream.address_span(), downstream.word_size()),
+ downstream(downstream)
+ {}
+
+ inline virtual bool read(std::uint32_t addr, std::uint32_t &data) final override
+ {
+ return downstream.read(addr, data);
+ }
+
+ inline virtual bool write
+ (
+ std::uint32_t addr, std::uint32_t data, unsigned byte_enable = 0b1111
+ ) final override
+ {
+ return downstream.write(addr, data, byte_enable);
+ }
+
+ private:
+ slave &downstream;
+ };
+}
+
+#endif