///|
fn md_bool(value : Bool) -> String {
if value {
"yes"
} else {
"no"
}
}
///|
fn md_optional_time(value : Int64?) -> String {
match value {
Some(time) => time.to_string()
None => "-"
}
}
///|
fn md_optional_value(value : String?) -> String {
match value {
Some(value) => value
None => "-"
}
}
///|
fn markdown_escape_cell(text : String) -> String {
let builder = StringBuilder()
for ch in text {
match ch {
'|' => builder.write_string("\\|")
'\n' | '\r' | '\t' => builder.write_char(' ')
_ => builder.write_char(ch)
}
}
builder.to_string()
}
///|
fn write_markdown_row(builder : StringBuilder, cells : Array[String]) -> Unit {
builder.write_char('|')
for cell in cells {
builder.write_char(' ')
builder.write_string(markdown_escape_cell(cell))
builder.write_char(' ')
builder.write_char('|')
}
builder.write_char('\n')
}
///|
fn write_markdown_separator(builder : StringBuilder, columns : Int) -> Unit {
builder.write_char('|')
for i = 0; i < columns; i = i + 1 {
builder.write_string(" --- |")
}
builder.write_char('\n')
}
///|
fn write_heading(builder : StringBuilder, level : Int, title : String) -> Unit {
for i = 0; i < level; i = i + 1 {
builder.write_char('#')
}
builder.write_char(' ')
builder.write_string(title)
builder.write_string("\n\n")
}
///|
fn write_key_value(
builder : StringBuilder,
key : String,
value : String,
) -> Unit {
builder.write_string("- ")
builder.write_string(key)
builder.write_string(": ")
builder.write_string(value)
builder.write_char('\n')
}
///|
fn signal_markdown_row(signal : Signal) -> Array[String] {
[
signal.full_name,
signal.signal_type,
signal.width.to_string(),
signal.identifier,
]
}
///|
fn coverage_markdown_row(coverage : SignalCoverage) -> Array[String] {
[
coverage.full_name,
md_bool(coverage.has_value),
coverage.change_count.to_string(),
coverage.transition_count.to_string(),
coverage.unknown_change_count.to_string(),
coverage.high_impedance_change_count.to_string(),
md_optional_time(coverage.first_change_time),
md_optional_time(coverage.last_change_time),
]
}
///|
fn issue_markdown_row(issue : VcdIssue) -> Array[String] {
[
issue.code,
md_optional_time(issue.timestamp),
md_optional_value(issue.signal),
issue.message,
]
}
///|
fn change_markdown_row(vcd : VcdFile, change : SignalChange) -> Array[String] {
[
change.timestamp.to_string(),
signal_name_for_identifier(vcd, change.identifier),
change.value,
]
}
///|
fn value_count_markdown_row(item : ValueCount) -> Array[String] {
[item.value, item.count.to_string()]
}
///|
/// Render a Markdown summary section for the parsed file.
pub fn markdown_summary(vcd : VcdFile) -> String {
let sum = summary(vcd)
let h = health(vcd)
let builder = StringBuilder()
write_heading(builder, 2, "VCD Summary")
write_key_value(
builder,
"Timescale",
if vcd.timescale.is_empty() {
"-"
} else {
vcd.timescale
},
)
write_key_value(builder, "Signals", sum.signal_count.to_string())
write_key_value(builder, "Scalar signals", h.scalar_signal_count.to_string())
write_key_value(builder, "Vector signals", h.vector_signal_count.to_string())
write_key_value(builder, "Changes", sum.change_count.to_string())
write_key_value(builder, "Start time", md_optional_time(sum.start_time))
write_key_value(builder, "End time", md_optional_time(sum.end_time))
write_key_value(builder, "Validation issues", h.issue_count.to_string())
builder.to_string()
}
///|
/// Render all signal declarations as a Markdown table.
pub fn markdown_signals(vcd : VcdFile) -> String {
let builder = StringBuilder()
write_heading(builder, 2, "Signals")
write_markdown_row(builder, ["Signal", "Type", "Width", "Identifier"])
write_markdown_separator(builder, 4)
for signal in vcd.signals {
write_markdown_row(builder, signal_markdown_row(signal))
}
builder.to_string()
}
///|
/// Render one signal's changes as a Markdown table.
pub fn markdown_signal_changes(
vcd : VcdFile,
full_name : String,
) -> String raise VcdError {
let builder = StringBuilder()
write_heading(builder, 2, "Changes: \{full_name}")
write_markdown_row(builder, ["Timestamp", "Value"])
write_markdown_separator(builder, 2)
for change in signal_changes(vcd, full_name) {
write_markdown_row(builder, [change.timestamp.to_string(), change.value])
}
builder.to_string()
}
///|
/// Render all parsed changes as a Markdown table.
pub fn markdown_changes(vcd : VcdFile) -> String {
let builder = StringBuilder()
write_heading(builder, 2, "Changes")
write_markdown_row(builder, ["Timestamp", "Signal", "Value"])
write_markdown_separator(builder, 3)
for change in vcd.changes {
write_markdown_row(builder, change_markdown_row(vcd, change))
}
builder.to_string()
}
///|
/// Render basic statistics for one signal as Markdown.
pub fn markdown_signal_stats(
vcd : VcdFile,
full_name : String,
) -> String raise VcdError {
let signal = match find_signal(vcd, full_name) {
Some(signal) => signal
None => raise VcdError("signal not found: \{full_name}")
}
let stats = signal_stats(vcd, full_name)
let builder = StringBuilder()
write_heading(builder, 2, "Signal Statistics")
write_key_value(builder, "Signal", signal.full_name)
write_key_value(builder, "Type", signal.signal_type)
write_key_value(builder, "Width", signal.width.to_string())
write_key_value(builder, "Changes", stats.change_count.to_string())
write_key_value(
builder,
"First change",
md_optional_time(stats.first_change_time),
)
write_key_value(
builder,
"Last change",
md_optional_time(stats.last_change_time),
)
if signal.width == 1 {
write_key_value(builder, "Rising edges", stats.rising_edges.to_string())
write_key_value(builder, "Falling edges", stats.falling_edges.to_string())
}
builder.to_string()
}
///|
/// Render one signal's value-count table as Markdown.
pub fn markdown_value_counts(
vcd : VcdFile,
full_name : String,
) -> String raise VcdError {
let builder = StringBuilder()
write_heading(builder, 2, "Value Counts: \{full_name}")
write_markdown_row(builder, ["Value", "Count"])
write_markdown_separator(builder, 2)
for item in value_counts(vcd, full_name) {
write_markdown_row(builder, value_count_markdown_row(item))
}
builder.to_string()
}
///|
/// Render coverage summaries for all signals as Markdown.
pub fn markdown_coverage(vcd : VcdFile) -> String raise VcdError {
let builder = StringBuilder()
write_heading(builder, 2, "Signal Coverage")
write_markdown_row(builder, [
"Signal", "Has value", "Changes", "Transitions", "X changes", "Z changes", "First",
"Last",
])
write_markdown_separator(builder, 8)
for coverage in all_signal_coverage(vcd) {
write_markdown_row(builder, coverage_markdown_row(coverage))
}
builder.to_string()
}
///|
/// Render validation issues as Markdown.
pub fn markdown_issues(vcd : VcdFile) -> String {
let builder = StringBuilder()
write_heading(builder, 2, "Validation")
let issues = validate_vcd(vcd)
if issues.is_empty() {
builder.write_string("No validation issues found.\n")
} else {
write_markdown_row(builder, ["Code", "Timestamp", "Signal", "Message"])
write_markdown_separator(builder, 4)
for item in issues {
write_markdown_row(builder, issue_markdown_row(item))
}
}
builder.to_string()
}
///|
/// Render a snapshot as a Markdown table.
pub fn markdown_snapshot(snapshot : VcdSnapshot) -> String {
let builder = StringBuilder()
write_heading(builder, 2, "Snapshot @ \{snapshot.timestamp}")
write_markdown_row(builder, ["Signal", "Value"])
write_markdown_separator(builder, 2)
for item in snapshot.values {
write_markdown_row(builder, [
item.signal.full_name,
md_optional_value(item.value),
])
}
builder.to_string()
}
///|
/// Render a timestamp-bucket overview as Markdown.
pub fn markdown_buckets(vcd : VcdFile) -> String {
let builder = StringBuilder()
write_heading(builder, 2, "Timestamp Buckets")
write_markdown_row(builder, ["Timestamp", "Changes"])
write_markdown_separator(builder, 2)
for bucket in timestamp_buckets(vcd) {
write_markdown_row(builder, [
bucket.timestamp.to_string(),
bucket.changes.length().to_string(),
])
}
builder.to_string()
}
///|
/// Render a compact Markdown report covering the most useful library outputs.
pub fn markdown_report(vcd : VcdFile) -> String raise VcdError {
let builder = StringBuilder()
write_heading(builder, 1, "MiniVCD-MBT Report")
builder.write_string(markdown_summary(vcd))
builder.write_char('\n')
builder.write_string(markdown_signals(vcd))
builder.write_char('\n')
builder.write_string(markdown_coverage(vcd))
builder.write_char('\n')
builder.write_string(markdown_issues(vcd))
builder.to_string()
}
///|
/// Render a plain one-line manifest useful for logs.
pub fn manifest_line(vcd : VcdFile) -> String {
let sum = summary(vcd)
"timescale=\{if vcd.timescale.is_empty() { "-" } else { vcd.timescale }} signals=\{sum.signal_count} changes=\{sum.change_count} start=\{md_optional_time(sum.start_time)} end=\{md_optional_time(sum.end_time)}"
}
///|
/// Render signal names as a newline-delimited manifest.
pub fn signal_manifest(vcd : VcdFile) -> String {
let builder = StringBuilder()
for signal in vcd.signals {
builder.write_string(signal.full_name)
builder.write_char('\n')
}
builder.to_string()
}
///|
/// Render a lightweight report for one signal.
pub fn single_signal_report(
vcd : VcdFile,
full_name : String,
) -> String raise VcdError {
let builder = StringBuilder()
builder.write_string(markdown_signal_stats(vcd, full_name))
builder.write_char('\n')
builder.write_string(markdown_value_counts(vcd, full_name))
builder.write_char('\n')
builder.write_string(markdown_signal_changes(vcd, full_name))
builder.to_string()
}