final version
This commit is contained in:
parent
098766bb65
commit
042bae9089
8 changed files with 143 additions and 21 deletions
|
|
@ -1,8 +1,9 @@
|
||||||
# Simple Makefile wrapper to configure, build and run the CMake project.
|
# Simple Makefile wrapper to configure, build and run the CMake project.
|
||||||
# Usage:
|
# Usage:
|
||||||
# make # builds (default)
|
# make # configure + build with all cores
|
||||||
# make build # configure + build
|
# make build # configure + build with all cores
|
||||||
# make run # build (if needed) and run the executable
|
# make all # clean + configure + build + run
|
||||||
|
# make run # just run the executable (no build)
|
||||||
# make clean # run CMake clean (or remove build files)
|
# make clean # run CMake clean (or remove build files)
|
||||||
# make distclean # remove build dir and generated targets
|
# make distclean # remove build dir and generated targets
|
||||||
|
|
||||||
|
|
@ -11,27 +12,33 @@ BUILD_DIR := build
|
||||||
CMAKE_BUILD_TYPE ?= Release
|
CMAKE_BUILD_TYPE ?= Release
|
||||||
TARGET := target/bin/simulator_exec
|
TARGET := target/bin/simulator_exec
|
||||||
GUI_TARGET := target/bin/simulator_qt
|
GUI_TARGET := target/bin/simulator_qt
|
||||||
|
NPROC := $(shell nproc)
|
||||||
|
|
||||||
.PHONY: all configure build run clean distclean
|
.PHONY: all configure build run clean distclean
|
||||||
|
|
||||||
all: build
|
# Default target: just build
|
||||||
|
default: build
|
||||||
|
|
||||||
|
# make all: clean, build, then run
|
||||||
|
all: clean build run
|
||||||
|
|
||||||
configure:
|
configure:
|
||||||
@echo "Configuring (build dir: $(BUILD_DIR), type: $(CMAKE_BUILD_TYPE))"
|
@echo "Configuring (build dir: $(BUILD_DIR), type: $(CMAKE_BUILD_TYPE))"
|
||||||
$(CMAKE) -S . -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE)
|
$(CMAKE) -S . -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE)
|
||||||
|
|
||||||
build: configure
|
build: configure
|
||||||
@echo "Building..."
|
@echo "Building with $(NPROC) cores..."
|
||||||
$(CMAKE) --build $(BUILD_DIR) -j$(shell nproc)
|
$(CMAKE) --build $(BUILD_DIR) -j$(NPROC)
|
||||||
|
|
||||||
run: build
|
# make run: just launch the executable (no build)
|
||||||
|
run:
|
||||||
@echo "Running primary target..."
|
@echo "Running primary target..."
|
||||||
# Prefer GUI if avail able, otherwise fall back to console executable
|
# Prefer GUI if available, otherwise fall back to console executable
|
||||||
@if [ -x "$(GUI_TARGET)" ]; then \
|
@if [ -x "$(GUI_TARGET)" ]; then \
|
||||||
echo "Launching GUI: $(GUI_TARGET)"; \
|
echo "Launching GUI: $(GUI_TARGET)"; \
|
||||||
sh -c 'nohup env QT_QPA_PLATFORM=xcb ./$(GUI_TARGET) >/dev/null 2>&1 & echo $! > "$(BUILD_DIR)/simulator_qt.pid"'; \
|
./$(GUI_TARGET); \
|
||||||
elif [ -x "$(TARGET)" ]; then \
|
elif [ -x "$(TARGET)" ]; then \
|
||||||
@./$(TARGET); \
|
./$(TARGET); \
|
||||||
else \
|
else \
|
||||||
echo "No runnable target found (tried $(GUI_TARGET) and $(TARGET))."; exit 1; \
|
echo "No runnable target found (tried $(GUI_TARGET) and $(TARGET))."; exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
@ -53,7 +60,9 @@ build-gui: configure
|
||||||
clean:
|
clean:
|
||||||
@echo "Cleaning build (CMake clean)..."
|
@echo "Cleaning build (CMake clean)..."
|
||||||
-$(CMAKE) --build $(BUILD_DIR) --target clean || true
|
-$(CMAKE) --build $(BUILD_DIR) --target clean || true
|
||||||
|
@echo "Removing target directory..."
|
||||||
|
-rm -rf target/
|
||||||
|
|
||||||
distclean:
|
distclean:
|
||||||
@echo "Removing build artifacts and generated files..."
|
@echo "Removing build artifacts and generated files..."
|
||||||
-rm -rf $(BUILD_DIR) CMakeFiles CMakeCache.txt cmake_install.cmake target/bin/*
|
-rm -rf $(BUILD_DIR) CMakeFiles CMakeCache.txt cmake_install.cmake target/
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,15 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
MachineController::MachineController(std::shared_ptr<Machine> machine, QObject *parent)
|
MachineController::MachineController(std::shared_ptr<Machine> machine, QObject *parent)
|
||||||
: QObject(parent), m_machine(std::move(machine))
|
: QObject(parent), m_machine(std::move(machine))
|
||||||
{
|
{
|
||||||
if (!m_machine) {
|
if (!m_machine) {
|
||||||
m_machine = std::make_shared<Machine>();
|
m_machine = std::make_shared<Machine>();
|
||||||
}
|
}
|
||||||
|
m_lastUpdateTime = steady_clock::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
MachineController::~MachineController() {
|
MachineController::~MachineController() {
|
||||||
|
|
@ -38,14 +41,27 @@ void MachineController::step() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MachineController::runLoop() {
|
void MachineController::runLoop() {
|
||||||
|
const auto minUpdateInterval = milliseconds(16);
|
||||||
|
|
||||||
while (m_running.load()) {
|
while (m_running.load()) {
|
||||||
try {
|
try {
|
||||||
if (m_machine) {
|
if (m_machine) {
|
||||||
m_machine->execute();
|
m_machine->execute();
|
||||||
m_machine->tick();
|
m_machine->tick();
|
||||||
emit tick();
|
m_ticksSinceLastUpdate++;
|
||||||
|
|
||||||
|
// Throttle GUI updates to 60 Hz
|
||||||
|
auto now = steady_clock::now();
|
||||||
|
auto elapsed = duration_cast<milliseconds>(now - m_lastUpdateTime);
|
||||||
|
|
||||||
|
if (elapsed >= minUpdateInterval) {
|
||||||
|
emit tick();
|
||||||
|
m_lastUpdateTime = now;
|
||||||
|
m_ticksSinceLastUpdate = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_machine->isStopped()) {
|
if (m_machine->isStopped()) {
|
||||||
|
emit tick();
|
||||||
m_running.store(false);
|
m_running.store(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ private:
|
||||||
std::atomic<bool> m_running{false};
|
std::atomic<bool> m_running{false};
|
||||||
std::thread m_thread;
|
std::thread m_thread;
|
||||||
std::shared_ptr<Machine> m_machine;
|
std::shared_ptr<Machine> m_machine;
|
||||||
|
std::atomic<int> m_ticksSinceLastUpdate{0};
|
||||||
|
std::chrono::steady_clock::time_point m_lastUpdateTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MACHINECONTROLLER_H
|
#endif // MACHINECONTROLLER_H
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
loadInstructionSet();
|
loadInstructionSet();
|
||||||
|
|
||||||
|
qputenv("QT_QPA_PLATFORM", "xcb");
|
||||||
|
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
MainWindow w;
|
MainWindow w;
|
||||||
w.show();
|
w.show();
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,8 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||||
|
|
||||||
// Connect menu actions
|
// Connect menu actions
|
||||||
connect(ui->actionLoad_Object_File, &QAction::triggered, this, &MainWindow::loadObjectFile);
|
connect(ui->actionLoad_Object_File, &QAction::triggered, this, &MainWindow::loadObjectFile);
|
||||||
|
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::showAboutDialog);
|
||||||
|
connect(ui->actionFrequency, &QAction::triggered, this, &MainWindow::showFrequencyDialog);
|
||||||
|
|
||||||
setupMemoryDisplay();
|
setupMemoryDisplay();
|
||||||
setupDisassemblyDisplay();
|
setupDisassemblyDisplay();
|
||||||
|
|
@ -978,3 +980,51 @@ void MainWindow::loadObjectFile()
|
||||||
tr("Failed to load object file: %1").arg(e.what()));
|
tr("Failed to load object file: %1").arg(e.what()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::showAboutDialog()
|
||||||
|
{
|
||||||
|
QMessageBox::about(this, tr("About SIC/XE Simulator"),
|
||||||
|
tr("SIC/XE Simulator\nby Zan Skvarca\n2025"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::showFrequencyDialog()
|
||||||
|
{
|
||||||
|
if (!m_machine) return;
|
||||||
|
|
||||||
|
QDialog dialog(this);
|
||||||
|
dialog.setWindowTitle(tr("Set Frequency"));
|
||||||
|
dialog.setModal(true);
|
||||||
|
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout(&dialog);
|
||||||
|
|
||||||
|
QLabel *currentLabel = new QLabel(tr("Current frequency: %1 Hz").arg(m_machine->getSpeed()), &dialog);
|
||||||
|
layout->addWidget(currentLabel);
|
||||||
|
|
||||||
|
QLabel *newLabel = new QLabel(tr("New frequency (Hz):"), &dialog);
|
||||||
|
layout->addWidget(newLabel);
|
||||||
|
|
||||||
|
QLineEdit *freqInput = new QLineEdit(&dialog);
|
||||||
|
freqInput->setValidator(new QIntValidator(1, 1000000000, &dialog));
|
||||||
|
freqInput->setText(QString::number(m_machine->getSpeed()));
|
||||||
|
layout->addWidget(freqInput);
|
||||||
|
|
||||||
|
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
||||||
|
QPushButton *submitBtn = new QPushButton(tr("Submit"), &dialog);
|
||||||
|
QPushButton *cancelBtn = new QPushButton(tr("Cancel"), &dialog);
|
||||||
|
buttonLayout->addWidget(submitBtn);
|
||||||
|
buttonLayout->addWidget(cancelBtn);
|
||||||
|
layout->addLayout(buttonLayout);
|
||||||
|
|
||||||
|
connect(submitBtn, &QPushButton::clicked, &dialog, &QDialog::accept);
|
||||||
|
connect(cancelBtn, &QPushButton::clicked, &dialog, &QDialog::reject);
|
||||||
|
|
||||||
|
if (dialog.exec() == QDialog::Accepted) {
|
||||||
|
bool ok;
|
||||||
|
int newFreq = freqInput->text().toInt(&ok);
|
||||||
|
if (ok && newFreq > 0) {
|
||||||
|
m_machine->setSpeed(newFreq);
|
||||||
|
QMessageBox::information(this, tr("Success"),
|
||||||
|
tr("Frequency set to %1 Hz").arg(newFreq));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ private slots:
|
||||||
void onDisassemblyGoToStart();
|
void onDisassemblyGoToStart();
|
||||||
void onDisassemblyGoToEnd();
|
void onDisassemblyGoToEnd();
|
||||||
void loadObjectFile();
|
void loadObjectFile();
|
||||||
|
void showAboutDialog();
|
||||||
|
void showFrequencyDialog();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::MainWindow *ui;
|
Ui::MainWindow *ui;
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,14 @@ void divf_handler(Machine &m, int ea, AddressingMode mode)
|
||||||
void j_handler(Machine &m, int ea, AddressingMode mode)
|
void j_handler(Machine &m, int ea, AddressingMode mode)
|
||||||
{
|
{
|
||||||
int target = resolveJumpTarget(m, ea, mode);
|
int target = resolveJumpTarget(m, ea, mode);
|
||||||
|
|
||||||
|
int instrSize = 3;
|
||||||
|
int instrAddr = m.getPC() - instrSize;
|
||||||
|
// Check if jumping to itself (halt pattern)
|
||||||
|
if (target == instrAddr) {
|
||||||
|
m.halt();
|
||||||
|
}
|
||||||
|
|
||||||
m.setPC(target);
|
m.setPC(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,6 +273,8 @@ void ldch_handler(Machine &m, int ea, AddressingMode mode)
|
||||||
int val;
|
int val;
|
||||||
if (mode == AddressingMode::IMMEDIATE) {
|
if (mode == AddressingMode::IMMEDIATE) {
|
||||||
val = ea & 0xFF;
|
val = ea & 0xFF;
|
||||||
|
} else if (mode == AddressingMode::INDIRECT) {
|
||||||
|
val = m.getByte(m.getWord(ea));
|
||||||
} else {
|
} else {
|
||||||
val = m.getByte(ea);
|
val = m.getByte(ea);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,15 +95,45 @@ int main()
|
||||||
|
|
||||||
loadInstructionSet();
|
loadInstructionSet();
|
||||||
std::shared_ptr<Machine> machine = std::make_shared<Machine>();
|
std::shared_ptr<Machine> machine = std::make_shared<Machine>();
|
||||||
Loader loader(machine, std::string(PATH_RESOURCES) + "test.obj");
|
Loader loader(machine, std::string(PATH_RESOURCES) + "rec.obj");
|
||||||
loader.load();
|
loader.load();
|
||||||
machine->execute();
|
|
||||||
machine->execute();
|
cout << "=== Starting execution of rec.obj ===" << endl;
|
||||||
machine->execute();
|
cout << "Initial PC: 0x" << std::hex << machine->getPC() << std::dec << endl;
|
||||||
machine->execute();
|
|
||||||
machine->execute();
|
int maxSteps = 10000;
|
||||||
machine->execute();
|
for (int step = 0; step < maxSteps; step++) {
|
||||||
cout << "Register A after execution: " << machine->getA() << endl;
|
int pc = machine->getPC();
|
||||||
|
int opcode = machine->getByte(pc) & 0xFC;
|
||||||
|
const char* instName = (opcode < 256 && instructions[opcode].type != InstructionType::INVALID)
|
||||||
|
? instructions[opcode].name : "???";
|
||||||
|
|
||||||
|
int regA = machine->getA();
|
||||||
|
int regB = machine->getB();
|
||||||
|
int regX = machine->getX();
|
||||||
|
int regL = machine->getL();
|
||||||
|
int sw = machine->getSW();
|
||||||
|
|
||||||
|
printf("Step %4d: PC=0x%05X %-6s A=0x%06X B=0x%06X X=0x%06X L=0x%06X SW=0x%06X\n",
|
||||||
|
step, pc, instName, regA, regB, regX, regL, sw);
|
||||||
|
|
||||||
|
machine->execute();
|
||||||
|
|
||||||
|
if (machine->isStopped()) {
|
||||||
|
cout << "Machine halted at step " << step << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step > 100 && machine->getPC() == pc) {
|
||||||
|
cout << "ERROR: Infinite loop detected at PC=0x" << std::hex << pc << std::dec << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "\n=== Final state ===" << endl;
|
||||||
|
cout << "A: " << machine->getA() << endl;
|
||||||
|
cout << "B: " << machine->getB() << endl;
|
||||||
|
cout << "X: " << machine->getX() << endl;
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue