assembling first version
This commit is contained in:
parent
9e9039af05
commit
d3e08abd30
7 changed files with 896 additions and 1 deletions
|
|
@ -6,6 +6,8 @@
|
||||||
#include "../../include/opcode.h"
|
#include "../../include/opcode.h"
|
||||||
#include "../../include/constants.h"
|
#include "../../include/constants.h"
|
||||||
#include "../../include/loader.h"
|
#include "../../include/loader.h"
|
||||||
|
#include "../../include/parser.h"
|
||||||
|
#include "../../include/code.h"
|
||||||
|
|
||||||
#include <QIntValidator>
|
#include <QIntValidator>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
|
|
@ -20,6 +22,9 @@
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
|
#include <QDir>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
class Loader;
|
class Loader;
|
||||||
|
|
||||||
|
|
@ -102,6 +107,7 @@ 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->actionLoad_Asm_file, &QAction::triggered, this, &MainWindow::loadAsmFile);
|
||||||
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::showAboutDialog);
|
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::showAboutDialog);
|
||||||
connect(ui->actionFrequency, &QAction::triggered, this, &MainWindow::showFrequencyDialog);
|
connect(ui->actionFrequency, &QAction::triggered, this, &MainWindow::showFrequencyDialog);
|
||||||
|
|
||||||
|
|
@ -981,6 +987,100 @@ void MainWindow::loadObjectFile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::loadAsmFile()
|
||||||
|
{
|
||||||
|
QString fileName = QFileDialog::getOpenFileName(this,
|
||||||
|
tr("Load Assembly File"),
|
||||||
|
QString(),
|
||||||
|
tr("Assembly Files (*.asm);;All Files (*)"));
|
||||||
|
|
||||||
|
if (fileName.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Stop execution if running
|
||||||
|
m_controller->stop();
|
||||||
|
|
||||||
|
// Reset machine state
|
||||||
|
m_machine->reset();
|
||||||
|
|
||||||
|
// Read assembly file
|
||||||
|
std::ifstream file(fileName.toStdString());
|
||||||
|
if (!file.is_open()) {
|
||||||
|
throw std::runtime_error("Could not open file: " + fileName.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string source((std::istreambuf_iterator<char>(file)),
|
||||||
|
std::istreambuf_iterator<char>());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// Parse and assemble
|
||||||
|
Parser parser;
|
||||||
|
Code code = parser.parse(source);
|
||||||
|
code.assemble();
|
||||||
|
|
||||||
|
// Generate object code
|
||||||
|
std::string objCode = code.emitText();
|
||||||
|
|
||||||
|
// Create resources directory if it doesn't exist
|
||||||
|
QDir dir;
|
||||||
|
if (!dir.exists("resources")) {
|
||||||
|
dir.mkpath("resources");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save object file to resources directory
|
||||||
|
QFileInfo fileInfo(fileName);
|
||||||
|
QString objFileName = "resources/" + fileInfo.completeBaseName() + ".obj";
|
||||||
|
|
||||||
|
std::ofstream objFile(objFileName.toStdString());
|
||||||
|
if (!objFile.is_open()) {
|
||||||
|
throw std::runtime_error("Could not create object file: " + objFileName.toStdString());
|
||||||
|
}
|
||||||
|
objFile << objCode;
|
||||||
|
objFile.close();
|
||||||
|
|
||||||
|
// Generate and save log file
|
||||||
|
QString logFileName = "resources/" + fileInfo.completeBaseName() + ".log";
|
||||||
|
std::ofstream logFile(logFileName.toStdString());
|
||||||
|
if (!logFile.is_open()) {
|
||||||
|
throw std::runtime_error("Could not create log file: " + logFileName.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
logFile << "=== SIC/XE Assembler Log ===\n\n";
|
||||||
|
logFile << "Source file: " << fileName.toStdString() << "\n";
|
||||||
|
logFile << "Object file: " << objFileName.toStdString() << "\n\n";
|
||||||
|
|
||||||
|
logFile << "=== Symbols ===\n";
|
||||||
|
logFile << code.dumpSymbols() << "\n\n";
|
||||||
|
|
||||||
|
logFile << "=== Code ===\n";
|
||||||
|
logFile << code.dumpCode() << "\n\n";
|
||||||
|
|
||||||
|
logFile << "=== Object Code ===\n";
|
||||||
|
logFile << objCode << "\n";
|
||||||
|
|
||||||
|
logFile.close();
|
||||||
|
|
||||||
|
// Load the generated object file
|
||||||
|
Loader loader(m_machine, objFileName.toStdString());
|
||||||
|
loader.load();
|
||||||
|
|
||||||
|
// Update displays
|
||||||
|
updateRegisterDisplays();
|
||||||
|
updateMemoryDisplay();
|
||||||
|
updateDisassemblyDisplay();
|
||||||
|
|
||||||
|
QMessageBox::information(this, tr("Success"),
|
||||||
|
tr("Assembly successful!\nObject file: %1\nLog file: %2")
|
||||||
|
.arg(objFileName).arg(logFileName));
|
||||||
|
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
QMessageBox::critical(this, tr("Error"),
|
||||||
|
tr("Failed to assemble file: %1").arg(e.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::showAboutDialog()
|
void MainWindow::showAboutDialog()
|
||||||
{
|
{
|
||||||
QMessageBox::about(this, tr("About SIC/XE Simulator"),
|
QMessageBox::about(this, tr("About SIC/XE Simulator"),
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ private slots:
|
||||||
void onDisassemblyGoToStart();
|
void onDisassemblyGoToStart();
|
||||||
void onDisassemblyGoToEnd();
|
void onDisassemblyGoToEnd();
|
||||||
void loadObjectFile();
|
void loadObjectFile();
|
||||||
|
void loadAsmFile();
|
||||||
void showAboutDialog();
|
void showAboutDialog();
|
||||||
void showFrequencyDialog();
|
void showFrequencyDialog();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -892,6 +892,7 @@
|
||||||
<string>File</string>
|
<string>File</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionLoad_Object_File"/>
|
<addaction name="actionLoad_Object_File"/>
|
||||||
|
<addaction name="actionLoad_Asm_file"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuMachine">
|
<widget class="QMenu" name="menuMachine">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
|
|
@ -924,6 +925,11 @@
|
||||||
<string>About</string>
|
<string>About</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionLoad_Asm_file">
|
||||||
|
<property name="text">
|
||||||
|
<string>Load Asm file</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include "node.h"
|
#include "node.h"
|
||||||
|
|
||||||
|
|
@ -17,8 +19,42 @@ public:
|
||||||
|
|
||||||
const string toString() const;
|
const string toString() const;
|
||||||
|
|
||||||
|
// Two-pass assembler methods
|
||||||
|
void assemble();
|
||||||
|
std::vector<uint8_t> emitCode();
|
||||||
|
std::string emitText();
|
||||||
|
std::string dumpSymbols() const;
|
||||||
|
std::string dumpCode() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::shared_ptr<Node>> _lines;
|
std::vector<std::shared_ptr<Node>> _lines;
|
||||||
|
|
||||||
|
// Assembler state
|
||||||
|
std::unordered_map<std::string, int> _symbolTable;
|
||||||
|
std::vector<int> _locationCounters; // Location counter per line
|
||||||
|
int _startAddress = 0;
|
||||||
|
int _programLength = 0;
|
||||||
|
std::string _programName;
|
||||||
|
int _baseRegister = -1; // -1 means not set
|
||||||
|
|
||||||
|
// Pass 1: build symbol table and assign addresses
|
||||||
|
void firstPass();
|
||||||
|
|
||||||
|
// Pass 2: generate code
|
||||||
|
void secondPass();
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
int getInstructionLength(const std::shared_ptr<Node>& node, int locationCounter) const;
|
||||||
|
std::vector<uint8_t> generateInstruction(const InstructionNode* inst, int address);
|
||||||
|
std::vector<uint8_t> generateData(const DataNode* data);
|
||||||
|
|
||||||
|
// Addressing mode selection
|
||||||
|
struct AddressingResult {
|
||||||
|
int nixbpe; // ni, x, b, p, e bits
|
||||||
|
int displacement; // 12-bit or 20-bit
|
||||||
|
bool success;
|
||||||
|
};
|
||||||
|
AddressingResult selectAddressingMode(int targetAddress, int pc, bool indexed, bool immediate, bool indirect, bool extended) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
223
simulator_SIC_XE/res/rec.asm
Normal file
223
simulator_SIC_XE/res/rec.asm
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
prog START 0
|
||||||
|
|
||||||
|
.-------------------------------------------
|
||||||
|
. MAIN LOOP
|
||||||
|
.
|
||||||
|
. Psevdo:
|
||||||
|
. sp = 0
|
||||||
|
. while true:
|
||||||
|
. n = readFA()
|
||||||
|
. if n == 0: halt
|
||||||
|
. acc = 1
|
||||||
|
. fact() ; rekurzivno: acc = n!
|
||||||
|
. printStdout(acc)
|
||||||
|
.-------------------------------------------
|
||||||
|
CLEAR A
|
||||||
|
STA sp
|
||||||
|
|
||||||
|
loop JSUB readFA
|
||||||
|
COMP #0
|
||||||
|
JEQ halt
|
||||||
|
|
||||||
|
STA n
|
||||||
|
LDA #1
|
||||||
|
STA acc
|
||||||
|
|
||||||
|
JSUB fact
|
||||||
|
LDA acc
|
||||||
|
JSUB printStdout
|
||||||
|
|
||||||
|
J loop
|
||||||
|
|
||||||
|
halt J halt
|
||||||
|
|
||||||
|
.-------------------------------------------
|
||||||
|
. readFA
|
||||||
|
.
|
||||||
|
. Psevdo:
|
||||||
|
. B = 0
|
||||||
|
. while true:
|
||||||
|
. ch = RD(FA)
|
||||||
|
. if ch == CR or ch == LF: break
|
||||||
|
. digit = ch - '0'
|
||||||
|
. B = B * 10 + digit
|
||||||
|
. return B
|
||||||
|
.-------------------------------------------
|
||||||
|
readFA CLEAR B
|
||||||
|
LDS #10
|
||||||
|
|
||||||
|
rd_loopFA RD #0xFA
|
||||||
|
COMP #0x0D . CR?
|
||||||
|
JEQ rd_doneCR_FA
|
||||||
|
COMP #0x0A . LF?
|
||||||
|
JEQ rd_doneFA
|
||||||
|
|
||||||
|
SUB #0x30
|
||||||
|
MULR S,B . B = B * 10
|
||||||
|
ADDR A,B . B = B + digit
|
||||||
|
J rd_loopFA
|
||||||
|
|
||||||
|
rd_doneCR_FA RD #0xFA . pogoltni LF po CR
|
||||||
|
rd_doneFA CLEAR A
|
||||||
|
RMO B,A
|
||||||
|
RSUB
|
||||||
|
|
||||||
|
.-------------------------------------------
|
||||||
|
. fact
|
||||||
|
.
|
||||||
|
. Psevdo (globalni n, acc, sklad L):
|
||||||
|
. fact():
|
||||||
|
. push(L)
|
||||||
|
. if n <= 1:
|
||||||
|
. pop(L); return
|
||||||
|
. acc = acc * n
|
||||||
|
. n = n - 1
|
||||||
|
. fact()
|
||||||
|
. pop(L); return
|
||||||
|
.-------------------------------------------
|
||||||
|
fact . push L
|
||||||
|
LDA sp
|
||||||
|
ADD #3
|
||||||
|
STA sp
|
||||||
|
LDX sp
|
||||||
|
STL stackL,X
|
||||||
|
|
||||||
|
LDA n
|
||||||
|
COMP #1
|
||||||
|
JGT fact_rec
|
||||||
|
|
||||||
|
. base case: n <= 1
|
||||||
|
LDX sp
|
||||||
|
LDL stackL,X
|
||||||
|
LDA sp
|
||||||
|
SUB #3
|
||||||
|
STA sp
|
||||||
|
RSUB
|
||||||
|
|
||||||
|
fact_rec . recursive case: acc *= n; n--; fact()
|
||||||
|
|
||||||
|
LDB acc
|
||||||
|
LDS n
|
||||||
|
MULR S,B
|
||||||
|
STB acc
|
||||||
|
|
||||||
|
LDA n
|
||||||
|
SUB #1
|
||||||
|
STA n
|
||||||
|
|
||||||
|
JSUB fact
|
||||||
|
|
||||||
|
. pop L in return to caller
|
||||||
|
LDX sp
|
||||||
|
LDL stackL,X
|
||||||
|
LDA sp
|
||||||
|
SUB #3
|
||||||
|
STA sp
|
||||||
|
RSUB
|
||||||
|
|
||||||
|
.-------------------------------------------
|
||||||
|
. printStdout
|
||||||
|
.
|
||||||
|
. Psevdo:
|
||||||
|
. if A == 0:
|
||||||
|
. print "0\n"
|
||||||
|
. return
|
||||||
|
. ps_val = A
|
||||||
|
. ps_len = 0
|
||||||
|
. while ps_val > 0:
|
||||||
|
. q = ps_val / 10
|
||||||
|
. r = ps_val % 10
|
||||||
|
. buf[ps_len] = '0' + r
|
||||||
|
. ps_len++
|
||||||
|
. ps_val = q
|
||||||
|
. for i = ps_len-1 .. 0:
|
||||||
|
. print buf[i]
|
||||||
|
. print "\r\n"
|
||||||
|
.-------------------------------------------
|
||||||
|
printStdout COMP #0
|
||||||
|
JEQ ps_zero
|
||||||
|
|
||||||
|
STA ps_val
|
||||||
|
LDA #0
|
||||||
|
STA ps_len
|
||||||
|
LDS #10
|
||||||
|
LDT #0x30 . '0'
|
||||||
|
|
||||||
|
ps_div LDA ps_val
|
||||||
|
COMP #0
|
||||||
|
JEQ ps_divdone
|
||||||
|
|
||||||
|
RMO A,B
|
||||||
|
DIVR S,B . kvocient v B
|
||||||
|
RMO B,X . X = kvocient
|
||||||
|
|
||||||
|
MULR S,B
|
||||||
|
SUBR B,A . A = ostanek
|
||||||
|
ADDR T,A . A = '0' + ostanek
|
||||||
|
STA psdigit
|
||||||
|
|
||||||
|
LDA ps_len
|
||||||
|
STA ps_idx
|
||||||
|
LDA #psbuf
|
||||||
|
ADD ps_idx
|
||||||
|
STA ps_ptr
|
||||||
|
LDA psdigit
|
||||||
|
STCH @ps_ptr
|
||||||
|
|
||||||
|
LDA ps_len
|
||||||
|
ADD #1
|
||||||
|
STA ps_len
|
||||||
|
|
||||||
|
RMO X,A
|
||||||
|
STA ps_val
|
||||||
|
J ps_div
|
||||||
|
|
||||||
|
ps_divdone LDA ps_len
|
||||||
|
SUB #1
|
||||||
|
STA ps_idx
|
||||||
|
|
||||||
|
ps_print LDA ps_idx
|
||||||
|
COMP #0
|
||||||
|
JLT ps_end
|
||||||
|
|
||||||
|
LDA #psbuf
|
||||||
|
ADD ps_idx
|
||||||
|
STA ps_ptr
|
||||||
|
LDCH @ps_ptr
|
||||||
|
WD #1
|
||||||
|
|
||||||
|
LDA ps_idx
|
||||||
|
SUB #1
|
||||||
|
STA ps_idx
|
||||||
|
J ps_print
|
||||||
|
|
||||||
|
ps_end LDA #0x0D . CR
|
||||||
|
WD #1
|
||||||
|
LDA #0x0A . LF
|
||||||
|
WD #1
|
||||||
|
RSUB
|
||||||
|
|
||||||
|
ps_zero LDA #0x30 . "0"
|
||||||
|
WD #1
|
||||||
|
LDA #0x0D
|
||||||
|
WD #1
|
||||||
|
LDA #0x0A
|
||||||
|
WD #1
|
||||||
|
RSUB
|
||||||
|
|
||||||
|
.data
|
||||||
|
. rekurzija faktoriala
|
||||||
|
sp WORD 0 . stack pointer
|
||||||
|
n WORD 0
|
||||||
|
acc WORD 0 . akumulator za faktorial
|
||||||
|
stackL RESB 60
|
||||||
|
|
||||||
|
. printStdout
|
||||||
|
ps_val WORD 0
|
||||||
|
ps_len WORD 0
|
||||||
|
ps_idx WORD 0
|
||||||
|
psdigit WORD 0
|
||||||
|
ps_ptr WORD 0
|
||||||
|
psbuf RESB 12
|
||||||
|
|
||||||
|
END prog
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
#include "code.h"
|
#include "code.h"
|
||||||
|
#include "opcode.h"
|
||||||
|
#include "constants.h"
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
void Code::addLine(const std::shared_ptr<Node> &line)
|
void Code::addLine(const std::shared_ptr<Node> &line)
|
||||||
{
|
{
|
||||||
|
|
@ -18,3 +24,510 @@ const string Code::toString() const
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// TWO-PASS ASSEMBLER IMPLEMENTATION
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void Code::assemble() {
|
||||||
|
firstPass();
|
||||||
|
secondPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Code::firstPass() {
|
||||||
|
_symbolTable.clear();
|
||||||
|
_locationCounters.clear();
|
||||||
|
_locationCounters.resize(_lines.size(), 0);
|
||||||
|
|
||||||
|
int locationCounter = 0;
|
||||||
|
bool startFound = false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < _lines.size(); ++i) {
|
||||||
|
auto& line = _lines[i];
|
||||||
|
_locationCounters[i] = locationCounter;
|
||||||
|
|
||||||
|
// Handle label
|
||||||
|
std::string label = line->getLabel();
|
||||||
|
if (!label.empty()) {
|
||||||
|
if (_symbolTable.find(label) != _symbolTable.end()) {
|
||||||
|
throw std::runtime_error("Duplicate symbol: " + label);
|
||||||
|
}
|
||||||
|
_symbolTable[label] = locationCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for directives
|
||||||
|
if (auto* directive = dynamic_cast<DirectiveNode*>(line.get())) {
|
||||||
|
switch (directive->kind()) {
|
||||||
|
case DirectiveKind::START: {
|
||||||
|
if (std::holds_alternative<int>(directive->arg())) {
|
||||||
|
_startAddress = std::get<int>(directive->arg());
|
||||||
|
locationCounter = _startAddress;
|
||||||
|
_locationCounters[i] = locationCounter;
|
||||||
|
if (!label.empty()) {
|
||||||
|
_symbolTable[label] = locationCounter;
|
||||||
|
_programName = label;
|
||||||
|
}
|
||||||
|
startFound = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DirectiveKind::END:
|
||||||
|
_programLength = locationCounter - _startAddress;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DirectiveKind::BASE: {
|
||||||
|
// BASE sets base register for addressing
|
||||||
|
if (std::holds_alternative<std::string>(directive->arg())) {
|
||||||
|
// Will resolve in second pass
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DirectiveKind::NOBASE:
|
||||||
|
_baseRegister = -1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DirectiveKind::EQU: {
|
||||||
|
// EQU defines symbol value
|
||||||
|
if (!label.empty() && std::holds_alternative<int>(directive->arg())) {
|
||||||
|
_symbolTable[label] = std::get<int>(directive->arg());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DirectiveKind::ORG: {
|
||||||
|
// ORG changes location counter
|
||||||
|
if (std::holds_alternative<int>(directive->arg())) {
|
||||||
|
locationCounter = std::get<int>(directive->arg());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle data directives
|
||||||
|
if (auto* data = dynamic_cast<DataNode*>(line.get())) {
|
||||||
|
int length = 0;
|
||||||
|
switch (data->kind()) {
|
||||||
|
case DataKind::WORD:
|
||||||
|
length = 3; // 24-bit word
|
||||||
|
break;
|
||||||
|
case DataKind::BYTE: {
|
||||||
|
if (std::holds_alternative<std::vector<uint8_t>>(data->value())) {
|
||||||
|
length = std::get<std::vector<uint8_t>>(data->value()).size();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DataKind::RESW: {
|
||||||
|
if (std::holds_alternative<int>(data->value())) {
|
||||||
|
length = std::get<int>(data->value()) * 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DataKind::RESB: {
|
||||||
|
if (std::holds_alternative<int>(data->value())) {
|
||||||
|
length = std::get<int>(data->value());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locationCounter += length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle instructions
|
||||||
|
if (auto* inst = dynamic_cast<InstructionNode*>(line.get())) {
|
||||||
|
int length = getInstructionLength(line, locationCounter);
|
||||||
|
locationCounter += length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startFound) {
|
||||||
|
_startAddress = 0;
|
||||||
|
}
|
||||||
|
_programLength = locationCounter - _startAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Code::getInstructionLength(const std::shared_ptr<Node>& node, int locationCounter) const {
|
||||||
|
auto* inst = dynamic_cast<InstructionNode*>(node.get());
|
||||||
|
if (!inst || !inst->getMnemonic()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mnemonic = inst->getMnemonic();
|
||||||
|
InstructionType type = mnemonic->type();
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case InstructionType::TYPE1:
|
||||||
|
return 1;
|
||||||
|
case InstructionType::TYPE2:
|
||||||
|
return 2;
|
||||||
|
case InstructionType::TYPE3_4:
|
||||||
|
return mnemonic->extended() ? 4 : 3;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Code::secondPass() {
|
||||||
|
// Generate code for all instructions and data
|
||||||
|
// This will be used by emitCode() and emitText()
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> Code::emitCode() {
|
||||||
|
std::vector<uint8_t> code;
|
||||||
|
code.resize(_programLength, 0);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < _lines.size(); ++i) {
|
||||||
|
auto& line = _lines[i];
|
||||||
|
int address = _locationCounters[i];
|
||||||
|
int offset = address - _startAddress;
|
||||||
|
|
||||||
|
if (offset < 0 || offset >= _programLength) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate instruction
|
||||||
|
if (auto* inst = dynamic_cast<InstructionNode*>(line.get())) {
|
||||||
|
auto bytes = generateInstruction(inst, address);
|
||||||
|
for (size_t j = 0; j < bytes.size() && (offset + j) < code.size(); ++j) {
|
||||||
|
code[offset + j] = bytes[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate data
|
||||||
|
if (auto* data = dynamic_cast<DataNode*>(line.get())) {
|
||||||
|
auto bytes = generateData(data);
|
||||||
|
for (size_t j = 0; j < bytes.size() && (offset + j) < code.size(); ++j) {
|
||||||
|
code[offset + j] = bytes[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> Code::generateInstruction(const InstructionNode* inst, int address) {
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
|
||||||
|
if (!inst || !inst->getMnemonic()) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mnemonic = inst->getMnemonic();
|
||||||
|
uint8_t opcode = mnemonic->opcode();
|
||||||
|
InstructionType type = mnemonic->type();
|
||||||
|
bool extended = mnemonic->extended();
|
||||||
|
const auto& operands = mnemonic->operands();
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case InstructionType::TYPE1: {
|
||||||
|
bytes.push_back(opcode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case InstructionType::TYPE2: {
|
||||||
|
bytes.push_back(opcode);
|
||||||
|
uint8_t r1 = 0, r2 = 0;
|
||||||
|
if (operands.size() >= 1 && std::holds_alternative<Register>(operands[0])) {
|
||||||
|
r1 = std::get<Register>(operands[0]).num & 0xF;
|
||||||
|
}
|
||||||
|
if (operands.size() >= 2 && std::holds_alternative<Register>(operands[1])) {
|
||||||
|
r2 = std::get<Register>(operands[1]).num & 0xF;
|
||||||
|
}
|
||||||
|
bytes.push_back((r1 << 4) | r2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case InstructionType::TYPE3_4: {
|
||||||
|
// Format 3 or 4 instruction
|
||||||
|
int ni = 0, x = 0, b = 0, p = 0, e = 0;
|
||||||
|
int targetAddress = 0;
|
||||||
|
bool immediate = false, indirect = false, indexed = false;
|
||||||
|
|
||||||
|
// Parse operand
|
||||||
|
if (!operands.empty()) {
|
||||||
|
if (std::holds_alternative<Immediate>(operands[0])) {
|
||||||
|
immediate = true;
|
||||||
|
targetAddress = std::get<Immediate>(operands[0]).value;
|
||||||
|
ni = 0x01; // n=0, i=1
|
||||||
|
} else if (std::holds_alternative<SymbolRef>(operands[0])) {
|
||||||
|
auto& sym = std::get<SymbolRef>(operands[0]);
|
||||||
|
immediate = sym.immediate;
|
||||||
|
indirect = sym.indirect;
|
||||||
|
indexed = sym.indexed;
|
||||||
|
|
||||||
|
// Look up symbol
|
||||||
|
auto it = _symbolTable.find(sym.name);
|
||||||
|
if (it != _symbolTable.end()) {
|
||||||
|
targetAddress = it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set ni bits
|
||||||
|
if (immediate) {
|
||||||
|
ni = 0x01; // n=0, i=1
|
||||||
|
} else if (indirect) {
|
||||||
|
ni = 0x02; // n=1, i=0
|
||||||
|
} else {
|
||||||
|
ni = 0x03; // n=1, i=1 (simple/direct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No operand (like RSUB)
|
||||||
|
ni = 0x03;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexed) {
|
||||||
|
x = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extended) {
|
||||||
|
e = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate PC for addressing
|
||||||
|
int pc = address + (extended ? 4 : 3);
|
||||||
|
|
||||||
|
// Select addressing mode
|
||||||
|
auto result = selectAddressingMode(targetAddress, pc, indexed, immediate, indirect, extended);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
b = (result.nixbpe >> 2) & 1;
|
||||||
|
p = (result.nixbpe >> 1) & 1;
|
||||||
|
e = result.nixbpe & 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int displacement = result.displacement;
|
||||||
|
|
||||||
|
// Build instruction bytes
|
||||||
|
uint8_t byte1 = (opcode & 0xFC) | ni;
|
||||||
|
uint8_t byte2 = (x << 7) | (b << 6) | (p << 5) | (e << 4);
|
||||||
|
|
||||||
|
bytes.push_back(byte1);
|
||||||
|
|
||||||
|
if (extended) {
|
||||||
|
// Format 4: 20-bit address
|
||||||
|
byte2 |= (displacement >> 16) & 0x0F;
|
||||||
|
bytes.push_back(byte2);
|
||||||
|
bytes.push_back((displacement >> 8) & 0xFF);
|
||||||
|
bytes.push_back(displacement & 0xFF);
|
||||||
|
} else {
|
||||||
|
// Format 3: 12-bit displacement
|
||||||
|
byte2 |= (displacement >> 8) & 0x0F;
|
||||||
|
bytes.push_back(byte2);
|
||||||
|
bytes.push_back(displacement & 0xFF);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Code::AddressingResult Code::selectAddressingMode(int targetAddress, int pc, bool indexed, bool immediate, bool indirect, bool extended) const {
|
||||||
|
AddressingResult result;
|
||||||
|
result.success = false;
|
||||||
|
result.nixbpe = 0;
|
||||||
|
result.displacement = 0;
|
||||||
|
|
||||||
|
// Immediate mode - use target address directly
|
||||||
|
if (immediate) {
|
||||||
|
if (extended) {
|
||||||
|
result.nixbpe = 0x01; // e=1, b=0, p=0
|
||||||
|
result.displacement = targetAddress & 0xFFFFF; // 20 bits
|
||||||
|
} else {
|
||||||
|
result.nixbpe = 0x00; // e=0, b=0, p=0
|
||||||
|
result.displacement = targetAddress & 0xFFF; // 12 bits
|
||||||
|
}
|
||||||
|
result.success = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extended format - use absolute address
|
||||||
|
if (extended) {
|
||||||
|
result.nixbpe = 0x01; // e=1, b=0, p=0
|
||||||
|
result.displacement = targetAddress & 0xFFFFF;
|
||||||
|
result.success = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try PC-relative (-2048 to +2047)
|
||||||
|
int pcDisp = targetAddress - pc;
|
||||||
|
if (pcDisp >= -2048 && pcDisp <= 2047) {
|
||||||
|
result.nixbpe = 0x02; // p=1, b=0, e=0
|
||||||
|
result.displacement = pcDisp & 0xFFF;
|
||||||
|
result.success = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try base-relative (0 to 4095)
|
||||||
|
if (_baseRegister >= 0) {
|
||||||
|
int baseDisp = targetAddress - _baseRegister;
|
||||||
|
if (baseDisp >= 0 && baseDisp <= 4095) {
|
||||||
|
result.nixbpe = 0x04; // b=1, p=0, e=0
|
||||||
|
result.displacement = baseDisp & 0xFFF;
|
||||||
|
result.success = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try direct (0 to 4095)
|
||||||
|
if (targetAddress >= 0 && targetAddress <= 4095) {
|
||||||
|
result.nixbpe = 0x00; // b=0, p=0, e=0
|
||||||
|
result.displacement = targetAddress & 0xFFF;
|
||||||
|
result.success = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try SIC format (0 to 32767, 15 bits)
|
||||||
|
if (targetAddress >= 0 && targetAddress <= 32767) {
|
||||||
|
result.nixbpe = 0x00;
|
||||||
|
result.displacement = targetAddress & 0x7FFF;
|
||||||
|
result.success = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Could not find suitable addressing mode
|
||||||
|
result.success = false;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> Code::generateData(const DataNode* data) {
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data->kind()) {
|
||||||
|
case DataKind::WORD: {
|
||||||
|
if (std::holds_alternative<int>(data->value())) {
|
||||||
|
int value = std::get<int>(data->value()) & 0xFFFFFF;
|
||||||
|
// SIC/XE stores words in big-endian (MSB first)
|
||||||
|
bytes.push_back((value >> 16) & 0xFF);
|
||||||
|
bytes.push_back((value >> 8) & 0xFF);
|
||||||
|
bytes.push_back(value & 0xFF);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DataKind::BYTE: {
|
||||||
|
if (std::holds_alternative<std::vector<uint8_t>>(data->value())) {
|
||||||
|
bytes = std::get<std::vector<uint8_t>>(data->value());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DataKind::RESW:
|
||||||
|
case DataKind::RESB:
|
||||||
|
// Reserved space - emit zeros (handled by initialized array)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Code::emitText() {
|
||||||
|
std::ostringstream oss;
|
||||||
|
|
||||||
|
// H record: program name, start address, length
|
||||||
|
oss << "H ";
|
||||||
|
std::string name = _programName.empty() ? "PROG" : _programName;
|
||||||
|
name.resize(6, ' ');
|
||||||
|
oss << name << " ";
|
||||||
|
oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << _startAddress << " ";
|
||||||
|
oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << _programLength;
|
||||||
|
oss << "\n";
|
||||||
|
|
||||||
|
// T records: text (code/data)
|
||||||
|
std::vector<uint8_t> code = emitCode();
|
||||||
|
int textStart = 0;
|
||||||
|
|
||||||
|
while (textStart < code.size()) {
|
||||||
|
int textLength = std::min(30, (int)code.size() - textStart);
|
||||||
|
|
||||||
|
// Skip all-zero sections for RESW/RESB
|
||||||
|
bool allZeros = true;
|
||||||
|
for (int i = 0; i < textLength; ++i) {
|
||||||
|
if (code[textStart + i] != 0) {
|
||||||
|
allZeros = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allZeros) {
|
||||||
|
oss << "T ";
|
||||||
|
oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << (_startAddress + textStart) << " ";
|
||||||
|
oss << std::setfill('0') << std::setw(2) << std::hex << std::uppercase << textLength << " ";
|
||||||
|
|
||||||
|
for (int i = 0; i < textLength; ++i) {
|
||||||
|
oss << std::setfill('0') << std::setw(2) << std::hex << std::uppercase << (int)code[textStart + i];
|
||||||
|
}
|
||||||
|
oss << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
textStart += textLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// E record: execution start address
|
||||||
|
oss << "E ";
|
||||||
|
oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << _startAddress;
|
||||||
|
oss << "\n";
|
||||||
|
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Code::dumpSymbols() const {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "=== Symbol Table ===\n";
|
||||||
|
oss << std::left << std::setw(20) << "Symbol" << "Address\n";
|
||||||
|
oss << std::string(30, '-') << "\n";
|
||||||
|
|
||||||
|
for (const auto& [symbol, address] : _symbolTable) {
|
||||||
|
oss << std::left << std::setw(20) << symbol;
|
||||||
|
oss << std::hex << std::uppercase << std::setw(6) << std::setfill('0') << address << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Code::dumpCode() const {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "=== Code Listing ===\n";
|
||||||
|
oss << std::hex << std::uppercase << std::setfill('0');
|
||||||
|
|
||||||
|
std::vector<uint8_t> code = const_cast<Code*>(this)->emitCode();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < _lines.size(); ++i) {
|
||||||
|
auto& line = _lines[i];
|
||||||
|
int address = _locationCounters[i];
|
||||||
|
int offset = address - _startAddress;
|
||||||
|
|
||||||
|
// Print address
|
||||||
|
oss << std::setw(6) << address << " ";
|
||||||
|
|
||||||
|
// Print generated bytes
|
||||||
|
int length = getInstructionLength(line, address);
|
||||||
|
if (auto* data = dynamic_cast<DataNode*>(line.get())) {
|
||||||
|
auto bytes = const_cast<Code*>(this)->generateData(data);
|
||||||
|
length = bytes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < length && (offset + j) < code.size(); ++j) {
|
||||||
|
oss << std::setw(2) << (int)code[offset + j];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad for alignment
|
||||||
|
for (int j = length; j < 12; ++j) {
|
||||||
|
oss << " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
oss << " " << line->toString() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -390,6 +390,18 @@ std::shared_ptr<Node> Parser::parseInstruction() {
|
||||||
|
|
||||||
lexer_.skipWhitespace();
|
lexer_.skipWhitespace();
|
||||||
|
|
||||||
|
// Check for comment after label - create a label-only instruction node
|
||||||
|
if (lexer_.peek() == '.') {
|
||||||
|
std::string comment = std::string(lexer_.readTo('\n'));
|
||||||
|
// Return an instruction node with just the label (null mnemonic)
|
||||||
|
auto node = std::make_shared<InstructionNode>(
|
||||||
|
std::move(label),
|
||||||
|
nullptr,
|
||||||
|
std::move(comment)
|
||||||
|
);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for extended format prefix
|
// Check for extended format prefix
|
||||||
bool isExtended = lexer_.peek() == '+';
|
bool isExtended = lexer_.peek() == '+';
|
||||||
if (isExtended) {
|
if (isExtended) {
|
||||||
|
|
@ -399,7 +411,11 @@ std::shared_ptr<Node> Parser::parseInstruction() {
|
||||||
std::string name = std::string(lexer_.readAlphanumeric());
|
std::string name = std::string(lexer_.readAlphanumeric());
|
||||||
|
|
||||||
if (name.empty()) {
|
if (name.empty()) {
|
||||||
throw SyntaxError("Mnemonic or directive expected", lexer_.row, lexer_.col);
|
throw SyntaxError(
|
||||||
|
"Mnemonic or directive expected (label='" + label + "')",
|
||||||
|
lexer_.row,
|
||||||
|
lexer_.col
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a directive or data directive
|
// Check if it's a directive or data directive
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue