シンプルなCPUを作ってみよう (その6)

      − C言語によるクロスアセンブラの制作 −   改定 (2006.11.15)

井澤 裕司 
四十 正稔


1. はじめに

第2章から、極めてシンプルな構成のCPUを ハードウェア記述言語を用いて設計してきました.
このCPUを動作させるためには,プログラムの開発環境が必要です.

今回,修士1年の
四十 正稔 君に,C言語を用いて専用のアセンブラを開発してもらいました.
本章では,その内容を紹介します.



2. アセンブリ言語によるCPUのプログラミング

アセンブリ言語を用いて

    
     1 + 2 + 3 + ‥+ 10 = 55

の計算を,次のようにプログラミングします.

jump 先のラベルに  :1 :2 という表記法を用い,分かりやすく記述されている点に注意して下さい.

ldl r0 0 -- Reg0(L) に 値 0 をセット
ldl r1 1 -- Reg1(L) に 値 1 をセット
ldl r2 0 -- Reg2(L) に 値 0 をセット
ldl r3 10 -- Reg3(L) に 値 10 をセット
:1 -- ラベル1
add r2 r1 -- Reg2 と Reg1 の和を Reg2 に上書き
add r0 r2 -- Reg0 と Reg2 の和を Reg0 に上書き
cmp r2 r3 -- Reg2 と Reg3 を比較し,一致したら flag = 1
je 2 -- flag = 1 のとき  ラベル2 に jump
jmp 1 -- ラベル1 に無条件 jump
:2 -- ラベル2
st r0 64 -- RAM の 64番地に Reg0 の内容を出力
hlt -- CPUの停止
nop -- No Operation
nop -- No Operation


このようなソースコードをアセンブルすると,次のような mif 形式 のPROMのデータが生成されます.





3. C言語によるクロスアセンブラの制作

それでは,「クロスアセンブラ」の内容について紹介しましょう.

以下にそのソースコードを示します.


[C言語を用いたクロスアセンブラの例]

// assembler 2006/11/14 (ver.2)
// 四十正稔

// CPU15e 用のクロスアセンブラ
// mifファイル出力

#include <stdio.h>
#include <string.h>
#define IN_FILENAME "./source.asm"
#define OUT_FILENAME "./rom_init.mif"
#define LINE_MAX 2048 // asmの一行の長さの最大値

/* OP Codes */

#define MOV 0
#define ADD 1
#define SUB 2
#define AND 3
#define OR 4
#define SL 5
#define SR 6
#define SRA 7
#define LDL 8
#define LDH 9
#define CMP 10
#define JE 11
#define JMP 12
#define LD 13
#define ST 14
#define HLT 15

int call_opcode( char *, char *);
int encoder( int , char *);
 
int main()
{
       
FILE *fp;
char buf[LINE_MAX];
int output_data[256]; // 出力データの保存領域
int input_line_cnt; // 入力側の行数をカウント
int output_line_cnt; // 出力側の行数をカウント
int ir;
int label[256]; // ラベル情報の保存領域
int label_num;
int i;
char opcode[256];
char output_tmpstr[16];
int err_cnt;
 
// 初期化
input_line_cnt = 0;
output_line_cnt = 0;
err_cnt = 0;
// ラベル用変数の初期化
for (i = 0; i < 256; i++)
label[i] = 0; // ラベル用変数の初期化
// 出力用変数の初期化
for (i = 0; i < 255; i++)
output_data[i] = 0; // NOPを入れる
output_data [255] = (HLT << 11); // HLTを入れる
// ソースファイルのopen
fp = fopen(IN_FILENAME, "r" );
if (fp == NULL){
printf( "ファイルが開けませんでした。\n" );
return -1;
}
printf( "%sを読み込み中。\n" , IN_FILENAME);
// 読み込み開始
while (fgets(buf, LINE_MAX, fp) != NULL){
if( sscanf(buf, "%s" , opcode) == 1){     // opcodeの取得
      // opcodeにより処理を分ける。
      if (opcode[0] == ':' ){
      // : (ラベル番号) ラベル行の場合
      if (sscanf(buf, ":%d" , &label_num) == 1){
            label[label_num] = output_line_cnt;     // 現在の出力側行数を代入
      } else {     // エラー処理
             printf( "%d 行目の\n%sが解析できませんでした。\n" , input_line_cnt + 1, buf);
             err_cnt++;
      }
} else if (opcode[0] ==  '-' ){
      // -- コメントなので何もしない
      } else {
      // 命令行
      // opcodeごとに変換
      ir = call_opcode(opcode, buf);
      if (ir < 0){               // エラー処理
          printf( "%d 行目の\n%sが解析できませんでした。\n" , input_line_cnt + 1, buf);
          err_cnt++;
      } else {
          output_data[output_line_cnt] = ir;
          output_line_cnt++;
      }
}
}
input_line_cnt++;
}
fclose(fp);
if (err_cnt != 0){
// エラーがある場合は終了
printf( "コンパイルができませんでした(エラー %d個)\n" , err_cnt);
return -1;
} else {
// エラーがない場合は書き込み開始
// ラベル情報の置き換え
for (i = 0; i < 256; i++){
if ((output_data[i] & 0x7800 ) == (JE << 11)){       // JE命令の場合
    label_num = (output_data[i] & 0x7FF);
    output_data[i] = (JE << 11) | label[label_num];   // ラベル番号をアドレスに置き換える
}
if ((output_data[i] & 0x7800 ) == (JMP << 11)){       // JMP命令の場合
    label_num = (output_data[i] & 0x7FF);
    output_data[i] = (JMP << 11) | label[label_num]; // ラベル番号をアドレスに置き換える
}
}
//書き込みファイルをopen
fp = fopen(OUT_FILENAME, "w" );
if (fp == NULL){
     printf( "書き込みファイルが開けませんでした。\n" );
     return -1;
}
//mifファイルを出力
fprintf(fp, "DEPTH = 256;\nWIDTH = 15;\n" );
fprintf(fp, "ADDRESS_RADIX = HEX;\nDATA_RADIX = BIN;\n" );
fprintf(fp, "CONTENT\n\tBEGIN\n" );
fprintf(fp, "[00..7F]\t:\t000000000000;\n" );
for (i = 0; i < 256; i++){
encoder(output_data[i], output_tmpstr);
if (i < 16){
      fprintf(fp, "\t0%X\t:\t%s;-- %X \n" , i, output_tmpstr, output_data[i]);
} else {
      fprintf(fp, "\t%X\t:\t%s;-- %X \n" , i, output_tmpstr, output_data[i]);
}
}
fprintf(fp, "END;\n\n" );
fclose(fp);
printf( "コンパイルは正常に終了しました。\n" );
printf( "%sに書き出しました。\n" , OUT_FILENAME);
return 0;
}
}
int call_opcode( char *opcode, char *op_data)
{
int  ra, rb, data, addr, label_num;
char tmp_str[256];
// opcodeごとに変換
if (strcmp(opcode, "mov" ) == 0){
if (sscanf(op_data, "%s r%d r%d" , tmp_str, &ra, &rb) == 3)
    return (MOV << 11) | (ra << 8) | (rb << 5);
}
if (strcmp(opcode, "add" ) == 0){
if (sscanf(op_data, "%s r%d r%d" , tmp_str, &ra, &rb) == 3)
    return (ADD << 11) | (ra << 8) | (rb << 5);
}
if (strcmp(opcode, "sub" ) == 0){
if (sscanf(op_data, "%s r%d r%d" , tmp_str, &ra, &rb) == 3)
    return (SUB << 11) | (ra << 8) | (rb << 5);
}
if (strcmp(opcode, "and" ) == 0){
if (sscanf(op_data, "%s r%d r%d" , tmp_str, &ra, &rb) == 3)
    return (AND << 11) | (ra << 8) | (rb << 5);
}
if (strcmp(opcode, "or" ) == 0){
if(sscanf(op_data, "%s r%d r%d" , tmp_str, &ra, &rb) == 3)
    return (OR << 11) | (ra << 8) | (rb << 5);
}
if (strcmp(opcode, "sl" ) == 0){
if (sscanf(op_data, "%s r%d" , tmp_str, &ra) == 2)
    return (SL << 11) | (ra << 8);
}
if (strcmp(opcode, "sr" ) == 0){
if (sscanf(op_data, "%s r%d" , tmp_str, &ra) == 2)
    return (SR << 11) | (ra << 8);
}
if (strcmp(opcode, "sra" ) == 0){
if (sscanf(op_data, "%s r%d" , tmp_str, &ra) == 2)
     return (SRA << 11) | (ra << 8);
}
if (strcmp(opcode, "ldl" ) == 0){
if (sscanf(op_data, "%s r%d %d" , tmp_str, &ra, &data) == 3)
    return (LDL << 11) | (ra << 8) | data;
}
if (strcmp(opcode, "ldh" ) == 0){
if (sscanf(op_data, "%s r%d %d" , tmp_str, &ra, &data) == 3)
    return (LDH << 11) | (ra << 8) | data;
}
if (strcmp(opcode, "cmp" ) == 0){
if (sscanf(op_data, "%s r%d r%d" , tmp_str, &ra, &rb) == 3)
    return (CMP << 11) | (ra << 8) | (rb << 5);
}
if (strcmp(opcode, "je" ) == 0){
if (sscanf(op_data, "%s %d" , tmp_str, &label_num) == 2)     // 一時的にレベル番号を書き込む
    return (JE << 11) | label_num;
}
if (strcmp(opcode, "jmp" ) == 0){
if (sscanf(op_data, "%s %d" , tmp_str, &label_num) == 2)     // 一時的にレベル番号を書き込む
    return (JMP << 11) | label_num;
}
if (strcmp(opcode, "ld" ) == 0){
if (sscanf(op_data, "%s r%d %d" , tmp_str, &ra, &addr) == 3)
    return (LD << 11) | (ra << 8) | addr;
}
if (strcmp(opcode, "st" ) == 0){
if (sscanf(op_data, "%s r%d %d" , tmp_str, &ra, &addr) == 3)
    return (ST << 11) | (ra << 8) | addr;
}
if (strcmp(opcode, "hlt" ) == 0){
return (HLT << 11);
}
if (strcmp(opcode, "nop" ) == 0){
return 0;
}
return -1;
}
int encoder( int ir, char *str)     // 数値の0.1を文字に直す
{
char tmp_str[16];
int  i;
tmp_str[15] = '\0';
for (i = 14; i >= 0; i--){
if (ir & 1){
tmp_str[i] = '1' ;
} else {
tmp_str[i] = '0' ;
}
ir = ir >> 1;
}
strcpy(str, tmp_str);
return 0;
}



「クロスアセンブラ」を実行した結果の一例を以下に示します.

ソースコードの中に誤りがあると,以下のようなメッセージが出力されます.





cnp は誤りです.
これを正しい cmp に修正すると,以下のメッセージが出力されます.





4. まとめ


以上,このコンテンツで紹介してきたCPU専用の「クロスアセンブラ」の内容について述べました.

これらの資料が,有効に活用されることを願っています.


=> 「シンプルなCPUを作ってみよう」 トップページに戻る.