Implement sending keypresses to UART with support for queueing
This commit is contained in:
parent
616f70bfa4
commit
ca5c7e3bd3
3 changed files with 146 additions and 25 deletions
|
@ -1,4 +1,5 @@
|
|||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_keycode.h>
|
||||
#include <SDL2/SDL_render.h>
|
||||
|
||||
#include <iostream>
|
||||
|
@ -14,29 +15,26 @@ SDL_Texture* texture = nullptr;
|
|||
#define FB_SCALE 2
|
||||
#define FB_OFFSET 0x10000
|
||||
|
||||
Framebuffer::Framebuffer()
|
||||
Framebuffer::Framebuffer(UART& uart)
|
||||
: Device(FRAMEBUFFER_ADDR, FB_WIDTH * FB_HEIGHT + FB_OFFSET),
|
||||
width(FB_WIDTH),
|
||||
height(FB_HEIGHT),
|
||||
buf(FB_WIDTH * FB_HEIGHT, 0) {
|
||||
buf(FB_WIDTH * FB_HEIGHT, 0),
|
||||
uart(uart) {
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||
throw std::runtime_error("Failed to initialize SDL");
|
||||
}
|
||||
|
||||
mainloop_thread = std::thread(&Framebuffer::mainloop, this);
|
||||
}
|
||||
|
||||
void Framebuffer::mainloop() {
|
||||
window = SDL_CreateWindow("rve Framebuffer", SDL_WINDOWPOS_CENTERED,
|
||||
SDL_WINDOWPOS_CENTERED, width, height, 0);
|
||||
if (!window) {
|
||||
throw std::runtime_error("Failed to create SDL window");
|
||||
}
|
||||
/*
|
||||
SDL_Surface* surface = SDL_GetWindowSurface(window);
|
||||
if (!surface) {
|
||||
throw std::runtime_error("Failed to create SDL surface");
|
||||
}
|
||||
*/
|
||||
|
||||
// renderer = SDL_CreateSoftwareRenderer(surface);
|
||||
|
||||
// renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
|
||||
if (!renderer) {
|
||||
throw std::runtime_error("Failed to create SDL renderer");
|
||||
|
@ -49,7 +47,60 @@ Framebuffer::Framebuffer()
|
|||
|
||||
SDL_ShowWindow(window);
|
||||
|
||||
draw();
|
||||
SDL_Event e;
|
||||
while (true) {
|
||||
while (SDL_PollEvent(&e) != 0) {
|
||||
if (e.type == SDL_QUIT) {
|
||||
}
|
||||
if (e.type == SDL_KEYDOWN || e.type == SDL_KEYUP) {
|
||||
uint8_t msb = (e.type == SDL_KEYDOWN) ? 0x80 : 0x0;
|
||||
|
||||
SDL_Keycode key = e.key.keysym.sym;
|
||||
|
||||
uint8_t asciiValue = 0;
|
||||
bool valid_key = true;
|
||||
if (key >= SDLK_a && key <= SDLK_z) {
|
||||
asciiValue = static_cast<uint8_t>(key - SDLK_a + 'a');
|
||||
} else if (key >= SDLK_0 && key <= SDLK_9) {
|
||||
asciiValue = static_cast<uint8_t>(key - SDLK_0 + '0');
|
||||
} else if (key == SDLK_SPACE) {
|
||||
asciiValue = ' ';
|
||||
} else if (key == SDLK_RETURN) {
|
||||
asciiValue = 8;
|
||||
} else if (key == SDLK_ESCAPE) {
|
||||
asciiValue = 7;
|
||||
} else if (key == SDLK_LEFT) {
|
||||
asciiValue = 0;
|
||||
} else if (key == SDLK_RIGHT) {
|
||||
asciiValue = 1;
|
||||
} else if (key == SDLK_UP) {
|
||||
asciiValue = 3;
|
||||
} else if (key == SDLK_DOWN) {
|
||||
asciiValue = 2;
|
||||
} else if (key == SDLK_RCTRL || key == SDLK_LCTRL) {
|
||||
asciiValue = 5;
|
||||
} else {
|
||||
valid_key = false;
|
||||
}
|
||||
|
||||
if (valid_key) {
|
||||
if ((msb && !is_pressed[asciiValue]) ||
|
||||
(!msb && is_pressed[asciiValue])) {
|
||||
uart.set_rx(asciiValue | msb);
|
||||
}
|
||||
is_pressed[asciiValue] = bool(msb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (render_request.exchange(false)) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
SDL_Delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
void Framebuffer::draw() {
|
||||
|
@ -60,11 +111,10 @@ void Framebuffer::draw() {
|
|||
buf[i] = cmap;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
SDL_UpdateTexture(texture, nullptr, &buf[0],
|
||||
width * 4); // Update the SDL texture
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
||||
SDL_RenderPresent(renderer);
|
||||
render_request = true;
|
||||
}
|
||||
|
||||
void Framebuffer::write_mem_u8(uint8_t* src, size_t addr) {
|
||||
|
|
63
src/vm.cpp
63
src/vm.cpp
|
@ -14,7 +14,7 @@ inline int32_t sign_extend(int32_t value, int bits) {
|
|||
}
|
||||
|
||||
VM::VM(const std::vector<uint8_t>& memory, const std::string& file_path)
|
||||
: ram(memory), pc(PROGRAM_ADDR), file_path(file_path) {
|
||||
: ram(memory), pc(PROGRAM_ADDR), file_path(file_path), framebuffer(uart) {
|
||||
devices = std::vector<Device*>(4, 0);
|
||||
devices[0] = &ram;
|
||||
devices[1] = &uart;
|
||||
|
@ -30,7 +30,18 @@ void VM::setreg(size_t regnum, uint32_t value) {
|
|||
registers[regnum] = value;
|
||||
}
|
||||
|
||||
void UART::read_mem_u8(uint8_t* dst, size_t addr) {
|
||||
UART::UART() : Device(UART_ADDR, 8) {
|
||||
// We start with an empty RX register
|
||||
clear_rx();
|
||||
}
|
||||
|
||||
void UART::clear_rx() {
|
||||
if (rx_queue.size() > 0) {
|
||||
rx_queue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void UART::read_mem_u8_impl(uint8_t* dst, size_t addr) {
|
||||
if (addr < UART_ADDR || addr + 1 > UART_ADDR + 8) {
|
||||
throw std::runtime_error(std::string("Memory access out of bounds: ") +
|
||||
std::format("{:x}", addr));
|
||||
|
@ -38,12 +49,16 @@ void UART::read_mem_u8(uint8_t* dst, size_t addr) {
|
|||
addr -= UART_ADDR;
|
||||
switch (addr) {
|
||||
case UART_RXDATA:
|
||||
*dst = 0x0;
|
||||
break;
|
||||
case UART_RXDATA + 3:
|
||||
// receiver is always empty for now
|
||||
*dst = 0x80;
|
||||
case UART_RXDATA + 1:
|
||||
case UART_RXDATA + 2:
|
||||
case UART_RXDATA + 3: {
|
||||
uint32_t val = 0x80000000;
|
||||
if (rx_queue.size() > 0) {
|
||||
val = rx_queue.front();
|
||||
}
|
||||
*dst = ((uint8_t*)&val)[addr - UART_RXDATA];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
*dst = 0;
|
||||
}
|
||||
|
@ -62,6 +77,14 @@ void UART::write_mem_u8(uint8_t* src, size_t addr) {
|
|||
}
|
||||
}
|
||||
|
||||
void UART::write_mem_u16(uint16_t* src, size_t addr) {
|
||||
uint8_t* s = (uint8_t*)src;
|
||||
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
write_mem_u8(s + i, addr + i);
|
||||
}
|
||||
}
|
||||
|
||||
void UART::write_mem_u32(uint32_t* src, size_t addr) {
|
||||
uint8_t* s = (uint8_t*)src;
|
||||
|
||||
|
@ -70,12 +93,36 @@ void UART::write_mem_u32(uint32_t* src, size_t addr) {
|
|||
}
|
||||
}
|
||||
|
||||
void UART::read_mem_u8(uint8_t* dst, size_t addr) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
read_mem_u8_impl(dst, addr);
|
||||
clear_rx();
|
||||
}
|
||||
|
||||
void UART::read_mem_u16(uint16_t* dst, size_t addr) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
uint8_t* d = (uint8_t*)dst;
|
||||
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
read_mem_u8_impl(d + i, addr + i);
|
||||
}
|
||||
clear_rx();
|
||||
}
|
||||
|
||||
void UART::read_mem_u32(uint32_t* dst, size_t addr) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
uint8_t* d = (uint8_t*)dst;
|
||||
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
read_mem_u8(d + i, addr + i);
|
||||
read_mem_u8_impl(d + i, addr + i);
|
||||
}
|
||||
clear_rx();
|
||||
}
|
||||
|
||||
void UART::set_rx(uint8_t value) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
rx_queue.push(value);
|
||||
}
|
||||
|
||||
void Timer::update() {
|
||||
|
|
28
src/vm.hpp
28
src/vm.hpp
|
@ -2,8 +2,11 @@
|
|||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
class EbreakException : std::exception {};
|
||||
|
@ -72,15 +75,28 @@ class Device {
|
|||
|
||||
class UART final : public Device {
|
||||
public:
|
||||
UART() : Device(UART_ADDR, 8) {}
|
||||
UART();
|
||||
|
||||
virtual void write_mem_u8(uint8_t *src, size_t addr);
|
||||
virtual void write_mem_u16(uint16_t *src, size_t addr);
|
||||
virtual void write_mem_u32(uint32_t *src, size_t addr);
|
||||
|
||||
virtual void read_mem_u8(uint8_t *dst, size_t addr);
|
||||
virtual void read_mem_u16(uint16_t *dst, size_t addr);
|
||||
virtual void read_mem_u32(uint32_t *dst, size_t addr);
|
||||
|
||||
void set_rx(uint8_t value);
|
||||
|
||||
private:
|
||||
void read_mem_u8_impl(uint8_t *dst, size_t addr);
|
||||
|
||||
void clear_rx();
|
||||
|
||||
// TODO: this mutex is to protect the RX register from concurrent
|
||||
// read/write. But in fact it can be an atomic variable to spend
|
||||
// fewer resources on access.
|
||||
std::mutex mtx;
|
||||
|
||||
enum Registers {
|
||||
UART_TXDATA = 0x00,
|
||||
UART_RXDATA = 0x04,
|
||||
|
@ -88,6 +104,7 @@ class UART final : public Device {
|
|||
|
||||
// Line Status Register bits
|
||||
enum LSRBits { LSR_TRANSMITTER_EMPTY = 0x20 };
|
||||
std::queue<uint8_t> rx_queue;
|
||||
};
|
||||
|
||||
class RAM final : public Device {
|
||||
|
@ -107,7 +124,7 @@ class Timer final : public Device {
|
|||
|
||||
class Framebuffer final : public Device {
|
||||
public:
|
||||
Framebuffer();
|
||||
Framebuffer(UART &uart);
|
||||
virtual void write_mem_u8(uint8_t *src, size_t addr);
|
||||
virtual void write_mem_u16(uint16_t *src, size_t addr);
|
||||
virtual void write_mem_u32(uint32_t *src, size_t addr);
|
||||
|
@ -115,9 +132,16 @@ class Framebuffer final : public Device {
|
|||
protected:
|
||||
void draw();
|
||||
|
||||
void mainloop();
|
||||
|
||||
size_t width;
|
||||
size_t height;
|
||||
std::vector<uint32_t> buf;
|
||||
UART &uart;
|
||||
std::thread mainloop_thread;
|
||||
std::mutex mtx;
|
||||
std::atomic<bool> render_request;
|
||||
bool is_pressed[256] = {false};
|
||||
};
|
||||
|
||||
class VM {
|
||||
|
|
Loading…
Reference in a new issue