mbv: now with functioning timer interrupts!

And a few other nice things.
The bootloader now has an embedded wozmon!
If you know its offset, you can jump to it from the app.
This commit is contained in:
Paul Mathieu 2025-06-10 23:46:43 -07:00
parent fa6ae7b667
commit 3db383d461
17 changed files with 485 additions and 93 deletions

View File

@ -10,6 +10,10 @@ helloworld: ## Build the helloworld app in docker
wozmon: ## Build the wozmon app in docker
docker build -o . --target export --build-arg TARGET=wozmon.bin .
.PHONY: timer
timer: ## Build the timer app in docker
docker build -o . --target export --build-arg TARGET=timer.bin .
.PHONY: dev-image
dev-image:
docker build -t mbv-dev --target dev .

View File

@ -1,6 +1,6 @@
MEMORY
{
RAM (rwx) : ORIGIN = 0x00000800, LENGTH = 14336
RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 0x10000000
}
_vector_table = 0x0;
@ -37,6 +37,6 @@ SECTIONS
_heap_begin = .;
_initial_stack_pointer = 16384;
_heap_end = _initial_stack_pointer - 1024;
_initial_stack_pointer = 0x90000000;
_heap_end = 0x8f000000; /* leave 1M for the stack */
}

View File

@ -1,43 +0,0 @@
MEMORY
{
/* RAM (rwx) : ORIGIN = 0x00000800, LENGTH = 14336 */
RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 0x10000000
}
_vector_table = 0x0;
SECTIONS
{
.text :
{
_text_begin = .;
KEEP(*(.start))
*(.text*)
_text_end = .;
*(.rodata*)
} > RAM
.bss (NOLOAD) :
{
_bss_begin = .;
*(.bss*)
*(COMMON)
_bss_end = .;
} > RAM
.data :
{
*(.data*)
__exidx_start = .;
*(.exidx*)
__exidx_end = .;
} > RAM
_heap_begin = .;
_initial_stack_pointer = 16384;
_heap_end = _initial_stack_pointer - 1024;
}

View File

@ -1,10 +1,12 @@
#include <cstdint>
#include "pol0.h"
struct Gpio {
volatile uint32_t data;
};
#define gpio0 ((Gpio*)0x40000000)
#define gpio0 ((Gpio*)GPIO0_BASE)
void sleep(int ms) {
for (int m = 0; m < ms; m++) {

47
mbv/apps/timer/timer.cc Normal file
View File

@ -0,0 +1,47 @@
#include <cstdint>
#include "intc.h"
#include "interrupts.h"
#include "pol0.h"
#include "timer.h"
namespace {
struct Gpio {
volatile uint32_t data;
};
Gpio* leds = reinterpret_cast<Gpio*>(GPIO0_BASE);
Timer* timer;
}
void Timer0Isr() {
static int counter = 0;
leds->data = counter++;
timer->Pet();
timer->ClearInterrupt();
}
void SetupTimer() {
timer = Timer::Instance(TIMER0_BASE);
timer->SetupAsWdt(100'000'000);
timer->EnableT1();
SetIsr(TIMER0_IRQN, Timer0Isr);
SetIrqEnabled(TIMER0_IRQN, true);
EnableInterrupts();
}
int main() {
leds->data = 0xa0;
SetupTimer();
SetExternalInterruptHandler(InterruptHandler);
EnableExternalInterrupts();
leds->data = 0xa1;
BiosWozmon();
}

View File

@ -125,7 +125,6 @@ int main() {
gpio0->data = 1;
uint32_t cur_addr = 0;
uint32_t cur_data = 0;
bool writing = false;
char inbuf[64] = {};
char* inptr = inbuf;

View File

@ -1,19 +1,23 @@
#include <cstdint>
#include "pol0.h"
#include "xuartlite.h"
uint8_t UartRead();
void UartWrite(uint8_t);
struct Gpio {
volatile uint32_t data;
};
#define gpio0 ((Gpio*)0x40000000)
#define gpio0 ((Gpio*)GPIO0_BASE)
namespace {
constexpr uintptr_t kUart0BaseAddress = 0x40600000;
XUartLite uart0_inst;
XUartLite_Config uart0_config = {
.DeviceId = 0,
.RegBaseAddr = kUart0BaseAddress,
.RegBaseAddr = UART0_BASE,
.BaudRate = 115200,
.UseParity = false,
.DataBits = 8,
@ -25,15 +29,6 @@ void InitUarts() {
XUartLite_CfgInitialize(uart0, &uart0_config, uart0_config.RegBaseAddr);
}
uint8_t UartRead() {
uint8_t c;
while (XUartLite_Recv(uart0, &c, 1) < 1) {
}
return c;
}
void UartWrite(uint8_t c) { XUartLite_Send(uart0, &c, 1); }
uint32_t UartRead32() {
uint32_t val = 0;
@ -45,8 +40,21 @@ uint32_t UartRead32() {
return val;
}
} // namespace
uint8_t UartRead() {
uint8_t c;
while (XUartLite_Recv(uart0, &c, 1) < 1) {
}
return c;
}
void UartWrite(uint8_t c) {
XUartLite_Send(uart0, &c, 1);
while (XUartLite_IsSending(uart0)) {}
}
int main() {
gpio0->data = 1;

View File

@ -1,6 +1,6 @@
MEMORY
{
BLRAM (rwx) : ORIGIN = 0x00000000, LENGTH = 2048
BLRAM (rwx) : ORIGIN = 0x00000000, LENGTH = 0x1000
}
SECTIONS
@ -30,5 +30,5 @@ SECTIONS
__exidx_end = .;
} > BLRAM
_initial_stack_pointer = 2048;
_initial_stack_pointer = 0x1000;
}

132
mbv/bootloader/wozmon.cc Normal file
View File

@ -0,0 +1,132 @@
#include <cstdint>
uint8_t UartRead();
void UartWrite(uint8_t);
namespace {
void Jump(uint32_t addr) {
auto jump = reinterpret_cast<void (*)()>(addr);
jump();
}
constexpr uint8_t kBackspace = 0x7f;
constexpr uint8_t kOtherBackspace = 0x08;
uint8_t ReadHexNibble(uint8_t c) {
// lowercase only
if (c <= '9') {
return c - '0';
}
return 10 + (c - 'a');
}
uint32_t ReadHex(const char* buf) {
uint32_t out = 0;
while (*buf == ' ') {
buf++;
}
for (int i = 0; i < 8; i++) {
uint8_t c = ReadHexNibble(*buf);
if (c > 0xf) {
break;
}
out = (out << 4) + c;
buf++;
}
return out;
}
void WriteHexNibble(uint8_t c) {
if (c > 9) {
UartWrite('a' + c - 10);
} else {
UartWrite('0' + c);
}
}
void UartWriteUint32(uint32_t a) {
for (int i = 0; i < 8; i++) {
WriteHexNibble((a >> 28) & 0xf);
a <<= 4;
}
}
void UartWriteUint8(uint8_t a) {
WriteHexNibble(a >> 4);
WriteHexNibble(a & 0xf);
}
void UartDump(uint32_t addr, int count) {
for (int i = 0; i < count; i++) {
UartWrite(' ');
UartWriteUint8(*reinterpret_cast<uint8_t*>(addr + i));
}
}
void DumpHex(uint32_t addr) {
addr &= 0xfffffffc;
UartWriteUint32(addr);
UartWrite(':');
UartDump(addr, 4);
UartWrite('\r');
UartWrite('\n');
}
int FindChar(const char* buf, uint8_t c) {
int found = 0;
while (*buf) {
if (*buf == c) {
return found;
}
found++;
buf++;
}
return -1;
}
} // namespace
__attribute__((used))
void wozmon() {
uint32_t cur_addr = 0;
uint32_t cur_data = 0;
char inbuf[64] = {};
char* inptr = inbuf;
while (1) {
uint8_t c = UartRead();
UartWrite(c); // echo
if (c == '\r') {
*inptr = 0;
if (inptr == inbuf) {
cur_addr += 4;
} else if (FindChar(inbuf, 'r') >= 0) {
Jump(cur_addr);
} else {
cur_addr = ReadHex(inbuf);
UartWrite('\n');
}
DumpHex(cur_addr);
int assigned = FindChar(inbuf, ':');
if (assigned >= 0) {
cur_data = ReadHex(inbuf + assigned + 1);
*(reinterpret_cast<uint32_t*>(cur_addr)) = cur_data;
}
inptr = inbuf;
} else if (c == kBackspace) {
inptr--;
if (inptr < inbuf) {
inptr = inbuf;
continue;
}
UartWrite(kOtherBackspace);
UartWrite(' ');
UartWrite(kOtherBackspace);
} else {
*inptr++ = c;
}
}
}

38
mbv/configure vendored
View File

@ -18,6 +18,7 @@ hostcflags = "-g -std=c++20 -fprofile-instr-generate -fcoverage-mapping"
hostlibs = "-lgtest -lgmock -lgtest_main"
hostldflags = "-fprofile-instr-generate -fcoverage-mapping"
include_dirs = [
"hal",
"hal/uart",
"hal/lib/common",
]
@ -31,7 +32,7 @@ common_flags = [
"-Wall",
"-Wextra",
"-flto",
"-march=rv32i",
"-march=rv32i_zicsr",
"-ffunction-sections",
"-Oz",
]
@ -44,7 +45,7 @@ ldflags = [
"-Wl,--gc-sections",
"-Wl,--print-memory-usage",
"-flto",
"-march=rv32i",
"-march=rv32i_zicsr",
]
@ -244,6 +245,8 @@ def make_coverage(binaries):
hal = source_set("hal", [
"hal/intc.cc",
"hal/interrupts.cc",
"hal/start.cc",
"hal/lib/common/xil_assert.c",
"hal/uart/xuartlite.c",
@ -252,9 +255,6 @@ hal = source_set("hal", [
])
bootloader = source_set("bootloader", glob.glob("./bootloader/**/*.cc", recursive=True))
helloworld = source_set("helloworld", glob.glob("./apps/helloworld/**/*.cc", recursive=True))
wozmon = source_set("wozmon", glob.glob("./apps/wozmon/**/*.cc", recursive=True))
bootloader_image = build_image(
bootloader,
dependencies=[hal],
@ -262,22 +262,22 @@ bootloader_image = build_image(
linker_script="bootloader/bootloader.ld",
)
helloworld_image = build_image(
helloworld,
dependencies=[hal],
elf_out="out/helloworld.elf",
bin_out="out/helloworld.bin",
)
def app_image(app):
return build_image(
source_set(app, glob.glob(f"./apps/{app}/**/*.cc", recursive=True)),
dependencies=[hal],
elf_out=f"out/{app}.elf",
bin_out=f"out/{app}.bin",
)
wozmon_image = build_image(
wozmon,
dependencies=[hal],
elf_out="out/wozmon.elf",
bin_out="out/wozmon.bin",
linker_script = "apps/fromddr.ld",
)
all = [
build_source_set(hal),
bootloader_image,
all = [build_source_set(hal), bootloader_image, helloworld_image, wozmon_image]
app_image("helloworld"),
app_image("wozmon"),
app_image("timer"),
]
def parse_args():

62
mbv/hal/intc.cc Normal file
View File

@ -0,0 +1,62 @@
#include "intc.h"
#include "pol0.h"
namespace {
struct IntC {
volatile uint32_t ISR;
volatile uint32_t IPR;
volatile uint32_t IER;
volatile uint32_t IAR;
volatile uint32_t SIE;
volatile uint32_t CIE;
volatile uint32_t IVR;
volatile uint32_t MER;
volatile uint32_t IMR;
volatile uint32_t ILR;
// the rest is not enabled
};
IntC* intc = reinterpret_cast<IntC*>(INTC_BASE);
Isr isrs[NIRQ] = {};
}
bool SetIrqEnabled(uint8_t irqn, bool enabled) {
uint32_t mask = 1 << irqn;
uint32_t ier = intc->IER;
bool was_enabled = (ier & (~mask)) > 0;
if (enabled) {
intc->IER = ier | mask;
} else {
intc->IER = ier & (~mask);
}
return was_enabled;
}
void SetIsr(uint8_t irqn, Isr isr) {
isrs[irqn] = isr;
}
void EnableInterrupts() {
intc->MER = 0x3;
}
void InterruptHandler() {
uint32_t ipr = intc->IPR;
for (int i = 0; i < NIRQ; i++) {
uint32_t mask = 1 << i;
if ((ipr & mask) > 0) {
// interrupt pending
if (isrs[i] != nullptr) {
isrs[i]();
}
intc->IAR = mask; // ack
}
}
}

16
mbv/hal/intc.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
using Isr = void(*)(void);
/// Returns: true if the IRQ was previously enabled
bool SetIrqEnabled(uint8_t irqn, bool enabled);
void SetIsr(uint8_t irqn, Isr isr);
// Call this once to enable all HW interrupts
void EnableInterrupts();
// Feed this to the CPU's interrupt handler
void InterruptHandler();

52
mbv/hal/interrupts.cc Normal file
View File

@ -0,0 +1,52 @@
#include <cstdint>
#include "interrupts.h"
namespace {
constexpr uint32_t kMstatusMieMask = 1 << 3;
constexpr uint32_t kMieExternalInterruptMask = 1 << 11;
constexpr uint32_t kExternalInterruptCause = 0x0b;
constexpr uint32_t kInterruptCauseMask = 0xff;
Isr external_handler = nullptr;
__attribute__((interrupt))
void TrapHandler() {
uint32_t mcause;
uint32_t mip;
asm volatile("csrr %0, mcause" : "=r"(mcause));
asm volatile("csrr %0, mip" : "=r"(mip));
// check for external interrupt
if ((mcause & kInterruptCauseMask) == kExternalInterruptCause) {
if (external_handler != nullptr) {
external_handler();
}
mip &= ~(kMieExternalInterruptMask);
asm volatile("csrw mip, %0" :: "r"(mip));
}
}
}
void SetExternalInterruptHandler(Isr handler) {
external_handler = handler;
}
void EnableExternalInterrupts() {
uint32_t mstatus;
uint32_t mie;
Isr trap = TrapHandler;
asm volatile("csrr %0, mstatus" : "=r"(mstatus));
asm volatile("csrr %0, mie" : "=r"(mie));
asm volatile("csrw mtvec, %0" :: "r"(trap));
mie |= kMieExternalInterruptMask;
asm volatile("csrw mie, %0" :: "r"(mie));
mstatus |= kMstatusMieMask;
asm volatile("csrw mstatus, %0" :: "r"(mstatus));
}

6
mbv/hal/interrupts.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
using Isr = void(*)();
void SetExternalInterruptHandler(Isr handler);
void EnableExternalInterrupts();

18
mbv/hal/pol0.h Normal file
View File

@ -0,0 +1,18 @@
// Platform definitions for pol0
// LED output
#define GPIO0_BASE (0x40000000)
// /dev/ttyUSB1
#define UART0_BASE (0x40600000)
// Interrupt controller
#define INTC_BASE (0x41200000)
// It's uh.. a timer.
#define TIMER0_BASE (0x41c00000)
// IRQs
#define UART0_IRQN (0)
#define TIMER0_IRQN (1)
#define NIRQ (2)

69
mbv/hal/timer.h Normal file
View File

@ -0,0 +1,69 @@
#pragma once
#include <cstdint>
struct TimerControl {
union {
struct {
uint32_t MDT0 : 1;
uint32_t UDT0 : 1;
uint32_t GENT0 : 1;
uint32_t CAPT0 : 1;
uint32_t ARHT0 : 1;
uint32_t LOAD0 : 1;
uint32_t ENIT0 : 1;
uint32_t ENT0 : 1;
uint32_t T0INT : 1;
uint32_t PWMA0 : 1;
uint32_t ENALL : 1;
uint32_t CASC : 1;
uint32_t reserved : 20;
};
uint32_t raw;
};
};
struct Timer {
volatile TimerControl TCSR0;
volatile uint32_t TLR0;
volatile uint32_t TCR0;
uint32_t _reserved;
volatile TimerControl TCSR1;
volatile uint32_t TLR1;
volatile uint32_t TCR1;
void EnableT1() {
TCSR1.ARHT0 = 1;
TCSR1.ENT0 = 1;
}
uint32_t GetT1Ticks() { return TCR1; }
void SetupAsWdt(uint32_t timeout_ticks) {
TLR0 = timeout_ticks;
TCSR0.LOAD0 = 1; // reset counter
TCSR0.UDT0 = 1; // count backwards from the load value
TCSR0.ENIT0 = 1; // enable interrupt
TCSR0.LOAD0 = 0; // allow counter to run
TCSR0.ENT0 = 1; // enable timer
}
void Pet() {
TCSR0.ENT0 = 0;
TCSR0.LOAD0 = 1;
TCSR0.LOAD0 = 0;
TCSR0.ENT0 = 1;
}
void ClearInterrupt() {
TCSR0.T0INT = 0;
}
static Timer* Instance(uint32_t base) {
return reinterpret_cast<Timer*>(base);
}
};

View File

@ -1,6 +1,8 @@
import argparse
import serial
import struct
import sys
import threading
import time
offset = 0x80000000
@ -29,28 +31,46 @@ def write(s, offset, dat):
def jump(s, offset):
cmd = struct.pack('<cI', b'j', offset)
print(f'Jumping to 0x{offset:04x}')
s.write(cmd)
def stream_logs(s):
while True:
dat = s.read()
if not dat:
continue
sys.stdout.buffer.write(dat.replace(b'\r', b''))
sys.stdout.buffer.flush()
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("binary")
parser.add_argument("--monitor", action="store_true",
help="wait for and display program serial output")
return parser.parse_args()
def main():
binfile = sys.argv[1]
with open(binfile, 'rb') as f:
args = parse_args()
with open(args.binary, 'rb') as f:
dat = f.read()
s = serial.Serial(tty, baud, timeout=1)
write(s, offset, dat)
jump(s, offset)
last_dat = time.time()
while True:
dat = s.read()
if not dat:
if time.time() - last_dat > 1.0:
print('Data timeout')
break
continue
last_dat = time.time()
sys.stdout.buffer.write(dat)
if args.monitor:
t = threading.Thread(target=lambda: stream_logs(s), daemon=True)
t.start()
try:
while True:
dat = input("") + '\r'
s.write(dat.encode())
except KeyboardInterrupt:
print("Bye.")
if __name__ == "__main__":