PCIE開發筆記(七)DLL介紹(上)中我們大致介紹了DLL工作的流程,但是對於一些重要的細節,和一些常見的情況還沒有介紹,這樣會使讀者對整個流程沒有一個直觀的了解,現在我將就幾個常見的情況進一步介紹。

圖1 ACK/NAK DLLP

在《DLL介紹(上)》中我們大致介紹了ACK/NAK協議的工作原理,其中ACK/NAK DLLP扮演著非常重要的角色,所以很有必要在這裡重新詳細地介紹這兩個DLLP。如圖1所示,DLLP有以下幾個部分:

  • Type欄位 Byte 0 bit7:0 :表明該DLLP是ACK還是NAK,對於ACK DLLP 該位元組為0000 0000。對於NAK DLLP該位元組為0001 0000。
  • AckNak_Seq_Num欄位: 該欄位主要是提供給發送端來表明那些TLP發送成功或者失敗。 AckNak_Seq_Num=NEXT_RCV_SEQ(After Incrementing) -1 這裡有一個需要注意的細節!!!!在上一篇我們說到一個,當Device B(接收端)接收一個TLP數據包,如果其Sequence Number等於NEXT_RCV_SEQ,那麼NEXT_RCV_SEQ將自行加一。如果此時Device B需要返回一個ACK DLLP,那麼此時公式中的NEXT_RCV_SEQ為自行加一後的值。其他的所有情況,不存在NEXT_RCV_SEQ值變化,所以不需要考慮這個細節。協議做這樣的定義使非常的巧妙,在下面你將會體會到它的巧妙。
  • 16-bit CRC: 用來校驗

圖2 發送端接收DLLP

ACK和NAK DLLP表示的意義不同。以圖二為例,當發送端接收一個Sequence Number為5的ACK DLLP,則表示接收端已經接收序號為5和更早的TLP,在此之後發送端將會從Replay Buffer中刪除這些數據包。當發送端接收到一個序列號為6的NAK DLLP,則表示接收端沒有成功接收序列號為7的TLP,但是於此同時間接的表明序號6及更早的TLP已經成功接收。也就是說不論接收到ACK/NAK DLLP,總是能夠證明接收端已經成功接收若干TLP。

接收端並不需要對每一個TLP都要返回一個DLLP,這樣可以有效減小ACK DLLP對鏈路帶寬的佔用。

從上一篇我們可以看到,發送端不會發送兩個序列號不同的NAK DLLP。例如在圖2,如果接收端發現序號4 TLP出現錯誤,那個它將發送一個NAK,如果此後發現 5 TLP錯誤,那個接收方不會再發送一個NAK,直到它成功接收到序號4 TLP。也就是說不可能有兩個NAK同時在傳播。

當發送端正在再次發送Buffer中的TLP時,如果此時發送端接收到ACK/NAK DLLP,那麼允許發送端先完成這個再次發送,之後再去處理接收的DLLP(儘管這可能不是一種高效的機制)。如果發送端接收一個ACK之後有接收一個新的ACK,那麼含有較早序列號的ACK將會被丟棄。


結合一些實例講述整個流程。

實例一:接收到ACK DLLP,著重點在Device A

圖3 接收ACK DLLP之後的行為

在上一篇中我已經以流程圖的形式說明了這個流程,這裡我就用圖3簡要的再闡述一下整個流程。

  1. Device A發送序列號為3、4、5、6、7 TLP,TLP3是第一個發送的,TLP7是最後發送的
  2. Device B按順序接收TLP 3、4、 5。TLP 6,7還正在發送途中。
  3. Device B將會對TLP3、4、5進行錯誤檢查,確認成功接收了這幾個TLP之後,它將返回一個序列號為5的ACK,表明接收了TLP3、4、5。
  4. Device A接收到ACK 5。
  5. Device A消除Buffer中TLP3、4、5。
  6. 當Device B接收到TLP6、7。則重複步驟3-4。

我們可以這樣總結,當Device A接收到一個ACK DLLP數據包之後,Device A需要做如下工作:

  • 使用DLLP數據包中的AckNak_Seq_Num[11:0]更新ACKD_SEQ寄存器。
  • 將計數器REPLAY_NUM和REPLAY_TIMER重置為0。
  • 消除Buffer中序列號為AckNak_Seq_Num[11:0]及更早的TLP。

實例2 接收到NAK ,著重點在Device A

圖4 接收NAK DLLP之後的行為

如圖4所示,簡述整個流程。

  1. Device A發送TLP 4094、4095、0、1、2。其中TLP 4094是第一個TLP,TLP 2是最後一個TLP。
  2. Device B按順序接收TLP 4094、4095、0。TLP1、2還在傳輸途中。
  3. Device B成功接收TLP4094,TLP4094不存在問題。同時將NEXT_RCV_SEQ增加至4095。
  4. Device B接收到含有CRC錯誤的TLP 4095。
  5. Device B返回一個序列號為4094的NAK DLLP。
  6. Device A接收到這個NAK 4094,阻塞DLL繼續接收TL層傳遞的新的TLP,直到再次發送結束。
  7. Device A去除Buffer中已經接收的TLP,本例為TLP 4094。
  8. Device A再次發送TLP 4095、0、1、2。但是不從Buffer中去除這些TLP。

我們可以這樣總結,當Device A接收到一個NAK DLLP數據包之後,Device A需要做如下工作:

  • 消除Buffer中序列號為AckNak_Seq_Num[11:0]及更早的TLP。
  • 使用DLLP數據包中的AckNak_Seq_Num[11:0]更新ACKD_SEQ寄存器。
  • 只有當NAK DLLP中AckNak_Seq_Num[11:0]在當前ACKD_SEQ之後,才將REPLAY_NUM重置為0。(確保Device A是在向前傳輸TLP,而不是在原地踏步)同時將REPLAY_TIMER置零。
  • 再次發送整個Buffer中的TLP。

每次Device A接收到NAK DLLP,Device A將會再次發送Buffer中的TLP,同時阻止DLL的Buffer接收來自TL層傳遞的TLP,直到再次發送這個過程結束。在Device A再次發送過程中,Device A接收到ACK/NAK DLLP,那麼允許Device A先完成這個再次發送,完成之後再去處理接收的DLLP(儘管這可能不是一種高效的機制)。每次再次發送時,Device A使用REPLAY_NUM來記錄再次發送的次數,也就是說接收NAK之後,再次發送這個Buffer中的TLP,如果再次發送的TLP沒有一個被Device B接收,那麼REPLAY_NUM繼續加1,並再次再次發送整個Buffer,如果再次發送的TLP有部分被Device B成功接收,那麼Device A將REPLAY_NUM置零。如果Device A再次發送Buffer四次,而Device B每次一個TLP都沒有接收成功,則表示整個鏈路存在一些問題,那麼DLL層停止工作,並通知PL層進行鏈路訓練。

引起DLL再次發送Buffer有兩中情況:

  • Device A接收到NAK DLLP。
  • Device A Replay Timer計數器溢出如果Device A發送TLP以後,但是遲遲沒有接收到返回的ACK/NAK DLLP,進而Device A什麼措施都不選取顯然是非常不合理的。Device A遲遲沒有接收到返回的ACK/NAK DLLP的原因可能是DLLP在鏈路傳播途中丟失,或者途中DLLP變化,被Device A接收到之後進行CRC檢查發現錯誤而將它丟棄,或者Device B出現問題而不能發送DLLP。因此很有必要引入一個機制避免這種情況發生。Device A通過設置一個時間計數器,如果長時間接收不到DLLP,那麼Device A再次發送整個Buffer,來解決這種情況的發生。

綜合實例一,二。我們可以看出,無論是ACK/NAK DLLP,都表明AckNak_Seq_Num[11:0]及更早的TLP已經成功的被Device B 成功的接受。這樣做事很巧妙地。我們從上一篇可以看出,Device A對NAK DLLP處理只是在ACK DLLP處理的基礎上多了一個再次發送Buffer的部分,這樣大大簡化了電路。

實例3 返回一個ACK DLLP,著重點在Device B

圖5 Device B返回ACK DLLP的情況

如圖5所示,簡述整個流程。

  1. Device A發送TLP 4094、4095、0、1、2。其中TLP 4094是第一個發送的,TLP 2是最後發送的。
  2. Device B按順序接收到TLP 4094、4095、0、1。NEXT_RCV_SEQ增加至2。TLP 2還在發送中。
  3. Device B進行CRC檢查,發送一個ACK DLLP,序列號為1。
  4. Device B將TLP 4094、4095、0、1傳遞給TL層。
  5. Device B最終接收TLP 2。重複步驟2-4。

我們可以這樣總結,Device B主要是做如下工作:

  • 進行CRC檢查,TLP正確並將TLP傳遞給TL層。
  • 對比TLP的Sequence Number和NEXT_RCV_SEQ,發現相同,說明是Device B想接收的TLP。遞增NEXT_RCV_SEQ。這一步保證Device B按順序接收TLP。
  • 清除NAK_SCHEDULED。
  • 在ACKNAK_LATENCY_TIMER溢出時,返回ACK DLLP,它的序列號為溢出時的NEXT_RCV_SEQ -1 。

實例4 返回一個NAK DLLP,著重點在Device B

圖6 Device B返回NAK DLLP的情況

如圖6所示,簡述整個流程。

  1. Device A發送TLP 4094、4095、0、1、2。
  2. Device B按順序接收TLP 4094、4095、0。TLP 1、2還在鏈路上傳遞。
  3. Device B正確無誤地接收TLP 4094,並將它傳遞至TL層。並將NEXT_RCV_SEQ增加至4095。
  4. Device B發現TLP 4095存在CRC錯誤。那麼設置NAK_SCHEDULED為1,同時返回一個NAK DLLP,序號為4094。NEXT_RCV_SEQ不增加。
  5. Device B丟棄TLP 4095。
  6. Device B依舊丟棄TLP 0,儘管它是一個正確的TLP。當TLP 1、2到達時同樣也丟棄。
  7. Device B不為TLP 0、1、2發送ACK DLLP。因為NAK_SCHEDULED被設置為1。
  8. Device A接收到NAK 4094。
  9. Device A不再接收來自TL的TLP。
  10. Device A將TLP 4094從Buffer中清楚。
  11. Device A再次發送TLP 4095、0、1、2。
  12. 再次發送的TLP 4095、0、1、2依次到達Device B。
  13. 在確認接收的TLP無錯誤之後,Device B發現TLP 4095為再次發送的TLP,因為TLP的Sequence Number等於NEXT_RCV_SEQ。清楚NAK_SCHEDULED。
  14. Device B將這些TLP按順序傳遞給TL層。

我們可以這樣總結,Device B主要是做如下工作:

  • 進行CRC檢查,發現TLP出現TLP錯誤。表明接收TLP失敗,那麼設置NAK_SCHEDULED位。同時返回一個NAK DLLP,序列號為NEXT_RCV_SEQ -1。
  • 如果CRC檢查通過,那麼查看TLP的Sequence Number是否等於NEXT_RCV_SEQ。如果相等,則表明是Device B期望接收的TLP。如果小於,則表明這個TLP Device B之前已經接收,那麼丟棄這個數據包,並將NAK_SCHEDULED置零。
  • 如果大於,說明存在TLP丟失。那麼如果此時NAK_SCHEDULED為0,就將NAK_SCHEDULED置1,返回一個NAK DLLP。如果NAK_SCHEDULED為1,那麼保持原狀,不返回NAK DLLP。

綜合實例一,二。我們可以總結,出現以下情況後會立即返回一個NAK,並將NAK_SCHEDULED置1:首先返回NAK DLLP的一個大前提是NAK_SCHEDULED=0,然後出現以下任意情況

  • PL層發現接受的TLP有問題。
  • TLP CRC檢查不通過。
  • TLP Sequence Number大於NEXT_RCV_SEQ,丟失TLP。

當TLP通過CRC,並且Sequence Number小於等於NEXT_RCV_SEQ。則Device B僅僅將NAK_SCHEDULED清零。

當以下條件滿足,Device A將返回一個ACK DLLP:

  • DLL層處於工作狀態。
  • 結合成功的TLP已經傳遞給TLP。但是還沒有返回一個ACK DLLP。
  • AckNak_LATENCY_TIMER溢出或者到達設置的值。
  • 用於傳輸ACK DLLP的鏈路已經處於L0狀態。
  • 用於傳輸ACK DLLP的鏈路當前沒有傳輸其他數據包。
  • NAK_SCHEDULED=0。

當NAK_SCHEDULED=1時,Device A不會返回任何DLLP,直到它被清0。

從上面的總結我們可以看出返回一個ACK DLLP更像是一個無意識的行為,只要這些條件滿足就自動返回一個ACK DLLP。而返回NAK DLLP目的性更加明顯,一旦需要返回NAK DLLP時,只需要置位NAK_SCHEDULED就馬上返回。「NAK的立即發送」可以隨時打斷「 ACK的等待條件滿足再發送」,而不會產生任何問題。

到這裡我們對ACK/NAK協議介紹已經結束了。


下面我們介紹更多發生錯誤的實例,來體現ACK/NAK協議保證TLP的正確傳輸。

實例1:傳輸途中丟失TLP。

圖6 丟失TLP

  1. Device A按順序發送發送TLP 4094、4095、0、1、2.
  2. Device B接收到TLP 4094、4095、0。並返回ACK 0。並將這些TLP傳遞給TL層。同時遞增NEXT_RCV_SEQ至1。
  3. Device A接收到ACK 0,清楚Buffer中的TLP 4094、4095、0。
  4. TLP 1在傳輸途中丟失。
  5. TLP 2到達Device B。Device B對比TLP 2的Sequence Number和NEXT_RCV_SEQ發現 TLP 2的Sequence Number較大。說明TLP存在丟失。
  6. Device B丟棄TLP 2。返回NAK 0(NEXT_RCV_SEQ-1)。
  7. Device A接收到NAK 0。再次發送TLP 1、2。
  8. TLP 1、2被Device B正確的接收,並傳遞給TL層。

實例2:傳輸途中丟失ACK DLLP

圖7 丟失ACK DLLP

  1. Device A按順序發送TLP 4094、4095、0、1、2。
  2. Device B接收到TLP 4094、4095、0。並返回ACK 0。並將這些TLP傳遞給TL層。同時遞增NEXT_RCV_SEQ至1。
  3. ACK 0丟失。TLP 4094、4095、0仍舊保存在Buffer中。
  4. TLP 1、2緊跟到達Device B,並被成功接收。NEXT_RCV_SEQ增加至3。
  5. Device B返回ACK 2,並將TLP 1、2傳遞給TL層。
  6. ACK 2到達Device A。
  7. Device A清除Buffer中的TLP 4094、4095、0、1、2。

在這個事例中,如果ACK 0存在CRC錯誤,後續流程也是一樣的。

如果返回的ACK 2也丟失或者存在CRC錯誤,那麼後續將不會再有ACK/NAK DLLP返回,那麼Device A的REPLAY_TIMER將會溢出,導致Device A再次發送Buffer中的TLP。最終Device B將它們全部接收,並返回ACK 2。整個傳輸任務成功完成。

實例3:傳輸途中丟失ACK DLLP

圖8 丟失ACK DLLP

  1. Device A按順序發送TLP 4094、4095、0、1、2。
  2. Device B接收到TLP 4094、4095、0。並返回ACK 0。並將這些TLP傳遞給TL層。同時遞增NEXT_RCV_SEQ至1。
  3. ACK 0丟失。TLP 4094、4095、0仍舊保存在Buffer中。
  4. TLP 1、2緊跟到達Device B。TLP 1並被成功接收,NEXT_RCV_SEQ增加至2。TLP 1被傳遞至TL層。
  5. TLP 2存在錯誤。NEXT_RCV_SEQ值保持在2。
  6. Device B返回NAK 1,丟棄TLP 2。
  7. NAK 1到達Device A。
  8. Device A清楚TLP 4094、4095、0、1。
  9. Device A再次發送TLP 2。
  10. TLP 2到達Device B,此時NEXT_RCV_SEQ為2。
  11. Device B接收這個正確的TLP 2,並將它傳遞給TL層。NEXT_RCV_SEQ增加至3。
  12. 如果此時ACKNAK_LATENCY_TIMER正好溢出,Device B返回ACK 2。
  13. Device A接收ACK 2,清楚Buffer中的TLP 2。

到此DLL層主要功能介紹結束。


以上內容讀者如果覺得有錯誤之處,請您私信我,我將及時改正。

歡迎轉發,如果有疑惑之處,歡迎評論,我們一起探討。

請勿轉載


推薦閱讀:
查看原文 >>
相关文章