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")
|
||||
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)
|
||||
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)
|
||||
endif()
|
||||
|
||||
|
||||
if(TARGET simulator_exec)
|
||||
add_custom_target(run
|
||||
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"
|
||||
)
|
||||
if(EXISTS "${PROJECT_SOURCE_DIR}/src/assembler.cpp")
|
||||
add_executable(assembler "${PROJECT_SOURCE_DIR}/src/assembler.cpp")
|
||||
target_link_libraries(assembler PRIVATE simulator_lib)
|
||||
endif()
|
||||
|
||||
message(STATUS "Project: ${PROJECT_NAME}")
|
||||
|
|
|
|||
|
|
@ -1,30 +1,65 @@
|
|||
# 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
|
||||
|
||||
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
|
||||
make run
|
||||
```
|
||||
|
||||
This single command will:
|
||||
- Configure the build system (if needed)
|
||||
- Compile all source files
|
||||
- Link the executable
|
||||
- Run the simulator
|
||||
This will build and run the simulator with the default program.
|
||||
|
||||
## Build Commands
|
||||
|
||||
| Command | Description |
|
||||
|--------------|----------------------------------------------------|
|
||||
| `make` | Build the project |
|
||||
| `make` | Build all executables |
|
||||
| `make build` | Build the project |
|
||||
| `make run` | Build run the simulator |
|
||||
| `make run` | Build and run the simulator |
|
||||
| `make clean` | Clean build artifacts |
|
||||
| `make run` | Clean build artifacts, build and run the simulator |
|
||||
|
||||
|
||||
## Project Structure
|
||||
|
|
|
|||
|
|
@ -37,6 +37,12 @@ private:
|
|||
std::string _programName;
|
||||
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
|
||||
void firstPass();
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ public:
|
|||
enum class RecordType {
|
||||
HEADER,
|
||||
TEXT,
|
||||
MODIFICATION,
|
||||
END,
|
||||
UNKNOWN
|
||||
};
|
||||
|
|
@ -40,6 +41,11 @@ public:
|
|||
int start_address;
|
||||
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 {
|
||||
int execution_start_address;
|
||||
};
|
||||
|
|
@ -54,10 +60,13 @@ private :
|
|||
shared_ptr<Machine> _machine;
|
||||
string _filename;
|
||||
shared_ptr<FileReader> _file_reader;
|
||||
int _relocation_address;
|
||||
HeaderMetadata readHeader();
|
||||
TextRecord readTextRecord();
|
||||
ModificationRecord readModificationRecord();
|
||||
EndRecord readEndRecord();
|
||||
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 <string_view>
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
|
||||
// ==============================
|
||||
// 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((displacement >> 8) & 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 {
|
||||
// Format 3: 12-bit displacement
|
||||
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 << "\n";
|
||||
|
||||
// Clear and rebuild modification records
|
||||
_modificationRecords.clear();
|
||||
|
||||
// T records: text (code/data)
|
||||
std::vector<uint8_t> code = emitCode();
|
||||
int textStart = 0;
|
||||
|
|
@ -462,6 +483,14 @@ std::string Code::emitText() {
|
|||
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
|
||||
oss << "E ";
|
||||
oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << _startAddress;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ Loader::~Loader()
|
|||
void Loader::load()
|
||||
{
|
||||
HeaderMetadata header = readHeader();
|
||||
_relocation_address = header.start_address;
|
||||
|
||||
while(true) {
|
||||
RecordType type = parseRecordType(static_cast<char>(_file_reader->readByte()));
|
||||
|
|
@ -28,6 +29,11 @@ void Loader::load()
|
|||
}
|
||||
break;
|
||||
}
|
||||
case RecordType::MODIFICATION: {
|
||||
ModificationRecord modRecord = readModificationRecord();
|
||||
applyModification(modRecord);
|
||||
break;
|
||||
}
|
||||
case RecordType::END: {
|
||||
EndRecord endRecord = readEndRecord();
|
||||
_machine->setPC(endRecord.execution_start_address);
|
||||
|
|
@ -45,6 +51,7 @@ Loader::RecordType Loader::parseRecordType(char c)
|
|||
switch (c) {
|
||||
case 'H': return RecordType::HEADER;
|
||||
case 'T': return RecordType::TEXT;
|
||||
case 'M': return RecordType::MODIFICATION;
|
||||
case 'E': return RecordType::END;
|
||||
default: return RecordType::UNKNOWN; // fallback; adjust as needed
|
||||
}
|
||||
|
|
@ -105,6 +112,29 @@ Loader::TextRecord Loader::readTextRecord()
|
|||
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()
|
||||
{
|
||||
EndRecord record;
|
||||
|
|
@ -132,3 +162,54 @@ bool Loader::load_into_memory(int start_address, const std::vector<uint8_t> &dat
|
|||
}
|
||||
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 <sstream>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
|
||||
string Node::toString() const {
|
||||
std::ostringstream oss;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue