【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗十:PS/2模塊④ — 普通鼠標

實驗十:PS/2模塊④ — 普通鼠標

學習PS/2鍵盤之後,接下來就要學習 PS/2 鼠標。PS/2鼠標相較PS/2鍵盤,驅動難度稍微高了一點點,由於FPGA(從機)不只僅是從PS/2鼠標哪裏讀取數據,FPGA還要往鼠標裏寫數據 ... 反之,FPGA只要對PS/2鍵盤讀取數據便可。然而,最傷腦筋的地方就在於PS/2傳輸協議有奇怪的寫時序。css

clip_image002

圖10.1 從機視角,從機讀數據。html

爲了方便理解,餘下咱們經由從機的視角去觀察PS/2的讀寫時序。圖10.1是從機視角的讀時序,從機都是皆由 PS2_CLK的降低沿讀取1幀爲11位的數據 ... 期間,有11個降低沿,不過爲了方便調用,筆者將其整理爲僞函數,結果如代碼10.1所示:函數

1.    32: // Start bit
2.    if( isH2L ) i <= i + 1'b1; 
3.                          
4.    33,34,35,36,37,38,39,40:  // Data byte
5.    if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1;  end
6.                          
7.    41: // Parity bit
8.    if( isH2L ) i <= i + 1'b1;
9.                          
10.    42: // Stop bit
11.    if( isH2L ) i <= Go;

代碼10.1學習

clip_image004

圖10.2從機視角,從機寫數據,第一幀。spa

首先咱們必須明白,PS2_CLK 與 PS2_DAT 信號是雙向的IO口,因爲FPGA是從機的關係,因此FPGA一開始都處於輸入狀態,而PS/2鼠標一開始則處於輸出狀態。假設 isQ1 是FPGA針對PS2_CLK的輸出控制,isQ2 是FPGA針對 PS2_DAT 的輸出控制,然而復位狀態(初始化)都爲拉低狀態。code

FPGA爲了獲取數據的發送權,首先它必須摘取 PS2_CLK,亦即拉高 isQ1而後又拉低PS2_CLK 100us。orm

過後,FPGA必須釋放 PS2_CLK,亦即關閉 IO或者拉低 isQ1,期間順手拉高 isQ2,讓 PS2_DAT 成爲輸出狀態。那麼重點來了,讀者是否看見黃色的圈圈?當FPGA釋放 PS2_CLK瞬間,PS/2鼠標早已拉低 PS2_CLK,FPGA也所以錯過PS2_CLK第一次珍貴的降低沿。在此,讀者須要好好注意,由於許多資料都忽略這點,筆者也不當心撲街好幾回。htm

讀者須要知道,根據PS/2傳輸協議,從機(FPGA)無論怎樣掙扎也沒有 PS2_CLK的擁有權。換句話說,不管從機(FPGA)是寫數據仍是讀數據,從機(FPGA)都必須藉助主機(PS/2鼠標)發送過來的 PS2_CLK降低沿。若是咱們不當心忽略第一個降低沿,餘下幾個降低沿,咱們也會搞錯次序,最終形成數據讀取錯位的悲劇。對象

FPGA不管是寫數據仍是讀數據,從機都必須借用 PS2_CLK 的降低沿。FPGA釋放PS2_CLK以後,isQ2當即拉高,PS2_DAT 也隨之拉低以示一幀數據的起始位 ... 直至下一個降低沿到來爲此。blog

FPGA一共用了10個降低沿,即T0~T9發送8位數據位,1位校驗位,還有1位中止位。T9過去不久,PS2_CLK就會引來上升沿,此刻PS/2鼠標則會反饋1位應答位,不過此刻也無暇招呼它。T10之際,那是最後一個降低沿,FPGA拉低isQ2讓PS2_DAT成爲輸入狀態,而且讀取應答位,然而懶惰的筆者決定無視它。就這樣從機寫一幀數據就結束了。

在這裏,讀者是否以爲從機寫一幀數據比從機讀一幀數據還要麻煩呢?沒錯,筆者也這樣以爲。但是,麻煩歸麻煩,餘下咱們還要完成Verilog的描述工做,結果如代碼10.2所示:

1.                  /****************/ // PS2 Write Function
2.                          
3.                 32: // Press low CLK 100us 
4.                if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
5.                else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
6.                          
7.                33: // Release PS2_CLK and set in, PS2_DAT set out
8.                begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
9.                          
10.                34: // start bit 
11.                begin rDAT <= 1'b0; i <= i + 1'b1; end
12.                          
13.                35,36,37,38,39,40,41,42,43:  // Data byte 
14.                if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
15.                          
16.                44: // Stop bit 
17.                if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
18.                          
19.                45: // Ack bit
20.                if( isH2L ) begin i <= i + 1'b1; end
21.                          
22.                46: // PS2_DAT set in
23.                begin isQ2 <= 1'b0; i <= i + 1'b1; end
24.                  。。。。。。
25.     
26.      assign PS2_CLK = isQ1 ? rCLK : 1'bz;
27.      assign PS2_DAT = isQ2 ? rDAT : 1'bz;

代碼10.2

如代碼10.2所示:

步驟32,來拉高 isQ1又拉低 rCLK 持續 100us。

步驟33,拉低 isQ1釋放 PS2_CLK,而後又拉高 isQ2 準備發送數據。

步驟34,重點!因爲錯過第一個降低沿,咱們只能拉低 rDAT產生起始位。

步驟35~43,發送8位數據位,還有1位校驗位。

步驟44,發送結束位。

步驟45,無視應答位。

步驟46,拉低isQ2,讓PS2_CLK爲輸入狀態。

clip_image006

圖10.3主機視角,從機寫數據,第一幀。

接下來,讓咱們經由主機的視角去觀察,主機如何讀取從機發送過來的數據。初始狀態,主機亦即 PS/2鼠標掌握 PS2_CLK與 PS2_DAT。一旦從機即FPGA載取PS2_CLK,而且拉低輸出100us,馬上PS/2鼠標已經理解從機準備發送數據。100us事後,FPGA釋放 PS2_CLK而且載取PS2_DAT,PS/2鼠標便開始產生時鐘。如圖10.2所示,PS/2鼠標都是借用上升沿讀取FPGA發送過來的數據。大約11個上升沿事後,PS/2鼠標就結束讀取動做,而且反饋應答位。

clip_image008

圖10.4主機視角,從機寫數據,第二幀。

每當主機接收完畢一幀數據,就會反饋一幀數據。如圖10.2所示,那是圖10.1的下半部分,依然是從主機視角觀察從機寫數據。圖中顯示,每當PS/2鼠標(主機)接收完畢一幀數據之後,PS/2鼠標便會反饋一幀數據,亦即PS/2鼠標會再度借用10個上升沿發送一幀數據。期間,FPGA(從機)的 PS2_CLK 與 PS2_DAT都都處於輸入狀態。

clip_image009

圖10.5主機視角,從機寫數據,完整時序。

圖10.5是圖10.3與圖10.4的完整時序(主機視角)。

clip_image011

圖10.6從機視角,從機寫數據,第二幀。

既然主機反饋一幀數據,那麼從機也不能無視,如圖10.6所示,那是經由從機視角觀察從機如何讀取下一幀數據。期間,從機借用11個降低沿讀取1一幀數據。

clip_image012

圖10.7從機視角,從機寫數據,完整時序。

圖10.7是圖10.2與圖10.5的完整時序(從機視角)。

爲此,代碼10.2能夠繼續擴張,結果如代碼10.3所示:

1.           /****************/ // PS2 Write Function
2.    
3.            32: // Press low CLK 100us 
4.            if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
5.            else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
6.                          
7.            33: // Release PS2_CLK and set in, PS2_DAT set out
8.            begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
9.                          
10.            34: // start bit 
11.            begin rDAT <= 1'b0; i <= i + 1'b1; end
12.                          
13.            35,36,37,38,39,40,41,42,43:  // Data byte 
14.            if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
15.                          
16.            44: // Stop bit 
17.            if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
18.                          
19.            45: // Ack bit
20.            if( isH2L ) begin i <= i + 1'b1; end
21.                          
22.            46: // PS2_DAT set in
23.            begin isQ2 <= 1'b0; i <= i + 1'b1; end
24.                          
25.            47,48,49,50,51,52,53,54,55,56,57: // 1 Frame
26.            if( isH2L ) i <= i + 1'b1;
27.                          
28.            58: // Return
29.            i <= Go;
30.            
31.           ......
32.      
33.        assign PS2_CLK = isQ1 ? rCLK : 1'bz;
34.        assign PS2_DAT = isQ2 ? rDAT : 1'bz;

代碼10.3

步驟32~46曾在前面說過,即從機發送一幀數據。餘下步驟47~57(共有11個步驟),主要用來過濾主機反饋過來的下一幀數據,步驟58則是步驟返回。不過爲何步驟47~57不是讀取數據,而是過濾數據?彆着急,答案很快就會揭曉,暫時忍耐一下。

呼!PS/2傳輸協議的寫數據(從機視角)解釋起來真有夠嗆。最後讓咱們來總結一下,主機不管是輸出數據,仍是讀取數據都是借用 PS2_CLK的上升沿。反之,從機不管是輸出數據,仍是讀取數據都是借用 PS2_CLK 的降低沿。PS/2傳輸協議是可謂是爺爺級別的傳輸協議吧,由於近代的傳輸協議無論對象是從機仍是主機,或者是寫仍是讀,通常都是上升沿設置數據降低沿鎖存數據。不多狀況是主機使用一個時間沿,從機使用另外一個時間沿,例如 SPI傳輸協議就是最好的例子。

說完PS/2傳輸協議,接下來咱們要進入PS/2鼠標的主題了。

鼠標也有普通鼠標與滾擴展鼠標之分 ... 所謂普通鼠標就是包含左鍵,中建還有右鍵;所謂擴展鼠標則包含左鍵,中建,右鍵還有滾輪,擴展鼠標也稱爲滾輪鼠標,不過通常的擴展鼠標只有一個滾輪而已。實驗十的實驗目的就是驅動普通鼠標。PS/2鼠標不像PS/2鍵盤,PS/2鼠標即便一上電也不會當即工做,期間從機必須將它使能才行。

普通鼠標一旦上電就便會當即復位,而後獲得默認化參數,而且進入Steam模式。所謂Steam模式,即位置情況或者按鍵情況一有變化就會鼠標便會當即發送報告。話雖然那麼說,實際上 Stream 模式還要依賴採集頻率,默認的採集頻率是 100次/秒,即採集間隔爲10ms,也就是說鼠標在一秒內會檢測100次位置情況還有按鍵情況。

舉例而言,假設筆者按着左鍵不放,那麼鼠標在一秒內會發送100次「左鍵好疼!左鍵好疼!「,直至筆者釋放左鍵爲止。再假設鼠標不當心被筆者退了一下,而後鼠標在10ms內向左移動10mm,當鼠標察覺位置情況發生變化之後,鼠標便會發送「哎呀!被人推向左邊10mm了!」。

clip_image014

圖10.7.1 鼠標的位置標示。

鼠標爲了標示位置,內建2組9位的寄存器X與Y,結果如圖10.7.1所示。默認下,鼠標的分辨率爲4計數/mm。此外,鼠標也有能力辨識4處的移動方向,例如左移 10mm 寄存器X便計數 -40,右移10mm 寄存器X便計數 +40,上移10mm 寄存器Y便計數 +40,下移 10mm 寄存器Y便計數 -40。鼠標每隔10ms(默認採集頻率)便會清零一次寄存器X與Y的內容。

PS/2鼠標不像PS/2鍵盤一上電便當即工做,咱們必須事先發送命令8’hF4即「使能鼠標發送數據」,開啓數據的水龍頭。每當鼠標接收一幀數據,鼠標便會反饋一幀數據,爲此 ... 從機每次向鼠標寫入一幀數據,就必須接收一幀反饋數據。反饋數據爲8’hFA表示「數據接收成功」,反饋數據爲 8’hFE表示「第一幀數據接收失敗」,反饋數據爲 8’hFC則表示「第二幀數據接收成功」(有些命令是由2幀或者以上組成)。

clip_image016

圖10.8 從機發送 「使能報告」命令,鼠標反饋接收成功。

爲了驅使鼠標工做,PS/2鼠標上電之後,從機必須發送命令 8’hF4,而且接收反饋 8’hFA。若是一切順利,那麼鼠標就會開始工做,結果如圖10.8所示。鼠標「使能」之後,鼠標便處於就緒狀態,採集便開始 ... 此刻,若是鼠標的位置情況或者按鍵情況發生變化,鼠標就會發送3幀,亦即3字節的報告。

clip_image018

圖10.9 鼠標發送報告。

如圖10.9所示,那是一份3個字節的報告,Verilog能夠這樣描述:

1.               0: // Read 1st byte
2.              begin i <= FF_Read; Go <= i + 1'b1; end
3.                          
4.              1: // Store 1st byte
5.              begin D1[7:0] <= T; i <= i + 1'b1; end
6.                          
7.              2: // Read 2nd byte
8.              begin i <= FF_Read; Go <= i + 1'b1; end
9.                          
10.              3: // Store 2nd byte
11.              begin D1[15:8] <= T; i <= i + 1'b1; end
12.                          
13.              4: // Read 3rd byte
14.              begin i <= FF_Read; Go <= i + 1'b1; end
15.                          
16.             5: // Store 3rd byte
17.             begin D1[23:16] <= T; i <= i + 1'b1; end
18.                          
19.              ......                      
20.                          
21.            32: // Start bit
22.            if( isH2L ) i <= i + 1'b1; 
23.                          
24.            33,34,35,36,37,38,39,40:  // Data byte
25.            if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1;  end
26.                          
27.            41: // Parity bit
28.            if( isH2L ) i <= i + 1'b1;
29.                          
30.            42: // Stop bit
31.            if( isH2L ) i <= Go;

代碼10.4

步驟32~42是讀一幀數據的僞函數,步驟0~1讀取第一字節而且暫存道D[7:0],步驟

2~3讀取第二字節而且暫存到D[15:8],步驟4~5讀取第三字節而且暫存到D[23:16]。

至於報告的內容如表10.1所示:

表10.1 普通鼠標的報告。

字節/位

[7]

[6]

[5]

[4]

[3]

[2]

[1]

[0]

字節一

Y溢出位

X溢出位

Y[8]符號位

X[8]符號位

保留

中鍵

右鍵

左鍵

字節二

X[7:0]

字節三

Y[7:0]

如表10.1所示,字節一的第四位表示按鍵情況之外,字節一的高四位也與內部寄存器X與Y有關。字節二爲寄存器X的內容,字節三位寄存器Y的內容。

字節一,[0]標示左鍵,1表示左鍵按下;[1]標示右鍵,1表示右鍵按下;[2]標示中鍵,1表示中鍵按下;[4]爲字寄存器的最高位也是符號位;[5]爲寄存器最高位也是符號位;節二是寄存器X的低八位,字節三是寄存器Y的低八位,所以寄存器X與Y的位寬爲9。這樣做的目的是爲了使用補碼錶示鼠標的移動情況。至於補碼是什麼?失憶的朋友請複習《時序篇》。

clip_image020

圖10.9.1 鼠標的有效位置。

假設寄存器X的內容爲 9’b1_1111_1100,[8]爲1’b1表示鼠標正在左移,[7:0]爲8’b1111_1100也是 8個計數,亦即移動2mm的距離。所以 9’b1_1111_1100 表示鼠標左移2mm的舉例。再假設寄存器Y的內容爲 9’b0_0001_0000,[8]爲0表示鼠標正在上移,[7:0] 爲 8’b0001_0000也是16個計數,亦即移動4mm。所以 9’b0_0001_0000表示鼠標上移4mm。結果如圖10.9.1所示。

上述內容理解完畢之後,咱們即可以開始建模了。

clip_image022

圖10.10 實驗十的建模圖。

圖10.10 是實驗十的建模圖,組合模塊 ps2_demo 內部包含,PS/2初始化功能模塊,PS/2讀功能模塊,數碼管基礎模塊,而後中間還要正直化的即時操做。顧名思義,PS/2初始化功能模塊主要負責初始化的工做,簡言之就是發送命令 8’hF4,完後便拉高oEn使能PS/2讀功能模塊。PS/2讀功能模塊接收 iEn拉高便會開始讀取3字節的報告,而且經由oData將其輸出。

稍微注意一下PS2_CLK還有PS2_DAT頂層信號,因爲PS/2初始化功能模塊須要雙向訪問PS/2鼠標,爲此該頂層信號皆是出入狀態(IO)。反之,PS/2讀功能模塊只有接收數據而已,所以該頂層信號只是出入狀態。PS/2讀功能模塊的oData,其中[2:0]是3只按鍵的情況,而且直接驅動三位LED資源。

至於[23:4]則是寄存器X與寄存器Y的內容,它們經由即時操做正直化之後便聯合驅動數碼管基礎模塊,而後再顯示內容。

ps2_init_funcmod.v

clip_image024

圖10.11 PS/2 初始化功能模塊的建模圖。

因爲該模塊須要來問讀寫PS/2鼠標,所以頂層信號 PS2_CLK與 PS2_DAT 都是雙向,亦即IO口。此外,一旦該模塊完成初始化的工做,oEn就會一直拉高。

1.    module ps2_init_funcmod
2.    (
3.         input CLOCK, RESET,
4.         inout PS2_CLK, 
5.         inout PS2_DAT,
6.         output oEn
7.    );  
8.         parameter T100US = 13'd5000;
9.         parameter FF_Write = 7'd32;

以上內容是出入端聲明。第8行是100us的常量聲明,第9行則是僞函數的入口。

11.         /*******************************/ // sub1
12.         
13.        reg F2,F1; 
14.         
15.        always @ ( posedge CLOCK or negedge RESET )
16.             if( !RESET )
17.                  { F2,F1 } <= 2'b11;
18.              else 
19.                  { F2, F1 } <= { F1, PS2_CLK };
20.    
21.         /*******************************/ // Core
22.         
23.         wire isH2L = ( F2 == 1'b1 && F1 == 1'b0 );

以上是檢測電平變化的周邊操做。第23行則是降低沿的即時聲明。

24.         reg [8:0]T;
25.         reg [6:0]i,Go;
26.         reg [12:0]C1;
27.         reg rCLK,rDAT;
28.         reg isQ1,isQ2,isEn;
29.         
30.         always @ ( posedge CLOCK or negedge RESET )
31.             if( !RESET )
32.                  begin
33.                         T <= 9'd0;
34.                         C1 <= 13'd0;
35.                         { i,Go } <= { 7'd0,7'd0 };
36.                         { rCLK,rDAT } <= 2'b11;
37.                         { isQ1,isQ2,isEn } <= 3'b000;
38.                    end
39.               else

以上內容爲相關的寄存器聲明以及復位操做。注意,PS2_CLK與PS2_DAT默認下都是高電平,所示 rCLK 與 rDAT 被賦予復位值 2’b11。

40.                    case( i )
41.                     
42.                         /***********/ // INIT Normal Mouse 
43.                          
44.                          0: // Send F4 1111_0100
45.                          begin T <= { 1'b0, 8'hF4 }; i <= FF_Write; Go <= i + 1'b1; end
46.                          
47.                          1:
48.                          isEn <= 1'b1;
49.                          

以上內容是部分核心操做。步驟0~1是主操做,主要發送命令 8’hF4,而後拉高isEn。第45行{ 1'b0, 8'hF4 },其中 1’b0是校驗位,PS/2的校驗位是「奇校驗」,若是「1」的數量爲單數,那麼校驗位即是 0。如第45所示,8’hF4有5個「1」所示,校驗位爲0。

50.                          /****************/ // PS2 Write Function
51.                          
52.                          32: // Press low CLK 100us 
53.                          if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
54.                          else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
55.                          
56.                          33: // Release PS2_CLK and set in, PS2_DAT set out
57.                          begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
58.                          
59.                          34: // start bit 
60.                          begin rDAT <= 1'b0; i <= i + 1'b1; end
61.                          
62.                          35,36,37,38,39,40,41,42,43:  // Data byte 
63.                          if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
64.                          
65.                          44: // Stop bit 
66.                          if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
67.                          
68.                          45: // Ack bit
69.                          if( isH2L ) begin i <= i + 1'b1; end
70.                          
71.                          46: // PS2_DAT set in
72.                          begin isQ2 <= 1'b0; i <= i + 1'b1; end
73.                          
74.                          47,48,49,50,51,52,53,54,55,56,57: // 1 Frame
75.                          if( isH2L ) i <= i + 1'b1;
76.                          
77.                          58: // Return
78.                          i <= Go;
79.                            
80.                     endcase

以上內容是部分核心操做。第32~58行是從機寫一幀數據,讀一幀反饋數據的僞函數。

81.         
82.         assign PS2_CLK = isQ1 ? rCLK : 1'bz;
83.         assign PS2_DAT = isQ2 ? rDAT : 1'bz;
84.         assign oEn = isEn;
85.    
86.    endmodule

以上內容是輸出驅動聲明。

ps2_read_funcmod.v

clip_image026

圖10.12 PS/2 讀化功能模塊的建模圖。

PS/2讀功能模塊,若是iEn不拉高就不工做。此外,該模塊也只是讀入3字節的報告而已,完後便經由oTrig產生完成信號,報告內容則經由 oData。

1.    module ps2_read_funcmod
2.    (
3.         input CLOCK, RESET,
4.         input PS2_CLK,PS2_DAT,
5.         input iEn,
6.         output oTrig,
7.         output [23:0]oData
8.    );  
9.         parameter FF_Read = 7'd32;

以上內容是出入端聲明。第9行則是僞函數的入口地址。

11.         /*******************************/ // sub1
12.         
13.        reg F2,F1; 
14.         
15.        always @ ( posedge CLOCK or negedge RESET )
16.             if( !RESET )
17.                  { F2,F1 } <= 2'b11;
18.              else 
19.                  { F2, F1 } <= { F1, PS2_CLK };
20.    
21.         /*******************************/ // core
22.         
23.         wire isH2L = ( F2 == 1'b1 && F1 == 1'b0 );

以上內容是檢測電平變化的周邊操做,第23行則是降低沿的即時聲明。

24.         reg [23:0]D1;
25.         reg [7:0]T;
26.         reg [6:0]i,Go;
27.         reg isDone;
28.         
29.         always @ ( posedge CLOCK or negedge RESET )
30.             if( !RESET )
31.                  begin
32.                         D1 <= 24'd0;
33.                         T <= 8'd0;
34.                         { i,Go } <= { 7'd0,7'd0 };
35.                         isDone <= 1'b0;
36.                    end

以上內容是相關的寄存器聲明以及復位操做。

37.               else if( iEn )  
38.                    case( i )
39.                     
40.                     /*********/ // Normal mouse 
41.                          
42.                          0: // Read 1st byte
43.                          begin i <= FF_Read; Go <= i + 1'b1; end
44.                          
45.                          1: // Store 1st byte
46.                          begin D1[7:0] <= T; i <= i + 1'b1; end
47.                          
48.                          2: // Read 2nd byte
49.                          begin i <= FF_Read; Go <= i + 1'b1; end
50.                          
51.                          3: // Store 2nd byte
52.                          begin D1[15:8] <= T; i <= i + 1'b1; end
53.                          
54.                          4: // Read 3rd byte
55.                          begin i <= FF_Read; Go <= i + 1'b1; end
56.                          
57.                          5: // Store 3rd byte
58.                          begin D1[23:16] <= T; i <= i + 1'b1; end
59.                          
60.                          6:
61.                          begin isDone <= 1'b1; i <= i + 1'b1; end
62.                          
63.                          7:
64.                          begin isDone <= 1'b0; i <= 7'd0; end
65.                          

以上內容爲部分核心操做。第37行 if( iEn ) 表示,iEn不拉高核心操做就不運行。步驟0~1是讀取第一字節,步驟2~3是讀取第二字節,步驟4~5是讀取第三字節,步驟6~7則是反饋完成信號,以示一次性的報告讀取已經完成。完後,i便指向步驟0。

66.                          /****************/ // PS2 Write Function
67.                          
68.                          32: // Start bit
69.                          if( isH2L ) i <= i + 1'b1; 
70.                          
71.                          33,34,35,36,37,38,39,40:  // Data byte
72.                          if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1;  end
73.                          
74.                          41: // Parity bit
75.                          if( isH2L ) i <= i + 1'b1;
76.                          
77.                          42: // Stop bit
78.                          if( isH2L ) i <= Go;
79.                            
80.                     endcase

以上內容爲部分核心操做。步驟32~42則是僞函數,主要是負責讀取一幀數據。

81.                     
82.         assign oTrig = isDone;
83.         assign oData = D1;
84.     
85.    endmodule

以上內容是輸出驅動聲明。

ps2_demo.v

組合模塊ps2_demo.v的建模圖就再也不重複粘貼了。

1.    module ps2_demo
2.    (
3.         input CLOCK, RESET,
4.         inout PS2_CLK, PS2_DAT,
5.         output [7:0]DIG,
6.         output [5:0]SEL,
7.         output [2:0]LED
8.    );
9.        wire EnU1;
10.    
11.         ps2_init_funcmod U1
12.         (
13.             .CLOCK( CLOCK ),
14.              .RESET( RESET ),
15.              .PS2_CLK( PS2_CLK ), // < top
16.              .PS2_DAT( PS2_DAT ), // < top
17.              .oEn( EnU1 ) // > U2
18.         );
19.         
20.         wire [23:0]DataU2;
21.         
22.          ps2_read_funcmod U2
23.         (
24.             .CLOCK( CLOCK ),
25.              .RESET( RESET ),
26.              .PS2_CLK( PS2_CLK ), // < top
27.              .PS2_DAT( PS2_DAT ), // < top
28.              .iEn( EnU1 ),       // < U1
29.               .oTrig(),
30.              .oData( DataU2 )   // > U2
31.         );
32.         
33.         // immediate proses
34.         wire[7:0] X = DataU2[4] ? (~DataU2[15:8] + 1'b1) : DataU2[15:8];
35.         wire[7:0] Y = DataU2[5] ? (~DataU2[23:16] + 1'b1) : DataU2[23:16];
36.         
37.       smg_basemod U3
38.        (
39.           .CLOCK( CLOCK ),
40.           .RESET( RESET ),
41.            .DIG( DIG ),  // > top
42.            .SEL( SEL ),  // > top
43.            .iData( { 3'd0,DataU2[5],Y,3'd0,DataU2[4],X }) // < U2
44.        );
45.        
46.        assign LED = {DataU2[1], DataU2[2], DataU2[0]};
47.                          
48.    endmodule

該代碼很是簡單,第30行表示U2的oTrig無用武之地。第34~35行是正直化的即時聲明。第43行是U3的聯合驅動,其中 3’d0, DataU2[5] 表示第1位數碼管顯示Y的符號位,Y表示第2~3位的數碼管顯示Y的正直結果,3’d0,DataU2[4] 表示第4位數碼管顯示X的符號位,X表示第5~6位數碼管顯示X的正直結果。第46行則是各個按鍵狀況直接驅動LED資源。

編譯完成而且下載程序。假設筆者按下左鍵,那麼LED[0]便會點亮,釋放則消滅。再假設筆者向左移動鼠標,那麼鼠標第4位數碼管會顯示1,第5~6數碼管則會顯示X的內容,亦即鼠標移動的舉例。

細節一:精簡與直觀

1.        0: // Read 1st byte
2.        begin i <= FF_Read; Go <= i + 1'b1; end 
3.        1: // Store 1st byte
4.        begin D1[7:0] <= T; i <= i + 1'b1; end
5.        2: // Read 2nd byte
6.        begin i <= FF_Read; Go <= i + 1'b1; end
7.        3: // Store 2nd byte
8.        begin D1[15:8] <= T; i <= i + 1'b1; end
9.        4: // Read 3rd byte
10.        begin i <= FF_Read; Go <= i + 1'b1; end
11.        5: // Store 3rd byte
12.        begin D1[23:16] <= T; i <= i + 1'b1; end

代碼10.5

代碼10.5是PS/2讀功能模塊的部份內容,期間步驟0~5表示3字節讀取且暫存的過程

。事實上,代碼10.5能夠進一步精簡,結果如代碼10.6所示:

13.        0: // Read 1st byte
14.        begin i <= FF_Read; Go <= i + 1'b1; end 
15.        1: // Store 1st byte
16.        begin D1[7:0] <= T; i <= FF_Read; Go <= i + 1'b1;end
17.        2: // Read 2nd byte
18.        begin D1[15:8] <= T; i <= FF_Read; Go <= i + 1'b1; end
19.        3: // Store 2nd byte
20.        begin  D1[23:16] <= T; i <= i + 1'b1; end
21.        ......

代碼10.6

代碼10.6相較代碼10.5,它雖然有很高程度的精簡度,不過直觀程度卻不如代碼10.6。

到頭來究竟是直觀好,仍是精簡好,惟有見仁見智了。

細節二:完整的個體模塊

clip_image028

圖10.13 實驗十的完整個體模塊。

圖10.13是PS/2鼠標基礎模塊,裏邊包含PS/2初始化功能模塊,還有PS/2讀功能模塊。

該模塊的最左邊是頂層信號 PS2_CLK 與 PS2_DAT 的輸入,鼠標完成初始化之後,PS/2初始化功能模塊便會拉高 oEn 使能 PS/2讀功能模塊。PS/2讀功能模塊的左邊除了頂層信號之外還有iEn,iEn不拉高該模塊就不工做。PS/2讀功能模塊每完成3字節報告的讀取,就會經由 oTrig 產生完成信號。

ps2mouse_basemod.v
1.    module ps2mouse_basemod    
2.    (
3.         input CLOCK, RESET,
4.         inout PS2_CLK, PS2_DAT,
5.         output oTrig,
6.         output [31:0]oData
7.    );
8.        wire EnU1;
9.    
10.         ps2_init_funcmod U1
11.         (
12.             .CLOCK( CLOCK ),
13.              .RESET( RESET ),
14.              .PS2_CLK( PS2_CLK ), // < top
15.              .PS2_DAT( PS2_DAT ), // < top
16.              .oEn( EnU1 ) // > U2
17.         );
18.         
19.         ps2_read_funcmod U2
20.         (
21.              .CLOCK( CLOCK ),
22.              .RESET( RESET ),
23.              .PS2_CLK( PS2_CLK ), // < top
24.              .PS2_DAT( PS2_DAT ), // < top
25.              .iEn( EnU1 ),       // < U1
26.              .oTrig( oTrig ),    // > Top
27.              .oData( oData )   // > Top
28.         );
29.                          
30.    endmodule
相關文章
相關標籤/搜索