はじめに
ここではGowin GW1NR-9 FPGAを搭載したTang Nano 9Kにインテル4001のROM(正確に言えばRAMをROMのように扱う)を作成する方法について説明します.1個の4001には1ワード8ビットのインストラクションを256個,記憶できます.実際の4001はROMですが,ここではRAMにインストラクションを書き込み,それを後からフェッチしてエグゼキュートするようにします.まずはその第一歩として,8ビット幅,256個のRAMを作成します.ここで利用するRAMは上記FPGAに備わるBlockRAMです.これにより,フリップフロップのような分散RAMと異なり,ある程度まとまった大きさの記憶を行えます.
環境
以下の環境で開発を行いました.
- FPGA: Gowin GW1NR-9 FPGA
- 開発ボード: Tang Nano 9K を搭載したボード
- 開発環境: Gowin EDA V.1.9.9 Beta-4 Education
作成する4001内のROM
オリジナルの4001に備わるROMのサイズに準じて作成します.このため,8ビット幅,256ワードとなります.このようなROMをFPGA内のBlockRAMに作成します.そしてそのモジュール上部にアドレス入力用スイッチ,データ入力用スイッチ,クロック用スイッチ,読み書き切替用スイッチ,RAM内容表示機(ドットマトリクスディスプレイ),ページ表示器(7セグメントLED)を作成したいと思います.ここでページについて説明します.ドットマトリクスディスプレイには32列しかないため,4001に備わるROMすべてを一度に表示することができません.このため,8個に分けて表示できるようにします.このように32ワードを1ページとここでは呼びます.つまり0~7までのページがあることになり,これを7セグメントLEDに表示することとします.
スイッチの役割
各スイッチとその役割について説明します.下のようにしましょう.
スイッチ | 役割 | ポート名 | ビット幅 |
SW1~SW8 | アドレス入力用スイッチ(SW1が最下位,SW8が最上位) | iAddr | 8 |
SW9~SW16 | データ入力用スイッチ(SW9が最下位,SW16が最上位) | iData | 8 |
SW17 | クロックスイッチ(レバーが下だとLow,上だとHigh) | iManualClk | 1 |
SW18 | 読み書き切替用スイッチ(レバーが下だとRAMから読み込み,上だとRAMへ書き込み) | iWriteEn | 1 |
RAM内容表示器とページ表示器
1ワードをRAM内容表示器(ドットマトリクスディスプレイ)1列に表示します.右側が下位アドレス,左側が上位アドレスとします.また,ページ番号をページ番号表示器(7セグメントLEDの一番右側にあるDP1)に表示します.
作成手順
まずはざっと流れについて説明します.
- FPGAのIP Coreを使ってBlockRAMを制御するi4001ROMモジュールを生成
- 上記モジュールをテスト動作させるためにMainモジュールを作成
- Mainモジュール内でi4001ROMをインスタンス化
以上の順番で作成していきます.
BlockRAMを制御するモジュールを生成
まずはFPGAのIP CoreでBlockRAMを制御するコードを生成しましょう.IP CoreとはIntellectural Property Coreのことで,FPGAなどに特定の機能をまとめられているコンポーネントのことを指します.今回はBlockRAMですが,そのほかにも通信を行うものや画像を扱うものなどがあります.
ではGowin EDAを立ち上げてください.立ち上げましたら下の図のようにNewProjectを選択します.
現れたダイアログに対して下記のようにOKボタンを押します.
今回のモジュールを下の図のようにi4001Blockとします.
次にターゲットデバイスを選択します.下の図のようにGW1NRシリーズを選んでください.
これで設定完了です.
次にIP Coreを生成します.下の図のようにGowin EDA上部にあるアイコンを押してください.
次にIP Coreの種類を選択します.下の図のようにHard Module⇒Memory⇒Blok Memoryの中にあるSP(Single Port)をダブルクリックします.これら4種の中で最もシンプルなものがこれです.SP以外にはDP(Dual Port)のものなどがあります.ここでPortとは入出力の端子を表していて,1つしかないもの(=Single)や2つあるもの(=Dual)というような違いがあります.
次に作成するRAMの設定を行います.上からFileNameとModuleNameがあり,ともにi4001Blockとここではしました.次にAddress Depth とData Widthです.4001には256ワードであるため256とし,1ワードが8ビットであるためData Widthを8にしました.その左側にあるRead/Write Modeについては特に変更する必要はありません.なお,この中にあるRead modeにはBypassとなっていますが,これは後述のOCE(Output Clock Enable)端子を使わないときにはBypassを選択しておく必要があります.
ファイルを追加してよいか問い合わせるダイアログが現れますのでOKを選んでください.
生成されると下のようにi4001Block.vファイルが出来上がります.このファイルに含まれるi4001Blockモジュールを制御するため,下図の右側にあるようにインスタンスを生成します.
生成されたi4001Blockのポート
各ポートの役割を説明します.
doutポート(出力)
RAMから出力されるデータです.
clkポート(入力)
RAMに入力されるクロックです.この立ち上がりエッジのとき,各種動作が行われます.
oceポート(入力)
Ouput Clock Enableポートです.前にIP CodeでRAMを生成したとき,Read modeをByPassにしましたが,その場合にはoceポートは何の役割も果たしません.反対にRead modeがPipelineの場合,oceポートがLowの時にはdoutポートへ作用を及ぼします.詳しくは省略します.
ceポート(入力)
Clock Enableポートです.この端子はHighの時に動作する,いわゆるHigh-activeです.役割としては,複数のRAMが存在するような回路構成の時,どのRAMをアクティブ(High)にしたりインアクティブ(Low)にしたりするときに用います.ただし今回の例ではRAMが1個しかないため,Highにしておきます.
resetポート(入力)
RAMをリセットするときに用います.この端子もHighの時に動作する,いわゆるHigh-activeです.今回はリセット機能を使わないため,ずっとLowにしておきます.
wreポート(入力)
Write Enableポートです.読むとき(Low)と書くとき(High)を切り替えるときに使います.
adポート(入力)
Addressポートです.
dinポート(入力)
RAMへ書き込むためのポートです.
Mainモジュールを作成
ではROMを操るMainモジュールを作成しましょう.下図のようにi4001ROMを右クリックし,New Fileを選択してください.
下の図のようにVerilog Fileを選択してください.
ここではMainと名付けました.
Mainモジュールには前に説明したスイッチのほか,入力ポートとしてiClk(27MHzのクロック)と,下の表に示すドットマトリクスディスプレイと7セグメントLEDを表示するための出力ポートが必要です.ドットマトリクスディスプレイについてはこちら,7セグメントLEDについてはこちらをご覧ください.
ポート名 | 役割 | 幅 |
oPattern | 表示したいパターン信号です.同じセグメント(たとえばセグメントA)は 電気的に接続されているため,もしDIGITが1111の場合にはすべてが同じ数字を 表示します.実際に使用するときには,上記DIGITは1個しかビットが立たないように 制御すれば,各桁は別の数字が表示されているように見えます. |
8 |
oDigit | ダイナミック点灯方式では,4個の7セグメントLEDから1個を指定してから, 表示するパターンを全7セグに送ります.oDigitは1個の7セグを指定するときに 用います.Highとなっている桁のみ表示するよう,回路設計がなされています ので,0001⇒0010⇒0100⇒1000⇒0001…を繰り返すことになります. |
4 |
oDmdClr | この信号が1'b1のときリセットされます. | 1 |
oDmdClk | 4個のシフトレジスタのクロックに接続されています. | 1 |
oDmdSeg | 4個のシフトレジスタのシリアルインと接続されており,同じくシフトレジスタに接続されているoClkの立ち上がりエッジのタイミングで,oSsgの信号をシフトレジスタは取り込みます. | 4 |
oDmdColumn | このドットマトリクスはダイナミック点灯方式を利用しており,目には見えませんが高速で1列ずつ描画しています.その1列のパターンを表しているのがこの信号です. | 8 |
この時点でのMainモジュールを下に示します.
{code}module Main(iAddr, iData, iManualClk, iWriteEn, iClk, oPattern, oDigit, oDmdClr, oDmdClk, oDmdSeg, oDmdColumn); input [7:0]iAddr; input [7:0]iData; input iManualClk; input iWriteEn;input iClk; output [7:0] oPattern; output [3:0] oDigit; output oDmdClr; output oDmdClk; output [3:0]oDmdSeg; output [7:0]oDmdColumn; endmodule {lang}c{end-code}
i4001ROMをインスタンス化
先ほど生成したi4001BlockモジュールをMainモジュールで利用するため,インスタンス化します.インスタンス化した時に接続する端子の多くは固定もしくはinputポートにありますが,1つだけ内部信号にしておくものがあります.それがoutputDataです.これはi4001Blockから出力されるデータです.このためwire [7:0]outputData;としておきます.下のコードは内部信号とインスタンス化した様子を表します.これを上記Mainモジュール内に追記します.
{code} wire [7:0]outputData; i4001Block i4b( .dout(outputData), //output [7:0] dout .clk(iClk), //input clk .oce(1'b1), //input oce .ce(1'b1), //input ce .reset(1'b0), //input reset .wre(iWriteEn), //input wre .ad(iAddr), //input [7:0] ad .din(iData) //input [7:0] din ); {lang}c{end-code}
出力データを各種表示器に
outputDataにはRAMから出力されるデータが