///|
/// A normalized VCD time scale.
pub struct TimeScale {
magnitude : Int
unit : String
femtoseconds_per_tick : Int64
source : String
} derive(Debug, Eq)
///|
/// Parse a VCD timescale such as `1ns`, `10 ps`, or `100fs`.
pub fn parse_timescale(text : String) -> Result[TimeScale, ParseError] {
let parts = text
.trim()
.to_owned()
.split(" ")
.filter(fn(item) { item != "" })
.map(fn(item) { item.to_owned() })
.to_array()
let compact = parts.join("")
let split = split_timescale_number(compact)
match split {
Some((number, unit)) => {
let magnitude = parse_positive_decimal(number)
let factor = unit_femtoseconds(unit)
match (magnitude, factor) {
(Some(amount), Some(scale)) if amount > 0 =>
Ok({
magnitude: amount,
unit,
femtoseconds_per_tick: amount.to_int64() * scale,
source: text,
})
_ => Err(InvalidDirective("invalid timescale: " + text))
}
}
None => Err(InvalidDirective("invalid timescale: " + text))
}
}
///|
/// Parse the header timescale, falling back to one nanosecond for absent data.
pub fn Trace::time_scale(self : Trace) -> TimeScale {
match self.header.timescale {
Some(text) =>
match parse_timescale(text) {
Ok(scale) => scale
Err(_) => default_timescale()
}
None => default_timescale()
}
}
///|
/// Return the physical duration of a tick as femtoseconds.
pub fn TimeScale::tick_femtoseconds(self : TimeScale) -> Int64 {
self.femtoseconds_per_tick
}
///|
/// Convert a VCD timestamp to femtoseconds.
pub fn TimeScale::to_femtoseconds(self : TimeScale, ticks : Int64) -> Int64 {
ticks * self.femtoseconds_per_tick
}
///|
/// Convert a femtosecond duration to the closest whole VCD tick.
pub fn TimeScale::from_femtoseconds(
self : TimeScale,
duration : Int64,
) -> Int64 {
if self.femtoseconds_per_tick <= 0L {
0L
} else {
duration / self.femtoseconds_per_tick
}
}
///|
/// Return the original spelling used for this scale.
pub fn TimeScale::source_text(self : TimeScale) -> String {
self.source
}
///|
/// Return a canonical compact spelling.
pub fn TimeScale::display(self : TimeScale) -> String {
self.magnitude.to_string() + self.unit
}
///|
/// Convert a trace timestamp into seconds.
pub fn timestamp_seconds(trace : Trace, ticks : Int64) -> Double {
trace.time_scale().to_femtoseconds(ticks).to_double() /
1_000_000_000_000_000.0
}
///|
/// Return the physical duration between two timestamps.
pub fn duration_seconds(trace : Trace, start : Int64, end : Int64) -> Double {
timestamp_seconds(trace, Int64::max(start, end) - start)
}
///|
fn default_timescale() -> TimeScale {
{ magnitude: 1, unit: "ns", femtoseconds_per_tick: 1_000_000L, source: "1ns" }
}
///|
fn split_timescale_number(text : String) -> (String, String)? {
if text == "" {
return None
}
let mut index = 0
for char in text {
if char >= '0' && char <= '9' {
index += 1
} else {
break
}
}
if index == 0 || index == text.length() {
None
} else {
Some(
(
text.unsafe_substring(start=0, end=index),
text.unsafe_substring(start=index, end=text.length()).to_lower(),
),
)
}
}
///|
fn parse_positive_decimal(text : String) -> Int? {
if text == "" {
return None
}
let mut result = 0
for char in text {
if char < '0' || char > '9' {
return None
}
result = result * 10 + char.to_int() - 48
}
Some(result)
}
///|
fn unit_femtoseconds(unit : String) -> Int64? {
match unit {
"s" => Some(1_000_000_000_000_000L)
"ms" => Some(1_000_000_000_000L)
"us" => Some(1_000_000_000L)
"ns" => Some(1_000_000L)
"ps" => Some(1_000L)
"fs" => Some(1L)
_ => None
}
}
///|
/// Convert a timestamp range to seconds while preserving order.
pub fn range_seconds(trace : Trace, range : (Int64, Int64)) -> (Double, Double) {
(timestamp_seconds(trace, range.0), timestamp_seconds(trace, range.1))
}
///|
/// Return a scale-aware duration report.
pub fn duration_report(trace : Trace, start : Int64, end : Int64) -> String {
let ticks = Int64::max(start, end) - Int64::min(start, end)
let scale = trace.time_scale()
"duration=" +
ticks.to_string() +
" ticks (" +
scale.to_femtoseconds(ticks).to_string() +
" fs)"
}