FPGA 08 基础 蜂鸣器实验音乐谱播放器实验
模块名称: music_test
主要功能 :每隔一段时间,输出的音调由低到高,且每个音调的发出的声音是固定的。
实现(设计)流程: 1、内部一共由3个部分(2个定时器+1个查找表)组成,自左向右分别是: 音频切换循环定时器、音频查找表、音频生成器
①音频切换循环定时器 : 通过设置内部循环定时器的计数值,保持当前的音调持续一个固定的时间。当每次计数器计数到设置的值以后,产生一个Full_flag 脉冲信号。随后将脉冲信号给音频查找表,作为其Clk信号,随后切换下一个信号。另外,循环定时计数器开始重新计数。
②音频查找表模块,由于Clk信号是:循环定时器的输出脉冲信号。所以,信号周期是定时器设定的周期。在该模块中,每当时钟上升沿到来的时候,音频信号进行切换一次(查找表值 +1),在内部通过自己设置查找表输出对应的信号,切换音频信号主要输出的是:计数器的计数值CNT_ARR[31:0]和带比较值Compare_NOW[31:0]。
③音频生成器模块,音频查找表输出了一个循环定时的计数器的计数值信号,这个信号给了音频生成器模块(本质就是一个定时器),CNT_ENABLE =1 的时候,循环定时器开始工作。在输出端口我们可以看到有两个输出: Full_flag 信号(计数器计满输出标志信号),和 ARR[31:0]当前(实时)计数器输出信号。
在整个模块中,音频查找表的比较值Compare_NOW和音频生成器的实时输出计数器值ARR[31:0]进行比较,计数器值 >= 比较值,输出高电平,否则输出低电平。
各个音调对应的频率(在sound_lut 模块中需要用到):
music_test.v 文件
module music_test(
Clk, //时钟信号
Rst_n, //复位信号
CNT_ENABLE, //Cnt_Enable=1 时,开启定时
//Cnt_Enable=0 时低电平时,不使能定时
beep
);
input Clk ;
input Rst_n ;
input CNT_ENABLE ;
output beep ;
wire [31:0]CNT_NOW ;
wire Full_Flag1;
wire [31:0]CNT_ARR;
wire [31:0]Compare_cnt;
//这里的频率是: 50_000_000/50_000 = 1000Hz 的频率
timer beep0(
.Clk(Clk) , //时钟
.Rst_n(Rst_n) , //复位信号
.CNT_ARR(CNT_ARR) , //定时器重装载值 24999(类似与32的重装载值),相当于定时
.MODE(1'b1) , // Mode : 1 循环定时 Mode : 0 单次定时
.CNT_ENABLE(~CNT_ENABLE) , //CNT_ENABLE : 1 定时器计数器使能,开始计数 //CNT_ENABLE : 0 定时器计数不使能,停止计数
// ~CNT_ENABLE 这里给定的是取反的信号,主要是太吵,不需要可以更改
.CNT_NOW(CNT_NOW) , //实时定时器计数器
.Full_Flag() //计数完成标志位,这里没有用到,且Full_Flag是输出信号,所以不用的时候可以不连接信号
);
sound_lut sound_lut0(
.Clk(Full_Flag1),
.Rst_n(Rst_n),
.ARR(CNT_ARR), // CNT_ARR 传递给 beep0 的 CNT_ARR 使用
.Compare_cnt(Compare_cnt)
);
//250ms固定定时,用于切换音调,Full_Flag1信号用于传递给 sound_lut0 模块使用
timer timer_250ms(
.Clk(Clk),
.Rst_n(Rst_n),
.CNT_ARR(6250000),
.MODE(1'b1),
.CNT_ENABLE(1'b1),
.CNT_NOW(),
.Full_Flag(Full_Flag1)
);
assign beep =(CNT_NOW >=Compare_cnt)? 1'b1:1'b0 ; //只有一个时钟周期是高电平,CNT_NOW是在 0-24999+1之间徘徊
endmodule
sound_lut.v 文件
// 音频查找表文件
// 里面的定时器之和比较值是 50%的占空比数值
module sound_lut(
Clk,
Rst_n,
ARR ,
Compare_cnt
);
input Clk;
input Rst_n;
output reg[31:0]ARR;
output reg[31:0]Compare_cnt;
reg [4:0]index;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
index <= 0;
else if(index >= 5'd20)
index <= 0;
else
index <= index + 1'b1;
always@(*)begin
case(index)
0 : ARR = 191130;
1 : ARR = 170241;
2 : ARR = 151689;
3 : ARR = 143183;
4 : ARR = 127550;
5 : ARR = 113635;
6 : ARR = 101234;
7 : ARR = 95546 ;
8 : ARR = 85134 ;
9 : ARR = 75837 ;
10: ARR = 71581 ;
11: ARR = 63775 ;
12: ARR = 56817 ;
13: ARR = 50617 ;
14: ARR = 47823 ;
15: ARR = 42563 ;
16: ARR = 37921 ;
17: ARR = 35793 ;
18: ARR = 31887 ;
19: ARR = 28408 ;
20: ARR = 25309 ;
default: ARR = 191130;
endcase
end
always@(*)begin
case(index)
0 : Compare_cnt= 95565 ;
1 : Compare_cnt= 85120 ;
2 : Compare_cnt= 75844 ;
3 : Compare_cnt= 71591 ;
4 : Compare_cnt= 63775 ;
5 : Compare_cnt= 56817 ;
6 : Compare_cnt= 50617 ;
7 : Compare_cnt= 47773 ;
8 : Compare_cnt= 42567 ;
9 : Compare_cnt= 37939 ;
10: Compare_cnt= 35792 ;
11: Compare_cnt= 31887 ;
12: Compare_cnt= 28408 ;
13: Compare_cnt= 25308 ;
14: Compare_cnt= 23912 ;
15: Compare_cnt= 21282 ;
16: Compare_cnt= 18960 ;
17: Compare_cnt= 17896 ;
18: Compare_cnt= 15943 ;
19: Compare_cnt= 14204 ;
20: Compare_cnt= 12655 ;
default: Compare_cnt = 24999;
endcase
end
endmodule
timer.v 文件
//通用定时器模块
//描述:
//通过定义 timer 里面的 input 的数值
// 得到 output 里面的数值,这样就可以得到我们想要的输出(2个 分别是 CNT_NOW 和 Full_Flag 两个输出信号)
// 时钟和复位信号是输入信号,一般由系统给定
// CNT_ARR、MODE、CNT_ENABLE 可以自己给定固定值或者通过外部的输入给定一个值(信号)
module timer(
Clk , //时钟
Rst_n , //复位信号
CNT_ARR , //定时器重装载值(类似与32的重装载值)
MODE , // Mode : 1 循环定时 Mode : 0 单次定时
CNT_ENABLE , //CNT_ENABLE : 1 定时器计数器使能,开始计数
//CNT_ENABLE : 0 定时器计数不使能,停止计数
CNT_NOW , //实时定时器计数器
Full_Flag //计数完成标志位
);
// 定义:
input Clk ;
input Rst_n;
input [31:0]CNT_ARR ; //通用32位定时计数器
input MODE ;
input CNT_ENABLE;
output [31:0]CNT_NOW ; //当前32位定时计数器的值
output reg Full_Flag ; //计数完成标志位
//自己模块定义的信号
reg [31:0]cnt; //定义一个32位的计数器
reg oneshot ; //定义一个 单次计数值计满 触发信号
wire Full_Flag_r; //Full_Flag_r 用于延时Full_Flag 的一个周期
assign CNT_NOW = cnt; //定时器的值
assign Full_Flag_r = (CNT_NOW == CNT_ARR - 1)?1'b1:1'b0;
//第1个 always 块
always@(posedge Clk)
Full_Flag <= Full_Flag_r;
//第2个 always 块
always@(posedge Clk or negedge Rst_n)
if(!Rst_n) //复位信号到来,数据清零(qingling)
cnt <=0;
else if(MODE == 1'b1)begin //判断是否是循环模式
if((CNT_ENABLE == 1'b1) && (cnt < CNT_ARR)) //判断CNT_ENABLE 是否启动,且计数值是否小于重装载值
cnt <= cnt + 1'b1; //cnt++
else //否则数据清零
cnt <= 0;
end
else if(!MODE)begin //判断是否是单次计数模式
if(oneshot) //oneshot信号为1,表示满足一次计数条件
cnt <= cnt + 1'b1;
else
cnt <= 0;
end
//第3个 always 块
always@(posedge Clk or negedge Rst_n)
if(!Rst_n) //复位信号清零
oneshot <= 1'b0;
else if(!MODE)
begin //判断是在一次计数模式下
if(CNT_ENABLE == 1'b1) //判断CNT_ENABLE 是否启动
oneshot <= 1'b1;
else if(CNT_NOW == CNT_ARR - 1) //判断计数值是否等于重转载值
oneshot <= 1'b0;
else //保持不变
oneshot <= oneshot;
end
else//在循环模式下,oneshot =0 ;
oneshot <= 1'b0;
endmodule
timer_tb.v 文件
`timescale 1ns/1ns
`define clk_period 20
// 定时器定时模块测试源文件
module timer_tb; // 定义的测试模块的名称,一般命名 是后面加 _tb,表示这是测试模块
// 测试模块的输入输出 定义: input 类型 --> reg 类型替换
// output 类型 --> wire 类型替换
reg Clk ;
reg Rst_n;
reg [31:0]CNT_ARR ; //通用32位定时计数器
reg MODE ;
reg CNT_ENABLE;
wire [31:0]CNT_NOW ; //当前32位定时计数器的值
wire Full_Flag ; //计数完成标志位
timer timer0(
.Clk(Clk) , //时钟
.Rst_n(Rst_n) , //复位信号
.CNT_ARR(CNT_ARR) , //定时器重装载值(类似与32的重装载值)
.MODE(MODE) , // Mode : 1 循环定时 Mode : 0 单次定时
.CNT_ENABLE(CNT_ENABLE) , //CNT_ENABLE : 1 定时器计数器使能,开始计数 //CNT_ENABLE : 0 定时器计数不使能,停止计数
.CNT_NOW(CNT_NOW) , //实时定时器计数器
.Full_Flag(Full_Flag) //计数完成标志位
);
/*********** 定义初始化测试信号 ************/
initial Clk = 1; //定义时钟信号
always #(`clk_period/2) Clk = ~Clk;
initial begin //执行一次,叫 initial 加入 begin
//初始化所有输入信号
Rst_n = 0;
CNT_ARR = 0;
MODE = 0;
CNT_ENABLE = 0;
#(`clk_period*20 + 1);
//复位信号 Rst_n = 1; 模块正式开始工作
Rst_n = 1;
#(`clk_period*20);
//设置预设值为1000,模式为循环定时模式
CNT_ARR = 1000;
MODE = 1;
#(`clk_period*20); //设置为循环模式,延时20个时钟周期
CNT_ENABLE = 1; //开始启动计数定时器
#(`clk_period*12000);
CNT_ENABLE = 0; //关闭计数定时器
#(`clk_period*20);
//设置预设值为600,模式为循环定时模式
CNT_ARR = 600;
MODE = 1;
#(`clk_period*20);
CNT_ENABLE = 1;
#(`clk_period*8000);
CNT_ENABLE = 0;
#(`clk_period*20);
//设置预设值为1000,模式为单次定时模式
CNT_ARR = 1000;
MODE = 0;
#(`clk_period*20); // CNT_ENABLE = 1; #(`clk_period) CNT_ENABLE = 0; 表示给定的是一个脉冲信号,进入的是单次计数模式
CNT_ENABLE = 1;
#(`clk_period);
CNT_ENABLE = 0;
#(`clk_period*1200);
//设置预设值为600,模式为单次定时模式
CNT_ARR = 600;
MODE = 0;
#(`clk_period*20); // CNT_ENABLE = 1; #(`clk_period) CNT_ENABLE = 0; 表示给定的是一个脉冲信号,进入的是单次计数模式
CNT_ENABLE = 1;
#(`clk_period);
CNT_ENABLE = 0;
#(`clk_period*1200);
$stop;
end
endmodule
assigment .tcl 文件
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to Clk
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to Rst_n
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to CNT_ENABLE
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to beep
set_location_assignment PIN_E1 -to Clk
set_location_assignment PIN_E16 -to Rst_n
set_location_assignment PIN_M16 -to CNT_ENABLE
set_location_assignment PIN_L16 -to beep