IIC儲存器是筆者用來練習精密控時的經典例子。《整合篇》之際,IIC儲存器的解釋,筆者也自認變態。如今筆者回頭望去,筆者也不知道自己當初到底發什么神經,既然將IIC的時序都解釋一番。由於開發上板也嵌着IIC儲存器(24LC04),筆者還得循例地介紹一下。
IIC儲存器是應用IIC總線的儲存器,時序本身並不是很復雜不過缺有一大堆時序參數,而且官方提供的時序也不利於描述,所以許多時序都必須自行繪制,真是麻煩死人。麻煩歸麻煩,筆者終究還要吃飯,為了肚子,再麻煩的事情也要硬着頭皮捱過去 ... 這也是白駱駝的惡作劇!
圖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_×。
圖16.2 24LC04的寫操作(主機視角)。
IIC總線的時序,感覺上一組完成的操作宛如是一堆拼圖。如圖16.2所示,那是24LC04的寫操作,時序先填上為起始位,再來是設備地址,余下是應答位,隨之是數據地址,然后又是應答位,接着是寫如數據,再一次應答位,最后掛上結束位以示一次性的寫操作已經完成。那么,寫操作的經過如下所示:
(一)主機發送起始位;
(二)主機發送設備地址(寫);
(三)等待從機應答;
(四)主機發送數據地址;
(五)等待從機應答;
(六)主機發送數據;
(七)等待從機應答;
(八)主機發送結束位。
讀者稍微注意一下設備地址的最低位,筆者稍微用藍色將其高亮。由於此刻是寫操作,所以設備地址的訪問方向是“寫”,所以訪問方向位設置為0。
圖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 的時序參數,請讀者暫時忍耐,往后會解釋。
(話題繼續之前,請讀者確保自己對“整合時序”有一定的理解,不然的話 ... 接下來的內容,讀者一定會看到淚流滿面。)
圖 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行表示一個步驟所逗留的時間。
圖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行表示該步驟所逗留的時間。
圖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。
圖16.7 數據位。
不管對象是設備地址,數據地址,寫入數據,讀出數據,還是應答位,大伙都視為數據位。IIC總線類似其他傳輸協議,它有時鍾信號也有上升沿與下降沿。如圖16.7所示,SCL信號的下降沿導致設備設置(更新)數據,上升沿則是鎖存(讀取)數據。期間,TF+TLOW 表示時鍾信號的前半周期,TR+THIGH則表示后半周期。此外,為了確保數據成功打入寄存器,數據被上升沿鎖存哪一刻起,TSU_DAT 還有 THD_DAT 必須得到滿足。
圖16.8 數據位更新有效。
除此之外,為了確保數據有效被更新,我們也必須確保TAA得到滿足,結果如圖16.8所示。理解完畢以后,我們就可以開始學習,寫一字節數據與讀一字節數據,還有應答位。
圖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行表示該步驟逗留一個周期的時間。
圖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個周期的時間。
圖16.11 讀一字節。
所謂讀一字節數據就是重復讀取8次應答位。如圖16.11所示,SCL的下降沿導致從機更新數據,然后主機在SCL的上升沿讀取數據。此外,從機也會由高至低更新數據位。至於Verilog 則可以這樣表示,結果如代碼16.7所示:
1. 0,1,2,3,4,5,6,7: 2. 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行表示該步驟逗留一個周期的時間。
圖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行表示該步驟停留一個周期的時間。
理解完畢以后,我們便可以開始建模了。
圖16.13 實驗十六的建模圖。
圖16.13是實驗十六的建模圖,組合模塊iic_demo 內容包含 IIC儲存模塊,核心操作還有SMG基礎模塊。首先核心操作會將數據純如IIC儲存模塊,然后又從中讀取,完后再將讀出的數據驅動SMG基礎模塊。
iic_savemod.v
圖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. 170. 7: 171. begin isDone <= 1'b1; i <= i + 1'b1; end 172. 173. 8: 174. begin isDone <= 1'b0; i <= 5'd0; end 175. 以上內容為部分核心操作。步驟4用來寫入設備地址(讀),並且調用偽函數。步驟5用來讀取一個字節,並且調用偽函數。步驟6用來產生結束位。步驟7~8則用來產生完成信號。 176. /*******************************/ //function 177. 178. 9,10,11,12,13,14,15,16: 179. begin 180. isQ = 1'b1; 181. 182. rSDA <= D1[16-i]; 183. 184. if( C1 == 0 ) rSCL <= 1'b0; 185. else if( C1 == (TF + TLOW) ) rSCL <= 1'b1; 186. 187. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end 188. else C1 <= C1 + 1'b1; 189. end 190. 以上內容為部分核心操作。步驟9~16是用來寫一字節的偽函數。 191. 17: // waiting for acknowledge 192. begin 193. isQ = 1'b0; 194. 195. if( C1 == FHALF ) isAck <= SDA; 196. 197. if( C1 == 0 ) rSCL <= 1'b0; 198. else if( C1 == FHALF ) rSCL <= 1'b1; 199. 200. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end 201. else C1 <= C1 + 1'b1; 202. end 203. 204. 18: 205. if( isAck != 0 ) i <= 5'd0; 206. else i <= Go; 207. 以上內容為部分核心操作。步驟17用來讀取應答位,步驟18則用來判斷應答位。 208. /*****************************/ 209. 210. 19,20,21,22,23,24,25,26: // Read 211. begin 212. isQ = 1'b0; 213. if( C1 == FHALF ) D1[26-i] <= SDA; 214. 215. if( C1 == 0 ) rSCL <= 1'b0; 216. else if( C1 == FHALF ) rSCL <= 1'b1; 217. 218. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end 219. else C1 <= C1 + 1'b1; 220. end 221. 以上內容為部分核心操作。步驟19~26是讀取一字節的偽函數。 222. 27: // no acknowledge 223. begin 224. isQ = 1'b1; 225. //if( C1 == 100 ) isAck <= SDA; 226. 227. if( C1 == 0 ) rSCL <= 1'b0; 228. else if( C1 == FHALF ) rSCL <= 1'b1; 229. 230. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= Go; end 231. else C1 <= C1 + 1'b1; 232. end 233. 234. /*************************************/ // end fucntion 235. 236. endcase 237. 以上內容為部分核心操作。步驟27用來無視應答位。 238. /***************************************/ 239. 240. assign SCL = rSCL; 241. assign SDA = isQ ? rSDA : 1'bz; 242. assign oDone = isDone; 243. assign oData = D1; 244. 245. /***************************************/ 246. 247. endmodule 以上內容為相關的驅動聲明。 iic_demo.v 該組合模塊的連線部署請參考圖16.13,具體內容讓我們來看代碼吧。 1. module iic_demo 2. ( 3. input CLOCK, RESET, 4. output SCL, 5. inout SDA, 6. output [7:0]DIG, 7. output [5:0]SEL 8. ); 以上內容為相關的出入端聲明。 9. wire [7:0]DataU1; 10. wire DoneU1; 11. 12. iic_savemod U1 13. ( 14. .CLOCK( CLOCK ), 15. .RESET( RESET ), 16. .SCL( SCL ), // > top 17. .SDA( SDA ), // <> top 18. .iCall( isCall ), // < core 19. .oDone( DoneU1 ), // > core 20. .iAddr( D1 ), // < core 21. .iData( D2 ), // < core 22. .oData( DataU1 ) // > core 23. ); 24. 以上內容為IIC儲存模塊的實例化 。 25. smg_basemod U2 26. ( 27. .CLOCK( CLOCK ), 28. .RESET( RESET ), 29. .DIG( DIG ), // > top 30. .SEL( SEL ), // > top 31. .iData( D3 ) // < core 32. ); 33. 以上內容為數碼管基礎模塊的實例化。 34. /***************************/ 35. 36. reg [3:0]i; 37. reg [7:0]D1,D2; 38. reg [23:0]D3; 39. reg [1:0]isCall; 40. 41. always @ ( posedge CLOCK or negedge RESET ) // core 42. if( !RESET ) 43. begin 44. i <= 4'd0; 45. { D1,D2 } <= { 8'd0,8'd0 }; 46. D3 <= 24'd0; 47. isCall <= 2'b00; 48. end 49. else 以上內容為相關的寄存器聲明以及復位操作。 50. case( i ) 51. 52. 0: 53. if( DoneU1 ) begin isCall <= 2'b00; i <= i + 1'b1; end 54. else begin isCall <= 2'b10; D1 <= 8'd0; D2 <= 8'hAB; end 55. 56. 1: 57. if( DoneU1 ) begin isCall <= 2'b00; i <= i + 1'b1; end 58. else begin isCall <= 2'b10; D1 <= 8'd1; D2 <= 8'hCD; end 59. 60. 2: 61. if( DoneU1 ) begin isCall <= 2'b00; i <= i + 1'b1; end 62. else begin isCall <= 2'b10; D1 <= 8'd2; D2 <= 8'hEF; end 63. 64. 3: 65. if( DoneU1 ) begin D3[23:16] <= DataU1; isCall <= 2'b00; i <= i + 1'b1; end 66. else begin isCall <= 2'b01; D1 <= 8'd0; end 67. 68. 4: 69. if( DoneU1 ) begin D3[15:8] <= DataU1; isCall <= 2'b00; i <= i + 1'b1; end 70. else begin isCall <= 2'b01; D1 <= 8'd1; end 71. 72. 5: 73. if( DoneU1 ) begin D3[7:0] <= DataU1; isCall <= 2'b00; i <= i + 1'b1; end 74. else begin isCall <= 2'b01; D1 <= 8'd2; end 75. 76. 6: 77. i <= i; 78. 79. endcase 80. 81. endmodule
以上內容為核心操作。步驟0~2將數據8’hAB 寫入地址0,8’hCD寫入地址1,8’hEF寫入地址2。步驟3~5則是從地址0讀出數據 8’hAB並且暫存至 D3[23:16], 從地址1讀出數據 8’hCD 並且暫存至 D3[15:8],從地址2讀出數據 8’hEF 並且暫存至 D3[7:0]。編輯完畢便下載程序,如果數碼管從左至右顯示 “ABCDEF” ,那么表示實驗成功。
細節一: IIC儲存模塊,還是IIC功能模塊?
有關IIC儲存器的實驗曾在《整合篇》出現過,不過是作為功能類來對待。換之,本實驗的IIC儲存器則作為儲存類來看待,然而它究竟是功能類還是儲存類呢?其實這是見仁見智的問題。如果讀者認為功能類有助理解,那么它就是功能類 ... 相反的,筆者認為儲存類有助理解,所以承認它就是儲存類。
細節二: 100Khz 與 400Khz 速率
IIC儲存器——24LC04 有兩種速率供我們選擇,100Khz是比較規格的速率,因為SCL有50%的占空比,反之400Khz則是比較不規格的速率,因為SCL的前半周期為36%,后半周期為64%。審美而言,100Khz比400Khz美麗 ... 速度而言,400Khz比100Khz快4倍。100Khz的時序參數還有50Mhz量化結果如表16.3所示:
表16.3 相關的時序參數(50Mhz量化)
| 相關參數 |
標示 |
最小時間 |
最小時鍾 |
最大時間 |
最大時鍾 |
| Clock Frequency |
FCLK |
--- |
--- |
100Khz |
500 |
| Clock High Time |
THIGH |
4000ns |
200 |
--- |
--- |
| Clock Low Time |
TLOW |
4700ns |
235 |
--- |
--- |
| Rise Time |
TR |
--- |
--- |
1000ns |
50 |
| Fall Time |
TF |
--- |
--- |
300ns |
15 |
| Start Hold Time |
THD_STA |
4000ns |
200 |
--- |
--- |
| Start Setup Time |
TSU_STA |
4700ns |
235 |
--- |
--- |
| Data Input Hold Time |
THD_DAT |
0ns |
0 |
--- |
--- |
| Data Input Setup Time |
TSU_DAT |
250ns |
12 |
--- |
--- |
| Stop Setup Time |
TSU_STO |
4000ns |
200 |
--- |
--- |
| Output Valid From Clock |
TAA |
--- |
--- |
3500ns |
175 |
| Bus Free Time |
TBUF |
4700ns |
235 |
--- |
--- |
Verilog 的常量聲明如代碼16.9所示:
1. parameter FCLK = 10'd500, FHALF = 10'd250, FQUARTER = 10'd125;
2. parameter THIGH = 10'd200, TLOW = 10'd235, TR = 10'd50, TF = 10'd15;
3. parameter THD_STA = 10'd200, TSU_STA = 10'd235, TSU_STO = 10'd200;
代碼16.9
細節三:完整的個體模塊
實驗十六的IIC儲存模塊已經是完整的個體模塊,隨之可以調用。














