GR-SAKURA で遊んでみる(3) GNU toolchain による開発環境の構築
前回 LED の点滅プログラムを書いたものの、Renesas 製のツールチェインにてビルドすることを前提としたもので、Windows PC でないとビルドできない。
普段は NetBSD や Linux を使用しているので、ちょっとしたことを GR-SAKURA で試すのにもわざわざ Windows PC を起動してビルドするのは面倒である。
そこで GNU toolchain による RX 向けのクロス開発環境を整備した。ソースからのビルド手順は ARM や SH 等の他アーキテクチャの場合と同様であり、特に難しいものではない。しかし一からやり直すとなると、地道な試行錯誤を繰り返す羽目になるので、備忘録を兼ねて以下に手順をまとめておく。なお、この手順は NetBSD 6.1.2、Linux(Slackware-14.0)、MinGW(Windows XP) にて試行した*1。
binutils のインストール
RX 向けの as(1)、ld(1)、objdump(1)は、binutils をインストールすることで使用可能になる。
後述する gcc(1) と異なり、binutils は多倍長/浮動小数点に関するライブラリ群には依存しない。binutils のソースの入手、ビルドおよびインストールの手順を以下に示す。
$ wget ftp://ftp.jaist.ac.jp/pub/GNU/binutils/binutils-2.24.tar.gz $ tar zxvf binutils-2.24.tar.gz $ cd binutils-2.24 $ ./configure --prefix=/usr/local/rx-elf --target=rx-elf $ make # make install
※ # で始まる行は root 権限で行う必要があることを示す(MinGW 環境下では、特に root 権限を意識する必要はない)
binutils の動作確認結果:
$ cat > loop.s .text .globl _start _start: nop nop nop bra . .end (Ctrl-D を入力) $ rx-elf-as -o loop.o loop.s $ rx-elf-objdump -Dx loop.o loop.o: file format elf32-rx-le loop.o architecture: rx, flags 0x00000010: HAS_SYMS start address 0x00000000 private flags = 0x8:32-bit doubles, no dsp, no pid, RX ABI Sections: Idx Name Size VMA LMA File off Algn 0 P 00000005 00000000 00000000 00000034 2**0 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 D_1 00000000 00000000 00000000 00000039 2**0 CONTENTS, ALLOC, LOAD, DATA 2 B_1 00000000 00000000 00000000 00000039 2**0 ALLOC SYMBOL TABLE: 00000000 l d P 00000000 P 00000000 l d D_1 00000000 D_1 00000000 l d B_1 00000000 B_1 00000000 g P 00000000 _start Disassembly of section P: 00000000 <_start>: 0: 03 nop 1: 03 nop 2: 03 nop 3: 2e 00 bra.b 3 <_start+0x3>
objdump(1) の出力結果にて、コードに割り当てられたセクション名が(.text ではなく) P になっている点に注目されたい。
RX 向けの as(1) では、単に ".text" と記述すると、割り当てるセクションの名前は .text ではなく P になる*2。同様に擬似命令 ".data" および ".bss" は、それぞれ D_1 および B_1 セクションを割り当てる。もちろん後述の gcc(1) で C のソースをコンパイルした場合、.text/.data/.rodata/.bss の代わに P/D_1/C/B_1 を生成する。
GCC のインストール
GCC は binutils に加えて、GMP(GNU Multiple Precision Library)、MPFR(GNU Multiple Precision Floating-Point Reliably) および MPC の多倍長/浮動小数点数を扱うライブラリ群に依存している。従って、これらを先にインストールする必要がある。
各ライブラリのソースの入手からインストールまでの手順を以下に示す。
GMP:
$ wget ftp://ftp.jaist.ac.jp/pub/GNU/gmp/gmp-4.3.2.tar.gz $ tar zxvf gmp-4.3.2.tar.gz $ cd gmp-4.3.2 $ ./configure && make # make install $ make check
MPFR:
$ wget ftp://ftp.jaist.ac.jp/pub/GNU/mpfr/mpfr-2.4.2.tar.gz $ tar zxvf mpfr-2.4.2.tar.gz $ cd mpfr-2.4.2 $ ./configure --with-gmp=/usr/local && make # make install
MPC:
$ wget http://www.multiprecision.org/mpc/download/mpc-0.8.2.tar.gz $ tar zxvf mpc-0.8.2.tar.gz $ cd mpc-0.8.2 $ ./configure --with-gmp=/usr/local && make # make install
次に gcc(1) のソースを入手し、ビルドおよびインストールを行う。
ここでは、4.7.x 系のバージョンの内、2014 年 9 月 24 日時点での最新版である 4.7.4 をビルドした。
4.8.x や 4.9.x の場合、ビルドの途中でエラーになる*3。
$ wget ftp://ftp.jaist.ac.jp/pub/GNU/gcc/gcc-4.7.4/gcc-4.7.4.tar.gz $ tar zxvf gcc-4.7.4.tar.gz $ mkdir gcc-4.7.4.build && cd gcc-4.7.4.build $ ../gcc-4.7.4/configure --prefix=/usr/local/rx-elf --target=rx-elf --with-gmp=/usr/local --disable-libssp --enable-languages=c $ LD_LIBRARY_PATH=/usr/local/lib gmake # gmake install
※ 上記で(make ではなく)gmake を使用しているのは、GCC のソースに含まれる configure が GNU 拡張文法を使用した Makefile を生成することに因る(*BSD のユーザランドに含まれる make では解析に失敗する)。Linux や MinGW 環境下では通常 GNU make が標準なので、gmake とせずに make としても問題ない。
GCC 版 LED 点滅プログラム
前回の LED 点滅プログラムを gcc 向けに書き直した。ソース一式は以下に置いている。
http://he4.seikyou.ne.jp/home/tsukimizake/gr-sakura-dev/blink_gcc.tar.gz
各々のソースの内容は以下のとおり:
blink.S:
1 #define SCKCR 0x00080020 2 #define SCKCR2 0x00080024 3 #define SCKCR3 0x00080026 4 5 #define PLLCR_ADDR 0x00080028 6 #define PLLCR2_ADDR 0x0008002a 7 #define MOSCCR_ADDR 0x00080032 8 #define MOSCWTCR_ADDR 0x000800a2 9 #define PLLWTCR_ADDR 0x000800a6 10 11 #define IR12_ADDR 0x0008701c 12 #define IPR004_ADDR 0x00087304 13 #define IER03_ADDR 0x00087203 14 15 #define PRCR_ADDR 0x000803fe 16 #define MSTPCRA_ADDR 0x00080010 17 #define MSTPCRB_ADDR 0x00080014 18 #define MSTPCRC_ADDR 0x00080018 19 20 #define PORT0_PDR_ADDR 0x0008c000 21 #define PORT1_PDR_ADDR 0x0008c001 22 #define PORT2_PDR_ADDR 0x0008c002 23 #define PORT3_PDR_ADDR 0x0008c003 24 #define PORT4_PDR_ADDR 0x0008c004 25 #define PORT5_PDR_ADDR 0x0008c005 26 #define PORT6_PDR_ADDR 0x0008c006 27 #define PORT7_PDR_ADDR 0x0008c007 28 #define PORT8_PDR_ADDR 0x0008c008 29 #define PORT9_PDR_ADDR 0x0008c009 30 #define PORTA_PDR_ADDR 0x0008c00a 31 #define PORTB_PDR_ADDR 0x0008c00b 32 #define PORTC_PDR_ADDR 0x0008c00c 33 #define PORTD_PDR_ADDR 0x0008c00d 34 #define PORTE_PDR_ADDR 0x0008c00e 35 #define PORTF_PDR_ADDR 0x0008c00f 36 #define PORTG_PDR_ADDR 0x0008c010 37 #define PORTJ_PDR_ADDR 0x0008c012 38 #define PORTA_PODR_ADDR 0x0008c02a 39 40 #define CMSTR0_ADDR 0x00088000 41 #define CMT0_CMCR_ADDR 0x00088002 42 #define CMT0_CMCNT_ADDR 0x00088004 43 #define CMT0_CMCOR_ADDR 0x00088006 44 45 .text 46 47 .globl _start 48 .globl intvect 49 .globl unhandled_interrupt 50 .globl unhandled_exception 51 .globl cmt0_interrupt 52 53 _start: 54 ; disable interrupts 55 clrpsw i 56 57 mvtc #0x00020000, usp 58 mvtc #0x0001fc00, isp 59 mvtc #intvect, intb 60 61 ; stop unused peripherals 62 bsr stop_modules 63 64 ; initialize non-existent I/O port 65 ; see following: 66 ; 67 ; RX63N Group, RX631 Group 68 ; User's Manual:Hardware 69 ; 21.5.1 Products fewer than 176 Pins 70 bsr init_nonexistent_port 71 72 ; enable CMT0 73 mov.l #PRCR_ADDR, r1 74 mov.w #0xa503, [r1] 75 mov.l #MSTPCRA_ADDR, r1 76 mov.l [r1], r2 77 bclr #15, r2 78 mov.l r2, [r1] 79 80 ; if the main clock stabilization waiting time(tMAINOSC) is 20 ms, 81 ; we should set MOSCWTCR.MSTS[4:0] to 14 82 ; (262,144 cycle > 20.00 ms = 240,000 cycles), 83 ; so we need to wait 84 ; tMAINOSC + (262,144 + 16384)/ fMAIN 85 ; = 20 + (262,144 + 16,384) / 12,000 = approx. 43.23 ms 86 mov.l #MOSCWTCR_ADDR, r1 87 mov.b #0xe, [r1] ; MOSCWTCR.MSTS[4:0]=14 88 89 ; run the main clock oscillator 90 mov.l #MOSCCR_ADDR, r1 91 bclr #0, [r1].b ; MOSCCR.MOSTP=0 92 ; confirm that MOSCCR.MOSTP has actually been updated 93 1: movu.b [r1], r2 94 tst r2, r2 95 bnz 1b 96 97 mov.l #44, r1 98 bsr mdelay 99 100 ; configure the PLL clock: 101 ; to generate the PLL clock from main clock by dividing by 1 and 102 ; multiplying by 8(fPLL = 12.0 * 8 = 96.0 MHz), 103 ; we should set PLLWTCR.PSTS to 9 104 ; (n = 65536 cycles = 682.7 us), 105 ; so we need to wait 106 ; tPLL1 + (n + 131,072) / fPLL 107 ; = 0.5 + (65,536 + 131,072)/ 96,000 = 2.548 ms 108 mov.l #PLLCR_ADDR, r1 109 mov.w #0x0700, [r1] 110 mov.l #PLLWTCR_ADDR, r1 111 mov.b #9, [r1] 112 mov.l #PLLCR2_ADDR, r1 113 bclr #0, r1 ; PLLCR2.PLLEN=0 114 115 mov.l #3, r1 116 bsr mdelay 117 118 mov.l #SCKCR, r1 119 mov.l #0x22c23311, [r1] ; ICLK -> 1/4, PCLK -> 1/8 120 mov.l #SCKCR2, r1 121 mov.w #0x0002, [r1] 122 mov.l #SCKCR3, r1 123 mov.w #0x0400, [r1] ; use PLL clock 124 125 mov.l #PRCR_ADDR, r1 126 mov.w #0xa500,[r1] ; write protection for clock control registers 127 128 ; turn off user LEDs 129 mov.l #PORTA_PODR_ADDR, r1 130 bclr #0, [r1].b ; Set PA0 output level to L 131 bclr #1, [r1].b ; Set PA1 output level to L 132 bclr #2, [r1].b ; Set PA2 output level to L 133 bclr #6, [r1].b ; Set PA6 output level to L 134 mov.l #PORTA_PDR_ADDR, r1 135 bset #0, [r1].b ; Set PA0 direction to output 136 bset #1, [r1].b ; Set PA1 direction to output 137 bset #2, [r1].b ; Set PA2 direction to output 138 bset #6, [r1].b ; Set PA6 direction to output 139 140 ; initialize .bss section 141 mov.l #__bss_start, r1 142 mov.l #0, r2 143 mov.l #__bss_end, r3 144 sub r1, r3 145 sstr.b 146 147 mov.l #CMT0_CMCOR_ADDR, r1 148 mov.w #0x05dc, [r1] ; Set CMCOR 149 mov.l #CMT0_CMCR_ADDR, r1 150 mov.w #0x0040, [r1] ; Set CMCR 151 mov.l #CMT0_CMCNT_ADDR, r1 152 mov.w #0x0000, [r1] ; Clear CMCNT 153 154 ; enable interrupts 155 mov.l #IPR004_ADDR, r1 156 mov.b #0x01, [r1] ; Set IPR004 157 mov.l #IER03_ADDR, r1 158 bset #4, [r1].b ; Set IER03.IEN4 159 setpsw i ; Set I bit 160 161 mov.l #CMSTR0_ADDR, r1 162 mov.w #0x0001, [r1] ; Start CMT0 163 164 bra . 165 166 mdelay: 167 pushm r1-r3 168 169 mov.l #CMT0_CMCOR_ADDR, r2 170 mov.w #0x0010, [r2] 171 mov.l #CMT0_CMCR_ADDR, r2 172 mov.w #0x0040, [r2] 173 mov.l #CMT0_CMCNT_ADDR, r2 174 mov.w #0x0000, 4[r2] 175 176 mov.l #IPR004_ADDR, r2 177 bset #0, [r2].b ; Set IPR004 178 mov.l #IER03_ADDR, r2 179 bset #4, [r2].b ; Set IER03.IEN4 180 181 tst r1, r1 182 1: bz 3f 183 184 mov.l #CMSTR0_ADDR, r2 185 mov.w #0x0001, [r2] ; Start CMT0 186 187 2: mov.l #IR12_ADDR, r2 188 movu.b [r2], r3 189 tst r3, r3 190 bz 2b 191 mov.b #0x00, [r2] 192 193 mov.l #CMSTR0_ADDR, r2 194 mov.w #0x0000, [r2] ; Stop CMT0 195 196 sub #1, r1 197 bra 1b 198 199 3: mov.l #IER03_ADDR, r2 200 bclr #4, [r2].b ; Clear IER03.IEN4 201 mov.l #IPR004_ADDR, r2 202 bclr #0, [r2].b ; Clear IPR004 203 204 popm r1-r3 205 rts 206 207 stop_modules: 208 ; disable write protection for low power consumption module 209 mov.l #PRCR_ADDR, r1 210 mov.w #0xa502,[r1] 211 212 ; stop unused peripherals(DMA and EXDMA) 213 mov.l #MSTPCRA_ADDR, r1 214 mov.l [r1], r2 215 bset #28, r2 ; DMA 216 bset #29, r2 ; EXDMA 217 mov.l r2, [r1] 218 219 mov.l #PRCR_ADDR, r1 220 mov.w #0xa500, [r1] 221 rts 222 223 init_nonexistent_port: 224 ; set non-existent I/O ports' direction to output 225 push.l r1 226 mov.l #PORT0_PDR_ADDR, r1 227 mov.b #0x0f, [r1] ; P00-P03 228 mov.l #PORT1_PDR_ADDR, r1 229 mov.b #0x03, [r1] ; P10-P11 230 mov.l #PORT2_PDR_ADDR, r1 231 mov.b #0x00, [r1] ; none 232 mov.l #PORT3_PDR_ADDR, r1 233 mov.b #0x00, [r1] ; none 234 mov.l #PORT4_PDR_ADDR, r1 235 mov.b #0x00, [r1] ; none 236 mov.l #PORT5_PDR_ADDR, r1 237 mov.b #0xc0, [r1] ; P56-P57 238 mov.l #PORT6_PDR_ADDR, r1 239 mov.b #0xff, [r1] ; P60-67 240 mov.l #PORT7_PDR_ADDR, r1 241 mov.b #0xff, [r1] ; P70-77 242 mov.l #PORT8_PDR_ADDR, r1 243 mov.b #0xff, [r1] ; P80-87 244 mov.l #PORT9_PDR_ADDR, r1 245 mov.b #0xff, [r1] ; P90-97 246 mov.l #PORTA_PDR_ADDR, r1 247 mov.b #0x00, [r1] ; none 248 mov.l #PORTB_PDR_ADDR, r1 249 mov.b #0x00, [r1] ; none 250 mov.l #PORTC_PDR_ADDR, r1 251 mov.b #0x00, [r1] ; none 252 mov.l #PORTD_PDR_ADDR, r1 253 mov.b #0x00, [r1] ; none 254 mov.l #PORTE_PDR_ADDR, r1 255 mov.b #0x00, [r1] ; none 256 mov.l #PORTF_PDR_ADDR, r1 257 mov.b #0x3f, [r1] ; PF0-PF5 258 mov.l #PORTG_PDR_ADDR, r1 259 mov.b #0xff, [r1] ; PG0-OG7 260 mov.l #PORTJ_PDR_ADDR, r1 261 mov.b #0x20, [r1] ; PJ5 262 pop r1 263 rts 264 265 unhandled_exception: 266 ; turn on LED1 267 mov.l #PORTA_PODR_ADDR, r1 268 bset #0, [r1].b 269 mov.l #PORTA_PDR_ADDR, r1 270 bset #0, [r1].b 271 bra . 272 273 unhandled_interrupt: 274 ; turn on LED2 275 mov.l #PORTA_PODR_ADDR, r1 276 bset #1, [r1].b 277 mov.l #PORTA_PDR_ADDR, r1 278 bset #1, [r1].b 279 bra . 280 281 cmt0_interrupt: 282 pushm r1-r2 283 mov.l #count, r1 284 mov.l [r1], r2 285 tst r2, r2 286 bnz 1f 287 mov.l #0x01f4, r2 288 289 ; toggle output level 290 mov.l #PORTA_PODR_ADDR, r1 291 bnot #6, [r1].b 292 293 mov.l #count, r1 294 1: sub #1, r2 295 mov.l r2, [r1] 296 297 popm r1-r2 298 rte 299 300 301 .bss 302 303 count: .long 0x00000000 304 305 .end
vect.S:
1 .section .intvect, "a" 2 3 .globl unhandled_interrupt 4 .globl cmt0_interrupt 5 .globl intvect 6 7 .align 4 8 intvect: 9 .long unhandled_interrupt 10 .long unhandled_interrupt (中略) 36 .long unhandled_interrupt 37 .long cmt0_interrupt 38 .long unhandled_interrupt 39 .long unhandled_interrupt (中略) 264 .long unhandled_interrupt 265 266 267 .section .fixedvect, "a" 268 .globl fixedvect 269 .globl unhandled_exception 270 .globl _start 271 272 .align 4 273 fixedvect: 274 .long unhandled_exception 275 .long unhandled_exception 276 .long unhandled_exception 277 .long unhandled_exception 278 .long unhandled_exception 279 .long unhandled_exception 280 .long unhandled_exception 281 .long unhandled_exception 282 .long unhandled_exception 283 .long unhandled_exception 284 .long unhandled_exception 285 .long _start 286 287 .end
※セクション名に続く "a" は、allocatable なセクションであることを示す。これがないと、objcopy(1) による ELF -> S-recrd への変換時に、セクションの内容が破棄されてしまう。
blink.ld(リンカスクリプト):
1 SECTIONS 2 { 3 . = 0x00000000; 4 __bss_start = .; 5 B_1 : { *(B_1) } 6 __bss_end = .; 7 8 . = 0xfff80000; 9 P : { *(P) } 10 .intvect : { *(.intvect) } 11 . = 0xffffffd0; 12 .fixedvect : { *(.fixedvect) } 13 }
GNU-toolchain によるアセンブル、リンク、ELF -> S-recrd 変換の手順を以下に示す。
rx-elf-gcc -Wall -c -o blink.o blink.S rx-elf-gcc -Wall -c -o vect.o vect.S rx-elf-ld -o blink -T blink.ld blink.o vect.o rx-elf-objcopy -O srec blink blink.mot
気になった点
double foo(double a, double b) { return a + b; }
という C のソースを gcc(1) でコンパイルすると、その結果は
0: 7e aa push.l r10 2: 7e a6 push.l r6 4: 71 06 f8 add #-8, r0, r6 7: ef 60 mov.l r6, r0 9: e3 61 mov.l r1, [r6] b: a0 6a mov.l r2, 4[r6] d: ec 6a mov.l [r6], r10 f: fc 89 6a 01 fadd 4[r6].l, r10 13: ef a1 mov.l r10, r1 15: 62 80 add #8, r0 17: 7e b6 pop r6 19: 7e ba pop r10 1b: 02 rts
となった。浮動小数点数の加算に対して fadd 命令を使ったコードを生成するということは、RX200 シリーズは対応しないということか。RX 向けの gcc(1) では特に RX200 シリーズ向けのオプションは無かったし...
ともあれ、これで GNU toolchain でビルドできる環境が整った。とはいえ、FDT で S-record のファイルを書き込む際には、結局 Windows PC が必要になるのだが。
# ブートローダがあれば...