2011年12月23日金曜日

独自のCPUを創ってみる(10) - TD16


独自のCPUを創ってみる(9) - まとめ」の続き



TD32を創ってみて、CPUの設計方法が少しは分かった。
spartan-3 では、1個のスライスで最大 2:1 MUXしか実装できない。
4:1 MUXだとスライスが2個必要になる。
だから、32bit の 4:1 MUX を実装するには64スライス必要になる。
たった4種類の演算をするALUだけでこれだけのリソースが必要になる。
ところが pico blaze はCPU全体で96スライスだ。
だから、データ幅の広い信号のMUXは慎重に使わねばならない。
それと、メモリの使い方も重要なポイントだ。
TD32では単純にフラップフロップで複数のレジスタを実装したが、
これがリソース消費を拡大した。これは論外だった。
複数のレジスタは分散RAMかブロックRAMを使わねばならない。
どちらも高速なSRAMで、しかもデュアルポートの構成にできるから、
例えばパイプライン2ステージにして、それぞれのステージでRAMに
アクセスできる。pico blaze では16個の任意のレジスタ間で
sx <= sx + sy + carry
のような演算ができる。デュアルポートRAMをうまく使う必要があるだろう。

よく見かける、CPUのブロック図の見方も分かってきたような気がする。
データ幅の広い信号の流れに着目し、またMUXの使い方にも注意して
見るといいと思った。ブロック図にMUXが描かれているのは、
リソース消費や動作速度に大きく影響するからだろう。

改めて、16bit のCPUを作ってみた。
まだデバッグしてないのでまだまだバグがあると思うが。
注目したいのは動作速度とリソース消費だ。
現時点で最高速度 82.291MHz 、周辺回路含めて 142 スライスだった。

アーキテクチャは三段のパイプラインで、

(1) 命令アドレス計算、命令フェッチ、PC更新
(2) メモリアドレス計算、メモリ読み込み又は書き込み
(3) ALU実行、結果をアキュムレータに格納

という流れ。(1)と(2)ではデュアルポートのブロックRAMを同時にアクセスする。
アキュムレータは1つしかなく、フリップフロップで構成する。
パイプラインの制御はまだ実装してない。
(2)でブロックRAMから読み込んだ値をオペランドとしてALUを実行すると、
これだけで10nsecを超えてしまい、どうしても100MHz動作は無理なようだ。

高速化するためには、ALUのオペランドや結果の格納を分散RAMに
限定するしかなさそうだ。そうするとpico blazeと似てくるが、
pico blaze は2クロックで1命令実行だ。TD16 ではパイプラインで
なんとかして1クロックで1命令実行できるようにして、しかも16bit CPUと
することで差別化したい。当然pico blazeよりリソースを消費するだろうけど。

2011年12月22日木曜日

独自のCPUを創ってみる(9) - まとめ


独自のCPUを創ってみる(8) - DMMファイルの続き


未完成のTD32のプロジェクトファイル

拾い物のDDR SDRAMコントローラを試す

で試したSDRAMコントローラとTD32を繋ぎたいと、やってみたがうまく行かない。
原因は2点考えられ、一つはもともとこのコントローラはちょっと変、
ということと、もう一つはSDRAMコントローラは100MHz用で、
一方CPUのTD32はそんなに高速に動作しないので25MHzで
動かしていて、ちょうど4倍のクロックなのを利用してなんとか
接続する回路にしたはずなのだが、まだどこかにバグがあるかも知れない、
ということ。

コントローラが変なのは、もともとこれを作った人がコメントで
以下のようなことを書いてて、つまりうまくタイミングが合ってないのだ。

/***************************************************
 * De-duplex of the data path
 * For some reason, for read, the output is CL=2.5, not CL=2
 * Thus, catching of the data is 1/2 a cycle late.
 * Flip the main_clk and main_clk_n, 1/2 cycle delay catched
 * on data_mux_latch
 ***************************************************/

がんばれば、いつかはバグが取れるかも知れないが、
だれてきたので、ここで一旦締めて、別のテーマに移ることにする。
でも「独自CPUにDRAMを接続する」というのは諦めてないので、
いつかまた戻ってくるだろう。

もともと、以下の目的があった。


自分でCPUを設計して実装する技量を身につけたい。
Spartan starter kit に搭載されているDRAMを使いたい。
wishbone の使い方を学びたい。
DRAM以外は、まぁまぁ達成できた。

反省として、TD32はアーキテクチャというものを考慮しておらず、
そのため動作周波数が遅い上にリソース消費が大きいことだ。
単純に以下のようなレジスタを定義しているが、この部分で
512個のフリップフロップを使ってしまっている。

reg [31:0] gr[0:15];

また、以下のような32bitのMUXを多用していて、
このような記述でかなりリソースを使ってしまっている。

wire [31:0] alu2 = (cycrd | cycrd2) ? indata1 : 
(oprr | opar) ? grim8 : gr[opregj];

また、インタフェース仕様としてwishboneを使うこと自体は
よかったが、CPUが1クロックで命令を実行するというのに、
その1クロックの忙しい間にwishboneでI/OやRAMにアクセス
するのはぜんぜん無理だった。
次に作るときは、wishboneはI/O用とDRAM用で別にするか、
もしくはI/Oだけwishboneで接続して、DRAMは別途専用配線と
するのがいい。
今回はメインメモリもwishbone接続だったが、メインメモリは
BLOCK RAMをうまく使ってCPU内部の専用配線にしないと
タイミング的に間に合わない。また、レジスタファイルも
BLOCK RAMを活用しないとリソース的にも問題だ。

これらの反省を生かして、次は100MHz動作の16bit CPUを創ってみたい。

さて、最後に備忘録として各種ファイルの説明をしておく。
半年もすると絶対忘れてしまって自分でも分からなくなると思うので。

asm.cmd
TD32用のアセンブラを使ってアセンブルしてHEXファイルを生成する。
次にram1.bmm の記述に従って top.bit 内のBLOCK RAMを書き換え、
top_rp.bitを生成する。
data2mem -bm ram1.bmm -bd %MEM% -bt top.bit -p xc3s500e
次に、impactを使ってボードに書きこむ。
call wbit top_rp.bit

bus.v
wishboneバスを使ってCPU, RAM, I/O などを接続する。

Chattering.v
チャタリング除去用の簡単な回路

clkgen2.v
ddr_sdram.v で使われているDRAM用のクロック生成回路

dcm1.v
入力クロックを n/m 倍して任意のクロックを生成する

dcm2.v
90度ずつずれた4つのクロックを生成する

dcm4ddr.v
dcm1とdcm2を足したような回路

ddr_sdram.v
SDRAMコントローラ

hexconv.py
tool.py hex2hx2 に統合した

inport.v
汎用入力ポート

outport.v
汎用出力ポート

output.bmm
以下の出力例
data2mem -bm ram1.bmm -bd p2.mem -o p output

output.v
以下の出力例
data2mem -bm ram1.bmm -bd p2.mem -o v output

p1_led.txt
LEDを光らせるテストプログラム。ハンドアセンブルした。

p2.asm
p1_led.txt をアセンブラで置き換えた。

p3.asm
DRAMとの接続テスト用。結局、ちゃんと動作してない。

param.h
以下の定義を含むヘッダファイル。
シミュレーションのときは定義し、ボードに書きこむときはコメントアウトする。
`define DEBUG_SIM

paramconv.py
tool.py fmthex に統合した

paramlist_cpucyc.h
paramlist_opaddr.h
paramlist_opalu.h
paramlist_opcond.h
veritakでenum表示を使うための定義ファイル。
これらのファイルをincludeして、信号と紐付ける。
veritakでenum表示を選択すると、信号の値が数値ではなく
ラベルで表示される。

Prescaler.v
分周回路

prog.hex
prog.txtから、tool.py fmthex で整形した結果

prog.txt
ハンドアセンブルしてた頃のテストプログラム。
テストベンチt_top.v で使われ、多くの命令をひと通りテストする。

progconv.cmd
paramconv.py を起動するバッチファイル

ram.v
block ramを使用した512 word x 32 bit メモリ
wishbone 仕様

ram1.bmm
asm.cmd参照

rom.v
テストプログラムを含むROM。

td32.v
TD32 本体

td32asm.txt
TD32用のマクロ定義ファイル。汎用アセンブラAASMに適用する。
使用例はasm.cmd参照。
AASMのおかげで、簡単にアセンブラを利用できた。
ハンドアセンブルでずいぶん苦労したが、AASMがこんなに役に立つと
分かってたらもっと早くマクロ定義をしたのだが。

tool.py
各種のツールをまとめたもの。含まれるツールは下記。

tool.py fmthex [-h] [-f fillstring] [-s size] <in-file> [<out-file>]
    fmthex - format for $readmem(). "00_ab/*..*/cd" => "00abcd"

tool.py hex2hx2 [-h] [-f fillstring] [-s size] <in-file> [<out-file>]
    hex2hx2 - format for $readmem(). intel HEX (output of AASM) => HX2 (suitable for readmem())

tool.py hx22mem [-h] <in-file> [<out-file>]
    hx22mem - hx2 => xilinx mem file for data2mem command (to replace memory part of a bit file


top.ucf
UCFファイル

top.v
topファイル。クロック生成、リセット回路など。

top.vtakprj
top2.vtakprj
top3.vtakprj
veritak用プロジェクトファイル

t_top.v
t_top2.v
t_top3.v
テストベンチ

v13.gise
v13.xise
ISE用プロジェクトファイル

wbit.bat
IMPACTでボードに書きこむバッチファイル。
bitファイル名を省略すると、top.bitとなる。
wbit <bitファイル>

wishbone_wait.v
wishboneのslave側で、ACKの応答を固定クロック遅らせるための回路

以上

2011年12月3日土曜日

独自のCPUを創ってみる(8) - DMMファイル


独自のCPUを創ってみる(7)の続き


Xilinx の data2memというコマンドを使うと、bitファイルをリコンパイルすることなく、
その中のblock RAMの初期化データだけを入れ替えることができる。

ISEの中からも使えるような記述があるが、うまく使えないようだ。
(ネットの書き込みによれば、プロジェクトファイルを作りなおせばいいらしいが)

コマンドベースで一応、うまく行ったのでメモっておく。

(1) ISEでbitファイルを生成する。

(2) FPGA Editor を起動し、書き換えたいblock RAMのインスタンス名とロケーションを確認する。

それには、Place&Routeの中の"View/Edit Routed Design" をダブルクリック。
FPGA Editorの右上のName filterのところに*myram* のように分かっている名前の一部を入力。
見つかったらそのインスタンスを選択して、F2を押す。
プロパティ画面のName欄がインスタンス名、Locationが配置されたロケーションだ。

(3) UCF ファイルに制約を追加する。(ロケーションが勝手に変わると不便なため)
先ほど確認したものを、以下のように入力する。
INST "bus/ram1/Mram_ram" LOC = "RAMB16_X1Y7";

(4) BMMファイルを作成する。
以下のように書く。UCFに書いたものと微妙に書式が違う。
コード: 
// comment...ADDRESS_SPACE ram1 RAMB16 [0x00000000:0x000007FF]BUS_BLOCKbus/ram1/Mram_ram [31:0] LOC = X1Y7;END_BUS_BLOCK;END_ADDRESS_SPACE;
最初のram1は、アドレススペース名。使わないので何でもいい。
RAMB16は使用しているブロックRAMの種類。
パリティも使うときはRAMB18を指定する。

[0x00000000:0x000007FF] は全体のメモリサイズ。
ワード幅に関係なく、バイト単位のサイズを指定する。
[31:0]の部分が、ワード幅を示している。今回は32bit * 512ワードの構成。
[7:0] と書けば、8bit * 2048 ワードと解釈される。

試してないが、もし2個使って32bit * 1024ワードにするなら以下のようになるはず。

コード: 
// comment...ADDRESS_SPACE ram1 RAMB16 [0x00000000:0x00000FFF]BUS_BLOCKbus/ram1/Mram_ram [31:0] LOC = X1Y7;END_BUS_BLOCK;BUS_BLOCKbus/ram2/Mram_ram [31:0] LOC = X1Y8;END_BUS_BLOCK;END_ADDRESS_SPACE;
BUS_BLOCK で囲まれた範囲は、一度に同時にアクセスするRAMを示している。
例えば同じサイズでも、並列にアクセスすることで64bit * 512 ワードにしたいときは
以下のようにする。これをハードウェアの構成と合わせないと、読み込まれる
データの配置と合わなくなる。

コード: 
// comment...ADDRESS_SPACE ram1 RAMB16 [0x00000000:0x00000FFF]BUS_BLOCKbus/ram1/Mram_ram [63:32] LOC = X1Y7;bus/ram2/Mram_ram [31:0] LOC = X1Y8;END_BUS_BLOCK;END_ADDRESS_SPACE;
なお、BMMファイルの拡張子は .BMM にすること。

(5) 入れ替えるMEMファイルを用意する。
以下のような形式の単純なテキストファイル。

@00000000
01234567
89ABCDEF
....

拡張子は必ず .MEM とすること。

一行目はアドレス指定。常に「@00000000」で良い。
二行目からは設定したいデータワード。
BMMファイルの最初の例では32bitワードなので、
8桁の16進数で一行ずつ並べる。
全部で513行になる。

(6) 以下のコマンドで bit ファイルを書き換える。


data2mem -bm ram1.bmm -bd test.mem -bt top.bit -p xc3s500e

ram1.bmm はBMMファイル、test.mem はMEMファイル、top.bitはISEで作成したbitファイル。
xc3s500e はFPGAチップの種類。以下のコマンドで名前の一覧が表示される。 

data2mem -h  sup

正常終了すると、top_rp.bit ができる。