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}