ビルトインパッケージ (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
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
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は、一回しかできていません。他のシミュレータでも結果は同じになります。最初の選択が後の結果の原因となっているので、当然と言えば当然かもしれませんが、この結果は予想できませんでした。