【国产FPGA评测】安路(型号SF1S60CG121I) 04并行DAC的DDS模块设计
[复制链接]
本帖最后由 EPTmachine 于 2023-3-21 15:02 编辑
1.1 硬件介绍
在Demo板的一侧,共有16个可供输出的IO,并且提供了3.3V的供电输出,用户扩展IO的示意图如图所示,
SF1S60CG121I 管脚位置
|
SF1S60CG121I 管脚名称
|
IO电源域
|
IO名称
|
IO序号
|
H9
|
IO_R1P_0,GCLK3
|
2.5V
|
USER_IO0_N
|
3
|
G9
|
IO_R1N_0,GCLK2
|
2.5V
|
USER_IO0_P
|
4
|
H8
|
IO_R0N_0,GCLK0
|
2.5V
|
USER_IO1_N
|
5
|
H7
|
IO_R0P,GCLK1
|
2.5V
|
USER_IO1_P
|
6
|
D11
|
IO_R7N_0
|
2.5V
|
USER_IO2_N
|
7
|
D10
|
IO_R7P_0
|
2.5V
|
USER_IO2_P
|
8
|
C11
|
IO_R8N_0,GCLK10,D3
|
2.5V
|
USER_IO3_N
|
9
|
C10
|
IO_R8P_0,GCLK11,D2
|
2.5V
|
USER_IO3_P
|
10
|
B11
|
IO_R9N_0
|
2.5V
|
USER_IO4_N
|
11
|
B10
|
IO_R9P_0
|
2.5V
|
BUZZER
|
12
|
A11
|
IO_R10N_0,GCLK12,D1
|
2.5V
|
USER_IO5_N
|
13
|
A10
|
IO_R10P_0,GCLK13,D0
|
2.5V
|
USER_IO5_P
|
14
|
B8
|
IO_R11N_0
|
2.5V
|
USER_IO6_N
|
15
|
B9
|
IO_R11P_0
|
2.5V
|
USER_IO6_P
|
16
|
A8
|
IO_R12N_0,GCLK14,USRCLK
|
2.5V
|
USER_IO7_N
|
17
|
A9
|
IO_R12P_0,GCLK15,SCLK
|
2.5V
|
USER_IO7_P
|
18
|
以上资源搭配硬禾学堂的电赛训练上的10位告诉DAC芯片,可以实现DDS功能,电赛训练板的原理图如下
实物接线图如下,电赛训练板的供电由Demo板的3.3V输出端提供。
Demo板上的按键电路和对应的控制引脚如下:
按键外接上拉电阻,默认为高电平。按键控制引脚与FPGA管脚的映射关系为:
SF1S60CG121I 管脚位置
|
SF1S60CG121I 管脚名称
|
IO电源域
|
按键名称
|
H3
|
IO_B2N_2,GCLK2
|
1.2V
|
KEY0
|
H2
|
IO_B4P_2
|
1.2V
|
KEY1
|
G3
|
IO_B2P_2
|
1.2V
|
KEY2
|
1.2 ROM IP核使用和实现正弦波表
SF1器件支持的ERAM9K类型的嵌入式存储器模块(ERAM),可以实现ROM、RAM和FIFO等模块。使用起来的配置还是很方便的。
本次演示的DDS使用四分之一的10位正弦波表,数据位宽选择16位,表的深度选择64,完整的波表可以通过相位变换获得,原理是居于对称性来实现的。
但是在实际使用过程中出现了下述的编译错误,具体原因不明。
这里我选择使用LUT的实现方式。代码如下
module sin_table(address,sin);
output [8:0] sin; //实际波形表为9位分辨率(1/4周期)
input [5:0] address; //64个点来生成1/4个周期的波形,完整的一个周期为256个点
reg [8:0] sin;
always @(address)
begin
case(address)
6'h0: sin=9'h0;
6'h1: sin=9'hC;
6'h2: sin=9'h19;
6'h3: sin=9'h25;
6'h4: sin=9'h32;
6'h5: sin=9'h3E;
6'h6: sin=9'h4B;
6'h7: sin=9'h57;
6'h8: sin=9'h63;
6'h9: sin=9'h70;
6'ha: sin=9'h7C;
6'hb: sin=9'h88;
6'hc: sin=9'h94;
6'hd: sin=9'hA0;
6'he: sin=9'hAC;
6'hf: sin=9'hB8;
6'h10: sin=9'hC3;
6'h11: sin=9'hCF;
6'h12: sin=9'hDA;
6'h13: sin=9'hE6;
6'h14: sin=9'hF1;
6'h15: sin=9'hFC;
6'h16: sin=9'h107;
6'h17: sin=9'h111;
6'h18: sin=9'h11C;
6'h19: sin=9'h126;
6'h1a: sin=9'h130;
6'h1b: sin=9'h13A;
6'h1c: sin=9'h144;
6'h1d: sin=9'h14E;
6'h1e: sin=9'h157;
6'h1f: sin=9'h161;
6'h20: sin=9'h16A;
6'h21: sin=9'h172;
6'h22: sin=9'h17B;
6'h23: sin=9'h183;
6'h24: sin=9'h18B;
6'h25: sin=9'h193;
6'h26: sin=9'h19B;
6'h27: sin=9'h1A2;
6'h28: sin=9'h1A9;
6'h29: sin=9'h1B0;
6'h2a: sin=9'h1B7;
6'h2b: sin=9'h1BD;
6'h2c: sin=9'h1C3;
6'h2d: sin=9'h1C9;
6'h2e: sin=9'h1CE;
6'h2f: sin=9'h1D4;
6'h30: sin=9'h1D9;
6'h31: sin=9'h1DD;
6'h32: sin=9'h1E2;
6'h33: sin=9'h1E6;
6'h34: sin=9'h1E9;
6'h35: sin=9'h1ED;
6'h36: sin=9'h1F0;
6'h37: sin=9'h1F3;
6'h38: sin=9'h1F6;
6'h39: sin=9'h1F8;
6'h3a: sin=9'h1FA;
6'h3b: sin=9'h1FC;
6'h3c: sin=9'h1FD;
6'h3d: sin=9'h1FE;
6'h3e: sin=9'h1FF;
6'h3f: sin=9'h1FF;
endcase
end
endmodule
1.3 DDS波形频率和类型控制模块
关于波形的频率控制,其核心公式如下,fout为输出频率,fmclk为DDS的主时钟,Nmax为n位计数器的最大值,m为每个时钟周期的相位累加值。文中介绍的DDS发生器采用28位的相位累加器,通过改变步长m,来实现不同频率的输出。
关键代码如下:
reg [27:0] dds_phase;
wire [27:0] dds_phase_add;
assign dds_phase_add = wave_step;
always@(posedge sys_clk or negedge rst_n)begin
if(!rst_n)begin
dds_phase <= 28'd0;
end else begin
dds_phase <= dds_phase + dds_phase_add;
end
end
DDS输出的波形的类型中的方波、三角波、锯齿波通过相位累加器的高十位来控制。
方波使用相位累加器的最高位作为输出数据,在相位累加器从0到上限值的过程中,最高位在一半的时间内为0,在另一半的时间内为1,由于方波只需要DAC的两个量化值,数据全为0为最小值,数据全为1为最大值,对应DAC的数据引脚输出相应的数据位即可。关键代码如下:
assign square_dac = {10{dds_phase[27]}};
对于锯齿波,其输出电压信号,从DAC的最小值变化到最大值,取相位累加器的高10位作为DAC芯片的数据引脚的数据来源,就可以生成锯齿波,关键代码如下:
assign saw_dac = dds_phase[27:18];
对于三角波,其和锯齿波类似,不过由于其DAC的量化数据在单个周期内由0到1023然后从1023到0,相同周期下,其计数溢出的周期比锯齿波快一倍,而且计数方向会发生变化,所以,在生成锯齿波波形的基础上,使用相位累加器的最高位作为三角波相位的选取标志位,紧随其后的10位作为DAC数据端口的数据来源,根据不同相位对数据进行取反或不取反处理,从而实现三角波的输出。关键代码如下:
assign trig_dac = dds_phase[27] ? ~dds_phase[26:17] : dds_phase[26:17];
而正弦波则通过相位累加器的高八位作为正弦波表的地址,通过查表得到对应的10位DAC的量化值。前面实现了四分之一的正弦波表的地址与输出DAC量化值之间的关系。正弦波与三角波类似,只不过它有四个象限,所以在使用的时候,需要两位数据来确定波形位于正弦波的哪个象限,使用相位累加器的高两位确定象限,其后的六位作为波表的寻址控制位。两者结合而从而得到一个完整的正弦波输出。关键代码如下:
//查找表调用
lookup_tables u_lut(
.phase(dds_phase[27:20]),
.sin_out(sin_dac)
);
//////////////////////////////
//查找表实现
module lookup_tables(//sys_clk,rst_n,
phase, sin_out);
//input sys_clk;
//input rst_n;
input [7:0] phase;
output [9:0] sin_out;
wire [9:0] sin_out;
reg [5:0] address;
wire [1:0] sel;
wire [11:0] sine_table_out;
reg [9:0] sine_onecycle_amp;
assign sin_out = sine_onecycle_amp[9:0];
assign sel = phase[7:6];
sin_table u_sin_table(address,sine_table_out);
always @(sel or sine_table_out)
begin
case(sel)
2'b00: begin
sine_onecycle_amp = 9'h1ff + sine_table_out[8:0];
address = phase[5:0];
end
2'b01: begin
sine_onecycle_amp = 9'h1ff + sine_table_out[8:0];
address = ~phase[5:0];
end
2'b10: begin
sine_onecycle_amp = 9'h1ff - sine_table_out[8:0];
address = phase[5:0];
end
2'b11: begin
sine_onecycle_amp = 9'h1ff - sine_table_out[8:0];
address = ~ phase[5:0];
end
endcase
end
endmodule
1.4 DDS幅度控制模块
通过DAC芯片输出波形时,在DAC芯片的输入端,其输入位数决定了输出信号的量化值范围,上面的输出信号使用0变化到1023的,也就是在DAC芯片的满量程上进行的。如果要控制输出信号的峰峰值,这时需要给输出的数据进行除法操作,直接除的话会有损失很多精度。这里先将输出的量化值乘一个值x(0-255),然后除以255,就可以得到一个按照比例衰减的输出信号。测试中使用的DAC的输出范围时2.56V-0V,上面的x每个值可以对应一个单位0.01V的电压值。幅度控制的关键代码如下:
module dds_amp_adjust(
clk,
rst_n,
wave_amp,
signal_dat,
dac_dat
);
input clk,rst_n;
input [7:0] wave_amp;//用于调幅的因数
input [9:0] signal_dat;//未调幅的波形数据
output [9:0] dac_dat;//输出给DAC的数据
reg [17:0] amp_dat;//调幅后的波形数据
always @(posedge clk) begin
amp_dat<=signal_dat*wave_amp;//波形数据乘以调幅系数
end
assign dac_dat=amp_dat[17:8];//取高十位,相当于右移8位,除256
endmodule
1.5 按键输入检测模块
Demo板上共有三个按键,这里正好可以实现对信号的类型、频率、幅度的控制。按键的消抖程序如下:
module debounce(
//input
clk,
rst_n,
key,
//output
key_pluse);
parameter N=1; //要消除的按键数量
input clk;
input rst_n;
input [N-1:0] key; //输入的按键
output [N-1:0] key_pluse; //按键动作产生的脉冲
reg [N-1:0] key_signal_pre;
reg [N-1:0] key_signal;
wire [N-1:0] key_edge;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)begin
key_signal<={N{1'b1}};
key_signal_pre<={N{1'b1}};
end
else begin
key_signal<=key;
key_signal_pre<=key_signal;
end
end
assign key_edge = key_signal_pre&(~key_signal);
reg [17:0] cnt;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt<=18'h0;
else if(key_edge)
cnt<=18'h0;
else
cnt<=cnt+1'b1;
end
reg [N-1:0] key_sec_pre;
reg [N-1:0] key_sec;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
key_sec<={N{1'b1}};
else if(cnt==18'h3ffff)
key_sec<=key;
end
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
key_sec_pre<={N{1'b1}};
else
key_sec_pre<=key_sec;
end
assign key_pluse = key_sec_pre & (~key_sec);
endmodule
消抖模块在确定一次有效输入后,发出一个脉冲信号,用于其他模块检测按键的上升沿,在检测到上升沿后,切换不同的输出类型、频率和幅度。关键代码如下:
module dds_waveparactrl(
input clk,
input rst_n,
input wave_type_ctrl,
input wave_freq_ctrl,
input wave_amp_ctrl,
output reg [2:0] wave_type,
output reg [27:0] wave_freqm,
output reg [7:0] wave_amp
);
//波形输出形态控制
reg wave_type_ctrlr;//滤波后输出的按键脉冲为低电平
always@(posedge clk,negedge rst_n)begin
if(!rst_n)begin
wave_type_ctrlr <= 1'b0;
end
else begin
wave_type_ctrlr<=wave_type_ctrl;
end
end
wire wave_type_ctrl_pos;
assign wave_type_ctrl_pos=!wave_type_ctrlr&&wave_type_ctrl;
reg [1:0]type_cnt;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wave_type <= 3'b000;
type_cnt<=2'd1;
end
else if(wave_type_ctrl_pos)begin
case(type_cnt)
2'd0:begin wave_type <= 3'b000; type_cnt<=type_cnt+1'b1;end
2'd1:begin wave_type <= 3'b001; type_cnt<=type_cnt+1'b1;end
2'd2:begin wave_type <= 3'b010; type_cnt<=type_cnt+1'b1;end
2'd3:begin wave_type <= 3'b011; type_cnt<=type_cnt+1'b1;end
endcase
end
end
//波形输出频率控制
reg wave_freq_ctrlr;//滤波后输出的按键脉冲为低电平
always@(posedge clk,negedge rst_n)begin
if(!rst_n)begin
wave_freq_ctrlr <= 1'b0;
end
else begin
wave_freq_ctrlr<=wave_freq_ctrl;
end
end
wire wave_freq_ctrl_pos;
assign wave_freq_ctrl_pos=!wave_freq_ctrlr&&wave_freq_ctrl;
reg [2:0]freqm_cnt;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)begin
wave_freqm <= 28'd5369; //50M时钟频率下,1KHz信号的步进值m
freqm_cnt <=3'd0;
end
else if(wave_freq_ctrl_pos)begin
case(freqm_cnt)
3'd0:begin wave_freqm <= 28'd5369; freqm_cnt<=freqm_cnt+1'd1;end //1KHz
3'd1:begin wave_freqm <= 28'd53690; freqm_cnt<=freqm_cnt+1'd1;end //10KHz
3'd2:begin wave_freqm <= 28'd268450;freqm_cnt<=freqm_cnt+1'd1;end //50KHz
3'd3:begin wave_freqm <= 28'd536900;freqm_cnt<=freqm_cnt+1'd1;end //100KHz
3'd4:begin wave_freqm <= 28'd5369000; freqm_cnt<=freqm_cnt+1'd1;end //1MHz
3'd5:begin wave_freqm <= 28'd10738000; freqm_cnt<=freqm_cnt+1'd1;end //2MHz
3'd6:begin wave_freqm <= 28'd26845000; freqm_cnt<=freqm_cnt+1'd1;end //5MHz
3'd7:begin wave_freqm <= 28'd10738; freqm_cnt<=freqm_cnt+1'd1;end //2KHz
default:;
endcase
end
end
//波形输出幅度控制
reg wave_amp_ctrlr;//滤波后输出的按键脉冲为低电平
always@(posedge clk,negedge rst_n)begin
if(!rst_n)begin
wave_amp_ctrlr <= 1'b0;
end
else begin
wave_amp_ctrlr<=wave_amp_ctrl;
end
end
wire wave_amp_ctrl_pos;
assign wave_amp_ctrl_pos=!wave_amp_ctrlr&&wave_amp_ctrl;
reg [2:0] amp_cnt;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)begin
wave_amp <= 8'd255; //48M时钟频率下,1KHz信号的步进值m
amp_cnt <=3'd0;
end
else if(wave_amp_ctrl_pos)begin
case(amp_cnt)
3'd0:begin wave_amp <= 8'd255; amp_cnt<=amp_cnt+1'd1;end //2.56V
3'd1:begin wave_amp <= 8'd200; amp_cnt<=amp_cnt+1'd1;end //2.00V
3'd2:begin wave_amp <= 8'd180; amp_cnt<=amp_cnt+1'd1;end //1.8V
3'd3:begin wave_amp <= 8'd170; amp_cnt<=amp_cnt+1'd1;end //1.7V
3'd4:begin wave_amp <= 8'd150; amp_cnt<=amp_cnt+1'd1;end //1.5V
3'd5:begin wave_amp <= 8'd140; amp_cnt<=amp_cnt+1'd1;end //1.4V
3'd6:begin wave_amp <= 8'd130; amp_cnt<=amp_cnt+1'd1;end //1.3V
3'd7:begin wave_amp <= 8'd100; amp_cnt<=amp_cnt+1'd1;end //1.0V
default:;
endcase
end
end
endmodule
1.6 实验演示
dds演示
1.7 总结
这次实验一共占用了11个用户IO,而Demo板上的用户IO一共16个,本来计划是使用LCD,配合来进行参数设置的,当然,板上自带一个128x32的OLED屏幕,设置DDS参数还是可以的,但是要进行ADC采样的话可能不够,不过实验中的电赛训练板上有一块128x64的OLED,用来显示波形还是够的,后面的信息显示就使用OLED屏幕了。
|