救护车笛声
让我们交替发出两个音调。首先我们使用一个24位的计数器“tone”来产生一个低频的方波。其最高有效位(tone[23])以大约1.5Hz的频率翻转。
我们使用这一位(tone[23])来控制主计数器产生在两个频率之间切换的输出波形,这样一来就可以交替发出两个音调。下面是对应的Verilog HDL代码。
代码
- module music(clk, speaker);
- input clk;
- output speaker;
- parameter clkdivider = 25000000/440/2;
- reg [23:0] tone;
- always @(posedge clk) tone <= tone+1;
- reg [14:0] counter;
- always @(posedge clk) if(counter==0) counter <= (tone[23] ? clkdivider-1 : clkdivider/2-1); else counter <= counter-1;
- reg speaker;
- always @(posedge clk) if(counter==0) speaker <= ~speaker;
- endmodule
警车笛声
问题现在变得复杂起来。我们需要产生一个音调的变化,使之听起来像是警车的笛声。
仍然从“tone”计数器开始。我们仅使用23位,这样便可以得到两倍与前面的频率(最高有效位大约以3Hz的频率翻转)
下面是如何产生变化的音调的技巧。使用一个寄存器“ramp”来表征当前的音调,则要求ramp的值在某一区间来回变化,例如...-2-1-0- 1-2-3-...-127-126-125-...-2-1-0-1-2-...。考虑“tone”计数器的15到21位(tone[21:15]), 这是一个在0到127之间循环递增的值,0-1-2-...-127-0-。再考虑这几位的反转,即~tone[21:15],这是一个在127-0之间循环递减的值。如果能控制ramp在这两个值之间来回切换,即可得到一个形如...-0-1-2-...-127-126-125-...的计数器。而这个变化规律正好符合警车笛声的音调变化规律。为了让ramp在这两个值之间来回切换,我们使用tone[22]来控制。可以这样考虑,tone[22: 15]从0计数,对于前128个值(0-127),tone[22]等于0,后128个值(128-255),tone[22]等于1。于是我们就可以使用tone[22]来控制ramp的取值,当tone[22]等于0时,让ramp等于tone[21:15],当tone[22]等于1时,让ramp 等于~tone[21:15]。具体的硬件描述语言如下:
代码
- wire [6:0] ramp = (tone[22] ? tone[21:15] : ~tone[21:15]);
- // 含义
- // 当 tone[22]等于1 取 ramp=tone[21:15] 否则 ramp=~tone[21:15]
这样一来ramp就会在7b'0000000与7b'1111111之间来回变化. 为了得到一个对于产生声音有用的值, 我们在其前面补上两位数据"01",并且在其尾部也补上6个0,即"000000"。
代码
- wire [14:0] clkdivider = {2'b01, ramp, 6'b000000};
通过这样的处理,"clkdivider" 就拥有了一个在15'b010000000000000 与 15'b011111111000000之间来回变化的值(或者以16进制表示在15'h2000 与 15'h3FC0,以十进制表示在8192 到 16320之间变化)。当输入频率为25MHz时,将产生频率在765Hz到1525Hz之间变化的音调,从而产生类似于警车笛声的声音。下面是整个模块的Verilog HDL语言描述。
代码
- module music(clk, speaker);
- input clk;
- output speaker;
- reg [22:0] tone;
- always @(posedge clk) tone <= tone+1;
- wire [6:0] ramp = (tone[22] ? tone[21:15] : ~tone[21:15]);
- wire [14:0] clkdivider = {2'b01, ramp, 6'b000000};
- reg [14:0] counter;
- always @(posedge clk) if(counter==0) counter <= clkdivider; else counter <= counter-1;
- reg speaker;
- always @(posedge clk) if(counter==0) speaker <= ~speaker;
- endmodule
高速追击
现在让我们看看如何让FPGA发出“高速追击”的声音。这个时候警笛声时快时慢。因此使用"tone[21:15]" 来得到一个快速的变调, 而使用 "tone[24:18]"来得到一个慢速的变调。
代码
- wire [6:0] fastsweep = (tone[22] ? tone[21:15] : ~tone[21:15]);
- wire [6:0] slowsweep = (tone[25] ? tone[24:18] : ~tone[24:18]);
- wire [14:0] clkdivider = {2'b01, (tone[27] ? slowsweep : fastsweep), 6'b000000};
完整的Verilog HDL代码是这样子的:
代码
- module music(clk, speaker);
- input clk;
- output speaker;
- reg [27:0] tone;
- always @(posedge clk) tone <= tone+1;
- wire [6:0] fastsweep = (tone[22] ? tone[21:15] : ~tone[21:15]);
- wire [6:0] slowsweep = (tone[25] ? tone[24:18] : ~tone[24:18]);
- wire [14:0] clkdivider = {2'b01, (tone[27] ? slowsweep : fastsweep), 6'b000000};
- reg [14:0] counter;
- always @(posedge clk) if(counter==0) counter <= clkdivider; else counter <= counter-1;
- reg speaker;
- always @(posedge clk) if(counter==0) speaker <= ~speaker;
- endmodule