added M records
This commit is contained in:
parent
e0ce2fb3d0
commit
a711223abf
11 changed files with 297 additions and 20 deletions
|
|
@ -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}")
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,12 @@ private:
|
||||||
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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
32
simulator_SIC_XE/res/simple.asm
Normal file
32
simulator_SIC_XE/res/simple.asm
Normal 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
|
||||||
11
simulator_SIC_XE/res/test_format4.asm
Normal file
11
simulator_SIC_XE/res/test_format4.asm
Normal 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
|
||||||
77
simulator_SIC_XE/src/assembler.cpp
Normal file
77
simulator_SIC_XE/src/assembler.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue