这个项目是我完整详细介绍带有 ADC 和 DAC ZMOD 的 Eclypse Z7 的延续。正如我在那个项目中提到的那样,我通过将 DDS Complier IP 模块集成到模块设计中并使用它来生成数字 1 MHz 正弦波数据以供 DAC ZMOD 输出到其通道之一,从而添加到设计中。为了验证数据,它将在 ADC ZMOD 的一个通道上读取。我还将把它连接到我的信号分析仪上,看看物理 1MHz 波是什么样子的。
首先,我将 DAC ZMOD 的第一个通道连接到 ADC ZMOD 的第一个通道:
作为参考的旁注,我再次使用 Vivado 和 Vitis 2019.2 版,并且我使用的项目与我在上一篇关于Eclypse Z7 的项目帖子中详细介绍了如何创建的项目完全相同。
从 Vivado 中现有的硬件设计开始,首先需要修改的是块设计。我非常喜欢使用 DDS 编译器生成正弦波,因为它们是结构资源利用率和输出波精度之间的最佳折衷。
打开模块设计并将 DDS 编译器添加到 IP 模块设计,双击它以打开其定制窗口。
使用 DDS 编译器 IP 的最大优势之一是在更改输出信号的频率/相位时平滑/无缝过渡(因此您不必担心相位不连续)。这就是为什么我喜欢使用流选项来实现输出频率/相位的可编程性。
我现在选择只关注更改输出频率(通过相位增量可编程性)并将相位偏移可编程性设置为无:
对于带有 DMA 的 AXI 流协议与 DAC ZMOD 接口以将正弦波写出,流需要被打包,输出tready,并在正弦波的每个周期结束时断言 tlast。在 Data has TLAST 下选择“Packet Framing”选项,并选中“Output TREADY”复选框。
DDS 编译器会将其输出写入 Eclypse 的 DDR 内存,供 DAC 通过 DMA 引擎读取。为了使集成尽可能简单,我只是为 DDS 的输出启用了 DMA 引擎的写入通道。由于数据一次传输一个周期的正弦波,因此还需要为写入通道启用 DRE(数据重新对齐引擎)。选中写入通道的“允许未对齐传输”框(仅适用于写入通道,因为裸机 Zmod 库负责对齐数据以在读取通道上传输)。
手动将 DDS 编译器的 M_AXIS_DATA 输出连接到 DAC 的 DMA 引擎的 S_AXIS_S2MM。DMA 的 S_AXIS_S2MM 端口的 tkeep 信号需要在其总线上保持高电平,以表示所有传入数据都是有效的(包括任何零值数据字节)。为此,在设计中添加一个常量 IP 块,将输出宽度设置为与 DMA 的 S_AXIS_S2MM 端口的 tkeep 匹配,并将该值设置为总线上的高电平(在这种情况下,tkeep 信号为两位宽,因此常数值将设置为 3,即 2'b11)。然后手动将其输出连接到 DMA 的 S_AXIS_S2MM 端口。
我认为从 Zynq 芯片的可编程逻辑控制 DDS 编译器的输入会更容易。为了做到这一点,DDS 编译器的相位输入端口需要在框图外部可用,方法是右键单击端口名称并选择“Make external”选项。您将看到 DDS 编译器输入的 AXI 流总线端口出现。
来自 Zynq 处理系统的 FCLK_CLK1 时钟也需要引出到模块设计中的端口,以便在 Zynq 芯片的可编程逻辑中可供 HDL 使用。只需右键单击 Zynq 处理系统 IP 块上的 FCLK_CLK1 端口名称并选择“创建端口...”:
块设计现在应该类似于以下内容:
后面要添加逻辑分析仪进行调试,标记DDS的相位输入,连同DDS数据输出,DAC的DMA的AXIS_MM2S和ADC的DMA的AXIS_S2MM进行调试。只需右键单击每一行并选择“调试”:
片刻之后,将出现连接自动化选项。选择它并确保选中 AXI 协议检查器选项的复选框。
连接自动化完成运行后,验证模块设计以检查是否存在任何错误或严重警告,然后保存并关闭它。
由于已添加新的外部端口,因此需要为模块设计创建新的 HDL 实例化。只需右键单击 Sources Hierarchy 选项卡中的块设计文件并选择“Create HDL Wrapper...”选项即可完成此操作。它只会使用新的框图实例化更新现有的框图(就像在上一个项目中一样,选择让 Vivado 自动更新和管理它的选项)。
然而,由于没有一个新的外部端口将路由到 Zynq 芯片上的实际封装引脚,并且还需要添加其他自定义 HDL,因此需要从头开始创建一个新的顶级文件。即使有一个选项允许用户在最后一步管理包装器,我发现使用 Vivado 最好始终选择自动管理选项,然后从头开始创建自己的顶级文件并简单地复制+粘贴来自自动生成的包装器的框图实例化。
对于这个设计,我正在创建我自己的三个设计源:新的自定义顶层文件、用于生成相位增量值以发送到 DDS 编译器的逻辑,以及用于 AXI 流协议以与 DDS 编译器通信的状态机.
从 Flow Navigator 中选择 Add Sources 选项,然后在弹出窗口中选择 Add or create design sources。然后,该窗口将为您提供创建所需文件数量的选项(在本例中为三个 Verilog 模块文件)。
我将顶层文件命名为“eclypse_top”,将相位增量逻辑文件命名为“bb_logic”,并将 AXI 流状态机文件命名为“axis_sm”。
顶层文件主要是为块设计实例化自动生成的 design_wrapper 文件的复制 + 粘贴。它还将实例化相位增量逻辑模块。请注意,用于 DDS 编译器和 FCLK_CLK1 的从 AXI 流信号从模块端口说明符中被注释掉,因为它们被重定向到相位增量逻辑模块,而不是被路由到 Zynq 芯片上的封装引脚。然后相位增量逻辑模块负责例化 AXI 流状态机模块。
自定义顶级文件 Verilog:
module eclypse_top(
inout [14:0]DDR_addr,
inout [2:0]DDR_ba,
inout DDR_cas_n,
inout DDR_ck_n,
inout DDR_ck_p,
inout DDR_cke,
inout DDR_cs_n,
inout [3:0]DDR_dm,
inout [31:0]DDR_dq,
inout [3:0]DDR_dqs_n,
inout [3:0]DDR_dqs_p,
inout DDR_odt,
inout DDR_ras_n,
inout DDR_reset_n,
inout DDR_we_n,
// input [31:0]DDS_S_AXIS_PHASE_tdata,
// input DDS_S_AXIS_PHASE_tlast,
// output DDS_S_AXIS_PHASE_tready,
// input DDS_S_AXIS_PHASE_tvalid,
input DcoClk_0,
// output FCLK_CLK1,
inout FIXED_IO_ddr_vrn,
inout FIXED_IO_ddr_vrp,
inout [53:0]FIXED_IO_mio,
inout FIXED_IO_ps_clk,
inout FIXED_IO_ps_porb,
inout FIXED_IO_ps_srstb,
output adcClkIn_n_0,
output adcClkIn_p_0,
output adcSync_0,
input [1:0]btn_2bits_tri_i,
input [13:0]dADC_Data_0,
inout pmod_ja_pin10_io,
inout pmod_ja_pin1_io,
inout pmod_ja_pin2_io,
inout pmod_ja_pin3_io,
inout pmod_ja_pin4_io,
inout pmod_ja_pin7_io,
inout pmod_ja_pin8_io,
inout pmod_ja_pin9_io,
inout pmod_jb_pin10_io,
inout pmod_jb_pin1_io,
inout pmod_jb_pin2_io,
inout pmod_jb_pin3_io,
inout pmod_jb_pin4_io,
inout pmod_jb_pin7_io,
inout pmod_jb_pin8_io,
inout pmod_jb_pin9_io,
output [5:0]rgbled_6bits_tri_o,
output sADC_CS_0,
inout sADC_SDIO_0,
output sADC_Sclk_0,
output sCh1CouplingH_0,
output sCh1CouplingL_0,
output sCh1GainH_0,
output sCh1GainL_0,
output sCh2CouplingH_0,
output sCh2CouplingL_0,
output sCh2GainH_0,
output sCh2GainL_0,
output sDAC_CS_0,
output sDAC_ClkIO_0,
output sDAC_Clkin_0,
output [13:0]sDAC_Data_0,
output sDAC_EnOut_0,
output sDAC_Reset_0,
output sDAC_SCLK_0,
inout sDAC_SDIO_0,
output sDAC_SetFS1_0,
output sDAC_SetFS2_0,
output sRelayComH_0,
output sRelayComL_0,
input sys_clock
);
wire FCLK_CLK1;
wire [31:0]DDS_S_AXIS_PHASE_tdata;
wire DDS_S_AXIS_PHASE_tlast;
wire DDS_S_AXIS_PHASE_tready;
wire DDS_S_AXIS_PHASE_tvalid;
// block diagram instantiation
design_1 design_1_i(
.DDR_addr(DDR_addr),
.DDR_ba(DDR_ba),
.DDR_cas_n(DDR_cas_n),
.DDR_ck_n(DDR_ck_n),
.DDR_ck_p(DDR_ck_p),
.DDR_cke(DDR_cke),
.DDR_cs_n(DDR_cs_n),
.DDR_dm(DDR_dm),
.DDR_dq(DDR_dq),
.DDR_dqs_n(DDR_dqs_n),
.DDR_dqs_p(DDR_dqs_p),
.DDR_odt(DDR_odt),
.DDR_ras_n(DDR_ras_n),
.DDR_reset_n(DDR_reset_n),
.DDR_we_n(DDR_we_n),
.DDS_S_AXIS_PHASE_tdata(DDS_S_AXIS_PHASE_tdata),
.DDS_S_AXIS_PHASE_tlast(DDS_S_AXIS_PHASE_tlast),
.DDS_S_AXIS_PHASE_tready(DDS_S_AXIS_PHASE_tready),
.DDS_S_AXIS_PHASE_tvalid(DDS_S_AXIS_PHASE_tvalid),
.DcoClk_0(DcoClk_0),
.FCLK_CLK1(FCLK_CLK1),
.FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
.FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
.FIXED_IO_mio(FIXED_IO_mio),
.FIXED_IO_ps_clk(FIXED_IO_ps_clk),
.FIXED_IO_ps_porb(FIXED_IO_ps_porb),
.FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
.adcClkIn_n_0(adcClkIn_n_0),
.adcClkIn_p_0(adcClkIn_p_0),
.adcSync_0(adcSync_0),
.btn_2bits_tri_i(btn_2bits_tri_i),
.dADC_Data_0(dADC_Data_0),
.pmod_ja_pin10_i(pmod_ja_pin10_i),
.pmod_ja_pin10_o(pmod_ja_pin10_o),
.pmod_ja_pin10_t(pmod_ja_pin10_t),
.pmod_ja_pin1_i(pmod_ja_pin1_i),
.pmod_ja_pin1_o(pmod_ja_pin1_o),
.pmod_ja_pin1_t(pmod_ja_pin1_t),
.pmod_ja_pin2_i(pmod_ja_pin2_i),
.pmod_ja_pin2_o(pmod_ja_pin2_o),
.pmod_ja_pin2_t(pmod_ja_pin2_t),
.pmod_ja_pin3_i(pmod_ja_pin3_i),
.pmod_ja_pin3_o(pmod_ja_pin3_o),
.pmod_ja_pin3_t(pmod_ja_pin3_t),
.pmod_ja_pin4_i(pmod_ja_pin4_i),
.pmod_ja_pin4_o(pmod_ja_pin4_o),
.pmod_ja_pin4_t(pmod_ja_pin4_t),
.pmod_ja_pin7_i(pmod_ja_pin7_i),
.pmod_ja_pin7_o(pmod_ja_pin7_o),
.pmod_ja_pin7_t(pmod_ja_pin7_t),
.pmod_ja_pin8_i(pmod_ja_pin8_i),
.pmod_ja_pin8_o(pmod_ja_pin8_o),
.pmod_ja_pin8_t(pmod_ja_pin8_t),
.pmod_ja_pin9_i(pmod_ja_pin9_i),
.pmod_ja_pin9_o(pmod_ja_pin9_o),
.pmod_ja_pin9_t(pmod_ja_pin9_t),
.pmod_jb_pin10_i(pmod_jb_pin10_i),
.pmod_jb_pin10_o(pmod_jb_pin10_o),
.pmod_jb_pin10_t(pmod_jb_pin10_t),
.pmod_jb_pin1_i(pmod_jb_pin1_i),
.pmod_jb_pin1_o(pmod_jb_pin1_o),
.pmod_jb_pin1_t(pmod_jb_pin1_t),
.pmod_jb_pin2_i(pmod_jb_pin2_i),
.pmod_jb_pin2_o(pmod_jb_pin2_o),
.pmod_jb_pin2_t(pmod_jb_pin2_t),
.pmod_jb_pin3_i(pmod_jb_pin3_i),
.pmod_jb_pin3_o(pmod_jb_pin3_o),
.pmod_jb_pin3_t(pmod_jb_pin3_t),
.pmod_jb_pin4_i(pmod_jb_pin4_i),
.pmod_jb_pin4_o(pmod_jb_pin4_o),
.pmod_jb_pin4_t(pmod_jb_pin4_t),
.pmod_jb_pin7_i(pmod_jb_pin7_i),
.pmod_jb_pin7_o(pmod_jb_pin7_o),
.pmod_jb_pin7_t(pmod_jb_pin7_t),
.pmod_jb_pin8_i(pmod_jb_pin8_i),
.pmod_jb_pin8_o(pmod_jb_pin8_o),
.pmod_jb_pin8_t(pmod_jb_pin8_t),
.pmod_jb_pin9_i(pmod_jb_pin9_i),
.pmod_jb_pin9_o(pmod_jb_pin9_o),
.pmod_jb_pin9_t(pmod_jb_pin9_t),
.rgbled_6bits_tri_o(rgbled_6bits_tri_o),
.sADC_CS_0(sADC_CS_0),
.sADC_SDIO_0(sADC_SDIO_0),
.sADC_Sclk_0(sADC_Sclk_0),
.sCh1CouplingH_0(sCh1CouplingH_0),
.sCh1CouplingL_0(sCh1CouplingL_0),
.sCh1GainH_0(sCh1GainH_0),
.sCh1GainL_0(sCh1GainL_0),
.sCh2CouplingH_0(sCh2CouplingH_0),
.sCh2CouplingL_0(sCh2CouplingL_0),
.sCh2GainH_0(sCh2GainH_0),
.sCh2GainL_0(sCh2GainL_0),
.sDAC_CS_0(sDAC_CS_0),
.sDAC_ClkIO_0(sDAC_ClkIO_0),
.sDAC_Clkin_0(sDAC_Clkin_0),
.sDAC_Data_0(sDAC_Data_0),
.sDAC_EnOut_0(sDAC_EnOut_0),
.sDAC_Reset_0(sDAC_Reset_0),
.sDAC_SCLK_0(sDAC_SCLK_0),
.sDAC_SDIO_0(sDAC_SDIO_0),
.sDAC_SetFS1_0(sDAC_SetFS1_0),
.sDAC_SetFS2_0(sDAC_SetFS2_0),
.sRelayComH_0(sRelayComH_0),
.sRelayComL_0(sRelayComL_0),
.sys_clock(sys_clock));
IOBUF pmod_ja_pin10_iobuf(
.I(pmod_ja_pin10_o),
.IO(pmod_ja_pin10_io),
.O(pmod_ja_pin10_i),
.T(pmod_ja_pin10_t));
IOBUF pmod_ja_pin1_iobuf(
.I(pmod_ja_pin1_o),
.IO(pmod_ja_pin1_io),
.O(pmod_ja_pin1_i),
.T(pmod_ja_pin1_t));
IOBUF pmod_ja_pin2_iobuf(
.I(pmod_ja_pin2_o),
.IO(pmod_ja_pin2_io),
.O(pmod_ja_pin2_i),
.T(pmod_ja_pin2_t));
IOBUF pmod_ja_pin3_iobuf(
.I(pmod_ja_pin3_o),
.IO(pmod_ja_pin3_io),
.O(pmod_ja_pin3_i),
.T(pmod_ja_pin3_t));
IOBUF pmod_ja_pin4_iobuf(
.I(pmod_ja_pin4_o),
.IO(pmod_ja_pin4_io),
.O(pmod_ja_pin4_i),
.T(pmod_ja_pin4_t));
IOBUF pmod_ja_pin7_iobuf(
.I(pmod_ja_pin7_o),
.IO(pmod_ja_pin7_io),
.O(pmod_ja_pin7_i),
.T(pmod_ja_pin7_t));
IOBUF pmod_ja_pin8_iobuf(
.I(pmod_ja_pin8_o),
.IO(pmod_ja_pin8_io),
.O(pmod_ja_pin8_i),
.T(pmod_ja_pin8_t));
IOBUF pmod_ja_pin9_iobuf(
.I(pmod_ja_pin9_o),
.IO(pmod_ja_pin9_io),
.O(pmod_ja_pin9_i),
.T(pmod_ja_pin9_t));
IOBUF pmod_jb_pin10_iobuf(
.I(pmod_jb_pin10_o),
.IO(pmod_jb_pin10_io),
.O(pmod_jb_pin10_i),
.T(pmod_jb_pin10_t));
IOBUF pmod_jb_pin1_iobuf(
.I(pmod_jb_pin1_o),
.IO(pmod_jb_pin1_io),
.O(pmod_jb_pin1_i),
.T(pmod_jb_pin1_t));
IOBUF pmod_jb_pin2_iobuf(
.I(pmod_jb_pin2_o),
.IO(pmod_jb_pin2_io),
.O(pmod_jb_pin2_i),
.T(pmod_jb_pin2_t));
IOBUF pmod_jb_pin3_iobuf(
.I(pmod_jb_pin3_o),
.IO(pmod_jb_pin3_io),
.O(pmod_jb_pin3_i),
.T(pmod_jb_pin3_t));
IOBUF pmod_jb_pin4_iobuf(
.I(pmod_jb_pin4_o),
.IO(pmod_jb_pin4_io),
.O(pmod_jb_pin4_i),
.T(pmod_jb_pin4_t));
IOBUF pmod_jb_pin7_iobuf(
.I(pmod_jb_pin7_o),
.IO(pmod_jb_pin7_io),
.O(pmod_jb_pin7_i),
.T(pmod_jb_pin7_t));
IOBUF pmod_jb_pin8_iobuf(
.I(pmod_jb_pin8_o),
.IO(pmod_jb_pin8_io),
.O(pmod_jb_pin8_i),
.T(pmod_jb_pin8_t));
IOBUF pmod_jb_pin9_iobuf(
.I(pmod_jb_pin9_o),
.IO(pmod_jb_pin9_io),
.O(pmod_jb_pin9_i),
.T(pmod_jb_pin9_t));
// phase increment logic module instantiation
bb_logic bb_logic_i(
.clk(FCLK_CLK1),
.DDS_S_AXIS_PHASE_tdata(DDS_S_AXIS_PHASE_tdata),
.DDS_S_AXIS_PHASE_tlast(DDS_S_AXIS_PHASE_tlast),
.DDS_S_AXIS_PHASE_tready(DDS_S_AXIS_PHASE_tready),
.DDS_S_AXIS_PHASE_tvalid(DDS_S_AXIS_PHASE_tvalid));
endmodule
对于 DDS 的输出频率,我暂时选择对其进行硬核,以确保模块设计中的 DDS 编译器集成是正确的。由于 DAC 和 ADC 的最大采样率为 100Ms/s,因此按照 Nyquist 的最大输出频率为 50MHz。我决定将 1MHz 作为一个简单的数字(请参阅我最初的DDS 编译器教程,了解我如何计算 1MHz 的十六进制值相位增量输入)。
相位增量逻辑模块文件 Verilog:
module bb_logic(
input clk,
output [31:0] DDS_S_AXIS_PHASE_tdata, // input to block design
output DDS_S_AXIS_PHASE_tlast, // input to block design
input DDS_S_AXIS_PHASE_tready, // output from block design
output DDS_S_AXIS_PHASE_tvalid // input to block design
);
wire [31:0] Freq;
wire [31:0] Freq_period;
// setting the phase increment value to static for now
assign Freq = 32'h28f5c2;
assign Freq_period = 32'd100; // 1000ns/10ns = 100 --> max value here is 16384
wire latch_tdata;
// AXI stream state machine instantiation
axis_sm axis_sm_i(
.clk(clk),
.reset(1'b1),
.start(1'b1),
.latch_tdata(latch_tdata),
.s_phase_tvalid(DDS_S_AXIS_PHASE_tvalid), // output
.s_phase_tlast(DDS_S_AXIS_PHASE_tlast), // output
.s_phase_tready(DDS_S_AXIS_PHASE_tready), // input
.s_phase_tdata(DDS_S_AXIS_PHASE_tdata), // output
.carrier_freq(Freq),
.carrier_period(Freq_period)
);
endmodule
AXI Stream 协议状态机:
module axis_sm(
input clk,
input reset,
input start,
output reg latch_tdata,
output reg s_phase_tvalid,
output reg s_phase_tlast,
input s_phase_tready,
output reg [31:0] s_phase_tdata,
input [31:0] carrier_freq,
input [31:0] carrier_period
);
reg [4:0] state_reg;
reg [31:0] period_wait_cnt;
parameter init = 5'd0;
parameter WaitForStart = 5'd1;
parameter SetTvalidHigh = 5'd2;
parameter SetSlavePhaseValue = 5'd3;
parameter LatchTdata = 5'd4;
parameter CheckTready = 5'd5;
parameter WaitState = 5'd6;
parameter SetTlastHigh = 5'd7;
parameter WaitOneState = 5'd8;
parameter SetTlastLow = 5'd9;
parameter set_freq = 1'b0;
parameter set_phase = 1'b1;
parameter default_tdata = 32'h0;
always @ (posedge clk or posedge reset)
begin
// Default Outputs
latch_tdata <= 1'b0;
if (reset == 1'b0)
begin
s_phase_tdata[31:0] <= default_tdata;
state_reg <= init;
end
else
begin
case(state_reg)
init : //0
begin
latch_tdata <= 1'b0;
s_phase_tlast <= 1'b0;
s_phase_tvalid <= 1'b0;
period_wait_cnt <= 32'd0;
state_reg <= WaitForStart;
end
WaitForStart : //1
begin
if (start == 1'b1)
begin
state_reg <= SetTvalidHigh;
end
else
begin
state_reg <= WaitForStart;
end
end
SetTvalidHigh : //2
begin
s_phase_tvalid <= 1'b1;
state_reg <= SetSlavePhaseValue;
end
SetSlavePhaseValue : //3
begin
s_phase_tdata[31:0] <= carrier_freq;
state_reg <= LatchTdata;
end
LatchTdata : //4
begin
latch_tdata <= 1'b1;
state_reg <= CheckTready;
end
CheckTready : //5
begin
if (s_phase_tready == 1'b1)
begin
state_reg <= WaitState;
end
else if (start == 1'b0)
begin
state_reg <= init;
end
else
begin
state_reg <= CheckTready;
end
end
WaitState : //6
begin
if (period_wait_cnt >= carrier_period)
begin
period_wait_cnt <= 32'd0;
state_reg <= SetTlastHigh;
end
else
begin
period_wait_cnt <= period_wait_cnt + 1;
state_reg <= WaitState;
end
end
SetTlastHigh : //7
begin
s_phase_tlast <= 1'b1;
state_reg <= WaitOneState;
end
WaitOneState : //8
begin
state_reg <= SetTlastLow;
end
SetTlastLow : //9
begin
s_phase_tlast <= 1'b0;
state_reg <= WaitForStart;
end
endcase
end
end
endmodule
新的顶级文件与其他两个文件一起完成后,保存所有文件,您将看到 Sources Hierarchy 自动更新。通过右键单击 Sources Hierarchy 选项卡中的 eclypse_top 文件并选择“设置为顶部”选项,将 eclypse_top 文件设置为项目的新顶部文件。
有了新的自定义顶级文件,暂时不再需要自动生成的文件。不过我发现如果以后再更新积木设计,还是有它就好了。再加上我多次了解到删除 Vivado 自动生成的文件会导致工具出现未定义行为这一事实,我只是禁用了该文件。在 Sources Hierarchy 选项卡中右键单击它,然后选择“Disable”。更新后,您的 Sources Hierarchy 选项卡应类似于以下内容:
正如我在之前的项目中提到的那样,基础项目的时间安排已经结束。输出到 SYZYGY 连接器上的 DAC Zmod 的数据线的保持时间不够长。我在之前的项目中没有修复它,因为我知道我在这个项目中的添加会在实现过程中改变设计的位置和布线,并最终在一定程度上改变时序。幸运的是,这个项目中的添加增加了足够的延迟,我能够简单地延长约束文件中 DAC 数据线的保持时间(最后四行):
set_output_delay -clock [get_clocks sDAC_Clkin_0] -clock_fall -min -add_delay 0.330 [get_ports {sDAC_Data_0[*]}]
set_output_delay -clock [get_clocks sDAC_Clkin_0] -clock_fall -max -add_delay 0.250 [get_ports {sDAC_Data_0[*]}]
set_output_delay -clock [get_clocks sDAC_Clkin_0] -min -add_delay 0.330 [get_ports {sDAC_Data_0[*]}]
set_output_delay -clock [get_clocks sDAC_Clkin_0] -max -add_delay 0.150 [get_ports {sDAC_Data_0[*]}]
约束文件更新后,运行综合、实现并生成比特流。完成后,验证没有错误或严重警告并导出硬件以在 Vitis 中使用。在“文件”菜单下,选择“导出”下的“导出硬件...”选项。确认您正在导出到与 Vitis 工作区中现有硬件平台(XSA 文件)相同的位置,并选中包含比特流的选项。
从 Vivado 的工具菜单中启动 Vitis,然后选择项目的现有工作区。同样,我只是在此处修改我上一个项目教程中的现有裸机应用程序,因此请参阅创建新的 Vitis 项目和应用程序。
ZMOD 裸机库对 ADC 和 DAC 起作用的方式是,它们通过 DMA 交换从 Eclypse 上的 DDR 存储器填充缓冲区。对于 DAC,Digilent 的原始设计仅启用了读取通道(内存映射到流或 MM2S)。由于我们启用了写入通道(流到内存映射或 S2MM),因此还需要更新库以反映现在有一个双向的 DMA 实例。
从
修改dma.h:
#ifndef DMA_H_
#define DMA_H_
#include
/**
* Direction of a DMA transfer.
*/
enum dma_direction {
DMA_DIRECTION_TX, ///< TX transfer
DMA_DIRECTION_RX, ///< RX transfer
DMA_DIRECTION_TRX ///< TX & RX transfer
};
uint32_t fnInitDMA(uintptr_t addr, enum dma_direction direction, int dmaInterrupt);
void fnDestroyDMA(uintptr_t addr);
int fnOneWayDMATransfer(uintptr_t addr, uint32_t *buf, size_t length);
int fnS2MM_DMATransferCont(uintptr_t addr, uint32_t *buf, size_t transfer_size, int num_transfers);
int fnMM2S_DMATransfer(uintptr_t addr, uint32_t *buf, size_t transfer_size);
uint8_t fnIsDMATransferComplete(uintptr_t addr);
void* fnAllocBuffer(uintptr_t addr, size_t size);
void fnFreeBuffer(uintptr_t addr, void *buf, size_t size);
#endif /* DMA_H_ */
dma.c 中添加了新的 DMA 传输函数:
S2MM DMA传输功能:
int fnS2MM_DMATransferCont(uintptr_t addr, uint32_t *buf, size_t transfer_size, int num_transfers){
DMAEnv *dmaEnv = (DMAEnv *)addr;
if (!dmaEnv)
return -1;
dmaEnv->complete_flag = 0;
if(dmaEnv->direction != DMA_DIRECTION_TRX){
return -1;
} else {
// S2MM - read in DDS data
// Associate data buffer
writeDMAReg(dmaEnv->base_addr, AXIDMA_REG_ADDR_S2MM_DA, (uint32_t)buf);
// Set DMA RX Run bit, value 1, DMA register
writeDMARegFld(dmaEnv->base_addr, AXIDMA_REGFLD_S2MM_DMACR_RUNSTOP, 1);
for (int i=0;i<num_transfers;i++){
// Start DMA Transfer
writeDMAReg(dmaEnv->base_addr, AXIDMA_REG_ADDR_S2MM_DA_LENGTH, transfer_size);
}
}
return 0;
}
MM2S DMA传输功能:
int fnMM2S_DMATransfer(uintptr_t addr, uint32_t *buf, size_t transfer_size){
DMAEnv *dmaEnv = (DMAEnv *)addr;
if (!dmaEnv)
return -1;
dmaEnv->complete_flag = 0;
if(dmaEnv->direction != DMA_DIRECTION_TRX){
return -1;
} else {
// MM2S - write out to DAC
// Associate data buffer
writeDMAReg(dmaEnv->base_addr, AXIDMA_REG_ADDR_MM2S_SA, (uint32_t)buf);
// Set DMA RX Run bit, value 1, DMA register
writeDMARegFld(dmaEnv->base_addr, AXIDMA_REGFLD_MM2S_DMACR_RUNSTOP, 1);
// Start DMA Transfer
writeDMAReg(dmaEnv->base_addr, AXIDMA_REG_ADDR_MM2S_SA_LENGTH, transfer_size);
}
return 0;
}
DMA 函数是从 ZMOD 基础库中调用的,因此还需要添加两个函数。一个启动 MM2S DMA 事务,一个启动 S2MM DMA 事务(不要忘记将这些函数原型也添加到 Zmod.h)。
新的 Zmod.cpp 功能:
/**
* Start a DMA S2MM transfer using the transfer length configured previously.
*
* @return 0 on success, any other number on failure
*/
int ZMOD::startS2MMTransferCont(uint32_t* buffer, int num_transfers){
// transfer length is not configured
if (transferSize < 1) {
return ERR_FAIL;
}
return fnS2MM_DMATransferCont(dmaAddr, buffer, transferSize, num_transfers);
}
/**
* Start a DMA MM2S transfer using the transfer length configured previously.
*
* @return 0 on success, any other number on failure
*/
int ZMOD::startMM2STransfer(uint32_t* buffer){
// transfer length is not configured
if (transferSize < 1) {
return ERR_FAIL;
}
return fnMM2S_DMATransfer(dmaAddr, buffer, transferSize);
}
最后,在 DAC ZMOD 特定库中,需要两个新函数通过 DMA S2MM 事务从 DDS 编译器向 DDR 写入一段正弦波,并将该数据从 DDR 读取到缓冲区以通过 DMA 发送到 DAC MM2S 交易。还需要修改 DAC 实例的初始化,以指示附加到它的 DMA 现在是双向的(能够进行读取/MM2S 和写入/S2MM 事务)。
修改了 DAC ZMOD 的 init 实例函数以对 DMA 使用新的双向类型:
ZMODDAC1411::ZMODDAC1411(uintptr_t baseAddress, uintptr_t dmaAddress, uintptr_t iicAddress, uintptr_t flashAddress, int dmaInterrupt)
: ZMOD(baseAddress, dmaAddress, iicAddress, flashAddress, DMA_DIRECTION_TRX, -1, dmaInterrupt)
{
ZMOD::initCalib(sizeof(CALIBECLYPSEDAC), ZMODDAC1411_CALIB_ID, ZMODDAC1411_CALIB_USER_ADDR, ZMODDAC1411_CALIB_FACT_ADDR);
}
将 DDS 编译器输出写入 DDR 内存的函数:
/*
* Reads in the data values being output by the DDS Compiler and writes them to a
* memory location in the DDR
* @param none
* @return the status: ERR_SUCCESS for success*/
int ZMODDAC1411::readInDDSdata(uint32_t* buffer, size_t &length, int num_transfers){
uint8_t Status;
if(length > ZmodDAC1411_MAX_BUFFER_LEN){
length = ZmodDAC1411_MAX_BUFFER_LEN;
}
// DMA TX transfer length in number of elements
// multiply by the size of the data
setTransferSize(length * sizeof(uint32_t));
// Start DMA Transfer
Status = startS2MMTransferCont(buffer, num_transfers);
if (Status) {
return ERR_FAIL;
}
return ERR_SUCCESS;
}
将 DDS 输出数据从 DDR 读取到 DAC ZMOD 缓冲区的函数:
int ZMODDAC1411::sendDDSdataToDAC(uint32_t* buffer, size_t &length){
uint8_t Status;
if(length > ZmodDAC1411_MAX_BUFFER_LEN)
{
length = ZmodDAC1411_MAX_BUFFER_LEN;
}
// DMA TX transfer length in number of elements
// multiply by the size of the data
setTransferSize(length * sizeof(uint32_t));
// Start DMA Transfer
Status = startMM2STransfer(buffer);
if (Status) {
return ERR_FAIL;
}
// // Wait for DMA to Complete transfer
// while(!isDMATransferComplete()) {}
return ERR_SUCCESS;
}
随着 ZMOD 裸机库的更新,主要功能相当简单。主函数首先创建 DAC ZMOD 的实例,然后设置 14 位输出采样分频器和通道一的增益值。然后为缓冲区分配内存,以 DMA 能够达到的最大长度(0x3fff 或 16384)将数据从 DDS 读取到 DDR,并分配另一个缓冲区以将数据发送到 DAC。
一个周期的 1MHz 正弦波正从 DDR 内存(相当于 50 个样本)读取到第一个缓冲区中。然后将其冗余复制到第二个缓冲区中,直到缓冲区已满。然后将该缓冲区发送到 DAC 并启动 DAC。一旦数据成功发送到 DAC,两个缓冲区的内存都会被释放,DAC 会无限运行,输出 1MHz 正弦波。
对于 ADC,我只是重用了我在上一个项目中使用的 Digilent 的 ADC 演示功能。此 ADC 演示功能启动 ADC 以连续捕获和无限循环并格式化数据以输出到 UART 控制台。
主要功能代码:
#include
#include
#include
#include "xaxidma.h"
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "./zmodlib/Zmod/zmod.h"
#include "./zmodlib/ZmodADC1410/zmodadc1410.h"
#include "./zmodlib/ZmodDAC1411/zmoddac1411.h"
#include "./zmodlib/Zmod/dma.h"
#define TRANSFER_LEN 0x400
// ZMOD ADC parameters
#define ZMOD_ADC_BASE_ADDR XPAR_AXI_ZMODADC1410_0_S00_AXI_BASEADDR
#define DMA_ADC_BASE_ADDR XPAR_AXI_DMA_ADC_BASEADDR
#define IIC_BASE_ADDR XPAR_PS7_I2C_1_BASEADDR
#define FLASH_ADDR_ADC 0x30
#define ZMOD_ADC_IRQ XPAR_FABRIC_AXI_ZMODADC1410_0_LIRQOUT_INTR
#define DMA_ADC_IRQ XPAR_FABRIC_AXI_DMA_ADC_S2MM_INTROUT_INTR
//ZMOD DAC parameters
#define ZMOD_DAC_BASE_ADDR XPAR_AXI_ZMODDAC1411_V1_0_0_BASEADDR
#define DMA_DAC_BASE_ADDR XPAR_AXI_DMA_DAC_BASEADDR
#define FLASH_ADDR_DAC 0x31
#define DMA_DAC_IRQ XPAR_FABRIC_AXI_DMA_DAC_MM2S_INTROUT_INTR
#define IIC_BASE_ADDR XPAR_PS7_I2C_1_BASEADDR
//DMA for DDS output - XPAR_AXI_DMA_DDS_DEVICE_ID
#define MEM_BASE_ADDR (XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x1000000)
#define TRX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000)
#define TRX_BUFFER_HIGH (MEM_BASE_ADDR + 0x004FFFFF)
/*
* Simple ADC test, puts the ADC in the test mode (ramp),
* performs an acquisition under specific trigger conditions
* and verifies the acquired data to be consistent with these conditions.
*/
void testZMODADC1410Ramp_Auto(){
ZMODADC1410 adcZmod(ZMOD_ADC_BASE_ADDR, DMA_ADC_BASE_ADDR, IIC_BASE_ADDR, FLASH_ADDR_ADC,
ZMOD_ADC_IRQ, DMA_ADC_IRQ);
if(adcZmod.autoTestRamp(1, 0, 0, 4, TRANSFER_LEN) == ERR_SUCCESS){
xil_printf("Success autotest ADC ramp\r\n");
} else {
xil_printf("Error autotest ADC ramp\r\n");
}
}
/*
* Format data contained in the buffer and sends it over UART.
* It displays the acquired value (in mV), raw value (as 14 bits hexadecimal value)
* and time stamp within the buffer (in time units).
* @param padcZmod - pointer to the ZMODADC1410 object
* @param acqBuffer - the buffer containing acquired data
* @param channel - the channel where samples were acquired
* @param gain - the gain for the channel
* @param length - the buffer length to be used
*/
void formatADCDataOverUART(ZMODADC1410 *padcZmod, uint32_t *acqBuffer, uint8_t channel, uint8_t gain, size_t length){
char val_formatted[15];
char time_formatted[15];
uint32_t valBuf;
int16_t valCh;
float val;
xil_printf("New acquisition ------------------------\r\n");
xil_printf("Ch1\tRaw\tTime\t\r\n");
for (size_t i = 0; i < length; i++){
valBuf = acqBuffer[i];
valCh = padcZmod->signedChannelData(channel, valBuf);
val = padcZmod->getVoltFromSignedRaw(valCh, gain);
padcZmod->formatValue(val_formatted, 1000.0*val, "mV");
if (i < 100){
padcZmod->formatValue(time_formatted, i*10, "ns");
} else {
padcZmod->formatValue(time_formatted, (float)(i)/100.0, "us");
}
xil_printf("%s\t%X\t%s\r\n", val_formatted, (uint32_t)(valCh&0x3FFF), time_formatted);
}
}
/*
* Simple ADC test, acquires data and sends it over UART.
* @param channel - the channel where samples will be acquired
* @param gain - the gain for the channel
* @param length - the buffer length to be used
*/
void adcDemo(uint8_t channel, uint8_t gain, size_t length){
ZMODADC1410 adcZmod(ZMOD_ADC_BASE_ADDR, DMA_ADC_BASE_ADDR, IIC_BASE_ADDR, FLASH_ADDR_ADC,
ZMOD_ADC_IRQ, DMA_ADC_IRQ);
uint32_t *acqBuffer;
adcZmod.setGain(channel, gain);
while(1){
acqBuffer = adcZmod.allocChannelsBuffer(length);
adcZmod.acquireImmediatePolling(acqBuffer, length);
formatADCDataOverUART(&adcZmod, acqBuffer, channel, gain, length);
adcZmod.freeChannelsBuffer(acqBuffer, length);
sleep(2);
}
}
int main(){
init_platform();
xil_printf("Eclypse Z7 SDR baseband data generator...\r\n");
// init DAC Zmod
ZMODDAC1411 dacZmod(ZMOD_DAC_BASE_ADDR, DMA_DAC_BASE_ADDR, IIC_BASE_ADDR, FLASH_ADDR_DAC, DMA_DAC_IRQ);
// max buffer length:
size_t length = 0x3fff;
dacZmod.setOutputSampleFrequencyDivider(2);
dacZmod.setGain(0, 1);
int Status = 0;
uint32_t *TrxBufferPtr;
TrxBufferPtr = dacZmod.allocChannelsBuffer(length);
for (int i=0;i<16383;i++){
TrxBufferPtr[i] = 0;
}
uint32_t *acqBufferPtr;
acqBufferPtr = dacZmod.allocChannelsBuffer(length);
Status = dacZmod.readInDDSdata(TrxBufferPtr, length, 1);
if (Status) {
xil_printf("DMA MM2S error!...\r\n");
}
int start_index = 0;
// copy the one period of sine wave into the buffer until its full
while (start_index<16000){
for (int i=0;i<50;i++){
acqBufferPtr[start_index+i] = TrxBufferPtr[i];
}
start_index = start_index + 50;
}
Status = dacZmod.sendDDSdataToDAC(acqBufferPtr, length);
if (Status) {
xil_printf("DMA S2MM error!...\r\n");
}
// start the instrument
dacZmod.start();
// free the buffers since it's been transferred to the DAC
dacZmod.freeChannelsBuffer(TrxBufferPtr, length);
dacZmod.freeChannelsBuffer(acqBufferPtr, length);
// start channel 0 of the ADC collecting infinitely
adcDemo(0, 0, length);
cleanup_platform();
return 0;
}
保存所有文件并构建项目。
要运行应用程序并同时查看 ILA,首先通过右键单击资源管理器窗口中的应用程序名称并选择“Program FPGA”来对 Eclypse 进行编程。
请务必将比特流更改为新的比特流。当您单击比特流字段旁边的“搜索...”按钮时,您会看到 Vitis 在项目中检测到这两者。
对 Ecylpse 进行编程后,再次右键单击资源管理器窗口中的应用程序名称并在“调试为”下选择“在硬件上启动(单个应用程序调试)”,启动应用程序的调试运行。
一旦应用程序遇到主函数入口断点,使用 Vitis 串行终端连接到 Eclypse 的 UART。
在单步执行或运行应用程序之前,切换回 Vivado 并从 Flow Navigator 打开硬件管理器。从 Open Target 选项中选择自动连接。
如果 ILA 窗口打开并且不存在调试内核,您可能需要从硬件管理器重新编程 FPGA。我发现这是 Vivado 版本 2019.2 的一个错误。
在任何感兴趣的 AXI 流协议上触发 ILA 中的触发器并在 Vitis 中运行应用程序。我监视了 DDS 编译器的主 AXI 流端口和 DAC 的 DMA 的 AXI 流 S2MM 端口,以便在这个项目上进行调试。
一旦我能够看到 ILA 中的正弦波环回工作以及 ADC ZMOD 的 UART 控制台中的打印输出,我决定启动我的老式频谱分析仪,它位于我书柜的顶部架子上,看看 1MHz 正弦波是什么波实际上看起来像是从 DAC 端口的 SMA 端口出来的。
我的频谱分析仪实际上是用于电视维修的,因此射频输入为 75 欧姆,因此使用 75 至 50 欧姆的巴伦和一些适配器电缆将其 BNC 输入转换为 ZMOD 的 SMA 端口并获得日蚀相连。
正如你所看到的,对于一个应该是单一频率的纯连续正弦波,它与一系列额外的频率分量相当混乱。我确信这与我荒谬的长 SMA 电缆和破解连接转换有关。该频谱分析仪自 1984 年以来还没有进行过正确校准,但使用它仍然可以很好地看到 DAC ZMOD 确实以 1MHz 的频率发出了模拟信号。
我发现我拥有的 SMA 电缆比我拥有的任何 USB 电缆都长得多,所以我最终将信号分析仪留在了我的书架顶部,并将 Eclypse 板放在我的办公桌窝上,放在我的电脑上。
为什么我拥有比 USB 电缆更长的 SMA 电缆???好问题。我自己刚刚发现了这个异常。
总体而言,可以以非常简单的方式修改设计,使 DDS 编译器的相位增量和偏移输入可编程,并最终使用 ZMOD 制作出 Eclypse 的基带数据生成器。我会把它保存到另一个项目教程中!
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
全部0条评论
快来发表一下你的评论吧 !