Skip to content

Commit

Permalink
Merge pull request #138 from nbeddows/development
Browse files Browse the repository at this point in the history
#136 feat: release 1.6.2
  • Loading branch information
nbeddows authored Jul 24, 2024
2 parents 606d126 + d2ab803 commit ab49603
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 82 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
1.6.2 [24/07/24]
* Deprecated config options `ramOffset`, `ramSize`,
`romOffset` and `romSize`.
* Added support for fragmented ram/rom for load/save
files via the following config options:
`ram:file:offset`, `ram:file:size`, `rom:block:offset`
and `rom:block:size`.

1.6.1 [24/06/24]
* Disable running the unit tests in conanfile.py
for foreign architectures.
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ set(source_dir source)
set(libMachEmu mach_emu)
set(major 1)
set(minor 6)
set(bugfix 1)
set(bugfix 2)

project(${libMachEmu} VERSION ${major}.${minor}.${bugfix})

Expand Down
64 changes: 42 additions & 22 deletions Machine/source/Machine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,15 @@ namespace MachEmu
throw std::runtime_error("Incompatible memory controller");
}

std::vector<uint8_t> rom(opt_.RomSize());
auto romMetadata = opt_.Rom();
std::vector<uint8_t> rom;

for (auto addr = opt_.RomOffset(); addr < opt_.RomSize(); addr++)
for (const auto& rm : romMetadata)
{
rom[addr] = memoryController_->Read(addr);
for (int addr = rm.first; addr < rm.first + rm.second; addr++)
{
rom.push_back(memoryController_->Read(addr));
}
}

// The rom must be the same
Expand All @@ -213,21 +217,31 @@ namespace MachEmu
jsonRam["compressor"].get<std::string>(),
jsonRam["size"].get<uint32_t>(),
jsonRam["bytes"].get<std::string>());


auto ramMetadata = opt_.Ram();
int ramSize = 0;
int ramIndex = 0;

for (const auto& rm : ramMetadata)
{
ramSize += rm.second;
}

// Make sure the ram size matches the layout
if (ram.size() != opt_.RamSize())
if (ram.size() != ramSize)
{
throw std::runtime_error("Incompatible ram");
}

// Once all checks are complete, restore the cpu and the memory
cpu_->Load(json["cpu"].dump());

auto addr = opt_.RamOffset();

for (const auto& r : ram)

for (const auto& rm : ramMetadata)
{
memoryController_->Write(addr++, r);
for (int addr = rm.first; addr < rm.first + rm.second; addr++)
{
memoryController_->Write(addr, ram[ramIndex++]);
}
}
}
catch (const std::exception& e)
Expand Down Expand Up @@ -333,20 +347,23 @@ namespace MachEmu
throw std::runtime_error("Invalid memory controller uuid for save interrupt");
}

auto rm = [this](uint16_t offset, uint16_t size)
auto rm = [this](std::vector<std::pair<uint16_t, uint16_t>>&& metadata)
{
std::vector<uint8_t> mem(size);
std::vector<uint8_t> mem;

for (auto& byte : mem)
for (const auto& m : metadata)
{
byte = memoryController_->Read(offset++);
for (auto addr = m.first; addr < m.first + m.second; addr++)
{
mem.push_back(memoryController_->Read(addr));
}
}

return mem;
};

auto rom = rm(opt_.RomOffset(), opt_.RomSize());
auto ram = rm(opt_.RamOffset(), opt_.RamSize());
auto ram = rm(opt_.Ram());
auto rom = rm(opt_.Rom());
auto fmtStr = "{\"cpu\":%s,\"memory\":{\"uuid\":\"%s\",\"rom\":\"%s\",\"ram\":{\"encoder\":\"%s\",\"compressor\":\"%s\",\"size\":%d,\"bytes\":\"%s\"}}}";
auto romMd5 = Utils::Md5(rom.data(), rom.size());

Expand Down Expand Up @@ -530,20 +547,23 @@ namespace MachEmu
throw std::runtime_error("memory controller not set!");
}

auto rm = [this](uint16_t offset, uint16_t size)
auto rm = [this](std::vector<std::pair<uint16_t, uint16_t>>&& metadata)
{
std::vector<uint8_t> mem(size);
std::vector<uint8_t> mem;

for (auto& byte : mem)
for (const auto& m : metadata)
{
byte = memoryController_->Read(offset++);
for (auto addr = m.first; addr < m.first + m.second; addr++)
{
mem.push_back(memoryController_->Read(addr));
}
}

return mem;
};

auto rom = rm(opt_.RomOffset(), opt_.RomSize());
auto ram = rm(opt_.RamOffset(), opt_.RamSize());
auto rom = rm(opt_.Rom());
auto ram = rm(opt_.Ram());
auto fmtStr = "{\"cpu\":%s,\"memory\":{\"uuid\":\"%s\",\"rom\":\"%s\",\"ram\":{\"encoder\":\"%s\",\"compressor\":\"%s\",\"size\":%d,\"bytes\":\"%s\"}}}";
auto memUuid = memoryController_->Uuid();
auto romMd5 = Utils::Md5(rom.data(), rom.size());
Expand Down
30 changes: 12 additions & 18 deletions Opt/include/Opt/Opt.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,29 +113,23 @@ namespace MachEmu
*/
bool LoadAsync() const;

/** The ram offset
/** Ram metadata
This vector defines blocks of ram.
@return The offset in bytes from the start of memory to the ram.
The first pair entry is the offset from the start of memory to the start of the ram block.
The second pair entry is the size of the ram block.
*/
uint16_t RamOffset() const;
std::vector<std::pair<uint16_t, uint16_t>> Ram() const;

/** The ram size
/** Rom metadata
This vector defines blocks of rom.
@return The size in bytes of the ram.
The first pair entry is the offset from the start of memory to the start of the rom block.
The second pair entry is the size of the rom block.
*/
uint16_t RamSize() const;

/** The rom offset
@return The offset in bytes from the start of memory to the rom.
*/
uint16_t RomOffset() const;

/** The rom offset
@return The size in bytes of the ram.
*/
uint16_t RomSize() const;
std::vector<std::pair<uint16_t, uint16_t>> Rom() const;

/** Machine run mode
Expand Down
71 changes: 58 additions & 13 deletions Opt/source/Opt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ namespace MachEmu
#else
"none"
#endif
R"(","encoder":"base64","isrFreq":0,"loadAsync":false,"ramOffset":0,"ramSize":0,"romOffset":0,"romSize":0,"runAsync":false,"saveAsync":false})";
R"(","encoder":"base64","isrFreq":0,"loadAsync":false,"rom":{"file":[{"offset":0,"size":0}]},"ram":{"block":[{"offset":0,"size":0}]},"runAsync":false,"saveAsync":false})";
return defaults;
}

Expand Down Expand Up @@ -101,6 +101,45 @@ namespace MachEmu
throw std::runtime_error("mach-emu has been compiled with no zlib support");
}
#endif

// Handle deprecated properties, remove in 2.0.0
// Only convert if we don't have ram/rom properties, if we do, then any deprecated property
// usage will be dropped
if (json.contains("ram") == false && json.contains("rom") == false)
{
auto j = nlohmann::json::parse(R"({"rom":{"file":[{"offset":0,"size":0}]},"ram":{"block":[{"offset":0,"size":0}]}})");
bool update = false;

if(json.contains("ramOffset"))
{
j["ram"]["block"][0]["offset"] = json["ramOffset"].get<uint16_t>();
update = true;
}

if(json.contains("ramSize") && !json.contains("ram"))
{
j["ram"]["block"][0]["size"] = json["ramSize"].get<uint16_t>();
update = true;
}

if(json.contains("romOffset") && !json.contains("ram"))
{
j["rom"]["file"][0]["offset"] = json["romOffset"].get<uint16_t>();
update = true;
}

if(json.contains("ramSize"))
{
j["rom"]["file"][0]["size"] = json["romSize"].get<uint16_t>();
update = true;
}

if (update == true)
{
json.update(j);
}
}
// End remove
}

json_->update(json);
Expand Down Expand Up @@ -145,24 +184,30 @@ namespace MachEmu
return (*json_)["loadAsync"].get<bool>();
}

uint16_t Opt::RamOffset() const
std::vector<std::pair<uint16_t, uint16_t>> Opt::Ram() const
{
return (*json_)["ramOffset"].get<uint16_t>();
}
std::vector<std::pair<uint16_t, uint16_t>> ram;
auto blocks = (*json_)["ram"]["block"];

uint16_t Opt::RamSize() const
{
return (*json_)["ramSize"].get<uint16_t>();
}
for (const auto& block : blocks)
{
ram.emplace_back(std::pair(block["offset"].get<uint16_t>(), block["size"].get<uint16_t>()));
}

uint16_t Opt::RomOffset() const
{
return (*json_)["romOffset"].get<uint16_t>();
return ram;
}

uint16_t Opt::RomSize() const
std::vector<std::pair<uint16_t, uint16_t>> Opt::Rom() const
{
return (*json_)["romSize"].get<uint16_t>();
std::vector<std::pair<uint16_t, uint16_t>> rom;
auto files = (*json_)["rom"]["file"];

for(const auto& file : files)
{
rom.emplace_back(std::pair(file["offset"].get<uint16_t>(), file["size"].get<uint16_t>()));
}

return rom;
}

bool Opt::RunAsync() const
Expand Down
54 changes: 29 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ The following additional options are supported:
- disable running the exported package tests: `--test_folder=""`
- enable/disable the unit tests for the i8080 suites: `--options=with_i8080_test_suites=[True|False(default)]`

NOTE: a pre-requisite of the exporting the package is the running of the unit tests (unless disabled). The export process will halt if the unit tests fail.
NOTE: a pre-requisite of exporting the package is the running of the unit tests (unless disabled). The export process will halt if the unit tests fail.

Example command lines:
1. `conan create . --build=missing`: build the mach_emu package, run the unit tests, export it to the conan cache and then run a basic test to confirm that the exported package can be used.
Expand Down Expand Up @@ -234,7 +234,7 @@ machine->OnSave([](const char* json)

// Set the ram/rom sizes (0x2000 and 0x4000) and offsets (0x0000, 0x2000) for this custom memory controller
// These values are used for load and save requests
machine_->SetOptions(R"({"romOffset":0,"romSize":8192,"ramOffset":8192,"ramSize":16384})");
machine_->SetOptions(R"({"rom":{"file":[{"offset":0,"size":8192}]},"ram":{"block":[{"offset":8192,"size":16384}]}})");

// Set the clock resolution - not setting this will run the
// machine as fast as possible (default)
Expand Down Expand Up @@ -270,29 +270,33 @@ Supported protocols:
The following table describes the supported options (note, when no option is specifed the one marked as default will be used):
| Option | Type | Value | Remarks |
|:----------------|:-------|:-------------------|:-----------------------------------------------------------------------------------|
| clockResolution | int64 | -1 (default) | Run the machine as fast as possible with the highest possible resolution |
| | | 0 | Run the machine at realtime (or as close to) with the highest possible resolution |
| | | 0 - 1000000 | Will always spin the cpu to maintain the clock speed and is not recommended |
| | | n | A request in nanoseconds as to how frequently the machine clock will tick |
| compressor | string | "zlib" (default) | Use zlib compression library to compress the ram when saving its state |
| | | "none" | No compression will be used when saving the state of the ram |
| encoder | string | "base64" (default) | The binary to text encoder to use when saving the machine state ram to json |
| cpu | string | "i8080" (default) | A machine based on the Intel8080 cpu (can only be set via MachEmu::MakeMachine) |
| isrFreq | double | 0 (default) | Service interrupts at the completion of each instruction |
| | | 1 | Service interrupts after each clock tick |
| | | n | Service interrupts frequency, example: 0.5 - twice per clock tick |
| loadAsync | bool | true | Run the load initiation handler on a separate thread |
| | | false (default) | Run the load initiation handler from the thread specified by the `runAsync` option |
| ramOffset | uint16 | n (default: 0) | The offset in bytes from the start of the memory to the start of the ram |
| ramSize | uint16 | n (default: 0) | The size of the ram in bytes |
| romOffset | uint16 | n (default: 0) | The offset in bytes from the start of the memory to the start of the rom |
| romSize | uint16 | n (default: 0) | The size of the rom in bytes |
| runAsync | bool | true | `IMachine::Run` will launch its execution loop on a separate thread |
| | | false (default) | `IMachine::Run` will run its execution loop on the current thread |
| saveAsync | bool | true | Run the save completion handler on a separate thread |
| | | false (default) | Run the save completion handler from the thread specifed by the `runAsync` option |
| Option | Type | Value | Remarks |
|:----------------------|:-------|:-------------------|:-----------------------------------------------------------------------------------|
| clockResolution | int64 | -1 (default) | Run the machine as fast as possible with the highest possible resolution |
| | | 0 | Run the machine at realtime (or as close to) with the highest possible resolution |
| | | 0 - 1000000 | Will always spin the cpu to maintain the clock speed and is not recommended |
| | | n | A request in nanoseconds as to how frequently the machine clock will tick |
| compressor | string | "zlib" (default) | Use zlib compression library to compress the ram when saving its state |
| | | "none" | No compression will be used when saving the state of the ram |
| encoder | string | "base64" (default) | The binary to text encoder to use when saving the machine state ram to json |
| cpu | string | "i8080" (default) | A machine based on the Intel8080 cpu (can only be set via MachEmu::MakeMachine) |
| isrFreq | double | 0 (default) | Service interrupts at the completion of each instruction |
| | | 1 | Service interrupts after each clock tick |
| | | n | Service interrupts frequency, example: 0.5 - twice per clock tick |
| loadAsync | bool | true | Run the load initiation handler on a separate thread |
| | | false (default) | Run the load initiation handler from the thread specified by the `runAsync` option |
| ramOffset (deprecated)| uint16 | n (default: 0) | The offset in bytes from the start of the memory to the start of the ram |
| ramSize (deprecated) | uint16 | n (default: 0) | The size of the ram in bytes |
| romOffset (deprecated)| uint16 | n (default: 0) | The offset in bytes from the start of the memory to the start of the rom |
| romSize (deprecated) | uint16 | n (default: 0) | The size of the rom in bytes |
| ram:block:offset | uint16 | n (default: 0) | The offset in bytes from the start of the memory to the start of the ram block |
| ram:block:size | uint16 | n (default: 0) | The size of the ram block in bytes |
| rom:file:offset | uint16 | n (default: 0) | The offset in bytes from the start of the memory to the start of the rom block |
| rom:file:size | uint16 | n (default: 0) | The size of the rom block in bytes |
| runAsync | bool | true | `IMachine::Run` will launch its execution loop on a separate thread |
| | | false (default) | `IMachine::Run` will run its execution loop on the current thread |
| saveAsync | bool | true | Run the save completion handler on a separate thread |
| | | false (default) | Run the save completion handler from the thread specifed by the `runAsync` option |
There are two methods of supplying configuration options:
Expand Down
10 changes: 9 additions & 1 deletion Tests/MachineTest/source/MachineTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,15 @@ namespace MachEmu::Tests
// Set the rom/ram offsets for tst8080, note that tst8080 uses 256 bytes of stack space
// located at the end of the program so this will make up the ram size since the program
// never writes beyond this.
err = machine_->SetOptions(R"({"romOffset":0,"romSize":1727,"ramOffset":1727,"ramSize":256})");
// We will test the deprecated options romOffset, romSize, ramOffset, ramSize when we are async (remove this in 2.0.0)
if(runAsync == true)
{
err = machine_->SetOptions(R"({"romOffset":0,"romSize":1727,"ramOffset":1727,"ramSize":256})");
}
else
{
err = machine_->SetOptions(R"({"rom":{"file":[{"offset":0,"size":1727}]},"ram":{"block":[{"offset":1727,"size":256}]}})");
}
EXPECT_EQ(ErrorCode::NoError, err);
machine_->SetIoController(cpmIoController_);
machine_->OnSave([&](const char* json) { saveStates.emplace_back(json); });
Expand Down
2 changes: 1 addition & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

class MachEmuRecipe(ConanFile):
name = "mach_emu"
version = "1.6.1"
version = "1.6.2"
package_type = "library"
test_package_folder = "Tests/ConanPackageTest"

Expand Down
2 changes: 1 addition & 1 deletion docs/Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ PROJECT_NAME = "MACHine EMUlator"
# could be handy for archiving the generated documentation or if some version
# control system is used.

PROJECT_NUMBER = 1.6.1
PROJECT_NUMBER = 1.6.2

# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
Expand Down

0 comments on commit ab49603

Please sign in to comment.