FPGAのライフゲーム





ゲームオブライフ -セルラオートマトンは、すでにすべての可能なプログラミング言語で記述されているようです。



FPGAテクノロジーに興味があります。したがって、FPGA Alter Cyclone IIIのライフインプリメンテーションを作成した後は、 真実はチップに収まり、ほんのわずかです。32x16セルしかありません。 このような小さな分野では、複雑な数字を体験することは非常に困難です。



今、もう1つのボードを手にしています。すでに50,000個のロジックエレメントを備えたアルテラのMAX10があります。 面白かったのですが、少なくとも4回フィールドを拡大できますか? 一般的に、私は少なくとも64x32を作成することにしました。



結果はこのビデオに示されており、私はこの写真を「ゴスパーの銃が自殺する」と呼んでいます。



以下に実装の詳細を示します。

実際、3つ目のサイクロンの以前のプロジェクトで既に持っていたゲームの実装。 プロジェクト全体は、いわば、いくつかの部分で構成されています。



ゲームのライフフィールドは、Verilog HDLで記述された相互接続されたセルモジュールで構成されているため、1クロックサイクルで次世代のセル全体を計算できます。

画像

これはFPGAであるため、このような実装のみが必要です。 これは、並行して同時に動作し、パラメーターを相互に渡す複数の計算機のモデルです。 この並列性は想像力をかきたてるものです:ゲームフィールド64x32 = 2048の並列コンピューターがFPGAで同期して動作しています! モジュールと1つのセルのすべてのロジックは、Verilog HDLで記述されています。



module xcell( input wire clk, input wire seed_ena, input wire life_step, input wire in_up_left, input wire in_up, input wire in_up_right, input wire in_left, input wire in_right, input wire in_down_left, input wire in_down, input wire in_down_right, output reg cell_life ); wire [3:0]neighbor_number; assign neighbor_number = in_up_left + in_up + in_up_right + in_left + in_right + in_down_left + in_down + in_down_right; always @(posedge clk) if(seed_ena) cell_life <= in_left; //do load initial life into cell else if(life_step) //recalculate new generation of life begin if( neighbor_number == 3 ) cell_life <= 1'b1; //born else if( neighbor_number < 2 || neighbor_number > 3 ) cell_life <= 1'b0; //die end endmodule
      
      







次に、このモジュールのインスタンスが繰り返し作成され、Verilog HDL言語のgenerate-endgenerate構造を使用して、単一のフラットフィールド内のワイヤで相互接続されます。



プロジェクトで2番目に重要なモジュールは、シリアルポートを介してゲームの初期状態を読み込むためのモジュールです。 ステータスは、次のようなテキストファイルとして送信されます。



1 ------ ** ------------------------

2 ----- *-* -----------------------

3 ---- * ---- * ----------------------

4 --- * ------ * ---------------------

5 --- * ------ * ---------------------

6 ---- * ---- * ----------------------

7 ----- *-* -----------------------

8 ------ ** ------------------------

9 --------------------------------

--------------------------------

B --------------------------------

C --------------------------------

D --------------------------------

E --------------------------------

F --------------------------------



重要な文字は、「*」(生細胞)と「-」(生命なし)のみです。 115200ボーレート、8ビット、1ストップ、パリティなし。 プロジェクトをロードしている間、ボード上のボタンを保持する必要があります-テキストファイルに記述された新しい状態でライフフィールドがシードされます。



さて、最後のものはゲームの現在の状態を表示するためのモジュールです。 これは、セルセルの状態が定期的にコピーされるテキストビデオアダプタです。 すべてのセルはサイクリックシフトレジスタで接続されているため、ゲームのフィールド全体を読み取り、WIDTH * HEIGHTメジャーでビデオアダプターに書き込むことができます。



もちろん、これはすべて「トリッキーな」ゲーム自体のロジックは単純ですが、サービスモジュール、ロードモジュール、表示モジュールは「ライフ」自体よりもほとんど複雑であるため、非常に複雑です。



また、画面上の表示について詳しく説明します。 古いプロジェクトをMAX10に移植し、まったく異なるボードを使用するには、Mars rover3を少し工夫する必要があります。 実際には、ボードにはVGAコネクタがなく、操作がとても簡単で快適でした。 このボードにはHDMIコネクタがあり、HDMIラインはFPGAチップに直接接続されます。



HDMIラインを管理するために、多くの資料が研究されました。 このプロジェクトは、 www.fpga4fun.com / HDMI.htmlで基礎として取り上げられました。ここでは、すべてがかなり詳細に説明されています。



HDMIは、差動ペアでのシリアル伝送を使用します。 わずか4ペア。 3つのペアは、8ビットのR、G、BカラーとHSYNCおよびVSYNC制御信号を送信します。 シリアル伝送のため、TMDSエンコードには、画面上のピクセル周波数の10倍の動作周波数が必要です。 ピクセル周波数が74 MHzで解像度が1280x720の場合、信号のエンコードには既に740 MHzが必要であり、これは非常に大きなものです。 この状況は、FPGA出力にビルトインDDIOインターフェース、つまり2対1のシリアライザーがあるという事実によって保存されます。 そのため、プロジェクトの最大周波数を370 MHzに減らすことができます。



HDMIモジュールのソースコードを以下に示します。



 module hdmi( input wire pixclk, // 74MHz input wire clk_TMDS2, // 370MHz input wire hsync, input wire vsync, input wire active, input wire [7:0]red, input wire [7:0]green, input wire [7:0]blue, output wire TMDS_bh, output wire TMDS_bl, output wire TMDS_gh, output wire TMDS_gl, output wire TMDS_rh, output wire TMDS_rl ); wire [9:0] TMDS_red, TMDS_green, TMDS_blue; TMDS_encoder encode_R(.clk(pixclk), .VD(red ), .CD(2'b00) , .VDE(active), .TMDS(TMDS_red)); TMDS_encoder encode_G(.clk(pixclk), .VD(green), .CD(2'b00) , .VDE(active), .TMDS(TMDS_green)); TMDS_encoder encode_B(.clk(pixclk), .VD(blue ), .CD({vsync,hsync}), .VDE(active), .TMDS(TMDS_blue)); reg [2:0] TMDS_mod5=0; // modulus 5 counter reg [4:0] TMDS_shift_bh=0, TMDS_shift_bl=0; reg [4:0] TMDS_shift_gh=0, TMDS_shift_gl=0; reg [4:0] TMDS_shift_rh=0, TMDS_shift_rl=0; wire [4:0] TMDS_blue_l = {TMDS_blue[9],TMDS_blue[7],TMDS_blue[5],TMDS_blue[3],TMDS_blue[1]}; wire [4:0] TMDS_blue_h = {TMDS_blue[8],TMDS_blue[6],TMDS_blue[4],TMDS_blue[2],TMDS_blue[0]}; wire [4:0] TMDS_green_l = {TMDS_green[9],TMDS_green[7],TMDS_green[5],TMDS_green[3],TMDS_green[1]}; wire [4:0] TMDS_green_h = {TMDS_green[8],TMDS_green[6],TMDS_green[4],TMDS_green[2],TMDS_green[0]}; wire [4:0] TMDS_red_l = {TMDS_red[9],TMDS_red[7],TMDS_red[5],TMDS_red[3],TMDS_red[1]}; wire [4:0] TMDS_red_h = {TMDS_red[8],TMDS_red[6],TMDS_red[4],TMDS_red[2],TMDS_red[0]}; always @(posedge clk_TMDS2) begin TMDS_shift_bh <= TMDS_mod5[2] ? TMDS_blue_h : TMDS_shift_bh [4:1]; TMDS_shift_bl <= TMDS_mod5[2] ? TMDS_blue_l : TMDS_shift_bl [4:1]; TMDS_shift_gh <= TMDS_mod5[2] ? TMDS_green_h : TMDS_shift_gh [4:1]; TMDS_shift_gl <= TMDS_mod5[2] ? TMDS_green_l : TMDS_shift_gl [4:1]; TMDS_shift_rh <= TMDS_mod5[2] ? TMDS_red_h : TMDS_shift_rh [4:1]; TMDS_shift_rl <= TMDS_mod5[2] ? TMDS_red_l : TMDS_shift_rl [4:1]; TMDS_mod5 <= (TMDS_mod5[2]) ? 3'd0 : TMDS_mod5+3'd1; end assign TMDS_bh = TMDS_shift_bh[0]; assign TMDS_bl = TMDS_shift_bl[0]; assign TMDS_gh = TMDS_shift_gh[0]; assign TMDS_gl = TMDS_shift_gl[0]; assign TMDS_rh = TMDS_shift_rh[0]; assign TMDS_rl = TMDS_shift_rl[0]; endmodule module TMDS_encoder( input clk, input [7:0] VD, // video data (red, green or blue) input [1:0] CD, // control data input VDE, // video data enable, to choose between CD (when VDE=0) and VD (when VDE=1) output reg [9:0] TMDS = 0 ); wire [3:0] Nb1s = VD[0] + VD[1] + VD[2] + VD[3] + VD[4] + VD[5] + VD[6] + VD[7]; wire XNOR = (Nb1s>4'd4) || (Nb1s==4'd4 && VD[0]==1'b0); wire [8:0] q_m = {~XNOR, q_m[6:0] ^ VD[7:1] ^ {7{XNOR}}, VD[0]}; reg [3:0] balance_acc = 0; wire [3:0] balance = q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7] - 4'd4; wire balance_sign_eq = (balance[3] == balance_acc[3]); wire invert_q_m = (balance==0 || balance_acc==0) ? ~q_m[8] : balance_sign_eq; wire [3:0] balance_acc_inc = balance - ({q_m[8] ^ ~balance_sign_eq} & ~(balance==0 || balance_acc==0)); wire [3:0] balance_acc_new = invert_q_m ? balance_acc-balance_acc_inc : balance_acc+balance_acc_inc; wire [9:0] TMDS_data = {invert_q_m, q_m[8], q_m[7:0] ^ {8{invert_q_m}}}; wire [9:0] TMDS_code = CD[1] ? (CD[0] ? 10'b1010101011 : 10'b0101010100) : (CD[0] ? 10'b0010101011 : 10'b1101010100); always @(posedge clk) TMDS <= VDE ? TMDS_data : TMDS_code; always @(posedge clk) balance_acc <= VDE ? balance_acc_new : 4'h0; endmodule module ddio( input wire d0, input wire d1, input wire clk, output wire out ); reg r_d0; reg r_d1; always @(posedge clk) begin r_d0 <= d0; r_d1 <= d1; end assign out = clk ? r_d0 : r_d1; endmodule
      
      







Mars rover3ボードのプロジェクト全体は、githubで取得できます: github.com/marsohod4you/FPGA_game_life



アルテラQuartus Primeコンパイラレポート:

フローステータス成功-木4月28日16:08:48 2016

Quartus Primeバージョン15.1.0ビルド185 2015年1月21日SJ Liteエディション

リビジョン名max10_50

トップレベルのエンティティ名

ファミリーMAX 10

デバイス10M50SAE144C8GES

タイミングモデルの予備

合計ロジックエレメント29,432 / 49,760(59%)

合計組み合わせ関数28.948 / 49.760(58%)

専用ロジックレジスタ2,238 / 49,760(4%)

合計レジスター2254

ピン総数23/101(23%)

合計仮想ピン0

合計メモリビット147,456 / 1,677,312(9%)

Embedded Multiplier 9ビット要素0/288(0%)

合計PLL 1/1(100%)

UFMブロック0/1(0%)

ADCブロック0/1(0%)



おそらく、ゲームの「人生」はすでに多くにうんざりしています。 しかし、私の意見では、熟考することがあります。 そのシンプルさにもかかわらず、相互接続された計算機の興味深い原理が含まれています。 おそらく同様のアイデアは、特別なクラスの問題で使用できます。 たとえば、回路基板にコンポーネントを配置することは、コンポーネント間の接続の長さなど、多くの要因を考慮する必要がある複雑な組み合わせ作業です。 プリント回路基板上のコンポーネントは、コンポーネント間の結合力の影響下で生活の場のより良い場所を求めて戦っているセルであると想像できます。 時間の経過とともに、そのようなタスクはFPGAを使用して計算されると思います。



All Articles