Xilinx Vivado FFT IP核使用介绍

配置项说明

打开Vivado的Fast Fourier Transform IP核,左边窗口有三个选项卡,分别是:

  • IP端口:例化时我们需要注意如何连接这些端口
  • 实现细节:实现方式,资源占用估计,数据端口和配置端口的每一位代表什么
  • 延迟:变换所需要的时钟周期,以及特定时钟下需要的时间

右边窗口的三个选项卡,分别是:

  • IP核配置:包括通道数,变换长度,实现架构

  • 实现:包括数据格式,缩放选项,舍入选项,位宽,复位信号,输出顺序,溢出标志位,实时/非实时模式

  • 详细实现:包括数据和蝶形运算因子的RAM实现方式,复数乘法的实现方式,蝶形运算的实现方式等等

接下来我们详细展开介绍。

IP配置

通道数

该选项是用来配置同时进行的FFT运算个数,默认为1,比如设置为2,那左边Implementation Details选项卡下面就可以看到,通道0和通道1的输入数据应该放到哪个位置,这两个通道是相互独立的,最终同时输出两个通道各自的FFT结果。

变换长度

这个就是变换点数,在进行设计时,如果有优化的可能,尽量使用小点数的,点数较大时,资源占用和变换周期都会非线性增加。

时钟频率和吞吐量

该配置与IP核具体实现无关,只影响到架构的选择,而且仅当你选择了“自动配置”。还有就是左边Latency里的变换时间会受这个影响。如果确定了使用某种架构,那这两个可以直接忽视。

架构选择

  • 自动选择:它会根据你输入的“时钟频率核吞吐量”配置项,来自动确定使用哪种架构,就是下面几种

  • 流水线:这种配置下,使用的资源会稍多一些,在每一级运算后都会使用存储单元保存同时送到下一级。它的好处是,连续两段输入数据可以紧挨着输入,而不用等FFT的输出完成。

  • 基4突发IO:使用基4分解的实现架构,所有标有“Burst I/O”的,都不能流水线计算,也就是要等到FFT计算完成,它给出一个标志位之后,你根据这个标志位再输入下一段。

  • 基2突发IO:与基4类似

  • 基2Lite:与基2类似,只是优化掉了很多运算过程,精度上有所损失

实时可配点数

勾选此选项后,可以实时配置FFT的变换点数,具体细节参见左边选项卡的"Implementation Details",在配置通道进行配置。占用资源为最大变换点数的资源。

实现

数据格式

定点数,浮点数。

一般我们都使用定点数。

缩放选项

缩放,不缩放,块浮点。由于FFT每一级的计算有乘法和加法,为了保证精度同时减少溢出情况,提供了这个选项。

选缩放时,需要在配置通道配置缩放因子,代表每一级的缩放,举一个例子。

[01 00 10 11 10] = [1 0 2 3 2]

以2个bits为一组,代表一个缩放倍数,可以缩放的倍数为0,1,2或3,如果是1024点的基4结构,那FFT的级数为\(log_4 1024=5\),对应的缩放因子就是10bits,如果是1024点的基2结构,那级数为\(log2_1024=10\),对应缩放因子就是20bits,如果是4096点的基4结构,那就是\(log_4 4096=6\),对应12bits,其他类似。

1,0,2,3,2分别代表最后一级到第一级的缩放倍数,总的缩放倍数为\(2^{(1+0+2+3+2)}=2^8\),在后面的使用示例中会进一步说明。

选不缩放时,每一级均不缩放,位宽会一直增长,使用时需要注意定点位置来进行截位。

选块浮点时,使用资源会增加,IP核自动确定缩放倍数保证不溢出,同时给出\(/blk\_exp/\),block_exponent,块指数,根据这个值对结果进行缩放。

舍入类型

截断,收敛舍入。

关于收敛舍入的介绍可以参考这一篇文章:CSDN-舍入处理

这个选项我一般是使用收敛舍入。

位宽

输入数据的位宽和蝶形因子的位宽。

注意这个位宽是实部或者虚部的位宽,而不是加起来。比如这里设置16,那总的输入进来的长度就是虚部拼接实部,32bits。

复位

低电平有效同步清零,复位至少持续2个时钟周期。

时钟使能我没有用过,这里给出官方描述,

If the clock enable (aclken) pin is present on the core, driving the pin Low pauses the core in its current state. All logic within the core is paused. Driving the aclken pin High allows the core to continue processing. Note that aclken can reduce the maximum frequency at which the core runs.

输出顺序

自然顺序,逆序

逆序就是我们教科书上介绍8点FFT时那个04261537,就是写成2进制,然后把MSB和LSB的顺序反一下(基2情况下)。

自然顺序就是IP核再给你转成0到N-1的自然序号,这一过程会大大增加延迟。但是一般我们要进行的处理也是需要换成自然顺序的,因此该选项一般是Natrue Order。

循环前缀

做OFDM的同学可能需要用到此选项,比如4096长度,需要插入1024长度循环前缀,在你4096个数据进去之后,会有大约1024个时钟周期,你是不能输入数据的,IP核的Ready端口为低,在差不多5120个时钟周期才会给你拉高,这个操作是为了匹配两端的数据吞吐量。

可选的输出端口

分别是Xk的序号和是否溢出。

前者仅在逆序输出时有参考意义,后者当发生溢出时,会在整个输出valid有效时,拉高一个单比特信号。

实时/非实时

该选项默认是非实时,如果改成实时,如图1所示,它具有以下特性:

image

图1 FFT-IP核 实时模式下的数据传输

即使输入有效信号为低,它也会选取一个数据参与FFT运算,这个数据就是上一个有效信号为高时的输入信号。因此根据需要来判断是否需要改成实时。

端口说明

具体请参见使用示例中的注释部分。

使用示例

这里给出了一个简单的使用示例,由于比较简单,代码直接在文中粘贴出来,不再上传到Github污染环境。

设计文件

示例使用的 fft_demo.vhd 在此处给出,端口说明参见 PORT MAP 部分。

----------------------------------------------------------------------------------
-- Company: 
-- Engineer: Devin  balddonkey@outlook.com
-- 
-- Create Date: 2022/10/15 18:07:57
-- Design Name: 
-- Module Name: fft_demo - Behavioral
-- Project Name: 
-- Target Devices: 
-- Tool Versions: vivado 2018.3
-- Description: Demo for fft ip. 4096 points, pipelined architecture.
-- 
-- Dependencies: 
-- 
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
-- 
----------------------------------------------------------------------------------


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;

-- Uncomment the following library declaration if instantiating
-- any Xilinx leaf cells in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity fft_demo is
  Port (
    i_clk       : in  std_logic;
    i_rst       : in  std_logic;
    i_valid     : in  std_logic;
    i_last      : in  std_logic;
    iv_din      : in  std_logic_vector(31 downto 0);
    ov_dout     : out std_logic_vector(31 downto 0);
    o_valid     : out std_logic;
    o_last      : out std_logic;
    o_ovflo     : out std_logic
  );
end fft_demo;

architecture Behavioral of fft_demo is

----------------------------------------------------------------------------
-- Component
----------------------------------------------------------------------------

-- Comments see PORT MAP.
-- 注释参见端口映射表部分。
component ip_fft IS
  PORT (
    aclk : IN STD_LOGIC;
    s_axis_config_tdata : IN STD_LOGIC_VECTOR(15 DOWNTO 0);
    s_axis_config_tvalid : IN STD_LOGIC;
    s_axis_config_tready : OUT STD_LOGIC;
    s_axis_data_tdata : IN STD_LOGIC_VECTOR(31 DOWNTO 0);
    s_axis_data_tvalid : IN STD_LOGIC;
    s_axis_data_tready : OUT STD_LOGIC;
    s_axis_data_tlast : IN STD_LOGIC;
    m_axis_data_tdata : OUT STD_LOGIC_VECTOR(31 DOWNTO 0);
    m_axis_data_tuser : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
    m_axis_data_tvalid : OUT STD_LOGIC;
    m_axis_data_tready : IN STD_LOGIC;
    m_axis_data_tlast : OUT STD_LOGIC;
    m_axis_status_tdata : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
    m_axis_status_tvalid : OUT STD_LOGIC;
    m_axis_status_tready : IN STD_LOGIC;
    event_frame_started : OUT STD_LOGIC;
    event_tlast_unexpected : OUT STD_LOGIC;
    event_tlast_missing : OUT STD_LOGIC;
    event_fft_overflow : OUT STD_LOGIC;
    event_status_channel_halt : OUT STD_LOGIC;
    event_data_in_channel_halt : OUT STD_LOGIC;
    event_data_out_channel_halt : OUT STD_LOGIC
  );
END component;

----------------------------------------------------------------------------
-- Signals
----------------------------------------------------------------------------

constant SCALE_SCH  : std_logic_vector(11 downto 0):="010101010110";  -- Scaling factor: 2^7, scale 2^2 for the first stage.
constant FWD_INV    : std_logic:='0';  -- IFFT.

signal ip_config_data   : std_logic_vector(15 downto 0);
signal ip_config_valid  : std_logic;
signal ip_config_ready  : std_logic;
signal ip_i_data        : std_logic_vector(31 downto 0);
signal ip_i_valid       : std_logic;
signal ip_i_ready       : std_logic;
signal ip_i_last        : std_logic;
signal ip_o_data        : std_logic_vector(31 downto 0);
signal ip_o_valid       : std_logic;
signal ip_o_last        : std_logic;
signal ip_data_usr      : std_logic_vector(7 downto 0);
signal ip_status_data   : std_logic_vector(7 downto 0);
signal ip_status_vld    : std_logic;
signal ip_ovflo         : std_logic;


begin

----------------------------------------------------------------------------
-- Process
----------------------------------------------------------------------------

ip_init_proc: process(i_clk,i_rst)
begin
    if(i_clk'event and i_clk='1') then
        if(i_rst='1') then
            ip_config_valid     <=  '0';
        else
            ip_config_valid     <=  '1';
            ip_config_data(12 downto 1) <= SCALE_SCH;   -- See Implementation Details Tab.
            ip_config_data(0)   <=  FWD_INV;            -- 参见“Implementation Details”选项卡。
        end if;
    end if;
end process;

ip_din_proc: process(i_clk,i_rst)
begin
    if(i_clk'event and i_clk='1') then
        if(i_rst='1') then
            ip_i_data   <=  (others=>'0');
            ip_i_valid  <=  '0';
            ip_i_last   <=  '0';
        else
            ip_i_data   <=  iv_din;
            ip_i_valid  <=  i_valid;
            ip_i_last   <=  i_last;
        end if;
    end if;
end process;

out_proc: process(i_clk,i_rst)
begin
    if(i_clk'event and i_clk='1') then
        if(i_rst='1') then
            ov_dout <=  (others=>'0');
            o_valid <=  '0';
            o_last  <=  '0';
            o_ovflo <=  '0';
        else
            ov_dout <=  ip_o_data;
            o_valid <=  ip_o_valid;
            o_last  <=  ip_o_last;
            o_ovflo <=  ip_ovflo;
        end if;
    end if;
end process;

----------------------------------------------------------------------------
-- Port Map
----------------------------------------------------------------------------

inst_ip_fft: ip_fft
  PORT map(
    aclk                        =>  i_clk,
    s_axis_config_tdata         =>  ip_config_data,  -- CONFIG channel, include SCALE_SCH, transform direction.
                                                     -- 配置通道,包括缩放因子,变换方向。
    s_axis_config_tvalid        =>  ip_config_valid, -- Config data valid.
                                                     -- 配置数据有效信号。
    s_axis_config_tready        =>  ip_config_ready, -- Output port. Asserted by IP that it could accept config data.
                                                     -- 输出端口,IP核产生,告诉我们它是否能接受配置通道的数据。
    s_axis_data_tdata           =>  ip_i_data,  -- DATA channel, imag & real.
                                                -- 数据通道,虚部在前,实部在后。
    s_axis_data_tvalid          =>  ip_i_valid, -- Input data valid signal.
                                                -- 输入数据有效信号。
    s_axis_data_tready          =>  ip_i_ready, -- Output port. Asserted by IP that it could accept data.
                                                -- 输出端口,IP核产生,告诉我们它是否能接受数据。
    s_axis_data_tlast           =>  ip_i_last,  -- Input last sign, keep high in the last sample for 1 cycle.
                                                -- 输入数据通道最后一个数据标志位,在最后一个数据的位置同步拉高,持续一个周期。
    m_axis_data_tdata           =>  ip_o_data,  -- Output data.
    m_axis_data_tuser           =>  ip_data_usr,-- Tuser for data channel, carries additional information for per sample, such as
                                                -- XK_INDEX, OVFLO, BLK_EXP.
                                                -- 数据通道的用户信息,包含每个采样点的额外信息,例如序号索引(倒序模式下),
                                                -- 溢出(overflow),块指数(block exponent)。
    m_axis_data_tvalid          =>  ip_o_valid,
    m_axis_data_tready          =>  '1',        -- Is ready to accept DATA channel output. Always be ready when set to '1'.
    m_axis_data_tlast           =>  ip_o_last,
    m_axis_status_tdata         =>  ip_status_data, -- Status channel out. Carries status data: BLK_EXP or OVFLO.
                                                    -- 状态通道输出,包括状态信息,块指数(block exponent)和溢出标志(overflow)。
    m_axis_status_tvalid        =>  ip_status_vld,
    m_axis_status_tready        =>  '1',        -- Is ready to accept STATUS channel output. Always be ready when set to '1'.
    event_frame_started         =>  open,
    event_tlast_unexpected      =>  open,
    event_tlast_missing         =>  open,
    event_fft_overflow          =>  ip_ovflo,   -- Overflow sign for per block. 每个数据块的溢出标志。
    event_status_channel_halt   =>  open,
    event_data_in_channel_halt  =>  open,
    event_data_out_channel_halt =>  open
  );

end Behavioral;

IP核配置

主要配置项如下图所示,建议勾选OVFLO方便观察是否溢出。

image

仿真文件

示例使用的 tb_fft_demo.vhd 在此处给出。

----------------------------------------------------------------------------------
-- Company: 
-- Engineer: Devin  balddonkey@outlook.com
-- 
-- Create Date: 2022/10/15 18:08:26
-- Design Name: 
-- Module Name: tb_fft_demo - Behavioral
-- Project Name: 
-- Target Devices: 
-- Tool Versions: 
-- Description: Testbench for FFT demo.
-- 
-- Dependencies: 
-- 
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
-- 
----------------------------------------------------------------------------------


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_TEXTIO.ALL;
use STD.TEXTIO.ALL;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;

-- Uncomment the following library declaration if instantiating
-- any Xilinx leaf cells in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity tb_fft_demo is
--  Port ( );
end tb_fft_demo;

architecture Behavioral of tb_fft_demo is

component fft_demo is
  Port (
    i_clk       : in  std_logic;
    i_rst       : in  std_logic;
    i_valid     : in  std_logic;
    i_last      : in  std_logic;
    iv_din      : in  std_logic_vector(31 downto 0);
    ov_dout     : out std_logic_vector(31 downto 0);
    o_valid     : out std_logic;
    o_last      : out std_logic;
    o_ovflo     : out std_logic
  );
end component;

file fin_vector     : text is in    -- IEEE.STD_LOGIC_TEXTIO  &  STD.TEXTIO
    "D:\workspace\fft_demo\fft_demo.srcs\sources_1\tb\tb_fft_demo_din.dat";
file fout_vector    : text is out
    "D:\workspace\fft_demo\fft_demo.srcs\sources_1\tb\tb_fft_demo_dout.dat";
    
constant CLK_PERIOD : time:= 10ns;

signal clk  : std_logic:='0';
signal rst  : std_logic:='0';

signal in_en    : std_logic;
signal in_last  : std_logic;
signal in_data  : std_logic_vector(31 downto 0);
signal out_data : std_logic_vector(31 downto 0);
signal out_vld  : std_logic;
signal out_last : std_logic;
signal ovflo    : std_logic;

signal din_im   : std_logic_vector(15 downto 0);
signal din_re   : std_logic_vector(15 downto 0);
signal dout_im  : std_logic_vector(15 downto 0);
signal dout_re  : std_logic_vector(15 downto 0);

begin

fin_proc: process(clk,rst)
variable v_iline    : line;
variable v_ien      : std_logic;
variable v_ilast    : std_logic;
variable v_dini     : integer;
variable v_dinr     : integer;
begin
    if(clk'event and clk='1') then
        if(rst='1') then
            in_en   <=  '0';
            in_last <=  '0';
            in_data <=  (others=>'0');
        else
            if(not endfile(fin_vector)) then
                readline(fin_vector,v_iline);
                read(v_iline,v_ien);
                read(v_iline,v_ilast);
                read(v_iline,v_dini);
                read(v_iline,v_dinr);
                
                in_en   <=  v_ien;
                in_last <=  v_ilast;
                in_data <=  conv_std_logic_vector(v_dini,16) & conv_std_logic_vector(v_dinr,16);    -- IEEE.STD_LOGIC_ARITH
            end if;
        end if;
    end if;
end process;

fout_proc: process(clk,rst)
variable v_oline    : line;
variable v_douti    : integer;
variable v_doutr    : integer;
begin
    if(clk'event and clk='1') then
        if(rst='1') then

        else
            if(out_vld='1') then
                v_douti :=  conv_integer(out_data(31 downto 16));   -- IEEE.STD_LOGIC_UNSIGNED  or  IEEE.STD_LOGIC_SIGNED
                v_doutr :=  conv_integer(out_data(15 downto 0));
                write(v_oline,v_douti);
                write(v_oline,HT);  -- Horizontal Tab
                write(v_oline,v_doutr);
                writeline(fout_vector,v_oline);
            end if;
        end if;
    end if;
end process;

clk_proc: process
begin
    wait for CLK_PERIOD/2;
    clk <= not clk;
end process;

rst <= '1' after 5 ns,
       '0' after 125 ns;

din_im  <=  in_data(31 downto 16);
din_re  <=  in_data(15 downto 0);
dout_im <=  out_data(31 downto 16);
dout_re <=  out_data(15 downto 0);

tb_fft_demo: fft_demo
  port map(
    i_clk       =>  clk,
    i_rst       =>  rst,
    i_valid     =>  in_en,
    i_last      =>  in_last,
    iv_din      =>  in_data,
    ov_dout     =>  out_data,
    o_valid     =>  out_vld,
    o_last      =>  out_last,
    o_ovflo     =>  ovflo
  );

end Behavioral;

MATLAB产生数据源

仿真用到的数据源 tb_fft_demo_din.dat ,是使用以下MATLAB程序产生的,fft_demo_din.m

% Stimulate fft demo
% devin balddonkey@outlook.com
clear,clc
close all;
rng(0);
fid = fopen('tb_fft_demo_din.dat','w+');

RB_num = 256;

xk = ofdm_gen(RB_num,2,2);
xk = ifftshift(xk);
xk = xk.*2^12;
xk = reshape(xk,[],1);


for i = 1:20
    fprintf(fid,'0\t0\t0\t0\n');
end
for i = 1:4096*2
    fprintf(fid,'1\t');
    if(mod(i,4096)==0)
        fprintf(fid,'1\t');
    else
        fprintf(fid,'0\t');
    end
    fprintf(fid,'%d\t%d\n',imag(xk(i)),real(xk(i)));
end
for i = 1:20
    fprintf(fid,'0\t0\t0\t0\n');
end

用到的 ofdm_gen 函数如下:

function ofdm_data = ofdm_gen(RB_num,Q_m,symb_num)
% Generate RE of certain number of RB.
% devin  balddonkey@outlook.com
% Inputs:
%   RB_num   : Resource Block Number.
%   Q_m      : Modulation order, not completed.
%   symb_num : OFDM symbol number.
% Outputs:
%   ofdm_data : 4096*symb_num matrix.

if Q_m == 1
    x = pskmod(randi([0 1],RB_num*12,symb_num),2,0.25*pi);
elseif Q_m == 2
    x = pskmod(randi([0 3],RB_num*12,symb_num),4,0.25*pi);
end

Ng = 4096-RB_num*12;
ofdm_data = [zeros(Ng/2,symb_num);x;zeros(Ng/2,symb_num)];

q = quantizer([16,12],'round');
ofdm_data = quantize(q,ofdm_data);

MATLAB比对误差

Vivado的输出结果和Matlab浮点计算结果的误差对比使用以下程序:fft_demo_err.m

% fft demo error analysis
% devin balddonkey@outlook.com
clear,clc
close all;

%% Matlab floating fft
xk = load('tb_fft_demo_din.dat');
xk = xk(21:20+4096*2,3:4);
xk = xk./2^12;
xk = 1i*xk(:,1)+xk(:,2);
xk = reshape(xk,[],2);

xn_ref = zeros(4096,2);
for i = 1:2
    xn_ref(:,i) = ifft(xk(:,i)).*2^5;
end
xn_ref = xn_ref(:);

%% Vivado FFT IP
xn = load('tb_fft_demo_dout.dat');
xn = int2fx(xn,4,12);
xn = 1i*xn(:,1)+xn(:,2);

%% compare
err = sqrt(mean(abs(xn-xn_ref).^2)./mean(abs(xn_ref).^2));
plot(real(xn)-real(xn_ref),'b'); hold on;
plot(imag(xn)-imag(xn_ref),'r');

给出 int2fx.m 函数文件如下:

function [dataFixed_dec] = int2fx(dataInteger,intPart,fracPart)
% Convert vivado unsigned integer output to fixed point fraction.
% Devin balddonkey@outlook.com

width = intPart+fracPart;
[r,c] = size(dataInteger);
dataFixed_dec = zeros(r,c);
for i= 1:r
    for j= 1:c
        if(dataInteger(i,j) > pow2(width-1))
            dataFixed_dec(i,j) = (dataInteger(i,j)-pow2(width))./pow2(fracPart);
        else
            dataFixed_dec(i,j) = (dataInteger(i,j))./pow2(fracPart);
        end
    end
end

这里我要特别说一下下面这一句,为什么除以2的5次方。这是因为matlab的IFFT本身就除以了2的12次方(4096),正变换不除以N,而Vivado的IP核无论是正变换还是反变换,都不除以任何数,而是通过缩放因子进行每一级的缩放,在设计文件一节中,我们设置的缩放因子 SCALE_SCH缩放倍数是2的7次方,为了二者数据保持一致,Matlab这里需要乘以2的5次方。关于为什么那个缩放因子是2的7次方,参考 配置项说明->实现->缩放选项

xn_ref(:,i) = ifft(xk(:,i)).*2^5;

C-Model工具使用

C-Model工具是Xilinx官方给出的C语言搭建的平台,可以使用Matlab进行调用方便确定缩放因子。它可以在matlab中产生与IP核完全一致的输出。这个工具可以在你调用IP核之后,工程文件夹下面的 cmodel 文件夹下找到。例如以下路径>D:\workspace\fft_demo\fft_demo.srcs\sources_1\ip\ip_fft\cmodel,其中lin64适用于linux系统,具体哪个发行版我也不太清楚,nt64适用于windows系统。

关于这部分内容,暂且不更新(不是不会用),如果博客有人看了并且想知道的,请邮件我,我看到会更新。

注意:pipelined结构下,C-Model输出的结果与IP核实际输出结果不一致,这一点在官方文档中有写,我亲自实验也是如此,IP核发生很离谱的溢出时,C-Model工具可能给出的还是正确的结果。

posted @ 2022-10-14 23:02  devindd  阅读(1000)  评论(0编辑  收藏  举报