Zynq—AD9238数据采集DDR3缓存千兆以太网发送实验(二)

Zynq---AD9238数据采集DDR3缓存千兆以太网发送实验(前导)

Zynq---AD9238数据采集DDR3缓存千兆以太网发送实验(一)

Zynq---AD9238数据采集DDR3缓存千兆以太网发送实验(三)


五、实验目的

本次实验使用电脑上的网络调试助手,将命令帧通过以太网芯片RTL8211 (RGMII接口)发送至ACZ7015 开发板,提取UDP报文内容转换成控制命令,从而实现对ACM9238模块采样频率、数据采样个数以及采样通道的配置。

配置完成之后,ACM9238 模块开始采集数据,将采集的数据存储至DDR3中,然后通过网口以UDP协议传输到电脑。用户可以在电脑上通过网口调试工具进行指令的下发,并以文件的形式保存接收到的数据,然后使用MATLAB软件进行进一步的数据处理分析。

PL 部分的模块说明如下:

  • pll 模块: 锁相环模块, 输入时钟 50M, 由 PS 输出给 PL; 输出 100M 的时钟给到 DDR3 控制器使用; 输出 50M 的时钟给其它模块使用。
  • eth_receive_cmd 模块: 以太网接收命令模块, 对以太网接收到的数据进行分析, 将接收的数据转换成相应的控制数据并输出到对应的模块。
  • ad9238_ctrl 模块: ACM9238 控制器模块, 该模块内部包含速度控制模块,以及数据位宽转换模块。
  • state_ctrl 模块: ADC 采集数据 DDR3 缓存以太网发送状态控制模块, 协调各个模块的信号控制, 程序状态的总控制模块。
  • fifo_axi4_adapter 模块: fifo 接口到 AXI4 接口的转换模块(含 2 个 FIFO)。

六、ACM9238模块

两路,

采样上限50Msps,如果期望以1Msps 的采样速率采样, 则只需要每间隔 50 个采样数据取一个结果存储或使用, 其他 49 个数据直接舍弃。不建议降低ADC芯片的时钟信号。

七、设计实例

7.1模块设计

7.1.1 eth_receive_cmd模块

将以太网接收到的数据进行解析, 得到控制命令。

(1)eth_udp_rx_gmii模块和rgmii_to_gmii模块
(2)mmcm模块

锁相环模块,将 rgmii 接口时钟信号 rgmii_rx_clk_i 偏移90 °得到 rgmii_rx_clk 时钟信号。(为了在时钟信号的上升沿/下降沿取数据时,取得数据刚好是数据信号 rgmii_rxd 的正中间, 使得采样的数据处于最稳定的状态。)

锁相环IP配置

(3)fifo_rx模块

使用该 IP 核解决采集过程中会出现的跨时钟域数据交互问题(以太网125MHz,ACM9238-50MHz)。

(4)eth_cmd模块

接收转命令模块。

cpp 复制代码
//非空时产生FIFO读请求信号
always@(posedge clk or negedge reset_n)
if(!reset_n)
    fifo_rd_req <= 1'b0;
else if(!rx_empty)
    fifo_rd_req <= 1'b1;
else
    fifo_rd_req <= 1'b0;


//获得帧命令数据
always@(posedge clk)
if(fifo_rd_req)begin
    data_0[7] <= #1 fifodout;
    data_0[6] <= #1 data_0[7];
    data_0[5] <= #1 data_0[6];
    data_0[4] <= #1 data_0[5];
    data_0[3] <= #1 data_0[4];
    data_0[2] <= #1 data_0[3];
    data_0[1] <= #1 data_0[2];
    data_0[0] <= #1 data_0[1];
end


//判断帧命令数据
always@(posedge clk or negedge reset_n)
if(!reset_n)begin
    address <= 0;
    cmd_data <= 32'd0;
    cmdvalid <= 1'b0;
end
else if(fifo_rx_done)begin
    if((data_0[0]==8'h55)&&(data_0[1]==8'hA5)&&(data_0[7]==8'hF0))
    begin
        cmd_data[7:0] <= #1 data_0[6];
        cmd_data[15:8] <= #1 data_0[5];
        cmd_data[23:16] <= #1 data_0[4];
        cmd_data[31:24] <= #1 data_0[3];
        address <= #1 data_0[2];
        cmdvalid <= #1 1;
    end
    else
        cmdvalid <= #1 0;
end
else
cmdvalid <= #1 0;
(5)cmd_rx模块

接收数据转换为控制数据。

寄存器说明:

cpp 复制代码
always@(posedge clk or negedge reset_n)
if(!reset_n)begin
    ChannelSel <= 2'b00;
    DataNum <= 32'd0;
    ADC_Speed_Set <= 32'd0;
    RestartReq <= 1'b0;
end
else if(cmdvalid)begin
    case(cmd_addr)
        0: RestartReq <= 1'b1;
        1: ChannelSel <= cmd_data[1:0];
        2: DataNum <= cmd_data[31:0];
        3: ADC_Speed_Set <= cmd_data[31:0];
    default:;
    endcase
end
else
    RestartReq <= 1'b0;

7.1.2 ad9238_ctrl 模块

控制ADC的采样速率,将12位数据转换为16位数据。

(1)speed_ctrl模块
cpp 复制代码
always@(posedge clk or negedge reset_n)
if(!reset_n)
    div_cnt <= 0;
else if(ad_sample_en)begin
    if(div_cnt >= div_set)
        div_cnt <= 0;
    else
        div_cnt <= div_cnt + 1'd1;
end
else
    div_cnt <= 0;


always@(posedge clk or negedge reset_n)
if(!reset_n)
    adc_data_en <= 0;
else if(div_cnt == div_set)
    adc_data_en <= 1;
else
    adc_data_en <= 0;
(2)ad_12bit_to_16bit模块
cpp 复制代码
always @(posedge clk)
    ad_out_valid <= ad_sample_en;


assign s_ad_in1 = ad_in1 + 12'd2048;
assign s_ad_in2 = ad_in2 + 12'd2048;


always @(posedge clk)
if(ad_sample_en && ch_sel == 2'b01)
    ad_out<={4'd0,s_ad_in1};//这样补 0 为了适应上位机
else if(ad_sample_en && ch_sel == 2'b10)
    ad_out<={4'd0,s_ad_in2};//
else if(ad_sample_en && ch_sel == 2'b00)
    ad_out<={4'd0,adc_test_data};
else
    ad_out <= 16'd0;

7.1.3 state_ctrl 模块

控制信号的产生以及 ADC 何时启动数据传输。

cpp 复制代码
localparam IDLE = 4'd0; //空闲状态
localparam DDR_WR_FIFO_CLEAR = 4'd1; //DDR 写 FIFO 清除状态
localparam ADC_SAMPLE = 4'd2; //ADC 采样数据状态
localparam DDR_RD_FIFO_CLEAR = 4'd3; //DDR 读 FIFO 清除状态
localparam DATA_SEND_START = 4'd4; //数据发送状态
localparam DATA_SEND_WORKING = 4'd5; //数据发送完成状态

(1)IDLE

cpp 复制代码
//ADC 模块开始采样标志信号寄存
always@(posedge clk or posedge reset)begin
if(reset)
    start_sample_rm <= 1'b0;
else if(state==IDLE)
    start_sample_rm <= start_sample;
else
    start_sample_rm <= 1'b0;
end

/*状态切换IDLE->DDR_WR_FIFO_CLEAR
begin
    if(start_sample_rm) begin //DDR 初始化完成并且产生启动采样信号
        state <= DDR_WR_FIFO_CLEAR; //进入写 FIFO 清除状态
    end
    else begin
        state <= state;
    end
end
*/

(2)DDR_WR_FIFO_CLEAR

cpp 复制代码
//延时10个节拍
always@(posedge clk or posedge reset)begin
if(reset)
    wrfifo_clr_cnt<=0;
else if(state==DDR_WR_FIFO_CLEAR)//如果进入了清 fifo 状态
begin
    if(wrfifo_clr_cnt==9)
        wrfifo_clr_cnt<=5'd9;
    else
        wrfifo_clr_cnt<=wrfifo_clr_cnt+1'b1;
end
else
    wrfifo_clr_cnt<=5'd0;
end

//给清FIFO信号足够的拉高时间
always@(posedge clk or posedge reset)begin
if (reset)
    wrfifo_clr<=0;
else if(state==DDR_WR_FIFO_CLEAR)
    begin
        if(wrfifo_clr_cnt==0||wrfifo_clr_cnt==1||wrfifo_clr_cnt==2)
            wrfifo_clr<=1'b1;
        else
            wrfifo_clr<=1'b0;
    end
else
    wrfifo_clr<=1'b0;
end

/*状态切换DDR_WR_FIFO_CLEAR->ADC_SAMPLE
begin
    if(!wrfifo_full && (wrfifo_clr_cnt==9))
        state<=ADC_SAMPLE;
    else
        state<=DDR_WR_FIFO_CLEAR;
end
*/

(3)ADC_SAMPLE

cpp 复制代码
//根据ADC输出使能信号计数
always@(posedge clk or posedge reset)begin
if(reset)
    adc_sample_cnt<=1'b0;
else if(state==ADC_SAMPLE)begin
    if(adc_data_en)
        adc_sample_cnt<=adc_sample_cnt+1'b1;
    else
        adc_sample_cnt<=adc_sample_cnt;
end
else
    adc_sample_cnt<=1'b0;
end

//产生采样使能信号给其他模块
always@(posedge clk or posedge reset)begin
if(reset)
    ad_sample_en<=0;
else if(state==ADC_SAMPLE)
    ad_sample_en<=1;
else
    ad_sample_en<=0;
end

/*状态切换ADC_SAMPLE->DDR_RD_FIFO_CLEAR
begin
    if((adc_sample_cnt>=set_sample_num-1'b1)&& adc_data_en)
        state<=DDR_RD_FIFO_CLEAR;
    else
        state<=state;
end
*/

(4)DDR_RD_FIFO_CLEAR

cpp 复制代码
//延时10个节拍
always@(posedge clk or posedge reset)begin
if(reset)
    rdfifo_clr_cnt<=0;
else if(state==DDR_RD_FIFO_CLEAR)//如果进入了清 fifo 状态
begin
    if(rdfifo_clr_cnt==9)
        rdfifo_clr_cnt<=5'd9;
    else
        rdfifo_clr_cnt<=rdfifo_clr_cnt+1'b1;
end
else
    rdfifo_clr_cnt<=5'd0;
end

//给清FIFO信号足够的拉高时间
always@(posedge clk or posedge reset)begin
if (reset)
    rdfifo_clr<=0;
else if(state==DDR_RD_FIFO_CLEAR)
begin
    if(rdfifo_clr_cnt==0||rdfifo_clr_cnt==1||rdfifo_clr_cnt==2)
        rdfifo_clr<=1'b1;
else
        rdfifo_clr<=1'b0;
end
else
    rdfifo_clr<=1'b0;
end

/*状态切换DDR_RD_FIFO_CLEAR->DATA_SEND_START
begin
    if(!rdfifo_empty && (rdfifo_clr_cnt==9))begin
        state<=DATA_SEND_START;
    end
    else
        state<=state;
end
*/

(5)DATA_SEND_START

cpp 复制代码
/*状态切换DATA_SEND_START->DATA_SEND_WORKING
begin
    state <= DATA_SEND_WORKING;
end
*/

(6)DATA_SEND_WORKING

cpp 复制代码
//发送数据计数
always@(posedge clk or posedge reset)begin
if(reset)
    send_data_cnt<=32'd0;
else if(state==IDLE)
    send_data_cnt<=32'd0;
else if(rdfifo_rden)
    send_data_cnt<=send_data_cnt+1;
else
    send_data_cnt<=send_data_cnt;
end

//DDR数据存到以太网缓存
always@(posedge clk or posedge reset)
if(reset) begin
    eth_fifo_wrreq <= 1'b0;
    eth_fifo_wrdata <= 'd0;
end
else if(rdfifo_rden) begin
    eth_fifo_wrreq <= 1'b1;
    eth_fifo_wrdata <= rdfifo_dout;
end
else begin
    eth_fifo_wrreq <= 1'b0;
    eth_fifo_wrdata <= 'd0;
end

/*状态切换DATA_SEND_WORKING->IDLE、DATA_SEND_WORKING->DATA_SEND_WORKING
begin
    if(send_data_cnt >= set_sample_num-1'b1) begin
        rdfifo_rden <= 1'b0;
        state <= IDLE;
    end
    else begin
        rdfifo_rden <= 1'b1;
        state <= DATA_SEND_WORKING;
    end
end
*/

7.1.4 fifo_axi_adapter模块

看文章开头:Zynq---AD9238数据采集DDR3缓存千兆以太网发送实验(一)。

cpp 复制代码
S_IDLE:
begin
    if(start)
        next_state = S_ARB;
    else
        next_state = S_IDLE;
end
cpp 复制代码
module fifo_axi4_adapter #(
  parameter FIFO_DW                = 16     ,
  parameter WR_AXI_BYTE_ADDR_BEGIN = 0      ,
  parameter WR_AXI_BYTE_ADDR_END   = 1023   ,
  parameter RD_AXI_BYTE_ADDR_BEGIN = 0      ,
  parameter RD_AXI_BYTE_ADDR_END   = 1023   ,

  parameter AXI_DATA_WIDTH         = 128    ,
  parameter AXI_ADDR_WIDTH         = 28     ,
  parameter AXI_ID                 = 4'b0000,
  parameter AXI_BURST_LEN          = 8'd31    //burst length = 32
)
(
  input                          start         ,
  // clock reset
  input                          clk           ,
  input                          reset         ,
  // wr_fifo wr Interface
  input                          wrfifo_clr    ,
  input                          wrfifo_clk    ,
  input                          wrfifo_wren   ,
  input   [FIFO_DW-1:0]          wrfifo_din    ,
  output                         wrfifo_full   ,
  output  [15:0]                 wrfifo_wr_cnt ,
  // rd_fifo rd Interface
  input                          rdfifo_clr    ,
  input                          rdfifo_clk    ,
  input                          rdfifo_rden   ,
  output  [FIFO_DW-1:0]          rdfifo_dout   ,
  output                         rdfifo_empty  ,
  output  [15:0]                 rdfifo_rd_cnt ,
  // Master Interface Write Address Ports
  output  [3:0]                  m_axi_awid    ,
  output  [AXI_ADDR_WIDTH-1:0]   m_axi_awaddr  ,
  output  [7:0]                  m_axi_awlen   ,
  output  [2:0]                  m_axi_awsize  ,
  output  [1:0]                  m_axi_awburst ,
  output  [0:0]                  m_axi_awlock  ,
  output  [3:0]                  m_axi_awcache ,
  output  [2:0]                  m_axi_awprot  ,
  output  [3:0]                  m_axi_awqos   ,
  output  [3:0]                  m_axi_awregion,
  output                         m_axi_awvalid ,
  input                          m_axi_awready ,
  // Master Interface Write Data Ports
  output  [AXI_DATA_WIDTH-1:0]   m_axi_wdata   ,
  output  [AXI_DATA_WIDTH/8-1:0] m_axi_wstrb   ,
  output                         m_axi_wlast   ,
  output                         m_axi_wvalid  ,
  input                          m_axi_wready  ,
  // Master Interface Write Response Ports
  input   [3:0]                  m_axi_bid     ,
  input   [1:0]                  m_axi_bresp   ,
  input                          m_axi_bvalid  ,
  output                         m_axi_bready  ,
  // Master Interface Read Address Ports
  output  [3:0]                  m_axi_arid    ,
  output  [AXI_ADDR_WIDTH-1:0]   m_axi_araddr  ,
  output  [7:0]                  m_axi_arlen   ,
  output  [2:0]                  m_axi_arsize  ,
  output  [1:0]                  m_axi_arburst ,
  output  [0:0]                  m_axi_arlock  ,
  output  [3:0]                  m_axi_arcache ,
  output  [2:0]                  m_axi_arprot  ,
  output  [3:0]                  m_axi_arqos   ,
  output  [3:0]                  m_axi_arregion,
  output                         m_axi_arvalid ,
  input                          m_axi_arready ,
  // Master Interface Read Data Ports
  input   [3:0]                  m_axi_rid     ,
  input   [AXI_DATA_WIDTH-1:0]   m_axi_rdata   ,
  input   [1:0]                  m_axi_rresp   ,
  input                          m_axi_rlast   ,
  input                          m_axi_rvalid  ,
  output                         m_axi_rready  
);

7.1.5 eth_send_data 模块

将DDR读出的ADC数据发送到电脑端。

(1)fifo_tx模块

First Word Fall Through( FWFT)可以不需要读命令, 自动将最新的数据放在 dout 上。

(2)eth_send_ctrl模块

以太网帧最大长度 1518字节(数据段 1500 字节) , 其中数据段 1500 字节还包括 20 字节 IP 报文头部和 8 字节 UDP 报文头部, 所以数据帧发送的ACM9238 采集的数据最大长度为 1472 字节。

cpp 复制代码
  always@(posedge clk125M or negedge reset_n)
  if(!reset_n) begin
    pkt_tx_en <= 1'd0;
    pkt_length <= 16'd0;
    data_num <= 32'd0;
    state <= 0;
    cnt_dly_time <= 28'd0;
  end
  else begin
    case(state)
        0://得到 pkt_length 信号的初始值
            begin
                if(restart_req)begin
                    data_num <= total_data_num;
                    if((total_data_num << 1) >= 16'd1472)begin
                        pkt_length <= 16'd1472;	//一个数据2个字节
                        state <= 1;
                    end
                    else if((total_data_num << 1) > 0)begin
                        pkt_length <= total_data_num << 1; //一个数据2个字节
                        state <= 1;
                    end
                    else begin
                        state <= 0;
                    end
				end
            end
         1: 
            begin
                if(fifo_rd_cnt >= (pkt_length -2)) begin
                    pkt_tx_en <= 1'd1;
                    state <= 2;
                end
                else begin
                    state <= 1;
                    pkt_tx_en <= 1'd0;
                end
            end
         2:
            begin
                pkt_tx_en <= 1'd0;
                if(eth_tx_done)begin
					data_num <= data_num - pkt_length/2;
					state <= 3;
				end
            end
         
        3:
			if(cnt_dly_time >= cnt_dly_min)begin
               state <= 4;
               cnt_dly_time <= 28'd0;
            end
            else begin
               cnt_dly_time <= cnt_dly_time + 1'b1;
			   state <= 3;
            end
         4:
            begin
                if(data_num * 2 >= 16'd1472)begin
					pkt_length <= 16'd1472;
					state <= 1;
				end
				else if(data_num * 2 > 0)begin
					pkt_length <= data_num * 2;
					state <= 1;
				end
				else begin
					state <= 0;
				end
            end
          
          default:state <= 0;
         
    endcase
  end
相关推荐
FakeOccupational6 小时前
fpga系列 HDL : Microchip FPGA开发软件 Libero 中导出和导入引脚约束配置
fpga开发
贝塔实验室9 小时前
LDPC 码的构造方法
算法·fpga开发·硬件工程·动态规划·信息与通信·信号处理·基带工程
Moonnnn.13 小时前
【FPGA】时序逻辑计数器——仿真验证
fpga开发
三贝勒文子14 小时前
Synopsys 逻辑综合之 ICG
fpga开发·eda·synopsys·时序综合
byte轻骑兵14 小时前
【驱动设计的硬件基础】CPLD和FPGA
fpga开发·cpld
dadaobusi15 小时前
看到一段SVA代码,让AI解释了一下
单片机·嵌入式硬件·fpga开发
G2突破手25915 小时前
FMC、FMC+ 详解
fpga开发
fpga和matlab15 小时前
FPGA时序约束分析4——Reg2Reg路径的建立时间与保持时间分析
fpga开发·reg2reg·建立时间·保持时间
高沉15 小时前
2025华为海思数字IC面经
华为·fpga开发
伊宇韵15 小时前
FPGA - GTX收发器-K码 以及 IBERT IP核使用
fpga开发