1. 程式人生 > >【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗十六:IIC儲存模組

【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗十六:IIC儲存模組

IIC儲存器是筆者用來練習精密控時的經典例子。《整合篇》之際,IIC儲存器的解釋,筆者也自認變態。如今筆者回頭望去,筆者也不知道自己當初到底發什麼神經,既然將IIC的時序都解釋一番。由於開發上板也嵌著IIC儲存器(24LC04),筆者還得循例地介紹一下。

IIC儲存器是應用IIC匯流排的儲存器,時序本身並不是很複雜不過缺有一大堆時序引數,而且官方提供的時序也不利於描述,所以許多時序都必須自行繪製,真是麻煩死人。麻煩歸麻煩,筆者終究還要吃飯,為了肚子,再麻煩的事情也要硬著頭皮捱過去 ... 這也是白駱駝的惡作劇!

clip_image002

圖16.1 IIC匯流排與IIC裝置。

圖16.1是IIC匯流排與IIC裝置常見的示意圖。理想上,一條IIC匯流排允許千萬IIC裝置佔據在上 ... 物理下,一條IIC總監究竟允許多少IIC裝置佔據其中必須根據裝置地址的長度。預設下,裝置地址為八位寬,因此裝置地址也稱為裝置位元組。裝置地址的高四位,即[7..4]記錄硬體ID,接續三位即 [3..1] 則記錄硬體地址,最後一位則是裝置的訪問方向。結果如表16.1所示:

表16.1 裝置地址的位分配。

[7]

[6]

[5]

[4]

[3]

[2]

[1]

[0]

硬體ID

硬體地址

訪問方向

所謂硬體ID就是IIC裝置的辨識ID,硬體ID會隨著廠商還有裝置的種類而有所改變。開發板上的IIC裝置是某廠商的IIC儲存器,即24LC04,硬體ID為 4’b1010。至於硬體地址就是IIC裝置在總線上辨識地址,預設下為3位,即同類的IIC裝置在同一條IIC總線上僅允許佔據8個而已。然而,開發板上的 24LC04 為3’b000。最後的訪問方向位則是主機用來通知從機,此刻的訪問目的是讀還是寫。

總結來說,裝置地址除了訪問方向以外,前七位一般都是固定的,例如開發板的IIC儲存器24LC04,裝置地址就是 8’b1010_000_×。

clip_image004

圖16.2 24LC04的寫操作(主機視角)。

IIC匯流排的時序,感覺上一組完成的操作宛如是一堆拼圖。如圖16.2所示,那是24LC04的寫操作,時序先填上為起始位,再來是裝置地址,餘下是應答位,隨之是資料地址,然後又是應答位,接著是寫如資料,再一次應答位,最後掛上結束位以示一次性的寫操作已經完成。那麼,寫操作的經過如下所示:

(一)主機發送起始位;

(二)主機發送裝置地址(寫);

(三)等待從機應答;

(四)主機發送資料地址;

(五)等待從機應答;

(六)主機發送資料;

(七)等待從機應答;

(八)主機發送結束位。

讀者稍微注意一下裝置地址的最低位,筆者稍微用藍色將其高亮。由於此刻是寫操作,所以裝置地址的訪問方向是“寫”,所以訪問方向位設定為0。

clip_image006

圖16.3 24LC04的讀操作(主機視角)。

圖16.3是24LC04的讀時序,同樣它也是由一堆“拼圖”組合而成。相較寫操作,讀操作不僅多了許多“拼圖”,而且途中也改變訪問方向。那麼,讀操作的經過如下所示:

(一)主機發送起始位;

(二)主機發送裝置地址(寫);

(三)等待從機應答;

(四)主機發送資料地址;

(五)主機發送起始位;

(六)主機發送裝置地址(讀);

(七)等待從機應答;

(八)主機讀取資料;

(九)從機沒有應答(主機無視應答);

(十)主機發送結束位。

未進入正題之前,請允許筆者加入一些小插曲。IIC匯流排是一種低速的匯流排,不過IIC匯流排有 100Khz 還有 400Khz 兩種速率提供我們選擇,要麼100Khz,要麼400Khz,要麼兩者兼施,不管哪一種《整合篇》都曾實驗過。在此,實驗十六會以400Khz的速率作為標準。

筆者曾在前面說過,IIC匯流排之所以麻煩,因為IIC匯流排有大小不同的時序引數(時間引數)。一般而言,時間引數都都被順序語言一笑而過,那是因為順序語言無法實現精密控時。雖然描述語言也可以一笑而過,但是語言的本質卻不允我們這麼作,如果我們選擇無視時序引數 ... 那麼,打從一開始我們還是不學為好。

此外,描述IIC的匯流排時序有各種各樣的方法,但是筆者會選擇表達能力更高,控制能力更細的描述手段。我們知道IIC的匯流排時序是由一塊又一塊的拼圖拼湊而成,當我們在建模的時候,我們會針對各個拼圖作出區域性性的描述。期間,我們也必須考慮各種時序引數,如表16.2所示:

表16.2 各種時序引數(50Mhz量化)。

相關引數

標示

最小時間

最小時鐘

最大時間

最大時鐘

Clock Frequency

FCLK

---

---

400Khz

125

Clock High Time

THIGH

600ns

30

---

---

Clock Low Time

TLOW

1300ns

65

---

---

Rise Time

TR

---

---

300ns

15

Fall Time

TF

---

---

300ns

15

Start Hold Time

THD_STA

600ns

30

---

---

Start Setup Time

TSU_STA

600ns

30

---

---

Data Input Hold Time

THD_DAT

0ns

0

---

---

Data Input Setup Time

TSU_DAT

100ns

5

---

---

Stop Setup Time

TSU_STO

600ns

30

---

---

Output Valid From Clock

TAA

---

---

900ns

45

Bus Free Time

TBUF

1300ns

65

---

---

相比許多同學遇見表16.2便會立即憋著蛋蛋,因為它會嚇壞一群小朋友。話雖如此,表16.2只有外表可怕的紙老虎而已,任何有時序基礎的同學,隨便擦擦兩下就搞定。筆者雖然也想一笑打過,不過筆者還要循例介紹一下:

l Clock Frequency,既是頻率也是速率,在此是400Khz。

l Clock High Time,既SCL訊號保持高電平所需的最小時間。

l Clock Low Time,既SCL訊號保持低電平所需的最小時間。

l Rise Time,既訊號由底變高所需最大的時間。

l Fall Time,既訊號又高變低所需最小的時間。

l Start Hold Time,既起始位所需最小的保持時間。

l Start Setup Time,既起始位所需最小的建立時間。

l Data Input Hold Time,既資料位所需最小的保持時間。

l Data Input Setup Time,既資料位所需最小的建立時間。

l Stop Setup Time,既結束位所需的最小保持時間。

l Ouput Valid From Clock,既資料位經時鐘沿觸發以後的有效時間。

l Bus Free Time,既釋放匯流排的最小時間。

IIC匯流排是一種序列傳輸協議,既有時鐘訊號SCL,還有資料訊號SDA。Clock Frequency 表示SCL訊號的頻率,Clock High Time 表示 SCL訊號保持高電平所需的最小時間,Clock Low Time則表示 SCL訊號保持低電平所需的最小的時間。

至於 Rise Time 與 Fall Time 表示,SCL訊號還有 SDA訊號由高變低或者由低變高時所需的最小時間,即上山與下山時間。Hold Time 與 Setup Time 是用來評估資料是否成功打入暫存器的時序引數,算是典型中的典型。Setup Time 表示建立時間,即資料寫入暫存器之前所需的穩定時間;反之,Hold Time則是保持時間,即資料打入暫存器之後所需的穩定時間。只要兩者得到滿足,那麼資料的寄存活動就得到確保。

Start是IIC匯流排的起始位,Stop是IIC匯流排的結束位,Data 是IIC匯流排的資料位,為了確保三者成功寫入從機,Setup Time 與 Hold Time 必須得到滿足。Ouput Valid From Clock是關係資料位的時序引數,還有 Bus Free Time 是關係結束位的時序引數,在此先丟胃口一下。此外,為了簡化時序,筆者將各種引數的實際時間轉換為50Mhz量化以後的結果。對此,Verilog 可以這樣表示,結果如程式碼16.1所示:

1. parameter FCLK = 10'd125, FHALF = 10'd62, FQUARTER = 10'd31;

2. parameter THIGH = 10'd30, TLOW = 10'd65, TR = 10'd15, TF = 10'd15;

3. parameter THD_STA = 10'd30, TSU_STA = 10'd30, TSU_STO = 10'd30;

程式碼16.1

如程式碼16.1所示,FCLK表示400Khz的週期,FHALF表示1/2週期,FQUARTER表示1/4週期。至於為什麼程式碼16.1不見,Data Input Hold Time 與 Bus Free Time 的時序引數,請讀者暫時忍耐,往後會解釋。

(話題繼續之前,請讀者確保自己對“整合時序”有一定的理解,不然的話 ... 接下來的內容,讀者一定會看到淚流滿面。)

clip_image008

圖 16.4 起始位。

首先讓我們先瞧瞧起始位這枚拼圖。如圖16.4所示,左圖是起始位的理想時序,右圖是起始位的物理時序。IIC匯流排的起始位也就類似串列埠或者PS/2等傳輸協議的起始位,然而不同的是,IIC匯流排的起始位是 SCL 拉高 TR + TSU_STA + THD_STA + TF 之久,換之 SDA 則是拉高 TR + THIGH 然後拉低 TF + TLOW。起始位總和所用掉的時間,恰恰好有一個速率的週期。對此,Verilog則可以這樣描述,結果如程式碼16.2所示:

1. begin

2. isQ = 1;

3. rSCL <= 1'b1;

4. if( C1 == 0 ) rSDA <= 1'b1;

5. else if( C1 == (TR + THIGH) ) rSDA <= 1'b0;

6. if( C1 == (FCLK) -1) begin C1 <= 10'd0; i <= i + 1'b1; end

7. else C1 <= C1 + 1'b1;

8. end

程式碼16.2

如程式碼16.2所示,第2行的isQ = 1 表示設定 SDA 為輸出狀態(即時結果),第3行則表示 SCL一直持續拉高狀態,第4~5行表示C1為0的時候SDA拉高,直到C1為TR+THIGH才拉低SDA。第6~7行表示一個步驟所逗留的時間。

clip_image010

圖16.5 結束位。

圖16.5是結束位的時序圖,IIC裝置的操作好壞一般都取決結束位。保險起見,SCL與SDA都事先拉低1/4週期,緊接著 SCL會拉高 TR+TSU_STO(或者1/2週期),最後又保持高電平1/2週期。反之,SDA會拉低1/2週期,隨之拉高 TR+THIGH(或者1/2週期)。對此,Verilog可以這樣表示,結果如程式碼16.3所示:

1. begin

2. isQ = 1'b1;

3. if( C1 == 0 ) rSCL <= 1'b0;

4. else if( C1 == FQUARTER ) rSCL <= 1'b1;

5. if( C1 == 0 ) rSDA <= 1'b0;

6. else if( C1 == (FQUARTER + TR + TSU_STO) ) rSDA <= 1'b1;

7. if( C1 == ( FQUARTER + FCLK ) -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

8. else C1 <= C1 + 1'b1;

9. end

程式碼16.3

如程式碼16.3所示,第2行表示 SDA為輸出狀態(即時),第3~4行表示C1為0拉高SCL,C1為1/4週期就拉高。第5~6行表示,C1為0拉低SDA,C1為 1/4週期 + TR + TSU_STO就拉高 SDA。第7~8行表示該步驟所逗留的時間。

clip_image012

圖16.6 釋放匯流排。

此外,結束位還有 Bus Free Tme 這個時序引數,IIC匯流排在閒置的狀態下 SCL 與 SDA 等訊號都持續高電平。主機發送結束位以示結束操作,然而主機持續拉高SCL訊號與SDA訊號 TBUF以示匯流排釋放。TBUF的有效時間從SCL訊號與SDA訊號拉高那一刻開始算起

根據表16.2所示,TBUF是65個時鐘,結果如圖16.6所示,SDA訊號拉高之後,SCL與SDA訊號只要持續保持 1/2週期(即62個時),基本上就能滿足TBUF。如果筆者是一位緊密控時狂人,可能無法接受這樣的結果,因為滿足 TBUF 少了3個時鐘,為此程式碼16.3需要更動一下:

7. if( C1 == ( FQUARTER + FCLK + 3) -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

8. else C1 <= C1 + 1'b1;

9. end

程式碼16.4

如程式碼16.4所示,筆者為第7行寫下 +3 表示該步驟多逗留3個時鐘,以致滿足TBUF。

clip_image014

圖16.7 資料位。

不管物件是裝置地址,資料地址,寫入資料,讀出資料,還是應答位,大夥都視為資料位。IIC匯流排類似其他傳輸協議,它有時鐘訊號也有上升沿與下降沿。如圖16.7所示,SCL訊號的下降沿導致裝置設定(更新)資料,上升沿則是鎖存(讀取)資料。期間,TF+TLOW 表示時鐘訊號的前半週期,TR+THIGH則表示後半週期。此外,為了確保資料成功打入暫存器,資料被上升沿鎖存哪一刻起,TSU_DAT 還有 THD_DAT 必須得到滿足。

clip_image016

圖16.8 資料位更新有效。

除此之外,為了確保資料有效被更新,我們也必須確保TAA得到滿足,結果如圖16.8所示。理解完畢以後,我們就可以開始學習,寫一位元組資料與讀一位元組資料,還有應答位。

clip_image018

圖16.9 寫一位元組。

IIC匯流排一般都是一個位元組一個位元組讀寫資料,如圖16.9所示,那是寫一位元組的理想時序圖,一位元組資料是從最高位開始寫起。對此,Verilog可以這樣描述,結果如程式碼16.5所示:

1. 0,1,2,3,4,5,6,7:

2. begin

3. isQ = 1'b1;

4. rSDA <= D1[7-i];

5. if( C1 == 0 ) rSCL <= 1'b0;

6. else if( C1 == (TF + TLOW) ) rSCL <= 1'b1; 

7. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

8. else C1 <= C1 + 1'b1;

9. end

程式碼16.5

如程式碼16.5所示,第1行有8個步驟,表示寫一個位元組。第3行isQ為1表示SDA為輸出狀態。第4行表示從最高位開始更新SDA的資料位。第5~6行表示,C1為0拉低SCL,C1為TF+TLOW則拉高SCL。第7~8行表示該步驟逗留一個週期的時間。

clip_image020

圖16.10 應答位。

應答位是從機給予主機的回答,0為是1為否。然而,從旁觀看,讀取應答位也是讀取一位資料位。當主機完成寫入一個位元組或者讀取一個位元組資料的時候,從機都會產生應答位。主機拉低SCL那刻,從機便會發送應答位,然後主機會藉由上升沿讀取應答位。如圖16.10所示,上升沿會產生在 TF + TLOW 之後,也是1/2週期。對此,Verilog可以這樣表示,結果如程式碼16.6所示:

程式碼16.6

如程式碼16.6所示,第2行表示SDA為輸入狀態。第4~5行表示,C1為0拉低SCL,C1為1/2週期則拉高SCL。第3行表示,C1為1/2週期的時候讀取應答位。第6~7行表示該步驟逗留1個週期的時間。

clip_image022

圖16.11 讀一位元組。

所謂讀一位元組資料就是重複讀取8次應答位。如圖16.11所示,SCL的下降沿導致從機更新資料,然後主機在SCL的上升沿讀取資料。此外,從機也會由高至低更新資料位。至於Verilog 則可以這樣表示,結果如程式碼16.7所示:

1. 0,1,2,3,4,5,6,72. begin

3. isQ = 1'b0;

4. if( C1 == FHALF ) D1[7-i] <= SDA;

5. if( C1 == 0 ) rSCL <= 1'b0;

6. else if( C1 == FHALF ) rSCL <= 1'b1; 

7. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

8. else C1 <= C1 + 1'b1;

9. end

程式碼16.7

如程式碼16.7所示,第1行表示讀取一位元組。第3行表示SDA為輸入狀態,第5~6行表示,C1為0拉低SCL,C1為1/2週期則拉高SCL。第4行表示,C1為1/2週期的時候讀取資料,而且資料位由高至低存入D1。第7~8行表示該步驟逗留一個週期的時間。

clip_image024

圖16.12 第二次起始位。

我們知道主機向從機讀取資料的時候,它必須改變裝置地址的方向,因此讀操作又第二次起始位。如圖16.12所示,感覺上第二次起始位也是第一次起始位,不過為了促使改變方向成功,第二次起始位相較第一次起始位的前後都拉低1/4週期。對此,Verilog 可以這樣表示,結果如程式碼16.8所示:

1. begin

2. isQ = 1'b1;

3. if( C1 == 0 ) rSCL <= 1'b0;

4. else if( C1 == FQUARTER ) rSCL <= 1'b1;

5. else if( C1 == (FQUARTER + TR + TSU_STA + THD_STA + TF) ) rSCL <= 1'b0;

6.

7. if( C1 == 0 ) rSDA <= 1'b0; 

8. else if( C1 == FQUARTER ) rSDA <= 1'b1;

9. else if( C1 == ( FQUARTER + TR + THIGH) ) rSDA <= 1'b0;

10.

11. if( C1 == (FQUARTER + FCLK + FQUARTER) -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

12. else C1 <= C1 + 1'b1;

13. end

程式碼16.8

如程式碼16.8所示,第2行表示SDA為輸出狀態。第3~5行表示,C1為0拉低SCL,C1為1/4週期拉高 SCL,C1為 1/4週期 + TR + TSU_STA + THD_STA + TF 便拉低SCL。第7~9行表示,C1為0拉低SDA,C1為1/4週期拉高SDA,C1為1/4週期 + TR + THIGH 便拉低SDA。第11~12行表示該步驟停留一個週期的時間。

理解完畢以後,我們便可以開始建模了。

clip_image026

圖16.13 實驗十六的建模圖。

圖16.13是實驗十六的建模圖,組合模組iic_demo 內容包含 IIC儲存模組,核心操作還有SMG基礎模組。首先核心操作會將資料純如IIC儲存模組,然後又從中讀取,完後再將讀出的資料驅動SMG基礎模組。

iic_savemod.v

clip_image028

圖16.14 IIC儲存模組的建模圖。

圖16.14是IIC儲存模組的建模圖,左邊是頂層訊號,右邊則是溝通用的問答訊號,寫入地址iAddr,寫入資料 iData,還有讀出資料oData。Call/Done有兩位,即表示該模組有讀功能還有些功能。具體內容,我們還是來看程式碼吧:

1. module iic_savemod

2. (

3. input CLOCK, RESET,

4. output SCL,

5. inout SDA,

6. input [1:0]iCall,

7. output oDone,

8. input [7:0]iAddr,

9. input [7:0]iData,

10. output [7:0]oData

11. );

以上內容為相關的出入端宣告。

12. parameter FCLK = 10'd125, FHALF = 10'd62, FQUARTER = 10'd31; //(1/400E+3)/(1/50E+6)

13. parameter THIGH = 10'd30, TLOW = 10'd65, TR = 10'd15, TF = 10'd15;

14. parameter THD_STA = 10'd30, TSU_STA = 10'd30, TSU_STO = 10'd30;

15. parameter FF_Write1 = 5'd7;

16. parameter FF_Write2 = 5'd9, RDFUNC = 5'd19;

17.

以上內容為相關的速率還有時序引數宣告。第15~16行則是相關的偽函式宣告。

18. reg [4:0]i;

19. reg [4:0]Go;

20. reg [9:0]C1;

21. reg [7:0]D1;

22. reg rSCL,rSDA;

23. reg isAck, isDone, isQ;

24.

25. always @ ( posedge CLOCK or negedge RESET )

26. if( !RESET )

27. begin

28. { i,Go } <= { 5'd0,5'd0 };

29. C1 <= 10'd0;

30. D1 <= 8'd0;

31. { rSCL,rSDA,isAck,isDone,isQ } <= 5'b11101;

32. end

以上內容為相關的暫存器宣告以及復位操作。

33. else if( iCall[1] )

34. case( i )

35.

36. 0: // Call

37. begin

38. isQ = 1;

39. rSCL <= 1'b1;

40.

41. if( C1 == 0 ) rSDA <= 1'b1; 

42. else if( C1 == (TR + THIGH) ) rSDA <= 1'b0;

43.

44. if( C1 == (FCLK) -1) begin C1 <= 10'd0; i <= i + 1'b1; end

45. else C1 <= C1 + 1'b1;

46. end

47.

以上內容為部分核心操作。第33行的 iCall[1] 為使能寫操作。步驟0用來產生起始位。

48. 1: // Write Device Addr

49. begin D1 <= {4'b1010, 3'b000, 1'b0}; i <= 5'd7; Go <= i + 1'b1; end

50.

51. 2: // Wirte Word Addr

52. begin D1 <= iAddr; i <= FF_Write1; Go <= i + 1'b1; end

53.

54. 3: // Write Data

55. begin D1 <= iData; i <= FF_Write1; Go <= i + 1'b1; end

56.

57. /*************************/

58.

以上內容為部分核心操作。步驟1用來寫入裝置地址,並且呼叫偽函式。步驟2用來寫入資料地址,並且呼叫偽函式。步驟3用來寫入資料,並且呼叫偽函式。

59. 4: // Stop

60. begin

61. isQ = 1'b1;

62.

63. if( C1 == 0 ) rSCL <= 1'b0;

64. else if( C1 == FQUARTER ) rSCL <= 1'b1; 

65.

66. if( C1 == 0 ) rSDA <= 1'b0;

67. else if( C1 == (FQUARTER + TR + TSU_STO ) ) rSDA <= 1'b1;

68.

69. if( C1 == (FQUARTER + FCLK) -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

70. else C1 <= C1 + 1'b1; 

71. end

72.

以上內容為部分核心操作。步驟4用來產生結束位。

73. 5:

74. begin isDone <= 1'b1; i <= i + 1'b1; end

75.

76. 6: 

77. begin isDone <= 1'b0; i <= 5'd0; end

78.

以上內容為部分核心操作。步驟5~6用來產生完成訊號。

79. /*******************************/ //function

80.

81. 7,8,9,10,11,12,13,14:

82. begin

83. isQ = 1'b1;

84. rSDA <= D1[14-i];

85.

86. if( C1 == 0 ) rSCL <= 1'b0;

87. else if( C1 == (TF + TLOW) ) rSCL <= 1'b1; 

88.

89. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

90. else C1 <= C1 + 1'b1;

91. end

92.

以上內容為部分核心操作。步驟7~14是寫一個位元組的偽函式。

93. 15: // waiting for acknowledge

94. begin

95. isQ = 1'b0;

96. if( C1 == FHALF ) isAck <= SDA;

97.

98. if( C1 == 0 ) rSCL <= 1'b0;

99. else if( C1 == FHALF ) rSCL <= 1'b1;

100.

101. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

102. else C1 <= C1 + 1'b1; 

103. end

104.

105. 16:

106. if( isAck != 0 ) i <= 5'd0;

107. else i <= Go; 

108.

109. /*******************************/ // end function

110.

111. endcase

112.

以上內容為部分核心操作。步驟15則用來讀取應答位,步驟16則用來判斷應答位,應答成功返回步驟,失敗則重新來過。

113. else if( iCall[0] ) 

114. case( i )

115.

116. 0: // Start

117. begin

118. isQ = 1; 

119. rSCL <= 1'b1;

120.

121. if( C1 == 0 ) rSDA <= 1'b1; 

122. else if( C1 == (TR + THIGH) ) rSDA <= 1'b0;

123.

124. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

125. else C1 <= C1 + 1'b1;

126. end

127.

以上內容為部分核心操作。第113行表示 iCall[0] 使能讀操作。步驟0用來產生起始位。

128. 1: // Write Device Addr

129. begin D1 <= {4'b1010, 3'b000, 1'b0}; i <= 5'd9; Go <= i + 1'b1; end

130.

131. 2: // Wirte Word Addr

132. begin D1 <= iAddr; i <= FF_Write2; Go <= i + 1'b1; end

133.

134. 3: // Start again

135. begin

136. isQ = 1'b1;

137.

138. if( C1 == 0 ) rSCL <= 1'b0;

139. else if( C1 == FQUARTER ) rSCL <= 1'b1;

140. else if( C1 == (FQUARTER + TR + TSU_STA + THD_STA + TF) ) rSCL <= 1'b0;

141.

142. if( C1 == 0 ) rSDA <= 1'b0; 

143. else if( C1 == FQUARTER ) rSDA <= 1'b1;

144. else if( C1 == ( FQUARTER + TR + THIGH) ) rSDA <= 1'b0;

145.

146. if( C1 == (FQUARTER + FCLK + FQUARTER) -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

147. else C1 <= C1 + 1'b1;

148. end

149.

以上內容為部分核心操作。步驟1用來寫入裝置地址,並且呼叫偽函式。步驟2用來寫入資料地址,並且呼叫偽函式。步驟3用來產生第二次起始位。

150. 4: // Write Device Addr ( Read )

151. begin D1 <= {4'b1010, 3'b000, 1'b1}; i <= 5'd9; Go <= i + 1'b1; end

152.

153. 5: // Read Data

154. begin D1 <= 8'd0; i <= RDFUNC; Go <= i + 1'b1; end

155.

156. 6: // Stop

157. begin

158. isQ = 1'b1;

159.

160. if( C1 == 0 ) rSCL <= 1'b0;

161. else if( C1 == FQUARTER ) rSCL <= 1'b1; 

162.

163. if( C1 == 0 ) rSDA <= 1'b0;

164. else if( C1 == (FQUARTER + TR + TSU_STO) ) rSDA <= 1'b1;

165.

166. if( C1 == (FCLK + FQUARTER) -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

167. else C1 <= C1 + 1'b1; 

168. end

169.

1