IoT何をいまさら(91) ATSAMD51、CCL、極小のオンチップFPGA

Joseph Halfmoon

最近のマイコンには従来のマイコンになかった周辺回路が搭載されているものがあります。Microchip社のATSAMD51マイコンに搭載されているCCL (Configurable Custom Logic)もそんな周辺の一つじゃないかと思います。これ、極小ながらオンチップのFPGAです。ただし、小さすぎるので用途は限られますが。

(今回実験につかったソース全文は末尾に。Arm Cortex M4FコアのATSAMD51P19Aを搭載のWio Terminal実機で動作確認。ソフトウエアはArduinoプラットフォーム。)

まずCCL、Configurable Custom Logicで何ができるのか、今回実験した「回路」をご覧いただきましょう。

ATSAMD51 CCL 3 input AND単なる3入力ANDゲート1個です。でも、このゲートはATSAMD51マイコンのチップの内部に構成されているのであります。入出力の端子のカッコ内にPA04などと書いてあるのは、マイコンの端子名です。

任意の(容量の制限はあり)論理回路をオンチップ上に構成できるのであります。上記のような組み合わせ回路だけでなく、順序回路も可能です。LUT (Look Up Table)ベースの「コンフィギャラブル」な論理CELLです。まさにFPGAと一緒。ただし、今回実験したATSAMD51P19Aに搭載の容量は極小です。

  • 2グループ(論理CELLにあたる)搭載
  • 1グループあたり2個のLUT+1個のF/F(またはLatch)+2出力、1出力は順序回路可、1出力(奇数)は不可
  • 1LUTあたり3入力まで
  • となりの出力を入力として使うこと可能

合計 4 LUT + 2 F/F(Latch)です。大した回路は作れません。しかし、ちょっとした「グルーロジック」が必要になったとき、外部にICを追加することなく、勿論ASICの変更などなく、マイコン内部で処理できるというのは「コスト削減」考えるとなかなかアリな機能に思えます。まあ、苦し紛れの対策用にも思えますが。組み込みマイコンの主戦場である10円玉、100円玉の世界では、いくらFPGAがお安くなったといってもなかなか搭載はなく、マイコンだけでなんとかしないとならないので。それにマイコン内部に搭載されているだけに内部の信号を回路の入力として使うことができるのはアドバンテージあるかもです(今回はやってませんが。)

なお、このようなコンフィギャラブルなロジックを搭載しているマイコンは、Cypress社のPSoCを嚆矢とするのではないかと思います。PSoCは、コンフィギャラブルなアナログ回路搭載が「売り」ですが、アナログだけでなくデジタルな回路も搭載していた記憶です。やはり小規模ですが。

実験で構成した回路

実験した回路は、上記のような3入力ANDゲート1個です。CCLの中のLUTを1個だけ使い構成しました。外側から論理ゲートとしての動作確認をするために、入力信号、出力信号ともにダイレクトに外部端子に接続しました。以下に端子のアサインを掲げます。

Arduino# RaspberryPi# WioSch SAMD51 CCL
2 GPIO23 A2/D2 PA07 OUT0
6 GPIO13 A6/D6 PA04 IN0
8 GPIO26 A8/D8 PA06 IN2
10 GPIO7 DAC1 PA05 IN1

SAMD51の実験に使っている Wio Terminalは、Arduinoプラットフォームでプログラムしています。C++の関数上はArduino#に書かれている端子番号で操作することになります。また、外部にとりだせる端子としては、Raspberry Pi互換配列の拡張端子になっています。RaspberryPi#ということでそちらのピン番号も併記しています。WioSchは、Wio Terminalの回路図上の信号名です。Wio Terminalのドキュメントなどではそちらの端子名で呼ばれているものです。SAMD51はマイコンチップの端子番号。そしてCCLがマイコン内での信号名です。

マイコンの通例ですが、CCLのあるひとつの信号は、複数の入出力端子から選択して接続できるようになっています。今回はWio Terminalの拡張端子群の中で取り出しやすい端子を選んで上記のように選択してみました。

CCLのコンフィギュレーション

CCLはハードウエアの論理回路なので、動き出してしまえばCPUの介在なく動作し続づけます。しかし、どのような論理回路にするか(コンフィギュレーション)はCPUを介して関係のレジスタにプログラムしないとなりません。

末尾のソースコードで行っているのは以下のような操作です。

  1. GCLKの中の GCLK_CCL クロック信号に適当なクロックを与える。今回はCPUクロック120MHzを設定してみた。ただし、このクロックは順序回路を構成するときにしか使われない。今回は必要ないが、念のため設定。なお、クロック停止などあるクロックの場合は要注意。
  2. MCLKの中の CLK_CCL_APB クロック信号を許可。具体的には APBC Maskのビット14を1にする。
  3. 上の外部端子の入出力を適切にCCLに接続されるようにPortを操作。面倒。
  4. 念のため、CCLにソフトウエアRESETをかける。なお、CCLの論理セルのレジスタは、LUTCTRLx.ENABLE が 0 でないと変更できない(ロジックとして動作している間は変更不可)
  5. 順序回路は使わないように設定
  6. LUTの真理値表、入力のアサインなどを設定。(設定と同時にENABLEにしているが、末尾のコードでは無題にもう一回ENABLEしていた。これは不要。)
  7. CCL全体のイネーブル
動作確認

例によって Digilent Analog Discovery2のパターンジェネレータとロジアナを使い動作確認しました。パターンジェネレータで入力信号の3ビットに0b000から0b111までの8パターンを印加し、入力3ビット+出力1ビットをロジアナで観察したものがこちら。

ATSAMD51 CCL 3 input AND Waveformちゃんと、3入力ANDゲートとして動作しています。CCL面白いです。も少し掘ってみますか。

IoT何をいまさら(90) Wio Terminal、SAMD51、GLCK周波数リスト へ戻る

IoT何をいまさら(92) ATSAMD51、TRNG、「真性」乱数ジェネレータ へ進む

実験に使ったコード全文(Arduinoフレームワーク式CPP)
// Example SAMD51 CCL
//Arduino#	RaspberryPi#	WioSch	SAMD51	CCL
//2	        GPIO23	        A2/D2	PA07	OUT0
//6	        GPIO13	        A6/D6	PA04	IN0
//8	        GPIO26	        A8/D8	PA06	IN2
//10	        GPIO7	        DAC1	PA05	IN1
#include <Arduino.h>

int counter;

void setupCCLLUT0() {
  GCLK->PCHCTRL[33].reg = 0x40; //GCLK_CCL enabled, GEN0=120MHz
  MCLK->APBCMASK.bit.CCL_ = 1; // Enable CLK_CCL_APB

  PORT->Group[0].PMUX[2].reg = 0xDD;  // PA04,PA05 FUNC_N=CCL
  PORT->Group[0].PMUX[3].reg = 0xDD;  // PA06,PA07 FUNC_N=CCL
  PORT->Group[0].PINCFG[4].reg  = 3; //PA04 INPUT ENABLE, PMUX ENABLE
  PORT->Group[0].PINCFG[5].reg  = 3; //PA05 INPUT ENABLE, PMUX ENABLE
  PORT->Group[0].PINCFG[6].reg  = 3; //PA06 INPUT ENABLE, PMUX ENABLE
  PORT->Group[0].PINCFG[7].reg  = 1; //PA07 PMUX ENABLE
  PORT->Group[0].DIRCLR.reg &= 0x0070; //PA04,05,06 input
  PORT->Group[0].DIRSET.reg &= 0x0080; //PA07 output

  CCL->CTRL.bit.SWRST = 1;    // SWRST CCL
  CCL->LUTCTRL[0].bit.ENABLE = 0; //Disable LUT0 to modifiy settings 
  CCL->SEQCTRL[0].bit.SEQSEL = CCL_SEQCTRL_SEQSEL_DISABLE; // SEQ LOGIC not used.
  CCL->LUTCTRL[0].reg = 0x80044402; // TRUTH=10000000 Io, Io, Io, LUT0 Enable
  CCL->LUTCTRL[0].bit.ENABLE = 1; //Enable LUT0 
  CCL->CTRL.bit.ENABLE = 1;    // Enable CCL
}

void checkCCLLUT0() {
  Serial.printf("CCL.CTRL 0x%02x\r\n", CCL->CTRL.reg);
  Serial.printf("CCL.SEQ0 0x%02x\r\n", CCL->SEQCTRL[0].reg);
  Serial.printf("CCL.LUT0 0x%08x\r\n", CCL->LUTCTRL[0].reg);
}

void setup() {
  Serial.begin(9600);
  while(!Serial);
  counter = 0;
  pinMode(LED_BUILTIN, OUTPUT);
  setupCCLLUT0();
}

void loop() {
  Serial.println(counter++);
  checkCCLLUT0();
  digitalWrite(LED_BUILTIN, HIGH);
  delay(2500);
  digitalWrite(LED_BUILTIN, LOW);
  delay(2500);
}