Implement sending keypresses to UART with support for queueing

This commit is contained in:
Konstantin Nazarov 2024-12-22 22:19:41 +00:00
parent 616f70bfa4
commit ca5c7e3bd3
Signed by: knazarov
GPG key ID: 4CFE0A42FA409C22
3 changed files with 146 additions and 25 deletions

View file

@ -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) {

View file

@ -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() {

View file

@ -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 {