perlでAVRニモニックを機械語に変換

4-2.正規表現

本記事はリライト中です。


『アベンジャーズ』(2012)
神も含めた超人たちの中、生身の人間ホークアイが、危なっかしくてしょうがないです。でも、きっとそこが良いのでしょう。弓使いというだけで魅力的ですし。
コミックのタイトルは、『アベンジャーズ・アッセンブル』(Avengers Assemble)、「アベンジャーズ!集合せよ!」の意味で、コミック中の名セリフとなっているらしいです。しかし映画のセリフには出てきませんでした。まあいろいろあるのでしょうね。

AVRマイコンでトンボの羽根の周波数」で8ビットって面白いなあ、とAVRマイコンに興味を持ってしまったので、もう少し理解を深めていこうかと思います。理解を深めるために「Avengers Assemble」つながりで、「AVR Assemble」作っていきます。といっても、一気に作るのは大変なので、命令をひとつずつ理解しながら進めます。

ニモニックを機械語に変換その1

ニモニックからマシン語への変換するのに、以下のデータシートを参考にしました。

Click to access Atmel-0856-AVR-Instruction-Set-Manual.pdf

avrasm-001
上記は、ニモニックと、その機械語の構成です。このニモニックを機械語にするスクリプトを書いてみました。

~$ cat -n avr-asm.pl
     1	while(<>){
     2		if(/(?:LDI|ldi)\s+[rR](\d+),([0-9a-fxA-FX]+)/){
     3			$d=$1;
     4			$K=$2;
     5			$K=hex $K if $K =~ /x/;
     6			printf "%01X%01X%01X%01X\n"
     7				,$d-16
     8				,($K&0xf)
     9				,0xE
    10				,($K&0xf0)>>4
    11		}
    12	}
~$

解説です。正規表現でニモニック解析です。つまり2行目が要です。
(?:LDI|ldi)は、単純にオアです。(?:)をつけて、後方参照記憶をせずに、オアする単語を囲うだけにしています。
[rR](\d+)で、レジスタ番号を取得しています。この命令で指定できるレジスタはr16〜r31なので、$dに格納されて、機械語にするときに、16を引きます。
([0-9a-fxA-FX]+)は、16進または10進のみ許容です。

エラー処理は、まだ追加しません。

さて使ってみます。

~$ echo 'ldi r24,8' | perl avr-asm.pl
88E0
~$ echo 'ldi r24,9' | perl avr-asm.pl
89E0
~$ echo 'ldi r24,10' | perl avr-asm.pl
8AE0
~$ echo 'ldi r24,0xa' | perl avr-asm.pl
8AE0
~$ echo 'ldi r24,0xF' | perl avr-asm.pl
8FE0
~$ echo 'ldi r24,0x10' | perl avr-asm.pl
80E1
~$

マイコンを動かさないと合っているか分かりませんが、とりあえずここまで。

ニモニックを機械語に変換その2

一命令増やしてみましょう。

while(<>){
        if(/(?:LDI|ldi)\s+[rR](\d+),([0-9a-fxA-FX]+)/){
                $d=$1;
                $K=$2;
                $K=hex $K if $K =~ /x/;
                printf "%01X%01X%01X%01X\n"
                        ,$d-16
                        ,($K&0xf)
                        ,0xE
                        ,($K&0xf0)>>4
        }
        if(/(?:OUT|out)\s+([0-9a-fxA-FX]+),[rR](\d+)/){
                $A=$1;
                $r=$2;
                $A=hex $A if $A =~ /x/;
                printf "%01X%01X%01X%01X\n"
                        ,($r&0xf)
                        ,($A&0xf)
                        ,0xB
                        ,0x8 | ($A>>3&0x6) | ($r>>4 & 0x1)
        }
}

一命令増やしただけですが、行数が多くてげんなりします。命令一つに一行にしたいです。
ということで、書き換えました。要はニモニックを機械語に置換すれば良いのです。

~$ cat avr-asm.pl
while(<>){
s/(0[xX][0-9A-Fa-f]+)/hex $1/e;
s/LDI\s+[rR](\d+),(\d+)/sprintf("%01X%01X%01X%01X",($1-16),($2&0xf),0xE,($2>>4) )/e;
s/OUT\s+(\d+),[rR](\d+)/sprintf("%01X%01X%01X%01X",($2&0x0f),($1&0xf),0xB, 0x8|($1>>3&0x06)|($2>>4&0x01) )/e;
print;
}

命令を増やしつつ、ダイエットもしていきます。

~$ cat avr-asm.pl
while(<>){
s/(0X[0-9A-F]+)/hex $1/ei;
s/LDI\s+R(\d+),(\d+)/sprintf("%1X%1X%1X%1X",($1 -  16),($2 & 0xf),0xE,($2>>4) )/ei;
s/OUT\s+(\d+),R(\d+)/sprintf("%1X%1X%1X%1X",($2 & 0xf),($1 & 0xf),0xB,(8|($1>>3&6)|($2>>4&1)) )/ei;
s/EOR\s+R(\d+),R(\d+)/sprintf("%1X%1X%1X%1X",($1 & 0xf),($2 & 0xf),0x2,(4|($1>>4)|($2>>3&2)) )/ei;
print;
}

sprintfのところが、冗長に感じてきましたので関数化します。

~$ cat avr-asm.pl
while(<>){
s/(0X[0-9A-F]+)/hex $1/ei;
s/LDI\s+R(\d+),(\d+)/asm(0xE,($2>>4), ($1 -  16),($2 & 0xf) )/ei;
s/OUT\s+(\d+),R(\d+)/asm(0xB,(8|($1>>3&6)|($2>>4&1)), ($2 & 0xf),($1 & 0xf) )/ei;
s/EOR\s+R(\d+),R(\d+)/asm(0x2,(4|($1>>4)|($2>>3&2)), ($1 & 0xf),($2 & 0xf) )/ei;
print;
}
sub asm{
	my($x3,$x4,$x1,$x2) = @_;
	sprintf("%1X%1X%1X%1X",$x1,$x2,$x3,$x4);
}

()が多い気がします。
演算子の優先順位は、<<>>、|、&で比べると、

高い
<<>>
&
|
低い
ですので、()が取れます。
あと、正規表現を少し変えることで、各行を揃えることができます。

~$ cat avr-asm.pl
while(<>){
s/(0X[0-9A-F]+)/hex $1/ei;
s/LDI\s+R{1}(\d+),R{0}(\d+)/asm(0xE,(          $2>>4  ), $1-16 ,$2&0xf )/ei;
s/OUT\s+R{0}(\d+),R{1}(\d+)/asm(0xB,(8|$1>>3&6|$2>>4&1), $2&0xf,$1&0xf )/ei;
s/EOR\s+R{1}(\d+),R{1}(\d+)/asm(0x2,(4|$1>>4  |$2>>3&2), $1&0xf,$2&0xf )/ei;
print;
}
sub asm{
	my($x3,$x4,$x1,$x2) = @_;
	sprintf("%1X%1X%1X%1X",$x1,$x2,$x3,$x4);
}

使ってみます。

~$ cat sample
ldi r24,0x08
out 0x17,r24
eor r24,r25
~$ perl avr-asm.pl sample
88E0
87BB
8927
~$

いい感じです。

ニモニックを機械語に変換その3

作ったアセンブラの変換が正しく機能しているか確認するにはどうすればよいでしょうか。
いきなりマイコンに書き込んで動作確認するのは骨が砕けます。やはり、ただしいアセンブラとの結果比較がよいでしょう。

アセンブラの前に、C言語がどのように機械語に落とされているか確認します。このソースを使います。

root@raspberrypi:~# cat led-loop.c
#include <avr/io.h>
#include <util/delay.h>

int main()
{
        DDRB = _BV(3);

        while(1){
                PORTB ^= _BV(3);
                _delay_ms(500);
        }
}

ビルドします。

root@raspberrypi:~# make -f makefile-ledloop
avr-gcc -g -O2 -mmcu=attiny85 -DF_CPU=1000000UL -c -o led-loop.o led-loop.c
avr-gcc -g -O2 -mmcu=attiny85  led-loop.o   -o led-loop
avr-objcopy -j .text -j .data -O ihex led-loop led-loop.hex

ディスアセンブラです。

root@raspberrypi:~# avr-objdump -d led-loop.o

led-loop.o:     ファイル形式 elf32-avr


セクション .text.startup の逆アセンブル:

00000000 <main>:
   0:	88 e0       	ldi	r24, 0x08	; 8
   2:	87 bb       	out	0x17, r24	; 23
   4:	98 e0       	ldi	r25, 0x08	; 8
   6:	88 b3       	in	r24, 0x18	; 24
   8:	89 27       	eor	r24, r25
   a:	88 bb       	out	0x18, r24	; 24
   c:	2f e9       	ldi	r18, 0x9F	; 159
   e:	36 e8       	ldi	r19, 0x86	; 134
  10:	81 e0       	ldi	r24, 0x01	; 1
  12:	21 50       	subi	r18, 0x01	; 1
  14:	30 40       	sbci	r19, 0x00	; 0
  16:	80 40       	sbci	r24, 0x00	; 0
  18:	01 f4       	brne	.+0      	; 0x1a <main+0x1a>
  1a:	00 c0       	rjmp	.+0      	; 0x1c <main+0x1c>
  1c:	00 00       	nop
  1e:	00 c0       	rjmp	.+0      	; 0x20 <__zero_reg__+0x1f>

あれ、何か変です。18行目のbrneのジャンプ先がおかしい気がします。これだと動かないはずですが、LEDはピカピカしています。
HEXと比べてみましょう。

root@raspberrypi:~# cat led-loop.hex
:100000000EC015C014C013C012C011C010C00FC064
:100010000EC00DC00CC00BC00AC009C008C011241E
:100020001FBECFE5D2E0DEBFCDBF02D011C0E8CF0A
:1000300088E087BB98E088B3892788BB2FE936E83A
:1000400081E0215030408040E1F700C00000F3CF54
:04005000F894FFCF52
:00000001FF
root@raspberrypi:~#

avrasm-002

HEXの方は合ってそうです。これから作る予定のニモニック置換プログラムは(アセンブラとはあえて呼ばない)は、avr-asでアセンブルしたHEXと比べることにします。

ニモニックを機械語に変換その4

簡単なアセンブラを作って、avr-asでアセンブルしたものを、マイコンに書き込んで確認しました。
割り込みベクタは無視して0番地からプログラム配置しています。

        ldi r24,0x08
        ldi r25,0x00
        out 0x17,r24
        out 0x18,r24
LOOP:
        rjmp LOOP

このプログラムだと、LEDは光りました。

職業病でこの簡単なプログラムでも、リセットがかかっていないか確認したくなります。
マイコン起動時には、LEDはOFFさせて、nopをいれてから、LEDをONして、無限ループ。
ただしこのnopは、Vimで、yy1999pです。(nopが2000個)

        ldi r24,0x08
        ldi r25,0x00
        out 0x17,r24
        out 0x18,r25

	nop

        out 0x18,r24
LOOP:
        rjmp LOOP

2000個nopを入れると、書き込みも時間がかかりますね。

root@raspberrypi:~/led# make write
avrdude -p t85 -c linuxgpio -U flash:w:sample.hex

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.00s

avrdude: Device signature = 0x1e930b
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "sample.hex"
avrdude: input file sample.hex auto detected as Intel Hex
avrdude: writing flash (4012 bytes):

Writing | ################################################## | 100% 3.10s

avrdude: 4012 bytes of flash written
avrdude: verifying flash memory against sample.hex:
avrdude: load data flash data from input file sample.hex:
avrdude: input file sample.hex auto detected as Intel Hex
avrdude: input file sample.hex contains 4012 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 1.86s

avrdude: verifying ...
avrdude: 4012 bytes of flash verified

avrdude: safemode: Fuses OK (E:FF, H:DF, L:62)

avrdude done.  Thank you.

root@raspberrypi:~/led#

LEDの点灯の仕方は見た目では変わりませんでした。そこで、ONとOFFを逆にしてみます。

        ldi r24,0x08
        ldi r25,0x00
        out 0x17,r24
        out 0x18,r24

	nop

        out 0x18,r25
LOOP:
        rjmp LOOP

書き込んで確認すると、LEDが点灯しています。つまりどこかでリセットがかかっています。

コメント

タイトルとURLをコピーしました