User guide

Integration

  • Any simulator with extensive SystemVerilog OOP support required

  • No external dependencies

  • No defines

  • No plusargs

The package is developed and tested mostly using Verilator 5.24. Support of other simulators is planned, but Verilator still will be the main simulator for project as it is the only viable option to organize CI and opensource flow.

Sources and include directories are expressed as filelist (.f file), which is quite standard way of describing compilation unit for many EDA tools. Filelist of the project is src/filelist.f.

In order to make filelist portable, all paths are relative to SVJSON_ROOT environment variable. It should point to svjson repository root in your filesystem.

As a result, integration process consists of several simple steps:

  • Clone or copy svjson repository

  • Set environment variable SVJSON_ROOT with a path to the repository root on your filesystem

  • Add filelist to your simulator compilation options, e.g. -f ${SVJSON_ROOT}/src/filelist.f

  • json_pkg is ready to be compiled and used

JSON Values

According to the JSON specification there are 6 JSON value types.

The implementation follows the specification - these types are represented as a special wrapper classes of underlying SV types. However, there are some nuances. Summary table is below.

JSON values summary
JSON value Class Underlying SV type Note

-

json_value

-

Generic JSON value. Base class for all other values.

Object

json_object

Associative array

Class representation of JSON object.

Array

json_array

Queue

Class representation of JSON array.

Number

json_int

longint

Class representation of JSON integer number.

json_real

real

Class representation of JSON real number.

Bool

json_bool

bit

Class representation of JSON bool.

String

json_string

string

Class representation of JSON string.

json_enum

Parameterized enum

Class is inherited from JSON string. Can be used to convert custom enum type to and from string during JSON manipulations.

json_bits

Parameterized bit vector

Class is inherited from JSON string. Can be used to convert bit vector of custom width to and from string during JSON manipulations. This allows to use numbers of any width represented as strings in JSON.

Null

-

null

There is no special class to represent JSON null. Native null is used for this.

Inheritance tree for JSON values is shown below.

svg

Base Value

Class json_value is a base abstract class for all JSON values. This class is mainly used for polymorphism goals - to represent any value while decoding/encoding JSON. It has no parameters or attributes and almost all of its methods are for introspection and convenient casting to one of concrete classes.

json_value methods outline

bit compare(json_value value)

Perform compare with another instance. Return 1 if instances are equal and 0 otherwise.

json_value clone()

Create a deep copy of an instance.

  • bit is_object()

  • bit is_array()

  • bit is_string()

  • bit is_int()

  • bit is_real()

  • bit is_bool()

Check if current instance is specified type.

  • json_result#(json_object) try_into_object()

  • json_result#(json_array) try_into_array()

  • json_result#(json_string) try_into_string()

  • json_result#(json_int) try_into_int()

  • json_result#(json_real) try_into_real()

  • json_result#(json_bool) try_into_bool()

Try to cast to specified concrete class.

  • json_object into_object()

  • json_array into_array()

  • json_string into_string()

  • json_int into_int()

  • json_real into_real()

  • json_bool into_bool()

Cast to specified concrete class and throw fatal in case of failure.

  • bit matches_object(output json_object value)

  • bit matches_array(output json_array value)

  • bit matches_string(output json_string value)

  • bit matches_int(output json_int value)

  • bit matches_real(output json_real value)

  • bit matches_bool(output json_bool value)

Another option of trying to cast to specified concrete class. In this case, instance is an output argument, and returned result is 1 for success, 0 otherwise.

Object

json_object wrapper class represens standard JSON object value using SV string-indexed associative array of json_value. The class basically wraps standard SV associative array methods with some additional methods required to operate as JSON value.

No additional checks are implemented for "out-of-range" accesses and similar, so you can expect that this class will operate according to behavior of an original underlying SV associative array.

SystemVerilog associative array is used to implement JSON object. As a consequence, all keys are stored in a lexicographical order (IEEE IEEE-1800-2023, ch. 7.8.2) and original order of keys within source JSON is lost. This also affects encoder, so it always prints keys in a lexicographical order.
json_object methods outline

new(values_t values)

Create an instance from an associative array.

json_object from(values_t values)

Static method to create an instance from an associative array. Alternative to standard constructor.

json_value get(string key)

Get a value at the provided key.

void set(string key, json_value value)

Set the given value for the provided key.

values_t get_values()

Get all internal values as associative array.

keys_t get_keys()

Get all keys for internal values as queue.

void flush()

Remove all stored values.

size(), exists(), delete(), first(), last(), next(), prev()

Thin wrappers over the standard associative array methods which also mimic their signature and return values.

Array

json_array wrapper class represens standard JSON array value using SV queue of json_value. The class basically wraps standard SV queue methods with some additional methods required to operate as JSON value.

No additional checks are implemented for "out-of-range" accesses and similar, so you can expect that this class will operate according to behavior of an original underlying SV queue.

json_array methods outline

new(values_t values)

Create an instance from a queue.

json_array from(values_t values)

Static method to create an instance from a queue. Alternative to standard constructor.

json_value get(int index)

Get a value at the provided index.

void set(int index, json_value value)

Set the given value for the provided index.

values_t get_values()

Get all internal values as queue.

void flush()

Remove all stored values.

size(), insert(), delete(), push_front(), push_back(), pop_front(), pop_back()

Thin wrappers over the standard queue methods which also mimic their signature and return values.

String

json_string wrapper class represens standard JSON string value type using SV string.

\b and \u escape sequences are not supported.
json_string methods outline

new(string value)

Create an instance from a string.

json_string from(string value)

Static method to create an instance from a string. Alternative to standard constructor.

string get()

Get internal string value.

void set(string value)

Set internal string value.

Extension: Enum

json_enum#(ENUM_T) wrapper class, that inherits from json_string and represens SV enum value as standard JSON string. The class is parametrized with type ENUM_T to work with any enumeration.

Purpose of this class is to facilitate using SV enum with JSON decoder/encoder. For example, JSON values tree can be created with json_enum instances and then they can be seamlessly converted to strings during encoding. And vice versa for decoding.

json_enum#(ENUM_T) methods outline

new(ENUM_T value)

Create an instance from an enum.

json_enum#(ENUM_T) from(ENUM_T value)

Static method to create an instance from an enum. Alternative to standard constructor.

json_result#(json_enum#(ENUM_T)) try_from(string value)

Static method to create an instance from a string. Alternative to standard constructor. This option is failable, because only specific string (enumeration variants) can be used to create valid json_enum.

string get()

Get internal enum value as a string.

void set(string value)

Set internal enum value from a string. This function may fail due to wrong value is provided, and this fail is unrecoverable (fatal).

ENUM_T get_enum()

Get internal enum value.

void set_enum(ENUM_T value)

Set internal enum value.

Extension: Bit Vector

json_bits#(BITS_T) wrapper class, that inherits from json_string and represens SV bit vector value (e.g. bit[511:0]) as standard JSON string. The class is parametrized with type BITS_T to work with any bit vector - any width, signed or unsigned. Packed structures can be used as well.

Purpose of this class is to facilitate using SV bit vectors of arbitrary size with JSON decoder/encoder. As a result, any number, that cannot be represented as JSON number using longint or real, can be represented as a string.

There is an internal property preferred_radix, that can take values: json_bits::RADIX_DEC, json_bits::RADIX_BIN or json_bits::RADIX_HEX. This property can be changed any time and affects how bit vector is converted to the string - what base is used.

json_bits#(BITS_T) methods outline

new(BITS_T value, radix_e preferred_radix=RADIX_DEC)

Create an instance from a bit vector.

json_bits#(BITS_T) from(BITS_T value, radix_e preferred_radix=RADIX_DEC)

Static method to create an instance from a bit vector. Alternative to standard constructor.

json_result#(json_bits#(BITS_T)) try_from(string value)

Static method to create an instance from a string. Alternative to standard constructor. This option is failable, because only specific string can be used to create valid json_bits. It should contain only numbers "0"-"9", letters "a"-"f". Prefix "0x" is allowed for hexadecimal values and "0b" for binary ones. Radix is discovered automatically and saved into preferred_radix.

string get()

Get internal bit vector value as a string.

void set(string value)

Set internal bit vector value from a string. This function may fail due to wrong value is provided, and this fail is unrecoverable (fatal).

BITS_T get_bits()

Get internal bit vector value.

set_bits(BITS_T value)

Set internal bit vector value.

Number

JSON standard does not specify requirements for number types, but usually it is more convenient to operate with integers and real numbers separately. Hence, several classes are used to represent JSON number.

Integer Number

json_int wrapper class represens JSON integer number value using SV longint.

json_int methods outline

new(longint value)

Create an instance from a longint.

json_int from(longint value)

Static method to create an instance from a longint. Alternative to standard constructor.

longint get()

Get internal longint value.

void set(longint value)

Set internal longint value.

Real Number

json_real wrapper class represens JSON real number value using SV real.

json_real methods outline

new(real value)

Create an instance from a real.

json_real from(real value)

Static method to create an instance from a real. Alternative to standard constructor.

real get()

Get internal real value.

void set(real value)

Set internal real value.

Bool

json_bool wrapper class represens standard JSON bool value type using SV bit.

json_bool methods outline

new(bit value)

Create an instance from a bit.

json_bit from(bit value)

Static method to create an instance from a bit. Alternative to standard constructor.

bit get()

Get internal bit value.

void set(bit value)

Set internal bit value.

JSON Decoder

JSON decoder designed as an abstract class json_decoder that allows to parse either JSON string or file using corresponding static method:

  • json_decoder::load_string(string str)

  • json_decoder::load_file(string path)

For the compatibility of EDA tools only pure ASCII character set has to be used. \b and \u escape sequences are not supported.

Parsing result is returned as json_result instance, that wraps either json_error or json_value. To avoid error handling and get parsed value immediately method unwrap() can be used. However, $fatal() is thrown, when try to unwrap underlying error. This is described in details in JSON Error and Result section below.

In case of successful parsing, after json_value is extracted out from the result, it can be inspected and casted to any known JSON value class. More details in JSON Values section.

Key order of any object being parsed is not preserved due to internal implementation, see the note.

Decoder is recursive, therefore nesting depth is limited. The limit is 1024 by default and it is controllable via additional argument to any load_* method.

Below are several examples of JSON decoding.

Parse JSON string without processing of possible errors
string data =
    "{\"recipeName\":\"Vegetarian Pizza\",\"servings\":4,\"isVegan\":true}";

json_object jobject;
string recipe_name;

// Try to load string and get `json_result`, which can be either `json_error`
// or `json_value`. First unwrap() is required to get `json_value` from
// load result and avoid error handling. Second unwrap() is implicit and required
// to avoid error handling of possible unsuccessfull cast to `json_object`.
jobject = json_decoder::load_string(data).unwrap().into_object();

// Try to get a string for recipe name.
// unwrap() here is implicit to avoid error handling of possible unsuccessfull
// cast to `json_string`.
recipe_name = jobject.get("recipeName").into_string().get();

$display("Recipe name is %s", recipe_name);
Parse JSON file with processing of possible errors
// Content of pizza.json:
// {
//     "recipeName": "Vegetarian Pizza",
//     "servings": 4,
//     "isVegan": true
// }

json_error jerror;
json_value jvalue;

// Try to load file and get `json_result`,
// which can be either `json_error` or `json_value`.
json_result#(json_value) load_res = json_decoder::load_file("pizza.json");

// Use "pattern matching" to get value
case (1)
  load_res.matches_err(jerror): $fatal(jerror.to_string());

  load_res.matches_ok(jvalue): begin
    json_object jobject;
    json_result#(json_object) cast_res = jvalue.try_into_object();

    // Traditional if..else can be used as well
    if (cast_res.matches_err(jerror)) begin
      $fatal(jerror.to_string());
    end else if (cast_res.matches_ok(jobject)) begin
      $display("Keys of an object: %p", jobject.get_keys());
    end
  end
endcase

JSON Encoder

JSON encoder designed as an abstract class json_encoder. It allows to dump JSON encodable value into either string or file using corresponding static methods:

  • json_encoder::dump_string(json_value_encodable obj)

  • json_encoder::dump_file(json_value_encodable obj, string path)

There is no recursion detection for encoder.

Class json_value_encodable is a base interface class, that defines a tree of related encodable classes. Any other class can implement one of these classes to use json_encoder for dumping into JSON. Default JSON value classes implement them out of the box.

Dumping result is returned as json_result instance, that wraps either string or json_error. To avoid error handling and get parsed value immediately method unwrap() can be used. However, $fatal() is thrown, when try to unwrap underlying error. This is described in details in JSON Error and Result section below.

Keys of any object always follow lexicographical order while dumping due to internal implementation, see the note.

Below are several examples of JSON encoding. By default, the most compact representation style is used. However, indent_spaces argument can be provided to perform multiline encoding.

Dump JSON string without processing of possible errors
json_object jobject;
string data;

jobject = json_object::from(
  '{
    "recipeName": json_string::from("Meatballs"),
    "servings": json_int::from(8),
    "isVegan": json_bool::from(0)
  }
);

// Try to dump to string in the most compact way and get `json_result`,
// which can be either `json_error` or `string`.
// Here unwrap() is required to get `string` and avoid error handling.
// Displays:
//{"isVegan":false,"recipeName":"Meatballs","servings":8}
data = json_encoder::dump_string(jobject).unwrap();
$display(data);

// Try to dump using 2 spaces for indentation
// Displays:
//{
//  "isVegan": false,
//  "recipeName": "Meatballs",
//  "servings": 8
//}
data = json_encoder::dump_string(jobject, .indent_spaces(2)).unwrap();
$display(data);
Dump JSON file with processing of possible errors
json_object jobject;
json_error jerror;
json_result#(string) dump_res;
string data;

jobject = json_object::from('{"the_answer": json_int::from(42)});

// Try to dump file and get `json_result`,
// which can be either `json_error` or dumped `string`.
dump_res = json_encoder::dump_file(jobject, "answer.json");

// Use "pattern matching" to get value and handle errors
case (1)
  dump_res.matches_ok(data): begin
    //Dumped data is:
    //{"the_answer":42}
    $display("Dumped data is:\n%s", data);
  end

  dump_res.matches_err_eq(json_error::FILE_NOT_OPENED, jerror): begin
    $display("Something wrong with a file!");
  end

  dump_res.matches_err(jerror): begin
    $fatal(jerror.to_string());
  end
endcase

JSON Encodable Interfaces

The encoder designed in such way, that it accepts object of any class if it implements one of "encodable" interface classes. Inheritance tree for these classes is shown below.

svg

All classes require only single method to_json_encodable() to be implemented. It is expected that the only one of that interfaces is implemented in any other class. Default JSON value classes implement these interfaces out of the box.

Method signatures are shown in the table below.

Interface methods fot json_value_encodable derived classes
Class Method

json_object_encodable

json_object_encodable::values_t to_json_encodable()

json_array_encodable

json_object_encodable::values_t to_json_encodable()

json_string_encodable

string to_json_encodable()

json_int_encodable

longint to_json_encodable()

json_real_encodable

real to_json_encodable()

json_bool_encodable

bit to_json_encodable()

For example, there is a class some_cfg that stores some configuration values and can be a part of any inheritance tree. This class can implement json_object_encodable interface, and as a result it will become encodable into JSON object.

Make custom class encodable as JSON object
class some_config implements json_object_encodable;
  int unsigned max_addr;
  bit is_active;
  string id;

  // Single method has to be implemented for json_object_encodable interface.
  // It has to return associative array of JSON values
  virtual function json_object_encodable::values_t to_json_encodable();
    json_object_encodable::values_t values;
    values["max_addr"] = json_int::from(longint'(this.max_addr));
    values["is_active"] = json_bool::from(this.is_active);
    values["id"] = json_string::from(this.id);
    return values;
  endfunction: to_json_encodable
endclass : some_config

// Then any `config` instance can be passed to encoder
function void dump_config(some_config cfg);
  void'(json_encoder::dump_file(cfg, "config.json"));
endfunction : dump_config

JSON Error and Result

Classes json_result#(VAL_T) and json_error provide a robust way to manage success and error states during JSON manipulations. These classes are inspired by Rust’s Result enumeration, allowing for a clear and concise way to propagate and handle errors in SystemVerilog.

Result

The json_result#(VAL_T) class represents the result of an operation that can either succeed with a value (Rust’s Ok) or fail with an error (Rust’s Err). This class is parametrized with a value type (VAL_T) for successful results, while errors are hardcoded to use the json_error type. By default, VAL_T is json_value.

The error handling mechanism is designed with pattern matching in mind to enable handling different outcomes in a structured manner. However, SystemVerilog provides true pattern matching only for tagged unions, which are still quite exotic, so pattern matching emulation with "reverse case" is suggested:

Pattern matching using "reverse case"
json_result#(json_value) result = json_decoder::load_file("foo.json");
json_value value;
json_error error;

case (1)
  result.matches_err(error): begin
    // Handle error
  end

  result.matches_ok(value): begin
    // Use value
  end
endcase

Traditional if..else can be also used

Pattern matching using if..else
json_result#(json_value) result = json_decoder::load_file("foo.json");
json_value value;
json_error error;

if (result.matches_err(error)) begin
  // Handle error
end else if (result.matches_ok(value)) begin
  // Use value
end

Pattern matching can be also more strict:

Stricter pattern matching with specific errors
json_result#(string) result = json_encoder::dump_file(obj, path);
string value;
json_error error;

case (1)
  result.matches_err_eq(json_error::TYPE_CONVERSION, error): begin
    // Handle "type conversion" error
  end

  result.matches_err_eq(json_error::FILE_NOT_OPENED, error): begin
    // Handle "file not opened" error
  end

  result.matches_ok(value): begin
    // Use value if needed
  end
endcase

The class provides methods to check whether the result is successful is_ok() or not is_err() without pattern matching. Also, there is a way to skip graceful error handling and get a value immediately using unwrap() method. However, this may lead to fatal error and stoping simulation in case of error being unwrapped.

json_result methods outline

json_result#(VAL_T) ok(VAL_T value)

Static method to create an OK result.

json_result#(VAL_T) err(json_error error)

Static method to create an error result.

bit is_ok()

Checks if the result is an OK.

bit is_err()

Checks if the result is an error.

bit matches_ok(output VAL_T value)

Matches the result with an OK value and retrieves the value if successful. Return 1 on successful match, 0 otherwise.

bit matches_err(output json_error error)

Matches the result with any error and retrieves the error if successful. Return 1 on successful match, 0 otherwise.

bit matches_err_eq(input json_error::kind_e kind, output json_error error)

Matches the result with a specific error kind and retrieves the error if successful. Return 1 on successful match, 0 otherwise.

Error

The json_error class encapsulates various types of errors that can occur during JSON operations. Each error is characterized by an error kind (json_error::kind_e) and additional context such as a description, file, line number, and the JSON string that caused the error.

The json_error::kind_e enumeration defines the following error types:

json_error types

Error Type

Description

EOF_VALUE

EOF while parsing some JSON value

EOF_OBJECT

EOF while parsing an object

EOF_ARRAY

EOF while parsing an array

EOF_STRING

EOF while parsing a string

EOF_LITERAL

EOF while parsing a literal

EXPECTED_TOKEN

Current character should be some expected token

EXPECTED_COLON

Current character should be ':'

EXPECTED_OBJECT_COMMA_OR_END

Current character should be either ',' or '}'

EXPECTED_ARRAY_COMMA_OR_END

Current character should be either ',' or ']'

EXPECTED_DOUBLE_QUOTE

Current character should be '\"'

EXPECTED_VALUE

Current character should start some JSON value

INVALID_ESCAPE

Invaid escape code

INVALID_CHAR

Unexpected control character

INVALID_LITERAL

Invaid literal that should be 'true', 'false', or 'null'

INVALID_NUMBER

Invaid number

INVALID_OBJECT_KEY

String must be used as a key

TRAILING_COMMA

Unexpected comma after the last value

TRAILING_CHARS

Unexpected characters after the JSON value

DEEP_NESTING

This JSON value exceeds nesing limit for a decoder

TYPE_CONVERSION

Type conversion failed

FILE_NOT_OPENED

File opening failed

NOT_IMPLEMENTED

Feature is not implemented

INTERNAL

Unspecified internal error

The class have a few public methods, which can facilitate error handling and debugging.

json_error methods outline
json_error create(
  kind_e kind,
  string description="",
  string json_str="",
  int json_pos=-1,
  string source_file="",
  int source_line=-1
);

Creates a new error with the specified kind and context.

void throw_error()

Logs the error.

void throw_fatal()

Logs the error and finishes simulation.

string to_string()

Converts the error to a human readable string.