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