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 等も同じフォルダに入れておく必要があるようです。