Virtual CPU House Keeping - Following Part 2

IF YOU HAVE JUST ARRIVED HERE, PLEASE SEE: http://megalomaniacbore.blogspot.co.uk/2014/04/virtual-cpu-in-c-4001-cpu.html

-- main.cpp --

#include <iostream>
#include <exception>

#include "Memory.h"
#include "CPU.h"

using namespace std;
using namespace CPU_4001;

void DefaultProgram(Memory*);
void ReportMemory(Memory*);
void ClearMemory (Memory*);
void LoadMemory(Memory*);
void SaveMemory(Memory*);

void ShowMenu();
const bool ChooseFromMenu(const char&, Memory*);
const bool IsExitOption(const char& selection);

void ProgramEntry(Memory*);

bool _debugMode = false;
void ToggleDebugMode();

int main ()
{
cout << "Init Memory...";
Memory* theMemory = new Memory();
cout << "Ready" << endl;

// Changes here: We have the CPU ONLY active
// for the cycle from the menu through, however
// we don't want to loose the memory, so that is
// created outside the loop
bool quitAll = false;
char selectedOption = ' ';
do
{
// Show the menu & read in a
// character (plus enter) for
// the next option to perform
ShowMenu();
cin >> selectedOption;

// Check if we're exiting
if ( !IsExitOption(selectedOption) )
{
// Check if we're running
if ( ChooseFromMenu(selectedOption, theMemory) )
{
cout << "************************************" << endl;
// Create the CPU
CPU* theCPU = new CPU(theMemory, _debugMode);
// Run the program in memory
theCPU->Run();
// The CPU is done
delete theCPU;
cout << "************************************" << endl;
}
}
else
{
quitAll = true;
}
}
while ( !quitAll );

delete theMemory;
}

void ProgramEntry(Memory* p_Memory)
{
cout << "Entering Program Entry, by memory address:" << endl;
cout << "N : Next address" << endl;
cout << "B : Previous Address" << endl;
cout << "<BYTE> changes current address + increment address" << endl;
if ( p_Memory != nullptr )
{
bool exitEditor = false;
byte value;
std::string input;
byte currentAddress = 2; // This is our base address
do
{
cout << "[" << (int)currentAddress << "] (" << (int)p_Memory->Read(currentAddress) << ") > ";
cin >> input;

// Now we need to know
// what our options are
// first off, if we enter
// a number it'll just
// get added to the current
// memory address and
// the memory address
// will increase by 1
try
{
// We also offer other options
if ( input == "x" || input == "X" )
{
exitEditor = true;
}
else if ( input == "b" || input == "B" || input == "back" )
{
--currentAddress;
}
else if ( input == "n" || input == "N" || input == "next" )
{
++currentAddress;
}
else
{
// So lets convert the string input
// to a number
int temp = atoi(input.c_str());
value = (byte)temp;

p_Memory->Write(currentAddress, value);
++currentAddress;
}
}
catch (std::exception& ex)
{
// Ptoblem
}
}
while ( !exitEditor );
}
}

void ShowMenu()
{
cout << endl << "===== Menu =====" << endl;
cout << "1. Load Default Program" << endl;
cout << "2. Clear the Memory" << endl;
cout << "3. Report Memory" << endl;
cout << "R. Run the current memory state through the CPU" << endl;
cout << "E. Enter Program Editor" << endl;
cout << endl;
cout << "D. Toggle Debug Settings [" << _debugMode << "]" << endl;
cout << "L. Load Memory" << endl;
cout << "S. Save Memory" << endl;
cout << endl;
cout << "X. Exit Interpter" << endl;
cout << endl;
cout << "Selection: ";
}

const bool IsExitOption(const char& selection)
{
return (selection == 'x' || selection == 'X');
}

const bool ChooseFromMenu(const char& selection, 
Memory* theMemory)
{
bool l_RunThroughCPU = false;

switch (selection)
{
case '1': 
DefaultProgram(theMemory);
break;

case '2':
ClearMemory(theMemory);
break;

case '3':
ReportMemory(theMemory);
break;

case 'r':
case 'R':
l_RunThroughCPU = true;
break;

case 'e':
case 'E':
ProgramEntry(theMemory);
break;

case 'd':
case 'D':
ToggleDebugMode();
break;

case 's':
case 'S':
SaveMemory(theMemory);
break;

case 'l':
case 'L':
LoadMemory(theMemory);
break;
}

return l_RunThroughCPU;
}

void ClearMemory(Memory* theMemory)
{
if ( theMemory != nullptr )
{
cout << "Clearing the memory...";
theMemory->Clear();
cout << "Complete" << endl;
}
}

void DefaultProgram(Memory* theMemory)
{
if ( theMemory != nullptr )
{
ClearMemory(theMemory);

// Add the program
cout << "Adding our default machine code program..." << endl;
// Load0 value 1
theMemory->Write(1, 1);
theMemory->Write(2, 1);

// Load1 Value 2
theMemory->Write(3, 2);
theMemory->Write(4, 2);

// Add
theMemory->Write(5, 3);

// Store to 12
theMemory->Write(6, 5);
theMemory->Write(7, 12);

// Print from 12
theMemory->Write(8, 6);
theMemory->Write(9, 12);

// Beep
theMemory->Write(10, 4);

// HALT
theMemory->Write(11, 0);
}
}

void ReportMemory(Memory* theMemory)
{
// Now, we only need to add "(int)" here, because the cout
// stream does not know to use our "byte" as a number, the
// C++ language would just assume that our memory spot is
// an "unsigned char"... or character, so we'd output garbage.
// (int) in front simply means "Treat this as a number"...
cout << "Memory Size: " << (int)theMemory->c_MaxAddress << endl;

cout << "Do you want to list the memory?";
char yesNo;
cin >> yesNo;
if ( yesNo == 'Y' || yesNo == 'y' )
{
for (byte currentAddress = 0; currentAddress < theMemory->c_MaxAddress; ++currentAddress)
{
// Again, add "(int)" to force usage as a number
cout << "Address [" << (int)currentAddress << "] = " << (int)theMemory->Read(currentAddress) << endl;
}
}
}

void LoadMemory(Memory* theMemory)
{
if ( theMemory != nullptr )
{
cout << endl << "Load Memory from: ";
string filename;
cin >> filename;
theMemory->Load(filename);
cout << endl;
}
}

void SaveMemory(Memory* theMemory)
{
if ( theMemory != nullptr )
{
cout << endl << "Save Memory to: ";
string filename;
cin >> filename;
theMemory->Save(filename);
cout << endl;
}
}

void ToggleDebugMode ()
{
_debugMode = !_debugMode;
}

-- Memory.h --

#ifndef CPU_MEMORY
#define CPU_MEMORY

#include <string>

namespace CPU_4001
{

// Because C++ does not work directly in byte sized memory as numbers
// we'll use the byte sized character type as our byte of memory.
typedef unsigned char byte;

class Memory
{
public:

/// A constant we're going to define which
/// tells us the maximum address we can
/// read or write from.
const byte c_MaxAddress;

private:

/// This is the memory space we're going to
/// be using.
byte* m_MemorySpace;

public:

/// Construct a Memory class instance
/// for us, and clear the memory
Memory();

/// Delete the memory class, releasing
/// all the allocated memory space
~Memory();

/// Function to clear the memory values all to zero
void Clear();

/// Function to read the given address value
const byte& Read (const byte& p_Address);

/// Function to write the value to the given address
void Write (const byte& p_Address, const byte& p_Value);

/// Save to file
void Save(const std::string& p_Filename);

/// Load from file
void Load(const std::string& p_Filename);

};

}

#endif

-- Memory.cpp --

#include "Memory.h"

#include <iostream>
#include <fstream>
#include <cstdlib>

namespace CPU_4001
{

Memory::Memory()
:
c_MaxAddress(255), // The maximum constant
m_MemorySpace (new byte[c_MaxAddress]) // The memory
{
Clear(); // Our only function, clears the memory
// in C++ we allocate the variables in the
// constructor header, NOT here in the 
// Constructor body!
}

Memory::~Memory()
{
if ( m_MemorySpace != nullptr )
{
delete[] m_MemorySpace;
m_MemorySpace = nullptr;
}
}

void Memory::Clear()
{
for (byte i = 0; i < c_MaxAddress; ++i)
{
m_MemorySpace[i] = 0;
}
}

const byte& Memory::Read(const byte& p_Address)
{
return m_MemorySpace[p_Address];
}

void Memory::Write(const byte& p_Address, const byte& p_Value)
{
m_MemorySpace[p_Address] = p_Value;
}

void Memory::Save(const std::string& p_Filename)
{
using namespace std;
ofstream file (p_Filename, ios_base::out);
if ( file.good() )
{
for (byte i = 0; i < c_MaxAddress; ++i)
{
file << (int)m_MemorySpace[i] << endl;
}

file.close();
}
else
{
cout << "Bad path [" << p_Filename << "]" << endl;
}
}

void Memory::Load(const std::string& p_Filename)
{
Clear();

using namespace std;
ifstream file(p_Filename, ios_base::in);
if ( file.good() )
{
int i = 0;
int temp;
while ( file.good() )
{
string buff;
//cout << "Load [" << i << "]" << endl;
file >> buff;
//cout << "Loaded [" << buff << "]" << endl;
temp = atoi(buff.c_str());
//cout << "atoi [" << (int)temp << "]" << endl;
m_MemorySpace[i] = (byte)temp;
++i;
}


file.close();
}
else
{
cout << "File not found [" << p_Filename << "]" << endl;
}
}
}

-- CPU.h --

#ifndef CPU_PROCESSOR
#define CPU_PROCESSOR

#include "Memory.h"
#include <string>

namespace CPU_4001
{

class CPU
{
public:

const byte c_ReservedAddress;
const byte c_BaseAddress;
const byte c_JumpToAddress;
const byte c_AddressCeiling;

private:

byte m_ProgramCounter;
byte m_Register0;
byte m_Register1;
bool m_OverflowError;
bool m_UnderflowError;
bool m_SignedMode;
bool m_Halt;
bool m_DebugMode;

Memory* m_TheMemory;

const byte Fetch();

void Decode(const byte& p_OpCode);

void Halt();

void Add();

void Multiply();

void Beep();

void Store();

void Print();

void ClearRegister0();

void ClearRegister1();

void ClearBoth();

void JumpTo();

void JumpEqual();

// DOH, ALWAYS SAVE YOUR HEADER AND SOURCE FILES!
void Copy0();

void Copy1();

void Log(const std::string& p_Message);

void Load0();

void Load1();

public:

CPU(Memory* p_TheMemory, const bool& p_DebugMode = false);

~CPU();

void Reset();

void Run();

};

}

#endif

-- CPU.cpp --

#include "CPU.h"

#include <iostream>

namespace CPU_4001
{

CPU::CPU(Memory* p_TheMemory,
const bool& p_DebugMode)
:
c_ReservedAddress(0),
c_BaseAddress(2),
c_JumpToAddress(1),
c_AddressCeiling(253),
m_ProgramCounter(c_BaseAddress),
m_Register0(0),
m_Register1(0),
m_OverflowError(false),
m_UnderflowError(false),
m_SignedMode(false),
m_TheMemory(p_TheMemory), // DOH!
m_Halt(false),
m_DebugMode(p_DebugMode)
{
}

CPU::~CPU()
{
m_TheMemory = nullptr;
}

void CPU::Reset()
{
m_Halt = false;
m_ProgramCounter = c_BaseAddress; // FIX!
m_OverflowError = false;
m_UnderflowError = false;
m_SignedMode = false;
}

const byte CPU::Fetch()
{
byte l_opCode = 0;
l_opCode = m_TheMemory->Read(m_ProgramCounter); // Whoops!
++m_ProgramCounter;
if ( m_ProgramCounter > c_AddressCeiling )
{
Halt();
}
return l_opCode;
}

void CPU::Halt()
{
if ( m_DebugMode )
{
Log("Halt");
}
m_Halt = true;
}

void CPU::Add()
{
if ( m_DebugMode )
{
Log("Add");
std::cout << "Before [" << (int)m_Register0 << ", " << (int)m_Register1 << "]" << std::endl;
}

m_Register0 = m_Register0 + m_Register1;

if ( m_DebugMode )
{
std::cout << "After [" << (int)m_Register0 << ", " << (int)m_Register1 << "]" << std::endl;
}

}

void CPU::Multiply()
{
if ( m_DebugMode )
{
Log("Add");
std::cout << "Before [" << (int)m_Register0 << ", " << (int)m_Register1 << "]" << std::endl;
}

m_Register0 = m_Register0 * m_Register1;

if ( m_DebugMode )
{
std::cout << "After [" << (int)m_Register0 << ", " << (int)m_Register1 << "]" << std::endl;
}
}

void CPU::Beep()
{
if ( m_DebugMode )
{
Log("Beep");
}
std::cout << '\a';
}

void CPU::Store()
{
if ( m_DebugMode )
{
Log("Store");
}

// Load the target address into register 1
m_Register1 = m_TheMemory->Read(m_ProgramCounter);

if ( m_DebugMode )
{
std::cout << "Target Address: " << (int)m_Register1 << std::endl;
std::cout << "Value to Write: " << (int)m_Register0 << std::endl;
}

++m_ProgramCounter; // Skip the memory location data
// Write the register 0 value to this address
m_TheMemory->Write(m_Register1, m_Register0);
// Remember the order of our parameters
// was ADDRESS then VALUE!

if ( m_DebugMode )
{
std::cout << "Written value: " << (int)m_TheMemory->Read(m_Register1) << std::endl;
}
}

void CPU::Print()
{
if ( m_DebugMode )
{
Log("Print");
}

// Load the target addressinto register 1 // DOH!
m_Register1 = m_TheMemory->Read(m_ProgramCounter);
++m_ProgramCounter;

// The value in the register is now the value to load
m_Register0 = m_TheMemory->Read(m_Register1);

if ( m_DebugMode )
{
std::cout << "Printing from: " << (int)m_Register1 << " -> value: " << (int)m_Register0 << std::endl;
}

// Output the register
std::cout << (int)m_Register0 << std::endl;
// I'm going to add endline!
}

void CPU::ClearRegister0()
{
if ( m_DebugMode )
{
Log("Clear 0");
}

m_Register0 = 0;
}

void CPU::ClearRegister1()
{
if ( m_DebugMode )
{
Log("Clear 1");
}
m_Register1 = 0;
}

void CPU::ClearBoth()
{
if ( m_DebugMode )
{
Log("Clear Both");
}
ClearRegister0();
ClearRegister1();
}

void CPU::JumpTo ()
{
if ( m_DebugMode)
{
Log("Jump To");
}

// Read the program counter location
// into the jump to address for use
m_TheMemory->Write(
c_JumpToAddress, // Whoops
m_TheMemory->Read(m_ProgramCounter));
++m_ProgramCounter;

if ( m_DebugMode )
{
std::cout << "Jump Register set to [" << (int)m_TheMemory->Read(c_JumpToAddress) << "]" << std::endl;
}

// Read back the jump to address
// as the program counter
m_ProgramCounter = m_TheMemory->Read(c_JumpToAddress);

if ( m_DebugMode )
{
std::cout << "Program Counter now [" << (int)m_ProgramCounter << "]" << std::endl;
}
}

void CPU::JumpEqual()
{
if ( m_Register0 == m_Register1 )
{
if ( m_DebugMode )
{
Log ("Jump Equal - Jumping");
}
JumpTo();
}
else
{
if ( m_DebugMode )
{
Log ("Jump Equal - Not Jumping");
}
// Skip over the address of the jump!
++m_ProgramCounter;
}
}

void CPU::Copy0()
{
if ( m_DebugMode )
{
Log("Copy0");
}

m_Register1 = m_Register0;
}

void CPU::Copy1()
{
if ( m_DebugMode )
{
Log("Copy1");
}
m_Register0 = m_Register1;
}

void CPU::Log(const std::string& p_Message)
{
std::cout << p_Message << std::endl;
}

void CPU::Load0()
{
if ( m_DebugMode )
{
Log("Load0");
}

// Load the address
m_Register0 = m_TheMemory->Read(m_ProgramCounter);

//std::cout << "Register 0 Load Address [" << (int)m_Register0 << "]" << std::endl;

// Skip past the address data
++m_ProgramCounter;
// Read from memory
m_Register0 = m_TheMemory->Read(m_Register0);

//std::cout << "Register 0 [" << (int)m_Register0 << "]" << std::endl;
}

void CPU::Load1()
{
if ( m_DebugMode )
{
Log("Load1");
}

// Load the Address
m_Register1 = m_TheMemory->Read(m_ProgramCounter);

//std::cout << "Register 1 Load Address [" << (int)m_Register1 << "]" << std::endl;

// Skip past the address data
++m_ProgramCounter;
// Read frommemory
m_Register1 = m_TheMemory->Read(m_Register1);

//std::cout << "Register 1 [" << (int)m_Register1 << "]" << std::endl;
}

void CPU::Decode(const byte& p_OpCode) // DOH!
{
// Special, when the system has pre-halted in Fetch
// We do not decode!
if ( m_Halt ) return;


switch (p_OpCode)
{
// HALT
case 0:
Halt();
break;

// Load 0
case 1:
Load0();
break;

// Load 1
case 2:
Load1();
break;

// Add
case 3:
Add();
break;

// Beep
case 4:
Beep();
break;

// Store operation
case 5:
Store();
break;

// Print operation
case 6:
Print();
break;

// Clear Register 0
case 7:
ClearRegister0();
break;

// Clear Register 1
case 8:
ClearRegister1();
break;

// Jump to
case 9:
JumpTo();
break;

// Jump Equal
case 10:
JumpEqual();
break;

// Copy 0
case 11:
Copy0();
break;

// Copy 1
case 12:
Copy1();
break;

// Clear both
case 13:
ClearBoth();
break;

// Multiply
case 14:
Multiply();
break;

default:
Log("Unknown Op code - halting");
Halt();
break;
}
}

void CPU::Run()
{
// While we don't see a HALT, keep going
while (!m_Halt)
{
if ( m_DebugMode )
{
std::cout << "[" << (int)m_ProgramCounter << "]" << std::endl;
}

// Fetch into the reserved memory
// location
m_TheMemory->Write(c_ReservedAddress, Fetch());

// Decode from the memory reserved address
Decode(m_TheMemory->Read(c_ReservedAddress));
}
}

}

No comments:

Post a Comment