diff --git a/src/framebuffer.cpp b/src/framebuffer.cpp index 4c20425..8c34c6b 100644 --- a/src/framebuffer.cpp +++ b/src/framebuffer.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -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(key - SDLK_a + 'a'); + } else if (key >= SDLK_0 && key <= SDLK_9) { + asciiValue = static_cast(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 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 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) { diff --git a/src/vm.cpp b/src/vm.cpp index 78c8e44..579c8e5 100644 --- a/src/vm.cpp +++ b/src/vm.cpp @@ -14,7 +14,7 @@ inline int32_t sign_extend(int32_t value, int bits) { } VM::VM(const std::vector& 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(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 lock(mtx); + read_mem_u8_impl(dst, addr); + clear_rx(); +} + +void UART::read_mem_u16(uint16_t* dst, size_t addr) { + std::lock_guard 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 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 lock(mtx); + + rx_queue.push(value); } void Timer::update() { diff --git a/src/vm.hpp b/src/vm.hpp index 824c99f..d1a159c 100644 --- a/src/vm.hpp +++ b/src/vm.hpp @@ -2,8 +2,11 @@ #include #include +#include +#include #include #include +#include #include 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 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 buf; + UART &uart; + std::thread mainloop_thread; + std::mutex mtx; + std::atomic render_request; + bool is_pressed[256] = {false}; }; class VM {