diff options
| author | Alejandro Soto <alejandro@34project.org> | 2023-10-05 06:31:27 -0600 |
|---|---|---|
| committer | Alejandro Soto <alejandro@34project.org> | 2023-10-05 13:07:57 -0600 |
| commit | e2d82e8e18ebddc78a0c187c4b7501b9f3aaa9c5 (patch) | |
| tree | eb72ed72e33f3a5ef3d9843ddcff624156177647 /tb/top | |
| parent | 59caca686d4d7798b617ee2a9f9d4c5d1d27b8ff (diff) | |
tb: move most C++ source files to tb/top/conspiracion
Diffstat (limited to 'tb/top')
| -rw-r--r-- | tb/top/conspiracion/avalon.hpp | 256 | ||||
| -rw-r--r-- | tb/top/conspiracion/avalon.impl.hpp | 183 | ||||
| -rw-r--r-- | tb/top/conspiracion/const.hpp | 38 | ||||
| -rw-r--r-- | tb/top/conspiracion/interrupt.cpp | 63 | ||||
| -rw-r--r-- | tb/top/conspiracion/interval_timer.cpp | 104 | ||||
| -rw-r--r-- | tb/top/conspiracion/interval_timer.hpp | 33 | ||||
| -rw-r--r-- | tb/top/conspiracion/jtag_uart.cpp | 155 | ||||
| -rw-r--r-- | tb/top/conspiracion/jtag_uart.hpp | 43 | ||||
| -rw-r--r-- | tb/top/conspiracion/mem.cpp | 50 | ||||
| -rw-r--r-- | tb/top/conspiracion/mem.hpp | 55 | ||||
| -rw-r--r-- | tb/top/conspiracion/null.hpp | 31 | ||||
| -rw-r--r-- | tb/top/conspiracion/vga.hpp | 82 | ||||
| -rw-r--r-- | tb/top/conspiracion/vga.impl.hpp | 420 | ||||
| -rw-r--r-- | tb/top/conspiracion/window.hpp | 37 |
14 files changed, 1550 insertions, 0 deletions
diff --git a/tb/top/conspiracion/avalon.hpp b/tb/top/conspiracion/avalon.hpp new file mode 100644 index 0000000..b184999 --- /dev/null +++ b/tb/top/conspiracion/avalon.hpp @@ -0,0 +1,256 @@ +#ifndef TALLER_AVALON_HPP +#define TALLER_AVALON_HPP + +#include <cassert> +#include <cstdint> +#include <cstdio> +#include <stdexcept> +#include <vector> + +#include <verilated.h> + +namespace taller::avalon +{ + union line + { + __int128 qword; + VlWide<4> verilated; + + struct + { + std::uint64_t lo, hi; + }; + + struct + { + std::uint32_t words[4]; + }; + + inline line() noexcept + : lo{0}, hi{0} + {} + + inline line(VlWide<4> verilated) noexcept + : verilated(verilated) + {} + + inline operator VlWide<4>() const noexcept + { + return this->verilated; + } + + inline bool operator==(const VlWide<4> &verilated) const noexcept + { + line verilated_line{verilated}; + return this->hi == verilated_line.hi && this->lo == verilated_line.lo; + } + + inline bool operator!=(const VlWide<4> &verilated) const noexcept + { + return !(*this == verilated); + } + }; + + static_assert(sizeof(line) == 16); + + class slave + { + public: + inline slave(std::uint32_t base, std::uint32_t size, std::size_t word_size) noexcept + : 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; + } + + inline virtual void tick() noexcept + {} + + inline virtual void tick_falling() noexcept + {} + + inline virtual void bail() noexcept + {} + + virtual bool read(std::uint32_t addr, std::uint32_t &data) + { + line line_data; + if (!this->read_line(addr >> 2, line_data, 0b1111 << ((addr & 0b11) * 4))) + return false; + + data = line_data.words[addr & 0b11]; + return true; + } + + virtual bool read_line(std::uint32_t addr, line &data, unsigned byte_enable) + { + //XXX: Realmente es esto lo que genera qsys? Avalon spec no es clara + if (byte_enable & 0x000f) + return this->read(addr << 2, data.words[0]); + else if (byte_enable & 0x00f0) + return this->read((addr << 2) + 1, data.words[1]); + else if (byte_enable & 0x0f00) + return this->read((addr << 2) + 2, data.words[2]); + else if (byte_enable & 0xf000) + return this->read((addr << 2) + 3, data.words[3]); + + return true; + } + + virtual bool write + ( + std::uint32_t addr, std::uint32_t data, unsigned byte_enable = 0b1111 + ) { + line line_data; + line_data.words[addr & 0b11] = data; + + return this->write_line(addr >> 2, line_data, byte_enable << ((addr & 0b11) * 4)); + } + + virtual bool write_line(std::uint32_t addr, const line &data, unsigned byte_enable) + { + unsigned offset = 0; + if (byte_enable & 0x00f0) + offset = 1; + else if (byte_enable & 0x0f00) + offset = 2; + else if (byte_enable & 0xf000) + offset = 3; + + return this->write + ( + (addr << 2) + offset, + data.words[offset], + (byte_enable >> (offset * 4)) & 0b1111 + ); + } + + inline virtual bool irq() noexcept + { + return false; + } + + 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; + } + }; + + struct irq_lines + { + slave *timer = nullptr; + slave *jtaguart = nullptr; + }; + + class interrupt_controller : private slave + { + public: + interrupt_controller(std::uint32_t base) noexcept; + + virtual bool read(std::uint32_t addr, std::uint32_t &data) noexcept final override; + virtual bool write(std::uint32_t addr, std::uint32_t data, unsigned byte_enable) noexcept final override; + + virtual bool irq() noexcept final override; + + inline slave &as_slave() noexcept + { + return *this; + } + + inline irq_lines &lines() noexcept + { + return irqs; + } + + private: + irq_lines irqs; + std::uint32_t mask = 0; + + std::uint32_t status() noexcept; + }; + + class avl_bus_error : public std::runtime_error + { + public: + using std::runtime_error::runtime_error; + }; + + template<class Platform> + class interconnect + { + public: + interconnect(Platform &plat) noexcept; + + bool tick(bool clk) noexcept; + void tick_rising(); + void tick_falling() noexcept; + + void attach(slave &dev); + void attach_intc(interrupt_controller &intc); + void bail() noexcept; + + bool dump(std::uint32_t addr, std::uint32_t &word); + bool patch(std::uint32_t addr, std::uint32_t readdata); + + private: + struct binding + { + std::uint32_t base; + std::uint32_t mask; + slave &dev; + }; + + Platform &plat; + slave* active = nullptr; + std::vector<binding> devices; + interrupt_controller *root_intc = nullptr; + std::uint32_t avl_address = 0; + line avl_writedata; + unsigned avl_byteenable = 0; + bool avl_read = false; + bool avl_write = false; + + slave *resolve_external(std::uint32_t avl_address); + }; +} + +#include "avalon.impl.hpp" + +#endif diff --git a/tb/top/conspiracion/avalon.impl.hpp b/tb/top/conspiracion/avalon.impl.hpp new file mode 100644 index 0000000..12483f9 --- /dev/null +++ b/tb/top/conspiracion/avalon.impl.hpp @@ -0,0 +1,183 @@ +#ifndef TALLER_AVALON_IMPL_HPP +#define TALLER_AVALON_IMPL_HPP + +#include <cassert> +#include <cstdio> + +namespace taller::avalon +{ + template<class Platform> + inline interconnect<Platform>::interconnect(Platform &plat) noexcept + : plat(plat) + {} + + template<class Platform> + void interconnect<Platform>::attach(slave &dev) + { + auto base = dev.base_address(); + auto mask = dev.address_mask(); + assert((base & mask) == base); + + devices.push_back(binding { base, mask, dev }); + } + + template<class Platform> + void interconnect<Platform>::attach_intc(interrupt_controller &intc) + { + assert(root_intc == nullptr); + + attach(intc.as_slave()); + root_intc = &intc; + } + + template<class Platform> + bool interconnect<Platform>::tick(bool clk) noexcept + { + if (!plat.reset_reset_n) { + active = nullptr; + plat.avl_irq = 0; + + avl_read = false; + avl_write = false; + avl_address = 0; + avl_byteenable = 0; + + return true; + } + + if (active) { + assert(avl_address == plat.avl_address); + assert(avl_read == plat.avl_read); + assert(avl_write == plat.avl_write); + assert(avl_writedata == plat.avl_writedata); + assert(avl_byteenable == plat.avl_byteenable); + } + + if (!clk) { + tick_falling(); + return true; + } else if (!active) + assert(!avl_read || !avl_write); + + try { + tick_rising(); + return true; + } catch (const avl_bus_error&) { + return false; + } + } + + template<class Platform> + void interconnect<Platform>::tick_rising() + { + for (auto &binding : devices) + binding.dev.tick(); + + if (root_intc) + plat.avl_irq = root_intc->irq(); + + if (!active) { + avl_address = plat.avl_address; + avl_read = plat.avl_read; + avl_write = plat.avl_write; + avl_writedata = plat.avl_writedata; + avl_byteenable = plat.avl_byteenable; + + if (!avl_read && !avl_write) + return; + + for (auto &binding : devices) + if ((avl_address & binding.mask) == binding.base) { + active = &binding.dev; + break; + } + + if (!active) [[unlikely]] { + bail(); + + const char *op = avl_read ? "read" : "write"; + fprintf(stderr, "[avl] attempt to %s memory hole at 0x%08x\n", op, avl_address); + + throw avl_bus_error{"memory hole addressed"}; + } else if(avl_address & 0b1111) [[unlikely]] { + bail(); + fprintf(stderr, "[avl] unaligned address: 0x%08x\n", avl_address); + + throw avl_bus_error{"unaligned address"}; + } + } + + auto pos = (avl_address & ~active->address_mask()) >> 4; + + if (avl_read) { + line readdata; + plat.avl_waitrequest = !active->read_line(pos, readdata, avl_byteenable); + plat.avl_readdata = readdata; + } else if (avl_write) + plat.avl_waitrequest = !active->write_line(pos, avl_writedata, avl_byteenable); + } + + template<class Platform> + void interconnect<Platform>::tick_falling() noexcept + { + for (auto &binding : devices) + binding.dev.tick_falling(); + + if (!plat.avl_waitrequest) + active = nullptr; + } + + template<class Platform> + void interconnect<Platform>::bail() noexcept + { + for (auto &binding : devices) + binding.dev.bail(); + } + + template<class Platform> + bool interconnect<Platform>::dump(std::uint32_t addr, std::uint32_t &word) + { + std::uint32_t avl_address = addr << 2; + + auto *dev = resolve_external(avl_address); + if (!dev) + return false; + + auto pos = (avl_address & ~dev->address_mask()) >> dev->word_bits(); + + while (!dev->read(pos, word)) + continue; + + return true; + } + + template<class Platform> + bool interconnect<Platform>::patch(std::uint32_t addr, std::uint32_t writedata) + { + std::uint32_t avl_address = addr << 2; + + auto *dev = resolve_external(avl_address); + if (!dev) + return false; + + auto pos = (avl_address & ~dev->address_mask()) >> dev->word_bits(); + + while (!dev->write(pos, writedata, 0b1111)) + continue; + + return true; + } + + template<class Platform> + slave* interconnect<Platform>::resolve_external(std::uint32_t avl_address) + { + for (auto &binding : devices) + if ((avl_address & binding.mask) == binding.base) + return &binding.dev; + + fprintf(stderr, "[avl] attempt to access hole at 0x%08x\n", avl_address); + return nullptr; + } +} + +#endif diff --git a/tb/top/conspiracion/const.hpp b/tb/top/conspiracion/const.hpp new file mode 100644 index 0000000..4ccad3e --- /dev/null +++ b/tb/top/conspiracion/const.hpp @@ -0,0 +1,38 @@ +#ifndef TALLER_CONST_HPP +#define TALLER_CONST_HPP + +#include <cstdint> +#include <memory> + +#include "avalon.hpp" + +namespace taller::avalon +{ + class const_map : public slave + { + public: + inline const_map(std::uint32_t addr, std::uint32_t value, std::uint32_t size = 4) noexcept + : slave(addr, size, 4), + value(value) + {} + + inline virtual bool read(std::uint32_t addr, std::uint32_t &data) final override + { + data = value; + return true; + } + + inline virtual bool write + ( + std::uint32_t addr, std::uint32_t data, unsigned byte_enable = 0b1111 + ) final override + { + return true; + } + + private: + std::uint32_t value; + }; +} + +#endif diff --git a/tb/top/conspiracion/interrupt.cpp b/tb/top/conspiracion/interrupt.cpp new file mode 100644 index 0000000..4164bb7 --- /dev/null +++ b/tb/top/conspiracion/interrupt.cpp @@ -0,0 +1,63 @@ +#include <cstdint> + +#include "avalon.hpp" + +namespace taller::avalon +{ + interrupt_controller::interrupt_controller(std::uint32_t base) noexcept + : slave(base, 8, 4) + {} + + bool interrupt_controller::read(std::uint32_t addr, std::uint32_t &data) noexcept + { + switch(addr) + { + case 0: + data = status(); + break; + + case 1: + data = mask; + break; + } + + return true; + } + + bool interrupt_controller::write(std::uint32_t addr, std::uint32_t data, unsigned byte_enable) noexcept + { + switch(addr) + { + case 0: + break; + + case 1: + mask = data; + break; + } + + return true; + } + + bool interrupt_controller::irq() noexcept + { + return status() != 0; + } + + std::uint32_t interrupt_controller::status() noexcept + { + std::uint32_t lines = 0; + + if(irqs.timer) + { + lines |= irqs.timer->irq() << 0; + } + + if(irqs.jtaguart) + { + lines |= irqs.jtaguart->irq() << 1; + } + + return lines & mask; + } +} diff --git a/tb/top/conspiracion/interval_timer.cpp b/tb/top/conspiracion/interval_timer.cpp new file mode 100644 index 0000000..7ab15d8 --- /dev/null +++ b/tb/top/conspiracion/interval_timer.cpp @@ -0,0 +1,104 @@ +#include <cstdint> + +#include "avalon.hpp" +#include "interval_timer.hpp" + +namespace taller::avalon +{ + interval_timer::interval_timer(std::uint32_t base) noexcept + : slave(base, 32, 4) + {} + + void interval_timer::tick() noexcept + { + if(!status_run) + { + return; + } else if(count > 0) + { + --count; + } else + { + count = period; + status_to = 1; + status_run = control_cont; + } + } + + bool interval_timer::read(std::uint32_t addr, std::uint32_t &data) noexcept + { + switch(addr) + { + case 0: + data + = status_run << 1 + | status_to << 0; + + break; + + case 1: + data + = control_cont << 1 + | control_ito << 0; + + break; + + case 2: + data = period & 0xffff; + break; + + case 3: + data = period >> 16; + break; + + case 4: + data = snap & 0xffff; + break; + + case 5: + data = snap >> 16; + break; + } + + return true; + } + + bool interval_timer::write(std::uint32_t addr, std::uint32_t data, unsigned byte_enable) noexcept + { + switch(addr) + { + case 0: + status_to = 0; + break; + + case 1: + control_ito = !!(data & (1 << 0)); + control_cont = !!(data & (1 << 1)); + + status_run = (status_run && !!(data & (1 << 3))) || !!(data & (1 << 2)); + break; + + case 2: + period = (period & 0xffff'0000) | (data & 0xffff); + count = period; + break; + + case 3: + period = (period & 0xffff) | (data & 0xffff) << 16; + count = period; + break; + + case 4: + case 5: + snap = count; + break; + } + + return true; + } + + bool interval_timer::irq() noexcept + { + return control_ito && status_to; + } +} diff --git a/tb/top/conspiracion/interval_timer.hpp b/tb/top/conspiracion/interval_timer.hpp new file mode 100644 index 0000000..8d6f8c7 --- /dev/null +++ b/tb/top/conspiracion/interval_timer.hpp @@ -0,0 +1,33 @@ +#ifndef TALLER_INTERVAL_TIMER_HPP +#define TALLER_INTERVAL_TIMER_HPP + +#include <cstdint> + +#include "avalon.hpp" + +namespace taller::avalon +{ + class interval_timer : public slave + { + public: + interval_timer(std::uint32_t base) noexcept; + + virtual void tick() noexcept final override; + + virtual bool read(std::uint32_t addr, std::uint32_t &data) noexcept final override; + virtual bool write(std::uint32_t addr, std::uint32_t data, unsigned byte_enable) noexcept final override; + + virtual bool irq() noexcept final override; + + private: + std::uint32_t count; + std::uint32_t period; + std::uint32_t snap; + bool status_to = false; + bool status_run = false; + bool control_ito = false; + bool control_cont = false; + }; +} + +#endif diff --git a/tb/top/conspiracion/jtag_uart.cpp b/tb/top/conspiracion/jtag_uart.cpp new file mode 100644 index 0000000..f661cdf --- /dev/null +++ b/tb/top/conspiracion/jtag_uart.cpp @@ -0,0 +1,155 @@ +#include <cstdint> + +#include <ncursesw/ncurses.h> + +#include "avalon.hpp" +#include "jtag_uart.hpp" + +namespace taller::avalon +{ + jtag_uart::jtag_uart(std::uint32_t base) noexcept + : slave(base, 8, 4) + {} + + jtag_uart::~jtag_uart() noexcept + { + release(); + } + + void jtag_uart::tick() noexcept + { + if(!took_over || rx_avail == sizeof rx) + { + return; + } + + if(countdown > 0) + { + --countdown; + return; + } + + countdown = 10'000; + + int input = getch(); + if(input != ERR) + { + unsigned index = rx_next + rx_avail; + if(index >= sizeof rx) + { + index -= sizeof rx; + } + + rx[index] = input; + ++rx_avail; + } + + ctrl_ac = 1; + } + + bool jtag_uart::read(std::uint32_t addr, std::uint32_t &data) noexcept + { + bool valid_read; + bool ctrl_ri; + bool ctrl_wi; + bool ctrl_rrdy; + + std::uint8_t read; + + switch(addr) + { + case 0: + read = rx[rx_next]; + valid_read = rx_avail > 0; + + if(valid_read) + { + --rx_avail; + rx_next = rx_next + 1 < sizeof rx ? rx_next + 1 : 0; + } + + data + = rx_avail << 16 + | valid_read << 15 + | (read & 0xff); + + break; + + case 1: + ctrl_ri = ctrl_re && rx_avail > 0; + + // Siempre se puede escribir + ctrl_wi = ctrl_we; + + // Este bit no existe pero U-Boot lo espera por alguna razón + ctrl_rrdy = rx_avail > 0; + + data + = ctrl_re << 0 + | ctrl_we << 1 + | ctrl_ri << 8 + | ctrl_wi << 9 + | ctrl_ac << 10 + | ctrl_rrdy << 12 + | 63 << 16; + + break; + } + + return true; + } + + bool jtag_uart::write(std::uint32_t addr, std::uint32_t data, unsigned byte_enable) noexcept + { + switch(addr) + { + case 0: + putchar(data & 0xff); + fflush(stdout); + + ctrl_ac = 1; + break; + + case 1: + ctrl_re = !!(data & (1 << 0)); + ctrl_we = !!(data & (1 << 1)); + if(!!(data & (1 << 10))) + { + ctrl_ac = 0; + } + + break; + } + + return true; + } + + bool jtag_uart::irq() noexcept + { + return ctrl_we || (ctrl_re && rx_avail > 0); + } + + void jtag_uart::takeover() noexcept + { + if(took_over) + { + return; + } + + assert(::initscr() != nullptr); + assert(::noecho() != ERR); + assert(::nodelay(stdscr, TRUE) != ERR); + assert(::cbreak() != ERR); + + took_over = true; + } + + void jtag_uart::release() noexcept + { + if(took_over) + { + ::endwin(); + putchar('\n'); + } + } +} diff --git a/tb/top/conspiracion/jtag_uart.hpp b/tb/top/conspiracion/jtag_uart.hpp new file mode 100644 index 0000000..3b8e7e2 --- /dev/null +++ b/tb/top/conspiracion/jtag_uart.hpp @@ -0,0 +1,43 @@ +#ifndef TALLER_JTAG_UART_HPP +#define TALLER_JTAG_UART_HPP + +#include <cstdint> + +#include "avalon.hpp" + +namespace taller::avalon +{ + class jtag_uart : public slave + { + public: + jtag_uart(std::uint32_t base) noexcept; + ~jtag_uart() noexcept; + + virtual void tick() noexcept final override; + + inline virtual void bail() noexcept final override + { + release(); + } + + virtual bool read(std::uint32_t addr, std::uint32_t &data) noexcept final override; + virtual bool write(std::uint32_t addr, std::uint32_t data, unsigned byte_enable) noexcept final override; + + virtual bool irq() noexcept final override; + + void takeover() noexcept; + void release() noexcept; + + private: + unsigned countdown = 0; + unsigned rx_avail = 0; + unsigned rx_next = 0; + bool ctrl_re = false; + bool ctrl_we = false; + bool ctrl_ac = true; + bool took_over = false; + std::uint8_t rx[64]; + }; +} + +#endif diff --git a/tb/top/conspiracion/mem.cpp b/tb/top/conspiracion/mem.cpp new file mode 100644 index 0000000..a58eaa9 --- /dev/null +++ b/tb/top/conspiracion/mem.cpp @@ -0,0 +1,50 @@ +#include <cassert> +#include <cstdint> +#include <memory> + +#include "avalon.hpp" +#include "mem.hpp" + +namespace taller::avalon +{ + mem::mem(std::uint32_t base, std::uint32_t size) + : slave(base, size, 4), + block(std::make_unique<line[]>(size >> 4)) + {} + + bool mem::read_line(std::uint32_t addr, line &data, unsigned byte_enable [[maybe_unused]]) + { + data = block[addr]; + return true;/*ready();*/ + } + + bool mem::write_line(std::uint32_t addr, const line &data, unsigned byte_enable) + { + for (unsigned i = 0; i < 4; ++i) { + 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; + + byte_enable >>= 4; + block[addr].words[i] = (data.words[i] & bytes) | (block[addr].words[i] & ~bytes); + } + + return true;/*ready();*/ + } + + bool mem::ready() noexcept + { + count = count > 0 ? count - 1 : 2; + return count == 0; + } +} diff --git a/tb/top/conspiracion/mem.hpp b/tb/top/conspiracion/mem.hpp new file mode 100644 index 0000000..abcf386 --- /dev/null +++ b/tb/top/conspiracion/mem.hpp @@ -0,0 +1,55 @@ +#ifndef TALLER_MEM_HPP +#define TALLER_MEM_HPP + +#include <cstdint> +#include <memory> + +#include "avalon.hpp" + +namespace taller::avalon +{ + class mem : public slave + { + public: + mem(std::uint32_t base, std::uint32_t size); + + virtual bool read_line + ( + std::uint32_t addr, line &data, unsigned byte_enable + ) final override; + + virtual bool write_line + ( + std::uint32_t addr, const line &data, unsigned byte_enable + ) final override; + + template<typename F> + void load(F loader, std::size_t offset = 0); + + private: + std::unique_ptr<line[]> block; + unsigned count = 0; + + bool ready() noexcept; + }; + + template<typename F> + void mem::load(F loader, std::size_t offset) + { + const auto base = base_address(); + const auto bits = 4; + + 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) + break; + + addr += read << bits; + } + } +} + +#endif diff --git a/tb/top/conspiracion/null.hpp b/tb/top/conspiracion/null.hpp new file mode 100644 index 0000000..37de464 --- /dev/null +++ b/tb/top/conspiracion/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/top/conspiracion/vga.hpp b/tb/top/conspiracion/vga.hpp new file mode 100644 index 0000000..578dd17 --- /dev/null +++ b/tb/top/conspiracion/vga.hpp @@ -0,0 +1,82 @@ +#ifndef LIBTALLER_VGA_HPP +#define LIBTALLER_VGA_HPP + +#include <array> +#include <cstddef> +#include <cstdint> + +#include <SDL2/SDL_surface.h> +#include <SDL2/SDL_video.h> + +#include "avalon.hpp" + +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 avalon::slave + { + public: + display(Crtc &crtc, std::uint32_t base, std::uint32_t clock_hz, std::uint32_t bus_hz = 0) noexcept; + + ~display() noexcept; + + virtual void tick() noexcept final override; + + virtual bool read(std::uint32_t addr, std::uint32_t &data) noexcept final override; + virtual bool write(std::uint32_t addr, std::uint32_t data, unsigned byte_enable) noexcept final override; + + void signal_tick(bool clk) noexcept; + + inline bool key(std::size_t index) + { + return keys.at(index); + } + + private: + unsigned ticks = 0; + unsigned refresh_ticks = 0; + unsigned max_addr = 0; + 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/top/conspiracion/vga.impl.hpp b/tb/top/conspiracion/vga.impl.hpp new file mode 100644 index 0000000..5888b5a --- /dev/null +++ b/tb/top/conspiracion/vga.impl.hpp @@ -0,0 +1,420 @@ +#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> + +#include "avalon.hpp" + +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 base, std::uint32_t clock_hz, std::uint32_t bus_hz) noexcept + : avalon::slave(base, 64 << 20, 4), + crtc(crtc), + clock_hz(clock_hz) + { + if(bus_hz > 0) + { + mode = &MODES[0]; + max_addr = mode->h.active * mode->v.active; + + refresh_ticks = + static_cast<float>(bus_hz) + / mode->pixel_clk + * (mode->h.active + mode->h.front_porch + mode->h.sync + mode->h.back_porch) + * (mode->v.active + mode->v.front_porch + mode->v.sync + mode->v.back_porch); + + ticks = refresh_ticks - 1; + } + } + + template<class Crtc> + display<Crtc>::~display() noexcept + { + mode = nullptr; + update_window(); + } + + template<class Crtc> + void display<Crtc>::tick() noexcept + { + if(++ticks == refresh_ticks) + { + ticks = 0; + if(!window) + { + update_window(); + } + + if(window) + { + ::SDL_UpdateWindowSurface(window); + } + } + } + + template<class Crtc> + bool display<Crtc>::read(std::uint32_t addr, std::uint32_t &data) noexcept + { + return true; + } + + template<class Crtc> + bool display<Crtc>::write(std::uint32_t addr, std::uint32_t data, unsigned byte_enable) noexcept + { + if(!window || !mode) + { + return true; + } + + auto *surface = ::SDL_GetWindowSurface(window); + if(!surface) + { + return true; + } + + auto *pixels = static_cast<std::uint32_t*>(surface->pixels); + if(addr < max_addr) + { + pixels[addr] = data; + } + + return true; + } + + template<class Crtc> + void display<Crtc>::signal_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/top/conspiracion/window.hpp b/tb/top/conspiracion/window.hpp new file mode 100644 index 0000000..181f3c1 --- /dev/null +++ b/tb/top/conspiracion/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 |
