今天清理电脑,好久以前的VHDL逻辑,感觉以后都不会和FPGA有缘了,传git吧。
现在大部分PCI板卡都是基于PCI9054(日本产的一个芯片),对于一些板卡而言,没必要,第一9054芯片占用板卡面积,第二增加连线,第三9054的DMA速度大概在80-100MB/s,记得它好像每传32个字节要停一下.
虽然现在家用电脑很多都是PCIE了,但是一些老的测控设备,特别是军工使用的,还都是使用PCI槽比较多的主板,所以还是有点用处的。
感兴趣的读者请先读懂《PCI体系结构》这本书,这本书讲得很多,但是主要的章节可能就几百页。
本VHDL逻辑经过测试,通用逻辑支持ALTERA或者XILINX器件,是32位,暂时不支持64位。
- 32位PIO:支持
- 32位DMA读:支持
- 32位DMA写:支持
- DMA速率:111MB/s
- 链式DMA:支持
全时序逻辑设计,没有组合逻辑,所以在各种FPGA芯片上基本没有时序差异的问题。
github地址:https://github.com/jbl19860422/pci_logic.git
简单说明,最好是参考《PCI体系结构》来设计,有个时序设计软件叫timeXXX,可以比较方便的来设计时序。
1、状态机
-- 空闲状态
CONSTANT IDLE_NOR:STD_LOGIC_VECTOR(1 DOWNTO 0):="00";
-- 繁忙状态
CONSTANT BUSY:STD_LOGIC_VECTOR(1 DOWNTO 0):="01";
-- 数据阶段
CONSTANT SDATA:STD_LOGIC_VECTOR(1 DOWNTO 0):="10";
-- 总线切换周期时处于这个状态
CONSTANT TURNAROUND:STD_LOGIC_VECTOR(1 DOWNTO 0):="11";
PROCESS(RST,CLK)
BEGIN
IF RST = '0' THEN
STATUS <= IDLE_NOR;
ELSIF CLK'EVENT AND CLK = '1' THEN
CASE STATUS IS
WHEN IDLE_NOR =>
IF FRAME = '0' THEN --FRAME下降沿,开始周期
STATUS <= BUSY;
END IF;
WHEN BUSY => --根据译码结果,判断处于什么周期
IF ConfCS = '1' OR Bar0_CS = '1' OR Bar1_CS = '1' THEN
STATUS <= SDATA;
ELSIF FRAME = '1' AND IRDY = '1' THEN --这里一般是DMA完成之类的,记不清了
STATUS <= IDLE_NOR;
END IF;
WHEN SDATA =>
IF IRDY = '0' AND TRDY = '0' AND FRAME = '1' THEN
STATUS <= TURNAROUND;
END IF;
WHEN TURNAROUND =>
IF FRAME = '1' THEN
STATUS <= IDLE_NOR;
END IF;
END CASE;
END IF;
END PROCESS;
配置空间选中的译码逻辑:
PROCESS(RST, Clk)
BEGIN
IF RST = '0' THEN
ConfCS <= '0';
ELSIF Clk'EVENT AND Clk = '1' THEN--记得CBE在IDSEL='1'时,是命令,3到1为101可能是读也可能是写
IF FRAME = '0' AND STATUS = IDLE_NOR AND IDSEL = '1' AND AD(1 DOWNTO 0) = "00" AND CBE(3 DOWNTO 1) = "101" THEN
ConfCS <= '1';
ELSIF TRDY = '0' AND IRDY = '0' THEN
ConfCS <= '0';
END IF;
END IF;
END PROCESS;
数据校验输出逻辑:
PROCESS(RST, Clk)
BEGIN
IF RST = '0' THEN
ParGenData <= (OTHERS => '0');
ELSIF Clk'EVENT AND Clk = '1' THEN
IF IRDY = '0' AND TRDY = '0' THEN
ParGenData <= AD&CBE;
ELSIF Curr_State_DMA = AddrPhase OR (Curr_State_DMA = DataPhase AND DMA_Dir = '1') THEN
ParGenData <= AD&CBE;
END IF;
END IF;
END PROCESS;
PROCESS(RST ,Clk)
BEGIN
IF RST = '0' THEN
ParGen <= '0';
ELSIF Clk'EVENT AND Clk = '1' THEN
IF Curr_State_DMA = AddrPhase THEN
ParGen <= '1';
ELSIF STATUS = SDATA AND IRDY = '0' AND TRDY = '0' AND RW = '0' THEN
ParGen <= '1';
ELSIF Curr_State_DMA = DataPhase AND DMA_Dir = '1' AND IRDY = '0' AND TRDY = '0' THEN
ParGen <= '1';
ELSE
ParGen <= '0';
END IF;
END IF;
END PROCESS;
PROCESS(RST, Clk)
BEGIN
IF RST = '0' THEN
PAR <= 'Z';
ELSIF Clk'EVENT AND Clk = '0' THEN
IF ParGen = '1' THEN
PAR <= ParGenData(0) XOR ParGenData(1) XOR ParGenData(2) XOR ParGenData(3) XOR ParGenData(4) XOR ParGenData(5)
XOR ParGenData(6) XOR ParGenData(7) XOR ParGenData(8) XOR ParGenData(9) XOR ParGenData(10) XOR ParGenData(11)
XOR ParGenData(12) XOR ParGenData(13) XOR ParGenData(14) XOR ParGenData(15) XOR ParGenData(16)
XOR ParGenData(17) XOR ParGenData(18) XOR ParGenData(19) XOR ParGenData(20) XOR ParGenData(21)
XOR ParGenData(22) XOR ParGenData(23) XOR ParGenData(24) XOR ParGenData(25) XOR ParGenData(26)
XOR ParGenData(27) XOR ParGenData(28) XOR ParGenData(29) XOR ParGenData(30) XOR ParGenData(31)
XOR ParGenData(32) XOR ParGenData(33) XOR ParGenData(34) XOR ParGenData(35);
ELSE
PAR <= 'Z';
END IF;
END IF;
END PROCESS;
配置空间读写逻辑,请参考《PCI体系结构》:
------------------------------CONFIG R/W LOGIC--------------------------
PROCESS(RST, Clk)
BEGIN
IF RST = '0' THEN
PCI_Config_Wr <= '0';
ELSIF Clk'EVENT AND Clk = '1' THEN
IF ConfCS = '1' AND IRDY = '0' AND STATUS = SDATA AND RW = '1' THEN
PCI_Config_Wr <= '1';--生成写信号
PCI_Config_WrData <= AD;--锁存数据
ELSE
PCI_Config_Wr <= '0';
END IF;
END IF;
END PROCESS;
PROCESS(RST, Clk)
BEGIN
IF RST = '0' THEN
ConfReg(0) <= X"12341103";
ConfReg(1) <= X"00000007";---Status|Command
ConfReg(2) <= X"00000000";---class code|revision id
ConfReg(3) <= X"00000000";---BIST|HeaderType|LatencyTimer|CacheLine
ConfReg(4) <= X"00000000";--BASE0
ConfReg(5) <= X"00000001";--BASE1
ConfReg(6) <= X"00000000";--BASE2
ConfReg(7) <= X"00000000";--BASE3
ConfReg(8) <= X"00000000";--BASE4
ConfReg(9) <= X"00000000";--BASE5
ConfReg(10) <= X"00000000";--CardBus CIS Pointer
ConfReg(11) <= X"00000000";--SubSystem Device ID|SubSystem Vender ID
ConfReg(12) <= X"00000000";
ConfReg(13) <= X"00000000";
ConfReg(14) <= X"00000000";
ConfReg(15) <= X"00000100";
ELSIF Clk'EVENT AND Clk = '0' THEN
IF PCI_Config_Wr = '1' AND ConfCS = '1' THEN--配置空间写有效时,根据地地址写入对应的配置寄存器
CASE ADDRESS(5 DOWNTO 2) IS
WHEN BaseAddr0 => --对于BAR空间的写,需要按照PCI协议将低位表示大小的部分固定为0
ConfReg(4)(31 DOWNTO 8) <= PCI_Config_WrData(31 DOWNTO 8);
ConfReg(4)(7 DOWNTO 0) <= X"00";
WHEN BaseAddr1 =>
ConfReg(5)(31 DOWNTO 16) <= X"0000";
ConfReg(5)(15 DOWNTO 6) <= PCI_Config_WrData(15 DOWNTO 6);
ConfReg(5)(5 DOWNTO 0) <= "000001";--IO空间
WHEN X"1" =>
IF CBE(0) = '0' THEN
ConfReg(1)(7 DOWNTO 0) <= PCI_Config_WrData(7 DOWNTO 0);
END IF;
IF CBE(1) = '0' THEN
ConfReg(1)(15 DOWNTO 8) <= PCI_Config_WrData(15 DOWNTO 8);
END IF;
IF CBE(2) = '0' THEN
ConfReg(1)(23 DOWNTO 16) <= PCI_Config_WrData(23 DOWNTO 16);
END IF;
IF CBE(3) = '0' THEN
ConfReg(1)(31 DOWNTO 24) <= PCI_Config_WrData(31 DOWNTO 24);
END IF;
WHEN X"3" =>
IF CBE(0) = '0' THEN
ConfReg(3)(7 DOWNTO 0) <= PCI_Config_WrData(7 DOWNTO 0);
END IF;
IF CBE(1) = '0' THEN
ConfReg(3)(15 DOWNTO 8) <= PCI_Config_WrData(15 DOWNTO 8);
END IF;
IF CBE(2) = '0' THEN
ConfReg(3)(23 DOWNTO 16) <= PCI_Config_WrData(23 DOWNTO 16);
END IF;
IF CBE(3) = '0' THEN
ConfReg(3)(31 DOWNTO 24) <= PCI_Config_WrData(31 DOWNTO 24);
END IF;
WHEN X"F" =>
IF CBE(0) = '0' THEN
ConfReg(15)(7 DOWNTO 0) <= PCI_Config_WrData(7 DOWNTO 0);
END IF;
IF CBE(1) = '0' THEN
ConfReg(15)(15 DOWNTO 8) <= PCI_Config_WrData(15 DOWNTO 8);
END IF;
IF CBE(2) = '0' THEN
ConfReg(15)(23 DOWNTO 16) <= PCI_Config_WrData(23 DOWNTO 16);
END IF;
IF CBE(3) = '0' THEN
ConfReg(15)(31 DOWNTO 24) <= PCI_Config_WrData(31 DOWNTO 24);
END IF;
WHEN OTHERS =>
NULL;
END CASE;
END IF;
END IF;
END PROCESS;
----------------------------------config end
PROCESS(Clk)--AD总线驱动逻辑
BEGIN
IF Clk'EVENT AND Clk = '0' THEN
IF RW = '0' AND ConfCS = '1' THEN
AD <= ConfReg(conv_integer(ADDRESS(5 DOWNTO 2)));
ELSIF RW = '0' AND Bar0_CS = '1' THEN--配置空间读取逻辑
CASE ADDRESS(7 DOWNTO 2) IS
WHEN "000000" =>
AD <= X"000000"&UUT_AD9642_SPI1_DataRecv;
WHEN "000001" =>
AD <= X"000000"&UUT_AD9642_SPI2_DataRecv;
WHEN "000010" =>--8
AD <= X"0000000"&IntReg;
WHEN "000011" =>--C
AD <= X"0000000"&IntMask;
WHEN "000101" =>--14
AD <= X"0000000"&"000"&Dma_Start;
WHEN "000110" =>--18
AD <= DMA_DonePCIAddress;
WHEN "000111" =>--1C
AD <= X"00000"&"00"&DMA_DoneCount;
WHEN "001000" =>--20
AD <= X"0000000"&"000"&CH1_EN;
WHEN "001001" =>--24
AD <= X"0000000"&"000"&CH2_EN;
WHEN "001010" =>--28
AD <= X"00000"&"0"&UUT_DmaRd_FIFO1_DataCount;
WHEN "001011" =>--2C
AD <= X"00000"&"0"&UUT_DmaRd_FIFO1_DataCount;
WHEN "001100" =>--30
AD <= X"00000"&"00"&CH1_Thres;
WHEN "001110" =>--34
AD <= X"00000"&"00"&CH2_Thres;
-- WHEN "001100" =>--30
-- AD <= X"000000"&DMAIntCount;
-- WHEN "001101" =>--34
-- AD <= X"000000"&DMAStatus;
-- WHEN "001111" =>--3C
-- AD <= X"000000"&DMACmd;
WHEN OTHERS =>
AD <= X"00000000";
END CASE;
--这里是BAR1的PIO读取逻辑,按照这种格式添加完寄存器后,这里扩展就行
ELSIF RW = '0' AND Bar1_CS = '1' THEN
CASE ADDRESS(5 DOWNTO 2) IS
WHEN "0000" =>
AD <= Bar1TestReg1;
WHEN OTHERS =>
AD <= X"00000000";
END CASE;
--DMA的输出,此时输出DMA的物理地址
ELSIF Curr_State_DMA = AddrPhase THEN
AD <= DMA_PCIAddress;
--DMA的数据阶段,如果是DMA读,则输出DMA数据,DMA数据存在FIFO中,也可以存在DDR等,需要自己再稍微适配下
ELSIF Curr_State_DMA = DataPhase AND DMA_Dir = '1' THEN
AD <= X"0000"&"00"&UUT_DmaRd_FIFO1_Dout;
ELSE
AD <= "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
END IF;
END IF;
END PROCESS;
BAR0译码逻辑【其他BAR空间译码,参考这个一样写即可】:
------------------------BAR0 R/W LOGIC-------------------------------------
PROCESS(RST, Clk)
BEGIN
IF RST = '0' THEN
Bar0_CS <= '0';
ELSIF Clk'EVENT AND Clk = '1' THEN
IF STATUS = IDLE_NOR AND FRAME = '0' AND AD(31 DOWNTO 8) = ConfReg(4)(31 DOWNTO 8) AND CBE(3 DOWNTO 1) = "011" THEN
Bar0_CS <= '1';
ELSIF IRDY = '0' AND TRDY = '0' THEN
Bar0_CS <= '0';
END IF;
END IF;
END PROCESS;
PROCESS(RST, Clk)
BEGIN
IF RST = '0' THEN
Bar0_Wr <= '0';
ELSIF Clk'EVENT AND Clk = '1' THEN
IF Bar0_CS = '1' AND IRDY = '0' AND STATUS = SDATA AND RW = '1' THEN
Bar0_Wr <= '1';--BAR0
Bar0_WrData <= AD;
ELSE
Bar0_Wr <= '0';
END IF;
END IF;
END PROCESS;
BAR空间的PIO写,其他BAR参考这里编写即可:
----------------------BAR0 WRITE LOGIC-----------------------------------
PROCESS(RST, Clk)---------------REGISTER WRITE
BEGIN
IF RST = '0' THEN
Bar0TestReg <= (OTHERS => '0');
IntReg_Clr <= (OTHERS => '0');
IntMask <= (OTHERS => '0');
Dma_Start <= '0';
CH1_EN <= '0';
CH2_EN <= '0';
UUT_AD9642_SPI1_Start <= '0';
UUT_AD9642_SPI2_Start <= '0';
UUT_DmaDesc_FIFO_Rst <= '0';
ELSIF Clk'EVENT AND Clk = '0' THEN
IF Bar0_CS = '1' AND Bar0_Wr = '1' THEN
CASE ADDRESS(7 DOWNTO 2) IS
WHEN "000000" =>--0
UUT_AD9642_SPI1_RW <= AD(23);
UUT_AD9642_SPI1_ADDR <= AD(20 DOWNTO 8);
UUT_AD9642_SPI1_DataSend <= AD(7 DOWNTO 0);
UUT_AD9642_SPI1_Start <= '1';
WHEN "000001" =>--4
UUT_AD9642_SPI2_RW <= AD(23);
UUT_AD9642_SPI2_ADDR <= AD(20 DOWNTO 8);
UUT_AD9642_SPI2_DataSend <= AD(7 DOWNTO 0);
UUT_AD9642_SPI2_Start <= '1';
WHEN "000010" =>--8
IntReg_Clr <= AD(3 DOWNTO 0);
WHEN "000011" =>--C
IntMask <= AD(3 DOWNTO 0);
WHEN "000101" =>--14
Dma_Start <= AD(0);
WHEN "001100" =>--30
CH1_EN <= AD(0);
WHEN "001101" =>--34
CH2_EN <= AD(0);
WHEN "010000" =>--40
UUT_DmaDesc_FIFO_Rst <= '0';
WHEN OTHERS => NULL;
END CASE;
ELSE
UUT_AD9642_SPI1_Start <= '0';
UUT_AD9642_SPI2_Start <= '0';
IntReg_Clr <= (OTHERS => '0');
UUT_DmaDesc_FIFO_Rst <= '0';
END IF;
END IF;
END PROCESS;
DMA总处理状态机:
PROCESS(RST, Clk)
BEGIN
IF RST = '0' THEN
Curr_State_Dma <= Idle;
ELSIF Clk'EVENT AND Clk = '1' THEN
CASE Curr_State_DMA IS
WHEN Idle =>
-- 每次DMA,应用程序通过驱动,使用PIO往DMA描述符FIFO中写入物理内存地址及数量
--DMA_START:通过PIO写入,描述符FIFO非空,且有偶数个数据,可以开始启动DMA处理逻辑
IF Dma_Start = '1' AND UUT_DmaDesc_FIFO_Empty = '0' AND UUT_DmaDesc_FIFO_DataCount(0) = '0' THEN
Curr_State_DMA <= ReadPCIAddr;
END IF;
-- 从FIFO读取DMA的物理地址
WHEN ReadPCIAddr =>
IF DmaPCIAddress_Rdy = '1' THEN
Curr_State_DMA <= ReadTransCount;
END IF;
-- 从FIFO读取DMA的字节数
WHEN ReadTransCount =>
IF DmaCount_Rdy = '1' THEN
Curr_State_DMA <= ReqPCIBus;
END IF;
-- 请求获取PCI总线控制权
WHEN ReqPCIBus =>
IF nGNT = '0' AND FRAME = '1' AND IRDY = '1' THEN
Curr_State_DMA <= AddrPhase;
END IF;
-- 如果是DMA读,可以直接读
-- 如果是DMA写,则需要转换总线输入输出状态,需要一个转换周期
WHEN AddrPhase =>
IF DMA_Dir = '0' THEN
Curr_State_DMA <= DataPhase;
ELSE
Curr_State_DMA <= TurnAroundPhase;
END IF;
WHEN TurnAroundPhase =>
Curr_State_DMA <= DataPhase;
-- 数据阶段,这里Devsel_counter的判断,参考《pci体系结构》,忘了哪章了
WHEN DataPhase =>
IF STOP = '0' OR Devsel_Counter = 6 OR DMA_Count = 0 THEN -----
Curr_State_DMA <= EndPhase;
END IF;
WHEN EndPhase =>
IF DMA_Count /= 0 THEN
Curr_State_DMA <= ReqPCIBus;
ELSE
Curr_State_DMA <= Idle;
END IF;
WHEN OTHERS => NULL;
END CASE;
END IF;
END PROCESS;
DMA数据处理逻辑:
PROCESS(RST, CLK)
BEGIN
IF RST = '0' THEN
DMA_Count <= (OTHERS => '0');
DMA_Channel <= '0';
ELSIF CLK'EVENT AND CLK = '1' THEN
IF DmaCount_Rdy = '1' AND Curr_State_DMA = ReadTransCount THEN
DMA_Channel <= UUT_DmaDesc_FIFO_Dout(31);--第31位表示通道,目前支持两通道
DMA_Count <= UUT_DmaDesc_FIFO_Dout(9 DOWNTO 0);
DMA_DoneCount <= UUT_DmaDesc_FIFO_Dout(9 DOWNTO 0);--这时先读出初始数量
ELSIF Curr_State_DMA = DataPhase THEN
IF TRDY = '0' AND IRDY = '0' THEN
DMA_Count <= DMA_Count - 1;--每完成一次交易,请求DMA数量减1,到0表示完成
END IF;
END IF;
END IF;
END PROCESS;
PROCESS(RST, CLK)
BEGIN
IF RST = '0' THEN
DMA_PCIAddress <= (OTHERS => '0');
DMA_Dir <= '0';
ELSIF CLK'EVENT AND CLK = '1' THEN
IF DmaPCIAddress_Rdy = '1' AND Curr_State_DMA = ReadPCIAddr THEN
DMA_PCIAddress <= UUT_DmaDesc_FIFO_Dout(31 DOWNTO 2)&"00";
DMA_DonePCIAddress <= UUT_DmaDesc_FIFO_Dout(31 DOWNTO 2)&"00";
DMA_Dir <= UUT_DMADesc_FIFO_Dout(0);--第0位表示方向
DMAIntFlag <= UUT_DMADesc_FIFO_Dout(1);-第1位表示DMA完成是否完成时,给PC中断,一般都会要吧
ELSIF Curr_State_DMA = DataPhase THEN
IF IRDY = '0' AND TRDY = '0' THEN--每完成一次交易DMA地址+1
DMA_PCIAddress(31 DOWNTO 2) <= DMA_PCIAddress(31 DOWNTO 2) + 1;
END IF;
END IF;
END IF;
END PROCESS;
还有一些DMA中断产生逻辑,中断屏蔽逻辑,至于PCI驱动,按照寄存器分配来编写即可,后面有时间再整理上传一份,可能也需要再调试下。
网友评论