Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ use a custom configuration, save the default configuration into a
file, edit it as needed, and pass it to the simulator using the
`--config` option.

To override only a small subset of options while using the default configuration
or a custom configuration file as a base, the `--config-override` option can be
used. This option allows one or more additional JSON configuration files to be specified,
whose fields take precedence over those in the base configuration.

Information on other options for the simulator is available from
`build/c_emulator/sail_riscv_sim -h`.

Expand Down
2 changes: 2 additions & 0 deletions c_emulator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ add_library(riscv_model
riscv_softfloat.h
config_utils.cpp
config_utils.h
file_utils.cpp
file_utils.h
symbol_table.cpp
symbol_table.h
sail_riscv_version.h
Expand Down
30 changes: 8 additions & 22 deletions c_emulator/config_utils.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#include "config_utils.h"

#include <fstream>
#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonschema/jsonschema.hpp>
#include <sstream>
#include <stdexcept>

#include "config_schema.h"
#include "default_config.h"
#include "file_utils.h"
#include "sail.h"
#include "sail_config.h"
#include "sail_riscv_model.h"
Expand Down Expand Up @@ -71,7 +72,7 @@ const char *get_config_schema() {
return CONFIG_SCHEMA;
}

void validate_config_schema(const std::string &conf_file) {
void validate_config_schema(const jsoncons::json &json_config, const std::string &source_desc) {
using jsoncons::json;
namespace jsonschema = jsoncons::jsonschema;

Expand All @@ -81,30 +82,15 @@ void validate_config_schema(const std::string &conf_file) {
// Throws schema_error if compilation fails.
jsonschema::json_schema<json> compiled = jsonschema::make_json_schema(std::move(schema), options);

std::string source = conf_file.empty() ? "default configuration" : "configuration in " + conf_file;

// Parse the config.
json config;
try {
if (conf_file.empty()) {
config = json::parse(get_default_config());
} else {
std::ifstream is(conf_file);
config = json::parse(is);
}
} catch (const std::exception &ex) {
throw std::runtime_error("Cannot parse " + source + ": " + ex.what() + ".");
}

// Validate.
bool is_valid = true;
auto report = [&is_valid](const jsonschema::validation_message &msg) -> jsonschema::walk_result {
std::ostringstream error_stream;
auto report = [&is_valid, &error_stream](const jsonschema::validation_message &msg) -> jsonschema::walk_result {
is_valid = false;
std::cerr << msg.instance_location().string() << ": " << msg.message() << std::endl;
error_stream << "- " << msg.instance_location().string() << ": " << msg.message() << "\n";
return jsonschema::walk_result::advance;
};
compiled.validate(config, report);
compiled.validate(json_config, report);
if (!is_valid) {
throw std::runtime_error("Schema conformance check failed for the " + source + ".");
throw std::runtime_error("Schema conformance check failed for " + source_desc + ":\n" + error_stream.str());
}
}
3 changes: 2 additions & 1 deletion c_emulator/config_utils.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <cstdint>
#include <jsoncons/json.hpp>
#include <string>
#include <vector>

Expand All @@ -13,4 +14,4 @@ uint64_t get_config_uint64(const std::vector<const char *> &keypath);
const char *get_default_config();
const char *get_config_schema();

void validate_config_schema(const std::string &conf_file);
void validate_config_schema(const jsoncons::json &json_config, const std::string &source_desc);
26 changes: 26 additions & 0 deletions c_emulator/file_utils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include "file_utils.h"
#include <cstdint>
#include <fstream>
#include <sstream>
#include <stdexcept>

std::string read_file_to_string(const std::string &file_path) {
std::ifstream instream(file_path);
if (!instream) {
throw std::runtime_error("Failed to open file: " + file_path);
}

std::ostringstream ss;
ss << instream.rdbuf();
return ss.str();
}

std::vector<uint8_t> read_file(const std::string &file_path) {
std::ifstream instream(file_path, std::ios::in | std::ios::binary);
if (!instream) {
throw std::runtime_error("Failed to open file: " + file_path);
}

std::vector<uint8_t> data{std::istreambuf_iterator<char>(instream), std::istreambuf_iterator<char>()};
return data;
}
6 changes: 6 additions & 0 deletions c_emulator/file_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <cstdint>
#include <string>
#include <vector>

std::string read_file_to_string(const std::string &file_path);
std::vector<uint8_t> read_file(const std::string &file_path);
75 changes: 66 additions & 9 deletions c_emulator/riscv_sim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "CLI11.hpp"
#include "elf_loader.h"
#include "jsoncons/config/version.hpp"
#include "jsoncons/json.hpp"
#include "rts.h"
#include "sail.h"
#include "sail_config.h"
Expand All @@ -29,6 +30,7 @@
#include "sail_coverage.h"
#endif
#include "config_utils.h"
#include "file_utils.h"
#include "riscv_callbacks_log.h"
#include "riscv_callbacks_rvfi.h"
#include "riscv_model_impl.h"
Expand Down Expand Up @@ -87,9 +89,29 @@ static void print_build_info() {
std::cout << "JSONCONS: " << jsoncons::version() << std::endl;
}

std::vector<uint8_t> read_file(const std::string &file_path) {
std::ifstream instream(file_path, std::ios::in | std::ios::binary);
return {std::istreambuf_iterator<char>(instream), std::istreambuf_iterator<char>()};
static jsoncons::json parse_json_or_exit(const std::string &json_text, const std::string &source_desc) {
try {
return jsoncons::json::parse(json_text);
} catch (const jsoncons::json_exception &e) {
std::cerr << "JSON parse error in " << source_desc << ":\n" << e.what() << "\n\n";
exit(EXIT_FAILURE);
}
}

// JSON objects are merged by replacing/adding fields from the override config.
// If a given field is an object in the base and override then instead of
// replacing the field entirely the merging process recurses into it.
// All other field types (including arrays) are simply replaced.
void deep_merge_json(jsoncons::json &base, const jsoncons::json &json_override) {
for (const auto &entry : json_override.object_range()) {
const auto &key = entry.key();
const auto &value = entry.value();
if (base.contains(key) && base[key].is_object() && value.is_object()) {
deep_merge_json(base[key], value);
} else {
base[key] = value;
}
}
}

const unsigned DEFAULT_SIGNATURE_GRANULARITY = 4;
Expand All @@ -105,6 +127,7 @@ struct CLIOptions {
bool do_print_isa = false;

std::string config_file;
std::vector<std::string> config_overrides;
std::string term_log;
std::string trace_log_path;
std::string dtb_file;
Expand Down Expand Up @@ -169,6 +192,16 @@ static CLIOptions parse_cli(int argc, char **argv) {
app.add_option("--terminal-log", opts.term_log, "Terminal log output file")->option_text("<file>");
app.add_option("--test-signature", opts.sig_file, "Test signature file")->option_text("<file>");
app.add_option("--config", opts.config_file, "Configuration file")->check(CLI::ExistingFile)->option_text("<file>");
app
.add_option(
"--config-override",
opts.config_overrides,
"Configuration override file (repeatable; later files override earlier ones). Use this when you only want to "
"change a small part of the base configuration."
)
->check(CLI::ExistingFile)
->option_text("<file>")
->allow_extra_args(false);
app.add_option("--trace-output", opts.trace_log_path, "Trace output file")->option_text("<file>");

app.add_option("--signature-granularity", opts.signature_granularity, "Signature granularity")->option_text("<uint>");
Expand Down Expand Up @@ -598,15 +631,39 @@ int inner_main(int argc, char **argv) {

model.set_config_print_step(opts.config_print_step);

// Always validate the schema conformance of the config.
validate_config_schema(opts.config_file);

// Initialize the model.
std::string config_json_string;
if (!opts.config_file.empty()) {
sail_config_set_file(opts.config_file.c_str());
config_json_string = read_file_to_string(opts.config_file);
} else {
sail_config_set_string(get_default_config());
config_json_string = get_default_config();
}

// Check json config and merge overrides
const std::string base_source_desc =
opts.config_file.empty() ? "default configuration" : "configuration file " + opts.config_file;
jsoncons::json config_json = parse_json_or_exit(config_json_string, base_source_desc);
for (const auto &override_path : opts.config_overrides) {
std::string override_json_string = read_file_to_string(override_path);
jsoncons::json override_item = parse_json_or_exit(override_json_string, "override file " + override_path);
deep_merge_json(config_json, override_item);
}

std::ostringstream os;
os << config_json;
config_json_string = os.str();

// Always validate the schema conformance of the config.
std::string config_source_desc = opts.config_file.empty() ? "default configuration" : opts.config_file;
if (!opts.config_overrides.empty()) {
config_source_desc = "merged configuration from " + config_source_desc;
for (const auto &override_path : opts.config_overrides) {
config_source_desc = config_source_desc + ", " + override_path;
}
}
validate_config_schema(config_json, config_source_desc);

// Initialize the model.
sail_config_set_string(config_json_string.c_str());

// Initialize platform.
init_platform_constants(model);
Expand Down
3 changes: 3 additions & 0 deletions doc/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

- A configuration parameter to specify the size of the reservation set
for Zalrsc atomics has been added.
- A `--config-override` cli option has been added to specify one or more
additional JSON configuration files that override the corresponding fields
in a configuration.

- The following extensions have been added:
- Za64rs, Za128rs
Expand Down
Loading
Loading