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)
124            // Thumb-2 ORR with modified immediate: 0x00F00000
125            code.extend_from_slice(&[0x41, 0xF0, 0xF0, 0x61]); // ORR R1, R1, #0x00F00000
126
127            // STR R1, [R0]
128            code.extend_from_slice(&[0xC0, 0xF8, 0x00, 0x10]); // STR R1, [R0, #0]
129
130            // DSB (data synchronization barrier)
131            code.extend_from_slice(&[0xBF, 0xF3, 0x4F, 0x8F]); // DSB SY
132
133            // ISB (instruction synchronization barrier)
134            code.extend_from_slice(&[0xBF, 0xF3, 0x6F, 0x8F]); // ISB SY
135        }
136
137        // Initialize R11 with linear memory base address (0x20000000)
138        // This is used for WASM linear memory access: LDR Rd, [R11, Raddr]
139        // Thumb-2 MOVW R11, #0x0000 (low 16 bits)
140        // Encoding: 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
141        // For R11=0x0000: i=0, imm4=0, imm3=0, imm8=0
142        code.extend_from_slice(&[0x40, 0xF2, 0x00, 0x0B]); // MOVW R11, #0
143
144        // Thumb-2 MOVT R11, #0x2000 (high 16 bits)
145        // Encoding: 1111 0 i 10 1 1 0 0 imm4 | 0 imm3 Rd imm8
146        // For 0x2000: imm4=2, i=0, imm3=0, imm8=0
147        code.extend_from_slice(&[0xC2, 0xF2, 0x00, 0x0B]); // MOVT R11, #0x2000
148
149        // Initialize R10 with linear memory size in bytes (for memory.size instruction)
150        // memory.size does LSR R10, #16 to convert bytes to WASM pages (65536 bytes/page)
151        {
152            let lo16 = self.memory_size & 0xFFFF;
153            let hi16 = self.memory_size >> 16;
154
155            // Thumb-2 MOVW R10, #lo16
156            // Encoding: 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
157            // Rd = R10 = 0xA
158            let i_bit = (lo16 >> 11) & 1;
159            let imm4 = (lo16 >> 12) & 0xF;
160            let imm3 = (lo16 >> 8) & 0x7;
161            let imm8 = lo16 & 0xFF;
162            let hw1 = (0xF240 | (i_bit << 10) | imm4) as u16;
163            let hw2 = ((imm3 << 12) | (0xA << 8) | imm8) as u16;
164            code.extend_from_slice(&hw1.to_le_bytes());
165            code.extend_from_slice(&hw2.to_le_bytes());
166
167            // Thumb-2 MOVT R10, #hi16
168            let i_bit = (hi16 >> 11) & 1;
169            let imm4 = (hi16 >> 12) & 0xF;
170            let imm3 = (hi16 >> 8) & 0x7;
171            let imm8 = hi16 & 0xFF;
172            let hw1 = (0xF2C0 | (i_bit << 10) | imm4) as u16;
173            let hw2 = ((imm3 << 12) | (0xA << 8) | imm8) as u16;
174            code.extend_from_slice(&hw1.to_le_bytes());
175            code.extend_from_slice(&hw2.to_le_bytes());
176        }
177
178        // Load entry point into R0
179        // LDR r0, [pc, #offset] (load from Align(PC,4) + imm*4)
180        // From here: LDR(2), BLX(2), B(2), NOP(2), literal(4)
181        // PC = LDR_addr + 4; Align(PC,4) + 4 = literal address
182        code.extend_from_slice(&[0x01, 0x48]); // LDR r0, [pc, #4]
183
184        // Thumb encoding for: BLX r0
185        code.extend_from_slice(&[0x80, 0x47]); // BLX r0
186
187        // Thumb encoding for: B . (infinite loop)
188        code.extend_from_slice(&[0xfe, 0xe7]); // B . (branch to self)
189
190        // Padding to align literal pool (need 2 bytes to align to 4)
191        code.extend_from_slice(&[0x00, 0x00]); // NOP padding
192
193        // Literal pool: entry point address (with Thumb bit set)
194        code.extend_from_slice(&(self.entry_point | 1).to_le_bytes());
195
196        code
197    }
198
199    /// Generate a default exception handler (infinite loop)
200    pub fn generate_default_handler() -> Vec<u8> {
201        // Thumb encoding for: B . (infinite loop)
202        vec![0xfe, 0xe7]
203    }
204}
205
206/// Memory layout for Cortex-M target
207#[derive(Debug, Clone)]
208pub struct MemoryLayout {
209    /// Flash base address
210    pub flash_base: u32,
211    /// Flash size in bytes
212    pub flash_size: u32,
213    /// RAM base address
214    pub ram_base: u32,
215    /// RAM size in bytes
216    pub ram_size: u32,
217}
218
219impl MemoryLayout {
220    /// STM32F407 memory layout
221    pub fn stm32f407() -> Self {
222        Self {
223            flash_base: 0x0800_0000,
224            flash_size: 1024 * 1024, // 1MB
225            ram_base: 0x2000_0000,
226            ram_size: 128 * 1024, // 128KB (main SRAM)
227        }
228    }
229
230    /// nRF52840 memory layout
231    pub fn nrf52840() -> Self {
232        Self {
233            flash_base: 0x0000_0000,
234            flash_size: 1024 * 1024, // 1MB
235            ram_base: 0x2000_0000,
236            ram_size: 256 * 1024, // 256KB
237        }
238    }
239
240    /// Generic Cortex-M layout (suitable for QEMU/Renode testing)
241    pub fn generic() -> Self {
242        Self {
243            flash_base: 0x0000_0000,
244            flash_size: 256 * 1024, // 256KB
245            ram_base: 0x2000_0000,
246            ram_size: 64 * 1024, // 64KB
247        }
248    }
249
250    /// Get stack top address (end of RAM)
251    pub fn stack_top(&self) -> u32 {
252        self.ram_base + self.ram_size
253    }
254}
255
256/// Build a complete Cortex-M binary with vector table and startup code
257pub struct CortexMBuilder {
258    layout: MemoryLayout,
259    code: Vec<u8>,
260    entry_name: String,
261}
262
263impl CortexMBuilder {
264    /// Create a new Cortex-M builder with the given memory layout
265    pub fn new(layout: MemoryLayout) -> Self {
266        Self {
267            layout,
268            code: Vec::new(),
269            entry_name: "main".to_string(),
270        }
271    }
272
273    /// Set the function code
274    pub fn with_code(mut self, code: Vec<u8>) -> Self {
275        self.code = code;
276        self
277    }
278
279    /// Set the entry point name
280    pub fn with_entry_name(mut self, name: &str) -> Self {
281        self.entry_name = name.to_string();
282        self
283    }
284
285    /// Build complete flash image with vector table + startup + code
286    ///
287    /// Returns (flash_image, entry_point_address)
288    pub fn build(&self) -> (Vec<u8>, u32) {
289        let mut image = Vec::new();
290
291        // Calculate addresses
292        let vector_table_size = 64; // 16 entries * 4 bytes
293        let startup_offset = vector_table_size;
294
295        // Generate startup code first to know its size
296        let startup = StartupCode::minimal(0); // Placeholder entry
297        let startup_code = startup.generate_thumb();
298        let startup_size = startup_code.len() as u32;
299
300        // Default handler comes after startup
301        let default_handler_offset = startup_offset + startup_size as usize;
302        let default_handler = StartupCode::generate_default_handler();
303        let default_handler_size = default_handler.len() as u32;
304
305        // User code comes after default handler, aligned to 4 bytes
306        let code_offset = (default_handler_offset + default_handler_size as usize).div_ceil(4) * 4;
307        let code_addr = self.layout.flash_base + code_offset as u32;
308
309        // Now regenerate startup with correct entry point
310        let startup = StartupCode::minimal(code_addr);
311        let startup_code = startup.generate_thumb();
312
313        // Generate vector table
314        let reset_handler_addr = self.layout.flash_base + startup_offset as u32;
315        let default_handler_addr = self.layout.flash_base + default_handler_offset as u32;
316
317        let vector_table = VectorTable::minimal(self.layout.stack_top(), reset_handler_addr);
318        let vector_table_data = vector_table.generate(default_handler_addr);
319
320        // Build the image
321        image.extend_from_slice(&vector_table_data);
322        image.extend_from_slice(&startup_code);
323        image.extend_from_slice(&default_handler);
324
325        // Pad to code offset
326        while image.len() < code_offset {
327            image.push(0);
328        }
329
330        // Add user code
331        image.extend_from_slice(&self.code);
332
333        (image, code_addr)
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340
341    #[test]
342    fn test_vector_table_generation() {
343        let vt = VectorTable::minimal(0x2001_0000, 0x0800_0040);
344        let data = vt.generate(0x0800_0080);
345
346        // Check size (16 entries * 4 bytes)
347        assert_eq!(data.len(), 64);
348
349        // Check initial SP
350        let sp = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
351        assert_eq!(sp, 0x2001_0000);
352
353        // Check reset handler (with Thumb bit)
354        let reset = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
355        assert_eq!(reset, 0x0800_0041); // Original | 1
356    }
357
358    #[test]
359    fn test_startup_code_generation() {
360        let startup = StartupCode::minimal(0x0800_0100);
361        let code = startup.generate_thumb();
362
363        // Should generate some code
364        assert!(!code.is_empty());
365
366        // Should be even length (Thumb alignment)
367        assert_eq!(code.len() % 2, 0);
368
369        // Should contain the entry point in the literal pool
370        let entry_bytes = &code[code.len() - 4..];
371        let entry = u32::from_le_bytes([
372            entry_bytes[0],
373            entry_bytes[1],
374            entry_bytes[2],
375            entry_bytes[3],
376        ]);
377        assert_eq!(entry, 0x0800_0101); // With Thumb bit
378    }
379
380    #[test]
381    fn test_memory_layout() {
382        let stm32 = MemoryLayout::stm32f407();
383        assert_eq!(stm32.flash_base, 0x0800_0000);
384        assert_eq!(stm32.stack_top(), 0x2002_0000); // 128KB RAM
385
386        let nrf = MemoryLayout::nrf52840();
387        assert_eq!(nrf.flash_base, 0x0000_0000);
388        assert_eq!(nrf.stack_top(), 0x2004_0000); // 256KB RAM
389    }
390
391    #[test]
392    fn test_cortex_m_builder() {
393        let layout = MemoryLayout::generic();
394
395        // Simple function that returns 42
396        // Thumb: MOV r0, #42; BX lr
397        let code = vec![
398            0x2a, 0x20, // MOV r0, #42
399            0x70, 0x47, // BX lr
400        ];
401
402        let builder = CortexMBuilder::new(layout).with_code(code);
403        let (image, entry) = builder.build();
404
405        // Image should start with vector table
406        assert!(image.len() >= 64);
407
408        // First word should be stack pointer (end of RAM)
409        let sp = u32::from_le_bytes([image[0], image[1], image[2], image[3]]);
410        assert_eq!(sp, 0x2001_0000); // 64KB RAM
411
412        // Entry point should be after startup code
413        assert!(entry > 64);
414    }
415}