Skip to main content

synth_core/
target.rs

1//! Target architecture specifications
2
3use serde::{Deserialize, Serialize};
4
5/// Target architecture for synthesis
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub enum TargetArch {
8    /// ARM Cortex-M series
9    ARMCortexM(CortexMVariant),
10
11    /// RISC-V
12    RISCV(RISCVVariant),
13}
14
15/// ARM Cortex-M variants
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub enum CortexMVariant {
18    /// Cortex-M3 (ARMv7-M)
19    M3,
20
21    /// Cortex-M4 (ARMv7E-M)
22    M4,
23
24    /// Cortex-M4F (with FPU)
25    M4F,
26
27    /// Cortex-M7 (ARMv7E-M, high performance)
28    M7,
29
30    /// Cortex-M7 with double-precision FPU
31    M7DP,
32
33    /// Cortex-M33 (ARMv8-M Mainline with TrustZone)
34    M33,
35
36    /// Cortex-M55 (ARMv8.1-M with Helium MVE)
37    M55,
38}
39
40/// RISC-V variants
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub enum RISCVVariant {
43    /// RV32I (32-bit integer)
44    RV32I,
45
46    /// RV32IMAC (32-bit with multiply, atomic, compressed)
47    RV32IMAC,
48
49    /// RV32GC (32-bit general-purpose with compressed)
50    RV32GC,
51
52    /// RV64I (64-bit integer)
53    RV64I,
54
55    /// RV64IMAC (64-bit with multiply, atomic, compressed)
56    RV64IMAC,
57
58    /// RV64GC (64-bit general-purpose with compressed)
59    RV64GC,
60}
61
62/// Hardware capabilities
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct HardwareCapabilities {
65    /// Target architecture
66    pub arch: TargetArch,
67
68    /// Has Memory Protection Unit (MPU) for ARM
69    pub has_mpu: bool,
70
71    /// Number of MPU regions (typically 8 or 16)
72    pub mpu_regions: u8,
73
74    /// Has Physical Memory Protection (PMP) for RISC-V
75    pub has_pmp: bool,
76
77    /// Number of PMP entries (up to 16)
78    pub pmp_entries: u8,
79
80    /// Has Floating Point Unit
81    pub has_fpu: bool,
82
83    /// FPU precision
84    pub fpu_precision: Option<FPUPrecision>,
85
86    /// Has SIMD/vector extensions
87    pub has_simd: bool,
88
89    /// SIMD level
90    pub simd_level: Option<SIMDLevel>,
91
92    /// Can execute-in-place (XIP) from flash
93    pub xip_capable: bool,
94
95    /// Flash size in bytes
96    pub flash_size: u64,
97
98    /// RAM size in bytes
99    pub ram_size: u64,
100}
101
102/// FPU precision
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
104pub enum FPUPrecision {
105    /// Single precision (32-bit)
106    Single,
107
108    /// Double precision (64-bit)
109    Double,
110}
111
112/// SIMD level
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114pub enum SIMDLevel {
115    /// ARM Helium (MVE - M-Profile Vector Extension)
116    Helium,
117
118    /// RISC-V Vector Extension
119    RISCVVector,
120
121    /// WebAssembly SIMD (128-bit)
122    WASMSIMD,
123}
124
125impl TargetArch {
126    /// Get the target triple string (for compilation)
127    pub fn target_triple(&self) -> &str {
128        match self {
129            TargetArch::ARMCortexM(variant) => match variant {
130                CortexMVariant::M3 => "thumbv7m-none-eabi",
131                CortexMVariant::M4 | CortexMVariant::M4F => "thumbv7em-none-eabi",
132                CortexMVariant::M7 | CortexMVariant::M7DP => "thumbv7em-none-eabihf",
133                CortexMVariant::M33 => "thumbv8m.main-none-eabi",
134                CortexMVariant::M55 => "thumbv8.1m.main-none-eabi",
135            },
136            TargetArch::RISCV(variant) => match variant {
137                RISCVVariant::RV32I | RISCVVariant::RV32IMAC | RISCVVariant::RV32GC => {
138                    "riscv32imac-unknown-none-elf"
139                }
140                RISCVVariant::RV64I | RISCVVariant::RV64IMAC | RISCVVariant::RV64GC => {
141                    "riscv64gc-unknown-none-elf"
142                }
143            },
144        }
145    }
146
147    /// Get CPU name for compiler flags
148    pub fn cpu_name(&self) -> &str {
149        match self {
150            TargetArch::ARMCortexM(variant) => match variant {
151                CortexMVariant::M3 => "cortex-m3",
152                CortexMVariant::M4 | CortexMVariant::M4F => "cortex-m4",
153                CortexMVariant::M7 | CortexMVariant::M7DP => "cortex-m7",
154                CortexMVariant::M33 => "cortex-m33",
155                CortexMVariant::M55 => "cortex-m55",
156            },
157            TargetArch::RISCV(variant) => match variant {
158                RISCVVariant::RV32I => "generic-rv32",
159                RISCVVariant::RV32IMAC | RISCVVariant::RV32GC => "generic-rv32",
160                RISCVVariant::RV64I => "generic-rv64",
161                RISCVVariant::RV64IMAC | RISCVVariant::RV64GC => "generic-rv64",
162            },
163        }
164    }
165
166    /// Check if target has FPU
167    pub fn has_hardware_fp(&self) -> bool {
168        match self {
169            TargetArch::ARMCortexM(variant) => matches!(
170                variant,
171                CortexMVariant::M4F
172                    | CortexMVariant::M7
173                    | CortexMVariant::M7DP
174                    | CortexMVariant::M55
175            ),
176            TargetArch::RISCV(variant) => {
177                matches!(variant, RISCVVariant::RV32GC | RISCVVariant::RV64GC)
178            }
179        }
180    }
181}
182
183impl HardwareCapabilities {
184    /// Create capabilities for a typical Cortex-M4F
185    pub fn cortex_m4f_typical() -> Self {
186        Self {
187            arch: TargetArch::ARMCortexM(CortexMVariant::M4F),
188            has_mpu: true,
189            mpu_regions: 8,
190            has_pmp: false,
191            pmp_entries: 0,
192            has_fpu: true,
193            fpu_precision: Some(FPUPrecision::Single),
194            has_simd: false,
195            simd_level: None,
196            xip_capable: true,
197            flash_size: 1024 * 1024, // 1MB
198            ram_size: 192 * 1024,    // 192KB
199        }
200    }
201
202    /// Create capabilities for Nordic nRF52840
203    pub fn nrf52840() -> Self {
204        Self {
205            arch: TargetArch::ARMCortexM(CortexMVariant::M4F),
206            has_mpu: true,
207            mpu_regions: 8,
208            has_pmp: false,
209            pmp_entries: 0,
210            has_fpu: true,
211            fpu_precision: Some(FPUPrecision::Single),
212            has_simd: false,
213            simd_level: None,
214            xip_capable: true,
215            flash_size: 1024 * 1024, // 1MB
216            ram_size: 256 * 1024,    // 256KB
217        }
218    }
219
220    /// Create capabilities for STM32F407
221    pub fn stm32f407() -> Self {
222        Self {
223            arch: TargetArch::ARMCortexM(CortexMVariant::M4F),
224            has_mpu: true,
225            mpu_regions: 8,
226            has_pmp: false,
227            pmp_entries: 0,
228            has_fpu: true,
229            fpu_precision: Some(FPUPrecision::Single),
230            has_simd: false,
231            simd_level: None,
232            xip_capable: true,
233            flash_size: 1024 * 1024, // 1MB
234            ram_size: 192 * 1024,    // 192KB (128KB + 64KB CCM)
235        }
236    }
237
238    /// Create capabilities for STM32H743 (Cortex-M7 with double-precision FPU)
239    ///
240    /// 16 MPU regions, 2MB Flash, 1MB RAM (DTCM + AXI SRAM + SRAM1-4).
241    pub fn stm32h743() -> Self {
242        Self {
243            arch: TargetArch::ARMCortexM(CortexMVariant::M7DP),
244            has_mpu: true,
245            mpu_regions: 16,
246            has_pmp: false,
247            pmp_entries: 0,
248            has_fpu: true,
249            fpu_precision: Some(FPUPrecision::Double),
250            has_simd: false,
251            simd_level: None,
252            xip_capable: true,
253            flash_size: 2 * 1024 * 1024, // 2MB
254            ram_size: 1024 * 1024,       // 1MB total
255        }
256    }
257
258    /// Create capabilities for i.MX RT1062 (Cortex-M7 with single-precision FPU)
259    ///
260    /// Representative high-end M7 with 16 MPU regions, single-precision FPU,
261    /// large OCRAM, and external XIP-capable QuadSPI Flash. Matches the
262    /// configuration of safety-grade lockstepped M7 platforms used in
263    /// industrial and embedded automotive contexts.
264    pub fn imxrt1062() -> Self {
265        Self {
266            arch: TargetArch::ARMCortexM(CortexMVariant::M7),
267            has_mpu: true,
268            mpu_regions: 16,
269            has_pmp: false,
270            pmp_entries: 0,
271            has_fpu: true,
272            fpu_precision: Some(FPUPrecision::Single),
273            has_simd: false,
274            simd_level: None,
275            xip_capable: true,
276            flash_size: 8 * 1024 * 1024, // 8MB external QSPI flash (typical)
277            ram_size: 1024 * 1024,       // 1MB OCRAM (FlexRAM 512KB + OCRAM 512KB)
278        }
279    }
280}
281
282// ============================================================================
283// Multi-backend target specification
284// ============================================================================
285
286/// Target architecture family (broader than CortexMVariant — spans all ARM + RISC-V)
287#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
288pub enum ArchFamily {
289    ArmCortexM,
290    ArmCortexR,
291    ArmCortexA,
292    RiscV,
293}
294
295/// ISA variant details — determines instruction encoding
296#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
297pub enum IsaVariant {
298    /// Cortex-M0 (v6-M, Thumb only)
299    Thumb,
300    /// Cortex-M3/M4/M7 (v7-M, Thumb-2)
301    Thumb2,
302    /// Cortex-R (v7-R, full ARM + Thumb-2)
303    Arm32,
304    /// Cortex-A53+ (v8-A)
305    AArch64,
306    /// RISC-V 32-bit with extension string (e.g. "imac")
307    RiscV32 { extensions: String },
308    /// RISC-V 64-bit with extension string
309    RiscV64 { extensions: String },
310}
311
312/// Memory protection model
313#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
314pub enum MemProtection {
315    /// ARM MPU (Cortex-M, Cortex-R)
316    Mpu { regions: u8 },
317    /// ARM MMU (Cortex-A)
318    Mmu,
319    /// RISC-V Physical Memory Protection
320    Pmp { entries: u8 },
321    /// No hardware protection
322    SoftwareOnly,
323}
324
325/// Full target specification for multi-backend compilation
326#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
327pub struct TargetSpec {
328    /// Architecture family
329    pub family: ArchFamily,
330    /// LLVM-style triple (e.g. "thumbv7em-none-eabihf")
331    pub triple: String,
332    /// ISA variant details
333    pub isa: IsaVariant,
334    /// Memory protection model
335    pub mem_protection: MemProtection,
336    /// Floating-point unit capability (None = soft-float only)
337    pub fpu: Option<FPUPrecision>,
338}
339
340// ============================================================================
341// ISA feature gating — determines which instructions each variant supports
342// ============================================================================
343
344impl CortexMVariant {
345    /// Returns whether this variant supports DSP instructions (SSAT, USAT, SMLAL, etc.)
346    /// Only ARMv7E-M (Cortex-M4+) and above have DSP extensions.
347    pub fn has_dsp(&self) -> bool {
348        !matches!(self, Self::M3)
349    }
350
351    /// Returns whether this variant has a single-precision FPU
352    pub fn has_single_precision_fpu(&self) -> bool {
353        matches!(self, Self::M4F | Self::M7 | Self::M7DP | Self::M55)
354    }
355
356    /// Returns whether this variant has a double-precision FPU
357    pub fn has_double_precision_fpu(&self) -> bool {
358        matches!(self, Self::M7DP)
359    }
360
361    /// Returns whether this variant supports TrustZone security extensions
362    pub fn has_trustzone(&self) -> bool {
363        matches!(self, Self::M33 | Self::M55)
364    }
365
366    /// Returns whether this variant supports Helium MVE (M-Profile Vector Extension)
367    pub fn has_helium(&self) -> bool {
368        matches!(self, Self::M55)
369    }
370}
371
372impl TargetSpec {
373    /// Returns whether this target has a single-precision FPU
374    pub fn has_single_precision_fpu(&self) -> bool {
375        matches!(
376            self.fpu,
377            Some(FPUPrecision::Single) | Some(FPUPrecision::Double)
378        )
379    }
380
381    /// Returns whether this target has a double-precision FPU
382    pub fn has_double_precision_fpu(&self) -> bool {
383        matches!(self.fpu, Some(FPUPrecision::Double))
384    }
385}
386
387impl TargetSpec {
388    /// Cortex-M3 (ARMv7-M, no FPU, 8 MPU regions)
389    pub fn cortex_m3() -> Self {
390        Self {
391            family: ArchFamily::ArmCortexM,
392            triple: "thumbv7m-none-eabi".to_string(),
393            isa: IsaVariant::Thumb2,
394            mem_protection: MemProtection::Mpu { regions: 8 },
395            fpu: None,
396        }
397    }
398
399    /// Cortex-M4 with 8 MPU regions (no FPU — M4 without F suffix)
400    pub fn cortex_m4() -> Self {
401        Self {
402            family: ArchFamily::ArmCortexM,
403            triple: "thumbv7em-none-eabi".to_string(),
404            isa: IsaVariant::Thumb2,
405            mem_protection: MemProtection::Mpu { regions: 8 },
406            fpu: None,
407        }
408    }
409
410    /// Cortex-M4F with single-precision FPU
411    pub fn cortex_m4f() -> Self {
412        Self {
413            family: ArchFamily::ArmCortexM,
414            triple: "thumbv7em-none-eabihf".to_string(),
415            isa: IsaVariant::Thumb2,
416            mem_protection: MemProtection::Mpu { regions: 8 },
417            fpu: Some(FPUPrecision::Single),
418        }
419    }
420
421    /// Cortex-M7 with single-precision FPU and 16 MPU regions
422    pub fn cortex_m7() -> Self {
423        Self {
424            family: ArchFamily::ArmCortexM,
425            triple: "thumbv7em-none-eabihf".to_string(),
426            isa: IsaVariant::Thumb2,
427            mem_protection: MemProtection::Mpu { regions: 16 },
428            fpu: Some(FPUPrecision::Single),
429        }
430    }
431
432    /// Cortex-M7 with double-precision FPU and 16 MPU regions
433    pub fn cortex_m7dp() -> Self {
434        Self {
435            family: ArchFamily::ArmCortexM,
436            triple: "thumbv7em-none-eabihf".to_string(),
437            isa: IsaVariant::Thumb2,
438            mem_protection: MemProtection::Mpu { regions: 16 },
439            fpu: Some(FPUPrecision::Double),
440        }
441    }
442
443    /// Cortex-R5 with 12 MPU regions
444    pub fn cortex_r5() -> Self {
445        Self {
446            family: ArchFamily::ArmCortexR,
447            triple: "armv7r-none-eabihf".to_string(),
448            isa: IsaVariant::Arm32,
449            mem_protection: MemProtection::Mpu { regions: 12 },
450            fpu: None,
451        }
452    }
453
454    /// Cortex-A53 with MMU
455    pub fn cortex_a53() -> Self {
456        Self {
457            family: ArchFamily::ArmCortexA,
458            triple: "aarch64-none-elf".to_string(),
459            isa: IsaVariant::AArch64,
460            mem_protection: MemProtection::Mmu,
461            fpu: None,
462        }
463    }
464
465    /// RISC-V RV32IMAC with 16 PMP entries
466    /// A bare-metal RV32 profile with the given ISA `extensions` string (e.g.
467    /// `"imac"`, `"imc"`, `"im"`, `"i"`, `"gc"`). All RV32 variants share the
468    /// PMP memory-protection model and have no hardware FPU in the base profile.
469    pub fn riscv32(extensions: &str) -> Self {
470        Self {
471            family: ArchFamily::RiscV,
472            triple: format!("riscv32{extensions}-unknown-none-elf"),
473            isa: IsaVariant::RiscV32 {
474                extensions: extensions.to_string(),
475            },
476            mem_protection: MemProtection::Pmp { entries: 16 },
477            fpu: None,
478        }
479    }
480
481    pub fn riscv32imac() -> Self {
482        Self::riscv32("imac")
483    }
484
485    /// Cortex-M55 with Helium MVE, single-precision FPU, TrustZone
486    pub fn cortex_m55() -> Self {
487        Self {
488            family: ArchFamily::ArmCortexM,
489            triple: "thumbv8.1m.main-none-eabi".to_string(),
490            isa: IsaVariant::Thumb2,
491            mem_protection: MemProtection::Mpu { regions: 16 },
492            fpu: Some(FPUPrecision::Single),
493        }
494    }
495
496    /// Parse from an LLVM triple or shorthand name
497    pub fn from_triple(triple: &str) -> std::result::Result<Self, String> {
498        match triple {
499            "thumbv7m-none-eabi" | "cortex-m3" => Ok(Self::cortex_m3()),
500            "thumbv7em-none-eabi" | "cortex-m4" => Ok(Self::cortex_m4()),
501            "thumbv7em-none-eabihf" | "cortex-m4f" => Ok(Self::cortex_m4f()),
502            "cortex-m7" => Ok(Self::cortex_m7()),
503            "cortex-m7dp" => Ok(Self::cortex_m7dp()),
504            "thumbv8.1m.main-none-eabi" | "cortex-m55" => Ok(Self::cortex_m55()),
505            "armv7r-none-eabihf" | "cortex-r5" => Ok(Self::cortex_r5()),
506            "aarch64-none-elf" | "cortex-a53" => Ok(Self::cortex_a53()),
507            // RV32 — accept the short ISA names that `riscv-runtime` and the
508            // toolchains use (`rv32imac`, …) as well as the long LLVM triples.
509            // The ESP32-C3 is RV32IMC (#218).
510            "riscv32imac-unknown-none-elf" | "riscv32imac" | "rv32imac" => {
511                Ok(Self::riscv32("imac"))
512            }
513            "riscv32imc" | "rv32imc" | "esp32c3" => Ok(Self::riscv32("imc")),
514            "riscv32im" | "rv32im" => Ok(Self::riscv32("im")),
515            "riscv32i" | "rv32i" => Ok(Self::riscv32("i")),
516            "riscv32gc" | "rv32gc" => Ok(Self::riscv32("gc")),
517            // Generic RV32 default → the broadly-supported imac profile.
518            "riscv32" | "rv32" => Ok(Self::riscv32("imac")),
519            _ => Err(format!(
520                "unknown target triple: {triple}. Supported: \
521                 cortex-m3, cortex-m4, cortex-m4f, cortex-m7, cortex-m7dp; \
522                 rv32imac, rv32imc, rv32im, rv32i, rv32gc, esp32c3"
523            )),
524        }
525    }
526
527    /// Whether this target has a floating-point unit
528    pub fn has_fpu(&self) -> bool {
529        self.fpu.is_some()
530    }
531
532    /// Whether this target uses Thumb-2 encoding (Cortex-M)
533    pub fn is_thumb2(&self) -> bool {
534        self.isa == IsaVariant::Thumb2
535    }
536}
537
538#[cfg(test)]
539mod tests {
540    use super::*;
541
542    /// #218: the short RV32 ISA names (and the ESP32-C3 board alias) that
543    /// `riscv-runtime` / the toolchains use must resolve to RISC-V target specs,
544    /// not "unknown target triple", so `synth compile -b riscv -t rv32imac` is
545    /// reachable.
546    #[test]
547    fn test_rv32_short_target_names_218() {
548        for (name, ext) in [
549            ("rv32imac", "imac"),
550            ("rv32imc", "imc"),
551            ("rv32im", "im"),
552            ("rv32i", "i"),
553            ("rv32gc", "gc"),
554            ("esp32c3", "imc"),  // ESP32-C3 is RV32IMC
555            ("riscv32", "imac"), // generic default
556            ("riscv32imac", "imac"),
557        ] {
558            let spec = TargetSpec::from_triple(name)
559                .unwrap_or_else(|e| panic!("RV32 target '{name}' must resolve: {e}"));
560            assert_eq!(spec.family, ArchFamily::RiscV, "{name}");
561            assert_eq!(
562                spec.isa,
563                IsaVariant::RiscV32 {
564                    extensions: ext.to_string()
565                },
566                "{name}"
567            );
568        }
569        // An unknown triple still errors, and now lists the RV32 options.
570        let err = TargetSpec::from_triple("rv99zzz").unwrap_err();
571        assert!(
572            err.contains("rv32imac"),
573            "error should list RV32 targets: {err}"
574        );
575    }
576
577    #[test]
578    fn test_target_spec_from_triple() {
579        let spec = TargetSpec::from_triple("cortex-m4").unwrap();
580        assert_eq!(spec.family, ArchFamily::ArmCortexM);
581        assert!(spec.is_thumb2());
582        assert_eq!(spec.mem_protection, MemProtection::Mpu { regions: 8 });
583        assert!(!spec.has_fpu());
584    }
585
586    #[test]
587    fn test_target_spec_unknown() {
588        assert!(TargetSpec::from_triple("mips-unknown-none").is_err());
589    }
590
591    #[test]
592    fn test_cortex_m3() {
593        let spec = TargetSpec::from_triple("cortex-m3").unwrap();
594        assert_eq!(spec.triple, "thumbv7m-none-eabi");
595        assert!(!spec.has_fpu());
596    }
597
598    #[test]
599    fn test_cortex_m4f() {
600        let spec = TargetSpec::from_triple("cortex-m4f").unwrap();
601        assert_eq!(spec.triple, "thumbv7em-none-eabihf");
602        assert!(spec.has_fpu());
603        assert_eq!(spec.fpu, Some(FPUPrecision::Single));
604    }
605
606    #[test]
607    fn test_cortex_m7dp() {
608        let spec = TargetSpec::from_triple("cortex-m7dp").unwrap();
609        assert!(spec.has_fpu());
610        assert_eq!(spec.fpu, Some(FPUPrecision::Double));
611        assert_eq!(spec.mem_protection, MemProtection::Mpu { regions: 16 });
612    }
613
614    #[test]
615    fn test_cortex_m7_has_fpu() {
616        let spec = TargetSpec::cortex_m7();
617        assert!(spec.has_fpu());
618        assert_eq!(spec.fpu, Some(FPUPrecision::Single));
619    }
620
621    // ========================================================================
622    // ISA feature gating tests
623    // ========================================================================
624
625    #[test]
626    fn test_cortex_m3_isa_features() {
627        let m3 = CortexMVariant::M3;
628        assert!(!m3.has_dsp(), "M3 should not have DSP");
629        assert!(!m3.has_single_precision_fpu(), "M3 should not have FPU");
630        assert!(
631            !m3.has_double_precision_fpu(),
632            "M3 should not have double FPU"
633        );
634        assert!(!m3.has_trustzone(), "M3 should not have TrustZone");
635        assert!(!m3.has_helium(), "M3 should not have Helium");
636    }
637
638    #[test]
639    fn test_cortex_m4_isa_features() {
640        let m4 = CortexMVariant::M4;
641        assert!(m4.has_dsp(), "M4 should have DSP");
642        assert!(
643            !m4.has_single_precision_fpu(),
644            "M4 (no F) should not have FPU"
645        );
646        assert!(
647            !m4.has_double_precision_fpu(),
648            "M4 should not have double FPU"
649        );
650    }
651
652    #[test]
653    fn test_cortex_m4f_isa_features() {
654        let m4f = CortexMVariant::M4F;
655        assert!(m4f.has_dsp(), "M4F should have DSP");
656        assert!(
657            m4f.has_single_precision_fpu(),
658            "M4F should have single-precision FPU"
659        );
660        assert!(
661            !m4f.has_double_precision_fpu(),
662            "M4F should not have double FPU"
663        );
664    }
665
666    #[test]
667    fn test_cortex_m7dp_isa_features() {
668        let m7dp = CortexMVariant::M7DP;
669        assert!(m7dp.has_dsp(), "M7DP should have DSP");
670        assert!(
671            m7dp.has_single_precision_fpu(),
672            "M7DP should have single-precision FPU"
673        );
674        assert!(
675            m7dp.has_double_precision_fpu(),
676            "M7DP should have double-precision FPU"
677        );
678    }
679
680    #[test]
681    fn test_cortex_m33_isa_features() {
682        let m33 = CortexMVariant::M33;
683        assert!(m33.has_dsp(), "M33 should have DSP");
684        assert!(m33.has_trustzone(), "M33 should have TrustZone");
685        assert!(!m33.has_helium(), "M33 should not have Helium");
686    }
687
688    #[test]
689    fn test_cortex_m55_isa_features() {
690        let m55 = CortexMVariant::M55;
691        assert!(m55.has_dsp(), "M55 should have DSP");
692        assert!(
693            m55.has_single_precision_fpu(),
694            "M55 should have single-precision FPU"
695        );
696        assert!(m55.has_trustzone(), "M55 should have TrustZone");
697        assert!(m55.has_helium(), "M55 should have Helium");
698    }
699
700    #[test]
701    fn test_target_spec_fpu_precision_queries() {
702        let m3 = TargetSpec::cortex_m3();
703        assert!(!m3.has_single_precision_fpu(), "M3 spec has no FPU");
704        assert!(!m3.has_double_precision_fpu(), "M3 spec has no double FPU");
705
706        let m4f = TargetSpec::cortex_m4f();
707        assert!(m4f.has_single_precision_fpu(), "M4F spec has single FPU");
708        assert!(
709            !m4f.has_double_precision_fpu(),
710            "M4F spec has no double FPU"
711        );
712
713        let m7dp = TargetSpec::cortex_m7dp();
714        assert!(m7dp.has_single_precision_fpu(), "M7DP spec has single FPU");
715        assert!(m7dp.has_double_precision_fpu(), "M7DP spec has double FPU");
716    }
717
718    #[test]
719    fn test_cortex_m55_target_spec() {
720        let m55 = TargetSpec::cortex_m55();
721        assert_eq!(m55.family, ArchFamily::ArmCortexM);
722        assert_eq!(m55.triple, "thumbv8.1m.main-none-eabi");
723        assert!(m55.is_thumb2());
724        assert!(m55.has_fpu());
725        assert!(m55.has_single_precision_fpu());
726        assert_eq!(m55.mem_protection, MemProtection::Mpu { regions: 16 });
727    }
728
729    #[test]
730    fn test_cortex_m55_from_triple() {
731        let m55 = TargetSpec::from_triple("cortex-m55").unwrap();
732        assert_eq!(m55.triple, "thumbv8.1m.main-none-eabi");
733        assert!(m55.has_fpu());
734
735        let m55_triple = TargetSpec::from_triple("thumbv8.1m.main-none-eabi").unwrap();
736        assert_eq!(m55_triple.triple, "thumbv8.1m.main-none-eabi");
737    }
738
739    // ========================================================================
740    // M7 hardware constructor tests (PR #86 patch coverage)
741    // ========================================================================
742
743    #[test]
744    fn test_imxrt1062_capabilities() {
745        let caps = HardwareCapabilities::imxrt1062();
746        assert_eq!(caps.arch, TargetArch::ARMCortexM(CortexMVariant::M7));
747        assert!(caps.has_mpu, "i.MX RT1062 has an MPU");
748        assert_eq!(caps.mpu_regions, 16, "M7 parts have 16 MPU regions");
749        assert!(!caps.has_pmp, "ARM parts do not have PMP");
750        assert_eq!(caps.pmp_entries, 0);
751        assert!(caps.has_fpu, "i.MX RT1062 has FPU");
752        assert_eq!(
753            caps.fpu_precision,
754            Some(FPUPrecision::Single),
755            "i.MX RT1062 has single-precision FPU"
756        );
757        assert!(!caps.has_simd);
758        assert!(caps.simd_level.is_none());
759        assert!(caps.xip_capable);
760        assert_eq!(caps.flash_size, 8 * 1024 * 1024, "8MB QSPI typical");
761        assert_eq!(caps.ram_size, 1024 * 1024, "1MB OCRAM");
762    }
763
764    #[test]
765    fn test_stm32h743_capabilities() {
766        let caps = HardwareCapabilities::stm32h743();
767        assert_eq!(caps.arch, TargetArch::ARMCortexM(CortexMVariant::M7DP));
768        assert!(caps.has_mpu);
769        assert_eq!(caps.mpu_regions, 16, "STM32H743 has 16 MPU regions");
770        assert!(!caps.has_pmp);
771        assert!(caps.has_fpu);
772        assert_eq!(
773            caps.fpu_precision,
774            Some(FPUPrecision::Double),
775            "STM32H743 has double-precision FPU"
776        );
777        assert_eq!(caps.flash_size, 2 * 1024 * 1024, "2MB Flash");
778        assert_eq!(caps.ram_size, 1024 * 1024, "1MB RAM total");
779        assert!(caps.xip_capable);
780    }
781
782    #[test]
783    fn test_imxrt1062_arch_target_triple() {
784        // imxrt1062 uses the M7 variant, which maps to thumbv7em-none-eabihf.
785        let caps = HardwareCapabilities::imxrt1062();
786        assert_eq!(caps.arch.target_triple(), "thumbv7em-none-eabihf");
787        assert_eq!(caps.arch.cpu_name(), "cortex-m7");
788        assert!(caps.arch.has_hardware_fp());
789    }
790
791    #[test]
792    fn test_stm32h743_arch_target_triple() {
793        // stm32h743 uses M7DP — same triple as M7 but flagged as double-precision.
794        let caps = HardwareCapabilities::stm32h743();
795        assert_eq!(caps.arch.target_triple(), "thumbv7em-none-eabihf");
796        assert_eq!(caps.arch.cpu_name(), "cortex-m7");
797        assert!(caps.arch.has_hardware_fp());
798    }
799
800    #[test]
801    fn test_m7_hardware_capabilities_distinct_from_m4() {
802        // Regression: M7 hardware must report 16 regions, M4 must report 8.
803        // Previously the CLI's --hardware dispatch silently fell through to
804        // a wrong default — this guards the constructor selection.
805        let m4 = HardwareCapabilities::nrf52840();
806        let m7 = HardwareCapabilities::imxrt1062();
807        let m7dp = HardwareCapabilities::stm32h743();
808        assert_eq!(m4.mpu_regions, 8);
809        assert_eq!(m7.mpu_regions, 16);
810        assert_eq!(m7dp.mpu_regions, 16);
811        // M7DP must report Double, M7 must report Single, M4F must report Single.
812        assert_eq!(m4.fpu_precision, Some(FPUPrecision::Single));
813        assert_eq!(m7.fpu_precision, Some(FPUPrecision::Single));
814        assert_eq!(m7dp.fpu_precision, Some(FPUPrecision::Double));
815    }
816}