PIC on FPGA

PIC16 の CPU コアを Verilog-HDL で記述し、FPGA に実装してみました(以下「自作 PIC16」)。

トップページへ戻る


目次

  1. 概要
  2. 命令デコーダ
  3. ALU
  4. 制御信号生成部
  5. 動作確認
  6. ダウンロード

概要

PIC16 CPU も一般的な CPU の例に漏れず、命令デコーダ、ALU、制御信号生成部、レジスタから構成されています。命令は全部で 35 個と少ないです。命令はすべて 14 ビット長で、命令バスとデータバスが独立しています(データバスは 8 ビット)。本物はいくつかの命令の実行に 2 インストラクション・サイクルかかりますが、自作 PIC16 ではすべて 1 インストラクション・サイクル(= 4 クロック)で実行します。良く言えば高速化、悪く言えば互換性の低下です。

使用スライス数は XC3S100E, ISE 12.4(lin), Speed 優先で 238 (58MHz), Area 優先で 192 (51MHz) でした。むむ、ちょっと多いか。。。

CLRWDT と SLEEP は未実装で NOP になります。

自分が書いた PIC プログラムの範囲で確認しただけなので、まだバグはあると思います。動かなかったら適宜修正するか、利用を諦めてください。 あと(ないとは思いますが)営利目的での利用はご遠慮ください。動作保証等は一切ありません。動作結果の責任も負いません。

トップページへ戻る


命令デコーダ

命令の数が少ないので、命令デコーダも他の CPU に比較して簡単です。


PIC16 の命令表(PIC16F87x のデータシートから抜粋)

上位 2 ビットで命令を大別できそうです。00 グループは ALU を使う XXXWF または XXXF 命令とその他命令、01 はビット系の命令、10 は分岐命令、11 は ALU を使った LW 系命令です。そんなわけで上位 2 ビットをデコードする decode2to4 の使用が確定しました。

ALU を使う命令については ALU のオペレーションが命令のビット[11:8] で決まります。よってこの 4 ビットをデコードする decode4to16 の使用が確定しました。

以上のように命令をデコードしたのは、いろんな制御信号を生成する際の条件として (I[13:8] == 6'b000111) というように信号ごとに都度完全一致を見ると、そのたびに比較器 (comparator) が必要になり、回路規模が大きくなってしまうと考えたからです。一旦デコードしてしまえば、展開された1ビットが 1 かどうかだけをみて制御信号を生成できるので、デコード結果を使えば使うほどデコードした効果がでてきます。PIC16 CPU のように規模の小さな CPU では比較器を使いまくってもローエンドの FPGA に入りきると思いますが、私は貧乏性なのでなるべく小さくしようと考えてしまいます。

00 グループの命令は ALU を使うものと、使用しないものがあります。ALU を使用しない CLRWDT, RETFIE, RETURN, SLEEP の4命令は特別扱いとし、 00 グループから除いています(ソース 121 行目)。

命令の数が少ないので、大まかなデコードはしやすいです。デコードだけで対応できないところは力技でなんとかします。

トップページへ戻る


ALU

命令デコーダがある程度できたら、次は ALU を設計します。ALU には入力が3つ必要です。8 ビットのデータ入力が2つ(今回は A と B)と、前の演算で出力されたキャリーフラグ(1 ビット)の入力です。CPU によってはハーフキャリー(ビット 3 からの繰り上がり)入力も必要になりますが、PIC16 CPU では必要ないです。

ALU の使われ方としては 1) W と F の演算、 2) F の演算、 3) W とリテラルの演算です。 演算によっては A, B の入力が逆になると結果が変わってしまうものがある(例: SUB)ため、 どちらに何を入力するかを決めてから ALU を実装していきます。今回は 1) A に W、B に F を入力、 2) A に F、B にゼロ、3) A に W、B にリテラルを入力します。

足し算(ADD) は A + B + C を実行します。C を足しているのは、ADD だけでなく他の命令(SUB, INC, DEC, RL)にもこの形が適用できるからです。

引き算(SUB)は 2 の補数を足す方法で実装します。A - B = A + (-B) = A + ((~B) + 1) です(~は全ビット反転を意味する)。最後の +1 は C に 1 を入力することで実現できます。

DEC は 2) のパターンなので、B にはゼロが入力されています。C に 0 を入力することで A + (~B) + 0 = A + 0xFF を実行します。

RL は ALU 側で入力 B を A に修正し、A + A + C を実行します。

トップページへ戻る


制御信号生成部

1命令サイクルは 4 クロックなので、今回はすべての命令を 4 ステップで完了するように記述しました。 例外なく 4 クロックで命令の実行が完了するため、ステート(r_ST)の記述は 4'b0001 から開始し、1 のビットをローテートシフトしているだけです。

ST[0] で命令フェッチ、ST[1] アドレス出力、ST[2]でデータ入力読み込み、ST[3] で ALU から演算結果を取り出しています。演算結果を F に書き戻す場合は ST[3] で write strobe (WR) を1にセットするため、書き戻しの完了と次の命令のフェッチがオーバーラップします。PIC16 アーキテクチャでは命令バスとデータバスが別になっているためこれが可能ですが、実チップがこのタイミングで書き戻しをしているかは調査しないと分かりません。

今回作った ALU では MOVLW と RETLW に対応できないことが後の方になって判明したので、MOVL と RETLW は ALU を使わないルートで W に値をロードしています。

トップページへ戻る


動作確認

本物の PIC16F873 用に作ったテストプログラムを自作 PIC16 でも動作させてみました。


動作確認の様子

左が自作 PIC16 (XC3S100E)、右が PIC16F873 です。両者とも同じように動いていることが確認できました。 テストプログラムは、単純に 7 セグメント LED に値をインクリメントしながら表示させるだけのものです。 ダイナミック点灯の表示切替えにはタイマー TMR0 とタイマー割り込みを使用しています。

右は余分な IC が付いていますが、テストプログラムでは使用していません。同じプログラムを 自作 PIC16 は 33.33MHz、 PIC16F873 は 20MHz で実行しているため、自作 PIC16 の方がカウントの進行が早いです。

トップページへ戻る


ダウンロード

作成した PIC テストプログラムと、今回動作させた自作 PIC16 の verilog ソースを置いておきます。いずれも tar.gz 形式です。Linux で開発したので改行が LF になっています。

トップページへ戻る


(C) Ki 2011

inserted by FC2 system