已验收,CHATGPT基底更改代码,针对方波以及上升耗时极短的方波表现良好,但上升时间较长的脉冲波实现不佳。因为上升时间极短的脉冲波近似方波,故本文章仍有效用。仅供参考、
如果对脉冲信号很执着,可以看我的另一篇博客,改了一点点
实验内容
使用直接测量法设计一个信号周期测量器,并动态显示测量结果。
实验代码
module zmj (
input clkref, // 参考时钟(如 100 MHz)
input ft, // 被测信号(ft)
output reg [7:0] SEG, // 动态显示位数信号
output reg [6:0] codeout, // 数位控制信号
output reg overflow, // 超量程指示灯
output reg [3:0] Y//数字
);
reg [1:0] SEL=2'b00; //位数选择器
reg [31:0] counter; //计数器,记录参考时钟周期数
reg [31:0] period; //存储测得的周期
reg reg_t1, reg_t2; //边沿检测寄存器
reg rest = 1'b1; //周期判断信号
// 边沿检测
always @(posedge clkref) begin
reg_t1 <= ft;
reg_t2 <= reg_t1;
if (reg_t1 == 1'b1 && reg_t2 == 1'b0 && rest == 1'b1) begin
// 上升
counter <= 0; // 清零计数器
rest <= 1'b0;
period <= counter * 20/1000+1; // 参考信号周期为20ns,转化为微妙
end
else if (reg_t1 == 1'b0 && reg_t2 == 1'b1 && rest == 1'b0) begin
// 下降
rest <= 1'b1;
end
else begin
counter <= counter + 1; // 计数
end
end
//超过量程
always@(period) begin
if(period>=1000) begin
overflow <=1'b1;
end
else begin
overflow <=1'b0;
end
end
//位数选择模块
always @(posedge ft) begin
if(SEL == 2'b11)
begin
SEL =2'b00;
end
else
begin
SEL<=SEL+1;
end
end
//选择位数
always @(SEL) begin
case(SEL)
2'b00: SEG=8'b00000001;
2'b01: SEG=8'b00000010;
2'b10: SEG=8'b00000100;
2'b11: SEG=8'b00001000;
endcase
end
//选择数字
always @(SEL) begin
case(SEL)
2'b00: Y=period%10;
2'b01: Y=period%100/10;
2'b10: Y=period%1000/100;
2'b11: Y=period%10000/1000;
endcase
end
//译码数字,动态扫描
always @(Y) begin
case (Y)
4'd0: codeout = 7'b1111110;
4'd1: codeout = 7'b0110000;
4'd2: codeout = 7'b1101101;
4'd3: codeout = 7'b1111001;
4'd4: codeout = 7'b0110011;
4'd5: codeout = 7'b1011011;
4'd6: codeout = 7'b1011111;
4'd7: codeout = 7'b1110000;
4'd8: codeout = 7'b1111111;
4'd9: codeout = 7'b1111011;
default: codeout = 7'b0000000;
endcase
end
endmodule
代码中已经标注一些解释,现在做详细解释:
首先,周期的处理第一步就是什么是周期:高电平加一个低电平,因此我们需要判断这个点评变化的拐点,就用到了reg_t1和reg_t2 <= reg_t1,他们将当前时刻的 ft
信号赋值给 reg_t1,
将上一周期 reg_t1
的值赋给 reg_t2(具体为什么会这样,我也不知道),实现了上升和下降的判断。
之后计数器进行运作,因为周期是高+低电平,所以设定了rest辅助判断,当判断上升沿并且rest为1,period存储结果、清零并开始计数,rest设定为0;接着遇到下降沿,rest修改回1,继续计数;再遇到上升沿并且rest为1,period存储结果、清零并开始计数,rest设定为0。循环往复,就可以得到每个周期的计数次数
计数次数×时钟信号周期,得到了被测信号的周期,÷1000得到微秒计数结果,最终得到了测量的结果。
之后就是动态显示处理,和实验六一样(我之前博客也有说过),设定八位SEG显示位置信号,Y数据存储信号,SEL进程信号,codeout译码信号。本次使用四个显示器,SEL为0-4计数,为0时选择period的个位传给Y,译码传给codeout,同时位置操作SEG为00000001,之后SEL为1,进程传十位,以此类推,实现显示微秒级h周期结果。
最后是超量程判断,本代码中设定4位显示器,给定信号位1khz-1mhz,微妙计数周期不会超量程,但是结合要求,我们设定当测定结果超过1000时,overflow就为1。
代码中两处投机取巧:因为clkref频率过高,会发生显示错误,可以用第二个时钟频率去稳定显示,这里为了方便,1KHZ-1MHZ的ft信号也可以使用,所以就直接用了。
其次在实验中period因为四舍五入(maybe)总缺1,所以我直接加了。
测试代码
`timescale 1 ns/ 1 ns
module zmj_vlg_tst();
reg clkref;
reg ft;
// wires
wire [7:0] SEG;
wire [6:0] codeout;
wire overflow;
wire [3:0] Y;
zmj i1 (
.SEG(SEG),
.clkref(clkref),
.codeout(codeout),
.ft(ft),
.overflow(overflow),
.Y(Y)
);
initial
begin
// code that executes only once
// insert code here --> begin
clkref=1'b0;
ft=1'b0;
//534*2微妙的周期
#534000 ft=~ft;
#534000 ft=~ft;
#534000 ft=~ft;
//234*2微妙的周期
#234000 ft=~ft;
#234000 ft=~ft;
#234000 ft=~ft;
//1230*2
#1230000 ft=~ft;
#1230000 ft=~ft;
#1230000 ft=~ft;
#1230000 ft=~ft;
// --> end
$display("Running testbench");
end
always
begin
#10 clkref = ~clkref;
// --> end
end
endmodule
如图,你可以修改ft更改测试信号周期。实验要求50MHZ作为时钟频率,因此clkref 10ns翻转一次。
仿真模拟
在period+1后,所有仿真模拟显示的周期测量都是最准确的数值,如果位置选择取决于ft,一个信号在test要多重复几次才好
依据TEST代码,在仿真中应有以1068us、468us和2460us为周期的三种ft信号,如图仿真结果正确。
overflow超进制信号在第三周期低电平结束后就出现了,这是因为代码的逻辑是测量相邻的高电平+低电平作为一个周期,在overflow变化处,程序将第二信号的最后一个高电平于第三信号的第一个低电平作为一个信号的周期去测量,得到周期为1463,234+1230=1424,验证正确,也足以见得overflow信号可以处理超出量程的问题。优化该问题,可以使用双重计数器,以高+低和低+高共同计数,得到一次一变化的period,会使测量更精准。
如图在第一次测量结束后,测得第一信号的周期按照SEG3-0位排序分别为:1067,overflow上升,误差主要来自于保留整数的机制问题。
如图在第二次测量结束后,测得第一信号的周期按照SEG3-0位排序分别为:0467,overflow下降,误差主要来自于保留整数的机制问题。
如图在第三次测量结束后,测得第三信号的周期按照SEG3-0位排序分别为:2459,误差主要来自于保留整数的机制问题。
引脚锁定
和实验六大差不差,添加测试信号输入和超量程显示:
最后,留下你的学号吧!