说到异步时钟域的信号处理,想必是一个FPGA设计中很关键的技术,也是令很多工程师对FPGA望而却步的原因。但是异步信号的处理真的有那么神秘吗?那么就让特权同学和你一起慢慢解开这些所谓的难点问题,不过请注意,今后的这些关于异步信号处理的文章里将会重点从工程实践的角度出发,以一些特权同学遇到过的典型案例的设计为依托,从代码的角度来剖析一些特权同学认为经典的跨时钟域信号处理的方式。这些文章都是即兴而写,可能不会做太多的分类或者归纳,也有一些特例,希望网友自己把握。
另外,关于异步时钟域的话题,推荐大家不妨去看看这些不错的文章,如《跨越鸿沟:同步世界中的异步信号》等。
首先说MCU与FPGA之间的异步通信,参加CPLD助学活动的朋友应该都注意到了那块BJ-EPM板子上预留了16PIN的单片机接口,但是那个实验里其实也没有给出什么实验代码。究其原因,大概是特权同学有点自私了吧(呵呵~~~),因为当初刚接触MCU与FPGA通信处理的时候,是为了做一个液晶控制板,用的是很老的EPM7128,资源很小,摸索了个把月才搞定,不过当时的处理方式上并不稳妥,后来随着不断学习不断积累经验才寻觅到现在的处理方式。不想公开源码自有所谓的“比较关键的技术”一说,现在想来蛮有些可笑的。网络这么大一个平台,凭什么你只索取不共享呢?所以,特权同学今后会努力把自己的点点滴滴设计经验和大家分享。当然了,在提出自己的观点和看法的同时,也一定会得到更多高人不同的也许更好的见解,帮助他人的同时自己也在进步,何乐而不为呢。
罗嗦了一大堆,步入正题吧……
首先,这个项目是基于单片机的应用,如果你对单片机的读写时序不是很熟练,不妨看看特权同学的一篇详细讨论51单片机扩展RAM读写时序的文章《单片机的扩展RAM读写时序》。下面简单看下11.0592MHz的51单片机的读写时序图吧。
大体和上面的波形相差无几,地址总线没有画出来,不过地址总线一般是会早于片选CS到来,并且晚于片选信号CS撤销(这个说法不是绝对的,但是至少对于下面的应用是这样)。
我们现在的工作是作为MCU的从机,即模拟MCU的扩展RAM。MCU若发出写时序,FPGA就得在数据稳定于数据总线时将其锁存起来;MCU发出读时序,FPGA就要在MCU锁存数据的建立时间之前把数据放到数据总线上,并且到MCU锁存数据的保持时间结束后才能将数据撤销。基本上,我们要干的就是这些活,下面讨论verilog在设计上如何实现,但是限于篇幅,不对时序分析做讨论,假定这是一个很理想的总线时序。
其实这个MCU的读写时序的时间相对还是很充裕的,因为我们的FPGA用的是50MHz的晶振。所以一个很基本的想法是要求我们把MCU端的信号同步到FPGA的时钟域上,达到异步信号的同步处理。
verilog代码:
//----------------------------------------------------------------------
//----------------------------------------------------------------------
input clk; //50MHz
input rst_n; //复位信号,低有效
input mcu_cs_n; //MCU片选信号,低有效
input mcu_wr_n; //MCU写信号,低有效
input[3:0] mcu_addr; //MCU地址总线
input[7:0] mcu_db; //MCU数据总线
reg[3:0] mcu_addr_r; //mcu_addr锁存寄存器
reg[7:0] mcu_db_r; // mcu_db锁存寄存器
//////mcu_cs_n和mcu_wr_n同时拉低时wr_state拉低,表示片选并写选通
wire wr_state = mcu_cs_n || mcu_wr_n; //写状态标志位,写选通时拉底
always @ (posedge clk or negedge rst_n)
if(!rst_n) begin
mcu_addr_r <= 4’h0;
mcu_db_r <= 8’h00;
end
else if(!wr_state) begin
mcu_addr_r <= mcu_addr;// mcu_addr锁存寄存器
mcu_db_r <= mcu_db;// mcu_db锁存寄存器
end
wire pos_wr; // MCU写状态上升沿标志位
reg wr1,wr2; // MCU写状态寄存器
always @ (posedge clk or negedge rst_n)
if(!rst_n) begin
wr1 <= 1'b1;
wr2 <= 1'b1;
end
else begin
wr1 <= wr_state;
wr2 <= wr1;
end
assign pos_wr = ~wr2 && wr1; //写选通信号上升沿pos_wr拉高一个时钟周期
上面的代码就是基于MCU发出的异步时序的一种同步处理。当然了,这种处理是基于特定的应用。MCU写选通撤销时,pos_wr信号(使用了脉冲边沿检测方法处理)会拉高一个时钟周期,就可以利用此信号作为后续处理的状态机中的一个指示信号。然后对已经锁存在FPGA内部相应寄存器里的地址总线和数据总线进行处理。
另外,对于mcu_addr_r和mcu_db_r的锁存为什么要在wr_state为低时进行,这个问题特权同学是这么考虑的:wr_state拉低期间即MCU片选和写选通同时有效期间数据总线/地址总线一定是稳定的,而为了有更充足的数据建立时间,比较常见的做法是用mcu_wr_n的上升沿锁存数据,而如果用诸如posedge mcu_wr_n来做触发锁存数据/地址,那就很容易出现异步冲突的问题(这个问题的危害以后的文章详细讨论),达不到同步的效果,所以这里就用一个电平信号作为使能信号来得更加稳妥。换个角度看,无非是wr_state上升沿的前0-20ns都有可能是最后锁存下来的数据,这对于我们充足的MCU写时序来说是绰绰有余了。理论上来说,wr_stata是一个总线使能信号,应该要做至少一级同步再使用更稳妥一些,但是出于我们充裕的时序,即便是wr_stata没有进行同步处理,退一步说,出现了wr_state的一个亚稳态时在锁存数据,那么此时的数据总线/地址总线的数据也不会受到影响,该什么值还是什么值。不同的应用中往往有允许非常规处理的时候,就像时序分析中的时序例外一样。希望大家能理解这个部分,不理解也没有关系,以后的文章会更深入探讨异步时钟域中亚稳态这个大问题,到时再回头看看也许你就明白了。