diff --git a/simulator_SIC_XE/Makefile b/simulator_SIC_XE/Makefile index 92ab3cc..93bc1f6 100644 --- a/simulator_SIC_XE/Makefile +++ b/simulator_SIC_XE/Makefile @@ -1,8 +1,9 @@ # Simple Makefile wrapper to configure, build and run the CMake project. # Usage: -# make # builds (default) -# make build # configure + build -# make run # build (if needed) and run the executable +# make # configure + build with all cores +# make build # configure + build with all cores +# make all # clean + configure + build + run +# make run # just run the executable (no build) # make clean # run CMake clean (or remove build files) # make distclean # remove build dir and generated targets @@ -11,27 +12,33 @@ BUILD_DIR := build CMAKE_BUILD_TYPE ?= Release TARGET := target/bin/simulator_exec GUI_TARGET := target/bin/simulator_qt +NPROC := $(shell nproc) .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: @echo "Configuring (build dir: $(BUILD_DIR), type: $(CMAKE_BUILD_TYPE))" $(CMAKE) -S . -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) build: configure - @echo "Building..." - $(CMAKE) --build $(BUILD_DIR) -j$(shell nproc) + @echo "Building with $(NPROC) cores..." + $(CMAKE) --build $(BUILD_DIR) -j$(NPROC) -run: build +# make run: just launch the executable (no build) +run: @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 \ 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 \ - @./$(TARGET); \ + ./$(TARGET); \ else \ echo "No runnable target found (tried $(GUI_TARGET) and $(TARGET))."; exit 1; \ fi @@ -53,7 +60,9 @@ build-gui: configure clean: @echo "Cleaning build (CMake clean)..." -$(CMAKE) --build $(BUILD_DIR) --target clean || true + @echo "Removing target directory..." + -rm -rf target/ distclean: @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/ diff --git a/simulator_SIC_XE/gui/qt/MachineController.cpp b/simulator_SIC_XE/gui/qt/MachineController.cpp index 9861e0c..881e97d 100644 --- a/simulator_SIC_XE/gui/qt/MachineController.cpp +++ b/simulator_SIC_XE/gui/qt/MachineController.cpp @@ -3,12 +3,15 @@ #include #include +using namespace std::chrono; + MachineController::MachineController(std::shared_ptr machine, QObject *parent) : QObject(parent), m_machine(std::move(machine)) { if (!m_machine) { m_machine = std::make_shared(); } + m_lastUpdateTime = steady_clock::now(); } MachineController::~MachineController() { @@ -38,14 +41,27 @@ void MachineController::step() { } void MachineController::runLoop() { + const auto minUpdateInterval = milliseconds(16); + while (m_running.load()) { try { if (m_machine) { m_machine->execute(); - m_machine->tick(); - emit tick(); + m_machine->tick(); + m_ticksSinceLastUpdate++; + + // Throttle GUI updates to 60 Hz + auto now = steady_clock::now(); + auto elapsed = duration_cast(now - m_lastUpdateTime); + + if (elapsed >= minUpdateInterval) { + emit tick(); + m_lastUpdateTime = now; + m_ticksSinceLastUpdate = 0; + } if (m_machine->isStopped()) { + emit tick(); m_running.store(false); break; } diff --git a/simulator_SIC_XE/gui/qt/MachineController.h b/simulator_SIC_XE/gui/qt/MachineController.h index fa904a3..801b5f4 100644 --- a/simulator_SIC_XE/gui/qt/MachineController.h +++ b/simulator_SIC_XE/gui/qt/MachineController.h @@ -27,6 +27,8 @@ private: std::atomic m_running{false}; std::thread m_thread; std::shared_ptr m_machine; + std::atomic m_ticksSinceLastUpdate{0}; + std::chrono::steady_clock::time_point m_lastUpdateTime; }; #endif // MACHINECONTROLLER_H diff --git a/simulator_SIC_XE/gui/qt/main.cpp b/simulator_SIC_XE/gui/qt/main.cpp index f9b6517..707d916 100644 --- a/simulator_SIC_XE/gui/qt/main.cpp +++ b/simulator_SIC_XE/gui/qt/main.cpp @@ -4,6 +4,9 @@ int main(int argc, char **argv) { loadInstructionSet(); + + qputenv("QT_QPA_PLATFORM", "xcb"); + QApplication app(argc, argv); MainWindow w; w.show(); diff --git a/simulator_SIC_XE/gui/qt/mainwindow.cpp b/simulator_SIC_XE/gui/qt/mainwindow.cpp index dcdb9bc..b53aa72 100644 --- a/simulator_SIC_XE/gui/qt/mainwindow.cpp +++ b/simulator_SIC_XE/gui/qt/mainwindow.cpp @@ -102,6 +102,8 @@ MainWindow::MainWindow(QWidget *parent) : // Connect menu actions 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(); setupDisassemblyDisplay(); @@ -978,3 +980,51 @@ void MainWindow::loadObjectFile() 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)); + } + } +} diff --git a/simulator_SIC_XE/gui/qt/mainwindow.h b/simulator_SIC_XE/gui/qt/mainwindow.h index caa9550..2dba671 100644 --- a/simulator_SIC_XE/gui/qt/mainwindow.h +++ b/simulator_SIC_XE/gui/qt/mainwindow.h @@ -51,6 +51,8 @@ private slots: void onDisassemblyGoToStart(); void onDisassemblyGoToEnd(); void loadObjectFile(); + void showAboutDialog(); + void showFrequencyDialog(); private: Ui::MainWindow *ui; diff --git a/simulator_SIC_XE/src/instructions.cpp b/simulator_SIC_XE/src/instructions.cpp index 02124b9..a1c4e55 100644 --- a/simulator_SIC_XE/src/instructions.cpp +++ b/simulator_SIC_XE/src/instructions.cpp @@ -210,6 +210,14 @@ void divf_handler(Machine &m, int ea, AddressingMode mode) void j_handler(Machine &m, int ea, AddressingMode 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); } @@ -265,6 +273,8 @@ void ldch_handler(Machine &m, int ea, AddressingMode mode) int val; if (mode == AddressingMode::IMMEDIATE) { val = ea & 0xFF; + } else if (mode == AddressingMode::INDIRECT) { + val = m.getByte(m.getWord(ea)); } else { val = m.getByte(ea); } diff --git a/simulator_SIC_XE/src/main.cpp b/simulator_SIC_XE/src/main.cpp index bd7a9e7..433a3f1 100644 --- a/simulator_SIC_XE/src/main.cpp +++ b/simulator_SIC_XE/src/main.cpp @@ -95,15 +95,45 @@ int main() loadInstructionSet(); std::shared_ptr machine = std::make_shared(); - Loader loader(machine, std::string(PATH_RESOURCES) + "test.obj"); + Loader loader(machine, std::string(PATH_RESOURCES) + "rec.obj"); loader.load(); - machine->execute(); - machine->execute(); - machine->execute(); - machine->execute(); - machine->execute(); - machine->execute(); - cout << "Register A after execution: " << machine->getA() << endl; + + cout << "=== Starting execution of rec.obj ===" << endl; + cout << "Initial PC: 0x" << std::hex << machine->getPC() << std::dec << endl; + + int maxSteps = 10000; + for (int step = 0; step < maxSteps; step++) { + 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;