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.h>
|
||||||
|
#include <SDL2/SDL_keycode.h>
|
||||||
#include <SDL2/SDL_render.h>
|
#include <SDL2/SDL_render.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
@ -14,29 +15,26 @@ SDL_Texture* texture = nullptr;
|
||||||
#define FB_SCALE 2
|
#define FB_SCALE 2
|
||||||
#define FB_OFFSET 0x10000
|
#define FB_OFFSET 0x10000
|
||||||
|
|
||||||
Framebuffer::Framebuffer()
|
Framebuffer::Framebuffer(UART& uart)
|
||||||
: Device(FRAMEBUFFER_ADDR, FB_WIDTH * FB_HEIGHT + FB_OFFSET),
|
: Device(FRAMEBUFFER_ADDR, FB_WIDTH * FB_HEIGHT + FB_OFFSET),
|
||||||
width(FB_WIDTH),
|
width(FB_WIDTH),
|
||||||
height(FB_HEIGHT),
|
height(FB_HEIGHT),
|
||||||
buf(FB_WIDTH * FB_HEIGHT, 0) {
|
buf(FB_WIDTH * FB_HEIGHT, 0),
|
||||||
|
uart(uart) {
|
||||||
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||||
throw std::runtime_error("Failed to initialize SDL");
|
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,
|
window = SDL_CreateWindow("rve Framebuffer", SDL_WINDOWPOS_CENTERED,
|
||||||
SDL_WINDOWPOS_CENTERED, width, height, 0);
|
SDL_WINDOWPOS_CENTERED, width, height, 0);
|
||||||
if (!window) {
|
if (!window) {
|
||||||
throw std::runtime_error("Failed to create SDL 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);
|
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
|
||||||
if (!renderer) {
|
if (!renderer) {
|
||||||
throw std::runtime_error("Failed to create SDL renderer");
|
throw std::runtime_error("Failed to create SDL renderer");
|
||||||
|
@ -49,7 +47,60 @@ Framebuffer::Framebuffer()
|
||||||
|
|
||||||
SDL_ShowWindow(window);
|
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() {
|
void Framebuffer::draw() {
|
||||||
|
@ -60,11 +111,10 @@ void Framebuffer::draw() {
|
||||||
buf[i] = cmap;
|
buf[i] = cmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
SDL_UpdateTexture(texture, nullptr, &buf[0],
|
SDL_UpdateTexture(texture, nullptr, &buf[0],
|
||||||
width * 4); // Update the SDL texture
|
width * 4); // Update the SDL texture
|
||||||
SDL_RenderClear(renderer);
|
render_request = true;
|
||||||
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
|
||||||
SDL_RenderPresent(renderer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Framebuffer::write_mem_u8(uint8_t* src, size_t addr) {
|
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)
|
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 = std::vector<Device*>(4, 0);
|
||||||
devices[0] = &ram;
|
devices[0] = &ram;
|
||||||
devices[1] = &uart;
|
devices[1] = &uart;
|
||||||
|
@ -30,7 +30,18 @@ void VM::setreg(size_t regnum, uint32_t value) {
|
||||||
registers[regnum] = 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) {
|
if (addr < UART_ADDR || addr + 1 > UART_ADDR + 8) {
|
||||||
throw std::runtime_error(std::string("Memory access out of bounds: ") +
|
throw std::runtime_error(std::string("Memory access out of bounds: ") +
|
||||||
std::format("{:x}", addr));
|
std::format("{:x}", addr));
|
||||||
|
@ -38,12 +49,16 @@ void UART::read_mem_u8(uint8_t* dst, size_t addr) {
|
||||||
addr -= UART_ADDR;
|
addr -= UART_ADDR;
|
||||||
switch (addr) {
|
switch (addr) {
|
||||||
case UART_RXDATA:
|
case UART_RXDATA:
|
||||||
*dst = 0x0;
|
case UART_RXDATA + 1:
|
||||||
break;
|
case UART_RXDATA + 2:
|
||||||
case UART_RXDATA + 3:
|
case UART_RXDATA + 3: {
|
||||||
// receiver is always empty for now
|
uint32_t val = 0x80000000;
|
||||||
*dst = 0x80;
|
if (rx_queue.size() > 0) {
|
||||||
|
val = rx_queue.front();
|
||||||
|
}
|
||||||
|
*dst = ((uint8_t*)&val)[addr - UART_RXDATA];
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
*dst = 0;
|
*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) {
|
void UART::write_mem_u32(uint32_t* src, size_t addr) {
|
||||||
uint8_t* s = (uint8_t*)src;
|
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) {
|
void UART::read_mem_u32(uint32_t* dst, size_t addr) {
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
uint8_t* d = (uint8_t*)dst;
|
uint8_t* d = (uint8_t*)dst;
|
||||||
|
|
||||||
for (size_t i = 0; i < 4; i++) {
|
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() {
|
void Timer::update() {
|
||||||
|
|
28
src/vm.hpp
28
src/vm.hpp
|
@ -2,8 +2,11 @@
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class EbreakException : std::exception {};
|
class EbreakException : std::exception {};
|
||||||
|
@ -72,15 +75,28 @@ class Device {
|
||||||
|
|
||||||
class UART final : public Device {
|
class UART final : public Device {
|
||||||
public:
|
public:
|
||||||
UART() : Device(UART_ADDR, 8) {}
|
UART();
|
||||||
|
|
||||||
virtual void write_mem_u8(uint8_t *src, size_t addr);
|
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 write_mem_u32(uint32_t *src, size_t addr);
|
||||||
|
|
||||||
virtual void read_mem_u8(uint8_t *dst, 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);
|
virtual void read_mem_u32(uint32_t *dst, size_t addr);
|
||||||
|
|
||||||
|
void set_rx(uint8_t value);
|
||||||
|
|
||||||
private:
|
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 {
|
enum Registers {
|
||||||
UART_TXDATA = 0x00,
|
UART_TXDATA = 0x00,
|
||||||
UART_RXDATA = 0x04,
|
UART_RXDATA = 0x04,
|
||||||
|
@ -88,6 +104,7 @@ class UART final : public Device {
|
||||||
|
|
||||||
// Line Status Register bits
|
// Line Status Register bits
|
||||||
enum LSRBits { LSR_TRANSMITTER_EMPTY = 0x20 };
|
enum LSRBits { LSR_TRANSMITTER_EMPTY = 0x20 };
|
||||||
|
std::queue<uint8_t> rx_queue;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RAM final : public Device {
|
class RAM final : public Device {
|
||||||
|
@ -107,7 +124,7 @@ class Timer final : public Device {
|
||||||
|
|
||||||
class Framebuffer final : public Device {
|
class Framebuffer final : public Device {
|
||||||
public:
|
public:
|
||||||
Framebuffer();
|
Framebuffer(UART &uart);
|
||||||
virtual void write_mem_u8(uint8_t *src, size_t addr);
|
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_u16(uint16_t *src, size_t addr);
|
||||||
virtual void write_mem_u32(uint32_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:
|
protected:
|
||||||
void draw();
|
void draw();
|
||||||
|
|
||||||
|
void mainloop();
|
||||||
|
|
||||||
size_t width;
|
size_t width;
|
||||||
size_t height;
|
size_t height;
|
||||||
std::vector<uint32_t> buf;
|
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 {
|
class VM {
|
||||||
|
|
Loading…
Reference in a new issue