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}