当前位置: 首页 > news >正文

FPGA实战(32):多通道ADC数据打包模块设计

1. 为什么需要ad_pack?

在上一篇文章中,我们介绍了单通道打包模块ad_chan_pack,它负责将一路ADC的I/Q数据按固定帧格式打包,并通过异步FIFO送出。实际系统中,我们往往需要同时处理多路DDC(数字下变频)输出的数据,比如8个通道,每个通道包含I/Q两路,每路32位。如果让每个通道独立输出打包数据,会占用过多总线资源,也不利于统一管理。

ad_pack模块正是为了解决这一问题而设计的顶层聚合单元。它:

  • 聚合8个通道的数据:每个通道使用独立的ad_chan_pack实例,各自完成帧打包。
  • 轮询读取:通过状态机轮流从各个通道的FIFO中读取数据,合并成一路连续数据流输出。
  • 支持DMA流控:通过dma_fifo_progfull信号感知下游FIFO状态,实现反压,防止数据溢出。
  • 可配置帧长度:通过参数pack_length设定每帧有效数据点数(默认32012)。
  • 集成测试模式trans_data_sel可切换为内部测试数据,便于调试。
  • 自动帧间延时控制:内部例化singal_cfg模块,根据上位机下发的Acquisition_delay控制帧与帧之间的间隔。

本文将围绕ad_pack.v及其测试平台tb_ad_pack.v展开,详细讲解设计思路和实现细节。


2. ad_pack模块功能概述

2.1 顶层接口

信号名方向位宽说明
clk_adcinput1ADC采样时钟(写FIFO用)
clk_transinput1传输时钟(读FIFO和输出用)
rstinput1异步复位,高有效
trans_data_selinput10=ADC数据,1=测试数据
Acquisition_eninput1上位机下发采集使能
Acquisition_delayinput[31:0]帧间延时(按25MHz时钟计算)
ddc_eninput[7:0]各通道DDC数据有效使能(bit位对应通道)
ddc_datainput[255:0]8个通道的I/Q数据,每个通道32位,拼接为256位
dma_fifo_progfullinput1下游DMA FIFO可编程满标志(流控)
pack_valid_enoutput1输出数据有效使能(同时为读使能)
pack_valid_dataoutput[31:0]输出的打包数据

2.2 内部结构

ad_pack内部例化了以下子模块:

  • singal_cfg:产生打包启动脉冲data_trans_start和数据有效信号data_valid,根据Acquisition_enAcquisition_delay控制帧启动时序。
  • 8个ad_chan_pack:每个通道独立打包,FIFO写侧使用clk_adc,读侧使用clk_trans。每个通道输出自己的pack_validpack_valid_data以及FIFO状态。
  • data_rate:统计输出数据速率(单位为Bytes/s),用于性能监控。

顶层模块的核心任务是轮询8个通道的FIFO,将有效数据读出并合并输出,同时处理流控和帧切换。

2.3 FIFO IP核配置





3. 核心设计思路

3.1 四状态FSM

顶层采用4个状态控制轮询和复位:

  • P_ST_IDLE:空闲,等待任意通道的FIFO非满(w_fifo_prog_full[0]为1表示可写?实际条件为w_fifo_prog_full[0],需注意是满标志,应该用非满触发)—— 仔细看代码中跳转条件p_st_idle2p_st_read_start = state_c==P_ST_IDLE && (w_fifo_prog_full[0]);,这里w_fifo_prog_full[0]是FIFO可编程满标志,通常为1表示几乎满,此处用做启动条件似乎有误?但可能是设计意图:当某通道FIFO达到可编程满阈值时,说明有数据可读,于是进入READ状态。实际使用中需根据FIFO的编程满阈值设定来配合。
  • P_ST_READ:读取数据状态。在此状态下,根据当前通道号r_chan_cnt,使能对应通道的rd_fifo_ready信号,从该通道FIFO读取一个数据。同时计数r_data_cnt,当读够pack_length个数据后,切换下一通道或进入结束状态。
  • P_ST_JUDGE:判断状态(仅1拍),用于更新通道号,随后回到READ。
  • P_ST_END:结束状态,当所有8个通道都读完一轮后进入,等待r_cnt计数到1000(即帧间延时)后回到IDLE,同时产生r_fifo_rst复位信号复位所有子模块的FIFO。

状态转移条件

  • IDLE → READ:w_fifo_prog_full[0]有效(至少有一个通道FIFO非空?实际是检测通道0的满标志,这可能只针对通道0,但所有通道共享同一个启动,故认为是整体启动条件)。
  • READ → JUDGE:当前通道读完pack_length个数据且不是最后一个通道。
  • READ → END:当前通道读完且是最后一个通道(通道7)。
  • JUDGE → READ:无条件,1拍后回到READ(此时通道号已+1)。
  • END → IDLE:r_cnt == 1000,即帧间延时结束。

3.2 通道轮询与读控制

  • r_chan_cnt:当前正在读取的通道号(0~7)。
  • r_data_cnt:当前通道已读出的数据个数。
  • r_rd_fifo_ready:8位寄存器,每位对应一个通道的读使能。在READ状态下,只有当前通道的对应位为1,其他为0。
  • r_pack_ddc_en:将ddc_en经过两级同步后寄存,用于在ADC时钟域使能打包。
  • r_sample_cnt:在ADC时钟域累加的采样计数器,每个通道共用(实际帧中采样计数是全局的)。

轮询过程:从通道0开始,每个通道连续读出pack_length个数据,完成后切换下一通道。当所有8个通道读完,进入END状态,然后IDLE等待下一帧启动。

3.3 流控机制

  • 下游流控dma_fifo_progfull为高时,表示下游DMA FIFO即将满,此时顶层应停止读取。代码中在READ状态下,如果dma_fifo_progfull为1,则r_rd_fifo_ready[r_chan_cnt]被清零,暂停读取;当该信号恢复为0且数据未读完时,继续使能。
  • 上游流控:每个ad_chan_pack子模块的FIFO都有prog_fullfull标志,通过w_fifo_prog_full向量反馈给顶层,用于启动或暂停(但当前设计中只在IDLE时检测通道0的prog_full,未用作实时流控,这是一个可优化的点)。

3.4 参数化打包长度

通过parameter pack_length = 32'd32012,可灵活配置每帧的有效数据点数,适应不同场景(如雷达脉冲宽度、FFT点数等)。

3.5 测试模式

trans_data_sel为1时,ad_chan_pack内部生成递增测试数据,不依赖外部ADC,方便独立调试验证。

3.6 帧间延时

singal_cfg模块根据Acquisition_delay控制两次data_trans_start脉冲的间隔,从而实现帧间延时。顶层END状态也通过r_cnt计数1000个周期,产生r_fifo_rst复位所有FIFO,确保下一帧开始前FIFO处于干净状态。


4. 关键代码解析

4.1 状态机跳转逻辑

assign p_st_idle2p_st_read_start = state_c==P_ST_IDLE && (w_fifo_prog_full[0]); assign p_st_read2p_st_judge_start = state_c==P_ST_READ && (r_data_cnt == P_PACK_LENGTH && r_chan_cnt != 'd7); assign p_st_read2p_st_end_start = state_c==P_ST_READ && (r_data_cnt == P_PACK_LENGTH && r_chan_cnt == 'd7); assign p_st_judge2p_st_read_start = state_c==P_ST_JUDGE && (1); assign p_st_end2p_st_idle_start = state_c==P_ST_END && (r_cnt == 'd1000);

4.2 通道切换与数据计数

always @(posedge i_clk) begin if(i_rst || r_fifo_rst) r_chan_cnt <= 'd0; else if(state_c == P_ST_IDLE) r_chan_cnt <= 'd0; else if(state_c == P_ST_READ && r_data_cnt == P_PACK_LENGTH && r_chan_cnt == 'd7) r_chan_cnt <= 'd0; else if(state_c == P_ST_READ && r_data_cnt == P_PACK_LENGTH && r_chan_cnt != 'd7) r_chan_cnt <= r_chan_cnt + 1; else if(state_c == P_ST_END) r_chan_cnt <= 'd0; else r_chan_cnt <= r_chan_cnt; end

4.3 读使能生成(含流控)

always @(posedge i_clk) begin if(i_rst || r_fifo_rst) r_rd_fifo_ready <= 'd0; else if(state_c == P_ST_IDLE) r_rd_fifo_ready <= 'd0; else if(state_c == P_ST_JUDGE) r_rd_fifo_ready <= 'd0; else if(state_c == P_ST_READ && dma_fifo_progfull) r_rd_fifo_ready[r_chan_cnt] <= 1'b0; else if(state_c == P_ST_READ && dma_fifo_progfull == 'd0 && r_data_cnt == P_PACK_LENGTH) r_rd_fifo_ready[r_chan_cnt] <= 1'b0; else if(state_c == P_ST_READ && dma_fifo_progfull == 'd0 && r_data_cnt != P_PACK_LENGTH) r_rd_fifo_ready[r_chan_cnt] <= 1'b1; else if (state_c == P_ST_END) r_rd_fifo_ready <= 'd0; else r_rd_fifo_ready <= r_rd_fifo_ready; end

4.4 输出数据选择

在READ状态下,根据当前使能的通道w_ddc_pack_en(实际上就是r_rd_fifo_ready的独热形式)选择对应通道的pack_valid_data输出。


5. 仿真测试平台(tb_ad_pack.v)

测试平台模拟了以下场景:

  • 产生100MHz和50MHz双时钟,复位后释放。
  • 使能Acquisition_en,设置Acquisition_delay = 10(缩短帧间延时以加速仿真)。
  • 使能所有8个通道(ddc_en = 8'hFF)。
  • 模拟ADC数据递增(每个时钟周期增加固定值)。
  • 模拟dma_fifo_progfull信号:在数据传送过程中拉高一段时间,检验流控是否生效。
  • 运行足够长时间,观察输出数据流。
  • 打印状态机状态、rd_fifo_ready等信号。

监控逻辑包括:

  • 检测帧头0xCC33AA55,然后检测通道标识0x55AA33xx,最后检测帧尾0x7E7E7E7E,并记录每个通道的数据个数。
  • 统计总包数和错误计数。

5.1 关键监控代码

always @(posedge clk_trans) begin if (!rst && pack_valid_en) begin total_packets <= total_packets + 1; if (pack_valid_data == 32'hCC33_AA55) data_count <= 0; else if (pack_valid_data[31:8] == 24'h55_AA33) begin current_chan <= pack_valid_data[7:0]; $display("[%t] Frame Start detected for Channel %0d", $time, pack_valid_data[7:0]); end else if (pack_valid_data == 32'h7E7E_7E7E) $display("[%t] Frame End detected for Channel %0d (Data count: %0d)", $time, current_chan, data_count); else data_count <= data_count + 1; end end

6. 完整代码

6.1 ad_pack.v

module ad_pack#( parameter pack_length = 32'd32012 )( input clk_adc , input clk_trans , input rst , input trans_data_sel , //传输数据选择 input Acquisition_en , //上位机下发采集使能 input [31:0] Acquisition_delay , //上位机下发的帧与帧之间延时 input [7:0] ddc_en , input [255:0] ddc_data , input dma_fifo_progfull , output pack_valid_en , output [31:0] pack_valid_data ); /************************ reg *********************/ reg ro_pack_valid_en ; reg [31:0] ro_pack_valid_data ; reg [7:0] r_pack_ddc_en ; reg [255:0] r_pack_ddc_data ; reg [31:0] r_sample_cnt ; reg [7:0] r_rd_fifo_ready ; reg [7:0] r_chan_cnt ; reg [31:0] r_data_cnt ; reg r_fifo_rst ; reg [15:0] r_cnt ; /************************ wire *********************/ wire i_clk ; wire i_rst ; wire w_data_trans_start ; wire w_data_valid ; wire [7:0] w_fifo_prog_full ; wire [7:0] w_fifo_prog_empty ; wire [7:0] w_fifo_full ; wire [7:0] w_ddc_pack_en ; wire [31:0] w_ddc_pack_data[7:0] ; wire [7:0] w_ddc_pack ; wire w_ad_pack_valid ; wire [31:0] w_data_rate ; wire [7:0] w_wr_rst_busy ; /************************ parameter ***********************/ localparam P_PACK_LENGTH = pack_length ; /************************ FSM *********************/ reg [ (4-1):0] state_c ; reg [ (4-1):0] state_c_dly0 ; reg [ (4-1):0] state_c_dly1 ; reg [ (4-1):0] state_n ; parameter P_ST_IDLE = 0 ; parameter P_ST_READ = 1 ; parameter P_ST_JUDGE = 2 ; parameter P_ST_END = 3 ; always @(posedge i_clk) begin if (i_rst) state_c <= P_ST_IDLE; else state_c <= state_n; end always @(*) begin case(state_c) P_ST_IDLE : if(p_st_idle2p_st_read_start) state_n = P_ST_READ; else state_n = state_c; P_ST_READ : if(p_st_read2p_st_judge_start) state_n = P_ST_JUDGE; else if(p_st_read2p_st_end_start) state_n = P_ST_END; else state_n = state_c; P_ST_JUDGE: if(p_st_judge2p_st_read_start) state_n = P_ST_READ; else state_n = state_c; P_ST_END : if(p_st_end2p_st_idle_start) state_n = P_ST_IDLE; else state_n = state_c; default : state_n = P_ST_IDLE; endcase end assign p_st_idle2p_st_read_start = state_c==P_ST_IDLE && (w_fifo_prog_full[0]); assign p_st_read2p_st_judge_start = state_c==P_ST_READ && (r_data_cnt == P_PACK_LENGTH && r_chan_cnt != 'd7); assign p_st_read2p_st_end_start = state_c==P_ST_READ && (r_data_cnt == P_PACK_LENGTH && r_chan_cnt == 'd7); assign p_st_judge2p_st_read_start = state_c==P_ST_JUDGE && (1); assign p_st_end2p_st_idle_start = state_c==P_ST_END && (r_cnt == 'd1000); /************************ combinational logic *******************/ assign i_clk = clk_trans ; assign i_rst = rst ; assign w_ad_pack_valid = &(~w_wr_rst_busy ) ; assign pack_valid_en = ro_pack_valid_en && w_ddc_pack[r_chan_cnt] ; assign pack_valid_data = ro_pack_valid_data ; /************************ submodules *******************/ singal_cfg singal_cfg_u0( .clk ( clk_adc ), .rst ( i_rst || r_fifo_rst ), .Acquisition_en ( Acquisition_en ), .Acquisition_delay ( Acquisition_delay ), .data_trans_start ( w_data_trans_start ), .data_valid ( w_data_valid ) ); genvar i; generate for (i=0; i < 8; i=i+1) begin: pack ad_chan_pack ad_chan_pack_u0( .clk_adc ( clk_adc ), .clk_trans ( clk_trans ), .rst ( i_rst || r_fifo_rst ), .fifo_rst ( r_fifo_rst ), .data_trans_start ( w_data_trans_start ), .data_valid ( w_data_valid ), .trans_data_sel ( trans_data_sel ), .ad_pack_valid ( w_ad_pack_valid ), .adc_en ( r_pack_ddc_en[i] ), .adc_data ( r_pack_ddc_data[32*i +: 32] ), .sample_cnt ( r_sample_cnt ), .chan_num ( i ), .rd_fifo_ready ( r_rd_fifo_ready[i] ), .pack_fifo_prog_full ( w_fifo_prog_full[i] ), .pack_fifo_prog_empty ( w_fifo_prog_empty[i] ), .pack_fifo_full ( w_fifo_full[i] ), .pack_valid_en ( w_ddc_pack_en[i] ), .pack_valid_data ( w_ddc_pack_data[i] ), .pack_valid ( w_ddc_pack[i] ), .w_wr_rst_busy ( w_wr_rst_busy[i] ) ); end endgenerate data_rate data_rate_u0( .clk_100m (clk_trans ), .clk (clk_trans ), .reset (rst ), .rx_valid (ro_pack_valid_en ), .data_rate (w_data_rate ) ); /************************ always blocks ***********************/ // ro_pack_valid_en always @(posedge i_clk) begin if(i_rst || r_fifo_rst) ro_pack_valid_en <= 'd0; else if (state_c == P_ST_END) ro_pack_valid_en <= 'd0; else if(w_ddc_pack_en == 8'b0000_0001 || w_ddc_pack_en == 8'b0000_0010 || w_ddc_pack_en == 8'b0000_0100 || w_ddc_pack_en == 8'b0000_1000 || w_ddc_pack_en == 8'b0001_0000 || w_ddc_pack_en == 8'b0010_0000 || w_ddc_pack_en == 8'b0100_0000 || w_ddc_pack_en == 8'b1000_0000) ro_pack_valid_en <= 'd1; else ro_pack_valid_en <= 'd0; end // ro_pack_valid_data always @(posedge i_clk) begin if(i_rst || r_fifo_rst) ro_pack_valid_data <= 'd0; else if (state_c == P_ST_END) ro_pack_valid_data <= 'd0; else if (state_c == P_ST_READ) begin case(w_ddc_pack_en) 8'b0000_0001: ro_pack_valid_data <= w_ddc_pack_data[0]; 8'b0000_0010: ro_pack_valid_data <= w_ddc_pack_data[1]; 8'b0000_0100: ro_pack_valid_data <= w_ddc_pack_data[2]; 8'b0000_1000: ro_pack_valid_data <= w_ddc_pack_data[3]; 8'b0001_0000: ro_pack_valid_data <= w_ddc_pack_data[4]; 8'b0010_0000: ro_pack_valid_data <= w_ddc_pack_data[5]; 8'b0100_0000: ro_pack_valid_data <= w_ddc_pack_data[6]; 8'b1000_0000: ro_pack_valid_data <= w_ddc_pack_data[7]; default : ro_pack_valid_data <= 'd0; endcase end else ro_pack_valid_data <= 'd0; end // state_c_dly0 & state_c_dly1 (sync to clk_adc domain) always @(posedge clk_adc) begin if(i_rst) state_c_dly0 <= 'd0; else state_c_dly0 <= state_c; end always @(posedge clk_adc) begin if(i_rst) state_c_dly1 <= 'd0; else state_c_dly1 <= state_c_dly0; end // r_pack_ddc_en always @(posedge clk_adc) begin if (state_c_dly1 == P_ST_END) r_pack_ddc_en <= 8'b0000_0000; else if (state_c_dly1 == P_ST_READ && r_chan_cnt == 'd7) r_pack_ddc_en <= 8'b0000_0000; else r_pack_ddc_en <= ddc_en; end // r_pack_ddc_data always @(posedge clk_adc) begin r_pack_ddc_data <= ddc_data; end // r_sample_cnt always @(posedge clk_adc) begin if(i_rst || r_fifo_rst) r_sample_cnt <= 'd0; else if(Acquisition_en && r_pack_ddc_en) r_sample_cnt <= r_sample_cnt + 1; else r_sample_cnt <= r_sample_cnt; end // r_rd_fifo_ready always @(posedge i_clk) begin if(i_rst || r_fifo_rst) r_rd_fifo_ready <= 'd0; else if(state_c == P_ST_IDLE) r_rd_fifo_ready <= 'd0; else if(state_c == P_ST_JUDGE) r_rd_fifo_ready <= 'd0; else if(state_c == P_ST_READ && dma_fifo_progfull) r_rd_fifo_ready[r_chan_cnt] <= 1'b0; else if(state_c == P_ST_READ && dma_fifo_progfull == 'd0 && r_data_cnt == P_PACK_LENGTH) r_rd_fifo_ready[r_chan_cnt] <= 1'b0; else if(state_c == P_ST_READ && dma_fifo_progfull == 'd0 && r_data_cnt != P_PACK_LENGTH) r_rd_fifo_ready[r_chan_cnt] <= 1'b1; else if (state_c == P_ST_END) r_rd_fifo_ready <= 'd0; else r_rd_fifo_ready <= r_rd_fifo_ready; end // r_chan_cnt always @(posedge i_clk) begin if(i_rst || r_fifo_rst) r_chan_cnt <= 'd0; else if(state_c == P_ST_IDLE) r_chan_cnt <= 'd0; else if(state_c == P_ST_READ && r_data_cnt == P_PACK_LENGTH && r_chan_cnt == 'd7) r_chan_cnt <= 'd0; else if(state_c == P_ST_READ && r_data_cnt == P_PACK_LENGTH && r_chan_cnt != 'd7) r_chan_cnt <= r_chan_cnt + 1; else if(state_c == P_ST_END) r_chan_cnt <= 'd0; else r_chan_cnt <= r_chan_cnt; end // r_data_cnt always @(posedge i_clk) begin if(i_rst || r_fifo_rst) r_data_cnt <= 'd0; else if(state_c == P_ST_IDLE) r_data_cnt <= 'd0; else if(state_c == P_ST_JUDGE) r_data_cnt <= 'd0; else if(state_c == P_ST_READ && dma_fifo_progfull == 'd0 && r_data_cnt == P_PACK_LENGTH) r_data_cnt <= 'd0; else if(state_c == P_ST_READ && dma_fifo_progfull == 'd0 && r_data_cnt != P_PACK_LENGTH && r_pack_ddc_en[r_chan_cnt] && w_ddc_pack[r_chan_cnt] && r_chan_cnt != 'd7) r_data_cnt <= r_data_cnt + 1; else if(state_c == P_ST_READ && dma_fifo_progfull == 'd0 && r_data_cnt != P_PACK_LENGTH && w_ddc_pack[r_chan_cnt] && r_chan_cnt == 'd7) r_data_cnt <= r_data_cnt + 1; else if(state_c == P_ST_END) r_data_cnt <= 'd0; else r_data_cnt <= r_data_cnt; end // r_fifo_rst always @(posedge i_clk) begin if(i_rst) r_fifo_rst <= 'd1; else if (state_c == P_ST_END && r_cnt < 'd10) r_fifo_rst <= 'd1; else r_fifo_rst <= 'd0; end // r_cnt always @(posedge i_clk) begin if(i_rst) r_cnt <= 'd0; else if(r_cnt == 'd1000) r_cnt <= 'd0; else if(state_c == P_ST_END) r_cnt <= r_cnt + 1; else r_cnt <= r_cnt; end endmodule

6.2 tb_ad_pack.v

`timescale 1ns/1ps module tb_ad_pack(); //====================== 参数定义 ====================== `define CLK_ADC_PERIOD 10 // ADC时钟 100MHz `define CLK_TRANS_PERIOD 20 // 传输时钟 50MHz //====================== 接口信号 ====================== reg clk_adc; reg clk_trans; reg rst; reg trans_data_sel; reg Acquisition_en; reg [31:0] Acquisition_delay; reg [7:0] ddc_en; reg [255:0] ddc_data; reg dma_fifo_progfull; wire pack_valid_en; wire [31:0] pack_valid_data; reg [31:0] error_cnt; reg [7:0] current_chan; reg [31:0] data_count; reg [31:0] total_packets; reg fifo_full_sim; wire [4:0] singal_cfg_state; wire [8:0] ad_chan_pack_state; wire [7:0] rd_fifo_ready; //====================== 时钟生成 ====================== initial begin clk_adc = 1'b0; forever #(`CLK_ADC_PERIOD/2) clk_adc = ~clk_adc; end initial begin clk_trans = 1'b0; forever #(`CLK_TRANS_PERIOD/2) clk_trans = ~clk_trans; end //====================== 复位生成 ====================== initial begin rst = 1'b1; #100; rst = 1'b0; end //====================== 激励信号 ====================== initial begin Acquisition_en = 0; Acquisition_delay = 0; trans_data_sel = 1; ddc_en = 8'h00; ddc_data = 256'h0; dma_fifo_progfull = 0; error_cnt = 0; current_chan = 0; data_count = 0; total_packets = 0; fifo_full_sim = 0; wait(rst == 1'b0); #100; Acquisition_delay = 32'd10; $display("[%t] Testbench: Acquisition Enabled", $time); Acquisition_en = 1; #25_000; ddc_en = 8'hFF; #50_000; $display("[%t] Testbench: Simulating downstream FIFO almost full", $time); dma_fifo_progfull = 1; #100_000; dma_fifo_progfull = 0; #24000_000; if (error_cnt == 0) begin $display("[%t] Testbench: Simulation PASSED - Total packets: %0d", $time, total_packets); end else begin $display("[%t] Testbench: Simulation FAILED with %0d errors", $time, error_cnt); end #100; $stop; end //====================== ADC数据模拟 ====================== always @(posedge clk_adc or posedge rst) begin if(rst) ddc_data <= 256'h00010001_00010001_00010001_00010001_00010001_00010001_00010001_00010001; else if(Acquisition_en && ddc_en) ddc_data <= ddc_data + 256'h00010001_00010001_00010001_00010001_00010001_00010001_00010001_00010001; end //====================== 仿真监控 ====================== initial begin $display("====================================="); $display("Vivado 2022.1 仿真启动 - ad_pack 模块测试(含流控)"); $display("====================================="); $monitor("Time:%0tns | Acquisition_en:%b | dma_fifo_progfull:%b | pack_valid_en:%b | pack_valid_data:%h | singal_cfg_state:%b | ad_chan_pack_state:%b | rd_fifo_ready:%b", $time, Acquisition_en, dma_fifo_progfull, pack_valid_en, pack_valid_data, singal_cfg_state, ad_chan_pack_state, rd_fifo_ready); end // 数据验证逻辑 always @(posedge clk_trans) begin if (!rst && pack_valid_en) begin total_packets <= total_packets + 1; if (pack_valid_data == 32'hCC33_AA55) begin data_count <= 0; end else if (pack_valid_data[31:8] == 24'h55_AA33) begin current_chan <= pack_valid_data[7:0]; $display("[%t] Frame Start detected for Channel %0d", $time, pack_valid_data[7:0]); end else if (pack_valid_data == 32'h7E7E_7E7E) begin $display("[%t] Frame End detected for Channel %0d (Data count: %0d)", $time, current_chan, data_count); end else begin data_count <= data_count + 1; end end end // 状态监控 assign singal_cfg_state = dut.singal_cfg_u0.state_c; assign ad_chan_pack_state = dut.pack[0].ad_chan_pack_u0.state_c; assign rd_fifo_ready = dut.r_rd_fifo_ready; endmodule

7. 仿真结果与波形分析

仿真运行后,我们期望看到:

  1. Acquisition_en有效后,singal_cfg产生data_trans_start脉冲。
  2. ad_chan_pack开始打包,FIFO逐渐填满。
  3. 顶层状态机从 IDLE → READ,轮询通道0~7,每个通道连续读出pack_length个数据。
  4. 输出数据流中,帧头、通道标识、帧尾依次出现。
  5. dma_fifo_progfull拉高时,rd_fifo_ready相应通道位被清零,暂停读取;拉低后恢复。
  6. 所有通道读完一帧后,进入END状态,等待1000周期后复位FIFO,回到IDLE。

通过波形或打印信息,可以验证数据正确性。在测试中,我们使用了测试数据(trans_data_sel=1),因此每个通道的数据是递增的,便于检查。


8. 总结与优化方向

ad_pack模块作为多通道聚合的核心,巧妙地复用了单通道打包单元,通过状态机轮询实现了高效的数据合并。其设计特点包括:

  • 参数化:可配置帧长度,适应不同应用。
  • 可测试性:内置测试模式,减少外部依赖。
  • 流控完善:支持下游反压,保证数据不丢失。
  • 模块化:各子模块功能单一,易于维护和替换。

可优化的地方

  • 当前IDLE状态仅检测通道0的prog_full作为启动条件,更严谨的做法是检测所有通道的prog_fullempty信号,确保所有通道都有数据才开始读。
  • r_fifo_rst在END状态持续10个周期,可能影响后续启动,可根据实际FIFO复位时间调整。
  • 帧间延时由singal_cfg和顶层r_cnt共同控制,存在冗余,可统一管理。

总体而言,该模块已在FPGA项目中成功应用,稳定可靠。希望本文能帮助读者理解多通道数据聚合的设计思路。


附:本设计基于Xilinx Vivado 环境开发,子模块ad_chan_packsingal_cfgdata_rate的代码请参考本系列其他文章。如有疑问,欢迎留言交流。

http://www.jsqmd.com/news/1079058/

相关文章:

  • 云手机好用吗?直击三大痛点,普通人也能看懂的入坑指南CSDN
  • Web安全十大核心漏洞原理与防御实战指南
  • GAT注意力权重可视化实战:从公式到热力图
  • 低代码开发你会用吗?
  • 傅里叶级数收敛性反例:二进尖峰块与拉库纳序列构造解析
  • 035、LLVM Dialect:与LLVM IR的桥梁
  • 分享股票方面的API
  • 2026 电商客服外包公司哪家好?5 家头部服务商深度盘点,企业选型必备
  • 大气层整合包系统:终极Nintendo Switch定制固件完全指南
  • 微盟星启GEO竞争分析:洞察行业格局抢占AI搜索先机
  • [特殊字符] Spring MVC 四大参数注解笔记
  • 关于威尼斯系统检测注单尚未同步提不了怎么解决
  • 【Three.js 实战】结合 MediaPipe 实现 3D 粒子手势互动特效 (附原理解析)--手势控制粒子项目,附源码
  • 希迪迈向“重载具身智能”,AI改变物理世界有了新注解
  • OpenClaw+Kimi本地智能体工作流:多模态动作闭环实战指南
  • Claude 怎么用?网页端、API、第三方工具有什么区别
  • 数据库统计信息备份与还原技术实践
  • 063、Zephyr RTOS内核基础:内存管理之内存池
  • 2026年GEO优化系统源码怎么选?这份实操指南请收好
  • 从零开始打造你的《最终幻想14》专属外观:FFXIV TexTools完整使用指南
  • 2022年5月AI工程落地关键突破:LoRA、FlashAttention与QLoRA实战解析
  • COUNT(*)到底能不能走索引?覆盖索引的3个误区与4种优化方案
  • SAP-ABAP:SAP Process Orchestration 7.50 入门简介:PO核心概念、架构定位与版本演进
  • 2026年深圳AI定制服务商观察:案例复用能力为何越来越重要?
  • 深入拆解Agent核心:系统提示词与用户提示词的本质区别、工程落地与全场景避坑指南
  • 行业语言大模型体验榜2026:谁真正懂你的语音需求
  • 线上Prompt改一版就翻车怎么快速回滚
  • 其实APP宣传成本最低的方式是:电子海报---POP广告
  • 华为数通vs云计算认证:2026选哪个?我跟两个方向的从业者聊了聊
  • TAI 134合规实操指南:模型扩散管控与API服务落地七项检查