///|
/// Escape a string for the compact JSON helpers in this package.
pub fn escape_text(text : String) -> String {
text
.replace_all(old="\\", new="\\\\")
.replace_all(old="\"", new="\\\"")
.replace_all(old="\n", new="\\n")
.replace_all(old="\r", new="\\r")
}
///|
/// Encode one value as a stable JSON object fragment.
pub fn value_json(value : Value) -> String {
match value {
Scalar(ch) =>
"{\"kind\":\"scalar\",\"value\":\"" + escape_text(ch.to_string()) + "\"}"
Vector(bits) =>
"{\"kind\":\"vector\",\"value\":\"" + escape_text(bits) + "\"}"
Real(number) => "{\"kind\":\"real\",\"value\":" + number.to_string() + "}"
}
}
///|
/// Encode one change as a compact JSON object.
pub fn change_json(change : Change) -> String {
"{\"time\":" +
change.time.to_string() +
",\"id\":\"" +
escape_text(change.id) +
"\",\"value\":" +
value_json(change.value) +
"}"
}
///|
/// Encode signal metadata as a compact JSON object.
pub fn signal_json(signal : Signal) -> String {
"{\"id\":\"" +
escape_text(signal.id) +
"\",\"name\":\"" +
escape_text(signal.name) +
"\",\"width\":" +
signal.width.to_string() +
",\"kind\":\"" +
escape_text(signal.kind) +
"\",\"path\":\"" +
escape_text(signal_path(signal)) +
"\"}"
}
///|
/// Export the complete trace as deterministic JSON.
pub fn trace_json(trace : Trace) -> String {
let signals = trace.signals.map(signal_json).join(",")
let changes = trace.changes.map(change_json).join(",")
"{\"end_time\":" +
trace.end_time.to_string() +
",\"signals\":[" +
signals +
"],\"changes\":[" +
changes +
"]}"
}
///|
/// Export signal changes as CSV for spreadsheet and plotting tools.
pub fn changes_csv(trace : Trace, signal_id? : String) -> String {
let out = StringBuilder()
out.write_string("time,id,value\n")
for change in trace.changes {
match signal_id {
Some(id) if id != change.id => continue
_ => ()
}
out.write_string(change.time.to_string())
out.write_char(',')
out.write_string(escape_text(change.id))
out.write_char(',')
out.write_string(escape_text(change.value.display()))
out.write_char('\n')
}
out.to_string()
}
///|
/// Render a trace back into a portable VCD document.
pub fn trace_to_vcd(trace : Trace) -> String {
let out = StringBuilder()
write_header(out, trace)
for signal in trace.signals {
write_signal_scope(out, signal)
}
out.write_string("$enddefinitions $end\n")
let mut current_time : Int64? = None
for change in trace.changes {
if current_time != Some(change.time) {
out.write_char('#')
out.write_string(change.time.to_string())
out.write_char('\n')
current_time = Some(change.time)
}
out.write_string(vcd_assignment(change))
out.write_char('\n')
}
out.to_string()
}
///|
/// Write one declaration under its original scope path.
fn write_signal_scope(out : StringBuilder, signal : Signal) -> Unit {
for scope in signal.scope {
out.write_string("$scope module " + scope + " $end\n")
}
if signal.scope.length() == 0 {
out.write_string("$scope module top $end\n")
}
out.write_string("$var ")
out.write_string(signal.kind)
out.write_char(' ')
out.write_string(signal.width.to_string())
out.write_char(' ')
out.write_string(signal.id)
out.write_char(' ')
out.write_string(signal.name)
out.write_string(" $end\n")
let mut index = signal.scope.length()
while index > 0 {
out.write_string("$upscope $end\n")
index -= 1
}
if signal.scope.length() == 0 {
out.write_string("$upscope $end\n")
}
}
///|
fn write_header(out : StringBuilder, trace : Trace) -> Unit {
match trace.header.date {
Some(value) => out.write_string("$date " + value + " $end\n")
None => ()
}
match trace.header.version {
Some(value) => out.write_string("$version " + value + " $end\n")
None => ()
}
match trace.header.timescale {
Some(value) => out.write_string("$timescale " + value + " $end\n")
None => ()
}
match trace.header.comment {
Some(value) => out.write_string("$comment " + value + " $end\n")
None => ()
}
}
///|
fn vcd_assignment(change : Change) -> String {
match change.value {
Scalar(value) => value.to_string() + change.id
Vector(bits) => "b" + bits + " " + change.id
Real(number) => "r" + number.to_string() + " " + change.id
}
}