Skip to main content

rustpix_tpx/
packet.rs

1//! TPX3 packet parsing.
2//!
3
4/// TPX3 packet wrapper providing efficient field extraction.
5///
6/// Packet format (64-bit):
7/// - Hit packets (ID 0xB*):
8///   - Bits 0-15: SPIDR time
9///   - Bits 16-19: Fine `ToA` (4-bit)
10///   - Bits 20-29: `ToT` (10-bit)
11///   - Bits 30-43: `ToA` (14-bit)
12///   - Bits 44-59: Pixel address (16-bit)
13///   - Bits 60-63: Packet type ID
14///
15/// - TDC packets (ID 0x6F):
16///   - Bits 12-41: 30-bit TDC timestamp
17///   - Bits 56-63: Packet type ID
18#[derive(Clone, Copy, Debug)]
19pub struct Tpx3Packet(u64);
20
21impl Tpx3Packet {
22    /// TPX3 header magic number ("TPX3" in little-endian).
23    pub const TPX3_HEADER_MAGIC: u64 = 0x3358_5054;
24
25    /// Create from raw 64-bit value.
26    #[inline]
27    #[must_use]
28    pub const fn new(raw: u64) -> Self {
29        Self(raw)
30    }
31
32    /// Get raw packet value.
33    #[inline]
34    #[must_use]
35    pub const fn raw(&self) -> u64 {
36        self.0
37    }
38
39    /// Check if this is a TPX3 header packet.
40    #[inline]
41    #[must_use]
42    pub const fn is_header(&self) -> bool {
43        (self.0 & 0xFFFF_FFFF) == Self::TPX3_HEADER_MAGIC
44    }
45
46    /// Check if this is a TDC packet (ID 0x6F).
47    #[inline]
48    #[must_use]
49    pub const fn is_tdc(&self) -> bool {
50        (self.0 >> 56) & 0xFF == 0x6F
51    }
52
53    /// Check if this is a hit packet (ID 0xB*).
54    #[inline]
55    #[must_use]
56    pub const fn is_hit(&self) -> bool {
57        (self.0 >> 60) & 0xF == 0xB
58    }
59
60    /// Get packet type identifier.
61    #[inline]
62    #[must_use]
63    pub const fn packet_type(&self) -> u8 {
64        ((self.0 >> 56) & 0xFF) as u8
65    }
66
67    /// Get chip ID from header packet (bits 32-39).
68    #[inline]
69    #[must_use]
70    pub const fn chip_id(&self) -> u8 {
71        ((self.0 >> 32) & 0xFF) as u8
72    }
73
74    /// Get 16-bit pixel address from hit packet.
75    #[inline]
76    #[must_use]
77    pub const fn pixel_address(&self) -> u16 {
78        ((self.0 >> 44) & 0xFFFF) as u16
79    }
80
81    /// Get 14-bit Time of Arrival.
82    #[inline]
83    #[must_use]
84    pub const fn toa(&self) -> u16 {
85        ((self.0 >> 30) & 0x3FFF) as u16
86    }
87
88    /// Get 10-bit Time over Threshold.
89    #[inline]
90    #[must_use]
91    pub const fn tot(&self) -> u16 {
92        ((self.0 >> 20) & 0x3FF) as u16
93    }
94
95    /// Get 4-bit fine `ToA`.
96    #[inline]
97    #[must_use]
98    pub const fn fine_toa(&self) -> u8 {
99        ((self.0 >> 16) & 0xF) as u8
100    }
101
102    /// Get SPIDR time (16-bit).
103    #[inline]
104    #[must_use]
105    pub const fn spidr_time(&self) -> u16 {
106        (self.0 & 0xFFFF) as u16
107    }
108
109    /// Get 30-bit TDC timestamp from TDC packet.
110    #[inline]
111    #[must_use]
112    pub const fn tdc_timestamp(&self) -> u32 {
113        ((self.0 >> 12) & 0x3FFF_FFFF) as u32
114    }
115
116    /// Decode pixel address to local (x, y) coordinates.
117    ///
118    /// - dcol = (addr >> 8) & 0xFE
119    /// - spix = (addr >> 1) & 0xFC
120    /// - pix = addr & 0x7
121    /// - x = dcol + (pix >> 2)
122    /// - y = spix + (pix & 0x3)
123    #[inline]
124    #[must_use]
125    pub const fn pixel_coordinates(&self) -> (u16, u16) {
126        let addr = self.pixel_address();
127        let dcol = (addr & 0xFE00) >> 8;
128        let spix = (addr & 0x1F8) >> 1;
129        let pix = addr & 0x7;
130        let x = dcol + (pix >> 2);
131        let y = spix + (pix & 0x3);
132        (x, y)
133    }
134}
135
136impl From<u64> for Tpx3Packet {
137    fn from(raw: u64) -> Self {
138        Self::new(raw)
139    }
140}
141
142impl Tpx3Packet {
143    /// Create from 8-byte array (little-endian).
144    #[inline]
145    #[must_use]
146    pub fn from_bytes(bytes: [u8; 8]) -> Self {
147        Self::new(u64::from_le_bytes(bytes))
148    }
149
150    /// Alias for `is_hit` - checks if this is pixel data.
151    #[inline]
152    #[must_use]
153    pub const fn is_pixel_data(&self) -> bool {
154        self.is_hit()
155    }
156
157    /// Calculate coarse timestamp in 25ns units from SPIDR time and `ToA`.
158    ///
159    /// Formula: (`spidr_time` << 14) | toa
160    /// Matches C++ reference implementation.
161    #[inline]
162    #[must_use]
163    pub fn timestamp_coarse(&self) -> u32 {
164        let spidr = u32::from(self.spidr_time());
165        let toa = u32::from(self.toa());
166
167        // Combine SPIDR time and ToA to get 25ns timestamp
168        (spidr << 14) | toa
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn test_header_detection() {
178        let header = Tpx3Packet::new(0x3358_5054);
179        assert!(header.is_header());
180
181        let non_header = Tpx3Packet::new(0x1234_5678);
182        assert!(!non_header.is_header());
183    }
184
185    #[test]
186    fn test_tdc_detection() {
187        let tdc = Tpx3Packet::new(0x6F00_0000_0000_0000);
188        assert!(tdc.is_tdc());
189        assert!(!tdc.is_hit());
190    }
191
192    #[test]
193    fn test_hit_detection() {
194        let hit = Tpx3Packet::new(0xB000_0000_0000_0000);
195        assert!(hit.is_hit());
196        assert!(!hit.is_tdc());
197    }
198
199    #[test]
200    fn test_pixel_coordinate_decode() {
201        // Test with a known address pattern
202        // For addr = 0, we expect x=0, y=0
203        let packet = Tpx3Packet::new(0xB000_0000_0000_0000);
204        let (x, y) = packet.pixel_coordinates();
205        assert_eq!(x, 0);
206        assert_eq!(y, 0);
207    }
208
209    #[test]
210    fn test_tdc_timestamp_extraction() {
211        // TDC packet with timestamp value
212        let tdc = Tpx3Packet::new(0x6F00_0001_2345_6000);
213        let ts = tdc.tdc_timestamp();
214        // bits 12-41 should be extracted
215        assert!(ts > 0);
216    }
217}