Skip to main content

gwr_models/
test_helpers.rs

1// Copyright (c) 2023 Graphcore Ltd. All rights reserved.
2
3use std::fmt::Debug;
4use std::rc::Rc;
5
6#[doc(hidden)]
7pub use gwr_components::build_component_harness;
8use gwr_engine::types::AccessType;
9use gwr_track::entity::Entity;
10
11use crate::memory::CacheHintType;
12use crate::memory::memory_access::MemoryAccess;
13use crate::memory::memory_map::{DeviceId, MemoryMap};
14use crate::memory::traits::AccessMemory;
15
16/// Builds a simulation test harness for models that implement `AccessMemory`.
17///
18/// This macro uses the same harness DSL and generated API as
19/// `gwr_components::build_component_harness!`, but requires `AccessMemory` and
20/// checks TX expectations against `MemoryTxn`.
21///
22/// See the crate-level Testing documentation for the intended usage pattern,
23/// generated API, and examples.
24#[macro_export]
25macro_rules! build_model_harness {
26    (
27        $(#[$meta:meta])*
28        $vis:vis harness $harness:ident <$item:ident> {
29            component: $component_field:ident : $component_ty:ty,
30            $($sections:tt)*
31        }
32    ) => {
33        $crate::build_model_harness! {
34            @normalize
35            [$(#[$meta])*]
36            [$vis]
37            [$harness]
38            [$item]
39
40            [$component_field: $component_ty]
41            []
42            []
43            []
44            []
45            $($sections)*
46        }
47    };
48
49    (
50        @normalize
51        [$($meta:tt)*]
52        [$vis:vis]
53        [$harness:ident]
54        [$item:ident]
55        [$component_field:ident : $component_ty:ty]
56        [$($rx_ports:tt)*]
57        [$($tx_ports:tt)*]
58        [$($rx_arrays:tt)*]
59        [$($tx_arrays:tt)*]
60        rx ports: { $($rx_section:tt)* }, $($rest:tt)*
61    ) => {
62        $crate::build_model_harness! {
63            @normalize
64            [$($meta)*] [$vis] [$harness] [$item] [$component_field: $component_ty]
65            [$($rx_section)*] [$($tx_ports)*] [$($rx_arrays)*] [$($tx_arrays)*]
66            $($rest)*
67        }
68    };
69
70    (
71        @normalize
72        [$($meta:tt)*]
73        [$vis:vis]
74        [$harness:ident]
75        [$item:ident]
76        [$component_field:ident : $component_ty:ty]
77        [$($rx_ports:tt)*]
78        [$($tx_ports:tt)*]
79        [$($rx_arrays:tt)*]
80        [$($tx_arrays:tt)*]
81        rx ports: { $($rx_section:tt)* }
82    ) => {
83        $crate::build_model_harness! {
84            @normalize
85            [$($meta)*] [$vis] [$harness] [$item] [$component_field: $component_ty]
86            [$($rx_section)*] [$($tx_ports)*] [$($rx_arrays)*] [$($tx_arrays)*]
87        }
88    };
89
90    (
91        @normalize
92        [$($meta:tt)*]
93        [$vis:vis]
94        [$harness:ident]
95        [$item:ident]
96        [$component_field:ident : $component_ty:ty]
97        [$($rx_ports:tt)*]
98        [$($tx_ports:tt)*]
99        [$($rx_arrays:tt)*]
100        [$($tx_arrays:tt)*]
101        tx ports: { $($tx_section:tt)* }, $($rest:tt)*
102    ) => {
103        $crate::build_model_harness! {
104            @normalize
105            [$($meta)*] [$vis] [$harness] [$item] [$component_field: $component_ty]
106            [$($rx_ports)*] [$($tx_section)*] [$($rx_arrays)*] [$($tx_arrays)*]
107            $($rest)*
108        }
109    };
110
111    (
112        @normalize
113        [$($meta:tt)*]
114        [$vis:vis]
115        [$harness:ident]
116        [$item:ident]
117        [$component_field:ident : $component_ty:ty]
118        [$($rx_ports:tt)*]
119        [$($tx_ports:tt)*]
120        [$($rx_arrays:tt)*]
121        [$($tx_arrays:tt)*]
122        tx ports: { $($tx_section:tt)* }
123    ) => {
124        $crate::build_model_harness! {
125            @normalize
126            [$($meta)*] [$vis] [$harness] [$item] [$component_field: $component_ty]
127            [$($rx_ports)*] [$($tx_section)*] [$($rx_arrays)*] [$($tx_arrays)*]
128        }
129    };
130
131    (
132        @normalize
133        [$($meta:tt)*]
134        [$vis:vis]
135        [$harness:ident]
136        [$item:ident]
137        [$component_field:ident : $component_ty:ty]
138        [$($rx_ports:tt)*]
139        [$($tx_ports:tt)*]
140        [$($rx_arrays:tt)*]
141        [$($tx_arrays:tt)*]
142        rx port arrays: { $($rx_array_section:tt)* }, $($rest:tt)*
143    ) => {
144        $crate::build_model_harness! {
145            @normalize
146            [$($meta)*] [$vis] [$harness] [$item] [$component_field: $component_ty]
147            [$($rx_ports)*] [$($tx_ports)*] [$($rx_array_section)*] [$($tx_arrays)*]
148            $($rest)*
149        }
150    };
151
152    (
153        @normalize
154        [$($meta:tt)*]
155        [$vis:vis]
156        [$harness:ident]
157        [$item:ident]
158        [$component_field:ident : $component_ty:ty]
159        [$($rx_ports:tt)*]
160        [$($tx_ports:tt)*]
161        [$($rx_arrays:tt)*]
162        [$($tx_arrays:tt)*]
163        rx port arrays: { $($rx_array_section:tt)* }
164    ) => {
165        $crate::build_model_harness! {
166            @normalize
167            [$($meta)*] [$vis] [$harness] [$item] [$component_field: $component_ty]
168            [$($rx_ports)*] [$($tx_ports)*] [$($rx_array_section)*] [$($tx_arrays)*]
169        }
170    };
171
172    (
173        @normalize
174        [$($meta:tt)*]
175        [$vis:vis]
176        [$harness:ident]
177        [$item:ident]
178        [$component_field:ident : $component_ty:ty]
179        [$($rx_ports:tt)*]
180        [$($tx_ports:tt)*]
181        [$($rx_arrays:tt)*]
182        [$($tx_arrays:tt)*]
183        tx port arrays: { $($tx_array_section:tt)* }, $($rest:tt)*
184    ) => {
185        $crate::build_model_harness! {
186            @normalize
187            [$($meta)*] [$vis] [$harness] [$item] [$component_field: $component_ty]
188            [$($rx_ports)*] [$($tx_ports)*] [$($rx_arrays)*] [$($tx_array_section)*]
189            $($rest)*
190        }
191    };
192
193    (
194        @normalize
195        [$($meta:tt)*]
196        [$vis:vis]
197        [$harness:ident]
198        [$item:ident]
199        [$component_field:ident : $component_ty:ty]
200        [$($rx_ports:tt)*]
201        [$($tx_ports:tt)*]
202        [$($rx_arrays:tt)*]
203        [$($tx_arrays:tt)*]
204        tx port arrays: { $($tx_array_section:tt)* }
205    ) => {
206        $crate::build_model_harness! {
207            @normalize
208            [$($meta)*] [$vis] [$harness] [$item] [$component_field: $component_ty]
209            [$($rx_ports)*] [$($tx_ports)*] [$($rx_arrays)*] [$($tx_array_section)*]
210        }
211    };
212
213    (
214        @normalize
215        [$($meta:tt)*]
216        [$vis:vis]
217        [$harness:ident]
218        [$item:ident]
219        [$component_field:ident : $component_ty:ty]
220        [$($rx_ports:tt)*]
221        [$($tx_ports:tt)*]
222        [$($rx_arrays:tt)*]
223        [$($tx_arrays:tt)*]
224    ) => {
225        $crate::build_model_harness! {
226            @impl
227            [$($meta)*] [$vis] [$harness] [$item] [$component_field: $component_ty]
228            rx ports: { $($rx_ports)* },
229            tx ports: { $($tx_ports)* },
230            rx port arrays: { $($rx_arrays)* },
231            tx port arrays: { $($tx_arrays)* },
232        }
233    };
234
235    (
236        @impl
237        [$($meta:tt)*]
238        [$vis:vis]
239        [$harness:ident]
240        [$item:ident]
241        [$component_field:ident : $component_ty:ty]
242        rx ports: { $($rx_variant:ident <$rx_ty:ty> => $rx_field:ident),* $(,)? },
243        tx ports: { $($tx_variant:ident <$tx_ty:ty> => $tx_field:ident),* $(,)? },
244        rx port arrays: {
245            $($rx_array_variant:ident <$rx_array_ty:ty> => $rx_array_field:ident {
246                count: $rx_array_count:ident
247            }),* $(,)?
248        },
249        tx port arrays: {
250            $($tx_array_variant:ident <$tx_array_ty:ty> => $tx_array_field:ident {
251                count: $tx_array_count:ident
252            }),* $(,)?
253        } $(,)?
254    ) => {
255        $crate::test_helpers::build_component_harness! {
256            @impl_model
257            [$($meta)*]
258            [$vis]
259            [$harness]
260            [$item]
261
262            [$crate::test_helpers::MemoryTxn]
263            [$crate::memory::traits::AccessMemory]
264            [$component_field: $component_ty]
265            rx ports: {
266                $(
267                    $rx_variant <$rx_ty> => $rx_field
268                ),*
269            },
270            tx ports: {
271                $(
272                    $tx_variant <$tx_ty> => $tx_field
273                ),*
274            },
275            rx port arrays: {
276                $(
277                    $rx_array_variant <$rx_array_ty> => $rx_array_field {
278                        count: $rx_array_count
279                    }
280                ),*
281            },
282            tx port arrays: {
283                $(
284                    $tx_array_variant <$tx_array_ty> => $tx_array_field {
285                        count: $tx_array_count
286                    }
287                ),*
288            },
289        }
290    };
291
292}
293
294#[derive(Clone, Debug)]
295pub struct MemoryTxn {
296    access_type: AccessType,
297    dst_addr: u64,
298    src_addr: Option<u64>,
299    bytes: Option<usize>,
300    total_bytes: Option<usize>,
301    destination: Option<u64>,
302    dst_device: Option<DeviceId>,
303    src_device: Option<DeviceId>,
304    cache_hint: Option<CacheHintType>,
305}
306
307impl MemoryTxn {
308    #[must_use]
309    pub fn new(access_type: AccessType, dst_addr: u64) -> Self {
310        Self {
311            access_type,
312            dst_addr,
313            src_addr: None,
314            bytes: None,
315            total_bytes: None,
316            destination: None,
317            dst_device: None,
318            src_device: None,
319            cache_hint: None,
320        }
321    }
322
323    #[must_use]
324    pub fn control(dst_addr: u64) -> Self {
325        Self::new(AccessType::Control, dst_addr)
326    }
327
328    #[must_use]
329    pub fn read_req(dst_addr: u64) -> Self {
330        Self::new(AccessType::ReadRequest, dst_addr)
331    }
332
333    #[must_use]
334    pub fn read_rsp(dst_addr: u64) -> Self {
335        Self::new(AccessType::ReadResponse, dst_addr)
336    }
337
338    #[must_use]
339    pub fn write_req(dst_addr: u64) -> Self {
340        Self::new(AccessType::WriteRequest, dst_addr)
341    }
342
343    #[must_use]
344    pub fn write_np_req(dst_addr: u64) -> Self {
345        Self::new(AccessType::WriteNonPostedRequest, dst_addr)
346    }
347
348    #[must_use]
349    pub fn write_np_rsp(dst_addr: u64) -> Self {
350        Self::new(AccessType::WriteNonPostedResponse, dst_addr)
351    }
352
353    #[must_use]
354    pub fn with_src_addr(mut self, src_addr: u64) -> Self {
355        self.src_addr = Some(src_addr);
356        self
357    }
358
359    #[must_use]
360    pub fn with_bytes(mut self, bytes: usize) -> Self {
361        self.bytes = Some(bytes);
362        self
363    }
364
365    #[must_use]
366    pub fn with_total_bytes(mut self, total_bytes: usize) -> Self {
367        self.total_bytes = Some(total_bytes);
368        self
369    }
370
371    #[must_use]
372    pub fn with_destination(mut self, destination: u64) -> Self {
373        self.destination = Some(destination);
374        self
375    }
376
377    #[must_use]
378    pub fn with_dst_device(mut self, dst_device: DeviceId) -> Self {
379        self.dst_device = Some(dst_device);
380        self
381    }
382
383    #[must_use]
384    pub fn with_src_device(mut self, src_device: DeviceId) -> Self {
385        self.src_device = Some(src_device);
386        self
387    }
388
389    #[must_use]
390    pub fn with_cache_hint(mut self, cache_hint: CacheHintType) -> Self {
391        self.cache_hint = Some(cache_hint);
392        self
393    }
394}
395
396pub trait MemoryAccessMatcher<T>
397where
398    T: AccessMemory + Debug,
399{
400    fn assert_matches(&self, step: &str, actual: &T);
401}
402
403impl<T> MemoryAccessMatcher<T> for MemoryTxn
404where
405    T: AccessMemory + Debug,
406{
407    fn assert_matches(&self, check_id: &str, actual: &T) {
408        assert_eq!(
409            actual.access_type(),
410            self.access_type,
411            "{check_id}: access type mismatch for actual {actual:?}",
412        );
413        assert_eq!(
414            actual.dst_addr(),
415            self.dst_addr,
416            "{check_id}: address mismatch for actual {actual:?}",
417        );
418        if let Some(src_addr) = self.src_addr {
419            assert_eq!(
420                actual.src_addr(),
421                src_addr,
422                "{check_id}: source address mismatch for actual {actual:?}",
423            );
424        }
425        if let Some(bytes) = self.bytes {
426            assert_eq!(
427                actual.access_size_bytes(),
428                bytes,
429                "{check_id}: byte count mismatch for actual {actual:?}",
430            );
431        }
432        if let Some(total_bytes) = self.total_bytes {
433            assert_eq!(
434                actual.total_bytes(),
435                total_bytes,
436                "{check_id}: total byte count mismatch for actual {actual:?}",
437            );
438        }
439        if let Some(destination) = self.destination {
440            assert_eq!(
441                actual.destination(),
442                destination,
443                "{check_id}: destination mismatch for actual {actual:?}",
444            );
445        }
446        if let Some(dst_device) = self.dst_device {
447            assert_eq!(
448                actual.dst_device(),
449                dst_device,
450                "{check_id}: dst device mismatch for actual {actual:?}",
451            );
452        }
453        if let Some(src_device) = self.src_device {
454            assert_eq!(
455                actual.src_device(),
456                src_device,
457                "{check_id}: src device mismatch for actual {actual:?}",
458            );
459        }
460        if let Some(cache_hint) = self.cache_hint {
461            assert_eq!(
462                actual.cache_hint(),
463                cache_hint,
464                "{check_id}: cache hint mismatch for actual {actual:?}",
465            );
466        }
467    }
468}
469
470impl<T> MemoryAccessMatcher<T> for T
471where
472    T: AccessMemory + Debug,
473{
474    fn assert_matches(&self, check_id: &str, actual: &T) {
475        MemoryTxn::new(self.access_type(), self.dst_addr())
476            .with_src_addr(self.src_addr())
477            .with_bytes(self.access_size_bytes())
478            .with_total_bytes(self.total_bytes())
479            .with_destination(self.destination())
480            .with_dst_device(self.dst_device())
481            .with_src_device(self.src_device())
482            .with_cache_hint(self.cache_hint())
483            .assert_matches(check_id, actual);
484    }
485}
486
487impl<T> gwr_components::test_helpers::ValueCheck<T> for MemoryTxn
488where
489    T: AccessMemory + Debug,
490{
491    fn assert_matches(&self, check_id: &str, actual: &T) {
492        MemoryAccessMatcher::assert_matches(self, check_id, actual);
493    }
494}
495
496impl gwr_components::test_helpers::ValueCheck<MemoryAccess> for MemoryAccess {
497    fn assert_matches(&self, check_id: &str, actual: &MemoryAccess) {
498        MemoryAccessMatcher::assert_matches(self, check_id, actual);
499    }
500}
501
502#[must_use]
503pub fn create_default_memory_map() -> MemoryMap {
504    let mut memory_map = MemoryMap::new();
505
506    // Map all addresses to a single device ID.
507    memory_map.insert(0x0, u64::MAX, DeviceId(0)).unwrap();
508
509    memory_map
510}
511
512#[must_use]
513pub fn create_read(
514    created_by: &Rc<Entity>,
515    memory_map: &Rc<MemoryMap>,
516    num_bytes: usize,
517    dst_addr: u64,
518    src_addr: u64,
519    overhead_size_bytes: usize,
520) -> MemoryAccess {
521    let (dst_device, _) = memory_map.lookup(dst_addr).unwrap();
522    let (src_device, _) = memory_map.lookup(src_addr).unwrap();
523    MemoryAccess::new(
524        created_by,
525        AccessType::ReadRequest,
526        num_bytes,
527        dst_addr,
528        src_addr,
529        dst_device,
530        src_device,
531        overhead_size_bytes,
532    )
533}
534
535#[must_use]
536pub fn create_write(
537    created_by: &Rc<Entity>,
538    memory_map: &Rc<MemoryMap>,
539    num_bytes: usize,
540    dst_addr: u64,
541    src_addr: u64,
542    overhead_size_bytes: usize,
543) -> MemoryAccess {
544    let (dst_device, _) = memory_map.lookup(dst_addr).unwrap();
545    let (src_device, _) = memory_map.lookup(src_addr).unwrap();
546    MemoryAccess::new(
547        created_by,
548        AccessType::WriteRequest,
549        num_bytes,
550        dst_addr,
551        src_addr,
552        dst_device,
553        src_device,
554        overhead_size_bytes,
555    )
556}
557
558#[must_use]
559pub fn create_write_np(
560    created_by: &Rc<Entity>,
561    memory_map: &Rc<MemoryMap>,
562    num_bytes: usize,
563    dst_addr: u64,
564    src_addr: u64,
565    overhead_size_bytes: usize,
566) -> MemoryAccess {
567    let (dst_device, _) = memory_map.lookup(dst_addr).unwrap();
568    let (src_device, _) = memory_map.lookup(src_addr).unwrap();
569    MemoryAccess::new(
570        created_by,
571        AccessType::WriteNonPostedRequest,
572        num_bytes,
573        dst_addr,
574        src_addr,
575        dst_device,
576        src_device,
577        overhead_size_bytes,
578    )
579}