Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ CFLAGS = -Wall -Wextra -fPIC -O3 -std=gnu11 -static
# Add debugging support, with zdis disassembler
#CFLAGS += -DDEBUG_SUPPORT

# Print what programs write to the debug console (dbg_printf) through gui_console_printf /
# gui_console_err_printf without debugging support too (cemu-headless, the autotester)
#CFLAGS += -DDEBUG_CONSOLE_SUPPORT

# Add these flags if your compiler supports it
#CFLAGS += -Wstack-protector -fstack-protector-strong --param=ssp-buffer-size=1 -fsanitize=address,bounds -fsanitize-undefined-trap-on-error

Expand Down
13 changes: 13 additions & 0 deletions core/flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
}
}

uint32_t *flash_line_profile;
void (*flash_line_hook)(uint32_t line);
uint32_t flash_fixed_cycles;

uint32_t flash_touch_cache(uint32_t addr) {
if (unlikely(++cpu.flashTotalAccesses == 0)) {
cpu.flashTotalAccesses = 0x80000000;
Expand All @@ -62,6 +66,12 @@
return 2;
} else {
flash.lastCacheLine = line;
if (unlikely(flash_line_profile != NULL)) {
flash_line_profile[2 * (line & (FLASH_PROFILE_LINES - 1))]++;
}
if (unlikely(flash_line_hook != NULL)) {
flash_line_hook(line);
}
flash_cache_set_t* set = &flash.cacheTags[line & (FLASH_CACHE_SETS - 1)];
uint16_t tag = (uint16_t)(line >> FLASH_CACHE_SET_BITS);
if (likely(set->mru == tag)) {
Expand All @@ -78,6 +88,9 @@
set->lru = set->mru;
set->mru = tag;
cpu.flashCacheMisses++;
if (unlikely(flash_line_profile != NULL)) {
flash_line_profile[2 * (line & (FLASH_PROFILE_LINES - 1)) + 1]++;
}
/* Supposedly this takes from 195-201 cycles, but typically seems to be 196-197 */
cpu.flashDelayCycles += 195;
return 197;
Expand Down Expand Up @@ -203,7 +216,7 @@
addr = 0;
} else if (addr == 2) {
value = 0x16;
while ((1 << value) < mem.flash.size) {

Check warning on line 219 in core/flash.c

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64-Dynamic

comparison of integer expressions of different signedness: ‘int’ and ‘uint32_t’ {aka ‘unsigned int’} [-Wsign-compare]

Check warning on line 219 in core/flash.c

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64

comparison of integer expressions of different signedness: ‘int’ and ‘uint32_t’ {aka ‘unsigned int’} [-Wsign-compare]
value++;
}
} else {
Expand Down
14 changes: 14 additions & 0 deletions core/flash.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ typedef struct flash_state {

extern flash_state_t flash;

/* Optional profiling of serial flash reads for front ends (off when NULL). A line is
* FLASH_CACHE_LINE_SIZE bytes; it is entered when a read follows a read of another line.
* flash_line_profile: 2 counters per line (FLASH_PROFILE_LINES lines), the times it was
* entered then its cache misses. flash_line_hook: called with each line entered, in order
* (for cache simulations and code layout studies). */
#define FLASH_PROFILE_LINES (0x1000000 >> FLASH_CACHE_LINE_BITS)
extern uint32_t *flash_line_profile;
extern void (*flash_line_hook)(uint32_t line);

/* Nonzero: every serial flash read costs this many cycles and the cache is not modeled.
* 10 is what a calculator made before revision M spends per byte (parallel flash with the
* OS's 9 wait states), to compare timings with a revision M ROM. */
extern uint32_t flash_fixed_cycles;

void flash_flush_cache(void);
uint32_t flash_touch_cache(uint32_t addr);

Expand Down
20 changes: 19 additions & 1 deletion core/mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@
}

static uint8_t mem_read_flash_serial(uint32_t addr) {
cpu.cycles += flash_touch_cache(addr);
cpu.cycles += unlikely(flash_fixed_cycles) ? flash_fixed_cycles : flash_touch_cache(addr);
return mem.flash.block[addr & flash.mask];
}

Expand Down Expand Up @@ -812,6 +812,24 @@

/* MMIO <-> Advanced Perphrial Bus */
case 0xE: case 0xF:
#if defined(DEBUG_CONSOLE_SUPPORT) || defined(DEBUG_SUPPORT)
if (addr >= DBGOUT_PORT_RANGE && addr < DBGEXT_PORT) {
static char console_buffer[2][SIZEOF_DBG_BUFFER];
static unsigned console_pos[2];
const int err = addr >= DBGERR_PORT_RANGE;
console_buffer[err][console_pos[err]] = (char)value;
console_pos[err] = (console_pos[err] + 1) % (SIZEOF_DBG_BUFFER - 1);
if (!value) {
if (err) {
gui_console_err_printf("%s", console_buffer[err]);
} else {
gui_console_printf("%s", console_buffer[err]);
}
console_pos[err] = 0;
}
break;
}
#endif
#ifdef DEBUG_SUPPORT
if (debug_get_flags() & DBG_SOFT_COMMANDS) {
if (addr >= DBG_PORT_RANGE) {
Expand Down Expand Up @@ -957,8 +975,8 @@

bool mem_restore(FILE *image) {
bool ret = true;
unsigned int i;

Check warning on line 978 in core/mem.c

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64-Dynamic

unused variable ‘i’ [-Wunused-variable]

Check warning on line 978 in core/mem.c

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64

unused variable ‘i’ [-Wunused-variable]

Check warning on line 978 in core/mem.c

View workflow job for this annotation

GitHub Actions / Build: arm

unused variable 'i' [-Wunused-variable]

Check warning on line 978 in core/mem.c

View workflow job for this annotation

GitHub Actions / Build: intel

unused variable 'i' [-Wunused-variable]
uint8_t *tmp_flash_ptr;

Check warning on line 979 in core/mem.c

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64-Dynamic

unused variable ‘tmp_flash_ptr’ [-Wunused-variable]

Check warning on line 979 in core/mem.c

View workflow job for this annotation

GitHub Actions / Build: ubuntu-22.04 - x64

unused variable ‘tmp_flash_ptr’ [-Wunused-variable]

Check warning on line 979 in core/mem.c

View workflow job for this annotation

GitHub Actions / Build: arm

unused variable 'tmp_flash_ptr' [-Wunused-variable]

Check warning on line 979 in core/mem.c

View workflow job for this annotation

GitHub Actions / Build: intel

unused variable 'tmp_flash_ptr' [-Wunused-variable]
uint8_t *tmp_ram_ptr;

assert(mem.ram.block);
Expand Down
192 changes: 191 additions & 1 deletion tests/autotester/headless_cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* keep one core instance alive without pulling in Qt or another IPC library.
*/

#include <cctype>
#include <cerrno>
#include <chrono>
#include <cstdarg>
Expand All @@ -26,6 +27,7 @@ namespace cemucore
{
extern "C"
{
#include "../../core/flash.h"
#include "../../core/usb/usb.h"

void gui_console_clear() {}
Expand Down Expand Up @@ -54,6 +56,29 @@ namespace cemucore

namespace
{
std::FILE *line_trace;
std::vector<uint32_t> line_trace_buffer;

void lineTraceHook(uint32_t line)
{
line_trace_buffer.push_back(line);
if (line_trace_buffer.size() == 65536) {
std::fwrite(line_trace_buffer.data(), sizeof(uint32_t), line_trace_buffer.size(), line_trace);
line_trace_buffer.clear();
}
}

void lineTraceStop()
{
if (line_trace) {
std::fwrite(line_trace_buffer.data(), sizeof(uint32_t), line_trace_buffer.size(), line_trace);
std::fclose(line_trace);
line_trace = nullptr;
}
line_trace_buffer.clear();
cemucore::flash_line_hook = nullptr;
}

struct options_t {
std::string rom;
std::string image;
Expand All @@ -75,6 +100,17 @@ bool parseUnsigned(const std::string& text, uint32_t& result)
return true;
}

bool parseAddress(const std::string& text, uint32_t& result)
{
char *end;
const unsigned long value = std::strtoul(text.c_str(), &end, 16);
if (!std::isxdigit(static_cast<unsigned char>(text[0])) || *end || value > 0xFFFFFFu) {
return false;
}
result = static_cast<uint32_t>(value);
return true;
}

bool fileExists(const std::string& path)
{
return !path.empty() && std::ifstream(path, std::ios::binary).good();
Expand Down Expand Up @@ -252,7 +288,11 @@ bool runCommand(const std::string& line)
"key <name> [hold-ms]; keys <sequence>; "
"screenshot <bmp-path>; screen-hash; save-state <path>; "
"send-file [ram|archive|auto] <path>; "
"usb <VID:PID|bus#address|disconnect>; reset; status; quit");
"usb <VID:PID|bus#address|disconnect>; reset; status; "
"peek <hex-address> [count]; poke <hex-address> <hex-bytes>; "
"keydown <name>; keyup <name>; regs; stats; lcd-dma <0|1>; "
"line-profile <on|off|save <path>>; line-trace <on <path>|off>; "
"flash-cycles <n>; quit");
return true;
}
if (command == "run") {
Expand Down Expand Up @@ -410,6 +450,155 @@ bool runCommand(const std::string& line)
return true;
}

if (command == "peek") {
std::string address_text;
std::string count_text;
uint32_t address;
uint32_t count = 1;
if (!(input >> address_text) || !parseAddress(address_text, address) ||
((input >> count_text) && (!parseUnsigned(count_text, count) || count == 0 || count > 4096))) {
respond("ERR usage: peek <hex-address> [count 1-4096]");
} else {
std::string bytes;
char hex[3];
for (uint32_t i = 0; i < count; ++i) {
std::snprintf(hex, sizeof hex, "%02X", cemucore::mem_peek_byte((address + i) & 0xFFFFFFu));
bytes += hex;
}
respond("OK peek " + bytes);
}
return true;
}
if (command == "poke") {
std::string address_text;
std::string bytes;
uint32_t address;
if (!(input >> address_text >> bytes) || !parseAddress(address_text, address) || bytes.size() % 2 ||
bytes.find_first_not_of("0123456789abcdefABCDEF") != std::string::npos) {
respond("ERR usage: poke <hex-address> <hex-bytes>");
} else {
for (size_t i = 0; i < bytes.size(); i += 2) {
cemucore::mem_poke_byte((address + static_cast<uint32_t>(i / 2)) & 0xFFFFFFu,
static_cast<uint8_t>(std::strtoul(bytes.substr(i, 2).c_str(), nullptr, 16)));
}
respond("OK poke " + std::to_string(bytes.size() / 2));
}
return true;
}
if (command == "keydown" || command == "keyup") {
std::string name;
autotester::key_coord_t coord{};
input >> name;
if (!autotester::keyCoordForName(name, coord)) {
respond("ERR unknown key " + name);
} else {
cemucore::emu_keypad_event(coord.y, coord.x, command == "keydown");
respond("OK " + command + " " + name);
}
return true;
}
if (command == "regs") {
const auto& r = cemucore::cpu.registers;
char text[192];
std::snprintf(text, sizeof text,
"OK regs pc=%06X sp=%06X af=%04X bc=%06X de=%06X hl=%06X ix=%06X iy=%06X adl=%d halted=%d",
static_cast<unsigned>(r.PC), static_cast<unsigned>(r.SPL), static_cast<unsigned>(r.AF),
static_cast<unsigned>(r.BC), static_cast<unsigned>(r.DE), static_cast<unsigned>(r.HL),
static_cast<unsigned>(r.IX), static_cast<unsigned>(r.IY),
static_cast<int>(cemucore::cpu.ADL), static_cast<int>(cemucore::cpu.halted));
respond(text);
return true;
}
if (command == "stats") {
char text[192];
std::snprintf(text, sizeof text,
"OK stats cycles=%llu halted=%llu dma=%llu flash-reads=%lu flash-misses=%lu flash-delay=%lld",
static_cast<unsigned long long>(cemucore::cpu.baseCycles + cemucore::cpu.cycles),
static_cast<unsigned long long>(cemucore::cpu.haltCycles),
static_cast<unsigned long long>(cemucore::cpu.dmaCycles),
static_cast<unsigned long>(cemucore::cpu.flashTotalAccesses),
static_cast<unsigned long>(cemucore::cpu.flashCacheMisses),
static_cast<long long>(cemucore::cpu.flashDelayCycles));
respond(text);
return true;
}
if (command == "lcd-dma") {
std::string value;
if (!(input >> value) || (value != "0" && value != "1")) {
respond("ERR usage: lcd-dma <0|1>");
} else {
cemucore::emu_set_lcd_dma(value == "1");
respond("OK lcd-dma " + value);
}
return true;
}

if (command == "line-profile") {
std::string what;
input >> what;
const size_t counters = 2 * FLASH_PROFILE_LINES;
if (what == "on") {
if (!cemucore::flash_line_profile) {
cemucore::flash_line_profile = static_cast<uint32_t *>(std::calloc(counters, sizeof(uint32_t)));
} else {
std::memset(cemucore::flash_line_profile, 0, counters * sizeof(uint32_t));
}
respond(cemucore::flash_line_profile ? "OK line-profile on" : "ERR out of memory");
} else if (what == "save" && cemucore::flash_line_profile) {
std::string path;
std::getline(input >> std::ws, path);
std::FILE *file = path.empty() ? nullptr : std::fopen(path.c_str(), "wb");
if (!file) {
respond("ERR cannot write " + path);
} else {
const bool ok = std::fwrite(cemucore::flash_line_profile, sizeof(uint32_t), counters, file) == counters;
std::fclose(file);
respond(ok ? "OK line-profile save" : "ERR cannot write " + path);
}
} else if (what == "off") {
std::free(cemucore::flash_line_profile);
cemucore::flash_line_profile = nullptr;
respond("OK line-profile off");
} else {
respond("ERR usage: line-profile <on|off|save <path>> (save needs on)");
}
return true;
}
if (command == "line-trace") {
std::string what;
input >> what;
if (what == "on") {
std::string path;
std::getline(input >> std::ws, path);
std::FILE *file = path.empty() ? nullptr : std::fopen(path.c_str(), "wb");
if (!file) {
respond("ERR cannot write " + path);
} else {
lineTraceStop();
line_trace = file;
cemucore::flash_line_hook = lineTraceHook;
respond("OK line-trace on");
}
} else if (what == "off") {
lineTraceStop();
respond("OK line-trace off");
} else {
respond("ERR usage: line-trace <on <path>|off>");
}
return true;
}
if (command == "flash-cycles") {
std::string value;
uint32_t cycles;
if (!(input >> value) || !parseUnsigned(value, cycles) || cycles > 1000) {
respond("ERR usage: flash-cycles <n> (0: the revision M cache)");
} else {
cemucore::flash_fixed_cycles = cycles;
respond("OK flash-cycles " + value);
}
return true;
}

respond("ERR unknown command " + command);
return true;
}
Expand Down Expand Up @@ -442,6 +631,7 @@ int main(int argc, char **argv)
std::string line;
while (std::getline(std::cin, line) && runCommand(line)) {}

lineTraceStop();
cemucore::emu_exit();
cemucore::asic_free();
return EXIT_SUCCESS;
Expand Down
31 changes: 31 additions & 0 deletions tests/autotester/headless_protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,40 @@ Supported commands:
emulated host controller, or disconnects the current USB peer.
- `reset` resets the emulated calculator.
- `status` reports the device, ASIC revision, Python flag, and run rate.
- `peek <hex-address> [count]` returns `count` bytes (1 to 4096, default 1) read
from the address space without side effects, as hex: `OK peek 3F00C9`.
- `poke <hex-address> <hex-bytes>` writes bytes (flash included, ignoring its
protection), for example `poke D0008E 01`.
- `keydown <name>` and `keyup <name>` press and release a key separately, without
running the emulator, to inspect the calculator while a key is held.
- `regs` reports the CPU registers (PC, SPL, AF, BC, DE, HL, IX, IY), ADL and
whether the CPU is halted. With a stack of IX frames, the caller's IX is at
(IX) and the return address at (IX+3): enough to see where a program hangs.
- `stats` reports the cycle counters since power-on: total and halted cycles,
cycles taken by the LCD's DMA, serial flash reads and cache misses (revision M
and later: an 8 KB cache, about 197 cycles per miss) and the flash wait cycles.
- `lcd-dma <0|1>` emulates the LCD's DMA, which takes RAM cycles from the CPU
while the screen refreshes (off by default here), or turns it off.
- `line-profile on` counts, for each 32-byte line of flash, how many times the
CPU entered it (a read after a read of another line) and how many of those
missed the serial flash cache; `line-profile save <path>` writes the counters
(2 per line: entries then misses, 32-bit, host byte order, 524288 lines for
the 16 MB address space); `line-profile off` stops counting.
- `line-trace on <path>` writes every line entered, in order, as 32-bit line
numbers (address / 32, host byte order), for cache simulations and code layout
studies; `line-trace off` closes the file.
- `flash-cycles <n>` makes every serial flash read cost n cycles without the
cache; 10 is close to a calculator made before revision M (parallel flash with
the OS's 9 wait states), to compare a program's speed on older hardware with a
revision M ROM. `flash-cycles 0` restores the cache.
- `help` lists commands.
- `quit` exits cleanly.

If the core was built with `-DDEBUG_CONSOLE_SUPPORT` or `-DDEBUG_SUPPORT` (for example `make
CPPFLAGS=-DDEBUG_CONSOLE_SUPPORT` in `core/`), what the calculator's programs print
to the debug console (the toolchain's `dbg_printf`) is written to standard error, as
the core's diagnostics are.

For example:

```text
Expand Down
Loading