ビルトインパッケージ (Std Package)

概要

SystemVerilogには、組み込み済のライブラリパッケージがあります。この中では、以下の部品が定義されています。

上の3つは、クラスが定義されていて、その実装は、シミュレーションベンダによります。

プロセス クラス(Fine Grain Process Control)
よく使われるのは、メールボックスだと思いますが、その実装では、セマフォが使われ、セマフォの実装には、プロセスを使っています。つまりプロセス クラスというのは、Lowレベルなクラスになります。実際、このクラスは特殊なクラスで、newでインスタンス化することはできませんし、クラスを継承することもできません。プロセス クラスと名前はついていますが、その実体は、シミュレータのスレッドをクラスでラップしたものです。VerilogHDLでは今まで、ユーザに生のスレッドを制御させる手段を提供してきませんでしたが、これは生のスレッドに対する制御になります。disable、wait fork、disable forkといった類では、制御しずらい場面で威力を発揮します。そういう意味で、細粒度のコントロールが可能、fine grain process control、になっています。

いずれにしても、かなり精通した人向けの上級機能だと思います。

class process;
	typedef enum { FINISHED, RUNNING, WAITING, SUSPENDED, KILLED } state;
	extern local function new();	// illegal to call
	extern static function process self();
	extern function state status();
	extern function void kill();
	extern task await();
	extern function void suspend();
	extern function void resume();
endclass


プロセスクラスのenumは、スレッドの状態を指します。

状態
FINISHED スレッドが正常終了した
RUNNING 現在、スレッドが走行中、(ブロックされていない。)
WAITING 実行待ちにある。#delayや、@(expression) ,wait等による実行待ち
SUSPENDED resume待ち
KILLED kill コマンドまたは、disableコマンドによりスレッドが消去された


この状態は、status()で取得することができます。プロセスクラスは、スレッドをラップしたものですが、どのようにスレッドにアクセスすればよいのでしょうか?

そのための関数が、self()で次のように使います。

program process_test;

        std::process p1;//std::は、なしで可
        
        task automatic t1;
                        p1=process::self();//現在走行中のスレッドの取得
                        $display("%p %d",p1.status(),$time);//%pでenum表示される
                        #100;
                        $display("---Finished t1 task.");
        endtask
        initial begin
                        
                        
                        fork //二つのスレッドを平行起動して
                                #20 $display("%p %d",p1.status(),$time);//途中で、スレッドの状態を見る
                                t1();
                        join
                        $display("%p %d",p1.status(),$time);//終了後にスレッドの状態を見る
        end

endprogram

実行結果です。

***** Veritak SV32 Engine Version 446 Build May 28 2013*****

RUNNING                    0
WAITING                   20
---Finished t1 task.
FINISHED                  100

---------- シミュレーションを終了します。time=100ns


つまり、スレッドを走行させて初めて取得できる、という訳です。通常の実装では、スレッドは、initialやalways,task コール fork- joinで実行時に生成されます。
ですから、スレッドの存在しない系( constant functionや継続的代入文)では、使用することができません。

サスペンドとリジューム
サスペンドは、一時休止状態です。resumeコマンドでのみ再開することができるので、resumeコマンドを待っている状態とも言えます。
サスペンドは、クラスハンドルを介し、自分自身でサスペンドすることが出来ますが、一旦サスペンドしてしまうと、自分自身で再開することはできません。誰かにresumeしてもらうしかありません。

program process_test;

	std::process p1;
	
	task automatic t1;
			p1=process::self();
			$display("%p %d",p1.status(),$time);
			p1.suspend();//自分自身をサスペンド
			$display("%p %d",p1.status(),$time);
			#100;
			$display("---Finished t1 task. %d",$time);
	endtask
	initial begin
			
			
			fork 
				#20 $display("%p %d",p1.status(),$time);
				#40 p1.resume();//レジュームする	
				t1();
			join
			$display("%p %d",p1.status(),$time);
	end

endprogram

実行結果です。

***** Veritak SV32 Engine Version 446 Build May 28 2013*****

RUNNING                    0
SUSPENDED                   20
RUNNING                   40
---Finished t1 task.                  140
FINISHED                  140

---------- シミュレーションを終了します。time=140ns

resumeで、次の行から実行が再開されます。suspend状態にある間は、delayや、wait, eventによる再開はありません。たとえば、delay中にsuspendされて、delay時間経って再開することはありません。delay時間前に、resumeされた場合は、delay後にスレッドが再開しますが、delay時間経過後にresumeされた場合は、resumeされた時点で、現在時刻(Activeまたは、ReActve Region)に再スケジュールされます。

suspendは、自分自身をsuspendするときブロッキングします。それ以外はブロッキングしません。

kill

kilは、スレッドの強制終了です。強制終了したスレッドでは、waitや、delayも強制終了となります。また、起動したサブスレッドも強制終了となります。ここまでの動作は、disableと同じです。違う点は、killは、単体スレッドについて作用するのに対して、disableは、同じラベルの兄弟スレッドについても作用します。兄弟スレッドが存在しないときは、動作は同じです。
なお、自分で自分をkillすることが可能なのは、disableに同じです。

await

指定したスレッドの終了を待ちます。これは、ブロッキング命令で、指定したスレッドが終了するまで、制御は戻ってきません。ブロッキングします。また、自分自身をawaitすることはできません。

killとawaitの例

5つのスレッドを起動し、3番目に起動したスレッドの終了を待ちます。その後、未だ実行中のスレッドは、killで強制終了させます。

program  pr24;
	parameter int N=5;

	task do_N_jobs( );

		process job[1:N];
		foreach( job[j]) 
			fork
				automatic int k = j;
				begin 
					job[k] = process::self();
					#(k);
					$display("k=%d time=%d",k,$time);
				end
			join_none
		foreach( job[j]) begin // wait for all processes to start
			wait( job[j] != null );
			$display("Job[%1d]=%p started time:%d",j, job[j].status(),$time);	
		end

		job[2].await();//wait job[2] is finishedl
		$display("Job[2] has finished!\n");
	
	
		foreach( job[k]) 		$display("job[%1d]=%p",k, job[k].status());
		$display("\n");		

		foreach (job[k]) 
			if ( job[k].status() != process::FINISHED ) begin
				$display("Killing the job[%1d]",k);
				job[k].kill();
			end
		$display("\n");

		foreach( job[k]) 		$display("job[%1d]=%p",k, job[k].status());


	endtask

	initial begin
		do_N_jobs();
		$display("Finished!");
	end



endprogram
***** Veritak SV32 Engine Version 447 Build Jun  7 2013*****

Job[1]=WAITING started time:                   0
Job[2]=WAITING started time:                   0
Job[3]=WAITING started time:                   0
Job[4]=WAITING started time:                   0
Job[5]=WAITING started time:                   0
k=          1 time=                   1
k=          2 time=                   2
Job[2] has finished!

job[1]=FINISHED
job[2]=FINISHED
job[3]=WAITING
job[4]=WAITING
job[5]=WAITING


Killing the job[3]
Killing the job[4]
Killing the job[5]


job[1]=FINISHED
job[2]=FINISHED
job[3]=KILLED
job[4]=KILLED
job[5]=KILLED
Finished!

---------- シミュレーションを終了します。time=5ns



セマフォの実装

プロセスクラスを使って、セマフォの実装を行ってみましょう。

-セマフォは、std::パッケージにビルトインしているので、あえて作る必要はないのですが、VeritakSVの内部実装ということで見ていただければよいかと思います

ここでは、my_semaphore というクラスを作ることにします。


セマフォクラスは、次のような定義です。
class semaphore;
 function new(int keyCount = 0);
 function void put(int keyCount = 1);
 task get(int keyCount = 1);
 function int try_get(int keyCount = 1);
endclass


ここで、getがtaskであり、それ以外は、functionで実装されていることが分かります。つまり、putとgetでは、時間を消費しない、ブロッキングされない実装にしなくてはいけないということが分かります。 一方taskであるgetは、keyを取得できなかったときは、取得できるまで待つことになります。さて、取得できるまで待つ、取得できたら再開というのは、どう書けばよいでしょうか? そんなときは、プロセスクラスの出番です。suspend()で待って、他のスレッドでresumeをしてやればよさそうです。getというのは、内部カウンタkeyが減ることを意味し、反対にputは、内部カウンタkeyが増えることになる唯一のメソッドです。ですから、putの実装でresume()してやればよいということになります。
class my_semaphore;
        typedef struct {
                std::process p1;
                int req_key_count;
        }key_count_process;

        local key_count_process queue[$];
        local int Count;

        function new(int keyCount);
                this.Count = keyCount;
        
        endfunction

        function void put(int keyCount );
        
                this.Count +=keyCount;
        
                while (queue.size()) begin
                        if (this.Count >=queue[0].req_key_count) begin
                                key_count_process K;
                                K= queue.pop_front();
                                Count -=K.req_key_count;
                                K.p1.resume();
                        end else break;
                end
        endfunction

        task get(int keyCount );
                if (this.Count>=keyCount) this.Count -=keyCount;
                else begin
                
                        key_count_process kp;
                        kp.req_key_count=keyCount;
                        kp.p1=process::self();
        
                        queue.push_back(kp);
                        kp.p1.suspend();
                end

        endtask
        function int try_get(int keyCount );
                        if (this.Count>=keyCount) return 1;
                        else return 0;
        endfunction
endclass
メンバは、keyの個数をカウントするCountと、取得できなかったスレッドと要求数を待ち行列とするキューです。getで要求keyを取得できなかった場合には、それをUnboundedキューに貯めておきます。スレッドは、無制限に生成されることが考えられるのでこのような処理にしています。一方putでは、key数を増やしますが、その際に貯まっているキューで処理可能なものは、順番にresumeして行きます。


複数のキーを持つセマフォの問題

キーが1個の場合の動作は、mutexと動作は同じになります。複数のキーを持つ場合には、注意することがあります。

このセマフォでプログラムを次のように書きました。3つのスレッドがありますが、この実行順は分かりますでしょうか?

program semaphore_example ;

 my_semaphore sm =new(0);
 initial 
        fork
                begin:Thread1
                        sm.put(2);
                        sm.get(4);//2個しか残っていないので取得できず、suspend
                        $display("Thread1, completed at time %d",$time);        
                end
                begin:Thread2
                        #10;
                        sm.get(2);//取得できる。FIFO動作とならず、Thread1を追い越してしまう
                        $display("Thread2, completed at time %d",$time);//終了
                end
                begin:Thread3
                        #20;
                        sm.put(4);//4個のキーを与えるのでThread1をresumeする
                        $display("Thread3, completed at time %d",$time);
                end
        join

endprogram

実行結果は次です。

***** Veritak SV32 Engine Version 446 Build May 28 2013*****

Thread2, completed at time                   10
Thread3, completed at time                   20
Thread1, completed at time                   20

---------- シミュレーションを終了します。time=20ns

DelayがないThread1から実行されます。4個のキーを取得しようとしますが、2個しか残っていないので、取得できずsuspendされます。次にThread1が実行されます。2個のキーを取得できsuspendされずに実行継続になります。結果的にThread1の実行よりも早く終了してしまいます。Thread3で4個のキーを追加していますが、このとき、Thread1をresumeしています。
Thread3の終了後にThread1が実行されます。

このように複数のキーを持つ場合は、必ずしもgetの実行順に実行される(FIFO動作)とは限らないことに注意してください。この動作は、my_semaphoreに限らず、std::semaphoreでも同じです。(LRMで、この動作の規定はないのですが他のシミュレータでも同じ結果となります。)

FIFO動作させたい場合には、getを常にget(1)で取得するとよいでしょう。

program semaphore_example ;

 my_semaphore sm =new(0);
 initial 
	fork
		begin:Thread1
			sm.put(2);
			repeat(4) sm.get(1);//一個づつ取得すれば、FIFO動作になる
			$display("Thread1, completed at time %d",$time);	
		end
		begin:Thread2
			#10;
			repeat(2) sm.get(1);//一個づつ取得すれば、FIFO動作になる
			$display("Thread2, completed at time %d",$time);
		end
		begin:Thread3
			#20;
			sm.put(4);
			$display("Thread3, completed at time %d",$time);
		end
	join

endprogram

あるいは、my_semaphoreを継承し、getを次のように書き換えます。(キューにデータがある場合には、常にキューイングするようにしてしまいます。)

virtual task get(int keyCount );

                if (queue.size() || this.Count< keyCount) begin
                        key_count_process kp;
                        kp.req_key_count=keyCount;
                        kp.p1=process::self();
        
                        queue.push_back(kp);
                        kp.p1.suspend();
                end else this.Count -=keyCount;
        endtask

全体のソースです。

        
class my_semaphore;
        typedef struct {
                std::process p1;
                int req_key_count;
        }key_count_process;


        protected key_count_process queue[$];
        protected int Count;

        function new(int keyCount);
                this.Count = keyCount;
        
        endfunction

        function void put(int keyCount );
        
                this.Count +=keyCount;
        
                while (queue.size()) begin
                        if (this.Count >=queue[0].req_key_count) begin
                                key_count_process K;
                                K= queue.pop_front();
                                Count -=K.req_key_count;
                                K.p1.resume();
                        end else break;
                end
        endfunction

        virtual task get(int keyCount );
                if (this.Count>=keyCount) this.Count -=keyCount;
                else begin
                
                        key_count_process kp;
                        kp.req_key_count=keyCount;
                        kp.p1=process::self();
        
                        queue.push_back(kp);
                        kp.p1.suspend();
                end
        endtask

        

        function int try_get(int keyCount );
                        if (this.Count>=keyCount) return 1;
                        else return 0;
        endfunction
endclass

class my_semaphore_blocked extends my_semaphore;

        function new(int keyCount);
                super.new (keyCount);
        
        endfunction
        virtual task get(int keyCount );

                if (queue.size() || this.Count< keyCount) begin
                        key_count_process kp;
                        kp.req_key_count=keyCount;
                        kp.p1=process::self();
        
                        queue.push_back(kp);
                        kp.p1.suspend();
                end else this.Count -=keyCount;
        endtask
endclass 

program semaphore_example ;

 my_semaphore_blocked sm =new(0);
 initial 
        fork
                begin:Thread1
                        sm.put(2);
                        sm.get(4);
                        $display("Thread1, completed at time %d",$time);        
                end
                begin:Thread2
                        #10;
                        sm.get(2);
                        $display("Thread2, completed at time %d",$time);
                end
                begin:Thread3
                        #20;
                        sm.put(4);
                        $display("Thread3, completed at time %d",$time);
                end
        join

endprogram

実行結果です。

***** Veritak SV32 Engine Version 446 Build May 28 2013*****

Thread3, completed at time                   20
Thread1, completed at time                   20
Thread2, completed at time                   20

---------- シミュレーションを終了します。time=20ns

生産者と消費者問題

セマフォを参考にして、コーディングしてみましょう。Wekipediaの例のように二つのセマフォを使うとうまく行きますが、シミュレータをまともに走らせるには意外に難しいです。うまく走ると無限ループになります。デバッグ用に、イベントが枯渇すると、3番目のスレッドが効いて$stopするようにしています。ブロックするステートメント(この場合は、semaphoreのget)がどれかを意識して書かないとうまく走ってくれません。(イベントが枯渇して$stopしたりします。)

program pr7;
	parameter  int N=1;
	parameter int M=N;
	parameter int K=M;
	my_semaphore Spaces=new(N);
	my_semaphore Elements=new(0);
	int value=0;	
	int q[$];


task producer;

	forever begin

	
		repeat(M) begin
			Spaces.get(1);
			q.push_back(value++);
			$display("Produced %d q.size()=%d %d",value,q.size(),$time);
		end
		Elements.put(M);

	
	end
endtask

task consumer;
	forever begin
		int i;
		
		repeat (K) begin
			Elements.get(1);
			i=q.pop_front();
			$display(" Consumed %d q.size()=%d %d",i,q.size(),$time);
		end
		Spaces.put(K);

	
	end
endtask

				

	initial			
		fork
			producer();
			consumer();
			forever #100 $stop;	
		join_any
	

endprogram
***** Veritak SV32 Engine Version 446 Build May 28 2013*****

Produced           1 q.size()=          1                    0
 Consumed           0 q.size()=          0                    0
Produced           2 q.size()=          1                    0
 Consumed           1 q.size()=          0                    0
....

生産者と消費者のシミュレーション問題

上の例では、生産者と消費者が1対1の場合でした。それでは、消費者が複数いた場合は、どうなるのでしょうか?消費者が4人いたとしてシミュレーションしてみます。

起動スレッドが分かりやすいようにナンバーを渡しています。

program pr7;
        parameter  int N=8;
        parameter int M=1;
        parameter int Consumer=4;
        my_semaphore Spaces;
        my_semaphore Elements;
        int value=0;    
        int q[$];

        

task producer(int n);

        forever begin

                Spaces.get(M);
                repeat(M) begin
        
                        q.push_back(value++);
                
                        $display("Produced(%1d) %d q.size()=%d %d",n,value,q.size(),$time);
                        
                end
                Elements.put(M);
        
        
        end
endtask


task automatic consumer(int n);

        forever begin
                
                
                process p1;
                int i;

                p1=process::self();
                
                        
                Elements.get(1);
                i=q.pop_front();
                
                $display("      Consumed(%1d)  %d q.size()=%d %d",n,i,q.size(),$time);
                Spaces.put(1);
                

        

        end
endtask



        initial begin
                Elements=new(0);
                Spaces=new (N);
                                        

                                
                fork
                        producer(1);

                        consumer(1);
                        consumer(2);
                        consumer(3);
                        consumer(4);
                                
                                
                        
                        forever #100 $stop;     
                join_any
        end

endprogram


これを走らせると、スレッド4に偏って生成されていることが分かります。この結果は、シミュレータ依存ではありますが、他のシミュレータでも似たような結果となります。シミュレータは、イベント生成の順にイベントを処理しているだけで、偏りのないスレッド生成に関しては関知しないことに注意してください。 

***** Veritak SV32 Engine Version 446 Build May 30 2013*****

Produced(1)           1 q.size()=          1                    0
Produced(1)           2 q.size()=          2                    0
Produced(1)           3 q.size()=          3                    0
Produced(1)           4 q.size()=          4                    0
Produced(1)           5 q.size()=          5                    0
Produced(1)           6 q.size()=          6                    0
Produced(1)           7 q.size()=          7                    0
Produced(1)           8 q.size()=          8                    0
        Consumed(4)            0 q.size()=          7                    0
        Consumed(4)            1 q.size()=          6                    0
        Consumed(4)            2 q.size()=          5                    0
        Consumed(4)            3 q.size()=          4                    0
        Consumed(4)            4 q.size()=          3                    0
        Consumed(3)            5 q.size()=          2                    0
        Consumed(2)            6 q.size()=          1                    0
        Consumed(1)            7 q.size()=          0                    0
Produced(1)           9 q.size()=          1                    0
Produced(1)          10 q.size()=          2                    0
Produced(1)          11 q.size()=          3                    0
Produced(1)          12 q.size()=          4                    0
Produced(1)          13 q.size()=          5                    0
Produced(1)          14 q.size()=          6                    0
Produced(1)          15 q.size()=          7                    0
Produced(1)          16 q.size()=          8                    0
        Consumed(4)            8 q.size()=          7                    0
        Consumed(4)            9 q.size()=          6                    0
        Consumed(4)           10 q.size()=          5                    0
        Consumed(4)           11 q.size()=          4                    0
        Consumed(4)           12 q.size()=          3                    0
        Consumed(3)           13 q.size()=          2                    0
        Consumed(2)           14 q.size()=          1                    0
        Consumed(1)           15 q.size()=          0                    0
....

それでは、偏りのないように、テストベンチを書くには、どうしたらよいでしょうか?

生産者と消費者のシミュレーションの改善

なるべくならスレッド生成管理を自分で書くのがよいでしょう。イベントによるハンドシェークや、#0を使うのは、スパゲテッィプログラムになり易いので、こちらのテクニックの方が分かり易い場面があるでしょう。

下は、Consumerのスレッド起動をFIFOで管理しています。具体的には、task select_resume_threadで、起動するスレッドを選択しています。この例では、単純にFIFOで管理しています。前のソースから追加部は、この部分だけです。ランダムに起動スレッドを選択したり、分布重みで起動するスレッドを選択する追加をしても、この部分だけでよいでしょう。このように、スレッド生成に関し、resume/suspendを使って見通しがよくなる場合があります。

Windowsでは、Fiber、一般のプログラミング言語的には、コルーチンと呼ばれたりしますが、VerilogHDL自体、コルーチンの塊みたいなものなのでNative機能とも言えます。しかし、Veraでは、搭載されていませんでした。

program pr7;
        parameter  int N=6;
        parameter int M=1;
        parameter int Consumer=4;
        my_semaphore Spaces;
        my_semaphore Elements;
        int value=0;    
        int q[$];
        process pq[$];//スレッド起動管理を追加
        
        

task producer(int n);

        forever begin

                Spaces.get(M);
                repeat(M) begin
        
                        q.push_back(value++);
                
                        $display("Produced(%1d) %d q.size()=%d %d",n,value,q.size(),$time);
                        
                end
                Elements.put(M);
        
        
        end
endtask

task automatic  select_resume_thread(process p);//Conumer起動スレッド管理
//SUSPENDしたものからresumeして行く

                if (Consumer==1) return;//Nothing to do.

                pq.push_back(p);
//休止するスレッドをセーブ。pは、select_resume_threadのスレッドで、直接のConsumerスレッドではない。しかし復帰すれば、問題なくConsumerに継続する。
                if (pq.size()>=Consumer) begin//シンプルFIFO
                        process p1;
                        p1=pq.pop_front();
                        p1.resume();
//suspendしたスレッドを復帰させる、一個のスレッドだけを生かせば、実行順は、常にユーザ制御可能。一個もないとイベントは枯渇する。
                end 
                p.suspend();
//休止状態に入る。ブロックし他のスレッドへ!
endtask
task automatic consumer(int n);

        forever begin
                
                
                process p1;
                int i;

                p1=process::self();
                
                        
                Elements.get(1);
                i=q.pop_front();
                
                $display("       Consumed(%1d)  %d q.size()=%d %d",n,i,q.size(),$time);
                Spaces.put(1);
                
                select_resume_thread(p1);//Conumer起動スレッド管理を追加
        

        end
endtask



        initial begin
                Elements=new(0);
                Spaces=new (N);
                                        

                                
                fork
                        producer(1);

                        consumer(1);
                        consumer(2);
                        consumer(3);
                        consumer(4);
                                
                                
                        
                        forever #100 $stop;     
                join_any
        end

endprogram

下が実行結果ですが、過渡期のあと、偏りなくConsumer スレッドが順に処理されている様子が分かります。

***** Veritak SV32 Engine Version 446 Build May 30 2013*****

Produced(1)           1 q.size()=          1                    0
Produced(1)           2 q.size()=          2                    0
Produced(1)           3 q.size()=          3                    0
Produced(1)           4 q.size()=          4                    0
Produced(1)           5 q.size()=          5                    0
Produced(1)           6 q.size()=          6                    0
         Consumed(4)            0 q.size()=          5                    0
         Consumed(3)            1 q.size()=          4                    0
         Consumed(2)            2 q.size()=          3                    0
         Consumed(1)            3 q.size()=          2                    0
Produced(1)           7 q.size()=          3                    0
Produced(1)           8 q.size()=          4                    0
Produced(1)           9 q.size()=          5                    0
Produced(1)          10 q.size()=          6                    0
         Consumed(4)            4 q.size()=          5                    0
Produced(1)          11 q.size()=          6                    0
         Consumed(3)            5 q.size()=          5                    0
Produced(1)          12 q.size()=          6                    0
         Consumed(2)            6 q.size()=          5                    0
Produced(1)          13 q.size()=          6                    0
         Consumed(1)            7 q.size()=          5                    0
Produced(1)          14 q.size()=          6                    0
         Consumed(4)            8 q.size()=          5                    0
Produced(1)          15 q.size()=          6                    0
         Consumed(3)            9 q.size()=          5                    0
Produced(1)          16 q.size()=          6                    0
         Consumed(2)           10 q.size()=          5                    0
Produced(1)          17 q.size()=          6                    0
         Consumed(1)           11 q.size()=          5                    0
Produced(1)          18 q.size()=          6                    0
         Consumed(4)           12 q.size()=          5                    0
Produced(1)          19 q.size()=          6                    0
         Consumed(3)           13 q.size()=          5                    0
Produced(1)          20 q.size()=          6                    0
...

食事する哲学者のシミュレーション

ダイクストラが提起した汎用的な問題ですが、SystemVerilogでシミュレーションするとどうなるのでしょうか?こちらを参考にしてCodingしてみましょう。


うまくいかない例のうまくいかないシミュレーション

各フォークをセマフォで管理した場合のシミュレーションです。

program process23b;
        my_semaphore sems[5];
        int map[int];
        

        task automatic t1(int num);

                forever begin
                        sems[num].get(1);
                        sems[ (num+1)%5].get(1);
                        $display("Eating %d %d",num,$time);
                        map[num] +=1;
                        sems[(num+1)%5].put(1);
                        sems[num].put(1);
                end

        endtask


        initial begin
                foreach (sems[i]) sems[i]=new(1);
                fork
                                t1(0);
                                t1(1);
                                t1(2);
                                t1(3);
                                t1(4);
                                #100000;
                join_any
                $display("%p",map);
        end



endprogram

結果は次のように哲学者0ばかりの無限ループになり、他の哲学者(スレッド)にいきません。これは、アルゴリズムの問題ではなく、シミュレーションの仕方の問題です。 foreverループ内にブロックする命令がないのが原因です。semaphoreのgetは、取得できない場合、ブロックされますが、後行で返しているために常に取得可能でブロックされる機会がありません。
***** Veritak SV32 Engine Version 446 Build May 31 2013*****

Eating           0                    0
C:\Users\Public\sv_test\process\pr23b.sv(65)::Warning: 連想配列のインデックスが存在しません。default値を返します。
Eating           0                    0
Eating           0                    0
Eating           0                    0
Eating           0                    0
Eating           0                    0
Eating           0                    0
Eating           0                    0
...
そこで、次のようにブロックするステートメントを置いて他のスレッドに行くようにします。
task automatic t1(int num);

		forever begin
			sems[num].get(1);
			#10;//ブロッキングステートメント
			$display("Got Left %d",num);
			sems[ (num+1)%5].get(1);
			$display("Got Right %d",num);
			#10;//ブロッキングステートメント
			$display("Eating %d %d",num,$time);
			map[num] +=1;
			sems[(num+1)%5].put(1);
			sems[num].put(1);
		end

	endtask
これの結果は、次のように途中で止まってしまいます。うまくいかないアルゴリズムの問題、デッドロックがうまく再現できました。
***** Veritak SV32 Engine Version 446 Build May 31 2013*****

Got Left           0
Got Left           1
Got Left           2
Got Left           3
Got Left           4
'{}

---------- シミュレーションを終了します。time=100000ns

食事する哲学者のシミュレーション 解1

"1つのプロセスだけが逆順でフォークを要求"のシミュレーションです。

program process23c;
        my_semaphore sems[5];
        int map[int];
        

        task automatic t1(int num);

                forever begin
                        sems[num].get(1);
                        #10;//ブロッキングステートメント
                
                        sems[ (num+1)%5].get(1);
        
                        #10;//ブロッキングステートメント
                        $display("Eating %d %d",num,$time);
                        map[num] +=1;
                        sems[(num+1)%5].put(1);
                        sems[num].put(1);
                end

        endtask

        task automatic t2(int num);

                forever begin
                        sems[ (num+1)%5].get(1);
                
                        #10;//ブロッキングステートメント
        
                        sems[num].get(1);
        
                        #10;//ブロッキングステートメント
                        $display("Eating %d %d",num,$time);
                        map[num] +=1;
                
                        sems[num].put(1);
                        sems[(num+1)%5].put(1);
                end

        endtask



        initial begin
                foreach (sems[i]) sems[i]=new(1);
                fork
                                t1(0);
                                t1(1);
                                t1(2);
                                t1(3);
                                t2(4);//哲学者4だけ逆順に取る
                                #100000;
                join_any
                $display("%p",map);
        end



endprogram

こちらは、確かにうまく周っています。一つの時刻に最大2哲学者が食事しています。下の最後の行は、度数を現していますが、哲学者3だけが、倍の食事している以外は、平等に食事しています。哲学者3だけが多いのは、予想されたことではありますが、汎用的な結果なのかどうかは筆者の力では分かりません。(他のシミュレータで同じ結果になるのは確認しました。)
.

...
Eating           0                99850
Eating           3                99870
Eating           4                99880
Eating           2                99880
Eating           1                99890
Eating           3                99900
Eating           0                99900
Eating           3                99920
Eating           4                99930
Eating           2                99930
Eating           1                99940
Eating           3                99950
Eating           0                99950
Eating           3                99970
Eating           4                99980
Eating           2                99980
Eating           1                99990
'{0:1999,1:2000,2:2000,3:3999,4:1999}

**** Test Done. Total 187.00[msec] ****

食事する哲学者のシミュレーション 解2

こちらを参考にしています。

program process23a;
        parameter int N=5;
        parameter int THINKING= 0,
                                 HUNGRY=1,
                                 EATING= 2;
        `define LEFT (i+N-1)%N /* i の左側*/
        `define RIGHT (i+i)%N /* i の右側*/

        my_semaphore sems[N];
        int state[N];
        int map[int];



        task automatic  test(int i);

                if (  state[i] == HUNGRY &&
                        state[`LEFT] != EATING &&
                        state[`RIGHT] != EATING) 
                begin
                                state[i] = EATING;
                                sems[i].put(1);
                end
        endtask

        task take_forks(int i);
                
                state[i] = HUNGRY;
                test(i);
                sems[i].get(1);
        endtask

        task automatic put_forks(int i);
                state[i] = THINKING; /* 食事終了*/
                test(`LEFT); /* 左側は食事可能? */
                test(`RIGHT); /* 右側は食事可能? */
        endtask
        
        task think;
                #10;
        endtask

        task automatic eat(int i);
                map[i] +=1;
                $display("Eating %d %d",i,$time);
                #10;
        endtask


        task automatic t1(int i);

                forever begin
                        think();
                        take_forks(i); /* 2本のフォークを取るか、封鎖*/
                        eat(i);
                        put_forks(i); /* 2本のフォークを置く*/
                end

        endtask



        

        initial begin
                foreach (sems[i]) sems[i]=new(0);
                fork
                                
                                for (int i=0;i< N;i++) begin 
                                        fork
                                                automatic int k=i;
                                                begin
                                                        t1(k);
                                                end
                                        join_none
                                end
                                #10000;
                join
                $display("%p",map);
                
        end



endprogram
....
Eating           0                 9940
Eating           2                 9940
Eating           4                 9950
Eating           1                 9950
Eating           0                 9960
Eating           2                 9960
Eating           4                 9970
Eating           1                 9970
Eating           0                 9980
Eating           2                 9980
Eating           4                 9990
Eating           1                 9990
'{0:499,1:500,2:499,3:1,4:500}
Info: $finishコマンドを実行します。time=10000ns

**** Test Done. Total 31.00[msec] ****

一つの時刻にきっちり2哲学者が食事をしています。しかし、哲学者3は、一回しかできていません。他のシミュレータでも結果は同じになります。最初の選択が後の結果の原因となっているので、当然と言えば当然かもしれませんが、この結果は予想できませんでした。