重要:总之一句话,与外部信号通信时一定要多一个心眼。
在下面的讲解中,你会发现4、6小节都用了双重甚至是三重的always语句。那是因为此时接收的数据是异步的,通过多重always语句可以避免冒险竞争,可以同步时钟信号。
也许你也可以看出在1小节中,我们用了双重的“<=”赋值语句,其目的是检测到wr_state信号的变化,因为是外部信号,即使是存入寄存器也还是不太稳定,所以我们就把数据传给wr1和wr2两个寄存器,这样就稳定了,然后比较前后,就可以知道变化了。
1、那么当我们想知道寄存器的变化情况该怎么办?
可以用下列语句:
wire wr_state = mcu_cs_n || mcu_wr_n; //外部信号
always@(posedge clk or negedge rst_n) begin
wr1<=wr_state;
wr2<=wr1;
end
wire pos_wr=~wr2&&wr1; //这样就可以间接地检测到寄存器的变化了。
2、我们如何检测变化?
wire wr_state=mcu_cs_n||mcu_wr_n; //同时都是低电平的时候
wire pos_wr=~wr2&&wr1; //这是很常见的对一个信号稳定后的检测
与 或 非 都可以进行想要的处理。
3、关于数据是否稳定的问题?
且看下面的语句:
always@(posedge clk or negedge rst_n) begin
if(wr_state) //这里里可以检测到变化
mcu_addr_r<=mcu_addr; //地址锁存
mcu_db_r<=mcu_db; //数据锁存
end
分析:这里在检测到wr_state变化时,就马上锁存数据,那么,总线(mcu_addr和mcu_db)上的数据此时是否稳定呢,不可而知。所以比较保险的做法,就是延迟一两个时钟周期,再锁存数据。
4、那么问题来了,我们如何延迟一两个时钟周期呢?
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
reqr1<=1'b1;
reqr2<=1'b1;
reqr3<=1'b1;
end
else begin
reqr1<=req;
reqr2<=req1;
req3<=req2;
end
wire pos_req1=reqr1&~reqr2; //延迟一个时钟周期
wire pos_req2=reqr2&~reqr3; //延迟两个时钟周期
分析:
这种写法不仅可以检测到req的数据变化,还可以检测到延迟的数据变化,还可以随意延迟N个时钟周期,在延迟期间,其他数据就得以稳定。
5、如何看待程序中的硬件连接?
可以用下列图示,表示硬件连接。
6、不可在两个always语句中对同一个寄存器赋值,如何破解?
可以申请两个寄存器。
***************************************
reg key_rst;
always @(posedge clk or negedge rst_n)
if (!rst_n) key_rst <= 3'b111;
else key_rst <= {sw3_n,sw2_n,sw1_n};
***************************************
reg low_sw;
always @(posedge clk or negedge rst_n)
if (!rst_n) low_sw <= 3'b111;
else if (cnt == 20'hfffff)
low_sw <= {sw3_n,sw2_n,sw1_n};
***************************************
wire[2:0] key = key_rst & (~low_sw);
7、编写程序要从硬件连接的角度去思考
如第7要点,对于同一个按键,我们要看到的是下面的硬件连接。
话说只有这样的硬件连接才看着舒服些,如果是接着在key_rst后面在获取按键的值就有些不妥了,因为key_rst后面已经有了一些组合逻辑的的门电路,本身就比较复杂了。
8、输出可以是reg寄存器吗?不可以,最好是wire型。
reg就是用always语句处理,wire就是用assign语句处理,所以通常是assign语句作为输出,如下代码:
reg d1;
reg d2;
reg d3;
always @ (posedge clk or negedge rst_n)
if (!rst_n) begin
d1 <= 1'b0;
d2 <= 1'b0;
d3 <= 1'b0;
end
else begin //某个按键值变化时,LED将做亮灭翻转
if ( led_ctrl[0] ) d1 <= ~d1; //这里不可以直接用led_d3<=led_ctrl,因为led不是reg型,况且,输出不能是寄存器,只能是wire型
if ( led_ctrl[1] ) d2 <= ~d2;
if ( led_ctrl[2] ) d3 <= ~d3;
end
assign led_d3 = d1 ? 1'b1 : 1'b0; //LED翻转输出
assign led_d2 = d2 ? 1'b1 : 1'b0;
assign led_d1 = d3 ? 1'b1 : 1'b0;
9、同一个寄存器不可以在两个always语句中赋值,但是可以在两个语句中判断该寄存器。
reg led_dir;
reg led_on;
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
led_dir<=1'b1;
led_on<=1;
end
else if(key_r_later[0])//按键0---LED开关
led_on<=~led_on;
else if(key_r_later[1])
led_dir<=1'b1;
else
led_dir<=1'b0;
//--------------------------------------------
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
led_r<=4'b0001;
end
else if(cnt==24'hffffff&&led_on) begin
if(led_dir)
led_r<={led_r[2:0],led_r[3]};
else
led_r<={led_r[0],led_r[3:1]};
end
10、对于input类型的端口
我们没法改变其原值。因为他是由外部输入决定的,因此,我们不可以在对其赋值。我没只能是读取其值。
11、可以滤波的一种写法
由于FPGA的周期很短,全频率很高,计算速度很快,这些有可能在检测的时候引进一些干扰波。怎样滤除这些干扰,下面就是一种方法。
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
rs232_rx0 <= 1'b0;
rs232_rx1 <= 1'b0;
rs232_rx2 <= 1'b0;
rs232_rx3 <= 1'b0;
end
else
begin
rs232_rx0 <= rs232_rx;
rs232_rx1 <= rs232_rx0;
rs232_rx2 <= rs232_rx1;
rs232_rx3 <= rs232_rx2;
end
end
//下面的下降沿检测可以滤掉<20ns-40ns的毛刺(包括高脉冲和低脉冲毛刺)
assign neg_rs232_rx = rs232_rx3 & rs232_rx2 & ~rs232_rx1 & ~rs232_rx0;
// 0123
// 0000------0 0111-------0
// 1000------0 0011-------1
// 1100------0 ----> 0001-------0
// 1110------0 0000-------0
// 1111------0 1000-------0
11、如何一次性输出所有数组寄存器的数据?
if(num == 4'd12) //到这里是一起都传递过去
begin
num <= 4'd0;
rx_data_r <= rx_temp_data; //但是我们不能在这里传递,所以需要一个寄存器过渡。
end
assign rx_data = rx_data_r;
12、模块中有两种输入,第一种是clk和rst_n;第二种是其他引脚输入,如 bps_clk,我们如何应对?
对于第一种情况:
reg num; //自己创建寄存器
always@(posedge clk or negedge rst_n)
if(!rst_n)
num<=1'b0;
else
num<=num+1'b1; //这里对寄存器赋值
对于第二种情况:
input num;
reg num; //哎!实事求是吧,这也是其中一种情况。。。。。。
always@(posedge clk or negedge rst_n)
if(!rst_n)
num<=1'b0;
else
num<=num+1'b1; //这里对寄存器赋值
13、与FPGA外部端口的连接,如何设置???
FPGA与外部信号的链接,是一对一的,与端口传输的是多少bit无关。
例如:
module ps2_top(clk,rst_n,ps2k_clk,ps2k_data,rs232_tx);
input clk,rst_n;
input ps2k_clk;
input ps2k_data; //这里传输的8bit数据,但是我们秉持一对一的原则。
output rs232_tx;//这是传输1bit数据
reg[7:0] ps2k_data_r;//重新定义一个寄存器,用来储存8比特数据。
endmodule
14、注意的是在不同的if下,往往会产生不同的结果,一定要想清楚。
反正是注意就行了,慢慢就熟练了。
else if(num==4'd10)//接收完毕
begin
if(ps2_byte_r==8'hf0)//如果是松开按键
key_f0<=1'b1;//松开标志位为1,说明是无效数据
else //如果是按下按键
begin
if(!key_f0)//如果是0(说明这是按下的键,应该接收)
begin //说明有键按下
ps2_int_r<=1'b1;//开始传输
ps2_dyte_r1<=ps2_byte_r; //锁存当前键值
end
else //如果是1(说明这是松开的按键,我们就不用接收)
begin
ps2_int_r<=1'b0;
key_f0<=1'b0;
end
end
end