数字信号分析仪
一、摘要
2011年国家电子设计大赛E题,设计一个简易数字信号传输分析仪。可参照网络E题设计内容及要求。这里主要关注m序列发生器设计及同步信号提取两部分。
二、实验平台
ModelSim-Altera 6.4a (Quartus II 9.0)
三、m序列发生器
3.1 m序列的产生
m序列可转化为线性反馈移位寄存器结构,如下图所示。
图1 线性反馈移位寄存器结构
数字信号V1为f1(x)=1+x2+x3+x4+x8的m序列,根据要求的m序列和线性反馈移位寄存器的结构,得到下面的结构图。
图2 得到的m序列线性反馈移位寄存器结构
设m序列的初始状态为1000_0000,则根据反馈网络得到的下一个状态为:
初始状态:1 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0
1 0 1 0 0 0 0 0
1 1 0 1 0 0 0 0
0 1 1 0 1 0 0 0
0 0 1 1 0 1 0 0
0 0 0 1 1 0 1 0
1 0 0 0 1 1 0 1
1 1 0 0 0 1 1 0
1 1 1 0 0 0 1 1
…
上述每行数据的最后一个数据组成的序列即是m序列。
3.2 时钟信号的产生
由于系统要求数据传输速率为10kbps~100kbps,以10kbps为步进。
首先以10kbps为例,即传输速率为,则传输1bit所需要的时间为,所以数字信号产生的时钟周期为。而FPGA采用50MHZ的晶振,所以一个时钟周期为20ns,FPGA采用计数器的方式产生数字发生器所需要的时钟,设计数器为x,则满足下面的式子:
算出x = 9765,所以x/2 = 9765/2,即高低计数器里面的值分别是9765和4882。
同样,当传输速率为100kbps时,即传输速率为,则传输1bit所需要的时间为,所以数字信号产生的时钟周期为。而FPGA采用50MHZ的晶振,所以一个时钟周期为20ns,FPGA采用计数器的方式产生数字发生器所需要的时钟,设计数器为x,则满足下面的式子:
算出x = 976,所以x/2 = 976/2,即高低计数器里面的值分别是976和488。
因为传输速率是以10kbps为步进,所以在高低计数器里面的值分别如下:
其中,temp为步进刻度。
3.3 verilog 实现
根据上面的结构,编写程序如下:
module mXuLie(
CLK,
KEY,
LED,
MOUT,
M_CLOCK,
NOISE_OUT
);
input CLK;
input KEY;
output LED;
output MOUT;
output NOISE_OUT;
output M_CLOCK;
reg [3:0] LED;
// for m sequence
reg [7:0] cnt = 8'b1000_0000;
reg [23:0] counter;
reg m_clk;
assign MOUT = cnt[0];
assign M_CLOCK = m_clk;
// 10 scale
reg [23:0] counter_L = 24'h989;
reg [23:0] counter_H = 24'h1312;
reg [3:0] flag;
// for reset
//reg [23:0] counter_delay;
reg reset = 1'b1;
/*
always@(posedge CLK)
begin
counter_delay <= counter_delay + 1;
if(counter_delay == 24'h1ffff)
begin
// counter_delay <= 0;
reset <= 1;
end
end
*/
reg [19:0] key_cnt/*synthesis noprune*/;
reg key_d1 /*synthesis noprune*/;
reg key_d2/*synthesis noprune*/;
wire key_en/*synthesis keep*/;
assign key_en = ((~key_d1)&(key_d2));
// Away shaking
always@(posedge CLK or negedge reset)
begin
if(~reset)
begin
key_cnt <= 0;
key_d1 <= 0;
key_d2 <= 0;
end
else
begin
key_d2 <= key_d1;
key_cnt <= key_cnt + 1;
if(key_cnt == 20'hfffff)
key_d1 <= KEY;
end
end
// 10 scale
always@(posedge CLK or negedge reset)
begin
if(~reset)
begin
flag <= 0;
LED <= 4'h0;
counter_L <= 24'h989;
counter_H <= 24'h1312;
end
else
begin
case(flag)
0:
begin
counter_L <= 24'h989;
counter_H <= 24'h1312;
LED <= 4'b0001;
if(key_en == 1)
flag <= 1;
end
1:
begin
counter_L <= 24'h895;
counter_H <= 24'h112A;
LED <= 4'b0010;
if(key_en == 1)
flag <= 2;
end
2:
begin
counter_L <= 24'h7A1;
counter_H <= 24'hF42;
LED <= 4'b0011;
if(key_en == 1)
flag <= 3;
end
3:
begin
counter_L <= 24'h6AD;
counter_H <= 24'hD5A;
LED <= 4'b0100;
if(key_en == 1)
flag <= 4;
end
4:
begin
counter_L <= 24'h5B9;
counter_H <= 24'hB72;
LED <= 4'b0101;
if(key_en == 1)
flag <= 5;
end
5:
begin
counter_L <= 24'h4C5;
counter_H <= 24'h98A;
LED <= 4'b0110;
if(key_en == 1)
flag <= 6;
end
6:
begin
counter_L <= 24'h3D1;
counter_H <= 24'h7A2;
LED <= 4'b0111;
if(key_en == 1)
flag <= 7;
end
7:
begin
counter_L <= 24'h2DD;
counter_H <= 24'h5BA;
LED <= 4'b1000;
if(key_en == 1)
flag <= 8;
end
8:
begin
counter_L <= 24'h1E9;
counter_H <= 24'h3D2;
LED <= 4'b1001;
if(key_en == 1)
flag <= 9;
end
9:
begin
counter_L <= 24'hF5;
counter_H <= 24'h1EA;
LED <= 4'b1010;
if(key_en == 1)
flag <= 0;
end
default:
begin
counter_L <= 24'hF5;
counter_H <= 24'h1EA;
LED <= 4'b0001;
end
endcase
end
end
// generate clock for m sequence
always@(posedge CLK or negedge reset)
begin
if(~reset)
begin
counter <= 0;
m_clk <= 0;
end
else
begin
counter <= counter + 1;
if(counter == counter_L)
begin
m_clk <= 1;
end
else if(counter == counter_H)
begin
m_clk <= 0;
counter <= 0;
end
end
end
// generate m sequence
always@(posedge m_clk or negedge reset)
begin
if(~reset)
begin
cnt <= 8'b1000_0000;
end
else
begin
cnt[6:0] <= cnt[7:1];
cnt[7] <= cnt[0] ^ cnt[4] ^ cnt[5] ^ cnt[6];
end
end
reg [11:0] noise_cnt = 12'b1000_0000_0000;
reg [23:0] noise_counter;
reg noise_clk;
assign NOISE_OUT = noise_cnt[0];
// generate noise sequence
always@(posedge noise_clk or negedge reset)
begin
if(~reset)
begin
noise_cnt <= 12'b1000_0000_0000;
end
else
begin
noise_cnt[10:0] <= noise_cnt[11:1];
noise_cnt[11] <= noise_cnt[0] ^ noise_cnt[7] ^ noise_cnt[8] ^ noise_cnt[11];
end
end
// generate clock for noise sequence
always@(posedge CLK or negedge reset)
begin
if(~reset)
begin
noise_counter <= 0;
noise_clk <= 0;
end
else
begin
noise_counter <= noise_counter + 1;
if(noise_counter == 24'h2)
begin
noise_clk <= 1;
end
else if(noise_counter == 24'h4)
begin
noise_clk <= 0;
noise_counter <= 0;
end
end
end
endmodule
上述程序包括m序列产生,m序列的时钟信号,可调m序列速率,10~100kbps,以及伪随机序列。
图3 生成的m序列
图4 叠加伪随机信号之后的波形
图5 经过低通滤波器之后的波形
四、曼彻斯特编码
曼彻斯特编码有两种,如下图所示。这里采用的是第二种,即直接将原始数据和时钟信号取异或,即得到的曼彻斯特编码。
图6 曼彻斯特编码
五、同步信号提取
观察曼彻斯特编码可知,设原始时钟Clock周期为T,将原始数据Data经过曼彻斯特编码,编码后的数据的高/低电平保持时间有2种情况,T和T/2。因此,如果能将编码后的数据根据其跳变沿得到的脉冲信号与补出的脉冲信号(这里是指在高/低电平保持时间为T时,在T/2处补出脉冲信号)相叠加,并做进一步处理,就可以得到同步信号。根据标注的部分,这里分3个详细步骤完成。
第一步:根据编码后的数据的跳变沿得到脉冲信号
程序如下:
always@(posedge clk)
begin
q1<=data_in;
q2<=q1;
end
assign clk_1=(q2^q1);
波形如下:
图7 根据编码后的数据的跳变沿得到脉冲信号
注解:这里的clk为同步信号提取端系统时钟,这个时钟周期要远远小于待提取的同步信号(同步时钟),下同。clk_1为得到的脉冲信号。
第二步:补出脉冲信号
这是难点之所在,采用的办法是应用计数器。采用一计数器counter和一个寄存器value,counter在系统时钟clk下计数,当clk_1 = 1时,也即遇到clk_1的高电平时,将counter的当前值与value进行比较,如果counter的当前值大于value,将counter当前值赋于value,之后counter清零,再次计数,当counter的当前值等于value/2时,这时,应该补出一个脉冲信号。
注意这里的一个细节所在,编码后的第一个数据的保持时间问题。前面已经说了,保持时间只有T和T/2两种情况;
第一种情况:当编码后的第一个数据的保持时间为T/2时,而后面任意数据保持时间为T时,均能正常补出相应的脉冲。
第二种情况:当编码后的第一个数据的保持时间为T时,无论后面的数据保持时间是多少,counter的值均不会大于value的值,这样就判断不出value的值是不是最大值,因此也就不能确定是否要在value/2处补出脉冲。
这里采用标志位加状态机的方法,来区分什么时候要在value/2处补出脉冲。
程序如下:
- always@(posedge clk)
- begin
- counter <= counter + 1;
- if(clk_1)
- begin
- case(state)
- 0:
- begin
- value <= counter - 2;
- state <= 1;
- counter <= 0;
- end
- 1:
- begin
- if(value < counter)
- begin
- flag1 = 1;
- value <= counter;
- end
- if(value > counter)
- begin
- flag2 <= 1;
- end
- state <= 1;
- counter <= 0;
- end
- endcase
- end
- if((counter == value/2 - 1)&&((flag1 == 1)||(flag2 == 1)))
- begin
- clk_2 <= ~clk_2;
- clk_3 <= (clk_2 <= ~clk_2);
- end
- if(counter == vaule)
- begin
- clk_2 <= ~clk_2;
- clk_3 <= (clk_2 <= ~clk_2);
- end
- end
- always@(posedge clk)
- begin
- clk_4 <= clk_3;
- end
- assign clk_5 = clk_3^clk_4;
- assign clk_6 = clk_1|clk_5;
程序变量说明:
clk_2、clk_3、clk_4均为得到clk_5的中间时钟信号,clk_5为补出的时钟脉冲,clk_6为叠加的脉冲信号,flag1和flag2均为判断value和counter当前值大小的标志位。
程序解析:
第一种情况:当编码后的第一个数据的保持时间为T时。
首先,在系统时钟clk下,counter计数器递增,当遇到第一个clk_1 = 1时,进入状态机的"0"状态,这里vaule <= counter - 2;是因为第一个数据无论保持时间是T或是T/2,counter的计数值总会比后面数据保持时间为T或是T/2时大2,本质原因是阻塞赋值的影响,所以这里要减去2。之后,状态机的状态为"1",counter清零,退出if(clk_1)。
其次,后续clk_1 = 1到来时,进入并保持在状态机"1"状态,后面的counter只会出现value > counter这种情况,当遇到 value > counter 时,说明当前数据的保持时间为T/2,这时,就可以判断出之前的value为最大值了,也就可以在下面的value/2处补出脉冲(如果有value/2处的话)。
第二种情况:当编码后的第一个数据的保持时间为T/2时。
首先,这里跟第一种情况相同。
其次,后续clk_ = 1到来时,进入并保持在状态机"1"状态,后面的counter只会出现value < counter这种情况,当遇到value < counter时 ,说明当前数据的保持时间为T,这时,将当前counter值重新赋值于value,之后,就可以在下面的value/2处补出脉冲(如果有value/2处的话)。
程序的几点说明:
1. 第33行程序,counter == value/2 - 1这里之所以减1,是因为前面根据编码数据得到脉冲的时候是用的阻塞赋值,所以这里要相应的减去1。
2. 以上两种情况,如果前几个编码数据保持时间都是T的话,则提取出来的同步时钟的前面几个的时钟周期就为2T,直到遇到编码数据保持时间为T/2。
针对上述2点,可以有2个办法解决,第一个:发送编码数据时,先发送几个数据保持时间为T/2的无关数据;第二个:采用FIFO缓存编码,得到同步时钟之后在解码。这2个办法不在一一详述。
波形如下:
图8 第一个clk_1 = 1时,counter的值总比后面的大2
图9 补出的脉冲clk_5、叠加的脉冲clk_6
上图前几个数据保持时间均为T,正对应了“程序的几点说明”的第2点。
图10 补出的脉冲clk_5、叠加的脉冲clk_6
上图第一个数据保持时间为T/2。
第三步:进一步处理
根据得到的脉冲clk_6,整理成完整的时钟周期signal_out,有signal_out就可以很方便的解码曼彻斯特编码了,每次同步时钟上升沿,如果编码为0,则解码数据变为1;如果编码为1,则解码数据变为0。
程序如下:
always@(posedge clk_6)
begin
signal_out <= ~signal_out;
end
always@(posedge signal_out)
begin
if(data_in == 1)
data_out <= 0;
else
data_out <= 1;
end
波形如下:
图11 前几个数据保持时间为T时,提取的同步信号和数据。
图12 第一个数据保持时间为T/2时,提取的同步信号和数据。
PS:完整程序已上传附件,2011_guosai。