FPGA

基于FPGA的PCI总线DMA逻辑

2019-07-24  本文已影响0人  hijiang

今天清理电脑,好久以前的VHDL逻辑,感觉以后都不会和FPGA有缘了,传git吧。
现在大部分PCI板卡都是基于PCI9054(日本产的一个芯片),对于一些板卡而言,没必要,第一9054芯片占用板卡面积,第二增加连线,第三9054的DMA速度大概在80-100MB/s,记得它好像每传32个字节要停一下.
虽然现在家用电脑很多都是PCIE了,但是一些老的测控设备,特别是军工使用的,还都是使用PCI槽比较多的主板,所以还是有点用处的。
感兴趣的读者请先读懂《PCI体系结构》这本书,这本书讲得很多,但是主要的章节可能就几百页。
本VHDL逻辑经过测试,通用逻辑支持ALTERA或者XILINX器件,是32位,暂时不支持64位。

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驱动,按照寄存器分配来编写即可,后面有时间再整理上传一份,可能也需要再调试下。

上一篇下一篇

猜你喜欢

热点阅读