公開日:2020/11/07
title.png

13th Season


#13-5 DUEのTRNGを使ってみよう

 Arduino DUEに搭載されたMCU、SAM3X8Eには、TRNGユニットと言う乱数発生器が搭載されています。

 Arduino等のプログラムで通常使用しているrandom()関数やrand()関数は、実は「乱数」ではなくて、「乱数に見える数列」を順番に出力してくるだけの関数であり、「擬似乱数列」と呼ばれています。一般に、真の乱数を発生する装置は、熱雑音や放射性元素が崩壊する時に放出する放射線をカウントするなどして生成しています。
 筆者が「#6-10 真の乱数を生成する」の記事で作った真乱数発生プログラム(true_random.ino)は、「フローティングノードの雑音を使った乱数の生成」と、捉えて良いと思います。

 SAM3X8Eに搭載されたTRNGユニットは、データシートからはアルゴリズムを知ることが出来ないのですが、何らかのランダムな現象を使って乱数を生成するユニットだと考えられます。データシートには、「Passed NIST Special Publication 800-22 Tests Suite」と記載されているので、筆者の作ったいい加減(?)なプログラム(true_random.ino)よりは、ちゃんとした乱数を発生してくれるのでしょう。

※NISTのドキュメント(800-22)をチラ見してみたところ、100ページ以上の大作でした。勿論、読んではいません(1^^;)。分量もさることながら、内容について行けるとは到底思えなかったので……。

 今回のテーマは、UNO等、他のArduinoに搭載されていなかった、真乱数発生器TRNGユニットを使ってみることです。

まずtrue_random()関数

 筆者が作ったプログラム(true_random.ino)で作成したtrue_random()関数は、フローティングノードのAD変換結果が、ランダムに変動することを利用した真乱数発生関数です。アルゴリズムの詳細については重複を避けたいと思いますので、以下記事を参照してください。

  「#6-10 真の乱数を生成する

 このプログラムはコアライブラリだけを使っているので、AD変換器を持っていてanalogRead()関数が使えるArduinoなら、どの機種(MCU)でも使用可能なことが利点の1つです。DUEにもAD変換器が搭載されていてanalogRead()関数が使えるので、まずは、true_random.inoをDUEでコンパイルして使ってみようと思います。

※DUEのコアライブラリでは、デフォルトでanalogRead()関数の解像度が10ビットになっています。DUEのAD変換器は12ビットなので、「analogReadResolution (12);」として、12ビット解像度に設定して使いました。この方が、乱数の元となるノイズ成分が大きくなって有利なハズ。

 DUE対応版のプログラムをリンクしておきます。

  DUE対応版プログラム:true_random.ino

 例によって、オリジナルが差し替わっています。DUEであっても元のプログラムのままで、ほぼそのままコンパイル可能だったのですが、一部uint32などの未定義変数型を宣言したり、解像度を12ビットに設定したり等、ちょっとした修正を施してあります。


 早速実験してみましょう。
 未使用(未接続、フローティング)のアナログ入力端子を使います。デフォルトではA0端子です(↓)。

  const int ana_TRAND=0;  // noise source

 DUEのA0端子をフローティング(未接続)としてあれば、後は、プログラム(true_random.ino)をDUEにコンパイル&書き込みするだけです。シリアルモニタを開いて以下の様に実験してみましょう。

  実験サンプル:【 】内はシリアルモニタからの送信データ
  【trnd,1000】
  OK+ true_random(), 729866285, 17, 32.00, 1.247
  OK+ true_random(), 1293107885, 18, 33.00, 1.170
  OK+ true_random(), 1924787196, 20, 33.75, 1.045
  OK+ true_random(), 1591128812, 19, 33.58, 1.236
  OK+ true_random(), 178825263, 17, 32.00, 1.161
  OK+ true_random(), 1217012900, 17, 32.00, 1.205
  OK+ true_random(), 2847499884, 17, 32.00, 1.201
  OK+ true_random(), 3066083814, 17, 32.00, 1.203
  OK+ true_random(), 4111812782, 17, 32.00, 1.205
  OK+ true_random(), 1000073191, 17, 32.00, 1.200
    :
    : (中略)
    :
  OK+ true_random(), 3529807522, 17, 32.00, 1.197
  OK+ true_random(), 985837999, 17, 32.00, 1.204
  OK+ true_random(), 426138272, 17, 32.00, 1.195
  OK+ true_random(), 2511955375, 17, 32.00, 1.205
  OK: trnd (err=0/1000)

 各行1個目の数値から順に、真乱数値、AD変換の繰り返し回数、雑音成分の蓄積ビット数、生成に要した時間[ms]です。繰り返し回数の所が、UNO等と比べて、ちょっと怪しいです。上に示した一部のデータだけでも明らかなのですが、複数回の真乱数生成において、最初の数回は繰り返し回数がばらついている(オレンジ着色)のですが、暫くすると安定して、ずっと17回で乱数が求められています。

※ランダムな挙動(雑音)が必要なので、ずっとばらつき続けているなら返って好都合なのですが、上の例では最初だけばらついて、暫くすると安定してしまっているのが気にかかります。

 ちょっと、データを記録していなかったのだけど、初めの数回で"ERR+"が出ることもありました。AD変換を連続して実行していると、アナログ端子A0が何らかの安定状態へ収束しているのかもしれません。だとすると、「乱数」を生成するという目的に対しては、ちょっと否定的ですね。

 発生した真乱数のヒストグラムを書かせてみました。UNOのデータと並べて比較してみましょう(下図)。

a135_histd.pngar6a_histr.png

 UNO(上右図)に比べるとDUE(上左図)の方が、凸凹が大きく、累積度数分布も直線からの乖離が大きく見えます。

 以下表に、UNOやSTM32など、他のMCUの評価結果(再掲)と共にDUEの評価結果を追加します。

Arduino 真乱数
一様性
繰り返し回数N ave(B[i]) 生成時間[ms]
MAX MIN AVE MAX MIN AVE MAX MIN AVE
UNO ATmega328P-PU 91 20 43 1.68 0.36 0.98 17.4 5.4 9.5
Pro Mini ATmega328P-AU 59 20 33 1.61 0.57 1.09 14.3 5.7 8.3
Micro ATmega32U4-MU 37 18 25 1.83 0.90 1.40 8.8 5.2 6.6
STM32 STM32F103C08 20 17 17 1.88 1.60 1.86 1.5 1.1 1.3
DUE SAM3X8E 20 17 17 1.88 1.69 1.88 1.2 1.0 1.2

 一様性の評価については、ちょっと凸凹が目立ったことと、初めて乱数を発生させてから、数回までの間が安定しなかったことも含めて、△の判定にしておきました。○判定にしても良いくらいだとは思うのですが、感覚的に……。
 生成速度はSTM32とほぼ同等で、平均約1.2msでした。

TRNGユニットの使い方

 続いては肝心なTRNGユニットです。SAM3X8Eのデータシートによると、I/Oレジスタはシンプルです。イネーブルビットだけを含む制御レジスタと、割り込み関係のレジスタ、生成した真乱数の読み出しレジスタ、これだけです。
 まずは、初期設定から見て行きましょう(↓)。

  const int rng_KEY=0x524e4700;  // rng: security key 'RNG\0'

  // PMC setting
  // TRNG peripheral id=41
  REG_PMC_PCER1=PMC_PCER1_PID41;
  // start TRNG
  REG_TRNG_CR=rng_KEY | TRNG_CR_ENABLE;


 いつもの様に、TRNGにクロックを供給しないと、何も始まりません。TRNGのペリフェラルIDは41なので、PMCのPMC_PCER1レジスタでTRNGへのクロック供給を許可します。
 制御レジスタTRNG_CRには、イネーブルビットTRNG_CR_ENABLEがあります。ここに1を立てるとTRNGが動き始めるのですが、この時、キーコードを一緒に書き込む必要があります。キーコードはASCII文字で'RNG'の3バイトです。
 以下、真乱数の読み出しコードを示し、その下にコメントを記入します。

  // generate
  for (idx=0; idx<cnt; idx++)
  {
    // polling DATARDY flag
    while (!(REG_TRNG_ISR&TRNG_ISR_DATRDY))
      ;


 真乱数を生成するのに、SAM3X8EのCPUクロックで84クロックかかります。fcpu=84MHzなので、1μsです。84クロックかけて新しい真乱数が生成されたら、ステータスレジスタTRNG_ISRのDATRDYビットに1が立つ仕組みになっています。
 DATRDYビットで割り込みをかけることもできるのですが、84クロック(1μs)毎に割り込みがかかってしまうので、返って使い難いです。上のコードの様に、DATRDYフラグをポーリングした方が絶対楽チンです。

    // get random number
    trng=REG_TRNG_ODATA;


 DATRDY=1を確認したら、TRNG_ODATAレジスタを読み出すと、32ビットの真乱数値を得ることが出来ます。これだけ、簡単。

    // clear DATRDY flag if set
    REG_TRNG_ISR;


 データシートによると、TRNG_CRレジスタのENABLE=1に設定すると、そこから84クロック毎にTRNG_ODATAレジスタが更新され続けるようです。TRNG_ODATAレジスタおよび、TRNG_ISRレジスタの読み出し如何にかかわらず、です。
 なので、念のため、TRNG_ODATAレジスタを読み出した後で、DATRDYフラグをクリアしています。さもないと、最悪、次回読み出した時に同じ乱数値を読んでしまう恐れがあります。ここ、地味に重要。DATRDYフラグはTRNG_ISRレジスタを読み出すと自動クリアされるので、次回のポーリングによって、毎回新しい真乱数値を読み出すことが出来ます。

    // show data
    sprintf (lbuf,"OK+ trng, %lu, %d",trng,idx);
    Serial.println (lbuf);
  }


 ここは、読み出した乱数値をシリアルモニタへ送信しただけです。

 先程リンクしたプログラム(true_random.ino)に【trng】コマンドが追加してあって、上の処理が入っています。全体像はプログラムでご確認ください。

TRNGを使った実験

 と、言う事で、先程のtrue_random()関数を評価した同じプログラムでTRNGユニットの実験を進めてみましょう。プログラム(true_random.ino)をDUEにコンパイル&書き込むと、シリアルモニタから受け付けられる以下のコマンドが追加されています。


  実験サンプル:【 】内はシリアルモニタからの送信データ
  【trng,1000】
  OK+ trng, 1853804265, 0
  OK+ trng, 95189728, 1
  OK+ trng, 1908321158, 2
  OK+ trng, 3872918495, 3
  OK+ trng, 1064281020, 4
  OK+ trng, 2156582540, 5
  OK+ trng, 4104967046, 6
  OK+ trng, 2912567590, 7
    :
    : (中略)
    :
  OK+ trng, 3799905640, 996
  OK+ trng, 3536397927, 997
  OK+ trng, 1827007088, 998
  OK+ trng, 379814260, 999
  OK: trng (1000)

 1個目の数値がTRNGユニットから読みだした乱数値です。2個目の数値は単なるインデックスで0〜カウントアップしています。
 データシートによると、例のNISTのドキュメントで評価済みのユニットなので、それに比べたらお恥ずかしい限りですが、表示された真乱数(1,000個)の発生頻度(ヒストグラム📊)を作って見ました(下図)。

a135_histg.png

 少し凹凸が大きいようにも思えますが、特に偏りは無い様です。使用する雑音のモデル次第では、一様乱数にならない場合もあるみたいなので、念のため確認しておきました。

 拙作true_random()関数では、ノイズ源であるDUE(SAM3X8E)のAD変換器の挙動が怪しいですし、変換時間も1.2ms程かかります。対して、TRNGでは特に問題なく、生成時間も1μs程度なので、やはり、DUEではTRNGを使うのが良いでしょう。


 当たり前な結論でゴメンなさい(1^_^;)。TRNGユニットがとてもお手軽に使えることが判った事は収穫でした。自前のtrue_random()関数については、AD変換器の挙動次第では怪しげな結果になる場合があることが判ってしまって、ちょっとショックですorz。


btn_arduino.png

btn_prev.png btn_next.png

home