下面只是我自己的一些想法,对于时钟同步的问题里面没有考虑,只是简单的测试一下而已,应付急需。。。。在我第一次写这篇文章的时候,iIove314就提出这个问题,我也看了一些rippe和特权的一些博客文章,对于异步时钟的问题真的还是挺棘手的。。。不过还是放出来,大家指点一下比较好,后续会对异步时钟的处理上花点时间,在拿出来。。。
单片机与FPGA的通信可以使用总线否认方式来搜寻,但是实际上很多情况下由于接口的限制不能够使用总线,另一另外的角度说,其实FPGA和单片机的通信方式有很多,只要能够稳定的传输就可以了,串口也行,并口也行,总线也行,但是不同的情况需要具体对待,下面是一些常用的情况
1.
仿8086时序(用的最多的一种方式)
(1)一般情况下的使用
单片机和FPGA之间有N位数据线、M位地址线、时钟SCLK、读写选择RW、使能CS,不管是读和写数据,总是先把地址放在接口上,在时钟的上升沿将数据输入或者让读出;RW高电平读数据,低电平写数据;CS线低电平有效,也可以作为内部寄存器更新数据线。一般情况下读和写是放在2个文件里面的,而且根据不同的情况需要在做不同的调整
-----------------------------------------------------------------------------------------------------------
写数据的参考文件:WriteData
_1.v
module
WriteData(MCU_CtrlData,MCU_CtrlAddr,MCU_clk,MCU_rw,
trigger_way,
trigger_trigger,
MngClk_fifowrclk,
ReadData_select,
FIFO_aclr,
FRE_b_e,
AC_DC,
D10_D1);
input
MCU_clk;
input
MCU_rw;
input
[3:0]MCU_CtrlData;
input
[3:0]MCU_CtrlAddr;
output [3:0]trigger_way;
output [3:0]MngClk_fifowrclk;
output [3:0]ReadData_select;
output
trigger_trigger;
output
[1:0]FRE_b_e;
output
FIFO_aclr;
output AC_DC;
output D10_D1;
reg
[3:0]trigger_way;
reg
trigger_trigger;
reg
[3:0]MngClk_fifowrclk;
reg
[3:0]ReadData_select;
reg FIFO_aclr;
reg [1:0]FRE_b_e;
reg AC_DC;
reg D10_D1;
//////////////////////////
//地址定义
parameter [3:0]param_trigger_way = 0;
parameter [3:0]param_trigger_trigger = 1;
parameter
[3:0]param_MngClk_fifowrclk = 2;
parameter
[3:0]param_ReadData_select = 3;
parameter
[3:0]param_FIFO_aclr = 4;
parameter
[3:0]param_FRE_b_e = 5;
parameter
[3:0]param_AC_DC = 6;
parameter
[3:0]param_D10_D1 = 7;
/////////////////////////
always @ (posedge
MCU_clk)
begin
if(!MCU_rw)
begin
case(MCU_CtrlAddr)
param_trigger_way :
trigger_way <=
MCU_CtrlData;
param_trigger_trigger :
trigger_trigger <=
MCU_CtrlData[0];
param_MngClk_fifowrclk :
MngClk_fifowrclk <= MCU_CtrlData;
param_ReadData_select :
ReadData_select <= MCU_CtrlData;
param_FIFO_aclr :
FIFO_aclr <= MCU_CtrlData[0];
param_FRE_b_e :
FRE_b_e <= MCU_CtrlData[1:0];
param_AC_DC :
AC_DC <= MCU_CtrlData[0];
param_D10_D1 :
D10_D1 <= MCU_CtrlData[0];
endcase
end
end
endmodule
下面是一个读数据的例子:可以参考文件ReadData_1l.v
/***************************************
读数据
cnt_std为外部提供的32位数据
addr
地址线
8位
data
数据线 8位
sclk
时钟线
在时钟的上升沿将对应地址的数据读出
****************************************/
module parial2serial(cnt_std,addr,data,sclk);
input [31:0]cnt_std;
input [7:0]addr;
output [7:0]data;
input sclk;
reg [7:0]data;
always @ (posedge sclk)
begin
case(addr)
8'b00000000:
data <= cnt_std[7:0];
8'b00000001:
data <= cnt_std[15:8];
8'b00000010:
data <= cnt_std[23:16];
8'b00000011:
data <= cnt_std[31:24];
default:
data <= cnt_std[7:0];
endcase
end
endmodule
-----------------------------------------------------------------------------------------------------
上面的例子中红色的部分是必须的,和上面说的一样,RW决定读写,SCLK上升沿读入或者写入数据到对应的地址;黑色的文字例如写模块中trigger_way,、trigger_trigger,、
MngClk_fifowrclk,是其他模块的控制字,数据写入后就会改变其他模块的控制字,从而控制过程;读模块中cnt_std,是要读出的数据,这里的数据是一个32位的控制字,你不能能使用32位的数据线,所以你要分几次把数据读出,具体你在使用过程中根据具体情况来使用,传输过来的数据可能是一个RAM,可能是一个很大的数组,总之对应的地址对应着该有的数据就可以了。
上面的读和写模块虽然在2和文件中,但是在顶层文件中,这些数据线、地址线、时钟、RW是接在一起的;
上面的例子中写模块的数据线和地址线都是4位,在读模块中都是8位,其实数据和地址线具体多少可以具体情况再去改变,可能位数不相同。一个不很好的例子就是RAM的读写,RAM的读写,数据线是8位,地址线就不只8位了,需要根据你RAM的深度来决定
例如你的RAM是4096字节,地址线就有12条,只要在时钟上升沿正确设置RW以及addr,就可以读出或者让写入数据
RAM写数据时序
RAM读数据时序
(2)不使用地址线的情况
如果在管脚限制的情况下,尤其是在读写RAM的时候,内部开辟的空间太大的话,地址线占用很多的IO,这时候地址线可以采取内部产生或者分时输入的方式,对于分时输入的方法还没有测试过,倒是内部产生地址的方式用过,最好的例子是FIFO,不过FIFO经常使用的是内部的IP。很少自己去写,FIFO需要数据线,时钟,使能就可以了。就像5月份的示波表那样做。但是有时候用不到FIFO,地址自己产生就是用SCLK的边沿驱动计数器来产生地址。下面是一些例子(在数据很少的时候可以这样使用):
/*****************************************************************************
作者:深夜孤灯
整理时间:09-7-24-4-24
描述:将并行数据转换为串行数据输出
接口:
clk_tra 上升沿将输入数据转换为寄存器数据
ParDataIn 输入数据
SerDataOut 输出数据 8位
clk 输出数据时钟上升沿有效
在读取频率之前需要将srw拉低SCLK下降沿来初始化里面的地址计数器为0,在sclK的下降沿地址计数器增加 在时钟的上升沿 将对应地址的数据输出
*/
module
parial2serial(ParDataIn,SerDataOut,sclk,srw);
input [63:0]ParDataIn;
input sclk;
input srw;
output
[7:0]SerDataOut;
reg
[7:0]SerDataOut;
reg [3:0]AddrCnt;
always @ (negedge
sclk)
begin
if(!srw)
AddrCnt
= 4'b0000;
else if(AddrCnt == 7)
AddrCnt =
4'b0000;
else
AddrCnt =
AddrCnt + 4'b0001;
end
always @ (posedge
sclk)
begin
if(srw)
begin
case(AddrCnt)
4'b0000:
SerDataOut <= ParDataIn[7:0];
4'b0001:
SerDataOut <= ParDataIn[15:8];
4'b0010:
SerDataOut <= ParDataIn[23:16];
4'b0011:
SerDataOut <= ParDataIn[31:24];
4'b0100:
SerDataOut <= ParDataIn[39:32];
4'b0101:
SerDataOut <= ParDataIn[47:40];
4'b0110:
SerDataOut <= ParDataIn[55:48];
4'b0111:
SerDataOut <= ParDataIn[63:56];
default:
SerDataOut <= 8'b00000000;
endcase
end
end
endmodule
这个例子在频率测量的时候测试过,在要求不高的时候可以使用。输出的数据可能不是这样的,但是原理一样的
下面是一个带有UPDATA信号的写数据的例子:
-----------------------------------------------------------------------------------
/***********************************************
作者:深夜孤灯
整理时间;09-7-24-4-51
描述: 通过内部产生地址来写入数据到寄存器,通过lock_up来把数据锁存到相应的模块寄存器中、
接口说明:
sclk 时钟
cs 使能,低电平有效
data 数据传输
lock_up 上升沿更新数据到寄存器
*************************************************/
module
parral_communicate(sclk,cs,data,lock_up,
frequence_word,
wave_select,
clk_select,
compare_word
);
input sclk;
input cs;
input [7:0]data;
input lock_up;
output [31:0]frequence_word;
output [2:0] wave_select;
output [3:0] clk_select;
output [7:0] compare_word;
reg [31:0]frequence_word;
reg [2:0] wave_select;
reg [3:0] clk_select;
reg [7:0] compare_word;
reg [7:0]temp_reg[5:0];
reg [3:0]counter;
//ganerate regster addres
always@(negedge wr or
posedge cs)
begin
if(cs)
counter<=0;
else if(!cs)
counter<=counter+1;
end
//write data to regster
always@(posedge sclk)
begin
if(!cs)
temp_reg[counter]<=data;
end
//sent data to inter regster,and
come into effect with the rising edge of lock_up
always@(posedge
lock_up)
begin
frequence_word<={temp_reg[3],temp_reg[2],temp_reg[1],temp_reg[0]};
wave_select<=temp_reg[4][2:0];
clk_select <=temp_reg[4][7:4];
compare_word<=temp_reg[5];
end
endmodule
当然你也可以不使用UP_data信号直接跟新数据,不过该文件使用在DDS信号发生器里面,需要32位的频率字,但是不肯可能一次写入32位数据,所以这个时候还是有用的;这种方式有种弊端就似乎每次你必须把所有地方都设置一次,因为地址的关系,不过你也可以通过SCLK的空操作来找到相应的地址。。。。。
上面列举其实就是一位寄存器的并入串出。只不过载入数据的方式不一样而已。
对于比较大的RAM,数据线可以通过双向复用来读取数据和设置寄存器,读取ram的地址可以通过内部来产生,控制的地址不会太大可以通过用的地址线或者也用内部的地址产生
(3)IO的分时复用
FPGA可以使用双向口,这样的话,数据线可以设置为双向口,读数据和寄存器的设置分开进行。
(4)有状态信号
状态信号可以通过单独的管脚来输出,但是经常有管脚不够的情况,或许使用总线会比较合适,如果你已经在使用总线的方式在传输数据或者有输出数据的IO,
(5)内部有多个数据模块的情况
zhangshaobing517_935512703 2009-7-27 23:07
用户536736 2009-7-27 09:30