spo/ass2/Machine.cpp
2025-12-05 13:27:40 +01:00

658 lines
14 KiB
C++

#include "Machine.h"
#include <chrono>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <sstream>
#include "FileDevice.h"
#include "InputDevice.h"
#include "Opcode.h"
#include "OutputDevice.h"
#include "Utils.h"
Machine::Machine() {
A = 0;
X = 0;
L = 0;
B = 0;
S = 0;
T = 0;
F = 0.0;
PC = 0;
SW = 0;
running = false;
timerThread = nullptr;
speed = 1000; // default 1000 kHz = 1 MHz
for (int i = 0; i <= MAX_ADDRESS; i++) {
memory[i] = 0;
}
for (int i = 0; i < MAX_DEVICES; i++) {
devices[i] = nullptr;
}
devices[0] = new InputDevice(std::cin);
devices[1] = new OutputDevice(std::cout);
devices[2] = new OutputDevice(std::cerr);
}
Machine::~Machine() {
stop();
for (int i = 0; i < MAX_DEVICES; i++) {
if (devices[i] != nullptr) {
delete devices[i];
devices[i] = nullptr;
}
}
}
int Machine::getA() const {
return A;
}
int Machine::getX() const {
return X;
}
int Machine::getL() const {
return L;
}
int Machine::getB() const {
return B;
}
int Machine::getS() const {
return S;
}
int Machine::getT() const {
return T;
}
double Machine::getF() const {
return F;
}
int Machine::getPC() const {
return PC;
}
int Machine::getSW() const {
return SW;
}
// mask to 24 bits
void Machine::setA(int val) {
A = val & 0xFFFFFF;
}
void Machine::setX(int val) {
X = val & 0xFFFFFF;
}
void Machine::setL(int val) {
L = val & 0xFFFFFF;
}
void Machine::setB(int val) {
B = val & 0xFFFFFF;
}
void Machine::setS(int val) {
S = val & 0xFFFFFF;
}
void Machine::setT(int val) {
T = val & 0xFFFFFF;
}
void Machine::setF(double val) {
F = val;
}
void Machine::setPC(int val) {
PC = val & 0xFFFFFF;
}
void Machine::setSW(int val) {
SW = val & 0xFFFFFF;
}
void Machine::setCC_less() {
SW = (SW & 0xFFFF3F) | 0x0;
}
void Machine::setCC_equal() {
SW = (SW & 0xFFFF3F) | 0x40;
}
void Machine::setCC_greater() {
SW = (SW & 0xFFFF3F) | 0x80;
}
int Machine::getCC() const {
return SW & 0xC0;
}
int Machine::getReg(int reg) const {
switch (reg) {
case 0:
return A;
case 1:
return X;
case 2:
return L;
case 3:
return B;
case 4:
return S;
case 5:
return T;
case 6:
return (int)F;
case 8:
return PC;
case 9:
return SW;
default:
return 0;
}
}
void Machine::setReg(int reg, int val) {
switch (reg) {
case 0:
setA(val);
break;
case 1:
setX(val);
break;
case 2:
setL(val);
break;
case 3:
setB(val);
break;
case 4:
setS(val);
break;
case 5:
setT(val);
break;
case 6:
setF((double)val);
break;
case 8:
setPC(val);
break;
case 9:
setSW(val);
break;
default:
break;
}
}
int Machine::getByte(int addr) const {
if (addr < 0 || addr > MAX_ADDRESS) {
return 0;
}
return memory[addr];
}
void Machine::setByte(int addr, int val) {
if (addr >= 0 && addr <= MAX_ADDRESS) {
memory[addr] = val & 0xFF;
}
}
int Machine::getWord(int addr) const {
if (addr < 0 || addr + 2 > MAX_ADDRESS) {
return 0;
}
// big-endian
return (memory[addr] << 16) | (memory[addr + 1] << 8) | memory[addr + 2];
}
void Machine::setWord(int addr, int val) {
if (addr >= 0 && addr + 2 <= MAX_ADDRESS) {
val = val & 0xFFFFFF;
// big-endian
memory[addr] = (val >> 16) & 0xFF;
memory[addr + 1] = (val >> 8) & 0xFF;
memory[addr + 2] = val & 0xFF;
}
}
Device* Machine::getDevice(int num) {
if (num < 0 || num >= MAX_DEVICES) {
return nullptr;
}
// lazy init for file devices (3-255)
if (devices[num] == nullptr && num >= 3) {
std::stringstream ss;
ss << std::setw(2) << std::setfill('0') << std::hex << std::uppercase << num;
std::string filename = ss.str() + ".dev";
devices[num] = new FileDevice(filename);
}
return devices[num];
}
void Machine::setDevice(int num, Device* device) {
if (num >= 0 && num < MAX_DEVICES) {
devices[num] = device;
}
}
void Machine::notImplemented(const char* mnemonic) {
std::cerr << "Error: instruction " << mnemonic << " not implemented" << std::endl;
}
void Machine::invalidOpcode(int opcode) {
std::cerr << "Error: invalid opcode 0x" << std::hex << opcode << std::endl;
}
void Machine::invalidAddressing() {
std::cerr << "Error: invalid addressing mode" << std::endl;
}
int Machine::fetch() {
int byte = getByte(PC);
PC = (PC + 1) & 0xFFFFFF;
return byte;
}
void Machine::execute() {
int byte1 = fetch();
int opcode = byte1 & 0xFC;
int ni = byte1 & 0x03;
// format 1 (no operand)
if (execF1(opcode)) {
return;
}
int byte2 = fetch();
// format 2 (register operand)
if (execF2(opcode, byte2)) {
return;
}
// format 3 or 4
int xbpe = (byte2 >> 4) & 0x0F;
int disp = byte2 & 0x0F;
int byte3 = fetch();
disp = (disp << 8) | byte3;
// format 4 (extended)
if (xbpe & 0x01) {
int byte4 = fetch();
disp = (disp << 8) | byte4;
}
int addr = disp;
// base relative
if (xbpe & 0x04) {
addr = B + disp;
}
// pc relative
else if (xbpe & 0x02) {
// sign extend 12-bit displacement for format 3
if (!(xbpe & 0x01) && (disp & 0x800)) {
disp = disp | 0xFFFFF000;
}
addr = PC + disp;
}
// indexed
if (xbpe & 0x08) {
addr = addr + X;
}
addr = addr & 0xFFFFFF;
execSICF3F4(opcode, ni, addr);
}
bool Machine::execF1(int opcode) {
switch (opcode) {
case Opcode::FIX:
A = (int)F;
return true;
case Opcode::FLOAT:
F = (double)A;
return true;
case Opcode::NORM:
notImplemented("NORM");
return true;
case Opcode::SIO:
case Opcode::HIO:
case Opcode::TIO:
notImplemented("SIO/HIO/TIO");
return true;
}
return false;
}
bool Machine::execF2(int opcode, int operand) {
int r1 = (operand >> 4) & 0x0F;
int r2 = operand & 0x0F;
switch (opcode) {
case Opcode::ADDR:
setReg(r2, getReg(r1) + getReg(r2));
return true;
case Opcode::SUBR:
setReg(r2, getReg(r2) - getReg(r1));
return true;
case Opcode::MULR:
setReg(r2, getReg(r1) * getReg(r2));
return true;
case Opcode::DIVR:
setReg(r2, getReg(r2) / getReg(r1));
return true;
case Opcode::COMPR: {
int val1 = getReg(r1);
int val2 = getReg(r2);
if (val1 < val2)
setCC_less();
else if (val1 == val2)
setCC_equal();
else
setCC_greater();
return true;
}
case Opcode::SHIFTL:
setReg(r1, getReg(r1) << (r2 + 1));
return true;
case Opcode::SHIFTR:
setReg(r1, getReg(r1) >> (r2 + 1));
return true;
case Opcode::RMO:
setReg(r2, getReg(r1));
return true;
case Opcode::CLEAR:
setReg(r1, 0);
return true;
case Opcode::TIXR: {
X = X + 1;
int val = getReg(r1);
if (X < val)
setCC_less();
else if (X == val)
setCC_equal();
else
setCC_greater();
return true;
}
case Opcode::SVC:
notImplemented("SVC");
return true;
}
return false;
}
bool Machine::execSICF3F4(int opcode, int ni, int operand) {
int addr = operand;
int value;
// get value based on addressing mode
if (ni == 0x00) {
// SIC format (simple)
value = getWord(addr);
} else if (ni == 0x01) {
// immediate
value = addr;
} else if (ni == 0x02) {
// indirect
addr = getWord(addr);
value = getWord(addr);
} else {
// simple (ni == 0x03)
value = getWord(addr);
}
switch (opcode) {
case Opcode::LDA:
A = value;
return true;
case Opcode::LDX:
X = value;
return true;
case Opcode::LDL:
L = value;
return true;
case Opcode::LDB:
B = value;
return true;
case Opcode::LDS:
S = value;
return true;
case Opcode::LDT:
T = value;
return true;
case Opcode::LDCH:
if (ni == 0x01) {
A = (A & 0xFFFF00) | (addr & 0xFF);
} else {
A = (A & 0xFFFF00) | getByte(addr);
}
return true;
case Opcode::STA:
setWord(addr, A);
return true;
case Opcode::STX:
setWord(addr, X);
return true;
case Opcode::STL:
setWord(addr, L);
return true;
case Opcode::STB:
setWord(addr, B);
return true;
case Opcode::STS:
setWord(addr, S);
return true;
case Opcode::STT:
setWord(addr, T);
return true;
case Opcode::STSW:
setWord(addr, SW);
return true;
case Opcode::STCH:
setByte(addr, A & 0xFF);
return true;
case Opcode::ADD:
A = A + value;
return true;
case Opcode::SUB:
A = A - value;
return true;
case Opcode::MUL:
A = A * value;
return true;
case Opcode::DIV:
A = A / value;
return true;
case Opcode::AND:
A = A & value;
return true;
case Opcode::OR:
A = A | value;
return true;
case Opcode::COMP:
if (A < value)
setCC_less();
else if (A == value)
setCC_equal();
else
setCC_greater();
return true;
case Opcode::TIX:
X = X + 1;
if (X < value)
setCC_less();
else if (X == value)
setCC_equal();
else
setCC_greater();
return true;
case Opcode::J:
PC = addr;
return true;
case Opcode::JEQ:
if (getCC() == 0x40) PC = addr;
return true;
case Opcode::JGT:
if (getCC() == 0x80) PC = addr;
return true;
case Opcode::JLT:
if (getCC() == 0x00) PC = addr;
return true;
case Opcode::JSUB:
L = PC;
PC = addr;
return true;
case Opcode::RSUB:
PC = L;
return true;
case Opcode::TD: {
Device* dev = getDevice(getByte(addr));
if (dev != nullptr && dev->test()) {
setCC_less();
} else {
setCC_equal();
}
return true;
}
case Opcode::RD: {
Device* dev = getDevice(getByte(addr));
if (dev != nullptr) {
A = (A & 0xFFFF00) | dev->read();
}
return true;
}
case Opcode::WD: {
Device* dev = getDevice(getByte(addr));
if (dev != nullptr) {
dev->write(A & 0xFF);
}
return true;
}
default:
invalidOpcode(opcode);
return false;
}
}
void Machine::start() {
if (running) return;
running = true;
timerThread = new std::thread([this]() {
while (running) {
int instructionsPerTick = speed / 1000;
if (instructionsPerTick < 1) instructionsPerTick = 1;
for (int i = 0; i < instructionsPerTick && running; i++) {
execute();
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
});
}
void Machine::stop() {
running = false;
if (timerThread != nullptr) {
timerThread->join();
delete timerThread;
timerThread = nullptr;
}
}
void Machine::step() {
execute();
}
bool Machine::isRunning() const {
return running;
}
int Machine::getSpeed() const {
return speed;
}
void Machine::setSpeed(int kHz) {
speed = kHz;
}
bool Machine::loadSection(std::istream& r) {
int startAddr = 0;
int execAddr = 0;
char recordType;
while (r.get(recordType)) {
if (recordType == '\n' || recordType == '\r') {
continue;
}
if (recordType == 'H') {
// header: H^name(6)^startAddr(6)^length(6)
std::string name = Utils::readString(r, 6);
startAddr = Utils::readWord(r);
int length = Utils::readWord(r);
(void)name;
(void)length;
} else if (recordType == 'T') {
// text: T^addr(6)^length(2)^data
int addr = Utils::readWord(r);
int len = Utils::readByte(r);
for (int i = 0; i < len; i++) {
int byte = Utils::readByte(r);
setByte(addr + i, byte);
}
} else if (recordType == 'M') {
// modification: M^addr(6)^length(2)
// skip for absolute loader
Utils::readWord(r);
Utils::readByte(r);
} else if (recordType == 'E') {
// end: E^execAddr(6) or E alone
execAddr = Utils::readWord(r);
if (execAddr == 0) {
execAddr = startAddr;
}
PC = execAddr;
return true;
}
// skip to end of line
char c;
while (r.get(c) && c != '\n') {
// skip
}
}
return false;
}