新聞中心

FPGA:PCI項(xiàng)目

作者: 時(shí)間:2024-01-09 來(lái)源:EEPW編譯 收藏

是功能強(qiáng)大的 PCI 開(kāi)發(fā)平

本文引用地址:http://m.butianyuan.cn/article/202401/454595.htm

PCI 0 - 簡(jiǎn)單的

這是 PCI 代碼的一個(gè)示例。 我們使用 PCI 寫入命令來(lái)控制 LED。 寫“0”可關(guān)閉 LED,寫“1”可打開(kāi) LED!

臺(tái),這要?dú)w功于其可重新編程性和運(yùn)行速度。

// Very simple PCI target

// Just 3 flip-flops for the PCI logic, plus one to hold the state of an LED

module PCI(CLK, RSTn, FRAMEn, AD, CBE, IRDYn, TRDYn, DEVSELn, LED);

input CLK, RSTn, FRAMEn, IRDYn;

input [31:0] AD;

input [3:0] CBE;

inout TRDYn, DEVSELn;

output LED;

parameter IO_address = 32'h00000200;   // we respond to an "IO write" at this address

parameter CBECD_IOWrite = 4'b0011;

////////////////////////////////////////////////////

reg Transaction;

wire TransactionStart = ~Transaction & ~FRAMEn;

wire TransactionEnd = Transaction & FRAMEn & IRDYn;

wire Targeted = TransactionStart & (AD==IO_address) & (CBE==CBECD_IOWrite);

wire LastDataTransfer = FRAMEn & ~IRDYn & ~TRDYn;

always @(posedge CLK or negedge RSTn)

if(~RSTn) Transaction <= 0;

else

case(Transaction)

  1'b0: Transaction <= TransactionStart;

  1'b1: Transaction <= ~TransactionEnd;

endcase

reg DevSelOE;

always @(posedge CLK or negedge RSTn)

if(~RSTn) DevSelOE <= 0;

else

case(Transaction)

  1'b0: DevSelOE <= Targeted;

  1'b1: if(TransactionEnd) DevSelOE <= 1'b0;

endcase

reg DevSel;

always @(posedge CLK or negedge RSTn)

if(~RSTn) DevSel <= 0;

else

case(Transaction)

  1'b0: DevSel <= Targeted;

  1'b1: DevSel <= DevSel & ~LastDataTransfer;

endcase

assign DEVSELn = DevSelOE ? ~DevSel : 1'bZ;

assign TRDYn = DevSelOE ? ~DevSel : 1'bZ;

wire DataTransfer = DevSel & ~IRDYn & ~TRDYn;

reg LED; always @(posedge CLK) if(DataTransfer) LED <= AD[0];

endmodule

PCI 1 - PCI 的工作原理

我們?cè)谶@里專注于 PCI 2.2 32 位,這是當(dāng)今 PC
中使用的,較新的 PCI 版本包括 PCI 2.3 和 PCI 3.0。

PCI 規(guī)范

PCI 由一個(gè)名為 PCI 特別興趣小組(簡(jiǎn)稱 PCI-SIG)的小組開(kāi)發(fā)和維護(hù)。
與以太網(wǎng)規(guī)范不同,PCI規(guī)范不能免費(fèi)下載。 您需要成為 PCI-SIG 的成員才能訪問(wèn)該規(guī)范。 由于成為會(huì)員的費(fèi)用很高,您可能需要檢查您公司的硬件組(假設(shè)您在半導(dǎo)體行業(yè)工作),看看您是否可以訪問(wèn)該規(guī)范。

否則,這里有一個(gè)簡(jiǎn)短的介紹,然后是一些鏈接以獲取更多信息。

PCI特性

PCI總線有4個(gè)主要特點(diǎn):
  • 同步

  • 面向事務(wù)/突發(fā)

  • 總線母帶

  • 即插即用

PCI 是同步的
PCI 總線使用一個(gè)時(shí)鐘。 默認(rèn)情況下,時(shí)鐘以 33MHz 運(yùn)行,但可以運(yùn)行得更低(一直到空閑 = 0MHz)以節(jié)省功耗,如果您的硬件支持,也可以運(yùn)行更高 (66MHz)。
PCI 面向事務(wù)/突發(fā)
PCI是面向事務(wù)的。
  1. 您開(kāi)始交易

  2. 指定起始地址(一個(gè)時(shí)鐘周期)

  3. 您可以根據(jù)需要發(fā)送任意數(shù)量的數(shù)據(jù)(許多后續(xù)時(shí)鐘周期)

  4. 您結(jié)束交易

PCI 是 32 位總線,因此有 32 條線來(lái)傳輸數(shù)據(jù)。 在事務(wù)開(kāi)始時(shí),總線用于指定 32 位地址。 一旦指定了地址,就可以經(jīng)歷許多數(shù)據(jù)周期。該地址不會(huì)重新傳輸,而是在每個(gè)數(shù)據(jù)周期自動(dòng)遞增。 若要指定其他地址,將停止事務(wù),并啟動(dòng)新事務(wù)。 因此,PCI帶寬在突發(fā)模式下得到充分利用。
PCI 允許總線主控
PCI 事務(wù)在主從關(guān)系中工作。 主服務(wù)器是啟動(dòng)事務(wù)(可以是讀取或?qū)懭耄┑拇怼?br/>雖然主機(jī) CPU 通常是總線主控器,但所有 PCI 板卡都可能聲明總線并成為總線主站。
PCI是即插即用的
PCI板是即插即用的。這意味著 host-CPU/host-OS 可以:
  • 確定PCI總線中每個(gè)PCI板卡的標(biāo)識(shí)(制造商和功能(視頻,網(wǎng)絡(luò)...))

  • 確定每個(gè)板卡的能力/要求(需要多少內(nèi)存空間,多少個(gè)中斷......

  • 重新定位每個(gè)主板內(nèi)存空間

最后一個(gè)功能是即插即用的重要組成部分。 每塊板都響應(yīng)一些地址,但可以對(duì)它響應(yīng)的地址進(jìn)行編程(即每塊板生成自己的板/片選信號(hào))。 這允許操作系統(tǒng)將每個(gè)板的地址空間“映射”到他想要的位置。
PCI“空間”
PCI 定義了 3 個(gè)“空間”,您可以在其中讀取和寫入。
當(dāng)事務(wù)開(kāi)始時(shí),主節(jié)點(diǎn)指定事務(wù)的起始地址,是讀還是寫,以及他要與哪個(gè)空間通信。

  1. 內(nèi)存空間

  2. IO 空間

  3. 配置空間

它們的工作原理如下:
  • 內(nèi)存和 IO 空間是主力空間。 它們是“可重新定位的”(即每個(gè)板響應(yīng)的地址可以移動(dòng))。

  • 配置空間用于即插即用。 在這個(gè)空間中,每個(gè)板都必須在非常特定的地址實(shí)現(xiàn)非常特定的寄存器,以便主機(jī) CPU/OS 可以弄清楚每個(gè)板的身份/能力/要求是什么。 從那里,主機(jī) CPU/OS 啟用并配置其他兩個(gè)空間。
    此空間是固定的,并且始終從所有 PCI 板的地址 0 開(kāi)始;因此,PCI連接器的一行用作板選擇(僅適用于此空間)。

為了符合要求,PCI板需要實(shí)現(xiàn)配置空間。 內(nèi)存和 IO 空間是可選的,但在實(shí)踐中始終使用一個(gè)或兩個(gè)。

PCI橋接器

PCI 設(shè)備不直接連接到主機(jī) CPU,而是通過(guò)“橋接”芯片。
這是因?yàn)?CPU 通常不會(huì)本地“說(shuō)”PCI,因此橋接器必須將事務(wù)從 CPU 總線轉(zhuǎn)換為 PCI 總線。 此外,CPU 永遠(yuǎn)不會(huì)像 PCI 設(shè)備那樣有 3 個(gè)內(nèi)存空間。 大多數(shù) CPU 有 1 個(gè)空間(內(nèi)存空間),而其他 CPU 有 2 個(gè)空間(內(nèi)存和 IO)。 橋接器必須玩一些技巧,以便 CPU 仍然可以訪問(wèn)所有 3 個(gè) PCI 空間。

PCI電壓

PCI板可以使用3.3V或5V信號(hào)。 有趣的是,目前的 PC 都使用 5V 信號(hào)。
PCI 板連接器有一個(gè)或兩個(gè)插槽,用于識(shí)別板是符合 3.3V 還是 5V 標(biāo)準(zhǔn)。 例如,這是為了確保僅 3.3V 的電路板無(wú)法插入 PC 的僅 5V PCI 總線。

下面以純 5V 板為例: 雖然該板同時(shí)兼容 5V 和 3.3V:



PCI 時(shí)序

PCI 指定與其時(shí)鐘相關(guān)的時(shí)序。
使用33MHz時(shí)鐘,我們有:
  • 輸入端7ns/0ns Tsu/Th(建立/保持)約束

  • 輸出端 11ns Tco(時(shí)鐘至輸出)

PCI 2 - PCI 讀寫

現(xiàn)在讓我們做一些真正的PCI交易......

IO 事務(wù)

最容易使用的 PCI 空間是 IO 空間。
  • 沒(méi)有來(lái)自 CPU/OS 的虛擬化(即 CPU 地址 = 硬件地址)

  • 不需要驅(qū)動(dòng)程序(在 Win98/Me 上為 true,而在 Win XP/2K 上,需要驅(qū)動(dòng)程序,但下面提供了通用驅(qū)動(dòng)程序)

IO 空間的缺點(diǎn)是它很?。ㄔ?PC 上限制為 64KB,即使 PCI 支持 4GB)并且非常擁擠。

查找可用空間

在 Windows 98/Me 上,打開(kāi)“設(shè)備管理器”(從“控制面板”/系統(tǒng)),然后顯示計(jì)算機(jī)/屬性并檢查“輸入/輸出 (I/O)”面板。

在 Windows XP/2000 上,打開(kāi)“系統(tǒng)信息”程序(程序/附件/系統(tǒng)工具/系統(tǒng)信息),然后單擊“I/O”。

許多外圍設(shè)備都在使用 IO 空間,因此自由空間候選人需要進(jìn)行一些研究。

驅(qū)動(dòng)程序

在 Win98/Me 上,IO 空間不受保護(hù),因此不需要驅(qū)動(dòng)程序。
對(duì)于 WinXP/2K,GiveIO 和 UserPort 是開(kāi)放 IO 空間的免費(fèi)通用驅(qū)動(dòng)程序。

RAM PCI卡

讓我們?cè)赑CI卡中實(shí)現(xiàn)一個(gè)小的RAM。

RAM 為 32 位 x 16 個(gè)位置。 它足夠小,可以使用“直接尋址”(IO 空間非常擁擠,否則需要間接尋址)。
我們需要在主機(jī) PC 中選擇一個(gè)空閑的 IO 空間。 每個(gè) 32 位位置需要 4 個(gè)字節(jié)地址,因此我們需要 4x16=64 個(gè)連續(xù)的可用地址。 我們?cè)谶@里選擇了 0x200-0x23F,但您可能需要選擇其他東西。

首先是模塊聲明。

module PCI_RAM( PCI_CLK, PCI_RSTn, PCI_FRAMEn, PCI_AD, PCI_CBE, PCI_IRDYn, PCI_TRDYn, PCI_DEVSELn );
input PCI_CLK, PCI_RSTn, PCI_FRAMEn, PCI_IRDYn;
inout [31:0] PCI_AD;
input [3:0] PCI_CBE;
output PCI_TRDYn, PCI_DEVSELn;

parameter IO_address = 32'h00000200;   // 0x0200 to 0x23F
parameter PCI_CBECD_IORead = 4'b0010;
parameter PCI_CBECD_IOWrite = 4'b0011;

然后,我們通過(guò)“PCI_Transaction”寄存器跟蹤公交車上發(fā)生的事情。
“PCI_Transaction”在進(jìn)行任何交易時(shí)被斷言,無(wú)論是對(duì)我們,還是對(duì)公共汽車上的任何其他卡。

reg PCI_Transaction;

wire PCI_TransactionStart = ~PCI_Transaction & ~PCI_FRAMEn;
wire PCI_TransactionEnd = PCI_Transaction & PCI_FRAMEn & PCI_IRDYn;

always @(posedge PCI_CLK or negedge PCI_RSTn)
if(~PCI_RSTn) PCI_Transaction <= 0;
else
case(PCI_Transaction)
  1'b0: PCI_Transaction <= PCI_TransactionStart;
  1'b1: PCI_Transaction <= ~PCI_TransactionEnd;
endcase

// We respond only to IO reads/writes, 32-bits aligned
wire PCI_Targeted = PCI_TransactionStart & (PCI_AD[31:6]==(IO_address>>6)) & (PCI_AD[1:0]==0) & ((PCI_CBE==PCI_CBECD_IORead) | (PCI_CBE==PCI_CBECD_IOWrite));

// When a transaction starts, the address is available for us to register
// We just need a 4 bits address here
reg [3:0] PCI_TransactionAddr;
always @(posedge PCI_CLK) if(PCI_TransactionStart) PCI_TransactionAddr <= PCI_AD[5:2];

現(xiàn)在,再增加幾個(gè)寄存器,以便能夠聲明交易并記住它是讀取還是寫入

wire PCI_LastDataTransfer = PCI_FRAMEn & ~PCI_IRDYn & ~PCI_TRDYn;

// Is it a read or a write?
reg PCI_Transaction_Read_nWrite;
always @(posedge PCI_CLK or negedge PCI_RSTn)
if(~PCI_RSTn) PCI_Transaction_Read_nWrite <= 0;
else
if(~PCI_Transaction & PCI_Targeted) PCI_Transaction_Read_nWrite <= ~PCI_CBE[0];

// Should we claim the transaction?
reg PCI_DevSelOE;
always @(posedge PCI_CLK or negedge PCI_RSTn)
if(~PCI_RSTn) PCI_DevSelOE <= 0;
else
case(PCI_Transaction)
  1'b0: PCI_DevSelOE <= PCI_Targeted;
  1'b1: if(PCI_TransactionEnd) PCI_DevSelOE <= 1'b0;
endcase

讓我們認(rèn)領(lǐng)交易。

// PCI_DEVSELn should be asserted up to the last data transfer
reg PCI_DevSel;
always @(posedge PCI_CLK or negedge PCI_RSTn)
if(~PCI_RSTn) PCI_DevSel <= 0;
else
case(PCI_Transaction)
  1'b0: PCI_DevSel <= PCI_Targeted;
  1'b1: PCI_DevSel <= PCI_DevSel & ~PCI_LastDataTransfer;
endcase

最后,RAM本身被寫入或讀取,PCI_AD總線相應(yīng)地驅(qū)動(dòng)。

// PCI_TRDYn is asserted during the whole PCI_Transaction because we don't need wait-states
// For read transaction, delay by one clock to allow for the turnaround-cycle
reg PCI_TargetReady;
always @(posedge PCI_CLK or negedge PCI_RSTn)
if(~PCI_RSTn) PCI_TargetReady <= 0;
else
case(PCI_Transaction)
  1'b0: PCI_TargetReady <= PCI_Targeted & PCI_CBE[0]; // active now on write, next cycle on reads
  1'b1: PCI_TargetReady <= PCI_DevSel & ~PCI_LastDataTransfer;
endcase

// Claim the PCI_Transaction
assign PCI_DEVSELn = PCI_DevSelOE ? ~PCI_DevSel : 1'bZ;
assign PCI_TRDYn = PCI_DevSelOE ? ~PCI_TargetReady : 1'bZ;


wire PCI_DataTransferWrite = PCI_DevSel & ~PCI_Transaction_Read_nWrite & ~PCI_IRDYn & ~PCI_TRDYn;

// Instantiate the RAM
// We use Xilinx's synthesis here (XST), which supports automatic RAM recognition
// The following code creates a distributed RAM, but a blockram could also be used (we have an extra clock cycle to get the data out)
reg [31:0] RAM [15:0];
always @(posedge PCI_CLK) if(PCI_DataTransferWrite) RAM[PCI_TransactionAddr] <= PCI_AD;

// Drive the AD bus on reads only, and allow for the turnaround cycle
reg PCI_AD_OE;
always @(posedge PCI_CLK or negedge PCI_RSTn)
if(~PCI_RSTn) PCI_AD_OE <= 0;
else
  PCI_AD_OE <= PCI_DevSel & PCI_Transaction_Read_nWrite & ~PCI_LastDataTransfer;

// Now we can drive the PCI_AD bus
assign PCI_AD = PCI_AD_OE ? RAM[PCI_TransactionAddr] : 32'hZZZZZZZZ;

endmodule

現(xiàn)在我們可以讀寫PCI卡了!

設(shè)計(jì)注意事項(xiàng)
  1. 不使用 PCI_CBE 字節(jié)啟用,因此軟件應(yīng)該只發(fā)出 32 位交易,對(duì)齊。

  2. 您可能會(huì)驚訝地發(fā)現(xiàn),PCI“PAR”信號(hào)(總線奇偶校驗(yàn))也沒(méi)有使用。
    雖然 PAR 生成是 PCI 合規(guī)性所必需的,但它的檢查可能不是因?yàn)槲铱梢栽L問(wèn)的 PC 在沒(méi)有它的情況下工作正常...... 由于我無(wú)法在真實(shí)硬件中測(cè)試它,所以我省略了它。

  3. 上面的代碼支持突發(fā)傳輸,但當(dāng)前的 PC 網(wǎng)橋似乎不會(huì)發(fā)出突發(fā)(至少對(duì)于 IO 空間)。 x86 處理器支持突發(fā) IO 指令 (REP IN/OUTS),但它們最終被分解為 PCI 總線上的單個(gè)事務(wù)。 此外,我不確定突發(fā) IO 是否需要自動(dòng)遞增 IO 地址,特別是因?yàn)?REP INS/OUTS 指令不需要。
    但是,由于不遞增對(duì)時(shí)間有很好的影響(更多細(xì)節(jié)見(jiàn)下文),因此我以這種方式保留了代碼。

發(fā)出 IO 讀/寫事務(wù)

在 PC 上,使用 x8086“IN”和“OUT”處理器指令發(fā)出 IO 事務(wù)。
某些編譯器沒(méi)有對(duì)這些函數(shù)的本機(jī)支持,因此您可能必須使用內(nèi)聯(lián)匯編程序函數(shù)。下面是 Visual C++ 的示例:

void WriteIO_DWORD(WORD addr, DWORD data)
{
  __asm
  {
    mov dx, addr
    mov eax, data
    out dx, eax
  }
}

DWORD ReadIO_DWORD(WORD addr)
{
  __asm
  {
    mov dx, addr
    in eax, dx

  }
}

GUI PCI IO 訓(xùn)練器軟件

您可以使用這個(gè)簡(jiǎn)單的 IOtest 應(yīng)用程序在 PC 上發(fā)出 32 位 IO 讀取和寫入。
這直接適用于 Win98/Me。 確保 GiveIO 或 UserPort 在 WinXP/2K 上運(yùn)行。 有一點(diǎn)很重要:可用空間在讀取時(shí)返回0xFFFFFFFF。

時(shí)序注意事項(xiàng)

請(qǐng)記住,PCI 需要:

  • 輸入端7ns/0ns Tsu/Th(建立/保持)約束

  • 輸出端 11ns Tco(時(shí)鐘至輸出)

大多數(shù)PCI內(nèi)核都非常復(fù)雜,如果不在IO塊中注冊(cè)輸入,就不可能滿足Tsu的要求。 如果不對(duì)輸出做同樣的事情,也很難滿足 TCO。
但這些寄存器會(huì)增加設(shè)計(jì)延遲。 上面的代碼非常簡(jiǎn)單,不需要 IO 塊寄存器。

該代碼使用 Dragon 開(kāi)發(fā)板和 Xilinx 的 ISE 軟件進(jìn)行了測(cè)試。
它給出了類似的東西:

時(shí)序摘要:
---------------

時(shí)序誤差:0 成績(jī):0

設(shè)計(jì)統(tǒng)計(jì): 最小周期:9.667ns(最大頻率:103.445MHz)
時(shí)鐘前最短輸入所需時(shí)間:5.556ns 時(shí)鐘后最短輸出所需時(shí)間:
10.932ns


基本滿足了時(shí)鐘頻率(103MHz對(duì)33MHz)。
Tsu 在 PCI_DEVSELn 和 PCI_TRDYn 信號(hào)上以很大的優(yōu)勢(shì)(5.556ns 對(duì) 7ns)滿足,而 Tco 幾乎沒(méi)有滿足(10.932ns 對(duì) 11ns)。
如果必須在突發(fā)讀取時(shí)自動(dòng)遞增 IO 地址,則 AD 總線上不會(huì)滿足 Tco。 由于地址是靜態(tài)的,并且(僅用于讀取周期)PCI總線在地址階段之后需要一個(gè)周轉(zhuǎn)周期,因此數(shù)據(jù)有一個(gè)額外的時(shí)鐘周期來(lái)準(zhǔn)備。 如果沒(méi)有它,TCO約為13ns,因此高于最大11ns。 但是有了額外的時(shí)鐘周期,我們實(shí)際上以 28ns 的松弛(=余量)來(lái)滿足時(shí)序,這非常舒適。

唯一未滿足的時(shí)序是輸入保持時(shí)間(0nS),希望它足夠低(對(duì)于最嚴(yán)重的違規(guī)者為0.3nS)。 但 Xilinx 不支持限制保持時(shí)間的方法,可能是因?yàn)槭褂?IO 塊寄存器可以“按設(shè)計(jì)”( 的)保證 0ns 保持時(shí)間。


PCI 3 - PCI 邏輯分析儀

現(xiàn)在我們可以在總線上發(fā)出讀寫事務(wù),那么“查看”事務(wù)的實(shí)際情況不是很有趣嗎?

這是用 Dragon 捕獲的一個(gè)非常簡(jiǎn)單的交易。

在地址階段,CBE 0x3,這意味著“IO 寫入”。
它是地址0x00000000的 IO 寫入,數(shù)據(jù)0x0200。

作為 PCI 邏輯分析儀

能夠看到總線運(yùn)行可能很有趣:
  • 更好地了解其操作。

  • 檢查事務(wù)內(nèi)和事務(wù)之間的總線延遲。

  • 進(jìn)行事后分析(如果您的 PCI 內(nèi)核存在功能問(wèn)題)。

查看信號(hào)通常需要昂貴的設(shè)備,如總線擴(kuò)展器和邏輯分析儀。 這可能很棘手,因?yàn)镻CI規(guī)范不允許每個(gè)PCI信號(hào)(當(dāng)然每個(gè)PCI卡)上有一個(gè)以上的IO負(fù)載。 這是因?yàn)榭偩€對(duì)容性負(fù)載或線短截線很敏感,這些負(fù)載或短截線會(huì)使高速信號(hào)失真。

但是,F(xiàn)PGA不能像邏輯分析儀一樣工作嗎?

FPGA已經(jīng)連接到總線,并具有內(nèi)部存儲(chǔ)器,可用于實(shí)時(shí)捕獲總線操作。 Dragon 還有一個(gè) USB 接口,可用于轉(zhuǎn)儲(chǔ) PCI 捕獲,而不會(huì)干擾 PCI 接口實(shí)現(xiàn),即使 PCI 總線“死機(jī)”。

FPGA 還可以輕松創(chuàng)建復(fù)雜的觸發(fā)條件,這些條件將比大多數(shù)邏輯分析儀更智能......如果要在地址 17x0 進(jìn)行第二次讀取后捕獲第 1234 次寫入,該怎么辦?

捕獲 PCI 信號(hào)

我們?cè)谶@里構(gòu)建了一個(gè)“狀態(tài)”(=同步)邏輯分析器。

捕獲的信號(hào)是:

wire [47:0] dsbr = {
  PCI_AD,
  PCI_CBE, PCI_IRDYn, PCI_TRDYn, PCI_FRAMEn, PCI_DEVSELn,
  PCI_IDSEL, PCI_PAR, PCI_GNTn, PCI_LOCKn, PCI_PERRn, PCI_REQn, PCI_SERRn, PCI_STOPn};


只有 48 個(gè)信號(hào)!
很好,如果我們選擇 3 個(gè)時(shí)鐘的深度,則非常適合 256 個(gè)塊。

實(shí)現(xiàn)起來(lái)很簡(jiǎn)單:一旦設(shè)置了觸發(fā)條件,一個(gè) 8 位計(jì)數(shù)器開(kāi)始為模塊提供信號(hào),另一個(gè)計(jì)數(shù)器允許 USB 讀取模塊數(shù)據(jù)。 還添加了邏輯,以允許一定程度的預(yù)觸發(fā)采集 - Dragon 板文件中的詳細(xì)信息。

blockram 輸出按此順序多路復(fù)用至 USB 控制器

case(USB_readaddr[2:0])
  3'h0: USB_Data <= bro[ 7: 0];
  3'h1: USB_Data <= bro[15: 8];
  3'h2: USB_Data <= bro[23:16];
  3'h3: USB_Data <= bro[31:24];
  3'h4: USB_Data <= bro[39:32];
  3'h5: USB_Data <= bro[47:40];
  3'h6: USB_Data <= 8'h01;  // padding, added for ease of implementation
  3'h7: USB_Data <= 8'h02;  // padding, added for ease of implementation
endcase


最后,使用 USB 批量讀取命令,采集數(shù)據(jù)并將其保存到“.pciacq”文件中以供進(jìn)一步分析。
PCI總線查看器
用于查看“.pciacq”文件的軟件可以在這里下載。

包括一個(gè)示例“.pciacq”文件,該文件是此事務(wù)列表的結(jié)果捕獲:

ReadIO_DWORD( 0x200 );
ReadIO_DWORD( 0x204 );
ReadIO_DWORD( 0x208 );
ReadIO_DWORD( 0x210 );
WriteIO_DWORD( 0x204, 0x12345678 );
WriteIO_DWORD( 0x208, 0x87654321 );
WriteIO_DWORD( 0x210, 0xDEADBEEF );
ReadIO_DWORD( 0x200 );
ReadIO_DWORD( 0x204 );
ReadIO_DWORD( 0x208 );
ReadIO_DWORD( 0x210 );


該軟件如下所示: 一件有趣的事情:


在讀取周轉(zhuǎn)周期中,AD 總線顯示上一次讀取的數(shù)據(jù)......

PCI 4 - PCI 即插即用

既然讀寫訪問(wèn)正在進(jìn)行中,那么PCI即插即用需要什么才能工作?



我們的PCI卡還沒(méi)有在列表中...

配置空間

還記得PCI卡有三個(gè)“空間”嗎?
  1. 內(nèi)存空間

  2. IO 空間

  3. 配置空間

配置空間是PCI即插即用的核心。 操作系統(tǒng)(Windows、Linux等)首先讀取該信息,以查找是否插入了PCI卡及其特性。

對(duì)于簡(jiǎn)單的電路板,配置空間僅包含 64 個(gè)字節(jié)。 它們的重要領(lǐng)域是:

抵消名字功能注意長(zhǎng)度
0供應(yīng)商 ID指定生產(chǎn)商...由 PCI-SIG 分配2 字節(jié)
2設(shè)備 ID設(shè)備編號(hào)...由制造商自己分配2 字節(jié)
4命令打開(kāi)和關(guān)閉對(duì)PCI板的訪問(wèn)...但配置空間訪問(wèn)始終處于打開(kāi)狀態(tài)2 字節(jié)
16BAR0(基址寄存器 0)PCI板應(yīng)響應(yīng)的地址...后跟 BAR1 到 BAR5每個(gè) 4 個(gè)字節(jié)

通過(guò)在這些位置實(shí)現(xiàn)正確的值和寄存器,操作系統(tǒng)可以“找到”PCI卡。

配置空間事務(wù)

每個(gè)PCI插槽都作為稱為IDSEL的信號(hào)。 IDSEL 信號(hào)不沿總線共享;每個(gè)PCI插槽都有自己的插槽。
當(dāng) PCI 卡在總線上看到配置空間事務(wù),并且斷言其自己的 IDSEL 時(shí),它知道它應(yīng)該響應(yīng)。


parameter PCI_CBECD_CSRead = 4'b1010;   // configuration space read
parameter PCI_CBECD_CSWrite = 4'b1011;   // configuration space write

wire PCI_Targeted = PCI_TransactionStart & PCI_IDSEL & ((PCI_CBE==PCI_CBECD_CSRead) | (PCI_CBE==PCI_CBECD_CSWrite)) & (PCI_AD[1:0]==0);

之后,它可以是讀取或?qū)懭?,但它的工作方式與內(nèi)存或 IO 空間相同。

一些細(xì)節(jié):
  • 對(duì)于供應(yīng)商 ID,我們只需選擇一個(gè)數(shù)字;我們只是在實(shí)驗(yàn),對(duì)吧?好的,0x0100工作正常。

  • 設(shè)備 ID 可以保留為 0

  • 命令位 0 是 IO 空間的“開(kāi)/關(guān)”位,而位 1 是內(nèi)存空間的“開(kāi)/關(guān)”位。

  • BAR0 是操作系統(tǒng)寫入的寄存器,一旦它決定 PCI 卡應(yīng)該位于哪個(gè)地址。

還有一些其他細(xì)節(jié)被遺漏了,比如 BAR0 的某些部分是只讀的......
請(qǐng)參閱 PCI 規(guī)范/書籍,了解實(shí)際細(xì)節(jié)。

Windows 即插即用

實(shí)現(xiàn)這些寄存器后,操作系統(tǒng)可以發(fā)現(xiàn)新硬件。




但是操作系統(tǒng)需要驅(qū)動(dòng)程序才能...




。它同意分配內(nèi)存資源。


PCI 5 - 適用于 Windows 的 PCI 軟件驅(qū)動(dòng)程序

現(xiàn)在我們需要PCI卡的驅(qū)動(dòng)程序,有兩種方法可以獲取它。

簡(jiǎn)單的方法

簡(jiǎn)單的方法就是讓別人為你做艱苦的工作!

查看 WinDriver。
這是一個(gè)商業(yè)工具包,可以在幾分鐘內(nèi)為您構(gòu)建 PCI 即插即用驅(qū)動(dòng)程序解決方案。

它的工作原理是這樣的:

  • 運(yùn)行一個(gè)向?qū)?lái)檢測(cè)您的即插即用設(shè)備,包括 PCI 卡。

  • 您選擇您感興趣的卡,為您的設(shè)備命名并創(chuàng)建一個(gè)“.inf”文件。

  • 這足以讓 Windows 能夠識(shí)別硬件并說(shuō)服他應(yīng)該使用 WinDriver 的驅(qū)動(dòng)程序。 退出向?qū)?,然后通過(guò) Windows 的即插即用硬件檢測(cè)來(lái)安裝驅(qū)動(dòng)程序。

  • 安裝驅(qū)動(dòng)程序后,再次運(yùn)行向?qū)?,這次是生成一些示例源代碼來(lái)訪問(wèn) PCI 卡。

WinDriver 給您 30 天的試用時(shí)間。
Windriver 可能不錯(cuò),但 2000 美元,如果您只想嘗試 PCI 即插即用機(jī)制,那就太貴了。

艱難的道路

使用 Microsoft Windows DDK。

安裝 Windows DDK
最新的 Windows DDK 版本不是免費(fèi)的,而早期的化身 (98/2000) 可以免費(fèi)下載。
DDK 易于安裝。 對(duì)于 Win98 和 Win2000 DDK,首先安裝 Visual C++ 5.0 或 6.0,然后安裝 DDK 本身。 然后按照“install.htm”說(shuō)明使用“build”命令生成一些示例驅(qū)動(dòng)程序。
最低 WDM 即插即用驅(qū)動(dòng)程序
以下是 Windows 設(shè)備管理器分配 PCI 卡使用的內(nèi)存資源所需的最少代碼。
由于它是一個(gè)WDM驅(qū)動(dòng)程序,所以它可以在WinXP/2000/98中工作。

WDM 驅(qū)動(dòng)程序的入口點(diǎn)是“DriverEntry”函數(shù)(類似于 C 程序的“main”)。
其主要目的是發(fā)布回調(diào)函數(shù)的地址。 我們的最低驅(qū)動(dòng)程序只需要 2 個(gè)。

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
  DriverObject->DriverExtension->AddDevice = DevicePCI_AddDevice;
  DriverObject->MajorFunction[IRP_MJ_PNP] = DevicePCI_PnP;

  return STATUS_SUCCESS;

}

WDM 驅(qū)動(dòng)程序至少創(chuàng)建一個(gè)“設(shè)備”(如果你的電腦有多個(gè)類似的項(xiàng)目,則同一 WDM 驅(qū)動(dòng)程序可能會(huì)創(chuàng)建多個(gè)設(shè)備)。 在驅(qū)動(dòng)程序可以創(chuàng)建設(shè)備之前,我們需要一個(gè)“設(shè)備擴(kuò)展”結(jié)構(gòu)。 每個(gè)設(shè)備都使用該結(jié)構(gòu)來(lái)存儲(chǔ)信息。 我們可以讓它變得盡可能大,一個(gè)典型的設(shè)備會(huì)在其中存儲(chǔ)許多字段。 我們的最小設(shè)備只需要一個(gè)字段。

typedef struct
{
  PDEVICE_OBJECT NextStackDevice;
}
DevicePCI_DEVICE_EXTENSION, *PDevicePCI_DEVICE_EXTENSION;


這個(gè)“NextStackDevice”是干什么用的?WDM實(shí)現(xiàn)細(xì)節(jié)...
WDM 設(shè)備處理 IRP(“I/O 請(qǐng)求數(shù)據(jù)包”、創(chuàng)建/讀取/寫入/關(guān)閉...... WDM 設(shè)備不是單獨(dú)工作的,而是組裝在設(shè)備的邏輯“堆棧”中。 IRP 請(qǐng)求沿堆棧發(fā)送,并在途中進(jìn)行處理。 堆棧是從下到上創(chuàng)建的(底部=硬件層,頂部=邏輯層)。 創(chuàng)建堆棧時(shí),每個(gè)設(shè)備都會(huì)將自身附加到正下方的設(shè)備。 設(shè)備通常將有關(guān)設(shè)備的信息存儲(chǔ)在設(shè)備擴(kuò)展的正下方,以便以后可以轉(zhuǎn)發(fā) IRP 請(qǐng)求。 設(shè)備并不真正知道它在堆棧中的位置,它只是在請(qǐng)求到來(lái)時(shí)處理或轉(zhuǎn)發(fā)請(qǐng)求。

無(wú)論如何,現(xiàn)在我們可以實(shí)現(xiàn)DevicePCI_AddDevice。
它創(chuàng)建一個(gè)設(shè)備對(duì)象,并將設(shè)備附加到設(shè)備堆棧。

NTSTATUS DevicePCI_AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo)
{
  // Create the device and allocate the "Device Extension"
  PDEVICE_OBJECT fdo;
  NTSTATUS status = IoCreateDevice(DriverObject, sizeof(DevicePCI_DEVICE_EXTENSION), NULL, FILE_DEVICE_UNKNOWN, 0, FALSE, &fdo);
  if(!NT_SUCCESS(status)) return status;

  // Attach to the driver below us
  PDevicePCI_DEVICE_EXTENSION dx = (PDevicePCI_DEVICE_EXTENSION)fdo->DeviceExtension;
  dx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, pdo);

  fdo->Flags &= ~DO_DEVICE_INITIALIZING;
  return STATUS_SUCCESS;

}

最后,我們可以處理即插即用 IRP 請(qǐng)求。
我們的最小設(shè)備僅處理START_DEVICE和REMOVE_DEVICE請(qǐng)求。

NTSTATUS DevicePCI_PnP(PDEVICE_OBJECT fdo, PIRP IRP)
{
  PDevicePCI_DEVICE_EXTENSION dx = (PDevicePCI_DEVICE_EXTENSION)fdo->DeviceExtension;
  PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(IRP);
  ULONG MinorFunction = IrpStack->MinorFunction;

  switch(MinorFunction)
  {
  case IRP_MN_START_DEVICE:
    // we should check the allocated resource...
    break;
  case IRP_MN_REMOVE_DEVICE:
    status = IRP_NotCompleted(fdo, IRP);
    if(dx->NextStackDevice) IoDetachDevice(dx->NextStackDevice);
    IoDeleteDevice(fdo);
    break;
  }

  // call the device below us
  IoSkipCurrentIrpStackLocation(IRP);
  return IoCallDriver(dx->NextStackDevice, IRP);

}

START_DEVICE請(qǐng)求是我們接受或拒絕內(nèi)存資源的請(qǐng)求。 在這里,我們什么都不做,只是將請(qǐng)求向下轉(zhuǎn)發(fā)到堆棧中,在那里它總是被接受。



現(xiàn)在,我們的設(shè)備獲得了一些內(nèi)存資源,但對(duì)它們不做任何事情。
為了更有用,驅(qū)動(dòng)程序需要:
  • 在接受內(nèi)存資源之前檢查它們

  • 導(dǎo)出設(shè)備名稱

  • 實(shí)現(xiàn)一些“DeviceIOcontrol”以與 Win32 應(yīng)用程序通信

  • 處理更多 IO 請(qǐng)求 (“IRP”)

  • ...



關(guān)鍵詞: FPGA PCI接口

評(píng)論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉