Skip to main content

synth_backend/
cortex_m.rs

1//! Cortex-M specific code generation
2//!
3//! Generates vector tables, startup code, and runtime support for ARM Cortex-M targets.
4
5/// Cortex-M vector table generator
6pub struct VectorTable {
7    /// Initial stack pointer value
8    pub initial_sp: u32,
9    /// Reset handler address
10    pub reset_handler: u32,
11    /// NMI handler address (optional, defaults to infinite loop)
12    pub nmi_handler: Option<u32>,
13    /// HardFault handler address (optional, defaults to infinite loop)
14    pub hardfault_handler: Option<u32>,
15}
16
17impl VectorTable {
18    /// Create a minimal vector table with just SP and reset handler
19    pub fn minimal(initial_sp: u32, reset_handler: u32) -> Self {
20        Self {
21            initial_sp,
22            reset_handler,
23            nmi_handler: None,
24            hardfault_handler: None,
25        }
26    }
27
28    /// Generate the vector table as bytes
29    ///
30    /// Returns a 16-entry vector table (64 bytes) suitable for basic Cortex-M operation
31    pub fn generate(&self, default_handler: u32) -> Vec<u8> {
32        let mut table = Vec::with_capacity(64);
33
34        // Entry 0: Initial SP
35        table.extend_from_slice(&self.initial_sp.to_le_bytes());
36
37        // Entry 1: Reset handler (must have bit 0 set for Thumb mode)
38        table.extend_from_slice(&(self.reset_handler | 1).to_le_bytes());
39
40        // Entry 2: NMI handler
41        let nmi = self.nmi_handler.unwrap_or(default_handler) | 1;
42        table.extend_from_slice(&nmi.to_le_bytes());
43
44        // Entry 3: HardFault handler
45        let hardfault = self.hardfault_handler.unwrap_or(default_handler) | 1;
46        table.extend_from_slice(&hardfault.to_le_bytes());
47
48        // Entries 4-15: MemManage, BusFault, UsageFault, Reserved, SVCall, etc.
49        for _ in 4..16 {
50            table.extend_from_slice(&(default_handler | 1).to_le_bytes());
51        }
52
53        table
54    }
55}
56
57/// Cortex-M startup code generator
58pub struct StartupCode {
59    /// Stack top address
60    pub stack_top: u32,
61    /// Entry point (main function)
62    pub entry_point: u32,
63    /// Data section start (in RAM)
64    pub data_start: u32,
65    /// Data section end (in RAM)
66    pub data_end: u32,
67    /// Data load address (in flash)
68    pub data_load: u32,
69    /// BSS section start
70    pub bss_start: u32,
71    /// BSS section end
72    pub bss_end: u32,
73    /// Enable FPU (set CPACR for CP10+CP11 full access)
74    pub enable_fpu: bool,
75    /// Linear memory size in bytes (stored in R10 for memory.size)
76    pub memory_size: u32,
77}
78
79impl StartupCode {
80    /// Create minimal startup that just jumps to entry point
81    pub fn minimal(entry_point: u32) -> Self {
82        Self {
83            stack_top: 0x2000_0000 + 64 * 1024, // 64KB RAM, stack at top
84            entry_point,
85            data_start: 0,
86            data_end: 0,
87            data_load: 0,
88            bss_start: 0,
89            bss_end: 0,
90            enable_fpu: false,
91            memory_size: 64 * 1024, // Default 64KB linear memory
92        }
93    }
94
95    /// Generate Thumb-2 startup code (reset handler)
96    ///
97    /// This generates minimal startup code that:
98    /// 1. Optionally enables FPU (CPACR setup for CP10+CP11)
99    /// 2. Sets up the stack pointer (already done by hardware from vector table)
100    /// 3. Initializes R11 as linear memory base (0x20000000)
101    /// 4. Calls the entry point
102    /// 5. Loops forever if entry returns
103    pub fn generate_thumb(&self) -> Vec<u8> {
104        let mut code = Vec::new();
105
106        // Reset handler entry point
107        // The stack pointer is already set by hardware from vector table[0]
108
109        // Enable FPU: set CP10+CP11 to full access in SCB->CPACR (0xE000ED88)
110        if self.enable_fpu {
111            // MOVW R0, #0xED88 (low 16 bits of CPACR address)
112            // Thumb-2 MOVW encoding: 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
113            // 0xED88: imm4=0xE, i=1, imm3=0b101, imm8=0x88
114            code.extend_from_slice(&[0x4E, 0xF6, 0x88, 0x50]); // MOVW R0, #0xED88
115
116            // MOVT R0, #0xE000 (high 16 bits of CPACR address)
117            // 0xE000: imm4=0xE, i=0, imm3=0, imm8=0
118            code.extend_from_slice(&[0xCE, 0xF2, 0x00, 0x00]); // MOVT R0, #0xE000
119
120            // LDR R1, [R0]
121            code.extend_from_slice(&[0xD0, 0xF8, 0x00, 0x10]); // LDR R1, [R0, #0]
122
123            // ORR R1, R1, #0x00F00000 (enable CP10+CP11 full access). GI-FPU-002:
124            // the previous bytes [0x41,0xF0,0xF0,0x61] decode to #0x07800000 (a
125            // wrong mask that does NOT enable CP10/CP11); the correct Thumb-2
126            // modified-immediate encoding of #0x00F00000 is [0x41,0xF4,0x70,0x01].
127            code.extend_from_slice(&[0x41, 0xF4, 0x70, 0x01]); // ORR R1, R1, #0x00F00000
128
129            // STR R1, [R0]
130            code.extend_from_slice(&[0xC0, 0xF8, 0x00, 0x10]); // STR R1, [R0, #0]
131
132            // DSB (data synchronization barrier)
133            code.extend_from_slice(&[0xBF, 0xF3, 0x4F, 0x8F]); // DSB SY
134
135            // ISB (instruction synchronization barrier)
136            code.extend_from_slice(&[0xBF, 0xF3, 0x6F, 0x8F]); // ISB SY
137        }
138
139        // Initialize R11 with linear memory base address (0x20000000)
140        // This is used for WASM linear memory access: LDR Rd, [R11, Raddr]
141        // Thumb-2 MOVW R11, #0x0000 (low 16 bits)
142        // Encoding: 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
143        // For R11=0x0000: i=0, imm4=0, imm3=0, imm8=0
144        code.extend_from_slice(&[0x40, 0xF2, 0x00, 0x0B]); // MOVW R11, #0
145
146        // Thumb-2 MOVT R11, #0x2000 (high 16 bits)
147        // Encoding: 1111 0 i 10 1 1 0 0 imm4 | 0 imm3 Rd imm8
148        // For 0x2000: imm4=2, i=0, imm3=0, imm8=0
149        code.extend_from_slice(&[0xC2, 0xF2, 0x00, 0x0B]); // MOVT R11, #0x2000
150
151        // Initialize R10 with linear memory size in bytes (for memory.size instruction)
152        // memory.size does LSR R10, #16 to convert bytes to WASM pages (65536 bytes/page)
153        {
154            let lo16 = self.memory_size & 0xFFFF;
155            let hi16 = self.memory_size >> 16;
156
157            // Thumb-2 MOVW R10, #lo16
158            // Encoding: 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
159            // Rd = R10 = 0xA
160            let i_bit = (lo16 >> 11) & 1;
161            let imm4 = (lo16 >> 12) & 0xF;
162            let imm3 = (lo16 >> 8) & 0x7;
163            let imm8 = lo16 & 0xFF;
164            let hw1 = (0xF240 | (i_bit << 10) | imm4) as u16;
165            let hw2 = ((imm3 << 12) | (0xA << 8) | imm8) as u16;
166            code.extend_from_slice(&hw1.to_le_bytes());
167            code.extend_from_slice(&hw2.to_le_bytes());
168
169            // Thumb-2 MOVT R10, #hi16
170            let i_bit = (hi16 >> 11) & 1;
171            let imm4 = (hi16 >> 12) & 0xF;
172            let imm3 = (hi16 >> 8) & 0x7;
173            let imm8 = hi16 & 0xFF;
174            let hw1 = (0xF2C0 | (i_bit << 10) | imm4) as u16;
175            let hw2 = ((imm3 << 12) | (0xA << 8) | imm8) as u16;
176            code.extend_from_slice(&hw1.to_le_bytes());
177            code.extend_from_slice(&hw2.to_le_bytes());
178        }
179
180        // Load entry point into R0
181        // LDR r0, [pc, #offset] (load from Align(PC,4) + imm*4)
182        // From here: LDR(2), BLX(2), B(2), NOP(2), literal(4)
183        // PC = LDR_addr + 4; Align(PC,4) + 4 = literal address
184        code.extend_from_slice(&[0x01, 0x48]); // LDR r0, [pc, #4]
185
186        // Thumb encoding for: BLX r0
187        code.extend_from_slice(&[0x80, 0x47]); // BLX r0
188
189        // Thumb encoding for: B . (infinite loop)
190        code.extend_from_slice(&[0xfe, 0xe7]); // B . (branch to self)
191
192        // Padding to align literal pool (need 2 bytes to align to 4)
193        code.extend_from_slice(&[0x00, 0x00]); // NOP padding
194
195        // Literal pool: entry point address (with Thumb bit set)
196        code.extend_from_slice(&(self.entry_point | 1).to_le_bytes());
197
198        code
199    }
200
201    /// Generate a default exception handler (infinite loop)
202    pub fn generate_default_handler() -> Vec<u8> {
203        // Thumb encoding for: B . (infinite loop)
204        vec![0xfe, 0xe7]
205    }
206}
207
208/// Memory layout for Cortex-M target
209#[derive(Debug, Clone)]
210pub struct MemoryLayout {
211    /// Flash base address
212    pub flash_base: u32,
213    /// Flash size in bytes
214    pub flash_size: u32,
215    /// RAM base address
216    pub ram_base: u32,
217    /// RAM size in bytes
218    pub ram_size: u32,
219}
220
221impl MemoryLayout {
222    /// STM32F407 memory layout
223    pub fn stm32f407() -> Self {
224        Self {
225            flash_base: 0x0800_0000,
226            flash_size: 1024 * 1024, // 1MB
227            ram_base: 0x2000_0000,
228            ram_size: 128 * 1024, // 128KB (main SRAM)
229        }
230    }
231
232    /// nRF52840 memory layout
233    pub fn nrf52840() -> Self {
234        Self {
235            flash_base: 0x0000_0000,
236            flash_size: 1024 * 1024, // 1MB
237            ram_base: 0x2000_0000,
238            ram_size: 256 * 1024, // 256KB
239        }
240    }
241
242    /// Generic Cortex-M layout (suitable for QEMU/Renode testing)
243    pub fn generic() -> Self {
244        Self {
245            flash_base: 0x0000_0000,
246            flash_size: 256 * 1024, // 256KB
247            ram_base: 0x2000_0000,
248            ram_size: 64 * 1024, // 64KB
249        }
250    }
251
252    /// Get stack top address (end of RAM)
253    pub fn stack_top(&self) -> u32 {
254        self.ram_base + self.ram_size
255    }
256}
257
258/// Build a complete Cortex-M binary with vector table and startup code
259pub struct CortexMBuilder {
260    layout: MemoryLayout,
261    code: Vec<u8>,
262    entry_name: String,
263}
264
265impl CortexMBuilder {
266    /// Create a new Cortex-M builder with the given memory layout
267    pub fn new(layout: MemoryLayout) -> Self {
268        Self {
269            layout,
270            code: Vec::new(),
271            entry_name: "main".to_string(),
272        }
273    }
274
275    /// Set the function code
276    pub fn with_code(mut self, code: Vec<u8>) -> Self {
277        self.code = code;
278        self
279    }
280
281    /// Set the entry point name
282    pub fn with_entry_name(mut self, name: &str) -> Self {
283        self.entry_name = name.to_string();
284        self
285    }
286
287    /// Build complete flash image with vector table + startup + code
288    ///
289    /// Returns (flash_image, entry_point_address)
290    pub fn build(&self) -> (Vec<u8>, u32) {
291        let mut image = Vec::new();
292
293        // Calculate addresses
294        let vector_table_size = 64; // 16 entries * 4 bytes
295        let startup_offset = vector_table_size;
296
297        // Generate startup code first to know its size
298        let startup = StartupCode::minimal(0); // Placeholder entry
299        let startup_code = startup.generate_thumb();
300        let startup_size = startup_code.len() as u32;
301
302        // Default handler comes after startup
303        let default_handler_offset = startup_offset + startup_size as usize;
304        let default_handler = StartupCode::generate_default_handler();
305        let default_handler_size = default_handler.len() as u32;
306
307        // User code comes after default handler, aligned to 4 bytes
308        let code_offset = (default_handler_offset + default_handler_size as usize).div_ceil(4) * 4;
309        let code_addr = self.layout.flash_base + code_offset as u32;
310
311        // Now regenerate startup with correct entry point
312        let startup = StartupCode::minimal(code_addr);
313        let startup_code = startup.generate_thumb();
314
315        // Generate vector table
316        let reset_handler_addr = self.layout.flash_base + startup_offset as u32;
317        let default_handler_addr = self.layout.flash_base + default_handler_offset as u32;
318
319        let vector_table = VectorTable::minimal(self.layout.stack_top(), reset_handler_addr);
320        let vector_table_data = vector_table.generate(default_handler_addr);
321
322        // Build the image
323        image.extend_from_slice(&vector_table_data);
324        image.extend_from_slice(&startup_code);
325        image.extend_from_slice(&default_handler);
326
327        // Pad to code offset
328        while image.len() < code_offset {
329            image.push(0);
330        }
331
332        // Add user code
333        image.extend_from_slice(&self.code);
334
335        (image, code_addr)
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn test_vector_table_generation() {
345        let vt = VectorTable::minimal(0x2001_0000, 0x0800_0040);
346        let data = vt.generate(0x0800_0080);
347
348        // Check size (16 entries * 4 bytes)
349        assert_eq!(data.len(), 64);
350
351        // Check initial SP
352        let sp = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
353        assert_eq!(sp, 0x2001_0000);
354
355        // Check reset handler (with Thumb bit)
356        let reset = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
357        assert_eq!(reset, 0x0800_0041); // Original | 1
358    }
359
360    #[test]
361    fn test_startup_code_generation() {
362        let startup = StartupCode::minimal(0x0800_0100);
363        let code = startup.generate_thumb();
364
365        // Should generate some code
366        assert!(!code.is_empty());
367
368        // Should be even length (Thumb alignment)
369        assert_eq!(code.len() % 2, 0);
370
371        // Should contain the entry point in the literal pool
372        let entry_bytes = &code[code.len() - 4..];
373        let entry = u32::from_le_bytes([
374            entry_bytes[0],
375            entry_bytes[1],
376            entry_bytes[2],
377            entry_bytes[3],
378        ]);
379        assert_eq!(entry, 0x0800_0101); // With Thumb bit
380    }
381
382    #[test]
383    fn test_memory_layout() {
384        let stm32 = MemoryLayout::stm32f407();
385        assert_eq!(stm32.flash_base, 0x0800_0000);
386        assert_eq!(stm32.stack_top(), 0x2002_0000); // 128KB RAM
387
388        let nrf = MemoryLayout::nrf52840();
389        assert_eq!(nrf.flash_base, 0x0000_0000);
390        assert_eq!(nrf.stack_top(), 0x2004_0000); // 256KB RAM
391    }
392
393    #[test]
394    fn test_cortex_m_builder() {
395        let layout = MemoryLayout::generic();
396
397        // Simple function that returns 42
398        // Thumb: MOV r0, #42; BX lr
399        let code = vec![
400            0x2a, 0x20, // MOV r0, #42
401            0x70, 0x47, // BX lr
402        ];
403
404        let builder = CortexMBuilder::new(layout).with_code(code);
405        let (image, entry) = builder.build();
406
407        // Image should start with vector table
408        assert!(image.len() >= 64);
409
410        // First word should be stack pointer (end of RAM)
411        let sp = u32::from_le_bytes([image[0], image[1], image[2], image[3]]);
412        assert_eq!(sp, 0x2001_0000); // 64KB RAM
413
414        // Entry point should be after startup code
415        assert!(entry > 64);
416    }
417}