Skip to main content

bloqade_lanes_bytecode_core/arch/
addr.rs

1//! Bit-packed address types for bytecode instructions.
2//!
3//! These types encode device-level addresses into compact integer
4//! representations used in the 16-byte instruction format.
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8/// Site index within a word. Matches the 16-bit site_id field in LocationAddr.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct SiteRef(pub u16);
11
12/// Word index within a zone. Matches the 16-bit word_id field in LocationAddr.
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub struct WordRef(pub u16);
15
16/// Zone-qualified word reference for inter-zone bus entries.
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub struct ZonedWordRef {
19    pub zone_id: u8,
20    pub word_id: u16,
21}
22
23/// Atom movement direction along a transport bus.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25#[repr(u8)]
26pub enum Direction {
27    /// Movement from source to destination (value 0).
28    Forward = 0,
29    /// Movement from destination to source (value 1).
30    Backward = 1,
31}
32
33/// Type of transport bus used for an atom move operation.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35#[repr(u8)]
36pub enum MoveType {
37    /// Moves atoms between sites within a word (value 0).
38    SiteBus = 0,
39    /// Moves atoms between words (value 1).
40    WordBus = 1,
41    /// Moves atoms between zones (value 2).
42    ZoneBus = 2,
43}
44
45/// Bit-packed atom location address (zone + word + site).
46///
47/// Encodes `zone_id` (8 bits), `word_id` (16 bits), and `site_id` (16 bits)
48/// into a 64-bit word.
49///
50/// Layout: `[zone_id:8][word_id:16][site_id:16][pad:24]`
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub struct LocationAddr {
53    pub zone_id: u32,
54    pub word_id: u32,
55    pub site_id: u32,
56}
57
58impl LocationAddr {
59    /// Encode to a 64-bit packed integer.
60    ///
61    /// Layout: `[zone_id:8][word_id:16][site_id:16][pad:24]`
62    pub fn encode(&self) -> u64 {
63        ((self.zone_id as u8 as u64) << 56)
64            | ((self.word_id as u16 as u64) << 40)
65            | ((self.site_id as u16 as u64) << 24)
66    }
67
68    /// Decode a 64-bit packed integer into a `LocationAddr`.
69    pub fn decode(bits: u64) -> Self {
70        Self {
71            zone_id: ((bits >> 56) & 0xFF) as u32,
72            word_id: ((bits >> 40) & 0xFFFF) as u32,
73            site_id: ((bits >> 24) & 0xFFFF) as u32,
74        }
75    }
76}
77
78impl Serialize for LocationAddr {
79    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
80        serializer.serialize_u64(self.encode())
81    }
82}
83
84impl<'de> Deserialize<'de> for LocationAddr {
85    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
86        let bits = u64::deserialize(deserializer)?;
87        Ok(Self::decode(bits))
88    }
89}
90
91/// Bit-packed lane address for atom move operations.
92///
93/// Encodes direction (1 bit), move type (2 bits), zone_id (8 bits),
94/// word_id (16 bits), site_id (16 bits), and bus_id (16 bits) across
95/// two 32-bit data words.
96///
97/// Layout:
98/// - data0: `[word_id:16][site_id:16]`
99/// - data1: `[dir:1][mt:2][zone_id:8][pad:5][bus_id:16]`
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
101pub struct LaneAddr {
102    pub direction: Direction,
103    pub move_type: MoveType,
104    pub zone_id: u32,
105    pub word_id: u32,
106    pub site_id: u32,
107    pub bus_id: u32,
108}
109
110impl LaneAddr {
111    /// Encode to two 32-bit data words `(data0, data1)`.
112    pub fn encode(&self) -> (u32, u32) {
113        let data0 = ((self.word_id as u16 as u32) << 16) | (self.site_id as u16 as u32);
114        let data1 = ((self.direction as u32) << 31)
115            | ((self.move_type as u32) << 29)
116            | ((self.zone_id as u8 as u32) << 21)
117            | (self.bus_id as u16 as u32);
118        (data0, data1)
119    }
120
121    /// Encode to a single 64-bit packed integer (`data0 | (data1 << 32)`).
122    pub fn encode_u64(&self) -> u64 {
123        let (d0, d1) = self.encode();
124        (d0 as u64) | ((d1 as u64) << 32)
125    }
126
127    /// Decode two 32-bit data words into a `LaneAddr`.
128    pub fn decode(data0: u32, data1: u32) -> Self {
129        let direction = if (data1 >> 31) & 1 == 0 {
130            Direction::Forward
131        } else {
132            Direction::Backward
133        };
134        let mt_bits = (data1 >> 29) & 0x3;
135        let move_type = match mt_bits {
136            0 => MoveType::SiteBus,
137            1 => MoveType::WordBus,
138            2 => MoveType::ZoneBus,
139            _ => panic!("invalid move type bits: {}", mt_bits),
140        };
141        Self {
142            direction,
143            move_type,
144            zone_id: (data1 >> 21) & 0xFF,
145            word_id: (data0 >> 16) & 0xFFFF,
146            site_id: data0 & 0xFFFF,
147            bus_id: data1 & 0xFFFF,
148        }
149    }
150
151    /// Decode a 64-bit packed integer into a `LaneAddr`.
152    pub fn decode_u64(bits: u64) -> Self {
153        Self::decode(bits as u32, (bits >> 32) as u32)
154    }
155}
156
157/// Bit-packed zone address.
158///
159/// Encodes a zone identifier (8 bits) into a 32-bit value.
160/// Matches the 8-bit zone_id width used in [`LocationAddr`] and [`ZonedWordRef`].
161///
162/// Layout: `[pad:24][zone_id:8]`
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
164pub struct ZoneAddr {
165    pub zone_id: u32,
166}
167
168impl ZoneAddr {
169    /// Encode to a 32-bit packed integer.
170    pub fn encode(&self) -> u32 {
171        self.zone_id as u8 as u32
172    }
173
174    /// Decode a 32-bit packed integer into a `ZoneAddr`.
175    pub fn decode(bits: u32) -> Self {
176        Self {
177            zone_id: bits & 0xFF,
178        }
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_move_type_zone_bus() {
188        assert_eq!(MoveType::SiteBus as u8, 0);
189        assert_eq!(MoveType::WordBus as u8, 1);
190        assert_eq!(MoveType::ZoneBus as u8, 2);
191    }
192
193    #[test]
194    fn test_location_addr_64bit_round_trip() {
195        let addr = LocationAddr {
196            zone_id: 5,
197            word_id: 0x1234,
198            site_id: 0x5678,
199        };
200        let bits = addr.encode();
201        assert_eq!(LocationAddr::decode(bits), addr);
202        assert_eq!((bits >> 56) & 0xFF, 5);
203        assert_eq!((bits >> 40) & 0xFFFF, 0x1234);
204        assert_eq!((bits >> 24) & 0xFFFF, 0x5678);
205        assert_eq!(bits & 0xFFFFFF, 0);
206    }
207
208    #[test]
209    fn test_location_addr_zero() {
210        let addr = LocationAddr {
211            zone_id: 0,
212            word_id: 0,
213            site_id: 0,
214        };
215        assert_eq!(addr.encode(), 0u64);
216        assert_eq!(LocationAddr::decode(0), addr);
217    }
218
219    #[test]
220    fn test_lane_addr_round_trip() {
221        let addr = LaneAddr {
222            direction: Direction::Backward,
223            move_type: MoveType::WordBus,
224            zone_id: 0,
225            word_id: 0x1234,
226            site_id: 0x5678,
227            bus_id: 0x9ABC,
228        };
229        let (data0, data1) = addr.encode();
230        assert_eq!(LaneAddr::decode(data0, data1), addr);
231
232        // Check bit positions in data0
233        assert_eq!((data0 >> 16) & 0xFFFF, 0x1234); // word_id
234        assert_eq!(data0 & 0xFFFF, 0x5678); // site_id
235
236        // Check bit positions in data1
237        assert_eq!((data1 >> 31) & 1, 1); // direction = Backward
238        assert_eq!((data1 >> 29) & 0x3, 1); // move_type = WordBus
239        assert_eq!(data1 & 0xFFFF, 0x9ABC); // bus_id
240    }
241
242    #[test]
243    fn test_lane_addr_forward_sitebus() {
244        let addr = LaneAddr {
245            direction: Direction::Forward,
246            move_type: MoveType::SiteBus,
247            zone_id: 0,
248            word_id: 0,
249            site_id: 0,
250            bus_id: 1,
251        };
252        let (data0, data1) = addr.encode();
253        assert_eq!(data0, 0);
254        assert_eq!(data1, 1);
255        assert_eq!(LaneAddr::decode(data0, data1), addr);
256    }
257
258    #[test]
259    fn test_lane_addr_u64_round_trip() {
260        let addr = LaneAddr {
261            direction: Direction::Backward,
262            move_type: MoveType::WordBus,
263            zone_id: 0,
264            word_id: 1,
265            site_id: 0,
266            bus_id: 0,
267        };
268        let packed = addr.encode_u64();
269        assert_eq!(LaneAddr::decode_u64(packed), addr);
270    }
271
272    #[test]
273    fn test_lane_addr_with_zone_id() {
274        let addr = LaneAddr {
275            direction: Direction::Backward,
276            move_type: MoveType::ZoneBus,
277            zone_id: 7,
278            word_id: 0x1234,
279            site_id: 0x5678,
280            bus_id: 0x9ABC,
281        };
282        let (data0, data1) = addr.encode();
283        let decoded = LaneAddr::decode(data0, data1);
284        assert_eq!(decoded, addr);
285        assert_eq!((data0 >> 16) & 0xFFFF, 0x1234);
286        assert_eq!(data0 & 0xFFFF, 0x5678);
287        assert_eq!((data1 >> 31) & 1, 1);
288        assert_eq!((data1 >> 29) & 0x3, 2);
289        assert_eq!((data1 >> 21) & 0xFF, 7);
290        assert_eq!(data1 & 0xFFFF, 0x9ABC);
291    }
292
293    #[test]
294    fn test_zone_addr_round_trip() {
295        let addr = ZoneAddr { zone_id: 42 };
296        let bits = addr.encode();
297        assert_eq!(bits, 42);
298        assert_eq!(ZoneAddr::decode(bits), addr);
299    }
300
301    #[test]
302    fn test_zone_addr_max() {
303        let addr = ZoneAddr { zone_id: 0xFF };
304        let bits = addr.encode();
305        assert_eq!(bits, 0xFF);
306        assert_eq!(ZoneAddr::decode(bits), addr);
307    }
308
309    #[test]
310    fn test_site_ref_newtype() {
311        let s = SiteRef(42);
312        assert_eq!(s.0, 42);
313        let json = serde_json::to_string(&s).unwrap();
314        let deserialized: SiteRef = serde_json::from_str(&json).unwrap();
315        assert_eq!(s, deserialized);
316    }
317
318    #[test]
319    fn test_word_ref_newtype() {
320        let w = WordRef(100);
321        assert_eq!(w.0, 100);
322        let json = serde_json::to_string(&w).unwrap();
323        let deserialized: WordRef = serde_json::from_str(&json).unwrap();
324        assert_eq!(w, deserialized);
325    }
326
327    #[test]
328    fn test_zoned_word_ref() {
329        let zwr = ZonedWordRef {
330            zone_id: 3,
331            word_id: 42,
332        };
333        assert_eq!(zwr.zone_id, 3);
334        assert_eq!(zwr.word_id, 42);
335        let json = serde_json::to_string(&zwr).unwrap();
336        let deserialized: ZonedWordRef = serde_json::from_str(&json).unwrap();
337        assert_eq!(zwr, deserialized);
338    }
339}