2010年11月28日日曜日

DDR2 SDRAM CONTROLLERのDEBUG

表示画像が砂嵐状態だったのはline bufferがバグっていたためで、そこを修正したら表示されるようになってきたがまだおかしい。

cif (camera i/f) moduleもバグっているかな?
でも、予想していたよりは画質は良さそうだ。

DDR2 SDRAM CONTROLLERの合成 4

line bufferも出来たので組み上げ、合成と動作確認を開始した。



まだPHYのauto calibratorは組み込んでいないが、UART経由で調整できるようにしてあるので暫くはこれで進めようと考えている。








早速実機で動かしてみたが、
←の通りで何か変だ。

cameraの画像が正しく表示されたときは感動するんだろうな。
それを楽しみにdebug作業を進めよう。

2010年11月21日日曜日

DDR2 SDRAM CONTROLLERの合成 3

camera i/f (cif)とvideo timing生成部、dvi_enc (dvi encoder)部の組み込みまでできた。

clockは、cif部は25MHz、内部bus (memory i/f)は65MHz、ddr2 i/fは140MHz、dvi_encは325MHzだが全てmetしている。








dvi_encはshift registerからODDRに至る経路でtimingが満たせなかったため、repeater用のF/Fを2段追加した。repeater用のF/Fは元々入れてあった物と合わせて3個入れたことになる。











←planAheadでその内の1つの経路を確認した。(赤色の経路)残念な事にその内の2個は1つのSLICEに割り当てられており、repeaterの役を果たしていない。配置制約をかければもっと少ないrepeater (F/F)数に出来そうな気もするが、可能な限り制約をかけるのは避けたいと考えているので、現状で良しとしよう。



まだline buffer部を入れていないのでdram→dvi への出力は出来ないが、cif→dramへの部分は入れてあるのでdramに取り込んだdataをuart経由で吸い上げることは可能な筈だが、ここはあせらず全てを組み込んでから動作確認に進もうと思う。
また、multiport i/fは3port (cif, video, uif)に増やすつもりだ。

2010年11月20日土曜日

DDR2 SDRAM CONTROLLERの合成 2

ふぅ~~、


140MHzでmetするまで改善できた。











機能的にも問題はなさそうだ。











構造が汚くなってしまった。
こんな設計しか出来ない自分が情けない。くっそー、、
将来1から作り直すことにしよう。
とりあえず、今は全体の組み上げに進もうと思う。

2010年11月14日日曜日

DDR2 SDRAM CONTROLLERの合成


何とか合成も通るようになってきた。

結局、DCMのfeed back pathはLOOP_IN, OUTを使わずFPGA内部で戻すことにした。

現状、DRAMのclockは125MHzでdatasheet上の下限の周波数だ。

何とかせめて133MHzまではいきたい。と言っても性能は6%程度しか改善されないのだが、DRAM仕様の下限値でしか動かせないというのは残念な気がするので。。



125MHzでmetさせるのにも幾つかRTLの変更が必要だった。そのため、simulationでの確認も平行して行っている。
←はその結果だが、問題ないようだ。

timingがmetしない箇所はbank controllerの要求を調停する部分でこの論理が深くなってしまった。この部分を改善するのはなかなか難しい。

早く実機で動かしてみたい気持ちもあるが、
もう少しウニウニしてみよう。

2010年11月12日金曜日

DDR2 SDRAM CONTROLLERのSIMULATION

漸くMULTIPORT I/FとDDR2C COREとを統合できる段階に来たので、
統合してざっとsimulationをしてみた。

←は、p0はbank0、p1はbank2に連続値を書き込んだ後、p0、p1を交互に読み出しを行っている。
←の表示状態では波形表示の範囲が広く個々の値は見えないので、px_rdtはanalogとして表示させている。
連続値が読めるはずなので、左の波形のように三角波に見える筈だ。

2010年11月7日日曜日

MULTIPORT I/F部の検討 4

少々早い気もするが、現状のtest benchでのcode coverageを見てみた。
code coverageの計測はcoveredを使用した。
coveredはTrevor Williams氏が開発し、GPLで公開している Verilog code coverage analysis toolだ。line coverage, toggle, condition (combinational logic)等の他にOVLによるAssertionにも対応している優れ物だ。
昨日までに作成したtest benchはmpifの粗動作を確認するため簡単なものだが、line coverageは回路(記述)の冗長な部分を見極めるのにも有効であり、その点において現時点で実行する意味はあると思う。


simulatorはicarus verilog (iverilog)を使用した。
←はcoveredやiverilogへの入力fileの内容と、実行の様子だ。












coveredは結果をGUIで見ることもでき、
covered report -view cdd_file でGUI modeで立ち上がる。
←で分かる通り左側のwindowがcoverageの結果表示で右側のwindowがsource表示用だ。
←からmpifのline coverageは98%であり、cover出来ていない部分があることがわかる。その部分をsource windowで確認すると、state machineのGRANT state部のif文の記述であることが判った。w_cmdは2bitでその前の部分で全ての場合について記述されているので問題の箇所はw_cmdの値が不定値でも無い限りは到達しない。冗長な回路記述だ。削除しよう。


2010年11月6日土曜日

MULTIPORT I/F部の検討 3

MULTIPORT I/F部の設計とcodingが大体できた。


MULTIPORT I/Fの機能はCLIENT MODULEから入力されるpacketからDDR2Cへの信号に変換出力することと、DDR2Cから戻ってくるread dataを要求元のCLIENT MODULEに転送することである。また、CLIENT MODULEとのI/Fは複数(今回、現時点では2)あるのでこれらの要求の調停機能も持っている。
CLIENTMODULE、MULTIPORT I/F、DDR2C CORE各間の接続は左図のようになっている。DDR2C CORE側のportは10月31日付けの図にあるように非同期FIFOのportが直接見えている。左図では上段がCMD QUEUEに、中段はWDT QUEUEに下段はDPのportに対応している。DDR2 SDRAMはburst長として4と8を設定出来るがDDR2C COREでは4burstに固定している。また、今回targetとしているSpartan3A StarterKitに搭載されているDDR2 SDRAM(MT47H32M16)のdata busは16bitであるのでaccessの最小粒度は64bitとなる。がしかし、この粒度を内部busまで当てはめてしまうと、例えば各CLIENTMODULE内部に持つbufferやFIFO等も64bit x n段となり回路規模が無駄に大きくなりかねない。このため、本設計では32bitとしているが、反面、CLIENTMODULEとDDR2Cの間を取り持つMULTIPORT I/Fでの処理がちょっとばかしややこしくなってしまった。例えばCLIENTMODULEから奇数番地へのwrite要求がきた場合はその一つ前の偶数番地へのdummyのwriteを発生させる必要がある。また転送長が1以上 (length > 1)の場合は最後尾にもdummyのwriteが必要になる。これはread要求の場合も同様でDDR2Cから返ってくるread data列の先頭や最後尾のdataを破棄するなどの処理が必要になってくる。

これは偶数番地からのlength 8のreadの場合をsimulationした波形だ。












これは奇数番地の場合だ。
CLIENTMODLEは1~8番地までのreadを要求しているが、DDR2Cは64bit単位のaccessになるのでMULTIPORT I/Fは0~9番地までをreadし、DDR2Cから返ってくるdataの先頭(0番地のdata)と最後尾(9番地のdata)に該当するvalid信号(p0_rdv)を0にしてCLIENTMODULEがこれらを取り込まないようにしている。





これは番地は偶数だが転送長が奇数の場合だ。この場合は最後尾(9番地のdata)のdataのみを破棄する。











これは奇数番地から奇数個のreadの場合だ。この場合も先頭のdataのみを破棄している。











これは偶数番地への偶数個のwriteの場合だ。
writeはwrite dataの転送もあるのでreadよりもややこしい。
DDR2Cにwrite dataを2個転送する毎にcommand (rwb, ra, ba, ca)を1個転送する。









これは奇数番地への偶数個のwriteの場合だ。この場合は先頭にdummy のwriteを挿入するため、CLIENTMODULEに対してはpacketのheader転送成立後にackを0にしてwrite dataの転送をwaitさせている。一方DDR2Cに対してはwbeを0にしたwrite要求を発行している。奇数番地からの偶数個であるので、dummy writeは最後尾にも必要だ。






これは偶数番地への奇数個のwriteの場合だ。この場合は最後尾にのみdummy writeが必要になる。











これは奇数番地への奇数個のwriteの場合だ。この場合は先頭にのみdummy writeが必要になる。











これはswapの場合で、偶数番地の1 dataのみの場合だ。swapの場合はCLIENTMODULEに対してはpacketのheader転送成立後にackを0にしてwrite dataの転送をwaitさせ、DDR2Cに対しては転送長分のread要求を発行する。その後ackを1に戻してwrite dataの転送を行い、DDR2Cに対してwrite要求を発行する。swapの場合にも開始番地は転送長に応じてdummy cycleの挿入処理が必要になる。





これは奇数番地の1 dataのみの場合だ。













swap機能はCPU搭載時のmutex処理等を想定したものだが、他の使い道もあるかも知れないのでdata数(転送長)については制限をつけていない。つまり1個以上の領域のswapも可能な仕様にしている。左は偶数番地から偶数個のswapの場合だ。








これは奇数番地から偶数個の場合だ。













本仕様ではfill機能も持たせている。これは、frame buffer領域の初期化等を想定したものだ。左は偶数番地から偶数個の場合だが、fillの場合はCLIENTMODULEからMULTIPORT I/Fに渡されるdataは1つだけあればいいので、data転送は1回のみとなるが、DDR2Cへのwrite要求は転送長分発行させる必要がある。




これは奇数番地から偶数個の場合だ。


ここまではDDR2C側のFIFO(QUEUE)は空きがある状態、即ちrdyが1の状態でsimulationを行っているが、実際はDRAMのrow address切替に伴うstallやrefreshによるstallの為にQUEUEが満杯になりrdyが0になる場合も発生し得る。このような場合は当然であるが、rdyが1に回復するまで転送を停止するなどの動作が必要である。現状のRTLはこのような場合でも正しく動作する筈であるが、検証はこれからだ。


因みに、現状のRTLはこんな感じだ。

//===========================================================================
// circuit description
//===========================================================================
assign p0_ack = r_ack[0];
assign p1_ack = r_ack[1];




///////////////////////////
// ARBITER               //
// priority:             //
//   p0 > p1 > ...       //
///////////////////////////
assign w_req = p0_req | p1_req;
assign w_winner = (p0_req)? 2'b01:
  (p1_req)? 2'b10:2'b00;


always @(posedge clk)
begin
 if (rst) begin
   r_winner <= #UD {CLI_NUM{1'b0}};
 end else if (st == ST_IDLE && dc2mp_crdy) begin
   r_winner <= #UD w_winner;
 end
end


assign w_win_pkt = (r_winner[0])? p0_pkt:p1_pkt;
assign  w_cmd = w_win_pkt[LB_CMD_MSB:LB_CMD_LSB];


///////////////////////////
// STATE MACHINE         //
///////////////////////////
always @(posedge clk)
begin
 if (rst) begin
   st <= #UD ST_IDLE;
   r_ack <= #UD {CLI_NUM{1'b0}};
   mp2dc_cen <= #UD 1'd0;
   mp2dc_den <= #UD 1'd0;
 end else case (st)
  ST_IDLE :begin
            mp2dc_cen <= #UD 1'd0;
            mp2dc_den <= #UD 1'd0;
   if (w_req && dc2mp_crdy) begin
     st <= #UD ST_GRANT;
     r_ack <= #UD w_winner;
   end else begin
     st <= #UD ST_IDLE;
              r_ack <= #UD {CLI_NUM{1'b0}};
   end
  end


  ST_GRANT:begin
            mp2dc_den <= #UD 1'd0;
   if (w_cmd == CMD_READ || w_cmd == CMD_SWAP) begin
     st <= #UD ST_RD0;
              r_ack <= #UD {CLI_NUM{1'b0}};
              mp2dc_cen <= #UD 1'b1;
   end else if (w_cmd == CMD_WRITE || w_cmd == CMD_FILL) begin
     st <= #UD ST_WR0;
     //
     // the burst length of DDR2 SDRAM is configured to 4.
     // (16bit x 4 = 32bit x 2)
     // so we always have two data slot.
     // if xfer starts on odd(p+1) address we need to generate
     // a dummy cycle (i.e, a write cycle to even(p) address). 
     // thus, we need to delay r_ack assertion in this case.
     //
     r_ack <= #UD (w_win_pkt[LB_ADR_LSB])? {CLI_NUM{1'b0}}:r_winner;
              mp2dc_cen <= #UD 1'b0;
   end else begin
     st <= #UD ST_GRANT;
              r_ack <= #UD {CLI_NUM{1'b0}};
              mp2dc_cen <= #UD 1'b0;
   end
  end


  ST_RD0  :begin
            mp2dc_den <= #UD 1'd0;
            mp2dc_cen <= #UD (dc2mp_crdy && xcnt == 9'd1)? 1'b0:1'b1;


   if (cmd == CMD_READ && dc2mp_crdy && xcnt == 9'd1) begin
     st <= #UD ST_IDLE;
              r_ack <= #UD {CLI_NUM{1'b0}};
   end else if (cmd == CMD_SWAP && dc2mp_crdy && xcnt == 9'd1) begin
     st <= #UD ST_WR0;
     r_ack <= #UD (dc2mp_drdy && ~xadr[2])? r_winner:{CLI_NUM{1'b0}};
   end else begin
     st <= #UD ST_RD0;
              r_ack <= #UD {CLI_NUM{1'b0}};
   end
  end


  ST_WR0  :begin
            mp2dc_den <= #UD dc2mp_drdy;
            mp2dc_cen <= #UD 1'b0;


      st <= #UD (dc2mp_drdy)? ST_WR1:ST_WR0;


   if (dc2mp_drdy && (dcnt > 9'd1 || 
                  (dcnt == 9'd1 && r_ack == {CLI_NUM{1'b0}}))) begin
              r_ack <= #UD r_winner;
   end else begin
              r_ack <= #UD {CLI_NUM{1'b0}};
   end 
  end


  ST_WR1  :begin
            mp2dc_den <= #UD dc2mp_drdy;
            mp2dc_cen <= #UD dc2mp_drdy;


      st <= #UD (dc2mp_drdy && xcnt == 1)? ST_IDLE:
        (dc2mp_drdy)? ST_WR0:ST_WR1;
     if (dc2mp_drdy && dcnt > 9'd1) begin
        r_ack <= #UD r_winner;
   end else begin
              r_ack <= #UD {CLI_NUM{1'b0}};
   end
  end
 endcase
end


assign mp2dc_lst = (xcnt == 9'd1 && mp2dc_cen);


//
// command latch
//
always @(posedge clk)
begin
 if (rst) begin
   cmd <= #UD {LB_CMD_W{1'b0}};
 end else if (st == ST_GRANT) begin
   cmd <= #UD w_cmd;
 end
end


//////////////////////////////
// Transaction counter 
//
// LEN , ADR  -> xcnt
//------------------------
// EVEN, EVEN -> LEN/2
// EVEN, ODD  -> 1 + LEN/2
// ODD , EVEN -> 1 + LEN/2
// ODD , ODD  -> 1 + LEN/2
//////////////////////////////
assign xc_256 = ~|w_win_pkt[LB_LEN_MSB:LB_LEN_LSB];
assign xc_adj = w_win_pkt[LB_LEN_LSB] | w_win_pkt[LB_ADR_LSB];
assign w_xcnt = {xc_256,w_win_pkt[LB_LEN_MSB:LB_LEN_LSB+1]} + xc_adj;


always @(posedge clk)
begin
 if (rst) begin
   xcnt <= #UD {(LB_LEN_W){1'b0}};
 end else if (st == ST_GRANT) begin
   xcnt <= #UD w_xcnt;
 end else if (st == ST_RD0 && cmd == CMD_SWAP && xcnt == 1) begin
   xcnt <= #UD xcnt_bk;
 end else if (dc2mp_crdy && mp2dc_cen) begin
   xcnt <= #UD (xcnt > 0)? xcnt - 1'b1:0;
 end
end


//
// backup register for swap operation
//
always @(posedge clk)
begin
 if (rst) begin
   xcnt_bk <= #UD {(LB_LEN_W){1'b0}};
 end else if (st == ST_GRANT) begin
   xcnt_bk <= #UD w_xcnt;
 end
end


//
// data xfer counter
//
always @(posedge clk)
begin
 if (rst) begin
   dcnt <= #UD {(LB_LEN_W+1){1'b0}};
 end else if (st == ST_GRANT) begin
   dcnt <= #UD (w_cmd == CMD_FILL)? 9'd1:{xc_256,w_win_pkt[LB_LEN_MSB:LB_LEN_LSB]};
 end else if ((st == ST_WR0 || st == ST_WR1) &&
                  r_ack != {CLI_NUM{1'b0}} && dcnt != 9'd0) begin
   dcnt <= #UD dcnt - 1'b1;
 end
end


always @(posedge clk)
begin
 if (rst) begin
   odd_data <= #UD 1'b0;
 end else if (st == ST_GRANT) begin
   odd_data <= #UD w_win_pkt[LB_LEN_LSB];
 end
end


//
// address counter
//
always @(posedge clk)
begin
 if (rst) begin
   xadr <= #UD {LB_CMD_W{1'b0}};
 end else if (st == ST_GRANT) begin
   xadr <= #UD w_win_pkt[LB_ADR_MSB:LB_ADR_LSB];
 end else if (st == ST_RD0 && cmd == CMD_SWAP && xcnt == 1) begin
   xadr <= #UD xadr_bk;
 end else if (dc2mp_crdy && mp2dc_cen) begin
   xadr <= #UD xadr + 2;
 end
end


//
// backup register for swap operation
//
always @(posedge clk)
begin
 if (rst) begin
   xadr_bk <= #UD {LB_CMD_W{1'b0}};
 end else if (st == ST_GRANT) begin
   xadr_bk <= #UD w_win_pkt[LB_ADR_MSB:LB_ADR_LSB];
 end
end


//
assign mp2dc_rwb = (st == ST_RD0)? 1'b1:1'b0;
assign {mp2dc_ra,mp2dc_ba,mp2dc_ca} = {xadr[BA_W+ROW_W+COL_W+1:3],1'b0};


always @(posedge clk)
begin
 if (rst) begin
   mp2dc_wdt <= #UD {DAT_W{1'b0}};
 end else if (r_ack != {CLI_NUM{1'b0}}) begin
   mp2dc_wdt <= #UD w_win_pkt[LB_WDT_MSB:LB_WDT_LSB];
 end
end


always @(posedge clk)
begin
 if (rst) begin
   mp2dc_wbe <= #UD {DBE_W{1'b0}};
 end else if ((st == ST_WR0 || st == ST_WR1) && r_ack != {CLI_NUM{1'b0}}) begin
   mp2dc_wbe <= #UD w_win_pkt[LB_WBE_MSB:LB_WBE_LSB];
 end else if (st == ST_IDLE || (st == ST_WR1 &&
                   xcnt == 9'd1 && (xadr[2] ^ odd_data))) begin
   mp2dc_wbe <= #UD {DBE_W{1'b0}};
 end
end


//
// Read command fifo for data distributor
//
always @(posedge clk)
if (rst) begin
  rqwen <= #UD 1'b0;
end else begin
  rqwen <= #UD (st == ST_GRANT && (w_cmd == CMD_READ || w_cmd == CMD_SWAP));
end


assign rqwd = {r_winner[0],dcnt[7:0],xadr_bk[2]};


sfifo #(.DW(10), .AW(5))
i_rd_queue(
.clk (clk   ),
.rst (rst   ),
.wen (rqwen     ),
.wdt (rqwd      ),
.ren (rqren   ),
.rdt (rqrdt   ),
.space (/* open */),
.full (rqfull   ),
.empty (rqempty   )
);


//
// Data distributor
//
assign rqren = ~rqempty & (r_st == RST_IDLE || 
  (r_st == RST_VALID && r_cnt == 1 && dc2mp_rrdy && ~r_pst_avoid));
reg r_pst_avoid;


always @(posedge clk)
begin
 if (rst) begin
   r_st  <= #UD RST_IDLE;
 end else case (r_st)
   RST_IDLE :r_st <= #UD (~rqempty &&  rqrdt[0])? RST_AVOID:
                (~rqempty && ~rqrdt[0])? RST_VALID:RST_IDLE;
   RST_AVOID:r_st <= #UD (dc2mp_rrdy)? RST_VALID:RST_AVOID;
   RST_VALID:r_st <= #UD (dc2mp_rrdy && r_cnt == 1 && 
                                (rqempty || r_pst_avoid))? RST_IDLE:
    (dc2mp_rrdy && r_cnt == 1 && rqrdt[0])? RST_AVOID:RST_VALID;
 endcase
end


always @(posedge clk)
begin
 if (rst) begin
   r_pst_avoid <= #UD 1'd0;
 end else if (rqren) begin
   r_pst_avoid  <= #UD ~rqrdt[0] & rqrdt[1];
 end
end


always @(posedge clk)
begin
 if (rst) begin
   r_dst <= #UD 1'd0;
 end else if (r_st == RST_IDLE ||
              (r_st == RST_VALID && dc2mp_rrdy && r_cnt <= 1 && ~rqempty)) begin
   r_dst <= #UD rqrdt[9];
 end
end


always @(posedge clk)
begin
 if (rst) begin
   r_cnt <= #UD 8'd0;
 end else if (r_st == RST_IDLE) begin
   r_cnt <= #UD rqrdt[8:1];
 end else if (r_st == RST_VALID && dc2mp_rrdy) begin
   r_cnt <= #UD (r_cnt > 8'd1)? r_cnt - 1'b1:rqrdt[8:1];
 end
end


always @(posedge clk) p0_rdv <= #UD ( r_dst && dc2mp_rrdy && r_st == RST_VALID);
always @(posedge clk) p0_rdt <= #UD ( r_dst)? dc2mp_rdt:0;


always @(posedge clk) p1_rdv <= #UD (~r_dst && dc2mp_rrdy && r_st == RST_VALID);
always @(posedge clk) p1_rdt <= #UD (~r_dst)? dc2mp_rdt:0;


2010年11月3日水曜日

Statistics

このブログの統計情報によるとこの1週間の参加者は下のようになっている。


インドやポーランドから見にきている方が結構いらっしゃるみたいだ。その前の週まではこれらの国からのアクセスは無かったので、DDR2の検索でたどり着いたのかな?
こうやって世界と繋がっていくのかしら。
何だか面白い。とりあえず挨拶でもしておくか。

やっほー、インドの皆さんみてるぅ? (^-^)/""
Hello world, I'm here  (^-^)/""

TE0720 No.4 (BNN-PYNQを動かしてみる 2)

TE0720でBNN-PYNQを動かすことが出来た。 以下は前回に続いてBNN-PYNQが動くまでの記録。 gdb (GNU debugger)で例外が出る原因を調べてみた。 例外が発生しているのはシェアードライブラリ(python_hw-cnv-pynq.so)の中であ...