VerilogでTVゲーム(veritak version は、3.01以上です。)
Verilog HDL設計の定本、「わかるVerilog HDL入門」で記述されているTVゲームをシミュレーションしてみました。
<本の紹介>
この本では、シミュレーションをCVERとVeritakで行っています。説明は、常にシミュレータのベンチとその結果について、書いてあるので、シミュレーションしながら、文法を理解することができます。実践的な入門書になっており、最近の規格であるVerilog2001の書法についても説明があります。また、豊富な記述例と図式化した文法の説明があり。Verilog HDLの教科書としてお勧めできます。
<遊び方>
アーカイブ(ビルドに誤りがあり修正しました。Jul.6.2006)を解凍して、Veritakプロジェクトをダブルクリックで起動してみてください。マウスを画面上でクリックすると、ラケットの位置が変わります。(1フレームごとにポーリングで見ているので反応は遅いです)
<なんでもシミュレーションしたい..>
書籍の最後の例題として説明されているのが、本題のTVゲームです。従来、この手の検証は、
等の手段で行っていました。しかしながら、Ontheflyで、デバッグができればその方がよいのは、言うまでもありません。では、何が障害かというと、
ということが挙げられます。幸い、Veritak3.01で、FastモードがReleaseされ、VPIも使えるようになりましたので、これを利用すれば、簡単なハードなら、onthefly
でも耐えられる範囲内だと思います。また、GUIは、Visual C#が無償、VisualC++も無償と最良の開発&デバッグ環境の条件が整いました。ということで、問題なのは、VPIの書き方だけです。
<VPIを含むシステム構成>
今回の構成は下のようになっています。DLLは、二つのプロセス、VeritakとViewerによって共有されますが、インスタンス的には、別物になります。そのために、共有ファイル(メモリ)を宣言しこれを介して情報のやりとりを行うのがミソです。
<ハードウェアに関して>
著者の方から許可をいただいてHDLソースをアーカイブに入れています。動作説明は、書籍に詳しく書かれています。
<テストベンチについて>
ハードをいじらないように注意しました。ハードをシミュレートするという趣旨から中身はいじらずに外側からの操作のみです。
しかしモジュールインターフェースだけでは、面倒な部分もあるので、階層を飛び越えて信号の参照を行ったりしています。
テストベンチがハードとDLLの橋渡しの役目をになっています。書籍では、テストベンチはありませんので、今回、都合のよいように作成しました。
太字の部分がVPIとのIF部です。1ライン分のDOTを一つのレジスタに格納し、最終DOTが来たときにVPIを呼び出して、1ライン分のデータを格納します。このときマウス位置に対応した数値がpos_counterに入ってきますのでそれをハードの可変抵抗によるワンショットに対応させています。XC95topがハードトップです。
module test_bench; reg XTAL=0; // X'tal OSC clock / NE555 clock reg MANCLK=0; // manual clock / NE555 clock reg RESET_N=0; // reset (active low) reg [ 7:0] DIPSW=0; // main board DIP switch reg [ 7:0] TGLSW=0; // toggle switch with chattering // tglsw[3:0] = on main board, // tglsw[7:4] = on ext. board reg [ 3:0] PSHSW=0; // push switch with chattering reg [ 3:0] TGLCFSW=0; // toggle switch chattering free wire [ 1:0] SEGSEL_N; // main board 7-segment LED select // [ 1:0] = {left, right} wire [15:0] EXTLED_N; // extension board LED // [15:0] = LED matrix column // = {right, ... left} // [15:8] = 7-segment LED // = {dot,g,f,e,d,c,b,a} // [ 7:0] = single LED wire [ 3:0] EXTSEGSEL_N; // extension wire STPMT_A; // step motor dirve (phase A+) wire STPMT_A_N; // (phase A-) wire STPMT_B; // (phase B+) wire STPMT_B_N; // wire [ 7:0] LED_N; // main board LED wire [ 7:0] SEG_N; // main board 7-segment LED always #50 XTAL=~XTAL;//10MHz initial begin #105 RESET_N=1; end //TAK BENCH START reg [511:0] xdot_line=0; always @(posedge XTAL) begin if (dut.x_disp) begin xdot_line[dut.x_count]=dut.squash.video_data; end if (dut.x_count==511) begin $write_line_to_shared_file(dut.y_count,xdot_line,pos_counter);//y_counter, x_dot_line, delay_counter 0-32000 ->0pos -max pos end end wire trigger_pulse=EXTLED_N[0];//Trigger Pulse from Hardware integer counter=0; integer pos_counter=15000; always @(posedge XTAL) begin if (!trigger_pulse) counter<=0; else counter <=counter+1; end //Simulate One shot pulse always @(posedge XTAL) begin if (!trigger_pulse) TGLCFSW[0] <=1; else if (counter >pos_counter) TGLCFSW[0]<=0; end //TAK BENCH END XC95top dut( XTAL, // X'tal OSC clock / NE555 clock MANCLK, // manual clock / NE555 clock RESET_N, // reset (active low) DIPSW, // main board DIP switch TGLSW, // toggle switch with chattering // tglsw[3:0] = on main board, // tglsw[7:4] = on ext. board PSHSW, // push switch with chattering TGLCFSW, // toggle switch chattering free LED_N, // main board LED SEG_N, // main board 7-segment LED // [ 7:0] = {dot,g,f,e,d,c,b,a} SEGSEL_N, // main board 7-segment LED select // [ 1:0] = {left, right} EXTLED_N, // extension board LED // [15:0] = LED matrix column // = {right, ... left} // [15:8] = 7-segment LED // = {dot,g,f,e,d,c,b,a} // [ 7:0] = single LED EXTSEGSEL_N, // extension 7-segment LED select // [ 3:0] = {left, ... right} for 7-seg. LED // [ 1] = matrix clock for matrix LED STPMT_A, // step motor dirve (phase A+) STPMT_A_N, // (phase A-) STPMT_B, // (phase B+) STPMT_B_N // (phase B-) ); endmodule
<Viewer(GUI) C#ソースについて>
信じられないことに、これが手書きしたソースのすべてです。C#では、GUIが非常に簡単に書けることがお分かりでしょう。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; using System.Drawing.Imaging; // for PixelFormat namespace myviewer { public partial class Form1 : Form { public Form1() { InitializeComponent(); x_word_size = 0; y_line_size = 0; event_req = false; frame_counter = 0; } int x_word_size; int y_line_size; int frame_counter; bool event_req; [DllImport("USER32.DLL")] public static extern int MessageBoxA( int hWnd, String lpText, String lpCaption, uint uType ); [DllImport("my_vpi5.dll")] public static extern uint read_word_data(int pos); [DllImport("my_vpi5.dll")] public static extern void clicked_mouse_position(int xpos,int ypos); private void clicked(object sender, EventArgs e) { } protected override void WndProc(ref Message m) { const int WM_CLOSE = 0x0010; const int WM_ENDSESSION = 0x16; const int WM_SYSCOMMAND = 0x112; const int SC_CLOSE = 0xF060; const int WM_COMMAND = 0x111; const int WM_SIZE = 0x0005; switch (m.Msg) { case WM_ENDSESSION: //OSのシャットダウンで閉じられようとしている //Console.WriteLine("WM_ENDSESSION"); break; case WM_SYSCOMMAND: //if (m.WParam.ToInt32() == SC_CLOSE) //Xボタン、コントロールメニューの「閉じる」、 //コントロールボックスのダブルクリック、 //Atl+F4などにより閉じられようとしている // Console.WriteLine("SC_CLOSE"); break; case WM_CLOSE: //Application.Exit以外で閉じられようとしている //Console.WriteLine("WM_CLOSE"); break; case WM_SIZE: pictureBox1.Refresh(); break; case WM_COMMAND: x_word_size = (int)m.WParam; y_line_size = (int)m.LParam; event_req = true;//コマンドの時だけDrawする、CPU 負荷大の為 pictureBox1.Refresh();//再ドロー frame_counter++; event_req = false; break; } base.WndProc(ref m); } private void paint(object sender, PaintEventArgs e) { int sizeX = x_word_size * 32; int sizeY = y_line_size; if (!event_req || sizeX == 0 || sizeY == 0) return; Bitmap bBitmap = new Bitmap(sizeX,sizeY); // 変数の宣言 Graphics g = Graphics.FromImage(bBitmap); for (int Y = 0; Y < sizeY; Y++) { for (int X = 0; X < x_word_size; X++) { uint data = read_word_data(X + Y * x_word_size); for (int i = 0; i < 32; i++) { uint bit = (data >> i) & 0x01; int xaddress = X * 32 + i; if ( bit== 0x01) { bBitmap.SetPixel(xaddress, Y, Color.White); // 白色に設定 } else bBitmap.SetPixel(xaddress, Y, Color.Black); // 黒色に設定 } } } Font objFont1 = new Font("MS Pゴシック", 9); string strh1 = frame_counter.ToString("d4"); string strd1 = "C="; string str = strd1 + strh1; g.DrawString(str, objFont1, Brushes.Yellow, sizeX - 50, 0); string str_x_words = x_word_size .ToString("d4"); ; string str_y_line_size =y_line_size.ToString("d4"); g.DrawString(str_x_words, objFont1, Brushes.Yellow, sizeX - 50, 20); g.DrawString(str_y_line_size, objFont1, Brushes.Yellow, sizeX - 50, 40); pictureBox1.Image = bBitmap; } private void mouse_click(object sender, MouseEventArgs e) { clicked_mouse_position(e.X, e.Y); } } }
<VPIソースについて>
my_vpi5.dllに追加しています。まずは、スタートアップルーチンに登録関数を追加します。
# include "vpi_user.h" extern void sys_math_vpi_register(void); extern void sys_shared_file_register(void);//Jul.4.2006 __declspec(dllexport) void (*vlog_startup_routines[])() = { sys_math_vpi_register, sys_shared_file_register,//Jul.4.2006 0 };
次に登録関数を書きます。
extern "C" void sys_shared_file_register()//VPI call/callback register routine { s_vpi_systf_data tf_data; tf_data.type = vpiSysTask; tf_data.tfname = "$write_line_to_shared_file"; tf_data.calltf = write_to_shared_file;//y line_no,x_dot_reg tf_data.compiletf = 0; tf_data.sizetf = 0; vpi_register_systf(&tf_data); }
中身です。
1フレーム分のDOTが溜まったら、GUIに読み取り要求を発行します。
なお、別プロセスですので、本質的に同期の問題があります。
( ソースでは、Postmessageにしてスレッドを分けていますが、真面目にやる場合は、Sendmessageにしてください。 )
#include "shared_file.h" #include "vpi_user.h" #include "shellapi.h" #include "KLdebug.h" shared_class sh1;//インスタンス //外部Export関数 extern "C" __declspec(dllexport) unsigned read_word_data(unsigned pos) {//binary read unsigned data= sh1.read_word_data(pos); unsigned xpos=pos%16; unsigned ypos=pos/16; return data; } extern "C" __declspec(dllexport) void clicked_mouse_position(int xpos,int ypos) {//binary read if (!sh1.failure) sh1.set_mouse_xpos(xpos); } void shared_class::start_up() { hSharedMap= OpenFileMapping( FILE_MAP_ALL_ACCESS, // アクセスモード 0, // 継承フラグ MAPPED_FILE_NAME // オブジェクト名 ); if (hSharedMap){//既にCreate済 if((filebuffer =(char*) MapViewOfFileEx(hSharedMap,FILE_MAP_ALL_ACCESS,0,0,0,NULL))==NULL){ failure=true; MessageBox(0,"MapViewOfFileEx Error",0,MB_OK); goto exit_loop; } //共有File Mapping } else {//まだCreateしていない。 hSharedMap = CreateFileMapping( (HANDLE)0xffffffff, NULL, PAGE_READWRITE | SEC_COMMIT, 0, MAX_SHARED_FILE_SIZE, MAPPED_FILE_NAME); if (!hSharedMap){ MessageBox(0,"CreateFileMapping Error",0,MB_OK); goto exit_loop; } if((filebuffer =( char*) MapViewOfFileEx(hSharedMap,FILE_MAP_ALL_ACCESS,0,0,0,NULL))==NULL){ failure=true; MessageBox(0,"MapViewOfFileEx Error",0,MB_OK); goto exit_loop; } //共有変数 マウスPosの初期化 sh1.set_mouse_xpos(0); } exit_loop: size=MAX_SHARED_FILE_SIZE; return; } void shared_class::destroy() { if (hSharedMap) UnmapViewOfFile(hSharedMap); filebuffer=0; hSharedMap=0; } #define DUAL_CPU void send_data(HWND result) { #ifndef DUAL_CPU ::SendMessage(reinterpret_cast<HWND>(result),//handle GUIへ読み取り要求 #else ::PostMessage(reinterpret_cast<HWND>(result),//handle GUIへ読み取り要求 #endif WM_COMMAND,//WM_COMMAND sh1.get_x_word_size(),//WPARAM sh1.get_y_line_size()+1);//LPARAM } void send_data_to_gui() { HWND handle=FindWindow(NULL,MYGUI ); if (handle){ send_data(handle); }else { int result=reinterpret_cast<int>( ShellExecute(NULL, "open",MYGUI_EXE, NULL, NULL, SW_SHOWNORMAL)); for (unsigned i=0;i<6;i++){ Sleep(1000); handle=FindWindow(NULL,MYGUI ); if (handle) break; }//立ち上げ待ち if (handle){//立ち上がったら送る send_data(handle); } } } static int write_to_shared_file(char* name) { vpiHandle systfref, argsiter, argh; s_vpi_value value; systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */ argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */ argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */ if (argh && !sh1.failure){ value.format =vpiIntVal;// vpi_get_value(argh, &value); unsigned yline=value.value.integer;//一番目の引数は、ylineno argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */ if (argh){ value.format =vpiVectorVal;// unsigned wwid = vpi_get(vpiSize,argh);//ビット幅を知る vpi_get_value(argh, &value);//1Line分のDOTHardwareから読み取る unsigned words=(wwid+31)/32; bool done_line_operation=false; if (yline< sh1.get_y_line_size()){//新しいデータがやってきたので、GUIに全部読み取らせる sh1.set_x_word_size(words); send_data_to_gui(); done_line_operation=true; //ビデオ表示完了 } sh1.set_y_line_no(yline); for (unsigned i=0;i<words;i++){ sh1.write_word_data(yline*words+i,value.value.vector[i].aval);//共有ファイルに1Line書き込み } if (done_line_operation){ argh = vpi_scan(argsiter); if (argh){//3rdパラメータは、OneShotDelay値 int xpos=sh1.get_mouse_xpos(); #define MAX_DELAY 38000 if (xpos <40) xpos=200; if (xpos >(wwid-1)) xpos=wwid-1; int delay_counter=(xpos*MAX_DELAY)/wwid; value.format =vpiIntVal;// value.value.integer=delay_counter, vpi_put_value(argh, &value,NULL,vpiNoDelay); }else { vpi_printf("$write_to_shared_file: missing 3rd parameter. \n"); vpi_sim_control(vpiFinish, 1); } } }else {//error vpi_printf("$write_to_shared_file: missing second parameter. \n"); vpi_sim_control(vpiFinish, 1); } }else {//error vpi_printf("$write_to_shared_file: missing first parameter. \n"); vpi_sim_control(vpiFinish, 1); } if (argh) vpi_free_object(argsiter); return 0; }
<拡張について>
白黒しか対応していませんが、RGBにすることも容易でしょう。
<デバッグの注意>
C#からDLLの呼び出しは、関連するEXE,DLLを作業フォルダにいれておかないとモジュールが見つからない、と怒られます。
例えば、my_vpi5.dll を呼び出すのだったらveritak2.exe vpi.dll 等も同じフォルダに入れておく必要があるようです。