FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

オープンソースのマイクロプロセッサ テストプログラム生成ツール MicroTESK 試行 (2. NMLファイルを読み解く)

前回少し調査した、テストパタン生成ツールのMicroTESKだが、どのようにして使うのか調査したいので、あらかじめ提供されているモデルを改造してオリジナルのCPUモデルを構築してみようと思う。

関連記事

最もシンプルなモデル cpu.nml

MicroTESK は NML モデルと呼ばれるモデル記述言語を使ってCPUのモデルを記述している。 これについては例を見てみるほうが速い。 arch/demo/cpu/model/cpu.nmlというファイルが提供されており、それを開いてみる。 中身は大きく分けて以下のように構成されている。

  • CPUのモデルに存在するレジスタの長さ、メモリの大きさ
  • 命令の種類。挙動。オペランドの割り付け

最初にCPUモデルの基本事項を設定する。例えば、以下のように記述されており、レジスタ数は32本、演算の単位は8バイトであることが分かる。 レジスタファイルとしてGPR(8ビット長、16本)を定義し、PCはメモリのサイズに合わせて8ビットだ。

/*
 * Description:
 *
 * Example of a specification of a trivial microprocessor ISA.
 * Provided to demonstrate various features of nML.
 */

////////////////////////////////////////////////////////////////////////////////
// Constants

let   MEM_SIZE = 2 ** 8 // Memory size, 2 to the power of 8
let REG_NUMBER = 16     // Number of general-purpose registers

////////////////////////////////////////////////////////////////////////////////
// Types

type  INDEX = card(8) // 8-bit unsigned integer
type NIBBLE = card(4) // 4-bit unsigned integer
type   BYTE = int(8)  // 8-bit signed integer

////////////////////////////////////////////////////////////////////////////////
// Registers and Memory

// General-purpose registers.
reg GPR[REG_NUMBER, BYTE] // Format: <name>[<number>, <type>]

// A register for storing the program counter.
reg PC[INDEX] // This format means that there is 1 register of type INDEX

// A memory line
mem M[MEM_SIZE, BYTE]

次に、アドレッシングモードを記述していく。 アドレッシングモードは、主にオペランドに対してどのような値を取ることができるかを設定している。 一般的なRISCマシンであれば、オペランドレジスタか定数だ。 しかし、x86のようなCISCマシンであればメモリを取ることもできる。このように、アーキテクチャによってサポートしているアドレッシングモードが異なるため、 ここで定義しておく。 また、これらのオペランドが、どの命令でどのように使うことができるかも指定している。

例えば以下のような記述だ。まずは定数として6ビットの定数を定義している。これはオペランドの1つとして定義している。 次にレジスタだ。レジスタは全部で16本のため、レジスタインデックスとしては4ビットを定義している。 最後にメモリアクセスだ。メモリアドレスは6ビット長のアドレスインデックスを定義している。

ここでimageという値を設定していることに注意してほしい。 これはこれらの記述子が、どのようにオペランドに割り当てられるかを定義したものだ。レジスタは4ビットしか使っていないので、上に2ビットを乗せていることに注意してほしい。

(ランダムテストパタン生成ツールだから、こんな情報いらないじゃないか!?と思うのだが、どうやらパタンを生成するときに内部でシミュレーションを行っているようだ。このために必要なのだろうか?)

ちなみに、このアドレッシングのimageの値を少しずらしたりすると、実際にパタンを生成するときに思いっきりエラーが吐かれたりするので注意する。

////////////////////////////////////////////////////////////////////////////////
// Addressing Modes

// An addressing mode for an immediate value.
mode IMM(i: int(6)) = sign_extend(BYTE, i) // Value expression
  syntax = format("[%d]", i) // Textual format
  image  = format("%6s", i)  // Binary format

// An addressing mode for a register access.
mode REG(i: NIBBLE) = GPR[i]
  syntax = format("R%d", i)
  image  = format("00%4s", i)

// An addressing mode for a memory access.
mode MEM(i: card(6)) = M[i]
  syntax = format("(%d)", i)
  image  = format("%6s", i)

// Addressing modes are united into groups.
mode OPRNDL = MEM | REG
mode OPRNDR = OPRNDL | IMM

次に命令の定義だ。以下のようにして命令のコードと挙動を定義している。 これは説明不要。またimageがこちらでも定義されていることに注意する。 最後に、ADD, SUB, MOVという3種類の定義した命令をまとめてALUというオペレーション(コマンド)を定義している。

////////////////////////////////////////////////////////////////////////////////
// Arithmetic and Logic Instructions

op ADD()
  syntax = "add"
  image  = "00"
  action = {
    DEST = SRC1 + SRC2;
    // Function 'trace' prints text messages to the simulator log
    trace("%d + %d = %d", SRC1, SRC2, DEST);
  }

op SUB()
  syntax = "sub"
  image  = "01"
  action = { DEST = SRC1 - SRC2; }

op MOV()
  syntax = "mov"
  image  = "10"
  action = { DEST = SRC2; }

// A common alias for ADD, SUB and MOV
op ALU = ADD | SUB | MOV

このALUコマンドを使用する命令が、以下の定義だ。同様にsyntaximageが定義されていることに注意すること。 これも見ればわかるが、要するにSRC1とSRC2に対してコマンドで定義される演算を実行し、その結果をop1に代入している (つまりこれは2オペランド命令、 op1 = op1 op op2 の命令系列を定義していることに等しい)

ここで気がつくのは、"%s%s00%s"という表記だ。コマンドとオペランド2つの組み合わせなわけであるが、上記の通りコマンドは2ビット、オペランドは6ビットであるので、それに00をくっつけて合計16ビットの命令であることが分かる。

このように命令長もケアしながら定義していかないと、パタン生成時のシミュレーションでエラーを吐いてしまいパタンを生成できない。

// A common specification of all ALU instructions that describes their common
// format, performs all common actions and delegates unique responsibilities
// to specific operations (or subinstructions).

op alu_instr(command: ALU, op1: OPRNDL, op2: OPRNDR)
  syntax = format("%s %s %s", command.syntax, op1.syntax, op2.syntax)
  image  = format("%s%s00%s", command.image, op1.image, op2.image)
  action = {
    SRC1 = op1;
    SRC2 = op2;
    command.action;
    op1 = DEST;
    PC = PC + 2;
  }

さらに、同様に制御系の命令の定義が続いている。

////////////////////////////////////////////////////////////////////////////////
// Control Transfer Instructions

// Transfers control to the specified address.
//
// IMPORTANT NOTE: The target address can be specified via a label. In order to
// use the label name rather than an address constant in the generated test
// program, special label-based addressing modes must be described in
// the specifications.

label mode J_LABEL(value: INDEX) = value
  syntax = ""
  image  = format("%s", value)

mode J_IMM(value: INDEX) = value
  syntax =  format("0x%X", value)
...

最後に、instruction上記alu_instrbranch_instrを組み合わせたものとして定義し、完成だ。

////////////////////////////////////////////////////////////////////////////////
// Entry Point
//
// By nML conventions, the "instruction" operation is the root of the tree
// describing a microprocessor ISA.

op instruction = alu_instr
               | branch_instr