///|
/// Offline arbitrated-bus medium model for L5A.
///|
pub(all) enum ArbitratedBusErrorState {
ErrorActive
ErrorPassive
BusOff
Recovering
} derive(Eq, Debug)
///|
pub fn ArbitratedBusErrorState::label(self : ArbitratedBusErrorState) -> String {
match self {
ErrorActive => "error-active"
ErrorPassive => "error-passive"
BusOff => "bus-off"
Recovering => "recovering"
}
}
///|
pub fn ArbitratedBusErrorState::code(self : ArbitratedBusErrorState) -> Int {
match self {
ErrorActive => 1
ErrorPassive => 2
BusOff => 3
Recovering => 4
}
}
///|
pub(all) enum ArbitratedBusEventKind {
ArbitrationWin
ArbitrationLost
FrameRx
ErrorFrame
AutoRetransmit
BusOffEvent
RecoveryEvent
ReorderRejected
} derive(Eq, Debug)
///|
pub fn ArbitratedBusEventKind::label(self : ArbitratedBusEventKind) -> String {
match self {
ArbitrationWin => "arbitration-win"
ArbitrationLost => "arbitration-lost"
FrameRx => "rx"
ErrorFrame => "error-frame"
AutoRetransmit => "auto-retransmit"
BusOffEvent => "bus-off"
RecoveryEvent => "recovery"
ReorderRejected => "reorder-rejected"
}
}
///|
pub fn ArbitratedBusEventKind::direction(
self : ArbitratedBusEventKind,
) -> @trace.TraceDirection {
match self {
ArbitrationWin | ArbitrationLost | FrameRx | AutoRetransmit => @trace.Rx
ErrorFrame | BusOffEvent | ReorderRejected => @trace.Fault
RecoveryEvent => @trace.Meta
}
}
///|
pub fn ArbitratedBusEventKind::code(self : ArbitratedBusEventKind) -> Int {
match self {
ArbitrationWin => 1
ArbitrationLost => 2
FrameRx => 3
ErrorFrame => 4
AutoRetransmit => 5
BusOffEvent => 6
RecoveryEvent => 7
ReorderRejected => 8
}
}
///|
pub(all) struct ArbitratedBusFrame {
frame_id : Int
sender : @core.EndpointId
target : @core.EndpointId
arbitration_id : Int
payload_len : Int
submitted_at : @core.VTime
label : String
} derive(Eq, Debug)
///|
pub fn ArbitratedBusFrame::make(
frame_id~ : Int,
sender~ : @core.EndpointId,
target~ : @core.EndpointId,
arbitration_id~ : Int,
payload_len~ : Int,
submitted_at~ : @core.VTime,
label~ : String,
) -> ArbitratedBusFrame {
{
frame_id,
sender,
target,
arbitration_id,
payload_len: clamp_non_negative(payload_len),
submitted_at,
label,
}
}
///|
pub(all) struct ArbitratedBusProfile {
name : String
bit_time_ns : Int64
stuff_bit_divisor : Int
tec_error_increment : Int
bus_off_threshold : Int
error_passive_threshold : Int
supports_reorder : Bool
frame_level_duration_estimate : Bool
live_io_evidence : Bool
socketcan_vcan_evidence : Bool
protocol_semantics_present : Bool
} derive(Eq, Debug)
///|
pub fn ArbitratedBusProfile::offline_default() -> ArbitratedBusProfile {
{
name: "arbitrated-bus-offline",
bit_time_ns: 100L,
stuff_bit_divisor: 5,
tec_error_increment: 8,
bus_off_threshold: 16,
error_passive_threshold: 8,
supports_reorder: false,
frame_level_duration_estimate: true,
live_io_evidence: false,
socketcan_vcan_evidence: false,
protocol_semantics_present: false,
}
}
///|
pub(all) struct ArbitratedBusFrameSlot {
slot_id : Int
start : @core.VTime
end : @core.VTime
winner_frame_id : Int
winner_arbitration_id : Int
loser_frame_ids : Array[Int]
error_state_before : ArbitratedBusErrorState
error_state_after : ArbitratedBusErrorState
error_frame : Bool
auto_retransmit : Bool
bus_off : Bool
recovered : Bool
rejected_reorder_fault : Bool
} derive(Eq, Debug)
///|
pub(all) struct ArbitratedBusOfflineRun {
seed : Int
profile : ArbitratedBusProfile
slots : Array[ArbitratedBusFrameSlot]
trace : @trace.TraceLog
digest : @core.SimDigest
replay_count : Int
arbitration_count : Int
arbitration_lost_count : Int
delivered_count : Int
error_frame_count : Int
retransmit_count : Int
bus_off_count : Int
recovery_count : Int
reorder_rejected_count : Int
} derive(Debug)
///|
pub fn ArbitratedBusOfflineRun::passes(self : ArbitratedBusOfflineRun) -> Bool {
self.profile.frame_level_duration_estimate &&
!self.profile.live_io_evidence &&
!self.profile.socketcan_vcan_evidence &&
!self.profile.protocol_semantics_present &&
self.slots.length() >= 4 &&
self.arbitration_count >= 1 &&
self.arbitration_lost_count >= 1 &&
self.delivered_count >= 2 &&
self.error_frame_count >= 1 &&
self.retransmit_count >= 1 &&
self.bus_off_count >= 1 &&
self.recovery_count >= 1 &&
self.reorder_rejected_count >= 1 &&
self.replay_count == self.trace.len() &&
self.digest.event_count == self.trace.len()
}
///|
pub(all) struct ArbitratedBusOfflineReport {
profile_name : String
seed : Int
trace_event_count : Int
replay_count : Int
same_seed_stable : Bool
digest : @core.SimDigest
slot_count : Int
arbitration_count : Int
arbitration_lost_count : Int
delivered_count : Int
error_frame_count : Int
retransmit_count : Int
bus_off_count : Int
recovery_count : Int
reorder_rejected_count : Int
frame_level_duration_estimate : Bool
live_io_evidence : Bool
socketcan_vcan_evidence : Bool
protocol_semantics_present : Bool
} derive(Eq, Debug)
///|
pub fn ArbitratedBusOfflineReport::passes(
self : ArbitratedBusOfflineReport,
) -> Bool {
self.trace_event_count > 0 &&
self.replay_count == self.trace_event_count &&
self.same_seed_stable &&
self.slot_count >= 4 &&
self.arbitration_count >= 1 &&
self.arbitration_lost_count >= 1 &&
self.delivered_count >= 2 &&
self.error_frame_count >= 1 &&
self.retransmit_count >= 1 &&
self.bus_off_count >= 1 &&
self.recovery_count >= 1 &&
self.reorder_rejected_count >= 1 &&
self.frame_level_duration_estimate &&
!self.live_io_evidence &&
!self.socketcan_vcan_evidence &&
!self.protocol_semantics_present
}
///|
pub struct ArbitratedBusMedium {
priv profile : ArbitratedBusProfile
}
///|
pub fn ArbitratedBusMedium::new(profile~ : ArbitratedBusProfile) -> Self {
{ profile, }
}
///|
pub fn ArbitratedBusMedium::offline_default() -> Self {
ArbitratedBusMedium::new(profile=ArbitratedBusProfile::offline_default())
}
///|
pub fn ArbitratedBusMedium::profile(self : Self) -> ArbitratedBusProfile {
self.profile
}
///|
pub fn ArbitratedBusMedium::arbitrate(
self : Self,
candidates : Array[ArbitratedBusFrame],
) -> ArbitratedBusFrame? {
ignore(self)
let mut winner : ArbitratedBusFrame? = None
for frame in candidates {
match winner {
None => winner = Some(frame)
Some(current) =>
if frame_precedes(frame, current) {
winner = Some(frame)
}
}
}
winner
}
///|
pub fn ArbitratedBusMedium::frame_duration(
self : Self,
frame : ArbitratedBusFrame,
) -> @core.Duration {
let base_bits = 47 + frame.payload_len * 8
let divisor = if self.profile.stuff_bit_divisor <= 0 {
5
} else {
self.profile.stuff_bit_divisor
}
let stuff_bits = (base_bits + divisor - 1) / divisor
@core.Duration::from_ns(
Int64::from_int(base_bits + stuff_bits) * self.profile.bit_time_ns,
)
}
///|
pub fn run_arbitrated_bus_offline(seed? : Int = 764) -> ArbitratedBusOfflineRun {
let medium = ArbitratedBusMedium::offline_default()
let profile = medium.profile()
let trace = @trace.TraceLog::new()
let slots : Array[ArbitratedBusFrameSlot] = []
let mut event_id = 1
let mut now = @core.VTime::from_ns(0L)
let mut state = ErrorActive
let mut tec = 0
let mut delivered_count = 0
let mut error_frame_count = 0
let mut retransmit_count = 0
let mut bus_off_count = 0
let mut recovery_count = 0
let mut reorder_rejected_count = 0
let high = ArbitratedBusFrame::make(
frame_id=1,
sender=@core.EndpointId(1),
target=@core.EndpointId(3),
arbitration_id=0x080,
payload_len=8,
submitted_at=now,
label="high-priority-control",
)
let low = ArbitratedBusFrame::make(
frame_id=2,
sender=@core.EndpointId(2),
target=@core.EndpointId(3),
arbitration_id=0x180,
payload_len=8,
submitted_at=now,
label="low-priority-telemetry",
)
let winner = medium.arbitrate([low, high]).unwrap()
let duration = medium.frame_duration(winner)
let slot_end = now.add(duration)
slots.push({
slot_id: 1,
start: now,
end: slot_end,
winner_frame_id: winner.frame_id,
winner_arbitration_id: winner.arbitration_id,
loser_frame_ids: [low.frame_id],
error_state_before: state,
error_state_after: state,
error_frame: false,
auto_retransmit: false,
bus_off: false,
recovered: false,
rejected_reorder_fault: false,
})
event_id = append_bus_event(
trace,
event_id~,
seed~,
frame=winner,
at=now,
kind=ArbitrationWin,
state~,
slot_id=1,
)
event_id = append_bus_event(
trace,
event_id~,
seed~,
frame=low,
at=now,
kind=ArbitrationLost,
state~,
slot_id=1,
)
event_id = append_bus_event(
trace,
event_id~,
seed~,
frame=winner,
at=slot_end,
kind=FrameRx,
state~,
slot_id=1,
)
delivered_count += 1
now = slot_end
let mut retry_frame = low
for retry_slot in 2..<=3 {
let state_before = state
let retry_start = now
let retry_end = retry_start.add(medium.frame_duration(retry_frame))
tec += profile.tec_error_increment
state = error_state_from_tec(tec, profile)
slots.push({
slot_id: retry_slot,
start: retry_start,
end: retry_end,
winner_frame_id: retry_frame.frame_id,
winner_arbitration_id: retry_frame.arbitration_id,
loser_frame_ids: [],
error_state_before: state_before,
error_state_after: state,
error_frame: true,
auto_retransmit: true,
bus_off: state == BusOff,
recovered: false,
rejected_reorder_fault: false,
})
event_id = append_bus_event(
trace,
event_id~,
seed~,
frame=retry_frame,
at=retry_start,
kind=ErrorFrame,
state~,
slot_id=retry_slot,
)
event_id = append_bus_event(
trace,
event_id~,
seed~,
frame=retry_frame,
at=retry_end,
kind=AutoRetransmit,
state~,
slot_id=retry_slot,
)
error_frame_count += 1
retransmit_count += 1
if state == BusOff {
event_id = append_bus_event(
trace,
event_id~,
seed~,
frame=retry_frame,
at=retry_end,
kind=BusOffEvent,
state~,
slot_id=retry_slot,
)
bus_off_count += 1
}
now = retry_end
retry_frame = { ..retry_frame, submitted_at: now }
}
let recovery_start = now
state = Recovering
tec = 0
event_id = append_bus_event(
trace,
event_id~,
seed~,
frame=retry_frame,
at=recovery_start,
kind=RecoveryEvent,
state~,
slot_id=4,
)
recovery_count += 1
state = ErrorActive
let recovery_end = recovery_start.add(medium.frame_duration(retry_frame))
slots.push({
slot_id: 4,
start: recovery_start,
end: recovery_end,
winner_frame_id: retry_frame.frame_id,
winner_arbitration_id: retry_frame.arbitration_id,
loser_frame_ids: [],
error_state_before: Recovering,
error_state_after: state,
error_frame: false,
auto_retransmit: false,
bus_off: false,
recovered: true,
rejected_reorder_fault: false,
})
event_id = append_bus_event(
trace,
event_id~,
seed~,
frame=retry_frame,
at=recovery_end,
kind=FrameRx,
state~,
slot_id=4,
)
delivered_count += 1
let rejected_at = recovery_end.add(@core.Duration::from_ns(1L))
event_id = append_bus_event(
trace,
event_id~,
seed~,
frame=retry_frame,
at=rejected_at,
kind=ReorderRejected,
state~,
slot_id=5,
)
ignore(event_id)
reorder_rejected_count += 1
let arbitration_count = count_bus_events(trace, ArbitrationWin)
let arbitration_lost_count = count_bus_events(trace, ArbitrationLost)
let replay_count = replay_all(trace)
{
seed,
profile,
slots,
trace,
digest: trace.portable_digest(seed~),
replay_count,
arbitration_count,
arbitration_lost_count,
delivered_count,
error_frame_count,
retransmit_count,
bus_off_count,
recovery_count,
reorder_rejected_count,
}
}
///|
pub fn arbitrated_bus_offline_report(
seed? : Int = 764,
) -> ArbitratedBusOfflineReport {
let run = run_arbitrated_bus_offline(seed~)
let same = run.digest == run_arbitrated_bus_offline(seed~).digest
{
profile_name: run.profile.name,
seed,
trace_event_count: run.trace.len(),
replay_count: run.replay_count,
same_seed_stable: same,
digest: run.digest,
slot_count: run.slots.length(),
arbitration_count: run.arbitration_count,
arbitration_lost_count: run.arbitration_lost_count,
delivered_count: run.delivered_count,
error_frame_count: run.error_frame_count,
retransmit_count: run.retransmit_count,
bus_off_count: run.bus_off_count,
recovery_count: run.recovery_count,
reorder_rejected_count: run.reorder_rejected_count,
frame_level_duration_estimate: run.profile.frame_level_duration_estimate,
live_io_evidence: run.profile.live_io_evidence,
socketcan_vcan_evidence: run.profile.socketcan_vcan_evidence,
protocol_semantics_present: run.profile.protocol_semantics_present,
}
}
///|
pub fn ArbitratedBusOfflineRun::timeline_text(
self : ArbitratedBusOfflineRun,
) -> String {
let buf = StringBuilder::new()
buf.write_string("arbitrated_bus=")
buf.write_string(self.profile.name)
buf.write_string("|seed=")
buf.write_string(self.seed.to_string())
buf.write_string("|events=")
buf.write_string(self.trace.len().to_string())
for slot in self.slots {
buf.write_char('\n')
buf.write_string("slot=")
buf.write_string(slot.slot_id.to_string())
buf.write_string("|winner=")
buf.write_string(slot.winner_frame_id.to_string())
buf.write_string("|arb_id=")
buf.write_string(slot.winner_arbitration_id.to_string())
buf.write_string("|start_ns=")
buf.write_string(slot.start.ns().to_string())
buf.write_string("|end_ns=")
buf.write_string(slot.end.ns().to_string())
buf.write_string("|state_before=")
buf.write_string(slot.error_state_before.label())
buf.write_string("|state_after=")
buf.write_string(slot.error_state_after.label())
buf.write_string("|error_frame=")
buf.write_string(slot.error_frame.to_string())
buf.write_string("|auto_retransmit=")
buf.write_string(slot.auto_retransmit.to_string())
buf.write_string("|bus_off=")
buf.write_string(slot.bus_off.to_string())
buf.write_string("|recovered=")
buf.write_string(slot.recovered.to_string())
}
buf.to_string()
}
///|
fn append_bus_event(
trace : @trace.TraceLog,
event_id~ : Int,
seed~ : Int,
frame~ : ArbitratedBusFrame,
at~ : @core.VTime,
kind~ : ArbitratedBusEventKind,
state~ : ArbitratedBusErrorState,
slot_id~ : Int,
) -> Int {
trace.append(
@trace.TraceEvent::make(
event_id~,
parent_id=Some(frame.frame_id),
vtime=at,
clock_domain="sim",
raw_ns=at.ns(),
node_id="arbitrated-bus",
medium_id="arbitrated-bus",
channel_id=Some(@core.ChannelId(slot_id)),
direction=kind.direction(),
payload_digest=Some(
frame.arbitration_id * 31 + kind.code() * 7 + state.code(),
),
rng_step=event_id,
seed~,
backend=@core.SimNative,
label="bus.arbitrated." + kind.label() + "." + frame.label,
),
)
event_id + 1
}
///|
fn replay_all(trace : @trace.TraceLog) -> Int {
let replay = @trace.Replay::from_log(trace)
let mut count = 0
while !replay.is_exhausted() {
match replay.next_event() {
Some(_) => count += 1
None => ()
}
}
count
}
///|
fn count_bus_events(
trace : @trace.TraceLog,
kind : ArbitratedBusEventKind,
) -> Int {
let needle = ".\{kind.label()}."
let mut count = 0
for event in trace.events {
if event.label.contains(needle) {
count += 1
}
}
count
}
///|
fn frame_precedes(
left : ArbitratedBusFrame,
right : ArbitratedBusFrame,
) -> Bool {
if left.arbitration_id < right.arbitration_id {
true
} else if left.arbitration_id == right.arbitration_id {
left.frame_id < right.frame_id
} else {
false
}
}
///|
fn error_state_from_tec(
tec : Int,
profile : ArbitratedBusProfile,
) -> ArbitratedBusErrorState {
if tec >= profile.bus_off_threshold {
BusOff
} else if tec >= profile.error_passive_threshold {
ErrorPassive
} else {
ErrorActive
}
}
///|
fn clamp_non_negative(value : Int) -> Int {
if value < 0 {
0
} else {
value
}
}