commit
50e9905415
30 changed files with 919 additions and 376 deletions
|
@ -13,6 +13,7 @@ add_executable(
|
|||
console_ncurses.cpp
|
||||
console_posix.cpp
|
||||
cpu.cpp
|
||||
debugger.cpp
|
||||
error.cpp
|
||||
main.cpp
|
||||
memory.cpp
|
||||
|
|
|
@ -10,22 +10,17 @@
|
|||
|
||||
#define NEOPIXELS_PIN 25
|
||||
|
||||
console_esp32::console_esp32(std::atomic_bool *const terminate, bus *const b) :
|
||||
console(terminate, b)
|
||||
console_esp32::console_esp32(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b) :
|
||||
console(terminate, interrupt_emulation, b)
|
||||
{
|
||||
th = new std::thread(std::ref(*this));
|
||||
}
|
||||
|
||||
console_esp32::~console_esp32()
|
||||
{
|
||||
if (th) {
|
||||
th->join();
|
||||
|
||||
delete th;
|
||||
}
|
||||
stop_thread();
|
||||
}
|
||||
|
||||
int console_esp32::wait_for_char(const short timeout)
|
||||
int console_esp32::wait_for_char_ll(const short timeout)
|
||||
{
|
||||
for(short i=0; i<timeout / 10; i++) {
|
||||
if (Serial.available())
|
||||
|
@ -42,6 +37,13 @@ void console_esp32::put_char_ll(const char c)
|
|||
Serial.print(c);
|
||||
}
|
||||
|
||||
void console_esp32::put_string_lf(const std::string & what)
|
||||
{
|
||||
put_string(what);
|
||||
|
||||
put_string("\r\n");
|
||||
}
|
||||
|
||||
void console_esp32::resize_terminal()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -6,14 +6,16 @@
|
|||
class console_esp32 : public console
|
||||
{
|
||||
protected:
|
||||
int wait_for_char(const short timeout) override;
|
||||
int wait_for_char_ll(const short timeout) override;
|
||||
|
||||
void put_char_ll(const char c) override;
|
||||
|
||||
public:
|
||||
console_esp32(std::atomic_bool *const terminate, bus *const b);
|
||||
console_esp32(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b);
|
||||
virtual ~console_esp32();
|
||||
|
||||
void put_string_lf(const std::string & what) override;
|
||||
|
||||
void resize_terminal() override;
|
||||
|
||||
void refresh_virtual_terminal() override;
|
||||
|
|
1
ESP32/debugger.cpp
Symbolic link
1
ESP32/debugger.cpp
Symbolic link
|
@ -0,0 +1 @@
|
|||
../debugger.cpp
|
1
ESP32/debugger.h
Symbolic link
1
ESP32/debugger.h
Symbolic link
|
@ -0,0 +1 @@
|
|||
../debugger.h
|
|
@ -1,37 +0,0 @@
|
|||
#include <Arduino.h>
|
||||
|
||||
std::string read_terminal_line(const std::string & prompt)
|
||||
{
|
||||
Serial.print(prompt.c_str());
|
||||
Serial.print(F(">"));
|
||||
|
||||
std::string str;
|
||||
|
||||
for(;;) {
|
||||
if (Serial.available()) {
|
||||
char c = Serial.read();
|
||||
|
||||
if (c == 13 || c == 10)
|
||||
break;
|
||||
|
||||
if (c == 8) {
|
||||
if (!str.empty()) {
|
||||
str = str.substr(0, str.size() - 1);
|
||||
|
||||
Serial.print(char(8));
|
||||
Serial.print(' ');
|
||||
Serial.print(char(8));
|
||||
}
|
||||
}
|
||||
else if (c >= 32 && c < 127) {
|
||||
str += c;
|
||||
|
||||
Serial.print(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println(F(""));
|
||||
|
||||
return str;
|
||||
}
|
|
@ -1,3 +1,8 @@
|
|||
#include <string>
|
||||
#pragma once
|
||||
|
||||
std::string read_terminal_line(const std::string & prompt);
|
||||
#if defined(ESP32)
|
||||
#include <SPI.h>
|
||||
#define USE_SDFAT
|
||||
#define SD_FAT_TYPE 1
|
||||
#include <SdFat.h>
|
||||
#endif
|
||||
|
|
151
ESP32/main.ino
151
ESP32/main.ino
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "console_esp32.h"
|
||||
#include "cpu.h"
|
||||
#include "debugger.h"
|
||||
#include "error.h"
|
||||
#include "esp32.h"
|
||||
#include "memory.h"
|
||||
|
@ -29,9 +30,14 @@ uint16_t exec_addr = 0;
|
|||
|
||||
uint32_t start_ts = 0;
|
||||
|
||||
std::atomic_bool terminate { false };
|
||||
SdFat32 sd;
|
||||
|
||||
std::atomic_bool *running { nullptr };
|
||||
std::atomic_bool terminate { false };
|
||||
std::atomic_bool interrupt_emulation { false };
|
||||
|
||||
std::atomic_bool *running { nullptr };
|
||||
|
||||
bool trace_output { false };
|
||||
|
||||
// std::atomic_bool on_wifi { false };
|
||||
|
||||
|
@ -58,13 +64,20 @@ void setBootLoader(bus *const b) {
|
|||
c->setRegister(7, offset);
|
||||
}
|
||||
|
||||
void console_thread_wrapper(void *const c)
|
||||
void console_thread_wrapper_panel(void *const c)
|
||||
{
|
||||
console *const cnsl = reinterpret_cast<console *>(c);
|
||||
|
||||
cnsl->panel_update_thread();
|
||||
}
|
||||
|
||||
void console_thread_wrapper_io(void *const c)
|
||||
{
|
||||
console *const cnsl = reinterpret_cast<console *>(c);
|
||||
|
||||
cnsl->operator()();
|
||||
}
|
||||
|
||||
void setup_wifi_stations()
|
||||
{
|
||||
#if 0
|
||||
|
@ -115,6 +128,40 @@ void setup_wifi_stations()
|
|||
#endif
|
||||
}
|
||||
|
||||
std::vector<std::string> select_disk_files(console *const c)
|
||||
{
|
||||
c->debug("MISO: %d", int(MISO));
|
||||
c->debug("MOSI: %d", int(MOSI));
|
||||
c->debug("SCK : %d", int(SCK ));
|
||||
c->debug("SS : %d", int(SS ));
|
||||
|
||||
c->put_string_lf("Files on SD-card:");
|
||||
|
||||
if (!sd.begin(SS, SD_SCK_MHZ(15)))
|
||||
sd.initErrorHalt();
|
||||
|
||||
for(;;) {
|
||||
sd.ls("/", LS_DATE | LS_SIZE | LS_R);
|
||||
|
||||
c->flush_input();
|
||||
|
||||
std::string selected_file = c->read_line("Enter filename: ");
|
||||
|
||||
c->put_string("Opening file: ");
|
||||
c->put_string_lf(selected_file.c_str());
|
||||
|
||||
File32 fh;
|
||||
|
||||
if (fh.open(selected_file.c_str(), O_RDWR)) {
|
||||
fh.close();
|
||||
|
||||
return { selected_file };
|
||||
}
|
||||
|
||||
c->put_string_lf("open failed");
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
|
@ -143,7 +190,7 @@ void setup() {
|
|||
c->setEmulateMFPT(true);
|
||||
|
||||
Serial.println(F("Init console"));
|
||||
cnsl = new console_esp32(&terminate, b);
|
||||
cnsl = new console_esp32(&terminate, &interrupt_emulation, b);
|
||||
|
||||
running = cnsl->get_running_flag();
|
||||
|
||||
|
@ -155,12 +202,17 @@ void setup() {
|
|||
Serial.print(F("Starting panel (on CPU 0, main emulator runs on CPU "));
|
||||
Serial.print(xPortGetCoreID());
|
||||
Serial.println(F(")"));
|
||||
xTaskCreatePinnedToCore(&console_thread_wrapper, "panel", 2048, cnsl, 1, nullptr, 0);
|
||||
xTaskCreatePinnedToCore(&console_thread_wrapper_panel, "panel", 2048, cnsl, 1, nullptr, 0);
|
||||
|
||||
xTaskCreatePinnedToCore(&console_thread_wrapper_io, "c-io", 2048, cnsl, 1, nullptr, 0);
|
||||
|
||||
// setup_wifi_stations();
|
||||
|
||||
Serial.println(F("Load RK05"));
|
||||
b->add_rk05(new rk05({ }, b, cnsl->get_disk_read_activity_flag(), cnsl->get_disk_write_activity_flag()));
|
||||
auto disk_files = select_disk_files(cnsl);
|
||||
|
||||
b->add_rk05(new rk05(disk_files, b, cnsl->get_disk_read_activity_flag(), cnsl->get_disk_write_activity_flag()));
|
||||
|
||||
setBootLoader(b);
|
||||
|
||||
Serial.print(F("Free RAM after init: "));
|
||||
|
@ -170,17 +222,7 @@ void setup() {
|
|||
|
||||
Serial.flush();
|
||||
|
||||
Serial.println(F("Press <enter> to start"));
|
||||
|
||||
for(;;) {
|
||||
if (Serial.available()) {
|
||||
int c = Serial.read();
|
||||
if (c == 13 || c == 10)
|
||||
break;
|
||||
}
|
||||
|
||||
delay(1);
|
||||
}
|
||||
cnsl->start_thread();
|
||||
|
||||
Serial.println(F("Emulation starting!"));
|
||||
|
||||
|
@ -189,79 +231,8 @@ void setup() {
|
|||
*running = true;
|
||||
}
|
||||
|
||||
uint32_t icount = 0;
|
||||
|
||||
void dump_state(bus *const b) {
|
||||
cpu *const c = b->getCpu();
|
||||
|
||||
uint32_t now = millis();
|
||||
uint32_t t_diff = now - start_ts;
|
||||
|
||||
double mips = icount / (1000.0 * t_diff);
|
||||
|
||||
// see https://retrocomputing.stackexchange.com/questions/6960/what-was-the-clock-speed-and-ips-for-the-original-pdp-11
|
||||
constexpr double pdp11_clock_cycle = 150; // ns, for the 11/70
|
||||
constexpr double pdp11_mhz = 1000.0 / pdp11_clock_cycle;
|
||||
constexpr double pdp11_avg_cycles_per_instruction = (1 + 5) / 2.0;
|
||||
constexpr double pdp11_estimated_mips = pdp11_mhz / pdp11_avg_cycles_per_instruction;
|
||||
|
||||
Serial.print(F("MIPS: "));
|
||||
Serial.println(mips);
|
||||
|
||||
Serial.print(F("emulation speed (aproximately): "));
|
||||
Serial.print(mips * 100 / pdp11_estimated_mips);
|
||||
Serial.println('%');
|
||||
|
||||
Serial.print(F("PC: "));
|
||||
Serial.println(c->getPC());
|
||||
|
||||
Serial.print(F("Uptime (ms): "));
|
||||
Serial.println(t_diff);
|
||||
}
|
||||
|
||||
bool poll_char()
|
||||
{
|
||||
return Serial.available() > 0;
|
||||
}
|
||||
|
||||
char get_char()
|
||||
{
|
||||
char c = Serial.read();
|
||||
|
||||
if (c == 5)
|
||||
dump_state(b);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void put_char(char c)
|
||||
{
|
||||
Serial.print(c);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
icount++;
|
||||
debugger(cnsl, b, &interrupt_emulation, false);
|
||||
|
||||
c->step();
|
||||
|
||||
if (event || terminate) {
|
||||
*running = false;
|
||||
|
||||
Serial.println(F(""));
|
||||
Serial.println(F(" *** EMULATION STOPPED *** "));
|
||||
dump_state(b);
|
||||
delay(3000);
|
||||
Serial.println(F(" *** EMULATION RESTARTING *** "));
|
||||
|
||||
c->reset();
|
||||
c->setRegister(7, exec_addr);
|
||||
|
||||
start_ts = millis();
|
||||
icount = 0;
|
||||
|
||||
terminate = false;
|
||||
event = 0;
|
||||
|
||||
*running = true;
|
||||
}
|
||||
c->reset();
|
||||
}
|
||||
|
|
|
@ -2,13 +2,9 @@
|
|||
default_envs = serial32
|
||||
src_dir = .
|
||||
|
||||
[env]
|
||||
framework = arduino
|
||||
lib_ldf_mode = deep+
|
||||
lib_deps =
|
||||
src_filter = +<*> -<.git/> -<.svn/> -<example/> -<examples/> -<test/> -<tests/> -<build> -<player.cpp>
|
||||
|
||||
[env:serial32]
|
||||
lib_ldf_mode = deep+
|
||||
src_filter = +<*> -<.git/> -<.svn/> -<example/> -<examples/> -<test/> -<tests/> -<build> -<player.cpp>
|
||||
platform = espressif32
|
||||
board = wemos_d1_mini32
|
||||
framework = arduino
|
||||
|
@ -16,5 +12,5 @@ monitor_speed = 115200
|
|||
upload_speed = 1000000
|
||||
lib_deps = greiman/SdFat@^2.1.2
|
||||
adafruit/Adafruit NeoPixel@^1.10.4
|
||||
build_flags = -std=c++17 -Ofast -DESP32=1
|
||||
build_flags = -std=gnu++17 -Ofast -DESP32=1 -ggdb3 -D_GLIBCXX_USE_C99
|
||||
build_unflags = -std=gnu++11 -Os
|
||||
|
|
21
bus.cpp
21
bus.cpp
|
@ -49,7 +49,7 @@ void bus::init()
|
|||
MMR3 = 0;
|
||||
}
|
||||
|
||||
uint16_t bus::read(const uint16_t a, const bool word_mode, const bool use_prev)
|
||||
uint16_t bus::read(const uint16_t a, const bool word_mode, const bool use_prev, const bool peek_only)
|
||||
{
|
||||
uint16_t temp = 0;
|
||||
|
||||
|
@ -304,7 +304,7 @@ uint16_t bus::read(const uint16_t a, const bool word_mode, const bool use_prev)
|
|||
|
||||
int run_mode = (c->getPSW() >> (use_prev ? 12 : 14)) & 3;
|
||||
|
||||
uint32_t m_offset = calculate_physical_address(run_mode, a, true);
|
||||
uint32_t m_offset = calculate_physical_address(run_mode, a, !peek_only);
|
||||
|
||||
if (word_mode)
|
||||
temp = m -> readByte(m_offset);
|
||||
|
@ -389,12 +389,12 @@ uint16_t bus::write(const uint16_t a, const bool word_mode, uint16_t value, cons
|
|||
|
||||
if (a == 0177774 || a == 0177775) { // stack limit register
|
||||
D(fprintf(stderr, "writeb Set stack limit register: %o\n", value);)
|
||||
uint16_t v = c -> getStackLimitRegister();
|
||||
uint16_t v = c -> getStackLimitRegister();
|
||||
|
||||
if (a & 1)
|
||||
v = (v & 0xff00) | value;
|
||||
else
|
||||
v = (v & 0x00ff) | (value << 8);
|
||||
else
|
||||
v = (v & 0xff00) | value;
|
||||
|
||||
c -> setStackLimitRegister(v);
|
||||
return v;
|
||||
|
@ -403,7 +403,7 @@ uint16_t bus::write(const uint16_t a, const bool word_mode, uint16_t value, cons
|
|||
else {
|
||||
if (a == 0177776) { // PSW
|
||||
D(fprintf(stderr, "write PSW %o\n", value);)
|
||||
c -> setPSW(value, false);
|
||||
c -> setPSW(value, false);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -653,12 +653,17 @@ uint16_t bus::write(const uint16_t a, const bool word_mode, uint16_t value, cons
|
|||
|
||||
uint16_t bus::readWord(const uint16_t a)
|
||||
{
|
||||
return read(a, false);
|
||||
return read(a, false, false, false);
|
||||
}
|
||||
|
||||
uint16_t bus::peekWord(const uint16_t a)
|
||||
{
|
||||
return read(a, false, false, true);
|
||||
}
|
||||
|
||||
uint16_t bus::writeWord(const uint16_t a, const uint16_t value)
|
||||
{
|
||||
return write(a, false, value);
|
||||
return write(a, false, value, false);
|
||||
}
|
||||
|
||||
uint16_t bus::readUnibusByte(const uint16_t a)
|
||||
|
|
9
bus.h
9
bus.h
|
@ -54,14 +54,15 @@ public:
|
|||
|
||||
void init(); // invoked by 'RESET' command
|
||||
|
||||
uint16_t read(const uint16_t a, const bool word_mode, const bool use_prev=false);
|
||||
uint16_t readByte(const uint16_t a) { return read(a, true); }
|
||||
uint16_t read(const uint16_t a, const bool word_mode, const bool use_prev, const bool peek_only=false);
|
||||
uint16_t readByte(const uint16_t a) { return read(a, true, false); }
|
||||
uint16_t readWord(const uint16_t a);
|
||||
uint16_t peekWord(const uint16_t a);
|
||||
|
||||
uint16_t readUnibusByte(const uint16_t a);
|
||||
|
||||
uint16_t write(const uint16_t a, const bool word_mode, uint16_t value, const bool use_prev=false);
|
||||
uint8_t writeByte(const uint16_t a, const uint8_t value) { return write(a, true, value); }
|
||||
uint16_t write(const uint16_t a, const bool word_mode, uint16_t value, const bool use_prev);
|
||||
uint8_t writeByte(const uint16_t a, const uint8_t value) { return write(a, true, value, false); }
|
||||
uint16_t writeWord(const uint16_t a, const uint16_t value);
|
||||
|
||||
void writeUnibusByte(const uint16_t a, const uint8_t value);
|
||||
|
|
120
console.cpp
120
console.cpp
|
@ -1,3 +1,4 @@
|
|||
#include <chrono>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
@ -9,8 +10,9 @@
|
|||
#include "utils.h"
|
||||
|
||||
|
||||
console::console(std::atomic_bool *const terminate, bus *const b) :
|
||||
console::console(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b) :
|
||||
terminate(terminate),
|
||||
interrupt_emulation(interrupt_emulation),
|
||||
b(b)
|
||||
{
|
||||
memset(screen_buffer, ' ', sizeof screen_buffer);
|
||||
|
@ -20,15 +22,40 @@ console::~console()
|
|||
{
|
||||
}
|
||||
|
||||
void console::start_thread()
|
||||
{
|
||||
stop_thread_flag = false;
|
||||
|
||||
#if !defined(ESP32)
|
||||
th = new std::thread(std::ref(*this));
|
||||
#endif
|
||||
}
|
||||
|
||||
void console::stop_thread()
|
||||
{
|
||||
if (th) {
|
||||
stop_thread_flag = true;
|
||||
|
||||
th->join();
|
||||
delete th;
|
||||
|
||||
th = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool console::poll_char()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(input_lock);
|
||||
|
||||
return input_buffer.empty() == false;
|
||||
}
|
||||
|
||||
uint8_t console::get_char()
|
||||
int console::get_char()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(input_lock);
|
||||
|
||||
if (input_buffer.empty())
|
||||
return 0x00;
|
||||
return -1;
|
||||
|
||||
char c = input_buffer.at(0);
|
||||
|
||||
|
@ -37,6 +64,67 @@ uint8_t console::get_char()
|
|||
return c;
|
||||
}
|
||||
|
||||
int console::wait_char(const int timeout_ms)
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(input_lock);
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
if (input_buffer.empty() == false || have_data.wait_for(lck, timeout_ms * 1ms) == std::cv_status::no_timeout) {
|
||||
if (input_buffer.empty() == false) {
|
||||
int c = input_buffer.at(0);
|
||||
|
||||
input_buffer.erase(input_buffer.begin() + 0);
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void console::flush_input()
|
||||
{
|
||||
input_buffer.clear();
|
||||
}
|
||||
|
||||
std::string console::read_line(const std::string & prompt)
|
||||
{
|
||||
put_string(prompt);
|
||||
put_string(">");
|
||||
|
||||
std::string str;
|
||||
|
||||
for(;;) {
|
||||
char c = wait_char(500);
|
||||
|
||||
if (c == -1)
|
||||
continue;
|
||||
|
||||
if (c == 13 || c == 10)
|
||||
break;
|
||||
|
||||
if (c == 8) {
|
||||
if (!str.empty()) {
|
||||
str = str.substr(0, str.size() - 1);
|
||||
|
||||
put_char(8);
|
||||
put_char(' ');
|
||||
put_char(8);
|
||||
}
|
||||
}
|
||||
else if (c >= 32 && c < 127) {
|
||||
str += c;
|
||||
|
||||
put_char(c);
|
||||
}
|
||||
}
|
||||
|
||||
put_string_lf("");
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void console::debug(const std::string fmt, ...)
|
||||
{
|
||||
char *buffer = nullptr;
|
||||
|
@ -44,12 +132,11 @@ void console::debug(const std::string fmt, ...)
|
|||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
|
||||
int len = vasprintf(&buffer, fmt.c_str(), ap);
|
||||
vasprintf(&buffer, fmt.c_str(), ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
for(int i=0; i<len; i++)
|
||||
put_char(buffer[i]);
|
||||
put_string_lf(buffer);
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
@ -64,6 +151,10 @@ void console::put_char(const char c)
|
|||
tx = 0;
|
||||
else if (c == 10)
|
||||
ty++;
|
||||
else if (c == 8) { // backspace
|
||||
if (tx > 0)
|
||||
tx--;
|
||||
}
|
||||
else {
|
||||
screen_buffer[ty][tx++] = c;
|
||||
|
||||
|
@ -83,28 +174,35 @@ void console::put_char(const char c)
|
|||
}
|
||||
}
|
||||
|
||||
void console::put_string_ll(const std::string & what)
|
||||
void console::put_string(const std::string & what)
|
||||
{
|
||||
for(size_t x=0; x<what.size(); x++)
|
||||
put_char_ll(what.at(x));
|
||||
put_char(what.at(x));
|
||||
}
|
||||
|
||||
void console::operator()()
|
||||
{
|
||||
D(fprintf(stderr, "Console thread started\n");)
|
||||
|
||||
while(!*terminate) {
|
||||
int c = wait_for_char(500);
|
||||
set_thread_name("kek:console");
|
||||
|
||||
while(!*terminate && !stop_thread_flag) {
|
||||
int c = wait_for_char_ll(100);
|
||||
|
||||
if (c == -1)
|
||||
continue;
|
||||
|
||||
if (c == 3) // ^c
|
||||
*terminate = true;
|
||||
else if (c == 5) // ^e
|
||||
*interrupt_emulation = true;
|
||||
else if (c == 12) // ^l
|
||||
refresh_virtual_terminal();
|
||||
else
|
||||
else {
|
||||
input_buffer.push_back(c);
|
||||
|
||||
have_data.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
D(fprintf(stderr, "Console thread terminating\n");)
|
||||
|
|
41
console.h
41
console.h
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
|
@ -14,43 +15,53 @@ class console
|
|||
{
|
||||
private:
|
||||
std::vector<char> input_buffer;
|
||||
std::condition_variable have_data;
|
||||
std::mutex input_lock;
|
||||
|
||||
protected:
|
||||
std::atomic_bool *const terminate { nullptr };
|
||||
std::atomic_bool *const terminate { nullptr };
|
||||
std::atomic_bool *const interrupt_emulation { nullptr };
|
||||
|
||||
bus *const b { nullptr };
|
||||
std::thread *th { nullptr };
|
||||
bus *const b { nullptr };
|
||||
std::thread *th { nullptr };
|
||||
std::atomic_bool disk_read_activity_flag { false };
|
||||
std::atomic_bool disk_write_activity_flag { false };
|
||||
std::atomic_bool running_flag { false };
|
||||
std::atomic_bool running_flag { false };
|
||||
|
||||
bool stop_thread_flag { false };
|
||||
|
||||
char screen_buffer[t_height][t_width];
|
||||
uint8_t tx { 0 };
|
||||
uint8_t ty { 0 };
|
||||
uint8_t tx { 0 };
|
||||
uint8_t ty { 0 };
|
||||
|
||||
virtual int wait_for_char(const short timeout) = 0;
|
||||
virtual int wait_for_char_ll(const short timeout) = 0;
|
||||
|
||||
virtual void put_char_ll(const char c) = 0;
|
||||
|
||||
void put_string_ll(const std::string & what);
|
||||
|
||||
public:
|
||||
console(std::atomic_bool *const terminate, bus *const b);
|
||||
console(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b);
|
||||
virtual ~console();
|
||||
|
||||
bool poll_char();
|
||||
void start_thread();
|
||||
void stop_thread();
|
||||
|
||||
uint8_t get_char();
|
||||
bool poll_char();
|
||||
int get_char();
|
||||
int wait_char(const int timeout_ms);
|
||||
std::string read_line(const std::string & prompt);
|
||||
void flush_input();
|
||||
|
||||
void put_char(const char c);
|
||||
void put_char(const char c);
|
||||
void put_string(const std::string & what);
|
||||
virtual void put_string_lf(const std::string & what) = 0;
|
||||
|
||||
void debug(const std::string fmt, ...);
|
||||
void debug(const std::string fmt, ...);
|
||||
|
||||
virtual void resize_terminal() = 0;
|
||||
|
||||
virtual void refresh_virtual_terminal() = 0;
|
||||
|
||||
void operator()();
|
||||
void operator()();
|
||||
|
||||
std::atomic_bool * get_running_flag() { return &running_flag; }
|
||||
std::atomic_bool * get_disk_read_activity_flag() { return &disk_read_activity_flag; }
|
||||
|
|
|
@ -9,32 +9,26 @@
|
|||
#include "utils.h"
|
||||
|
||||
|
||||
console_ncurses::console_ncurses(std::atomic_bool *const terminate, bus *const b) :
|
||||
console(terminate, b)
|
||||
console_ncurses::console_ncurses(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b) :
|
||||
console(terminate, interrupt_emulation, b)
|
||||
{
|
||||
init_ncurses(true);
|
||||
|
||||
resize_terminal();
|
||||
|
||||
th = new std::thread(std::ref(*this));
|
||||
|
||||
th_panel = new std::thread(&console_ncurses::panel_update_thread, this);
|
||||
}
|
||||
|
||||
console_ncurses::~console_ncurses()
|
||||
{
|
||||
stop_thread();
|
||||
|
||||
if (th_panel) {
|
||||
th_panel->join();
|
||||
|
||||
delete th_panel;
|
||||
}
|
||||
|
||||
if (th) {
|
||||
th->join();
|
||||
|
||||
delete th;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lck(ncurses_mutex);
|
||||
|
||||
wattron(w_main->win, A_BOLD);
|
||||
|
@ -54,14 +48,19 @@ console_ncurses::~console_ncurses()
|
|||
endwin();
|
||||
}
|
||||
|
||||
int console_ncurses::wait_for_char(const short timeout)
|
||||
int console_ncurses::wait_for_char_ll(const short timeout)
|
||||
{
|
||||
struct pollfd fds[] = { { STDIN_FILENO, POLLIN, 0 } };
|
||||
|
||||
if (poll(fds, 1, timeout) == 1 && fds[0].revents) {
|
||||
std::unique_lock<std::mutex> lck(ncurses_mutex);
|
||||
|
||||
return getch();
|
||||
int c = getch();
|
||||
|
||||
if (c == ERR)
|
||||
return -1;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
@ -80,6 +79,13 @@ void console_ncurses::put_char_ll(const char c)
|
|||
}
|
||||
}
|
||||
|
||||
void console_ncurses::put_string_lf(const std::string & what)
|
||||
{
|
||||
put_string(what);
|
||||
|
||||
put_string("\n");
|
||||
}
|
||||
|
||||
void console_ncurses::resize_terminal()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(ncurses_mutex);
|
||||
|
@ -107,7 +113,7 @@ void console_ncurses::resize_terminal()
|
|||
|
||||
create_win_border(0, 0, 80, 25, "terminal", &w_main_b, &w_main, false);
|
||||
|
||||
create_win_border(0, 27, 80, 3, "panel", &w_panel_b, &w_panel, false);
|
||||
create_win_border(0, 27, 100, 4, "panel", &w_panel_b, &w_panel, false);
|
||||
|
||||
scrollok(w_main -> win, TRUE);
|
||||
|
||||
|
@ -116,6 +122,8 @@ void console_ncurses::resize_terminal()
|
|||
|
||||
void console_ncurses::panel_update_thread()
|
||||
{
|
||||
set_thread_name("kek:c-panel");
|
||||
|
||||
cpu *const c = b->getCpu();
|
||||
|
||||
uint64_t prev_instr_cnt = c->get_instructions_executed_count();
|
||||
|
@ -134,8 +142,13 @@ void console_ncurses::panel_update_thread()
|
|||
|
||||
uint16_t current_instr = b->readWord(current_PC);
|
||||
|
||||
auto data = c->disassemble(current_PC);
|
||||
|
||||
std::unique_lock<std::mutex> lck(ncurses_mutex);
|
||||
|
||||
werase(w_panel->win);
|
||||
|
||||
//
|
||||
wattron(w_panel->win, COLOR_PAIR(1 + run_mode));
|
||||
|
||||
for(uint8_t b=0; b<22; b++)
|
||||
|
@ -158,12 +171,38 @@ void console_ncurses::panel_update_thread()
|
|||
|
||||
wattron(w_panel->win, COLOR_PAIR(0));
|
||||
|
||||
// disassembler
|
||||
auto registers = data["registers"];
|
||||
auto psw = data["psw"][0];
|
||||
|
||||
std::string instruction_values;
|
||||
for(auto iv : data["instruction-values"])
|
||||
instruction_values += (instruction_values.empty() ? "" : ",") + iv;
|
||||
|
||||
std::string work_values;
|
||||
for(auto wv : data["work-values"])
|
||||
work_values += (work_values.empty() ? "" : ",") + wv;
|
||||
|
||||
std::string instruction = data["instruction-text"].at(0);
|
||||
|
||||
mvwprintw(w_panel->win, 2, 1, "R0: %s, R1: %s, R2: %s, R3: %s, R4: %s, R5: %s, SP: %s, PC: %s",
|
||||
registers[0].c_str(), registers[1].c_str(), registers[2].c_str(), registers[3].c_str(), registers[4].c_str(), registers[5].c_str(),
|
||||
registers[6].c_str(), registers[7].c_str());
|
||||
mvwprintw(w_panel->win, 3, 1, "PSW: %s, instr: %s",
|
||||
psw.c_str(),
|
||||
instruction_values.c_str());
|
||||
mvwprintw(w_panel->win, 3, 46, "%s - %s",
|
||||
instruction.c_str(),
|
||||
work_values.c_str());
|
||||
|
||||
// speed
|
||||
uint64_t cur_instr_cnt = c->get_instructions_executed_count();
|
||||
|
||||
mvwprintw(w_panel->win, 1, 1 + 39, "%8ld", (cur_instr_cnt - prev_instr_cnt) * refresh_rate);
|
||||
|
||||
prev_instr_cnt = cur_instr_cnt;
|
||||
|
||||
// ncurses
|
||||
wmove(w_main->win, ty, tx);
|
||||
|
||||
mydoupdate();
|
||||
|
|
|
@ -21,14 +21,16 @@ private:
|
|||
int ty { 0 };
|
||||
|
||||
protected:
|
||||
int wait_for_char(const short timeout) override;
|
||||
int wait_for_char_ll(const short timeout) override;
|
||||
|
||||
void put_char_ll(const char c) override;
|
||||
|
||||
public:
|
||||
console_ncurses(std::atomic_bool *const terminate, bus *const b);
|
||||
console_ncurses(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b);
|
||||
virtual ~console_ncurses();
|
||||
|
||||
void put_string_lf(const std::string & what) override;
|
||||
|
||||
void resize_terminal() override;
|
||||
|
||||
void refresh_virtual_terminal() override;
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
#include "error.h"
|
||||
|
||||
|
||||
console_posix::console_posix(std::atomic_bool *const terminate, bus *const b) :
|
||||
console(terminate, b)
|
||||
console_posix::console_posix(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b) :
|
||||
console(terminate, interrupt_emulation, b)
|
||||
{
|
||||
if (tcgetattr(STDIN_FILENO, &org_tty_opts) == -1)
|
||||
error_exit(true, "console_posix: tcgetattr failed");
|
||||
|
@ -18,24 +18,17 @@ console_posix::console_posix(std::atomic_bool *const terminate, bus *const b) :
|
|||
|
||||
if (tcsetattr(STDIN_FILENO, TCSANOW, &tty_opts_raw) == -1)
|
||||
error_exit(true, "console_posix: tcsetattr failed");
|
||||
|
||||
th = new std::thread(std::ref(*this));
|
||||
}
|
||||
|
||||
console_posix::~console_posix()
|
||||
{
|
||||
if (th) {
|
||||
th->join();
|
||||
|
||||
delete th;
|
||||
}
|
||||
stop_thread();
|
||||
|
||||
if (tcsetattr(STDIN_FILENO, TCSANOW, &org_tty_opts) == -1)
|
||||
error_exit(true, "~console_posix: tcsetattr failed");
|
||||
|
||||
}
|
||||
|
||||
int console_posix::wait_for_char(const short timeout)
|
||||
int console_posix::wait_for_char_ll(const short timeout)
|
||||
{
|
||||
struct pollfd fds[] = { { STDIN_FILENO, POLLIN, timeout } };
|
||||
|
||||
|
@ -52,6 +45,11 @@ void console_posix::put_char_ll(const char c)
|
|||
fflush(nullptr);
|
||||
}
|
||||
|
||||
void console_posix::put_string_lf(const std::string & what)
|
||||
{
|
||||
put_string(what + "\r\n");
|
||||
}
|
||||
|
||||
void console_posix::resize_terminal()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -9,16 +9,18 @@ private:
|
|||
struct termios org_tty_opts { 0 };
|
||||
|
||||
protected:
|
||||
int wait_for_char(const short timeout) override;
|
||||
int wait_for_char_ll(const short timeout) override;
|
||||
|
||||
void put_char_ll(const char c) override;
|
||||
|
||||
public:
|
||||
console_posix(std::atomic_bool *const terminate, bus *const b);
|
||||
console_posix(std::atomic_bool *const terminate, std::atomic_bool *const interrupt_emulation, bus *const b);
|
||||
virtual ~console_posix();
|
||||
|
||||
void resize_terminal() override;
|
||||
|
||||
void put_string_lf(const std::string & what) override;
|
||||
|
||||
void refresh_virtual_terminal() override;
|
||||
|
||||
void panel_update_thread() override;
|
||||
|
|
301
cpu.cpp
301
cpu.cpp
|
@ -27,9 +27,31 @@ cpu::~cpu()
|
|||
|
||||
void cpu::emulation_start()
|
||||
{
|
||||
instruction_count = 0;
|
||||
|
||||
running_since = get_ms();
|
||||
}
|
||||
|
||||
bool cpu::check_breakpoint()
|
||||
{
|
||||
return breakpoints.find(getPC()) != breakpoints.end();
|
||||
}
|
||||
|
||||
void cpu::set_breakpoint(const uint16_t addr)
|
||||
{
|
||||
breakpoints.insert(addr);
|
||||
}
|
||||
|
||||
void cpu::remove_breakpoint(const uint16_t addr)
|
||||
{
|
||||
breakpoints.erase(addr);
|
||||
}
|
||||
|
||||
std::set<uint16_t> cpu::list_breakpoints()
|
||||
{
|
||||
return breakpoints;
|
||||
}
|
||||
|
||||
uint64_t cpu::get_instructions_executed_count()
|
||||
{
|
||||
// this may wreck havoc as it is not protected by a mutex
|
||||
|
@ -38,6 +60,21 @@ uint64_t cpu::get_instructions_executed_count()
|
|||
return instruction_count;
|
||||
}
|
||||
|
||||
std::pair<double, double> cpu::get_mips_rel_speed()
|
||||
{
|
||||
uint32_t t_diff = get_ms() - running_since;
|
||||
|
||||
double mips = get_instructions_executed_count() / (1000.0 * t_diff);
|
||||
|
||||
// see https://retrocomputing.stackexchange.com/questions/6960/what-was-the-clock-speed-and-ips-for-the-original-pdp-11
|
||||
constexpr double pdp11_clock_cycle = 150; // ns, for the 11/70
|
||||
constexpr double pdp11_mhz = 1000.0 / pdp11_clock_cycle;
|
||||
constexpr double pdp11_avg_cycles_per_instruction = (1 + 5) / 2.0;
|
||||
constexpr double pdp11_estimated_mips = pdp11_mhz / pdp11_avg_cycles_per_instruction;
|
||||
|
||||
return { mips, mips * 100 / pdp11_estimated_mips };
|
||||
}
|
||||
|
||||
void cpu::reset()
|
||||
{
|
||||
memset(regs0_5, 0x00, sizeof regs0_5);
|
||||
|
@ -48,11 +85,6 @@ void cpu::reset()
|
|||
runMode = false;
|
||||
}
|
||||
|
||||
void cpu::setDisassemble(const bool state)
|
||||
{
|
||||
disas = state;
|
||||
}
|
||||
|
||||
uint16_t cpu::getRegister(const int nr, const bool prev_mode) const
|
||||
{
|
||||
if (nr < 6)
|
||||
|
@ -105,7 +137,7 @@ void cpu::put_result(const uint16_t a, const uint8_t dst_mode, const uint8_t dst
|
|||
if (dst_mode == 0)
|
||||
setRegisterLowByte(dst_reg, word_mode, value);
|
||||
else
|
||||
b->write(a, word_mode, value);
|
||||
b->write(a, word_mode, value, false);
|
||||
}
|
||||
|
||||
void cpu::addRegister(const int nr, const bool prev_mode, const uint16_t value)
|
||||
|
@ -202,11 +234,12 @@ void cpu::setPSW(const uint16_t v, const bool limited)
|
|||
}
|
||||
}
|
||||
|
||||
void cpu::check_queued_interrupts()
|
||||
bool cpu::check_queued_interrupts()
|
||||
{
|
||||
uint8_t current_level = getPSW_spl();
|
||||
|
||||
uint8_t start_level = current_level <= 3 ? 0 : current_level + 1;
|
||||
// uint8_t start_level = current_level <= 3 ? 0 : current_level + 1;
|
||||
uint8_t start_level = current_level + 1;
|
||||
|
||||
for(uint8_t i=start_level; i < 8; i++) {
|
||||
auto interrupts = queued_interrupts.find(i);
|
||||
|
@ -220,9 +253,11 @@ void cpu::check_queued_interrupts()
|
|||
|
||||
trap(*vector, i);
|
||||
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void cpu::queue_interrupt(const uint8_t level, const uint8_t vector)
|
||||
|
@ -281,33 +316,33 @@ void cpu::putGAM(const uint8_t mode, const int reg, const bool word_mode, const
|
|||
setRegister(reg, prev_mode, value);
|
||||
break;
|
||||
case 1:
|
||||
b -> write(getRegister(reg, prev_mode), word_mode, value);
|
||||
b -> write(getRegister(reg, prev_mode), word_mode, value, false);
|
||||
break;
|
||||
case 2:
|
||||
b -> write(getRegister(reg, prev_mode), word_mode, value);
|
||||
b -> write(getRegister(reg, prev_mode), word_mode, value, false);
|
||||
addRegister(reg, prev_mode, !word_mode || reg == 7 || reg == 6 ? 2 : 1);
|
||||
break;
|
||||
case 3:
|
||||
b -> write(b -> readWord(getRegister(reg, prev_mode)), word_mode, value);
|
||||
b -> write(b -> readWord(getRegister(reg, prev_mode)), word_mode, value, false);
|
||||
addRegister(reg, prev_mode, 2);
|
||||
break;
|
||||
case 4:
|
||||
addRegister(reg, prev_mode, !word_mode || reg == 7 || reg == 6 ? -2 : -1);
|
||||
b -> write(getRegister(reg, prev_mode), word_mode, value);
|
||||
b -> write(getRegister(reg, prev_mode), word_mode, value, false);
|
||||
break;
|
||||
case 5:
|
||||
addRegister(reg, prev_mode, -2);
|
||||
b -> write(b -> readWord(getRegister(reg, prev_mode)), word_mode, value);
|
||||
b -> write(b -> readWord(getRegister(reg, prev_mode)), word_mode, value, false);
|
||||
break;
|
||||
case 6:
|
||||
next_word = b -> readWord(getPC());
|
||||
addRegister(7, prev_mode, 2);
|
||||
b -> write(getRegister(reg, prev_mode) + next_word, word_mode, value);
|
||||
b -> write(getRegister(reg, prev_mode) + next_word, word_mode, value, false);
|
||||
break;
|
||||
case 7:
|
||||
next_word = b -> readWord(getPC());
|
||||
addRegister(7, prev_mode, 2);
|
||||
b -> write(b -> readWord(getRegister(reg, prev_mode) + next_word), word_mode, value);
|
||||
b -> write(b -> readWord(getRegister(reg, prev_mode) + next_word), word_mode, value, false);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -419,7 +454,7 @@ bool cpu::double_operand_instructions(const uint16_t instr)
|
|||
case 0b100: { // BIC/BICB Bit Clear Word/Byte
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
|
||||
|
||||
uint16_t result = b->read(a, word_mode) & ~src_value;
|
||||
uint16_t result = b->read(a, word_mode, false) & ~src_value;
|
||||
|
||||
put_result(a, dst_mode, dst_reg, word_mode, result);
|
||||
|
||||
|
@ -433,7 +468,7 @@ bool cpu::double_operand_instructions(const uint16_t instr)
|
|||
case 0b101: { // BIS/BISB Bit Set Word/Byte
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
|
||||
|
||||
uint16_t result = b->read(a, word_mode) | src_value;
|
||||
uint16_t result = b->read(a, word_mode, false) | src_value;
|
||||
|
||||
put_result(a, dst_mode, dst_reg, word_mode, result);
|
||||
|
||||
|
@ -547,7 +582,7 @@ bool cpu::additional_double_operand_instructions(const uint16_t instr)
|
|||
case 2: { // ASH
|
||||
int16_t R = getRegister(reg), oldR = R;
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, false, false);
|
||||
int16_t shift = b->read(a, false) & 077; // mask of lower 6 bit
|
||||
int16_t shift = b->read(a, false, false) & 077; // mask of lower 6 bit
|
||||
|
||||
if (shift == 0)
|
||||
setPSW_c(false);
|
||||
|
@ -579,7 +614,7 @@ bool cpu::additional_double_operand_instructions(const uint16_t instr)
|
|||
case 3: { // ASHC
|
||||
uint32_t R0R1 = (getRegister(reg) << 16) | getRegister(reg + 1);
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, false, false);
|
||||
int16_t shift = b->read(a, false) & 077; // mask of lower 6 bit
|
||||
int16_t shift = b->read(a, false, false) & 077; // mask of lower 6 bit
|
||||
|
||||
if (shift == 0) {
|
||||
setPSW_c(false);
|
||||
|
@ -614,12 +649,12 @@ bool cpu::additional_double_operand_instructions(const uint16_t instr)
|
|||
|
||||
case 4: { // XOR (word only)
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, false, false);
|
||||
uint16_t vl = b->read(a, false) ^ getRegister(reg);
|
||||
uint16_t vl = b->read(a, false, false) ^ getRegister(reg);
|
||||
|
||||
if (dst_mode == 0)
|
||||
putGAM(dst_mode, dst_reg, false, vl, false);
|
||||
else
|
||||
b->write(a, false, vl);
|
||||
b->write(a, false, vl, false);
|
||||
|
||||
setPSW_n(vl & 0x8000);
|
||||
setPSW_z(vl == 0);
|
||||
|
@ -697,7 +732,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
else {
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
|
||||
|
||||
b -> write(a, word_mode, 0);
|
||||
b -> write(a, word_mode, 0, false);
|
||||
}
|
||||
|
||||
setPSW_n(false);
|
||||
|
@ -726,7 +761,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
}
|
||||
else {
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
|
||||
uint16_t v = b -> read(a, word_mode);
|
||||
uint16_t v = b -> read(a, word_mode, false);
|
||||
|
||||
if (word_mode)
|
||||
v ^= 0xff;
|
||||
|
@ -738,7 +773,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
setPSW_v(false);
|
||||
setPSW_c(true);
|
||||
|
||||
b->write(a, word_mode, v);
|
||||
b->write(a, word_mode, v, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -759,14 +794,14 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
}
|
||||
else {
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
|
||||
uint16_t v = b -> read(a, word_mode);
|
||||
uint16_t v = b -> read(a, word_mode, false);
|
||||
int32_t vl = (v + 1) & (word_mode ? 0xff : 0xffff);
|
||||
|
||||
setPSW_n(SIGN(vl, word_mode));
|
||||
setPSW_z(IS_0(vl, word_mode));
|
||||
setPSW_v(word_mode ? vl == 0x80 : v == 0x8000);
|
||||
|
||||
b->write(a, word_mode, vl);
|
||||
b->write(a, word_mode, vl, false);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -788,14 +823,14 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
}
|
||||
else {
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
|
||||
uint16_t v = b -> read(a, word_mode);
|
||||
uint16_t v = b -> read(a, word_mode, false);
|
||||
int32_t vl = (v - 1) & (word_mode ? 0xff : 0xffff);
|
||||
|
||||
setPSW_n(SIGN(vl, word_mode));
|
||||
setPSW_z(IS_0(vl, word_mode));
|
||||
setPSW_v(word_mode ? vl == 0x7f : vl == 0x7fff);
|
||||
|
||||
b->write(a, word_mode, vl);
|
||||
b->write(a, word_mode, vl, false);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -818,9 +853,9 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
}
|
||||
else {
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
|
||||
uint16_t v = -b -> read(a, word_mode);
|
||||
uint16_t v = -b -> read(a, word_mode, false);
|
||||
|
||||
b->write(a, word_mode, v);
|
||||
b->write(a, word_mode, v, false);
|
||||
|
||||
setPSW_n(SIGN(v, word_mode));
|
||||
setPSW_z(IS_0(v, word_mode));
|
||||
|
@ -850,11 +885,11 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
}
|
||||
else {
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
|
||||
const uint16_t vo = b -> read(a, word_mode);
|
||||
const uint16_t vo = b -> read(a, word_mode, false);
|
||||
bool org_c = getPSW_c();
|
||||
uint16_t v = (vo + org_c) & (word_mode ? 0x00ff : 0xffff);
|
||||
|
||||
b->write(a, word_mode, v);
|
||||
b->write(a, word_mode, v, false);
|
||||
|
||||
setPSW_n(SIGN(v, word_mode));
|
||||
setPSW_z(IS_0(v, word_mode));
|
||||
|
@ -884,11 +919,11 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
}
|
||||
else {
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
|
||||
const uint16_t vo = b -> read(a, word_mode);
|
||||
const uint16_t vo = b -> read(a, word_mode, false);
|
||||
bool org_c = getPSW_c();
|
||||
uint16_t v = (vo - org_c) & (word_mode ? 0xff : 0xffff);
|
||||
|
||||
b->write(a, word_mode, v);
|
||||
b->write(a, word_mode, v, false);
|
||||
|
||||
setPSW_n(SIGN(v, word_mode));
|
||||
setPSW_z(IS_0(v, word_mode));
|
||||
|
@ -935,7 +970,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
}
|
||||
else {
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
|
||||
uint16_t t = b -> read(a, word_mode);
|
||||
uint16_t t = b -> read(a, word_mode, false);
|
||||
bool new_carry = t & 1;
|
||||
|
||||
uint16_t temp = 0;
|
||||
|
@ -944,7 +979,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
else
|
||||
temp = (t >> 1) | (getPSW_c() << 15);
|
||||
|
||||
b->write(a, word_mode, temp);
|
||||
b->write(a, word_mode, temp, false);
|
||||
|
||||
setPSW_c(new_carry);
|
||||
setPSW_n(SIGN(temp, word_mode));
|
||||
|
@ -978,7 +1013,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
}
|
||||
else {
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
|
||||
uint16_t t = b -> read(a, word_mode);
|
||||
uint16_t t = b -> read(a, word_mode, false);
|
||||
bool new_carry = false;
|
||||
|
||||
uint16_t temp = 0;
|
||||
|
@ -991,7 +1026,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
temp = (t << 1) | getPSW_c();
|
||||
}
|
||||
|
||||
b->write(a, word_mode, temp);
|
||||
b->write(a, word_mode, temp, false);
|
||||
|
||||
setPSW_c(new_carry);
|
||||
setPSW_n(SIGN(temp, word_mode));
|
||||
|
@ -1029,7 +1064,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
}
|
||||
else {
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
|
||||
uint16_t v = b -> read(a, word_mode);
|
||||
uint16_t v = b -> read(a, word_mode, false);
|
||||
uint16_t add = word_mode ? v & 0xff00 : 0;
|
||||
|
||||
bool hb = word_mode ? v & 128 : v & 32768;
|
||||
|
@ -1046,7 +1081,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
v |= hb << 15;
|
||||
}
|
||||
|
||||
b->write(a, word_mode, v);
|
||||
b->write(a, word_mode, v, false);
|
||||
|
||||
setPSW_n(SIGN(v, word_mode));
|
||||
setPSW_z(IS_0(v, word_mode));
|
||||
|
@ -1072,7 +1107,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
}
|
||||
else {
|
||||
uint16_t a = getGAMAddress(dst_mode, dst_reg, word_mode, false);
|
||||
uint16_t vl = b -> read(a, word_mode);
|
||||
uint16_t vl = b -> read(a, word_mode, false);
|
||||
uint16_t v = (vl << 1) & (word_mode ? 0xff : 0xffff);
|
||||
|
||||
setPSW_n(SIGN(v, word_mode));
|
||||
|
@ -1080,7 +1115,7 @@ bool cpu::single_operand_instructions(const uint16_t instr)
|
|||
setPSW_c(SIGN(vl, word_mode));
|
||||
setPSW_v(getPSW_n() ^ getPSW_c());
|
||||
|
||||
b->write(a, word_mode, v);
|
||||
b->write(a, word_mode, v, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1312,7 +1347,7 @@ bool cpu::misc_operations(const uint16_t instr)
|
|||
|
||||
case 0b0000000000000010: // RTI
|
||||
setPC(popStack());
|
||||
setPSW(popStack(), !!(getPSW() >> 12));
|
||||
setPSW(popStack(), !!((getPSW() >> 12) & 3));
|
||||
return true;
|
||||
|
||||
case 0b0000000000000011: // BPT
|
||||
|
@ -1325,7 +1360,7 @@ bool cpu::misc_operations(const uint16_t instr)
|
|||
|
||||
case 0b0000000000000110: // RTT
|
||||
setPC(popStack());
|
||||
setPSW(popStack(), !!(getPSW() >> 12));
|
||||
setPSW(popStack(), !!((getPSW() >> 12) & 3));
|
||||
return true;
|
||||
|
||||
case 0b0000000000000111: // MFPT
|
||||
|
@ -1433,15 +1468,16 @@ void cpu::trap(const uint16_t vector, const int new_ipl)
|
|||
D(fprintf(stderr, "TRAP %o: PC is now %06o, PSW is now %06o\n", vector, getPC(), new_psw);)
|
||||
}
|
||||
|
||||
std::pair<std::string, int> cpu::addressing_to_string(const uint8_t mode_register, const uint16_t pc)
|
||||
cpu::operand_parameters cpu::addressing_to_string(const uint8_t mode_register, const uint16_t pc, const bool word_mode) const
|
||||
{
|
||||
#if !defined(ESP32)
|
||||
assert(mode_register < 64);
|
||||
|
||||
uint16_t next_word = b->readWord(pc & 65535);
|
||||
uint16_t next_word = b->peekWord(pc & 65535);
|
||||
|
||||
int reg = mode_register & 7;
|
||||
|
||||
uint16_t mask = word_mode ? 0xff : 0xffff;
|
||||
|
||||
std::string reg_name;
|
||||
if (reg == 6)
|
||||
reg_name = "SP";
|
||||
|
@ -1452,50 +1488,51 @@ std::pair<std::string, int> cpu::addressing_to_string(const uint8_t mode_registe
|
|||
|
||||
switch(mode_register >> 3) {
|
||||
case 0:
|
||||
return { reg_name, 2 };
|
||||
return { reg_name, 2, -1, uint16_t(getRegister(reg) & mask) };
|
||||
|
||||
case 1:
|
||||
return { format("(%s)", reg_name.c_str()), 2 };
|
||||
return { format("(%s)", reg_name.c_str()), 2, -1, uint16_t(b->peekWord(getRegister(reg)) & mask) };
|
||||
|
||||
case 2:
|
||||
if (reg == 7)
|
||||
return { format("#%06o", next_word), 4 };
|
||||
return { format("#%06o", next_word), 4, int(next_word), uint16_t(next_word & mask) };
|
||||
|
||||
return { format("(%s)+", reg_name.c_str()), 2 };
|
||||
return { format("(%s)+", reg_name.c_str()), 2, -1, uint16_t(b->peekWord(getRegister(reg)) & mask) };
|
||||
|
||||
case 3:
|
||||
if (reg == 7)
|
||||
return { format("@#%06o", next_word), 4 };
|
||||
return { format("@#%06o", next_word), 4, int(next_word), uint16_t(b->peekWord(next_word) & mask) };
|
||||
|
||||
return { format("@(%s)+", reg_name.c_str()), 2 };
|
||||
return { format("@(%s)+", reg_name.c_str()), 2, -1, uint16_t(b->peekWord(b->peekWord(getRegister(reg))) & mask) };
|
||||
|
||||
case 4:
|
||||
return { format("-(%s)", reg_name.c_str()), 2 };
|
||||
return { format("-(%s)", reg_name.c_str()), 2, -1, uint16_t(b->peekWord(getRegister(reg) - (word_mode == false || reg >= 6 ? 2 : 1)) & mask) };
|
||||
|
||||
case 5:
|
||||
return { format("@-(%s)", reg_name.c_str()), 2 };
|
||||
return { format("@-(%s)", reg_name.c_str()), 2, -1, uint16_t(b->peekWord(b->peekWord(getRegister(reg) - 2)) & mask) };
|
||||
|
||||
case 6:
|
||||
if (reg == 7)
|
||||
return { format("%06o", (pc + next_word + 2) & 65535), 4 };
|
||||
return { format("%06o", (pc + next_word + 2) & 65535), 4, int(next_word), uint16_t(b->peekWord(getRegister(reg) + next_word) & mask) };
|
||||
|
||||
return { format("%o(%s)", next_word, reg_name.c_str()), 4 };
|
||||
return { format("%o(%s)", next_word, reg_name.c_str()), 4, int(next_word), uint16_t(b->peekWord(getRegister(reg) + next_word) & mask) };
|
||||
|
||||
case 7:
|
||||
if (reg == 7)
|
||||
return { format("@%06o", next_word), 4 };
|
||||
return { format("@%06o", next_word), 4, int(next_word), uint16_t(b->peekWord(b->peekWord(getRegister(reg) + next_word)) & mask) };
|
||||
|
||||
return { format("@%o(%s)", next_word, reg_name.c_str()), 4 };
|
||||
return { format("@%o(%s)", next_word, reg_name.c_str()), 4, int(next_word), uint16_t(b->peekWord(b->peekWord(getRegister(reg) + next_word)) & mask) };
|
||||
}
|
||||
#endif
|
||||
return { "??", 0 };
|
||||
|
||||
return { "??", 0, -1, 0123456 };
|
||||
}
|
||||
|
||||
void cpu::disassemble()
|
||||
std::map<std::string, std::vector<std::string> > cpu::disassemble(const uint16_t addr) const
|
||||
{
|
||||
#if !defined(ESP32)
|
||||
uint16_t pc = getPC();
|
||||
uint16_t instruction = b->readWord(pc);
|
||||
bool old_trace_output = trace_output;
|
||||
trace_output = false;
|
||||
|
||||
uint16_t instruction = b->peekWord(addr);
|
||||
|
||||
bool word_mode = !!(instruction & 0x8000);
|
||||
std::string word_mode_str = word_mode ? "B" : "";
|
||||
|
@ -1512,15 +1549,24 @@ void cpu::disassemble()
|
|||
uint8_t src_register = (instruction >> 6) & 63;
|
||||
uint8_t dst_register = (instruction >> 0) & 63;
|
||||
|
||||
std::vector<uint16_t> instruction_words { instruction };
|
||||
std::vector<uint16_t> work_values;
|
||||
|
||||
// TODO: 100000011
|
||||
|
||||
if (do_opcode == 0b000) {
|
||||
auto dst_text = addressing_to_string(dst_register, (pc + 2) & 65535);
|
||||
auto dst_text { addressing_to_string(dst_register, (addr + 2) & 65535, word_mode) };
|
||||
|
||||
auto next_word = dst_text.instruction_part;
|
||||
if (next_word != -1)
|
||||
instruction_words.push_back(next_word);
|
||||
|
||||
work_values.push_back(dst_text.work_value);
|
||||
|
||||
// single_operand_instructions
|
||||
switch(so_opcode) {
|
||||
case 0b00000011:
|
||||
text = "SWAB " + dst_text.first;
|
||||
text = "SWAB " + dst_text.operand;
|
||||
break;
|
||||
|
||||
case 0b000101000:
|
||||
|
@ -1593,11 +1639,17 @@ void cpu::disassemble()
|
|||
}
|
||||
|
||||
if (text.empty() && name.empty() == false)
|
||||
text = name + word_mode_str + space + dst_text.first;
|
||||
text = name + word_mode_str + space + dst_text.operand;
|
||||
}
|
||||
else if (do_opcode == 0b111) {
|
||||
std::string src_text = format("R%d", (instruction >> 6) & 7);
|
||||
auto dst_text = addressing_to_string(dst_register, (pc + 2) & 65535);
|
||||
auto dst_text { addressing_to_string(dst_register, (addr + 2) & 65535, word_mode) };
|
||||
|
||||
auto next_word = dst_text.instruction_part;
|
||||
if (next_word != -1)
|
||||
instruction_words.push_back(next_word);
|
||||
|
||||
work_values.push_back(dst_text.work_value);
|
||||
|
||||
switch(ado_opcode) { // additional double operand
|
||||
case 0:
|
||||
|
@ -1626,7 +1678,7 @@ void cpu::disassemble()
|
|||
}
|
||||
|
||||
if (text.empty() && name.empty() == false)
|
||||
text = name + space + src_text + comma + dst_text.first;
|
||||
text = name + space + src_text + comma + dst_text.operand;
|
||||
}
|
||||
else {
|
||||
switch(do_opcode) {
|
||||
|
@ -1658,16 +1710,31 @@ void cpu::disassemble()
|
|||
break;
|
||||
}
|
||||
|
||||
auto src_text = addressing_to_string(src_register, (pc + 2) & 65535);
|
||||
auto dst_text = addressing_to_string(dst_register, (pc + src_text.second) & 65535);
|
||||
// source
|
||||
auto src_text { addressing_to_string(src_register, (addr + 2) & 65535, word_mode) };
|
||||
|
||||
text = name + word_mode_str + space + src_text.first + comma + dst_text.first;
|
||||
auto next_word_src = src_text.instruction_part;
|
||||
if (next_word_src != -1)
|
||||
instruction_words.push_back(next_word_src);
|
||||
|
||||
work_values.push_back(src_text.work_value);
|
||||
|
||||
// destination
|
||||
auto dst_text { addressing_to_string(dst_register, (addr + src_text.length) & 65535, word_mode) };
|
||||
|
||||
auto next_word_dst = dst_text.instruction_part;
|
||||
if (next_word_dst != -1)
|
||||
instruction_words.push_back(next_word_dst);
|
||||
|
||||
work_values.push_back(dst_text.work_value);
|
||||
|
||||
text = name + word_mode_str + space + src_text.operand + comma + dst_text.operand;
|
||||
}
|
||||
|
||||
if (text.empty()) { // conditional branch instructions
|
||||
uint8_t cb_opcode = (instruction >> 8) & 255;
|
||||
int8_t offset = instruction & 255;
|
||||
uint16_t new_pc = (pc + 2 + offset * 2) & 65535;
|
||||
uint16_t new_pc = (addr + 2 + offset * 2) & 65535;
|
||||
|
||||
switch(cb_opcode) {
|
||||
case 0b00000001:
|
||||
|
@ -1756,18 +1823,22 @@ void cpu::disassemble()
|
|||
case 0b0000000010100000:
|
||||
case 0b0000000010110000:
|
||||
text = "NOP";
|
||||
work_values.clear();
|
||||
break;
|
||||
|
||||
case 0b0000000000000000:
|
||||
text = "HALT";
|
||||
work_values.clear();
|
||||
break;
|
||||
|
||||
case 0b0000000000000001:
|
||||
text = "WAIT";
|
||||
work_values.clear();
|
||||
break;
|
||||
|
||||
case 0b0000000000000010:
|
||||
text = "RTI";
|
||||
work_values.clear();
|
||||
break;
|
||||
|
||||
case 0b0000000000000011:
|
||||
|
@ -1780,6 +1851,7 @@ void cpu::disassemble()
|
|||
|
||||
case 0b0000000000000110:
|
||||
text = "RTT";
|
||||
work_values.clear();
|
||||
break;
|
||||
|
||||
case 0b0000000000000111:
|
||||
|
@ -1788,6 +1860,7 @@ void cpu::disassemble()
|
|||
|
||||
case 0b0000000000000101:
|
||||
text = "RESET";
|
||||
work_values.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1798,15 +1871,27 @@ void cpu::disassemble()
|
|||
text = format("TRAP %o", instruction & 255);
|
||||
|
||||
if ((instruction & ~0b111111) == 0b0000000001000000) {
|
||||
auto dst_text = addressing_to_string(dst_register, (pc + 2) & 65535);
|
||||
auto dst_text { addressing_to_string(dst_register, (addr + 2) & 65535, word_mode) };
|
||||
|
||||
text = std::string("JMP ") + dst_text.first;
|
||||
auto next_word = dst_text.instruction_part;
|
||||
if (next_word != -1)
|
||||
instruction_words.push_back(next_word);
|
||||
|
||||
work_values.push_back(dst_text.work_value);
|
||||
|
||||
text = std::string("JMP ") + dst_text.operand;
|
||||
}
|
||||
|
||||
if ((instruction & 0b1111111000000000) == 0b0000100000000000) {
|
||||
auto dst_text = addressing_to_string(dst_register, (pc + 2) & 65535);
|
||||
auto dst_text { addressing_to_string(dst_register, (addr + 2) & 65535, word_mode) };
|
||||
|
||||
text = format("JSR R%d,", src_register & 7) + dst_text.first;
|
||||
auto next_word = dst_text.instruction_part;
|
||||
if (next_word != -1)
|
||||
instruction_words.push_back(next_word);
|
||||
|
||||
work_values.push_back(dst_text.work_value);
|
||||
|
||||
text = format("JSR R%d,", src_register & 7) + dst_text.operand;
|
||||
}
|
||||
|
||||
if ((instruction & 0b1111111111111000) == 0b0000000010000000)
|
||||
|
@ -1816,25 +1901,64 @@ void cpu::disassemble()
|
|||
if (text.empty())
|
||||
text = "???";
|
||||
|
||||
fprintf(stderr, "R0: %06o, R1: %06o, R2: %06o, R3: %06o, R4: %06o, R5: %06o, SP: %06o, PC: %06o, PSW: %d%d|%d|%d|%c%c%c%c%c, instr: %06o: %s\n",
|
||||
getRegister(0), getRegister(1), getRegister(2), getRegister(3), getRegister(4), getRegister(5),
|
||||
sp[psw >> 14], pc,
|
||||
psw >> 14, (psw >> 12) & 3, (psw >> 11) & 1, (psw >> 5) & 7,
|
||||
psw & 16?'t':'-', psw & 8?'n':'-', psw & 4?'z':'-', psw & 2 ? 'v':'-', psw & 1 ? 'c':'-',
|
||||
instruction, text.c_str());
|
||||
#endif
|
||||
std::map<std::string, std::vector<std::string> > out;
|
||||
|
||||
// MOV x,y
|
||||
out.insert({ "address", { format("%06o", addr) } });
|
||||
|
||||
// MOV x,y
|
||||
out.insert({ "instruction-text", { text } });
|
||||
|
||||
// words making up the instruction
|
||||
std::vector<std::string> instruction_values;
|
||||
for(auto i : instruction_words)
|
||||
instruction_values.push_back(format("%06o", i));
|
||||
|
||||
out.insert({ "instruction-values", instruction_values });
|
||||
|
||||
// R0-R5, SP, PC
|
||||
std::vector<std::string> registers;
|
||||
|
||||
for(int i=0; i<8; i++) {
|
||||
if (i < 6)
|
||||
registers.push_back(format("%06o", getRegister(i)));
|
||||
else if (i == 6)
|
||||
registers.push_back(format("%06o", sp[psw >> 14]));
|
||||
else
|
||||
registers.push_back(format("%06o", addr));
|
||||
}
|
||||
|
||||
out.insert({ "registers", registers });
|
||||
|
||||
// PSW
|
||||
std::string psw_str = format("%d%d|%d|%d|%c%c%c%c%c", psw >> 14, (psw >> 12) & 3, (psw >> 11) & 1, (psw >> 5) & 7,
|
||||
psw & 16?'t':'-', psw & 8?'n':'-', psw & 4?'z':'-', psw & 2 ? 'v':'-', psw & 1 ? 'c':'-');
|
||||
out.insert({ "psw", { psw_str } });
|
||||
|
||||
// values worked with
|
||||
std::vector<std::string> work_values_str;
|
||||
for(auto v : work_values)
|
||||
work_values_str.push_back(format("%06o", v));
|
||||
out.insert({ "work-values", work_values_str });
|
||||
|
||||
trace_output = old_trace_output;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void cpu::step()
|
||||
{
|
||||
instruction_count++;
|
||||
|
||||
check_queued_interrupts();
|
||||
if (check_queued_interrupts())
|
||||
return;
|
||||
|
||||
if (scheduled_trap) {
|
||||
trap(scheduled_trap, 7);
|
||||
|
||||
scheduled_trap = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t temp_pc = getPC();
|
||||
|
@ -1843,9 +1967,6 @@ void cpu::step()
|
|||
busError();
|
||||
|
||||
try {
|
||||
if (disas)
|
||||
disassemble();
|
||||
|
||||
uint16_t instr = b->readWord(temp_pc);
|
||||
|
||||
addRegister(7, false, 2);
|
||||
|
|
25
cpu.h
25
cpu.h
|
@ -6,13 +6,13 @@
|
|||
#include <map>
|
||||
#include <set>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
#include "bus.h"
|
||||
|
||||
class cpu
|
||||
{
|
||||
private:
|
||||
bool disas { false };
|
||||
uint16_t regs0_5[2][6]; // R0...5, selected by bit 11 in PSW,
|
||||
uint16_t sp[3 + 1]; // stackpointers, MF../MT.. select via 12/13 from PSW, others via 14/15
|
||||
uint16_t pc { 0 };
|
||||
|
@ -28,11 +28,13 @@ private:
|
|||
// level, vector
|
||||
std::map<uint8_t, std::set<uint8_t> > queued_interrupts;
|
||||
|
||||
std::set<uint16_t> breakpoints;
|
||||
|
||||
bus *const b { nullptr };
|
||||
|
||||
uint32_t *const event { nullptr };
|
||||
|
||||
void check_queued_interrupts();
|
||||
bool check_queued_interrupts();
|
||||
|
||||
uint16_t getRegister(const int nr, const bool MF_MT) const;
|
||||
void setRegister(const int nr, const bool MF_MT, const uint16_t value);
|
||||
|
@ -49,19 +51,32 @@ private:
|
|||
bool condition_code_operations(const uint16_t instr);
|
||||
bool misc_operations(const uint16_t instr);
|
||||
|
||||
std::pair<std::string, int> addressing_to_string(const uint8_t mode_register, const uint16_t pc);
|
||||
void disassemble();
|
||||
struct operand_parameters {
|
||||
std::string operand;
|
||||
int length;
|
||||
int instruction_part;
|
||||
uint16_t work_value;
|
||||
};
|
||||
|
||||
operand_parameters addressing_to_string(const uint8_t mode_register, const uint16_t pc, const bool word_mode) const;
|
||||
|
||||
public:
|
||||
explicit cpu(bus *const b, uint32_t *const event);
|
||||
~cpu();
|
||||
|
||||
void setDisassemble(const bool state);
|
||||
bool check_breakpoint();
|
||||
void set_breakpoint(const uint16_t addr);
|
||||
void remove_breakpoint(const uint16_t addr);
|
||||
std::set<uint16_t> list_breakpoints();
|
||||
|
||||
void disassemble(void) const;
|
||||
std::map<std::string, std::vector<std::string> > disassemble(const uint16_t addr) const;
|
||||
|
||||
bus *getBus() { return b; }
|
||||
|
||||
void emulation_start();
|
||||
uint64_t get_instructions_executed_count();
|
||||
std::pair<double, double> get_mips_rel_speed();
|
||||
|
||||
void reset();
|
||||
|
||||
|
|
201
debugger.cpp
Normal file
201
debugger.cpp
Normal file
|
@ -0,0 +1,201 @@
|
|||
#include "bus.h"
|
||||
#include "console.h"
|
||||
#include "cpu.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
#if defined(ESP32)
|
||||
void setBootLoader(bus *const b);
|
||||
#endif
|
||||
|
||||
extern uint32_t event;
|
||||
extern std::atomic_bool terminate;
|
||||
|
||||
// returns size of instruction (in bytes)
|
||||
int disassemble(cpu *const c, console *const cnsl, const int pc, const bool instruction_only)
|
||||
{
|
||||
auto data = c->disassemble(pc);
|
||||
|
||||
auto registers = data["registers"];
|
||||
auto psw = data["psw"][0];
|
||||
|
||||
std::string instruction_values;
|
||||
for(auto iv : data["instruction-values"])
|
||||
instruction_values += (instruction_values.empty() ? "" : ",") + iv;
|
||||
|
||||
std::string work_values;
|
||||
for(auto wv : data["work-values"])
|
||||
work_values += (work_values.empty() ? "" : ",") + wv;
|
||||
|
||||
std::string instruction = data["instruction-text"].at(0);
|
||||
|
||||
std::string result;
|
||||
|
||||
if (instruction_only)
|
||||
result = format("PC: %06o, instr: %s\t%s\t%s",
|
||||
pc,
|
||||
instruction_values.c_str(),
|
||||
instruction.c_str(),
|
||||
work_values.c_str()
|
||||
);
|
||||
else
|
||||
result = format("R0: %s, R1: %s, R2: %s, R3: %s, R4: %s, R5: %s, SP: %s, PC: %06o, PSW: %s, instr: %s: %s - %s",
|
||||
registers[0].c_str(), registers[1].c_str(), registers[2].c_str(), registers[3].c_str(), registers[4].c_str(), registers[5].c_str(),
|
||||
registers[6].c_str(), pc,
|
||||
psw.c_str(),
|
||||
instruction_values.c_str(),
|
||||
instruction.c_str(),
|
||||
work_values.c_str()
|
||||
);
|
||||
|
||||
if (cnsl)
|
||||
cnsl->debug(result);
|
||||
else
|
||||
fprintf(stderr, "%s\n", result.c_str());
|
||||
|
||||
return data["instruction-values"].size() * 2;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> split(const std::vector<std::string> & kv_array, const std::string & splitter)
|
||||
{
|
||||
std::map<std::string, std::string> out;
|
||||
|
||||
for(auto pair : kv_array) {
|
||||
auto kv = split(pair, splitter);
|
||||
|
||||
if (kv.size() == 1)
|
||||
out.insert({ kv[0], "" });
|
||||
else if (kv.size() == 2)
|
||||
out.insert({ kv[0], kv[1] });
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void debugger(console *const cnsl, bus *const b, std::atomic_bool *const interrupt_emulation, const bool tracing)
|
||||
{
|
||||
cpu *const c = b->getCpu();
|
||||
|
||||
bool single_step = false;
|
||||
|
||||
while(!terminate) {
|
||||
bool temp = terminate;
|
||||
std::string cmd = cnsl->read_line(format("%d%d", event, temp));
|
||||
auto parts = split(cmd, " ");
|
||||
auto kv = split(parts, "=");
|
||||
|
||||
if (parts.empty())
|
||||
continue;
|
||||
|
||||
if (cmd == "go")
|
||||
single_step = false;
|
||||
else if (cmd == "single" || cmd == "s")
|
||||
single_step = true;
|
||||
else if ((parts[0] == "sbp" || parts[0] == "cbp") && parts.size() == 2){
|
||||
uint16_t pc = std::stoi(parts[1].c_str(), nullptr, 8);
|
||||
|
||||
if (parts[0] == "sbp") {
|
||||
c->set_breakpoint(pc);
|
||||
|
||||
cnsl->put_string_lf(format("Set breakpoint at %06o", pc));
|
||||
}
|
||||
else {
|
||||
c->remove_breakpoint(pc);
|
||||
|
||||
cnsl->put_string_lf(format("Clear breakpoint at %06o", pc));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (cmd == "lbp") {
|
||||
auto bps = c->list_breakpoints();
|
||||
|
||||
cnsl->put_string_lf("Breakpoints:");
|
||||
|
||||
for(auto pc : bps) {
|
||||
cnsl->put_string_lf(format(" %06o", pc));
|
||||
|
||||
pc += disassemble(c, cnsl, pc, true);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (parts[0] == "disassemble" || parts[0] == "d") {
|
||||
int pc = kv.find("pc") != kv.end() ? std::stoi(kv.find("pc")->second, nullptr, 8) : c->getPC();
|
||||
int n = kv.find("n") != kv.end() ? std::stoi(kv.find("n") ->second, nullptr, 10) : 1;
|
||||
|
||||
bool show_registers = kv.find("pc") == kv.end();
|
||||
|
||||
for(int i=0; i<n; i++) {
|
||||
pc += disassemble(c, cnsl, pc, !show_registers);
|
||||
|
||||
show_registers = false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (cmd == "reset" || cmd == "r") {
|
||||
terminate = false;
|
||||
|
||||
event = 0;
|
||||
|
||||
c->reset();
|
||||
|
||||
#if defined(ESP32)
|
||||
setBootLoader(b);
|
||||
#endif
|
||||
|
||||
continue;
|
||||
}
|
||||
#if !defined(ESP32)
|
||||
else if (cmd == "quit" || cmd == "q")
|
||||
break;
|
||||
#endif
|
||||
else if (cmd == "help" || cmd == "h" || cmd == "?") {
|
||||
cnsl->put_string_lf("disassemble/d - show current instruction (pc=/n=)");
|
||||
cnsl->put_string_lf("go - run until trap or ^e");
|
||||
#if !defined(ESP32)
|
||||
cnsl->put_string_lf("quit/q - stop emulator");
|
||||
#endif
|
||||
cnsl->put_string_lf("reset/r - reset cpu/bus/etc");
|
||||
cnsl->put_string_lf("single/s - run 1 instruction (implicit 'disassemble' command)");
|
||||
cnsl->put_string_lf("sbp/cbp/lbp - set/clear/list breakpoint(s)");
|
||||
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
cnsl->put_string_lf("?");
|
||||
continue;
|
||||
}
|
||||
|
||||
c->emulation_start();
|
||||
|
||||
while(!event && !terminate && !*interrupt_emulation) {
|
||||
if (tracing || single_step)
|
||||
disassemble(c, single_step ? cnsl : nullptr, c->getPC(), false);
|
||||
|
||||
if (c->check_breakpoint() && !single_step) {
|
||||
cnsl->put_string_lf("Breakpoint");
|
||||
break;
|
||||
}
|
||||
|
||||
c->step();
|
||||
|
||||
if (single_step)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!single_step) {
|
||||
auto speed = c->get_mips_rel_speed();
|
||||
cnsl->debug("MIPS: %.2f, relative speed: %.2f%%", speed.first, speed.second);
|
||||
}
|
||||
|
||||
if (*interrupt_emulation) {
|
||||
single_step = true;
|
||||
|
||||
event = 0;
|
||||
|
||||
*interrupt_emulation = false;
|
||||
}
|
||||
}
|
||||
}
|
4
debugger.h
Normal file
4
debugger.h
Normal file
|
@ -0,0 +1,4 @@
|
|||
#include "bus.h"
|
||||
#include "console.h"
|
||||
|
||||
void debugger(console *const cnsl, bus *const b, std::atomic_bool *const interrupt_emulation, const bool tracing);
|
6
gen.h
6
gen.h
|
@ -1,10 +1,14 @@
|
|||
// (C) 2018-2022 by Folkert van Heusden
|
||||
// Released under Apache License v2.0
|
||||
#pragma once
|
||||
|
||||
extern bool trace_output;
|
||||
|
||||
#if defined(ESP32)
|
||||
#define D(...) do { } while(0);
|
||||
#else
|
||||
#ifndef NDEBUG
|
||||
#define D(x) do { x } while(0);
|
||||
#define D(x) do { if (trace_output) { x } } while(0);
|
||||
#else
|
||||
#define D(...) do { } while(0);
|
||||
#endif
|
||||
|
|
50
main.cpp
50
main.cpp
|
@ -3,6 +3,7 @@
|
|||
#include <atomic>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
@ -10,6 +11,7 @@
|
|||
#include "console_ncurses.h"
|
||||
#include "console_posix.h"
|
||||
#include "cpu.h"
|
||||
#include "debugger.h"
|
||||
#include "gen.h"
|
||||
#include "memory.h"
|
||||
#include "terminal.h"
|
||||
|
@ -18,10 +20,11 @@
|
|||
#include "utils.h"
|
||||
|
||||
|
||||
bool withUI { false };
|
||||
uint32_t event { 0 };
|
||||
std::atomic_bool terminate { false };
|
||||
std::atomic_bool *running { nullptr };
|
||||
bool withUI { false };
|
||||
uint32_t event { 0 };
|
||||
std::atomic_bool terminate { false };
|
||||
std::atomic_bool *running { nullptr };
|
||||
bool trace_output { false };
|
||||
|
||||
void loadbin(bus *const b, uint16_t base, const char *const file)
|
||||
{
|
||||
|
@ -147,7 +150,8 @@ void help()
|
|||
printf("-p 123 set CPU start pointer to decimal(!) value\n");
|
||||
printf("-L f.bin load file into memory at address given by -p (and run it)\n");
|
||||
printf("-n ncurses UI\n");
|
||||
printf("-d enable disassemble\n");
|
||||
printf("-d enable debugger\n");
|
||||
printf("-t enable tracing (disassemble to stderr, requires -d as well)\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
|
@ -161,9 +165,11 @@ int main(int argc, char *argv[])
|
|||
c -> setEmulateMFPT(true);
|
||||
|
||||
std::vector<std::string> rk05_files;
|
||||
bool testCases = false;
|
||||
int opt = -1;
|
||||
while((opt = getopt(argc, argv, "hm:T:R:p:ndL:")) != -1)
|
||||
bool testCases = false;
|
||||
bool run_debugger = false;
|
||||
bool tracing = false;
|
||||
int opt = -1;
|
||||
while((opt = getopt(argc, argv, "hm:T:R:p:ndtL:")) != -1)
|
||||
{
|
||||
switch(opt) {
|
||||
case 'h':
|
||||
|
@ -171,7 +177,12 @@ int main(int argc, char *argv[])
|
|||
return 1;
|
||||
|
||||
case 'd':
|
||||
c->setDisassemble(true);
|
||||
run_debugger = true;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
tracing = true;
|
||||
trace_output = true;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
|
@ -211,14 +222,16 @@ int main(int argc, char *argv[])
|
|||
|
||||
console *cnsl = nullptr;
|
||||
|
||||
std::atomic_bool interrupt_emulation { false };
|
||||
|
||||
if (withUI)
|
||||
cnsl = new console_ncurses(&terminate, b);
|
||||
cnsl = new console_ncurses(&terminate, &interrupt_emulation, b);
|
||||
else {
|
||||
fprintf(stderr, "This PDP-11 emulator is called \"kek\" (reason for that is forgotten) and was written by Folkert van Heusden.\n");
|
||||
|
||||
fprintf(stderr, "Built on: " __DATE__ " " __TIME__ "\n");
|
||||
|
||||
cnsl = new console_posix(&terminate, b);
|
||||
cnsl = new console_posix(&terminate, &interrupt_emulation, b);
|
||||
}
|
||||
|
||||
if (rk05_files.empty() == false) {
|
||||
|
@ -243,9 +256,8 @@ int main(int argc, char *argv[])
|
|||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_RESTART;
|
||||
|
||||
if (withUI) {
|
||||
if (withUI)
|
||||
sigaction(SIGWINCH, &sa, nullptr);
|
||||
}
|
||||
|
||||
sigaction(SIGTERM, &sa, nullptr);
|
||||
sigaction(SIGINT , &sa, nullptr);
|
||||
|
@ -255,10 +267,16 @@ int main(int argc, char *argv[])
|
|||
// loadbin(b, 0, "test.dat");
|
||||
// c->setRegister(7, 0);
|
||||
|
||||
c->emulation_start(); // for statistics
|
||||
cnsl->start_thread();
|
||||
|
||||
while(!event && !terminate)
|
||||
c->step();
|
||||
if (run_debugger)
|
||||
debugger(cnsl, b, &interrupt_emulation, tracing);
|
||||
else {
|
||||
c->emulation_start(); // for statistics
|
||||
|
||||
while(!event && !terminate)
|
||||
c->step();
|
||||
}
|
||||
|
||||
*running = false;
|
||||
|
||||
|
|
61
rk05.cpp
61
rk05.cpp
|
@ -6,9 +6,6 @@
|
|||
#include "bus.h"
|
||||
#include "cpu.h"
|
||||
#include "error.h"
|
||||
#if defined(ESP32)
|
||||
#include "esp32.h"
|
||||
#endif
|
||||
#include "gen.h"
|
||||
#include "rk05.h"
|
||||
#include "utils.h"
|
||||
|
@ -32,56 +29,34 @@ rk05::rk05(const std::vector<std::string> & files, bus *const b, std::atomic_boo
|
|||
memset(registers, 0x00, sizeof registers);
|
||||
memset(xfer_buffer, 0x00, sizeof xfer_buffer);
|
||||
|
||||
#if defined(ESP32)
|
||||
Serial.print(F("MISO: "));
|
||||
Serial.println(int(MISO));
|
||||
Serial.print(F("MOSI: "));
|
||||
Serial.println(int(MOSI));
|
||||
Serial.print(F("SCK : "));
|
||||
Serial.println(int(SCK));
|
||||
Serial.print(F("SS : "));
|
||||
Serial.println(int(SS));
|
||||
|
||||
Serial.println(F("Files on SD-card:"));
|
||||
|
||||
if (!sd.begin(SS, SD_SCK_MHZ(15)))
|
||||
sd.initErrorHalt();
|
||||
|
||||
for(;;) {
|
||||
sd.ls("/", LS_DATE | LS_SIZE | LS_R);
|
||||
|
||||
while(Serial.available())
|
||||
Serial.read();
|
||||
|
||||
std::string selected_file = read_terminal_line("Enter filename: ");
|
||||
|
||||
Serial.print(F("Opening file: "));
|
||||
Serial.println(selected_file.c_str());
|
||||
|
||||
if (fh.open(selected_file.c_str(), O_RDWR))
|
||||
break;
|
||||
|
||||
Serial.println(F("rk05: open failed"));
|
||||
}
|
||||
#else
|
||||
for(auto file : files) {
|
||||
#if defined(ESP32)
|
||||
File32 *fh = new File32();
|
||||
|
||||
if (!fh->open(file.c_str(), O_RDWR)) {
|
||||
delete fh;
|
||||
error_exit(true, "rk05: cannot open \"%s\"", file.c_str());
|
||||
}
|
||||
#else
|
||||
FILE *fh = fopen(file.c_str(), "rb");
|
||||
if (!fh)
|
||||
error_exit(true, "rk05: cannot open \"%s\"", file.c_str());
|
||||
#endif
|
||||
|
||||
fhs.push_back(fh);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
rk05::~rk05()
|
||||
{
|
||||
for(auto fh : fhs) {
|
||||
#if defined(ESP32)
|
||||
fh.close();
|
||||
fh->close();
|
||||
delete fh;
|
||||
#else
|
||||
for(auto fh : fhs)
|
||||
fclose(fh);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t rk05::readByte(const uint16_t addr)
|
||||
|
@ -183,9 +158,10 @@ void rk05::writeWord(const uint16_t addr, uint16_t v)
|
|||
xfer_buffer[i] = b->readUnibusByte(memoff + i);
|
||||
|
||||
#if defined(ESP32)
|
||||
if (!fh.seek(diskoffb))
|
||||
File32 *fh = fhs.at(device);
|
||||
if (!fh->seek(diskoffb))
|
||||
fprintf(stderr, "RK05 seek error %s\n", strerror(errno));
|
||||
if (fh.write(xfer_buffer, reclen) != reclen)
|
||||
if (fh->write(xfer_buffer, reclen) != reclen)
|
||||
fprintf(stderr, "RK05 fwrite error %s\n", strerror(errno));
|
||||
#else
|
||||
FILE *fh = fhs.at(device);
|
||||
|
@ -220,7 +196,8 @@ void rk05::writeWord(const uint16_t addr, uint16_t v)
|
|||
bool proceed = true;
|
||||
|
||||
#if defined(ESP32)
|
||||
if (!fh.seek(diskoffb)) {
|
||||
File32 *fh = fhs.at(device);
|
||||
if (!fh->seek(diskoffb)) {
|
||||
fprintf(stderr, "RK05 seek error %s\n", strerror(errno));
|
||||
proceed = false;
|
||||
}
|
||||
|
@ -247,7 +224,7 @@ void rk05::writeWord(const uint16_t addr, uint16_t v)
|
|||
#if defined(ESP32)
|
||||
yield();
|
||||
|
||||
if (fh.read(xfer_buffer, cur) != size_t(cur))
|
||||
if (fh->read(xfer_buffer, cur) != size_t(cur))
|
||||
D(fprintf(stderr, "RK05 fread error: %s\n", strerror(errno));)
|
||||
#else
|
||||
if (fread(xfer_buffer, 1, cur, fh) != size_t(cur))
|
||||
|
|
9
rk05.h
9
rk05.h
|
@ -9,12 +9,10 @@
|
|||
#include <vector>
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <SPI.h>
|
||||
#define USE_SDFAT
|
||||
#define SD_FAT_TYPE 1
|
||||
#include <SdFat.h>
|
||||
#include "esp32.h"
|
||||
#endif
|
||||
|
||||
|
||||
// FIXME namen van defines
|
||||
#define RK05_DS 0177400 // drive status
|
||||
#define RK05_ERROR 0177402 // error
|
||||
|
@ -35,8 +33,7 @@ private:
|
|||
uint16_t registers[7];
|
||||
uint8_t xfer_buffer[512];
|
||||
#if defined(ESP32)
|
||||
SdFat32 sd;
|
||||
File32 fh;
|
||||
std::vector<File32 *> fhs;
|
||||
#else
|
||||
std::vector<FILE *> fhs;
|
||||
#endif
|
||||
|
|
|
@ -53,7 +53,7 @@ void init_ncurses(bool init_mouse)
|
|||
noecho();
|
||||
nonl();
|
||||
refresh();
|
||||
nodelay(stdscr, FALSE);
|
||||
nodelay(stdscr, TRUE);
|
||||
meta(stdscr, TRUE); /* enable 8-bit input */
|
||||
raw(); /* to be able to catch ctrl+c */
|
||||
leaveok(stdscr, FALSE);
|
||||
|
|
65
tests/mfpi.mac
Normal file
65
tests/mfpi.mac
Normal file
|
@ -0,0 +1,65 @@
|
|||
; make sure current run-mode is kernel and previous is user
|
||||
MOV #0177776,R0
|
||||
MOV #030000,(R0)
|
||||
NOP
|
||||
|
||||
; initialize kernel- and user stackpointers
|
||||
; kernel
|
||||
MOV #1000,R6
|
||||
;; user
|
||||
; MOV #0177717,R0
|
||||
; MOV #1000,(R0)
|
||||
|
||||
; user: 060000-080000 will be mapped to physical address 020000
|
||||
MOV #0177646,R0
|
||||
; 020000 / 0100 (0100 => 64 decimal)
|
||||
MOV #0200,(R0)
|
||||
NOP
|
||||
|
||||
; user: make sure write- and read-access is possible
|
||||
MOV #0177606,R0
|
||||
MOV #077406,(R0)
|
||||
|
||||
; kernel: flat mapping
|
||||
; 0-010000 -> 0
|
||||
MOV #0172340,R0
|
||||
MOV #0000,(R0)
|
||||
; 060000-0100000 -> 060000
|
||||
MOV #0172346,R0
|
||||
MOV #0600,(R0)
|
||||
NOP
|
||||
|
||||
; kernel: make sure write- and read-access is possible
|
||||
; 0-010000
|
||||
MOV #0172300,R0
|
||||
MOV #077406,(R0)
|
||||
; 060000-0100000
|
||||
MOV #0172306,R0
|
||||
MOV #077406,(R0)
|
||||
|
||||
; place a value at 020000 kernelspace which is
|
||||
; 060000 in userspace
|
||||
MOV #020000,R0
|
||||
MOV #01234,(R0)
|
||||
|
||||
; MRR0
|
||||
MOV #0177572,R0
|
||||
; enable MMU traps
|
||||
BIS #512,(R0)
|
||||
; enable MMU
|
||||
BIS #1,(R0)
|
||||
|
||||
; get word from 060000 in userspace and put that on
|
||||
; the kernel stack
|
||||
MOV #060000,R0
|
||||
MFPI (R0)
|
||||
NOP
|
||||
|
||||
; check for 01234
|
||||
MOV (SP)+,R0
|
||||
CMP #01234,R0
|
||||
NOP
|
||||
|
||||
HALT
|
||||
|
||||
make_raw
|
1
tty.h
1
tty.h
|
@ -25,7 +25,6 @@ private:
|
|||
bool have_char_1 { false }; // RCVR BUSY bit high (11)
|
||||
bool have_char_2 { false }; // RCVR DONE bit high (7)
|
||||
uint16_t registers[4] { 0 };
|
||||
bool withUI { false };
|
||||
|
||||
public:
|
||||
tty(console *const c);
|
||||
|
|
39
utils.cpp
39
utils.cpp
|
@ -8,6 +8,7 @@
|
|||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <sys/time.h>
|
||||
|
||||
void setBit(uint16_t & v, const int bit, const bool vb)
|
||||
|
@ -74,3 +75,41 @@ void myusleep(uint64_t us)
|
|||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<std::string> split(std::string in, std::string splitter)
|
||||
{
|
||||
std::vector<std::string> out;
|
||||
size_t splitter_size = splitter.size();
|
||||
|
||||
for(;;)
|
||||
{
|
||||
size_t pos = in.find(splitter);
|
||||
if (pos == std::string::npos)
|
||||
break;
|
||||
|
||||
std::string before = in.substr(0, pos);
|
||||
if (!before.empty())
|
||||
out.push_back(before);
|
||||
|
||||
size_t bytes_left = in.size() - (pos + splitter_size);
|
||||
if (bytes_left == 0)
|
||||
return out;
|
||||
|
||||
in = in.substr(pos + splitter_size);
|
||||
}
|
||||
|
||||
if (in.size() > 0)
|
||||
out.push_back(in);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void set_thread_name(std::string name)
|
||||
{
|
||||
#if !defined(ESP32)
|
||||
if (name.length() > 15)
|
||||
name = name.substr(0, 15);
|
||||
|
||||
pthread_setname_np(pthread_self(), name.c_str());
|
||||
#endif
|
||||
}
|
||||
|
|
7
utils.h
7
utils.h
|
@ -1,7 +1,8 @@
|
|||
// (C) 2018 by Folkert van Heusden
|
||||
// (C) 2018-2022 by Folkert van Heusden
|
||||
// Released under Apache License v2.0
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
void setBit(uint16_t & v, const int bit, const bool vb);
|
||||
int parity(int v);
|
||||
|
@ -10,5 +11,9 @@ int parity(int v);
|
|||
|
||||
std::string format(const char *const fmt, ...);
|
||||
|
||||
std::vector<std::string> split(std::string in, std::string splitter);
|
||||
|
||||
unsigned long get_ms();
|
||||
void myusleep(uint64_t us);
|
||||
|
||||
void set_thread_name(std::string name);
|
||||
|
|
Loading…
Add table
Reference in a new issue