2008年12月31日 星期三

SystemC Modules and Processes(2)

階層架構(Hierarchy)
一個EXOR閘是由4個NAND閘(instance)組成。要達成這個目的,是在EXOR閘的建構式裏去連結實體化的NAND閘。
底下列出EXOR閘的範例程式:

#include "systemc.h"
#include "nand2.h"
SC_MODULE(exor2)
{
sc_in<bool> A, B;
sc_out<bool> F;

nand2 n1, n2, n3, n4;

sc_signal<bool> S1, S2, S3;

SC_CTOR(exor2) : n1("N1"), n2("N2"), n3("N3"), n4("N4")
{
n1.A(A);
n1.B(B);
n1.F(S1);

n2.A(A);
n2.B(S1);
n2.F(S2);

n3.A(S1);
n3.B(B);
n3.F(S3);

n4.A(S2);
n4.B(S3);
n4.F(F);
}
};

程式說明:
這個程式看起來跟NAND閘的程式很像,但注意它有去include nand2.h的檔案,這是為了去使用NAND閘模組所必需的。

這個exor2模組被建立起來,並宣告這個模組的ports。請再注意一下,設計上是可以重複使用A,B及F的名字,因為它跟nand的層級是不同的。

在電路圖中你可以看到有許多的線用以連結每個NAND閘。而在SystemC裏線的表現方式則是用sc_signal S1,S2及S3。
sc_signal是一個類別(class),它包含了樣版參數(template parameter)用以定義資料的型態,在這個例子中是使用bool的資料型態。
sc_signal是一個SystemC內建的通道(primitive channel)類別,內建的通道當然在SystemC class library裏。它的行為就如同VHDL裏的signal。

EXOR閘的建構式會比NAND閘再複雜一點,它必需實體化4個nand2來用。在port的定義之後,緊接著宣告實體化4個nand2:n1,n2,n3,n4。
每個實體化的nand2都必需要有個名字,所以我們在exor2建構式初使化時傳了4個"N1","N2","N3","N4"的名字,分別給n1,n2,n3,n4這4個nand2。

最後,將所有的ports所需要的連接線接起來。這樣子就完成了這個建構式了。

測試程式(Test bench)
要測試這個設計,當然就需要測試訊號的產生程式。這就必需要再寫另一個module,跟前面寫的module很類似。
但比較不同的是,在這裏我們是使用執行緒(thread)SC_THREAD,這個行程會在需要時進入休眠狀態。
底下列出這個Test bench的範例程式stim.h


#include "systemc.h"
SC_MODULE(stim)
{
sc_out<bool> A, B;
sc_in<bool> Clk;

void StimGen()
{
A.write(false);
B.write(false);
wait();
A.write(false);
B.write(true);
wait();
A.write(true);
B.write(false);
wait();
A.write(true);
B.write(true);
wait();
sc_stop();
}
SC_CTOR(stim)
{
SC_THREAD(StimGen);
sensitive << Clk.pos();
}
};

程式說明:
這裏要注意的是最後它們呼叫sc_stop(),這個函式會讓整個模擬結束。

有了訊號產生程式後,我們還需要一個監控程式,用來監控我們想看到的訊號改變情況,這個程式相當簡單,我們先忽略這個部份,在這個範例裏監控程式檔名是mon.h

底下列出top level的程式碼,它是寫在main.cpp裏,當然main.cpp必需include之前所寫的那些子模組(submodules)

#include "systemc.h"
#include "stim.h"
#include "exor2.h"
#include "mon.h"

int sc_main(int argc, char* argv[])
{
sc_signal<bool> ASig, BSig, FSig;
sc_clock TestClk("TestClock", 10, SC_NS,0.5);

stim Stim1("Stimulus");
Stim1.A(ASig);
Stim1.B(BSig);
Stim1.Clk(TestClk);

exor2 DUT("exor2");
DUT.A(ASig);
DUT.B(BSig);
DUT.F(FSig);

mon Monitor1("Monitor");
Monitor1.A(ASig);
Monitor1.B(BSig);
Monitor1.F(FSig);
Monitor1.Clk(TestClk);

sc_start(); // run forever

return 0;

}

程式說明:
在top-level裏 signal被宣告成線(wire)來使用,時脈的產生則是用sc_clock;然後把所需要的模組都實體化必加以連結起來。

都做完後,必需呼叫sc_start()來啟動模擬,模擬會一直持續執行,一直到有人呼叫了sc_stop()後結束模擬。

底下列出輸出結果的範例:

       Time A B F
0 s 0 0 1
10 ns 0 0 0
20 ns 0 1 1
30 ns 1 0 1
40 ns 1 1 0

如果你有注意看輸出結果,你應該會發生很詭異的事情,在第一行time 0s時,當時的A,B輸入為0,而F的輸出結為為1,這不應該是EXOR閘的正常輸出結果。
但在10ns之後,所有的結果都是對的了。在time 0s時到底發生了什麼事情了呢?

模擬(Simulation)
在SystemC library裏包含了一個模擬核心(simulation kernel)。它決定了那個行程(software threads)要被執行。
在time 0s時,所有的SC_METHOD及SC_THREAD將會隨機的被叫起來執行,一直到它們全都進入休眠(suppend)結束一個cycle。
SC_CTHREAD則是會在時脈一發生時就被執行。

上面的情況是由於組合電路的情況造成的

  1. sc_clock在時間time 0s會發生的上升緣(rising edge ),因此所有的monitor及stimulus行程在此時都會執行(在這種情況下,並不知道誰會先被執行)

  2. 在C++裏的變數並不見得都會被付與初使值(除非它們被宣告為靜態變數static),因此才會造成F在time 0s時會出現1的情況

  3. do_nand2 SC_METHOD在time 0s執行,而排程器也會去對F做更新,但是F是一個signal,它的內容並不能馬上被更新,因此當monitor在處理時,F的內容還會為1
在這個範例裏,改善的方式,可以去修改sc_clock讓它產生在第1個clock產生後延遲,如下:

sc_clock TestClk("TestClock", 10, SC_NS,0.5, 1, SC_NS);

加入的1, SC_NS兩個參數,定義了在第一個時脈發生後延遲1 ns,這會讓F的內容得以更新。
底下列出修改過後的執行結果:

       Time A B F
1 ns 0 0 0
11 ns 0 0 0
21 ns 0 1 1
31 ns 1 0 1
41 ns 1 1 0

現在你可以看到F的輸出結果都是正確的了。

結論
結束這篇快速瀏覽modules及process,你已經看到了SystemC模擬核心及內建的sc_signal通道的重要性。
你也看到了一些基本實體化低階模組的範例,它包含了一個top-level模組,以及sc_main是如何被使用的。

資料來源:
http://www.doulos.com/knowhow/systemc/tutorial/modules_and_processes/

沒有留言: