added M records

This commit is contained in:
zanostro 2025-12-21 17:17:52 +01:00
parent e0ce2fb3d0
commit a711223abf
11 changed files with 297 additions and 20 deletions

View file

@ -15,7 +15,8 @@ file(GLOB_RECURSE SOURCES "${PROJECT_SOURCE_DIR}/src/*.cpp")
set(MAIN_SRC "${PROJECT_SOURCE_DIR}/src/main.cpp") set(MAIN_SRC "${PROJECT_SOURCE_DIR}/src/main.cpp")
list(REMOVE_ITEM SOURCES ${MAIN_SRC}) set(ASSEMBLER_SRC "${PROJECT_SOURCE_DIR}/src/assembler.cpp")
list(REMOVE_ITEM SOURCES ${MAIN_SRC} ${ASSEMBLER_SRC})
if(NOT SOURCES) if(NOT SOURCES)
message(WARNING "No source files found in ${PROJECT_SOURCE_DIR}/src — the build will create an empty library") message(WARNING "No source files found in ${PROJECT_SOURCE_DIR}/src — the build will create an empty library")
@ -32,15 +33,9 @@ if(EXISTS "${PROJECT_SOURCE_DIR}/src/main.cpp")
target_link_libraries(simulator_exec PRIVATE simulator_lib) target_link_libraries(simulator_exec PRIVATE simulator_lib)
endif() endif()
if(EXISTS "${PROJECT_SOURCE_DIR}/src/assembler.cpp")
if(TARGET simulator_exec) add_executable(assembler "${PROJECT_SOURCE_DIR}/src/assembler.cpp")
add_custom_target(run target_link_libraries(assembler PRIVATE simulator_lib)
DEPENDS simulator_exec
COMMAND ${CMAKE_COMMAND} -E echo "Running simulator_exec..."
COMMAND $<TARGET_FILE:simulator_exec>
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Builds and runs simulator_exec"
)
endif() endif()
message(STATUS "Project: ${PROJECT_NAME}") message(STATUS "Project: ${PROJECT_NAME}")

View file

@ -1,30 +1,65 @@
# SIC/XE Simulator # SIC/XE Simulator
A complete SIC/XE architecture simulator with instruction execution, device I/O, and memory management. A complete SIC/XE architecture simulator with instruction execution, device I/O, memory management, and assembler.
## Quick Start ## Quick Start
The easiest way to build and run the simulator: ### Building the Project
```bash
make
```
This will build:
- `target/bin/simulator_exec` - The main simulator
- `target/bin/assembler` - The SIC/XE assembler
- `target/bin/simulator_qt` - Qt GUI version (if Qt is available)
### Using the Assembler
Assemble a SIC/XE assembly file to object code:
```bash
./target/bin/assembler <file.asm>
```
**Example:**
```bash
./target/bin/assembler res/test_format4.asm
```
This will:
- Parse and assemble the input file
- Generate modification records (M records) for format 4 instructions
- Create `<file>.obj` with the object code
- Display the object code and symbol table
**Sample Output:**
```
H TESTF4 0003E8 00001B
T 0003E8 1B 031003F70F1003FA4B1003FD4F2C090000000000000100004F2BFD
M 0003E9 05
M 0003ED 05
M 0003F1 05
E 0003E8
```
### Running the Simulator
```bash ```bash
make run make run
``` ```
This single command will: This will build and run the simulator with the default program.
- Configure the build system (if needed)
- Compile all source files
- Link the executable
- Run the simulator
## Build Commands ## Build Commands
| Command | Description | | Command | Description |
|--------------|----------------------------------------------------| |--------------|----------------------------------------------------|
| `make` | Build the project | | `make` | Build all executables |
| `make build` | Build the project | | `make build` | Build the project |
| `make run` | Build run the simulator | | `make run` | Build and run the simulator |
| `make clean` | Clean build artifacts | | `make clean` | Clean build artifacts |
| `make run` | Clean build artifacts, build and run the simulator |
## Project Structure ## Project Structure

View file

@ -36,6 +36,12 @@ private:
int _programLength = 0; int _programLength = 0;
std::string _programName; std::string _programName;
int _baseRegister = -1; // -1 means not set int _baseRegister = -1; // -1 means not set
struct ModificationRecord {
int address;
int halfBytes;
};
mutable std::vector<ModificationRecord> _modificationRecords;
// Pass 1: build symbol table and assign addresses // Pass 1: build symbol table and assign addresses
void firstPass(); void firstPass();

View file

@ -27,6 +27,7 @@ public:
enum class RecordType { enum class RecordType {
HEADER, HEADER,
TEXT, TEXT,
MODIFICATION,
END, END,
UNKNOWN UNKNOWN
}; };
@ -40,6 +41,11 @@ public:
int start_address; int start_address;
std::vector<uint8_t> data; std::vector<uint8_t> data;
}; };
struct ModificationRecord {
int address; // Address to be modified
int length; // Length in nibbles
bool add; // true for +, false for -
};
struct EndRecord { struct EndRecord {
int execution_start_address; int execution_start_address;
}; };
@ -54,10 +60,13 @@ private :
shared_ptr<Machine> _machine; shared_ptr<Machine> _machine;
string _filename; string _filename;
shared_ptr<FileReader> _file_reader; shared_ptr<FileReader> _file_reader;
int _relocation_address;
HeaderMetadata readHeader(); HeaderMetadata readHeader();
TextRecord readTextRecord(); TextRecord readTextRecord();
ModificationRecord readModificationRecord();
EndRecord readEndRecord(); EndRecord readEndRecord();
bool load_into_memory(int start_address, const std::vector<uint8_t>& data); bool load_into_memory(int start_address, const std::vector<uint8_t>& data);
void applyModification(const ModificationRecord& mod);
}; };

View file

@ -6,6 +6,7 @@
#include <unordered_map> #include <unordered_map>
#include <string_view> #include <string_view>
#include <optional> #include <optional>
#include <cstdint>
// ============================== // ==============================
// Opcode definitions (SIC/XE) // Opcode definitions (SIC/XE)

View file

@ -0,0 +1,32 @@
SIMPLE START 0
+LDA NUM1
+ADD NUM2
+STA RESULT
LDX NUM1
LDL NUM2
LDA #0
ADDR X,A
ADDR L,A
+LDA RESULT
ADD #48
RMO A,S
SHIFTL S,16
SHIFTR S,16
RMO S,A
STCH RESULT
LDCH RESULT
WD OUTPUT
HALT J HALT
OUTPUT BYTE 1
NUM1 WORD 1
NUM2 WORD 2
RESULT RESW 1
END SIMPLE

View file

@ -0,0 +1,11 @@
TESTF4 START 1000
+LDA BUFFER
+STA OUTPUT
+JSUB FUNC
RSUB
BUFFER RESW 1
OUTPUT RESW 1
FUNC LDA #0
RSUB
END TESTF4

View file

@ -0,0 +1,77 @@
#include <iostream>
#include <fstream>
#include <string>
#include "code.h"
#include "parser.h"
#include "opcode.h"
using std::cout;
using std::endl;
using std::cerr;
int main(int argc, char* argv[]) {
if (argc != 2) {
cerr << "Usage: " << argv[0] << " <assembly_file.asm>" << endl;
return 1;
}
std::string inputFile = argv[1];
// Load instruction set
loadInstructionSet();
try {
// Read assembly file
cout << "Assembling: " << inputFile << endl;
std::ifstream file(inputFile);
if (!file.is_open()) {
throw std::runtime_error("Failed to open file: " + inputFile);
}
std::string input;
std::string line;
while (std::getline(file, line)) {
input += line + "\n";
}
file.close();
// Parse
Parser parser;
Code code = parser.parse(input);
// Assemble
code.assemble();
// Generate object code
std::string objectCode = code.emitText();
// Determine output filename
std::string outputFile = inputFile;
size_t lastDot = outputFile.find_last_of('.');
if (lastDot != std::string::npos) {
outputFile = outputFile.substr(0, lastDot);
}
outputFile += ".obj";
// Write to file
std::ofstream out(outputFile);
if (!out.is_open()) {
throw std::runtime_error("Failed to create output file: " + outputFile);
}
out << objectCode;
out.close();
// Display results
cout << "\n=== Object Code ===" << endl;
cout << objectCode;
cout << "\n=== Symbol Table ===" << endl;
cout << code.dumpSymbols();
cout << "\nOutput written to: " << outputFile << endl;
} catch (const std::exception& e) {
cerr << "ERROR: " << e.what() << endl;
return 1;
}
return 0;
}

View file

@ -312,6 +312,24 @@ std::vector<uint8_t> Code::generateInstruction(const InstructionNode* inst, int
bytes.push_back(byte2); bytes.push_back(byte2);
bytes.push_back((displacement >> 8) & 0xFF); bytes.push_back((displacement >> 8) & 0xFF);
bytes.push_back(displacement & 0xFF); bytes.push_back(displacement & 0xFF);
// Format 4 instructions with symbol references (not immediate values) need M records
bool needsRelocation = false;
if (!operands.empty() && std::holds_alternative<SymbolRef>(operands[0])) {
auto& sym = std::get<SymbolRef>(operands[0]);
// If it's not an immediate mode with a constant, it needs relocation
if (!sym.immediate || _symbolTable.find(sym.name) != _symbolTable.end()) {
needsRelocation = true;
}
}
// Record modification if needed
if (needsRelocation) {
ModificationRecord mod;
mod.address = address + 1; // Skip the opcode+ni byte, start at xbpe+addr
mod.halfBytes = 5; // 5 half-bytes (20 bits) for format 4 address field
_modificationRecords.push_back(mod);
}
} else { } else {
// Format 3: 12-bit displacement // Format 3: 12-bit displacement
byte2 |= (displacement >> 8) & 0x0F; byte2 |= (displacement >> 8) & 0x0F;
@ -443,6 +461,9 @@ std::string Code::emitText() {
oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << _programLength; oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << _programLength;
oss << "\n"; oss << "\n";
// Clear and rebuild modification records
_modificationRecords.clear();
// T records: text (code/data) // T records: text (code/data)
std::vector<uint8_t> code = emitCode(); std::vector<uint8_t> code = emitCode();
int textStart = 0; int textStart = 0;
@ -462,6 +483,14 @@ std::string Code::emitText() {
textStart += textLength; textStart += textLength;
} }
// M records: modifications for format 4 instructions
for (const auto& mod : _modificationRecords) {
oss << "M ";
oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << mod.address << " ";
oss << std::setfill('0') << std::setw(2) << std::hex << std::uppercase << mod.halfBytes;
oss << "\n";
}
// E record: execution start address // E record: execution start address
oss << "E "; oss << "E ";
oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << _startAddress; oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << _startAddress;

View file

@ -17,6 +17,7 @@ Loader::~Loader()
void Loader::load() void Loader::load()
{ {
HeaderMetadata header = readHeader(); HeaderMetadata header = readHeader();
_relocation_address = header.start_address;
while(true) { while(true) {
RecordType type = parseRecordType(static_cast<char>(_file_reader->readByte())); RecordType type = parseRecordType(static_cast<char>(_file_reader->readByte()));
@ -28,6 +29,11 @@ void Loader::load()
} }
break; break;
} }
case RecordType::MODIFICATION: {
ModificationRecord modRecord = readModificationRecord();
applyModification(modRecord);
break;
}
case RecordType::END: { case RecordType::END: {
EndRecord endRecord = readEndRecord(); EndRecord endRecord = readEndRecord();
_machine->setPC(endRecord.execution_start_address); _machine->setPC(endRecord.execution_start_address);
@ -45,6 +51,7 @@ Loader::RecordType Loader::parseRecordType(char c)
switch (c) { switch (c) {
case 'H': return RecordType::HEADER; case 'H': return RecordType::HEADER;
case 'T': return RecordType::TEXT; case 'T': return RecordType::TEXT;
case 'M': return RecordType::MODIFICATION;
case 'E': return RecordType::END; case 'E': return RecordType::END;
default: return RecordType::UNKNOWN; // fallback; adjust as needed default: return RecordType::UNKNOWN; // fallback; adjust as needed
} }
@ -105,6 +112,29 @@ Loader::TextRecord Loader::readTextRecord()
return record; return record;
} }
Loader::ModificationRecord Loader::readModificationRecord()
{
ModificationRecord record;
if(FILE_CONTAINS_WHITE_SPACES) _file_reader->readByte();
record.address = std::stoi(_file_reader->readString(6), nullptr, 16);
if(FILE_CONTAINS_WHITE_SPACES) _file_reader->readByte();
record.length = std::stoi(_file_reader->readString(2), nullptr, 16);
record.add = true;
std::string rest = _file_reader->readLine();
// Remove whitespace
rest.erase(std::remove_if(rest.begin(), rest.end(), ::isspace), rest.end());
if (!rest.empty()) {
if (rest[0] == '-') {
record.add = false;
}
}
return record;
}
Loader::EndRecord Loader::readEndRecord() Loader::EndRecord Loader::readEndRecord()
{ {
EndRecord record; EndRecord record;
@ -132,3 +162,54 @@ bool Loader::load_into_memory(int start_address, const std::vector<uint8_t> &dat
} }
return true; return true;
} }
void Loader::applyModification(const ModificationRecord& mod)
{
// M record specifies address and length in half-bytes (nibbles)
// We need to modify the value at that address by adding or subtracting
// the relocation address
int address = mod.address;
int halfBytes = mod.length;
// Calculate how many full bytes we need to read
// halfBytes can be odd or even
int numBytes = (halfBytes + 1) / 2;
if (address < 0 || address + numBytes > MEMORY_SIZE) {
throw std::runtime_error("Modification address out of bounds");
}
// Read the current value from memory
int currentValue = 0;
for (int i = 0; i < numBytes; ++i) {
currentValue = (currentValue << 8) | _machine->getByte(address + i);
}
// If odd number of half-bytes, we only modify the relevant nibbles
// For simplicity, we'll work with the full bytes and mask appropriately
int mask = 0;
for (int i = 0; i < halfBytes; ++i) {
mask = (mask << 4) | 0xF;
}
// Extract the value to modify
int shift = (numBytes * 2 - halfBytes) * 4;
int valueToModify = (currentValue >> shift) & mask;
// Apply modification
int newValue = mod.add ? (valueToModify + _relocation_address)
: (valueToModify - _relocation_address);
// Mask to keep only the relevant bits
newValue &= mask;
// Reconstruct the full value
int preservedBits = currentValue & ~(mask << shift);
int finalValue = preservedBits | (newValue << shift);
// Write back to memory (big-endian)
for (int i = 0; i < numBytes; ++i) {
_machine->setByte(address + i, (finalValue >> ((numBytes - 1 - i) * 8)) & 0xFF);
}
}

View file

@ -1,6 +1,7 @@
#include "node.h" #include "node.h"
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include <algorithm>
string Node::toString() const { string Node::toString() const {
std::ostringstream oss; std::ostringstream oss;