で、思いついてしまったのだが、bitstreamベースでAVR互換コアを再設計したら 規模がだいぶ減るのだろうか?
bitstreamベースと勝手に名付けたが、要するに 1 CLK を 8 フェーズに分割して 1 bit づつ演算することを考えたのだ。
なあに、現状でも 4 フェーズだ。8 フェーズにしたところで、どうということはない。それに、ALU は、可変 bit 長に拡張したのを既に作ってたりする。(1bit はそのままでは無理だが 4,8,16.. に対応)。 IOR や メモリは従来どおりでないと困るから基本そのまま使うことにすれば、変更すべきものは意外と少ない。
まずは、本当に減る可能性があるものなのか、ちょっと検討。
//Number of Slice Flip Flops 2 4 8 16 32
//Number of 4 input LUTs 23 39 72 159 305
//Number of occupied Slices 12 21 38 84 158
// Number used as a route-thru - 1 1 1 1
//Number of bonded IOBs 20 24 32 48 80
//Number of BUFGMUXs 1 1 1 1 1
これは、可変長版 rtavr_alu に レジスタ(1つ)をくっつけたものの Implement 結果。8bit で 38 スライスしか使っていない。これが、1 bit になったところで.. とは思うのだが、加減算は他にもある。インデックスレジスタや LDD 命令のオフセットなど。全部対応したら、多少は減るかも知れない。
まぁもともと、興味からやってみたいだけだ。とりあえず rtavr のソースを分岐させて作ってみよう。
... こんなことばかりやっているから、ツールの整備とか実機の準備が進まないわけだが、こちらの方が楽しいからしょうがない。実機を使い出したらまた興味が変わるのだろうが、今のところ実機で動かすのは "おまけ" のように感じていたりする。
ちなみに、rtavr はドキュメントを作りはじめている。それが終わったら ver 0.9 にする。実機テストが終わるまで ver 1.0 にはしないつもり。
設計で注意すべきこと
- パイプライン制御は、ずいぶん変わる。1 CLK 立たないと結果が出ないから S3_writeback が必要。
- メモリの REAd/WRITE は現在でも 前半 READ / 後半 WRITE に分かれている。前半 S3 (WRITE)/ 後半 S2(READ) として時分割?。
- 現状のものを使ってパイプライン制御だけをテストすることは可能。ただし、しっかり設計しないと、作りなおすハメになりそう。
- インデックスレジスタは、16 bit 分あるから 2bit づつの加算。
- フォワーディングが無理ならパイプランストールの条件を増やす。とにかく規模削減を優先。
デルタシグマメモ:『MachXO2 Pico Dev Kit』の記事より - ΔΣ変調の部屋
- 『AN283: シグマ・デルタ ADC/DAC の原理 (pdf)』 - アナログ・デバイセス社の アプリケーション・ノート
- 『簡易シグマデルタ ADC』 - Lattice 社のリファレンス・デザイン
login すれば、ソースコードと ドキュメントをダウンロードできるようだ。
- 差動LVDS入力 + RC だけ
- 54 LUT と書いてある。 - 『XAPP211: PN Generators using the SRL Macro』 - Xikinx 社: シフトレジスタを使った擬似乱数
PN ジェネレータ というのは、シフトレジスタ を作って作る。N bit の 結果を得るのに N クロック必要になるわけだが、この条件なら、SRL16 が使われ LUT が少なく済む。
また、N bit の 結果を得るのに N クロックかけて良いとなると、(LSB first の)bit-stream どうしの演算をベースにできる。
加算なら 1 bit の FULL-ADDR と ラッチ1 つで 済む。
積和でも 加算 x N と 遅延用の ラッチ N^2 / 2 があれば良い。そして遅延専用なら やはり SRL16 が使える。
なかなか設計が楽しそうだ。
GPR の検討
+----------- < ------------+
| _______________ |
r16 +---->|7|6|5|4|3|2|1|0| ---+---->
+----------- < ------------+
| _______________ |
r17 +---->|7|6|5|4|3|2|1|0| ---+---->
:
:
+----------- < ------------+
| _______________ |
r30 +---->|7|6|5|4|3|2|1|0| ---+---->
+----------- < ------------+
| _______________ |
r31 +---->|7|6|5|4|3|2|1|0| ---+---->
考えてみたのだが、GPR 自体も シフトレジスタにするのが良さそうだ。
16 本の シフトレジスタがあって ぐるぐるシフトしているイメージ。(実際は使わないレジスタは止めるが後で対応)
入力にセレクタがあって、どれを入力するか 独立に決める構造。
ちなみに、元の構造は、縦横が違う。各bit に LUT が割り当てられていて、レジスタ番号をアドレスとして選択。Dual Port なら 同じデータのものを 2 個使う。gpr_16 だと 8bit x 2 で 合計 16 LUT なのは同じ。
全部パラレルに読み出せるし書ける。ただし、インデックスレジスタの INC/DEC には 2T かかる。
読み出しだけなら 上位レジスタと下位レジスタをパラレルに読みだして 1T 。
加減算 は、1 つの FULL-ADDR で 1T 。AND/OR などの論理演算も 1T だが SWAP 命令は 2T かかる。
GPR 自体は 16 LUT で済むが、入力セレクタは load_data(8) + ALU + 元データ で 10。load_data 自体 ROM やら IOR など 巨大セレクタだし、ALU 自体も それぞれの演算結果からの選択。実際は 何to1 やら分からない。
ALU も IMM(8) + GPR(16) から 1 つを選択して B 入力になる。A 入力は GPR(16) to 1 。
save_data に格納するには、S2 開始から 1T。ここから 1/2 T で WRITE する。続く 1/2 T は 時分割で、READ 用につかう。
| | S1 | S2 | S3 |
|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
EA(PREDEC) ================================
EA(POSTINC) ===============
MEM READ ========
Rd/Rr READ ================
Rd Writeback ================
MEM WRITE ========
Rd Load from MEM =================
まとめるとこんな関係か。Rd の書き込みが、演算の場合はその場でやり、メモリからの場合は 1 T 後というのが、嫌らしいかも。
あと、GPR に対して入力するデータは、1T の期間中 HOLD されていないといけない。だから posedge_CLK_0 できっちりラッチしたデータ以外は使えない。またデータが揃うのは、1 T 後。要するに 1 T 毎にかちかち動くということだ。rtavr は、1/4 クロックとかややこしいタイミングがあったら、却ってわかりやすくなるかも知れない。
OP1の整理(ALU拡張)
これを始める前にやっておくべきことがある。
それは、OP1 に分類しているオペレーションの整理。
// COM 1001:010d:dddd:0000 # # -X0XX1
// NEG 1001:010d:dddd:0001 # # XXXXXX
// SWAP 1001:010d:dddd:0010 # # ------
// INC 1001:010d:dddd:0011 # # -XXXX-
// DEC 1001:010d:dddd:1010 # OP1_SEL = 4 # -XXXX-
// ASR 1001:010d:dddd:0101 # # -XXXXX
// LSR 1001:010d:dddd:0110 # # -XX0XX
// ROR 1001:010d:dddd:0111 # # -XXXXX
OP1 に分類したものは、8 つあるが、どれもこれもクセがある。SWAP やシフト系は、bitstream では特別な処理が必要だからしかたがないのだが、他のものは ALU に吸収してしまいたい。
で、考えたのだが、ALU に 次のオプションを追加すると良さそうだ。
ES[1] : A に 定数 0 を使う。
ES[0] : B に 定数 0xff を使う。
こうすると .. フラグとかいろいろ細工がいるものの
// NEG : 0 - B (A == B) : S = 2(SUB), ES[1] = 1
// COM : A ^ 0xff : S = 5(EOR), ES[0] = 1
// INC : A - 0xff : S = 2(SUB), ES[0] = 1
// DEC : A + 0xff : S = 0(ADD), ES[0] = 1
こんな風に演算を変換できるのだ。
NEG の場合は、 B(Rr_in) に A(Rd_in) と同じ内容 を 出力するように S0 をいじる。
あとはフラグだが、結構面倒。一応なんとかなりそうだ。
この拡張をしたら、12 スライス減った。結構嬉しい。
ところで、この拡張した ALU と シフトができるアキュームレータ を組み合わせたものを CoolRunnerII CPLD XA2C64A-VQ44 に Implement してみた。
// ALU_WIDTH 8
// Macrocells Used 47/64
// Pterms Used 201/224
// Registers Used 8/64
// Pins Used 32/33
// Function Block Inputs Used 117/160
ピンが足りなくなるので、シフト用端子を In/Out 共用にした。(74281 ベース) 。これで、33pin 中 32pin 使う。実際に、AVR を構成した ALU にシフトを付けているので、AVR と比べて 機能的に足りないのは、SWAP だけ。ちゃんと CPLD に収まるし、意外にも良いかんじに仕上がったのかも知れない。
『TTL ALU 74281』の記事の続きをいずれ書こうと思う。
(続く)