From e2d82e8e18ebddc78a0c187c4b7501b9f3aaa9c5 Mon Sep 17 00:00:00 2001 From: Alejandro Soto Date: Thu, 5 Oct 2023 06:31:27 -0600 Subject: tb: move most C++ source files to tb/top/conspiracion --- tb/avalon.hpp | 256 -------------------- tb/avalon.impl.hpp | 183 -------------- tb/const.hpp | 38 --- tb/interrupt.cpp | 63 ----- tb/interval_timer.cpp | 104 -------- tb/interval_timer.hpp | 33 --- tb/jtag_uart.cpp | 155 ------------ tb/jtag_uart.hpp | 43 ---- tb/mem.cpp | 50 ---- tb/mem.hpp | 55 ----- tb/null.hpp | 31 --- tb/top/conspiracion/avalon.hpp | 256 ++++++++++++++++++++ tb/top/conspiracion/avalon.impl.hpp | 183 ++++++++++++++ tb/top/conspiracion/const.hpp | 38 +++ tb/top/conspiracion/interrupt.cpp | 63 +++++ tb/top/conspiracion/interval_timer.cpp | 104 ++++++++ tb/top/conspiracion/interval_timer.hpp | 33 +++ tb/top/conspiracion/jtag_uart.cpp | 155 ++++++++++++ tb/top/conspiracion/jtag_uart.hpp | 43 ++++ tb/top/conspiracion/mem.cpp | 50 ++++ tb/top/conspiracion/mem.hpp | 55 +++++ tb/top/conspiracion/null.hpp | 31 +++ tb/top/conspiracion/vga.hpp | 82 +++++++ tb/top/conspiracion/vga.impl.hpp | 420 +++++++++++++++++++++++++++++++++ tb/top/conspiracion/window.hpp | 37 +++ tb/vga.hpp | 82 ------- tb/vga.impl.hpp | 420 --------------------------------- tb/window.hpp | 37 --- 28 files changed, 1550 insertions(+), 1550 deletions(-) delete mode 100644 tb/avalon.hpp delete mode 100644 tb/avalon.impl.hpp delete mode 100644 tb/const.hpp delete mode 100644 tb/interrupt.cpp delete mode 100644 tb/interval_timer.cpp delete mode 100644 tb/interval_timer.hpp delete mode 100644 tb/jtag_uart.cpp delete mode 100644 tb/jtag_uart.hpp delete mode 100644 tb/mem.cpp delete mode 100644 tb/mem.hpp delete mode 100644 tb/null.hpp create mode 100644 tb/top/conspiracion/avalon.hpp create mode 100644 tb/top/conspiracion/avalon.impl.hpp create mode 100644 tb/top/conspiracion/const.hpp create mode 100644 tb/top/conspiracion/interrupt.cpp create mode 100644 tb/top/conspiracion/interval_timer.cpp create mode 100644 tb/top/conspiracion/interval_timer.hpp create mode 100644 tb/top/conspiracion/jtag_uart.cpp create mode 100644 tb/top/conspiracion/jtag_uart.hpp create mode 100644 tb/top/conspiracion/mem.cpp create mode 100644 tb/top/conspiracion/mem.hpp create mode 100644 tb/top/conspiracion/null.hpp create mode 100644 tb/top/conspiracion/vga.hpp create mode 100644 tb/top/conspiracion/vga.impl.hpp create mode 100644 tb/top/conspiracion/window.hpp delete mode 100644 tb/vga.hpp delete mode 100644 tb/vga.impl.hpp delete mode 100644 tb/window.hpp (limited to 'tb') diff --git a/tb/avalon.hpp b/tb/avalon.hpp deleted file mode 100644 index b184999..0000000 --- a/tb/avalon.hpp +++ /dev/null @@ -1,256 +0,0 @@ -#ifndef TALLER_AVALON_HPP -#define TALLER_AVALON_HPP - -#include -#include -#include -#include -#include - -#include - -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 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 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/avalon.impl.hpp b/tb/avalon.impl.hpp deleted file mode 100644 index 12483f9..0000000 --- a/tb/avalon.impl.hpp +++ /dev/null @@ -1,183 +0,0 @@ -#ifndef TALLER_AVALON_IMPL_HPP -#define TALLER_AVALON_IMPL_HPP - -#include -#include - -namespace taller::avalon -{ - template - inline interconnect::interconnect(Platform &plat) noexcept - : plat(plat) - {} - - template - void interconnect::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 - void interconnect::attach_intc(interrupt_controller &intc) - { - assert(root_intc == nullptr); - - attach(intc.as_slave()); - root_intc = &intc; - } - - template - bool interconnect::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 - void interconnect::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 - void interconnect::tick_falling() noexcept - { - for (auto &binding : devices) - binding.dev.tick_falling(); - - if (!plat.avl_waitrequest) - active = nullptr; - } - - template - void interconnect::bail() noexcept - { - for (auto &binding : devices) - binding.dev.bail(); - } - - template - bool interconnect::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 - bool interconnect::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 - slave* interconnect::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/const.hpp b/tb/const.hpp deleted file mode 100644 index 4ccad3e..0000000 --- a/tb/const.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef TALLER_CONST_HPP -#define TALLER_CONST_HPP - -#include -#include - -#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/interrupt.cpp b/tb/interrupt.cpp deleted file mode 100644 index 4164bb7..0000000 --- a/tb/interrupt.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include - -#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/interval_timer.cpp b/tb/interval_timer.cpp deleted file mode 100644 index 7ab15d8..0000000 --- a/tb/interval_timer.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include - -#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/interval_timer.hpp b/tb/interval_timer.hpp deleted file mode 100644 index 8d6f8c7..0000000 --- a/tb/interval_timer.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef TALLER_INTERVAL_TIMER_HPP -#define TALLER_INTERVAL_TIMER_HPP - -#include - -#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/jtag_uart.cpp b/tb/jtag_uart.cpp deleted file mode 100644 index f661cdf..0000000 --- a/tb/jtag_uart.cpp +++ /dev/null @@ -1,155 +0,0 @@ -#include - -#include - -#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/jtag_uart.hpp b/tb/jtag_uart.hpp deleted file mode 100644 index 3b8e7e2..0000000 --- a/tb/jtag_uart.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef TALLER_JTAG_UART_HPP -#define TALLER_JTAG_UART_HPP - -#include - -#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/mem.cpp b/tb/mem.cpp deleted file mode 100644 index a58eaa9..0000000 --- a/tb/mem.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include -#include -#include - -#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(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/mem.hpp b/tb/mem.hpp deleted file mode 100644 index abcf386..0000000 --- a/tb/mem.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef TALLER_MEM_HPP -#define TALLER_MEM_HPP - -#include -#include - -#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 - void load(F loader, std::size_t offset = 0); - - private: - std::unique_ptr block; - unsigned count = 0; - - bool ready() noexcept; - }; - - template - 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/null.hpp b/tb/null.hpp deleted file mode 100644 index 37de464..0000000 --- a/tb/null.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef TALLER_NULL_HPP -#define TALLER_NULL_HPP - -#include -#include - -#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/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 +#include +#include +#include +#include + +#include + +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 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 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 +#include + +namespace taller::avalon +{ + template + inline interconnect::interconnect(Platform &plat) noexcept + : plat(plat) + {} + + template + void interconnect::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 + void interconnect::attach_intc(interrupt_controller &intc) + { + assert(root_intc == nullptr); + + attach(intc.as_slave()); + root_intc = &intc; + } + + template + bool interconnect::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 + void interconnect::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 + void interconnect::tick_falling() noexcept + { + for (auto &binding : devices) + binding.dev.tick_falling(); + + if (!plat.avl_waitrequest) + active = nullptr; + } + + template + void interconnect::bail() noexcept + { + for (auto &binding : devices) + binding.dev.bail(); + } + + template + bool interconnect::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 + bool interconnect::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 + slave* interconnect::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 +#include + +#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 + +#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 + +#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 + +#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 + +#include + +#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 + +#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 +#include +#include + +#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(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 +#include + +#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 + void load(F loader, std::size_t offset = 0); + + private: + std::unique_ptr block; + unsigned count = 0; + + bool ready() noexcept; + }; + + template + 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 +#include + +#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 +#include +#include + +#include +#include + +#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 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 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 +#include +#include +#include +#include + +#include +#include +#include + +#include "avalon.hpp" + +namespace +{ + // https://web.mit.edu/6.111/www/s2004/NEWKIT/vga.shtml + constexpr std::array 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 + display::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(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 + display::~display() noexcept + { + mode = nullptr; + update_window(); + } + + template + void display::tick() noexcept + { + if(++ticks == refresh_ticks) + { + ticks = 0; + if(!window) + { + update_window(); + } + + if(window) + { + ::SDL_UpdateWindowSurface(window); + } + } + } + + template + bool display::read(std::uint32_t addr, std::uint32_t &data) noexcept + { + return true; + } + + template + bool display::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(surface->pixels); + if(addr < max_addr) + { + pixels[addr] = data; + } + + return true; + } + + template + void display::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 + void display::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 + void display::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 + void display::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 + void display::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(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 + void display::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(hsync_hi)) <= 2 + && std::abs(lines - static_cast(vsync_hi)) <= 2 + && std::abs(static_cast(hsync_lo) - static_cast(candidate.h.sync)) <= 2 + && std::abs(static_cast(vsync_lo) - static_cast(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(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 + void display::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 + void display::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 +#include + +#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 diff --git a/tb/vga.hpp b/tb/vga.hpp deleted file mode 100644 index 578dd17..0000000 --- a/tb/vga.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef LIBTALLER_VGA_HPP -#define LIBTALLER_VGA_HPP - -#include -#include -#include - -#include -#include - -#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 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 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 deleted file mode 100644 index 5888b5a..0000000 --- a/tb/vga.impl.hpp +++ /dev/null @@ -1,420 +0,0 @@ -#ifndef TALLER_VGA_IMPL_HPP -#define TALLER_VGA_IMPL_HPP - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "avalon.hpp" - -namespace -{ - // https://web.mit.edu/6.111/www/s2004/NEWKIT/vga.shtml - constexpr std::array 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 - display::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(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 - display::~display() noexcept - { - mode = nullptr; - update_window(); - } - - template - void display::tick() noexcept - { - if(++ticks == refresh_ticks) - { - ticks = 0; - if(!window) - { - update_window(); - } - - if(window) - { - ::SDL_UpdateWindowSurface(window); - } - } - } - - template - bool display::read(std::uint32_t addr, std::uint32_t &data) noexcept - { - return true; - } - - template - bool display::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(surface->pixels); - if(addr < max_addr) - { - pixels[addr] = data; - } - - return true; - } - - template - void display::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 - void display::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 - void display::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 - void display::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 - void display::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(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 - void display::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(hsync_hi)) <= 2 - && std::abs(lines - static_cast(vsync_hi)) <= 2 - && std::abs(static_cast(hsync_lo) - static_cast(candidate.h.sync)) <= 2 - && std::abs(static_cast(vsync_lo) - static_cast(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(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 - void display::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 - void display::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/window.hpp b/tb/window.hpp deleted file mode 100644 index 181f3c1..0000000 --- a/tb/window.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef TALLER_wINDOW_HPP -#define TALLER_wINDOW_HPP - -#include -#include - -#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 -- cgit v1.2.3