PHPプログラムで学ぶアセンブラのしくみ

第1回PHPでアセンブラを作ってみた

最近、FPGAなどを使ってCPUを自作する記事を目にするようになりました。CPUの命令コードは数字ですから、アセンブラソースを1行1行手で命令コードに変換していく(ハンドアセンブルする)ことになります。昔は、このためにコーディング用紙というものが売っていました。

最近のPICなどのマイコンには開発環境が用意されており、アセンブラなど必要な道具はすべて入っています。ですから、用意されているアセンブラを使うことがあっても、アセンブラを自分で作るという機会はなかなかないと思います。ここでは、アセンブラを作ってみることにしましょう。

PHPでアセンブラを作る

アセンブラをどの言語で作るかですが、今回もPHPを使ってみました。フォーム上にソースを入力すると、バイナリが出力されます。対応しているのはPIC16Fの命令セットで、今回のアセンブラプログラムを使って開発環境で作ったものとまったく同じバイナリが得られました。

図1 アセンブリ言語の入力フォーム
図1 アセンブリ言語の入力フォーム
図2 アセンブラプログラムにより生成された機械語(バイナリ)
図2 アセンブラプログラムにより生成された機械語(バイナリ)

このアセンブラプログラムのPHPのソースはリスト1のようになります。

リスト1 PHPによるアセンブラ picasm
<?php

$labellist = array();
$codelist = array();
$datalist = array();

$codemap = array(
    "movwf" => 0x0080, 
    "clrf" => 0x0180, 
    "subwf" => 0x0200, 
    "decf" => 0x0300, 
    "iorwf" => 0x0400, 
    "andwf" => 0x0500, 
    "xorwf" => 0x0600, 
    "addwf" => 0x0700, 
    "movf" => 0x0800, 
    "comf" => 0x0900, 
    "incf" => 0x0a00, 
    "decfsz" => 0x0b00, 
    "rrf" => 0x0c00, 
    "rlf" => 0x0d00, 
    "swapf" => 0x0e00, 
    "incfsz" => 0x0f00, 
    "bcf" => 0x1000, 
    "bsf" => 0x1400, 
    "btfsc" => 0x1800, 
    "btfss" => 0x1c00, 
    "call" => 0x2000, 
    "goto" => 0x2800, 
    "movlw" => 0x3000, 
    "retlw" => 0x3400, 
    "iorlw" => 0x3800, 
    "andlw" => 0x3900, 
    "xorlw" => 0x3a00, 
    "sublw" => 0x3c00, 
    "addlw" => 0x3e00, 
    "nop" => 0x8000, 
    "return" => 0x8008, 
    "retfie" => 0x8009, 
    "sleep" => 0x8063, 
    "clrwdt" => 0x8064, 
    "clrw" => 0x8100, 
);

function    evaloprand($str)
{
    global    $labellist;
    
    $ret = 0;
    if (($str = trim(strtolower($str))) == "")    
        return null;
    if (substr($str, 0, 1) == '"')
        return $str;
    $remain = $str;
    while ($remain != "") {
        if (ereg('^([-+|&]?) *\((.*)$', $remain, $array)) {
            $sign = $array[1];
            $remain = $array[2];
            
            $level = 1;
            $pos = 0;
            while ($level > 0) {
                if ($pos >= strlen($remain))
                    die("unmatched bracket: ".htmlspecialchars($str)."\n");
                switch (substr($remain, $pos++, 1)) {
                    case    '(':
                        $level++;
                        break;
                    case    ')':
                        $level--;
                        break;
                }
            }
            $val = evaloprand(substr($remain, 0, $pos - 1));
            $remain = substr($remain, $pos);
        } else {
            ereg('^([-+|&]?) *([^-+|&() ]+) *([-+|&()].*)?$', $remain, $array);
            $sign = $array[1];
            $val = $array[2];
            $remain = $array[3];
            
            if (ereg('^[0-9]', $val)) {
                if (ereg('^[0-9]+$', $val))
                    $val += 0;
                else if (ereg('^0x[0-9a-f]+$', $val))
                    $val += 0;
                else if (ereg('^([0-9a-f]+)h$', $val, $array))
                    $val = ("0x".$array[1]) + 0;
                else
                    die("invalid number: ".htmlspecialchars($str)."\n");
            } else if (ereg("^h'([0-9a-f]+)'$", $val, $array))
                $val = ("0x".$array[1]) + 0;
            else if (($val = @$labellist[$val]) === null)
                die("undefined label: ".htmlspecialchars($str)."\n");
        }
        switch ($sign) {
            default:
            case    '+':
                $ret += $val;
                break;
            case    '-':
                $ret -= $val;
                break;
            case    '&':
                $ret &= $val;
                break;
            case    '|':
                $ret |= $val;
                break;
        }
    }
    return $ret;
}


$locallabel = "";
$org = 0;
if (@$argv[1] === "-")
    $remain = stream_get_contents(STDIN);
else  {
    if (($remain = @$_POST["src"]) === NULL) {
        print <<<EOO
<HTML><HEAD><TITLE>picasm.php</TITLE></HEAD><BODY>
<H1>picasm.php</H1>
<FORM method=POST>
<TEXTAREA cols=80 rows=30 name=src>

</TEXTAREA>
<BR>
<INPUT type=submit>
</FORM>
<HR>
</BODY></HTML>
EOO;
        die();
    }
    if (get_magic_quotes_gpc())
        $remain = stripslashes($remain);
}

while ($remain != "") {
    list($line, $remain) = split("\r|\n|\r\n", $remain, 2);
    if (($i = strpos($line, ";")) !== FALSE)
        $line = substr($line, 0, $i);
    list($label, $opcode, $oprand) = split("[ \t]+", $line, 3);
    $label = strtolower($label);
    $opcode = strtolower($opcode);
    $oprand = trim($oprand);
#    printf("label:%s\n  opcode:%s\n  oprand:%s\n", $label, $opcode, $oprand);
    
    if ($opcode == "org")
        $org = $nextorg = evaloprand($oprand);
    else {
        $nextorg = $org + 1;
    }
    if ($label != "") {
        if (ereg('^:', $label))
            $s = $locallabel.$label;
        else
            $s = $locallabel = $label;
        if ($opcode == "equ") {
            $labellist[$s] = evaloprand($oprand);
            continue;
        }
        $labellist[$s] = $org;
    }
    if (($l = @$codemap[$opcode]) !== null) {
        if (@$codelistl[$org] !== null)
            die("memory overwritten: ".htmlspecialchars($line)."\n");
        $codelist[$org] = array($l, $oprand);
        $org = $nextorg;
        continue;
    }
    switch ($opcode) {
        case    "#include":
            if (!ereg('^"([-_.0-9A-Za-z]+)"$', $oprand, $array))
                die("syntax error: ".htmlspecialchars($line)."\n");
            $remain = file_get_contents("include/".$array[1])."\n".$remain;
            continue 2;
        case    "__config":
            if (@$codemap[$addr = 0x2007] !== null)
                die("memory overwritten: ".htmlspecialchars($line)."\n");
            $codelist[$addr] = array(-1, $oprand);
            continue 2;
        case    "nolist":
        case    "ifdef":
        case    "ifndef":
        case    "endif":
        case    "messg":
        case    "":
        case    "end":
        case    "list":
        case    "__maxram":
        case    "__badram":
            continue 2;
        case    "org":
            continue 2;
        default:
            die("syntax error: ".htmlspecialchars($line)."\n");
    }
    
    
}

if (0) {
    foreach ($labellist as $key => $val)
        printf("%s\t%08x\n", $key, $val);
}

ksort($codelist);
foreach ($codelist as $key => $val) {
    list($l, $oprand) = $val;
    $array = explode(",", $oprand);
#    $oprand1 = evaloprand($oprand1);
#    $oprand2 = evaloprand($oprand2);
#printf("%08x: %04x %d, %d\n", $key, $l, $oprand1, $oprand2);
#continue;
    
    if ($l < 0)
        $l = evaloprand($oprand);
    else if (($l & 0x8000)) {
        if ($oprand != "")
            die("syntax error: ".htmlspecialchars($line)."\n");
        $l = $l & 0x3fff;
    } else if (($l & 0x3000) == 0x3000) {
        if (count($array) != 1)
            die("syntax error: ".htmlspecialchars($line)."\n");
        $oprand1 = evaloprand($oprand);
        if (($oprand1 < 0)||($oprand1 > 0xff))
            die("out of range: ".htmlspecialchars($line)."\n");
        $l = $l | $oprand1;
    } else if (($l & 0x3000) == 0x2000) {
        if (count($array) != 1)
            die("syntax error: ".htmlspecialchars($line)."\n");
        $oprand1 = evaloprand($oprand);
        if (($oprand1 < 0)||($oprand1 > 0x7ff))
            die("out of range: ".htmlspecialchars($line)."\n");
        $l = $l | $oprand1;
    } else if (($l & 0x3000) == 0x1000) {
        if (count($array) != 2)
            die("syntax error: ".htmlspecialchars($line)."\n");
        $oprand1 = evaloprand($array[0]);
        if (($oprand1 < 0)||($oprand1 > 0x7f))
            die("out of range: ".htmlspecialchars($line)."\n");
        $oprand2 = evaloprand($array[1]);
        if (($oprand2 < 0)||($oprand2 > 7))
            die("out of range: ".htmlspecialchars($line)."\n");
        $l = $l | $oprand1 | ($oprand2 << 7);
    } else if (($l & 0x80)) {
        if (count($array) != 1)
            die("syntax error: ".htmlspecialchars($line)."\n");
        $oprand1 = evaloprand($oprand);
        if (($oprand1 < 0)||($oprand1 > 0x7f))
            die("out of range: ".htmlspecialchars($line)."\n");
        $l = $l | $oprand1;
    } else {
        if (count($array) != 2)
            die("syntax error: ".htmlspecialchars($line)."\n");
        $oprand1 = evaloprand($array[0]);
        if (($oprand1 < 0)||($oprand1 > 0x7f))
            die("out of range: ".htmlspecialchars($line)."\n");
        $oprand2 = evaloprand($array[1]);
        if (($oprand2 < 0)||($oprand2 > 1))
            die("out of range: ".htmlspecialchars($line)."\n");
        $l = $l | $oprand1 | ($oprand2 << 7);
    }
#printf("%08x: %04x\n", $key, $l);
    $codelist[$key] = $l;
}

header('Content-Type: text/plain');

print <<<EOO
:020000040000FA

EOO;

$s = "";
foreach ($codelist as $key => $val) {
    if ($s == "")
        $sum = 0;
    $h = ($val >> 8) & 0xff;
    $l = $val & 0xff;
    $s .= sprintf("%02X%02X", $l, $h);
    $sum += ($h + $l);
    if ((@$codelist[$key + 1] === null)||(($key & 7) == 7)) {
        $len = strlen($s) / 2;
        $addr = ($key - ($len / 2 - 1)) * 2;
        $sum += $len;
        $sum += ($addr >> 8) & 0xff;
        $sum += $addr & 0xff;
        $sum = (0x10000 - $sum) & 0xff;
        printf(":%02X%04X00%s%02X\n", strlen($s) / 2, $addr, $s, $sum);
        $s = "";
    }
}

print <<<EOO
:00000001FF

EOO;

?>

今回は、ソースと同じ場所にincludeというディレクトリを用意する必要かあります。

まず、標準の開発環境であるMPLABをMicrochip Technorogyのページからダウンロードインストールします。インストール後にできたMPASM suiteの下にある*.incを、includeの中にコピーしてください。アセンブラソース中でincludeを使うと、ここから読み込まれます。

アセンブラの擬似命令ですが、すべてをサポートしているわけではありません。ただし、アセンブラそのものを自作するわけですので、必要な擬似命令は自分で追加することができます。

また、ソースの中に書ける式も、加減と、ビットの論理積・論理和だけです。これも、必要に応じて自分で追加しましょう。

アセンブラ作りの第一歩

さて、アセンブラなんて100年ぶり、という方もいらっしゃるでしょうから、ちょっと復習です。

アセンブラの最大の特徴は、原則としてソースの1行がCPUの1命令に対応するということです。CPUの命令は、すべて番号で表わされます。1番がif、2番がgotoといった具合です。さすがにこれでは、読むのも書くのも大変です。そこで、命令の番号を文字にしたものがアセンブリ言語です。CPUが違えば、アセンブリ言語も違ったものになります。

命令に与えるパラメータも、その命令で扱えるものに限られます。C言語では*(q++)=*(p++);と書けますが、アセンブラでは1ステップずつに分解しないといけません。Z80にはLDIという命令があり、DEレジスタとHLレジスタに限ってこのような処理が可能でしたが、多くのCPUでは複数の命令が必要です。

こうして見ると、アセンブラは融通の利かない時代遅れの言語に見えるかも知れません。しかし、CPUの全ての機能を100%引き出すことができるのは、アセンブラだけです。たとえば、C言語のプログラムが起動するときは、スタックやデータのためにメモリを初期化する必要があります。アセンブリ言語ではメモリを使わないでプログラムを動かすことができるのですが、C言語ではこういうことはできませんから、起動時の処理をC言語で書くことはできないわけです。同じ理由で、割り込み処理やOSなどを書く際も、アセンブラは避けて通ることができません。

そんなわけで、この機会にアセンブラに少しでも興味を持っていただければと思います。次回はアセンブラの内部がどうなっているのかを解説していきます。

おすすめ記事

記事・ニュース一覧