美文网首页FPGA
基于FPGA的PCI总线DMA逻辑

基于FPGA的PCI总线DMA逻辑

作者: hijiang | 来源:发表于2019-07-24 21:36 被阅读0次

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

    相关文章

      网友评论

        本文标题:基于FPGA的PCI总线DMA逻辑

        本文链接:https://www.haomeiwen.com/subject/osnfrctx.html