///|
/// APB signal mapping for transaction extraction.
pub struct ApbConfig {
pclk : String
psel : String
penable : String
pwrite : String
paddr : String
pwdata : String?
prdata : String?
pready : String?
pslverr : String?
} derive(Debug, Eq)
///|
/// One decoded APB transfer.
pub struct ApbTransfer {
index : Int
setup_time : Int64
access_time : Int64
complete_time : Int64
address : Int64?
write : Bool
write_data : Int64?
read_data : Int64?
ready : Bool
error : Bool
wait_cycles : Int
} derive(Debug, Eq)
///|
/// APB decode summary.
pub struct ApbReport {
transfers : Int
completed : Int
reads : Int
writes : Int
errors : Int
maximum_wait : Int
mean_wait : Double?
addresses : Int
} derive(Debug, Eq)
///|
/// Construct an APB4 mapping.
pub fn apb_config(
pclk : String,
psel : String,
penable : String,
pwrite : String,
paddr : String,
) -> ApbConfig {
{
pclk,
psel,
penable,
pwrite,
paddr,
pwdata: None,
prdata: None,
pready: None,
pslverr: None,
}
}
///|
/// Add data and response signals to an APB mapping.
pub fn ApbConfig::with_data(
self : ApbConfig,
pwdata : String,
prdata : String,
) -> ApbConfig {
{ ..self, pwdata: Some(pwdata), prdata: Some(prdata) }
}
///|
/// Add optional APB response signals.
pub fn ApbConfig::with_response(
self : ApbConfig,
pready : String,
pslverr : String,
) -> ApbConfig {
{ ..self, pready: Some(pready), pslverr: Some(pslverr) }
}
///|
/// Decode APB transfers by observing setup, access, and completion phases.
pub fn apb_decode(trace : Trace, config : ApbConfig) -> Array[ApbTransfer] {
let clocks = clock_edge_times(trace, clock_config(config.pclk))
let transfers : Array[ApbTransfer] = []
let mut active : ApbTransfer? = None
for time in clocks {
let selected = signal_is_one(trace, config.psel, time)
let enabled = signal_is_one(trace, config.penable, time)
let ready = match config.pready {
Some(id) => signal_is_one(trace, id, time)
None => true
}
let error = match config.pslverr {
Some(id) => signal_is_one(trace, id, time)
None => false
}
if selected && !enabled && active is None {
active = Some({
index: transfers.length(),
setup_time: time,
access_time: time,
complete_time: time,
address: optional_value_int(value_at(trace, config.paddr, time)),
write: signal_is_one(trace, config.pwrite, time),
write_data: optional_int_signal(trace, config.pwdata, time),
read_data: None,
ready: false,
error: false,
wait_cycles: 0,
})
} else if selected && enabled {
match active {
Some(item) => {
let updated = if ready {
{
..item,
access_time: if item.access_time == item.setup_time {
time
} else {
item.access_time
},
complete_time: time,
ready: true,
error,
read_data: optional_int_signal(trace, config.prdata, time),
}
} else {
{
..item,
access_time: if item.access_time == item.setup_time {
time
} else {
item.access_time
},
wait_cycles: item.wait_cycles + 1,
}
}
active = Some(updated)
if ready {
transfers.push(updated)
active = None
}
}
None => ()
}
} else if !selected {
match active {
Some(item) => {
transfers.push({ ..item, complete_time: time })
active = None
}
None => ()
}
}
}
match active {
Some(item) => transfers.push(item)
None => ()
}
transfers
}
///|
/// Calculate APB statistics.
pub fn apb_report(trace : Trace, config : ApbConfig) -> ApbReport {
let transfers = apb_decode(trace, config)
let waits = transfers.map(fn(item) { item.wait_cycles })
let addresses : Array[Int64] = []
for item in transfers {
match item.address {
Some(address) =>
if !addresses.contains(address) {
addresses.push(address)
}
None => ()
}
}
let completed = transfers.count_if(fn(item) { item.ready })
{
transfers: transfers.length(),
completed,
reads: transfers.count_if(fn(item) { !item.write }),
writes: transfers.count_if(fn(item) { item.write }),
errors: transfers.count_if(fn(item) { item.error }),
maximum_wait: waits.fold(init=0, fn(value, item) { Int::max(value, item) }),
mean_wait: if waits.length() == 0 {
None
} else {
Some(
waits.fold(init=0, fn(total, item) { total + item }).to_double() /
waits.length().to_double(),
)
},
addresses: addresses.length(),
}
}
///|
/// Render APB transfers as CSV.
pub fn apb_transfers_csv(transfers : Array[ApbTransfer]) -> String {
let rows : Array[String] = [
"index,setup,access,complete,address,write,write_data,read_data,ready,error,wait_cycles",
]
for item in transfers {
rows.push(
item.index.to_string() +
"," +
item.setup_time.to_string() +
"," +
item.access_time.to_string() +
"," +
item.complete_time.to_string() +
"," +
optional_int64_text(item.address) +
"," +
item.write.to_string() +
"," +
optional_int64_text(item.write_data) +
"," +
optional_int64_text(item.read_data) +
"," +
item.ready.to_string() +
"," +
item.error.to_string() +
"," +
item.wait_cycles.to_string(),
)
}
rows.join("\n")
}
///|
/// Render APB summary as text.
pub fn ApbReport::to_text(self : ApbReport) -> String {
"apb transfers=" +
self.transfers.to_string() +
" completed=" +
self.completed.to_string() +
" reads=" +
self.reads.to_string() +
" writes=" +
self.writes.to_string() +
" errors=" +
self.errors.to_string() +
" maximum_wait=" +
self.maximum_wait.to_string() +
" mean_wait=" +
optional_double_text(self.mean_wait) +
" addresses=" +
self.addresses.to_string()
}
///|
/// Render APB summary as JSON.
pub fn ApbReport::to_json(self : ApbReport) -> String {
"{\"transfers\":" +
self.transfers.to_string() +
",\"completed\":" +
self.completed.to_string() +
",\"reads\":" +
self.reads.to_string() +
",\"writes\":" +
self.writes.to_string() +
",\"errors\":" +
self.errors.to_string() +
",\"maximum_wait\":" +
self.maximum_wait.to_string() +
",\"mean_wait\":" +
optional_double_json(self.mean_wait) +
",\"addresses\":" +
self.addresses.to_string() +
"}"
}
///|
/// Read a scalar control signal at a time.
fn signal_is_one(trace : Trace, id : String, time : Int64) -> Bool {
match value_at(trace, id, time) {
Some(Scalar('1')) => true
Some(Vector(bits)) => bits == "1"
_ => false
}
}
///|
/// Read an optional integer signal.
fn optional_int_signal(trace : Trace, id : String?, time : Int64) -> Int64? {
match id {
Some(signal_id) => optional_value_int(value_at(trace, signal_id, time))
None => None
}
}
///|
/// Convert an optional waveform value to an integer without hiding unknowns.
fn optional_value_int(value : Value?) -> Int64? {
match value {
Some(item) => item.to_int()
None => None
}
}