summaryrefslogtreecommitdiff
path: root/tb/top
diff options
context:
space:
mode:
authorAlejandro Soto <alejandro@34project.org>2023-10-05 06:31:27 -0600
committerAlejandro Soto <alejandro@34project.org>2023-10-05 13:07:57 -0600
commite2d82e8e18ebddc78a0c187c4b7501b9f3aaa9c5 (patch)
treeeb72ed72e33f3a5ef3d9843ddcff624156177647 /tb/top
parent59caca686d4d7798b617ee2a9f9d4c5d1d27b8ff (diff)
tb: move most C++ source files to tb/top/conspiracion
Diffstat (limited to 'tb/top')
-rw-r--r--tb/top/conspiracion/avalon.hpp256
-rw-r--r--tb/top/conspiracion/avalon.impl.hpp183
-rw-r--r--tb/top/conspiracion/const.hpp38
-rw-r--r--tb/top/conspiracion/interrupt.cpp63
-rw-r--r--tb/top/conspiracion/interval_timer.cpp104
-rw-r--r--tb/top/conspiracion/interval_timer.hpp33
-rw-r--r--tb/top/conspiracion/jtag_uart.cpp155
-rw-r--r--tb/top/conspiracion/jtag_uart.hpp43
-rw-r--r--tb/top/conspiracion/mem.cpp50
-rw-r--r--tb/top/conspiracion/mem.hpp55
-rw-r--r--tb/top/conspiracion/null.hpp31
-rw-r--r--tb/top/conspiracion/vga.hpp82
-rw-r--r--tb/top/conspiracion/vga.impl.hpp420
-rw-r--r--tb/top/conspiracion/window.hpp37
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