陳鍾誠

Version 1.0

MCU0 組譯器 – 超簡易組譯器 (第二版)

組合語言輸入檔

檔案: sum.as0m

LOOP:   LD    I    
        CMP   K10  
        JEQ   EXIT
        ADD   K1   
        ST    I    
        LD    SUM  
        ADD   I    
        ST    SUM  
        JMP   LOOP
EXIT:		RET
SUM:    WORD  0    
I:      WORD  0    
K1:     WORD  1    
K10:    WORD  10   

程式碼

檔案: as0m.c

#include "mcu0m.h"
// 符號表宣告
int symTop = 0;
Pair symTable[1000];
// 指令表宣告
int opTop  = 8;
Pair opTable[] = {{"WORD", WORD}, {"LD", LD}, {"ADD", ADD},{"JMP", JMP}, 
                  {"ST", ST}, {"CMP", CMP}, {"JEQ", JEQ}, {"RET", RET}};
// 函數:將一行組合語言拆成 label, op, param 等三個欄位
// 例如  LOOP: LD I  會被拆成 label=LOOP, op=LD, param=I
void parse(char *line, char *label, char *op, char *param) {
  strcpy(label, ""); strcpy(op, ""); strcpy(param, "");
  char *rest=line;
  char *ptr = strchr(line, ':'); // 找看看是否這行有 : 符號,像是 LOOP: LD I 就有 
  if (ptr != NULL) { // 如果有 : 符號,代表前面是標記
    rest = ptr + 1; 
    sscanf(line, "%[^: \t]", label); // 將標記部分放入 label 欄位 (例如: label=LOOP)
  }
  int count = sscanf(rest, "%s %s", op, param); // 將後半剩下的部分拆成 (op, param) 兩個欄位
  if (count == 1) strcpy(param, ""); // 處理參數太少的情況
  if (count == 0) strcpy(op, "");
}
// 查詢表格 table 中有沒有名稱為 name 的項目,有的話傳回該項目 (pair)
Pair *table_get(Pair *table, int top, char *name) {
  int i;
  for (i=0; i<top; i++) {
    if (strcmp(name, table[i].name)==0)
      return &table[i];
  }
  return NULL;
}
// 將 pair=(name, value) 這個項目加入到表格中
int table_put(Pair *table, int *top, char *name, int value) {
  Pair *pair = table_get(table, *top, name);
  if (pair == NULL) {
    pair = &table[*top];
    pair->value = value;
    pair->name = strdup(name);
    (*top)++;
    return 1;
  } 
  return 0;
}
// 組譯器第一輪: 記住符號位址
void pass1(char *asmFile) {
  printf("\n============== pass1 ===============\n");
  FILE *file = fopen(asmFile, "rt"); // 用 rt 模式開啟文字檔
  char line[MAX_LEN], label[MAX_LEN], op[MAX_LEN], param[MAX_LEN];
  UINT16 address=0, i;
  for (i=1; fgets(line, MAX_LEN, file); i++) { // 每次讀一行
    parse(line, label, op, param);             
    if (strcmp(op, "")==0) continue;
    printf("%02d %04x %s", i, address, line);
    if (table_get(opTable, opTop, op)==NULL) { // 若不認識該行指令欄
      printf("op %s: not found!\n", op); // 就顯示錯誤
      exit(1);
    }
    if (strcmp(label, "")!=0) { // 如果有標記
      if (table_put(symTable, &symTop, label, address)==0) { // 新增標記,假如失敗
        printf("Error: symbol %s duplicate\n", label); // 就顯示錯誤
        exit(1);
      }
    }
    address += 1; // 每行指令都佔一個 WORD
  }
  fclose(file);
}
// 將指令 (op, param) 翻譯成 16 位元機器碼傳回
UINT16 translate(char *label, char *op, char *param) {
  Pair *op1 = table_get(opTable, opTop, op);
  if (op == NULL) {
    printf("Error: op(%s) not found!\n", op);
    exit(0);
  }
  UINT16 code = 0;
  if (op1->value == WORD) { // 處理 K10:    WORD  10  的這種情況
    sscanf(param, "%hu", &code); 
  } else {
    UINT16 paddress;
    if (op1->value == RET) // RET 指令後面沒有跟著參數,所以補 0
      paddress = 0;
    else {
      Pair *sym = table_get(symTable, symTop, param); // 處理一般性指令,查詢符號位址
      if (sym == NULL) {
        printf("Error: symbol(%s) not found!\n", param);
        exit(0);
      }
      paddress = sym->value; // paddress=符號位址
    }
    code = (op1->value << 12) | paddress; // 將 op, address 編成 16 位元的機器碼
  }
  return code;
}
// 組譯器第二輪: 將所有指令翻譯成機器碼並寫入目的檔
void pass2(char *asmFile, char *objFile) {
  printf("\n============== pass2 ===============\n");
  FILE *file = fopen(asmFile, "rt");
  FILE *oFile = fopen(objFile, "wb");
  char line[MAX_LEN], label[MAX_LEN], op[MAX_LEN], param[MAX_LEN];
  UINT16 address = 0;
  while (fgets(line, MAX_LEN, file)) {
    parse(line, label, op, param);
    if (strcmp(op, "")==0) continue;
    UINT16 code = translate(label, op, param); // 將每行指令都翻成機器碼
    printf("%04x %04x %s", address, code, line);
    fwrite(&code, sizeof(code), 1, oFile);
    address += 1;
  }
  fclose(file);
  fclose(oFile);
}
// 組譯器的主程式
int main(int argc, char *argv[]) {
  if (argc != 3) { printf("as0m <asmFile> <objFile>\n"); exit(1); }
  char *asmFile=argv[1], *objFile=argv[2];
  pass1(asmFile);          // 組譯器第一輪,計算符號表位址
  pass2(asmFile, objFile); // 組譯器第二輪,編機器碼後輸出到目的檔
}

執行結果

D:\ccc\mcu02>gcc as0m.c -o as0m

D:\ccc\mcu02>gcc vm0m.c -o vm0m

D:\ccc\mcu02>as0m sum.as0m sum.ob0m

============== pass1 ===============
01 0000 LOOP:   LD    I
02 0001         CMP   K10
03 0002         JEQ   EXIT
04 0003         ADD   K1
05 0004         ST    I
06 0005         LD    SUM
07 0006         ADD   I
08 0007         ST    SUM
09 0008         JMP   LOOP
10 0009 EXIT:   RET
12 000a SUM:    WORD  0
13 000b I:      WORD  0
14 000c K1:     WORD  1
15 000d K10:    WORD  10

============== pass2 ===============
0000 000b LOOP:   LD    I
0001 400d         CMP   K10
0002 5009         JEQ   EXIT
0003 100c         ADD   K1
0004 300b         ST    I
0005 000a         LD    SUM
0006 100b         ADD   I
0007 300a         ST    SUM
0008 2000         JMP   LOOP
0009 e000 EXIT: RET
000a 0000 SUM:    WORD  0
000b 0000 I:      WORD  0
000c 0001 K1:     WORD  1
000d 000a K10:    WORD  10

D:\ccc\mcu02>vm0m sum.ob0m
PC=0000 IR=000b  SW=1000 A=0000=   0
PC=0001 IR=400d  SW=8000 A=0000=   0
PC=0002 IR=5009  SW=8000 A=0000=   0
PC=0003 IR=100c  SW=8000 A=0001=   1
PC=0004 IR=300b  SW=8000 A=0001=   1
PC=0005 IR=000a  SW=8000 A=0000=   0
PC=0006 IR=100b  SW=8000 A=0001=   1
PC=0007 IR=300a  SW=8000 A=0001=   1
PC=0008 IR=2000  SW=8000 A=0001=   1
PC=0000 IR=000b  SW=8000 A=0001=   1
PC=0001 IR=400d  SW=8000 A=0001=   1
PC=0002 IR=5009  SW=8000 A=0001=   1
PC=0003 IR=100c  SW=8000 A=0002=   2
PC=0004 IR=300b  SW=8000 A=0002=   2
PC=0005 IR=000a  SW=8000 A=0001=   1
PC=0006 IR=100b  SW=8000 A=0003=   3
PC=0007 IR=300a  SW=8000 A=0003=   3
PC=0008 IR=2000  SW=8000 A=0003=   3
PC=0000 IR=000b  SW=8000 A=0002=   2
PC=0001 IR=400d  SW=8000 A=0002=   2
PC=0002 IR=5009  SW=8000 A=0002=   2
PC=0003 IR=100c  SW=8000 A=0003=   3
PC=0004 IR=300b  SW=8000 A=0003=   3
PC=0005 IR=000a  SW=8000 A=0003=   3
PC=0006 IR=100b  SW=8000 A=0006=   6
PC=0007 IR=300a  SW=8000 A=0006=   6
PC=0008 IR=2000  SW=8000 A=0006=   6
PC=0000 IR=000b  SW=8000 A=0003=   3
PC=0001 IR=400d  SW=8000 A=0003=   3
PC=0002 IR=5009  SW=8000 A=0003=   3
PC=0003 IR=100c  SW=8000 A=0004=   4
PC=0004 IR=300b  SW=8000 A=0004=   4
PC=0005 IR=000a  SW=8000 A=0006=   6
PC=0006 IR=100b  SW=8000 A=000a=  10
PC=0007 IR=300a  SW=8000 A=000a=  10
PC=0008 IR=2000  SW=8000 A=000a=  10
PC=0000 IR=000b  SW=8000 A=0004=   4
PC=0001 IR=400d  SW=8000 A=0004=   4
PC=0002 IR=5009  SW=8000 A=0004=   4
PC=0003 IR=100c  SW=8000 A=0005=   5
PC=0004 IR=300b  SW=8000 A=0005=   5
PC=0005 IR=000a  SW=8000 A=000a=  10
PC=0006 IR=100b  SW=8000 A=000f=  15
PC=0007 IR=300a  SW=8000 A=000f=  15
PC=0008 IR=2000  SW=8000 A=000f=  15
PC=0000 IR=000b  SW=8000 A=0005=   5
PC=0001 IR=400d  SW=8000 A=0005=   5
PC=0002 IR=5009  SW=8000 A=0005=   5
PC=0003 IR=100c  SW=8000 A=0006=   6
PC=0004 IR=300b  SW=8000 A=0006=   6
PC=0005 IR=000a  SW=8000 A=000f=  15
PC=0006 IR=100b  SW=8000 A=0015=  21
PC=0007 IR=300a  SW=8000 A=0015=  21
PC=0008 IR=2000  SW=8000 A=0015=  21
PC=0000 IR=000b  SW=8000 A=0006=   6
PC=0001 IR=400d  SW=8000 A=0006=   6
PC=0002 IR=5009  SW=8000 A=0006=   6
PC=0003 IR=100c  SW=8000 A=0007=   7
PC=0004 IR=300b  SW=8000 A=0007=   7
PC=0005 IR=000a  SW=8000 A=0015=  21
PC=0006 IR=100b  SW=8000 A=001c=  28
PC=0007 IR=300a  SW=8000 A=001c=  28
PC=0008 IR=2000  SW=8000 A=001c=  28
PC=0000 IR=000b  SW=8000 A=0007=   7
PC=0001 IR=400d  SW=8000 A=0007=   7
PC=0002 IR=5009  SW=8000 A=0007=   7
PC=0003 IR=100c  SW=8000 A=0008=   8
PC=0004 IR=300b  SW=8000 A=0008=   8
PC=0005 IR=000a  SW=8000 A=001c=  28
PC=0006 IR=100b  SW=8000 A=0024=  36
PC=0007 IR=300a  SW=8000 A=0024=  36
PC=0008 IR=2000  SW=8000 A=0024=  36
PC=0000 IR=000b  SW=8000 A=0008=   8
PC=0001 IR=400d  SW=8000 A=0008=   8
PC=0002 IR=5009  SW=8000 A=0008=   8
PC=0003 IR=100c  SW=8000 A=0009=   9
PC=0004 IR=300b  SW=8000 A=0009=   9
PC=0005 IR=000a  SW=8000 A=0024=  36
PC=0006 IR=100b  SW=8000 A=002d=  45
PC=0007 IR=300a  SW=8000 A=002d=  45
PC=0008 IR=2000  SW=8000 A=002d=  45
PC=0000 IR=000b  SW=8000 A=0009=   9
PC=0001 IR=400d  SW=8000 A=0009=   9
PC=0002 IR=5009  SW=8000 A=0009=   9
PC=0003 IR=100c  SW=8000 A=000a=  10
PC=0004 IR=300b  SW=8000 A=000a=  10
PC=0005 IR=000a  SW=8000 A=002d=  45
PC=0006 IR=100b  SW=8000 A=0037=  55
PC=0007 IR=300a  SW=8000 A=0037=  55
PC=0008 IR=2000  SW=8000 A=0037=  55
PC=0000 IR=000b  SW=8000 A=000a=  10
PC=0001 IR=400d  SW=4000 A=000a=  10
PC=0002 IR=5009  SW=4000 A=000a=  10
PC=0009 IR=e000