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