Cortex M0+ compile flow
- Details
- Last Updated: Saturday, 28 March 2020 18:35
- Published: Saturday, 28 March 2020 18:35
- Hits: 679
---------------
Cortex M0-plus (CM0+):
---------------
Here, we use only minimal required C code files from ARM.
steps:
1. make: make 32 bit bin file from c code: only these files needed:
*.c: test1.c, main.c, boot.c, IRQService.c, export.s
*.h: address_def.h, defines.h
*.txt: scatter.txt
2. In irun, load this bin file in rom memory, and start sim
make:
---
make -f /db/.../Makefile all
Makefile:
-------
CCFLAGS = --cpu=Cortex-M0 -c --asm --interleave --apcs=interwork -Otime -D RAM_ABSOLUTE_BASE_ADDR -D main=__main -g
LDFLAGS = --cpu=Cortex-M0 --map --no_debug --datacompressor=off --map --symbols --info=inline
IKDEPS = /sim/.../Makefile
all: clear test1_32.dat clean
############ rm files at start
clear:
rm -rf *.o *.sym *.list *.bin *.elf *.sec *.dis *.map *.ihex *.obj *.log *.inc *.dat
###########start compilation .dat<=.ihex<=.elf<=.o<=.c
test1_32.dat : test1.ihex
@echo " *** building $@ ***" => building test1_32.dat
ihex2dat32 -input $< -output $@ => o/p in 32 bit mem mage file
test1.ihex : test1.elf
fromelf --i32 $< --output $@
test1.elf : boot.o IRQService.o init.o export.o test1.o
armlink -o $@ boot.o IRQService.o init.o export.o test1.o $(LDFLAGS) --scatter scatter.txt
test1.o: test1.c $(IKDEPS)
armcc $(CCFLAGS) -c -o $@ $<
#get .o files for boot.c, IRQService.c, init.c, export.s
boot.o: boot.c $(IKDEPS) => similarly for IRQService.c, init.c
armcc $(CCFLAGS) -c -o $@ $<
IRQService.o: ...
init.o: ....
export.o: export.s $(IKDEPS) => export.c isn't there, use export.s
armasm --cpu=Cortex-M0 --keep -o $@ $<
##### remove files at end
clean:
rm -rf *.obj
------------------------
output of running this:
#clean
rm -rf *.o *.sym *.list *.bin *.elf *.sec *.dis *.map *.ihex *.obj *.log *.inc *.dat
#compile files
armcc --cpu=Cortex-M0 -c --asm --interleave --apcs=interwork -Otime -D RAM_ABSOLUTE_BASE_ADDR -D main=__main -g -c -o boot.o /sim/.../boot.c
armcc ... IRQService.c
armcc ... init.c
armasm --cpu=Cortex-M0 --keep -o export.o /sim/.../export.s
armcc ... test1.c
armlink -o test1.elf boot.o IRQService.o export.o init.o basic.o --cpu=Cortex-M0 --map --no_debug --datacompressor=off --map --symbols --info=inline --scatter /sim/.../scatter.txt => Here armlink is using --scatter which causes it to use scatter.txt for ro-base, rw-base, zi-base. For sporsho, we used --ro-base and --rw-base on armlink cmdline instead of using scatter file.
#show inline results => symbol table, memory map,
Image Symbol Table ....
Memory Map of the image ....
#generate .elf and .dat
fromelf --i32 test1.elf --output test1.ihex
ihex2dat32 -input test1.ihex -output test1_32.dat => final *.dat file generated
#clean
rm -rf *.obj
-------------------------------------------
#contents of various files
###### boot.c ######
typedef void (*intr_handler)(void);
extern void IRQService (void); // Default interrupt handler
void BaseIRQService0 (void); //similarly for IRQ0 to IRQ31
intr_handler IRQService0 = IRQService;
//define func for all intr handlers, either as C/asm code or via calling other func.
void Reset_Handler (void) {
init (); //this calls init func, defined in init.c
}
void BaseIRQService0 (void) { //irq0.
IRQService0 (); //this ultimately calls IRQService
}
typedef const int * vect_t; //vect_t is a ptr to type "const int"
const int * __Vectors[] __attribute__ ((section("vectors"))) = { //this matches contents of .dat binary file below
(vect_t) (&USR_STACK), //addr=0x0000, stores MSP value, here it stores 0x20046F00
(vect_t) Reset_Handler, //addr=0x0004
(vect_t) NMI_Handler, //addr=0x0008
(vect_t) HardFault_Handler,//addr=0x000C
(vect_t) 0xFFFFFFFC, //addr=0x0010
(vect_t) 0xFFFFFFFC, //addr=0x0014
(vect_t) 0xFFFFFFFC, //addr=0x0018
(vect_t) 0xFFFFFFFC, //addr=0x001C
(vect_t) 0xFFFFFFFC, //addr=0x0020
(vect_t) 0xFFFFFFFC, //addr=0x0024
(vect_t) 0xFFFFFFFC, //addr=0x0028
(vect_t) SVC_Handler, //addr=0x002C, stores addr of SVC intr handler, which in this case is 0x00000115
(vect_t) 0xFFFFFFFC, //addr=0x0030
(vect_t) 0xFFFFFFFC, //addr=0x0034
(vect_t) PendSV_Handler, //addr=0x0038, stores addr of Pend intr handler, which in this case is 0x00000127
(vect_t) SysTick_Handler, //addr=0x003C, stores addr of systick intr handler, which in this case is 0x00000139
(vect_t) BaseIRQService0, // similarly for IRQ0 to IRQ31, addr=0x0040 to 0x00BC (addr 0x00C0 is the first addr where this vector table ends). stores addr starting from 0x00000141 to 0x000001FB, each IRQ is 6 bytes.
//(vect_t) IRQService, //we could directly point to IRQService routine. That's what we used for compiled code below
}
asm code for boot
----------
AREA ||.text||, CODE, READONLY, ALIGN=2 => this is text section
Reset_Handler PROC
000000 b510 PUSH {r4,lr}
000002 f7fffffe BL init => jumps to init, here target addr is not yet finalized, so we see jump inst as f7ff_fffe. Once init addr is know in final linking of *.o files, this BL changes coding to f827_f000 (as seen in *.dat file below) which implies target_msb[10:0]=000, target_lsb[10:0]=027<<1 => pc=pc+(027<<1)=0c6+04e=0x114 (init section starts from 0x114, so it's correct). (LR stored with next PC=06=0xc6, so return from main() comes right here)
000006 bd10 POP {r4,pc}
ENDP
similarly for other proc handlers
NMI_Handler PROC
000008 4805 LDR r0,|L1.32| => Load pc+|L1.32| into r0 reg. (4805=>reg=r0,label=05<<2=0x14(dec=20),pc+0x14=0x0a+0x14=0x1e. Note that label=0x05 is resolved here itself, since the label is within this file, so compiler knows how much to add to goto that label.
00000a 6800 LDR r0,[r0,#0] ; NMIService, r0=
00000c 4700 BX r0 => pc=r0=
ENDP
HardFault_Handler PROC
|L1.14|
00000e bf00 NOP
000010 e7fd B |L1.14| => while (1) { __nop (); e7fd => offset=0x7fd=-3, so pc=pc-3=0x012-0x003=0x00e (prev inst)
ENDP
SVC_Handler PROC
|L1.18|
000012 bf00 NOP
000014 e7fd B |L1.18| => while (1) { __nop ();
ENDP
PendSV_Handler PROC
|L1.22|
000016 bf00 NOP
000018 e7fd B |L1.22|
ENDP
SysTick_Handler PROC
|L1.26|
00001a bf00 NOP
00001c e7fd B |L1.26|
ENDP
00001e 0000 DCW 0x0000
|L1.32| => NMIService is here
DCD ||.data||
##data section for boot.c starts from here
AREA ||.data||, DATA, ALIGN=2 => area named .data
NMIService
DCD IRQService
AREA ||area_number.5||, DATA, ALIGN=2 => area named .area_number.5
EXPORTAS ||area_number.5||, ||.data||
IRQService0
DCD IRQService
AREA vectors, DATA, ALIGN=2 => area named vectors
__Vectors
DCD 0x20046f00
DCD Reset_Handler
DCD NMI_Handler
DCD HardFault_Handler
DCD 0xfffffffc
DCD 0xfffffffc
DCD 0xfffffffc
DCD 0xfffffffc
DCD 0xfffffffc
DCD 0xfffffffc
DCD 0xfffffffc
DCD SVC_Handler
DCD 0xfffffffc
DCD 0xfffffffc
DCD PendSV_Handler
DCD SysTick_Handler
DCD IRQService
....
DCD IRQService (32 of these)
---------------
######## init.c ###########
void init (void) {
main(); //this calls "main" func in user code, defined in test1.c
}
asm code for init:
----------
AREA ||.text||, CODE, READONLY, ALIGN=1
init PROC
000000 b510 PUSH {r4,lr}
000002 f7fffffe BL __main => BL is 32 bit inst (LR stored with next PC=06=0x11a, so return from main() comes right here)
000006 bd10 POP {r4,pc}
ENDP
----------
######## IRQService.c ###########
void IRQService (void) { //default IRQ, doesn't do anything
(*(volatile uint32 *)(TB_RSVD_ADDR + 0x0F98))++; //FAIL_LOC++
(*(volatile uint32 *)(TB_RSVD_ADDR + 0x0FEC)) = 0xD0D0D0D0; //DISPLAY(0xD0D0D0D0)
(*(volatile uint32 *)(TB_RSVD_ADDR + 0x0F90)) = 0xB000B000; //FAIL; //writes to some reserved mem section to indicate to verilog tb that it's in IRQ
(*(volatile uint32 *)(TB_RSVD_ADDR + 0x0FAC)) = 0x50; __nop(); (*(volatile uint32 *)(TB_RSVD_ADDR + 0x0FAC)) = 0x11; //EXIT; wrt to BOOTCODE_ADDR
return; //just returns back
}
asm code:
-------
000000 4807 LDR r0,|L1.32|
000002 6981 LDR r1,[r0,#0x18] => FAIL_LOC++;
000004 1c49 ADDS r1,r1,#1
000006 6181 STR r1,[r0,#0x18]
000008 4a07 LDR r2,|L1.40| => DISPLAY(0xD0D0D0D0);
00000a 4906 LDR r1,|L1.36|
00000c 62d1 STR r1,[r2,#0x2c]
00000e 4907 LDR r1,|L1.44| => FAIL
000010 6101 STR r1,[r0,#0x10]
000012 2150 MOVS r1,#0x50 => EXIT
000014 62c1 STR r1,[r0,#0x2c]
000016 bf00 NOP
000018 2111 MOVS r1,#0x11
00001a 62c1 STR r1,[r0,#0x2c]
00001c 4770 BX lr => return
00001e 0000 DCW 0x0000
|L1.32|
DCD 0x20046f80
|L1.36|
DCD 0xd0d0d0d0
|L1.40|
DCD 0x20046fc0
|L1.44|
DCD 0xb000b000
------
######## test1.c ###########
int main (void) {
int i=0;
while(i<1) i=i+1;
return(0);
}
asm code:
---------
000000 2000 MOVS r0,#0 => r0=0
|L1.2|
000002 1c40 ADDS r0,r0,#1 => while(i<1) i=i+1;
000004 2801 CMP r0,#1
000006 dbfc BLT |L1.2|
000008 2000 MOVS r0,#0 => return(0)
00000a 4770 BX lr
ENDP
-----------
######## export.s ###########
IMPORT ||Image$$ROM_EXEC2$$RO$$Limit|| => ROM_EXEC2 region (in scatter.txt below)
IMPORT ||Image$$RAM_EXEC1$$RW$$Base|| => RAM_EXEC1 region
IMPORT ||Image$$RAM_EXEC1$$RW$$Length||
IMPORT ||Image$$RAM_EXEC2$$ZI$$Base|| => RAM_EXEC2 region
IMPORT ||Image$$RAM_EXEC2$$ZI$$Length||
END
######## scatter.txt #########
scatter file has 1 or more load region for diff obj *.o files. Each load region has RO and RW/ZI regions. The addr and placement of these regions is specified in
ROM_LOAD 0x0 {
ROM_EXEC1 0x0 { => start from addr=0x0000
boot.o (vectors, +First) => boot.c "vectors" sction is put here (RO)
* (InRoot$$Sections)
}
ROM_EXEC2 0xc0 { => addr=0xC0 is where the vector section has ended. We put all other code starting here (RO)
*.o
}
OTP_EXEC3 +0 {
*.o (.sec_text)
}
RAM_EXEC1 0x00043000 { //let's say this has 148 bytes (=0x94 bytes)
*.o (.data,.RW)
}
RAM_EXEC2 +0 { //then starting addr for this is 0x00043094
*.o (.bss)
}
}
#contents of .dat (32 bit):
Section 1 => ROM_EXEC1 (192 bytes)
---------
@00 20046F00 000000C1 000000C9 000000CF => addr 0(msp) loaded with 2004_6f00. Reset=0xC0/NMI=0xC8/HardFault=0xCE
@10 FFFFFFFC FFFFFFFC FFFFFFFC FFFFFFFC
@20 FFFFFFFC FFFFFFFC FFFFFFFC 000000D3 => 0xD2=SVC handler
@30 FFFFFFFC FFFFFFFC 000000D7 000000DB => 0xD6=PendSV, 0xDA=SysTick
@40 000000E5 000000E5 000000E5 000000E5 => IRQ0-IRQ31 all goto same IRQService
@50 000000E5 000000E5 000000E5 000000E5
@60 000000E5 000000E5 000000E5 000000E5
@70 000000E5 000000E5 000000E5 000000E5
@80 000000E5 000000E5 000000E5 000000E5
@90 000000E5 000000E5 000000E5 000000E5
@a0 000000E5 000000E5 000000E5 000000E5
@b0 000000E5 000000E5 000000E5 000000E5
--- All vector table ends before here ----
Section 2 => ROM_EXEC2 (104 bytes)
---------
@c0 => boot.c starts here. reset handler starts, then NMI, HardFault, SVC, PendSV, SysTick here
----
F000B510 => reset handler (B510 => PUSH {r4,lr})
BD10F827 => F827_F000 => BL init (target addr=000_027=>pc=pc+027=0xc6+(0x27<<1)=0x114, (BD10 => POP {r4,pc})
@c8 => NMI handler
68004805 BF004700 (BF00 = NOP from HardFault handler)
@d0 BF00E7FD BF00E7FD BF00E7FD 0000E7FD => HardFault, SVC, PendSV, SysTick (last 0000 is for next inst DCW 0x0000)
@e0 00043000 => DCD ||.data|| => 0x0004_3000 is addr of RAM region
@e4 => IRQservice.c starts here
---
69814807 61811C49 49064A07
@f0 490762D1 21506101 BF0062C1 62C12111
@100
00004770 => 4770=BX lr, 0000=DCW 0x0000
20046F80 D0D0D0D0 20046FC0 => all DCD
@110
B000B000 => all DCD
@114 => init.c starts here
---
F000B510 =>
BD10F801 => F801_F000 => BL __main (target addr=000_001 =>PC=PC+1=
@11c => main.c starts here
---
1C402000 => main starts from here __main
DBFC2801
47702000 => end of main.c
@128
000000E5 000000E5 => extra bytes, not sure for what
--------------
seq of exec of code above:
1. HCLK starts running. 1 cycle later, HRESET_N gets released.
2. HADDR goes from 0x00->0x04->0xC0,0xC4(reset_handler)->0x114,0x118(init)->0x11c,0x120,0x124(main)->
----------------------------------------
Section 3: RAM_EXEC1
---------
remove below lines ---
4770BF30 => Reset_Handler starts here (B510 => PUSH {r4,lr})
4770B662
4770B672 4770BF00
@d0 8F5FF3BF F3BF4770 47708F4F 8F6FF3BF
@e0 00004770
F000B510 => Reset_Handler starts here (B510 => PUSH {r4,lr})
BD10F827 => F827_F000 => BL init, (BD10 => POP {r4,pc})
68004805
@f0 BF004700 BF00E7FD BF00E7FD BF00E7FD
@100 0000E7FD 00043000 69814807 61811C49
@110 49064A07 490762D1 21506101 BF0062C1
@120 62C12111 00004770 20046F80 D0D0D0D0
@130 20046FC0 B000B000 F000B510 BD10F81D
@140 F7FFB510 BD10FFC5 F7FFB510 BD10FFC4
@150 F7FFB510 BD10FFC3 F7FFB510 BD10FFB7
@160 F7FFB510 BD10FFAF F7FFB510 BD10FFAD
@170 F7FFB510 BD10FFA5
@12C
1C402000 => main starts from here __main
DBFC2801
47702000 => ENDP
@138
000000E5 000000E5
-------------------------