一、项目介绍
iCE40UP5K由于其独特的性能得到海外创客们的热捧,尤其是其支持开源的开发工具链。不少RISC-V软核都是运行在iCE40UP5K上,基于这个大背景我们开发了一款FPGA核心模块,主要特点:
- 兼容树莓派基金会刚推出的Pico模块的管脚定义,Pico的GPIO性能比较强大,因此玩家可以对比Pico和FPGA IO管脚的使用,另外也可以借助位PICO推出的各种外设模块(本模块没有串行ADC的功能,只能支持数字端口的模块);
- 板上集成了基于DAPLink下载功能的STEPLINK功能,可以直接通过USB端口对FPGA的串行Flash进行配置,并可以通过同一个USB端口支持UART通信;
- 有开源的RISC-V可以移植使用,进而学习在FPGA上完成软核+FPGA的异构设计
本设计基于Lattice的ICE40UP5K FPGA,板载LPC11U35下载器,可以通过USB-C接口进行FPGA的配置,支持在ICE40UP5K上对RISC-V软核的移植以及开源的FPGA开发工具链,板上RGB三色LED灯用于简单的调试,总计28个IO用于扩展使用。
扩展板板上有:
-
2个按键输入
-
4个单色LED
-
12个WS2812B RGB三色灯
-
1个姿态传感器
-
1个128*64 OLED显示屏
-
1个蜂鸣器
-
1个可调电位计(用于电压表)
-
1路音频信号输入(用于示波器)
-
8位R-2R电阻网络构成的DAC(用于DDS信号发生器)
项目成果,实现了波形从10Hz到3MHz可调输出(最大可到10MHz但波形出现较大抖动),最小步进10Hz,信号幅值可由电位器进行调整,并同时将当前输出的波形,幅值,波形类型,频率显示在屏幕上。
二、项目使用说明
项目使用了4个按键分别用于系统的控制,由于所使用的板卡上只有3个按键,所以外接了一个按键,四个按键的功能分别是K1用来选择调整频率的位数,K2用来调整频率,RUN用作全局复位按键,外接的按键用于切换波形输出模式。
用模拟示波器产看输出波形。
三、代码讲解
文件DDS_MAIN.v中,有各模块的例化,是系统的TOP_module
module DDS_MAIN (
input wire clk,
input wire rstn,
//ADC
input wire i_analog_cmp,
output wire o_analog_out,
output wire o_sample_rdy,
output wire led,
//DAC
output wire [7:0] dac_data,
//KEY
input wire K1,
input wire K2,
input wire K3,
//OLED
output wire oled_spi_RSTn,
output wire oled_spi_DC,
output wire oled_spi_CLK,
output wire oled_spi_MOSI,
//output wire PWM_Beep
);
wire clk_120m;
wire clk_12m;
//使用PLL将时钟倍频到120M,用来输出频率幅度更大的波形
pll_120M pll(
.ref_clk_i(clk),
.rst_n_i(rstn),
.outglobal_o(clk_120m)
);
//由于pll的输入时钟只能用于PLL的输入,不能用于其他模块,同时没找到pll输出两路指定频率的办法,所以使用内置晶振产生一路12m时钟,给除DDS以外的其他模块使用
HSOSC
#(
.CLKHF_DIV ("0b10")
) HSOSC (
.CLKHFPU (1'b1), // I
.CLKHFEN (1'b1), // I
.CLKHF (clk_12m) // O
);
wire [2:0] dds_mode;
wire [2:0] multiple_mode;
wire [3:0] add_num;
reg [23:0] freq_save;
//K3用来切换不同输出波形
key_mode
#(
.N(4),
.bit(3)
) u_key_mode_K3 (
.sclk (clk_12m) ,
.s_rst_n (rstn) ,
.key_in (K3) ,
.mode (dds_mode)
);
//K1用来选择频率的位数
key_mode #(
.N(6),
.bit(3)
) u_key_multiple_K1(
.sclk (clk_12m) ,
.s_rst_n (rstn) ,
.key_in (K1) ,
.mode (multiple_mode)
);
//K2用来调整频率
key_mode #(
.N(10),
.bit(4)
) u_key_add_K2(
.sclk (clk_12m) ,
.s_rst_n (K1) ,
.key_in (K2) ,
.mode (add_num)
);
always @(negedge K2 or negedge rstn) begin
if(!rstn) freq_save <= 24'h100;
else if(!K2)begin
case (multiple_mode)
3'd0:freq_save[3 :0 ] <= add_num;
3'd1:freq_save[7 :4 ] <= add_num;
3'd2:freq_save[11:8 ] <= add_num;
3'd3:freq_save[15:12] <= add_num;
3'd4:freq_save[19:16] <= add_num;
3'd5:freq_save[23:20] <= add_num;
endcase
end
end
//例化adc模块,用来控制dac的输出幅值
wire [7:0] adc_data;
ADC_top ADC(
.i_clk_in(clk_12m), //comment out to use internal clock
.i_rst_in(rstn),
.i_analog_cmp(i_analog_cmp),
.o_digital_out(adc_data),
.o_analog_out(o_analog_out),
.o_sample_rdy(o_sample_rdy)
);
wire [15:0] bin_code = adc_data * 16'd129;
wire [19:0] bcd_code;
bin_to_bcd bin_to_bcd(
.rst_n (rstn),
.bin_code (bin_code),
.bcd_code (bcd_code)
);
//例化屏幕模块
OLED12864 OLED12864(
.clk (clk_12m),
.rst_n (rstn),
.datain (bcd_code),
.freq_datain (freq_save),
.multiple_mode (multiple_mode),
.dds_mode (dds_mode),
//.oled_csn (oled_csn),
.oled_rst (oled_spi_RSTn),
.oled_dcn (oled_spi_DC),
.oled_clk (oled_spi_CLK),
.oled_dat (oled_spi_MOSI)
);
//例化DDS信号发生器模块
dds_top dds_top(
.clk(clk_120m),
.rstn(rstn),
.dds_mode(dds_mode),
.freq (freq_save),
.adc_data(adc_data),
.dac_data(dac_data)
);
pwm pwm(
.clk(clk_12m),
.duty(dac_data),
.pwm_out(PWM_Beep)
);
endmodule //DDS_MAIN
1、pll IP core配置
将输入的12M晶振倍频到120M用来给dds提供时钟,产生更高频率的波形。
2、按键
按键消抖及控制模块的波形如图所示
3、波形驱动模块
DDS信号发生器原理(可以直接参考DDS生成任意频率波形原理及示例代码)。
module dds_top (
input wire clk,
input wire rstn,
input wire [3:0] dds_mode,
input wire [23:0] freq,
input wire [7:0] adc_data,
output wire [7:0] dac_data
);
reg [23:0] cnt;
always @(posedge clk or negedge rstn) begin
if(rstn == 0) begin
cnt<=0;
end
else begin
cnt <= cnt + 1'b1;
end
end
assign led= cnt[22];
wire [7:0] square_data;
wire [7:0] sawtooth_data;
wire [7:0] triangular_data;
wire [7:0] sinusoid_data;
reg [7:0] dac_data_or;
reg [31:0] phase_acc;
always @(posedge clk) phase_acc <= phase_acc
+ (26'd358) *freq[3 :0 ]
+ (26'd3579) *freq[7 :4 ]
+ (26'd35791) *freq[11:8 ]
+ (26'd357915) *freq[15:12]
+ (26'd3579151) *freq[19:16]
+ (26'd35791519)*freq[23:20]
;
//方波数据
assign square_data = {8{phase_acc[31]}};
//锯齿波数据
assign sawtooth_data = phase_acc[31:24];
//三角波数据
assign triangular_data = phase_acc[31] ? ~phase_acc[30:23] : phase_acc[30:23];
//正弦波数据
lookup_tables u_lookup_tables(.phase(phase_acc[31:24]), .sin_out(sinusoid_data));
//DDS输出
always @(posedge clk or negedge rstn) begin
case (dds_mode[1:0])
// 产生方波
2'b00: begin
dac_data_or <= square_data;
end
// 产生锯齿波
2'b01: begin
dac_data_or <= sawtooth_data;
end
// 产生三角波
2'b10: begin
dac_data_or <= triangular_data;
end
// 产生正弦波
2'b11: begin
dac_data_or <= sinusoid_data;
end
endcase
end
reg [13:0] amp_dat; //调幅后的波形数据
always @(posedge clk) amp_dat = dac_data_or * (adc_data[7-:6]); //波形数据乘以调幅因数
assign dac_data = amp_dat[13-:8]; //取高8位输出
endmodule //dds_top
4、OLED模块
这里直接参考了示例代码OLED驱动说明及Verilog代码实例
5、上电复位模块
通过上电自动拉低rstn,实现上电后自动复位系统。
module rst#(
parameter CNT = 'd12_000_0
)(
input wire clk,
input wire rst_in,
output wire rst_n
);
wire buf_rst_n;
reg [26:0] cnt = 27'd0;
assign buf_rst_n = (cnt == CNT - 1'b1) ? 1'b1 : 1'b0;
assign rst_n = buf_rst_n && rst_in;
always @ (posedge clk) begin
if (cnt < CNT - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= cnt;
end
endmodule
四、效果展示
1、10Hz 1V 四种波形
2、1kHz 1V 四种波形
3、1MHz 1V 四种波形
4、1kHz 0.5V 四种波形
4、1kHz 3V 四种波形(含输出正弦波时的频谱图)
五、总结
所使用的FPGA资源如图:
由于工作主要放在了DDS工作上,没有对oled模块进行优化,导致显示部分成为占用资源最多的部分(663+149)/1076。
硬件描述存在约束错误,但经过上板实测,可以正常使用。通过本次活动收获颇丰,学习了Verilog语言基础,并实现了一些基础的功能。感到了FPGA与单片机的区别。不过本次设计也有一些小遗憾,受限于家里的仪器条件,不能进一步精细调整,希望以后能够有时间做的更加完善。