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            // #882: name the FULL valid set — an unrecognised target is a
520            // hard error at the CLI seam and this message is the user's map.
521            _ => Err(format!(
522                "unknown target triple: {triple}. Supported: \
523                 cortex-m3, cortex-m4, cortex-m4f, cortex-m7, cortex-m7dp, \
524                 cortex-m55, cortex-r5 (ARM); \
525                 rv32imac, rv32imc, rv32im, rv32i, rv32gc, esp32c3, riscv32 \
526                 (RISC-V, -b riscv); cortex-a53 (-b aarch64)"
527            )),
528        }
529    }
530
531    /// Whether this target has a floating-point unit
532    pub fn has_fpu(&self) -> bool {
533        self.fpu.is_some()
534    }
535
536    /// Whether this target uses Thumb-2 encoding (Cortex-M)
537    pub fn is_thumb2(&self) -> bool {
538        self.isa == IsaVariant::Thumb2
539    }
540}
541
542#[cfg(test)]
543mod tests {
544    use super::*;
545
546    /// #218: the short RV32 ISA names (and the ESP32-C3 board alias) that
547    /// `riscv-runtime` / the toolchains use must resolve to RISC-V target specs,
548    /// not "unknown target triple", so `synth compile -b riscv -t rv32imac` is
549    /// reachable.
550    #[test]
551    fn test_rv32_short_target_names_218() {
552        for (name, ext) in [
553            ("rv32imac", "imac"),
554            ("rv32imc", "imc"),
555            ("rv32im", "im"),
556            ("rv32i", "i"),
557            ("rv32gc", "gc"),
558            ("esp32c3", "imc"),  // ESP32-C3 is RV32IMC
559            ("riscv32", "imac"), // generic default
560            ("riscv32imac", "imac"),
561        ] {
562            let spec = TargetSpec::from_triple(name)
563                .unwrap_or_else(|e| panic!("RV32 target '{name}' must resolve: {e}"));
564            assert_eq!(spec.family, ArchFamily::RiscV, "{name}");
565            assert_eq!(
566                spec.isa,
567                IsaVariant::RiscV32 {
568                    extensions: ext.to_string()
569                },
570                "{name}"
571            );
572        }
573        // An unknown triple still errors, and now lists the RV32 options.
574        let err = TargetSpec::from_triple("rv99zzz").unwrap_err();
575        assert!(
576            err.contains("rv32imac"),
577            "error should list RV32 targets: {err}"
578        );
579    }
580
581    #[test]
582    fn test_target_spec_from_triple() {
583        let spec = TargetSpec::from_triple("cortex-m4").unwrap();
584        assert_eq!(spec.family, ArchFamily::ArmCortexM);
585        assert!(spec.is_thumb2());
586        assert_eq!(spec.mem_protection, MemProtection::Mpu { regions: 8 });
587        assert!(!spec.has_fpu());
588    }
589
590    #[test]
591    fn test_target_spec_unknown() {
592        assert!(TargetSpec::from_triple("mips-unknown-none").is_err());
593    }
594
595    #[test]
596    fn test_cortex_m3() {
597        let spec = TargetSpec::from_triple("cortex-m3").unwrap();
598        assert_eq!(spec.triple, "thumbv7m-none-eabi");
599        assert!(!spec.has_fpu());
600    }
601
602    #[test]
603    fn test_cortex_m4f() {
604        let spec = TargetSpec::from_triple("cortex-m4f").unwrap();
605        assert_eq!(spec.triple, "thumbv7em-none-eabihf");
606        assert!(spec.has_fpu());
607        assert_eq!(spec.fpu, Some(FPUPrecision::Single));
608    }
609
610    #[test]
611    fn test_cortex_m7dp() {
612        let spec = TargetSpec::from_triple("cortex-m7dp").unwrap();
613        assert!(spec.has_fpu());
614        assert_eq!(spec.fpu, Some(FPUPrecision::Double));
615        assert_eq!(spec.mem_protection, MemProtection::Mpu { regions: 16 });
616    }
617
618    #[test]
619    fn test_cortex_m7_has_fpu() {
620        let spec = TargetSpec::cortex_m7();
621        assert!(spec.has_fpu());
622        assert_eq!(spec.fpu, Some(FPUPrecision::Single));
623    }
624
625    // ========================================================================
626    // ISA feature gating tests
627    // ========================================================================
628
629    #[test]
630    fn test_cortex_m3_isa_features() {
631        let m3 = CortexMVariant::M3;
632        assert!(!m3.has_dsp(), "M3 should not have DSP");
633        assert!(!m3.has_single_precision_fpu(), "M3 should not have FPU");
634        assert!(
635            !m3.has_double_precision_fpu(),
636            "M3 should not have double FPU"
637        );
638        assert!(!m3.has_trustzone(), "M3 should not have TrustZone");
639        assert!(!m3.has_helium(), "M3 should not have Helium");
640    }
641
642    #[test]
643    fn test_cortex_m4_isa_features() {
644        let m4 = CortexMVariant::M4;
645        assert!(m4.has_dsp(), "M4 should have DSP");
646        assert!(
647            !m4.has_single_precision_fpu(),
648            "M4 (no F) should not have FPU"
649        );
650        assert!(
651            !m4.has_double_precision_fpu(),
652            "M4 should not have double FPU"
653        );
654    }
655
656    #[test]
657    fn test_cortex_m4f_isa_features() {
658        let m4f = CortexMVariant::M4F;
659        assert!(m4f.has_dsp(), "M4F should have DSP");
660        assert!(
661            m4f.has_single_precision_fpu(),
662            "M4F should have single-precision FPU"
663        );
664        assert!(
665            !m4f.has_double_precision_fpu(),
666            "M4F should not have double FPU"
667        );
668    }
669
670    #[test]
671    fn test_cortex_m7dp_isa_features() {
672        let m7dp = CortexMVariant::M7DP;
673        assert!(m7dp.has_dsp(), "M7DP should have DSP");
674        assert!(
675            m7dp.has_single_precision_fpu(),
676            "M7DP should have single-precision FPU"
677        );
678        assert!(
679            m7dp.has_double_precision_fpu(),
680            "M7DP should have double-precision FPU"
681        );
682    }
683
684    #[test]
685    fn test_cortex_m33_isa_features() {
686        let m33 = CortexMVariant::M33;
687        assert!(m33.has_dsp(), "M33 should have DSP");
688        assert!(m33.has_trustzone(), "M33 should have TrustZone");
689        assert!(!m33.has_helium(), "M33 should not have Helium");
690    }
691
692    #[test]
693    fn test_cortex_m55_isa_features() {
694        let m55 = CortexMVariant::M55;
695        assert!(m55.has_dsp(), "M55 should have DSP");
696        assert!(
697            m55.has_single_precision_fpu(),
698            "M55 should have single-precision FPU"
699        );
700        assert!(m55.has_trustzone(), "M55 should have TrustZone");
701        assert!(m55.has_helium(), "M55 should have Helium");
702    }
703
704    #[test]
705    fn test_target_spec_fpu_precision_queries() {
706        let m3 = TargetSpec::cortex_m3();
707        assert!(!m3.has_single_precision_fpu(), "M3 spec has no FPU");
708        assert!(!m3.has_double_precision_fpu(), "M3 spec has no double FPU");
709
710        let m4f = TargetSpec::cortex_m4f();
711        assert!(m4f.has_single_precision_fpu(), "M4F spec has single FPU");
712        assert!(
713            !m4f.has_double_precision_fpu(),
714            "M4F spec has no double FPU"
715        );
716
717        let m7dp = TargetSpec::cortex_m7dp();
718        assert!(m7dp.has_single_precision_fpu(), "M7DP spec has single FPU");
719        assert!(m7dp.has_double_precision_fpu(), "M7DP spec has double FPU");
720    }
721
722    #[test]
723    fn test_cortex_m55_target_spec() {
724        let m55 = TargetSpec::cortex_m55();
725        assert_eq!(m55.family, ArchFamily::ArmCortexM);
726        assert_eq!(m55.triple, "thumbv8.1m.main-none-eabi");
727        assert!(m55.is_thumb2());
728        assert!(m55.has_fpu());
729        assert!(m55.has_single_precision_fpu());
730        assert_eq!(m55.mem_protection, MemProtection::Mpu { regions: 16 });
731    }
732
733    #[test]
734    fn test_cortex_m55_from_triple() {
735        let m55 = TargetSpec::from_triple("cortex-m55").unwrap();
736        assert_eq!(m55.triple, "thumbv8.1m.main-none-eabi");
737        assert!(m55.has_fpu());
738
739        let m55_triple = TargetSpec::from_triple("thumbv8.1m.main-none-eabi").unwrap();
740        assert_eq!(m55_triple.triple, "thumbv8.1m.main-none-eabi");
741    }
742
743    // ========================================================================
744    // M7 hardware constructor tests (PR #86 patch coverage)
745    // ========================================================================
746
747    #[test]
748    fn test_imxrt1062_capabilities() {
749        let caps = HardwareCapabilities::imxrt1062();
750        assert_eq!(caps.arch, TargetArch::ARMCortexM(CortexMVariant::M7));
751        assert!(caps.has_mpu, "i.MX RT1062 has an MPU");
752        assert_eq!(caps.mpu_regions, 16, "M7 parts have 16 MPU regions");
753        assert!(!caps.has_pmp, "ARM parts do not have PMP");
754        assert_eq!(caps.pmp_entries, 0);
755        assert!(caps.has_fpu, "i.MX RT1062 has FPU");
756        assert_eq!(
757            caps.fpu_precision,
758            Some(FPUPrecision::Single),
759            "i.MX RT1062 has single-precision FPU"
760        );
761        assert!(!caps.has_simd);
762        assert!(caps.simd_level.is_none());
763        assert!(caps.xip_capable);
764        assert_eq!(caps.flash_size, 8 * 1024 * 1024, "8MB QSPI typical");
765        assert_eq!(caps.ram_size, 1024 * 1024, "1MB OCRAM");
766    }
767
768    #[test]
769    fn test_stm32h743_capabilities() {
770        let caps = HardwareCapabilities::stm32h743();
771        assert_eq!(caps.arch, TargetArch::ARMCortexM(CortexMVariant::M7DP));
772        assert!(caps.has_mpu);
773        assert_eq!(caps.mpu_regions, 16, "STM32H743 has 16 MPU regions");
774        assert!(!caps.has_pmp);
775        assert!(caps.has_fpu);
776        assert_eq!(
777            caps.fpu_precision,
778            Some(FPUPrecision::Double),
779            "STM32H743 has double-precision FPU"
780        );
781        assert_eq!(caps.flash_size, 2 * 1024 * 1024, "2MB Flash");
782        assert_eq!(caps.ram_size, 1024 * 1024, "1MB RAM total");
783        assert!(caps.xip_capable);
784    }
785
786    #[test]
787    fn test_imxrt1062_arch_target_triple() {
788        // imxrt1062 uses the M7 variant, which maps to thumbv7em-none-eabihf.
789        let caps = HardwareCapabilities::imxrt1062();
790        assert_eq!(caps.arch.target_triple(), "thumbv7em-none-eabihf");
791        assert_eq!(caps.arch.cpu_name(), "cortex-m7");
792        assert!(caps.arch.has_hardware_fp());
793    }
794
795    #[test]
796    fn test_stm32h743_arch_target_triple() {
797        // stm32h743 uses M7DP — same triple as M7 but flagged as double-precision.
798        let caps = HardwareCapabilities::stm32h743();
799        assert_eq!(caps.arch.target_triple(), "thumbv7em-none-eabihf");
800        assert_eq!(caps.arch.cpu_name(), "cortex-m7");
801        assert!(caps.arch.has_hardware_fp());
802    }
803
804    #[test]
805    fn test_m7_hardware_capabilities_distinct_from_m4() {
806        // Regression: M7 hardware must report 16 regions, M4 must report 8.
807        // Previously the CLI's --hardware dispatch silently fell through to
808        // a wrong default — this guards the constructor selection.
809        let m4 = HardwareCapabilities::nrf52840();
810        let m7 = HardwareCapabilities::imxrt1062();
811        let m7dp = HardwareCapabilities::stm32h743();
812        assert_eq!(m4.mpu_regions, 8);
813        assert_eq!(m7.mpu_regions, 16);
814        assert_eq!(m7dp.mpu_regions, 16);
815        // M7DP must report Double, M7 must report Single, M4F must report Single.
816        assert_eq!(m4.fpu_precision, Some(FPUPrecision::Single));
817        assert_eq!(m7.fpu_precision, Some(FPUPrecision::Single));
818        assert_eq!(m7dp.fpu_precision, Some(FPUPrecision::Double));
819    }
820}