`timescale 1ns/1ns

/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
					   parameter WIDTH = 8)(
	 input wclk
	,input wenc
	,input [$clog2(DEPTH)-1:0] waddr  //深度对2取对数,得到地址的位宽。
	,input [WIDTH-1:0] wdata      	//数据写入
	,input rclk
	,input renc
	,input [$clog2(DEPTH)-1:0] raddr  //深度对2取对数,得到地址的位宽。
	,output reg [WIDTH-1:0] rdata 		//数据输出
);

reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];

always @(posedge wclk) begin
	if(wenc)
		RAM_MEM[waddr] <= wdata;
end 

always @(posedge rclk) begin
	if(renc)
		rdata <= RAM_MEM[raddr];
end 

endmodule  

/***************************************AFIFO*****************************************/
module asyn_fifo#(
	parameter	WIDTH = 8,
	parameter 	DEPTH = 16
)(
	input 					wclk	, 
	input 					rclk	,   
	input 					wrstn	,
	input					rrstn	,
	input 					winc	,
	input 			 		rinc	,
	input 		[WIDTH-1:0]	wdata	,

	output wire				wfull	,
	output wire				rempty	,
	output wire [WIDTH-1:0]	rdata
);
    //格雷码必须现在本地打一拍,然后再经过异域时钟大两拍,一般我们不在本地打拍也是可以的的,本例题时序是这样,另外最后空满判定也需要本地时钟打拍后的格雷码与异域时钟的进行判断!

//=========================================================================\\
//*****************************define sinals*******************************\\
//=========================================================================\\
//读写指针
reg   [$clog2(DEPTH):0]               wr_ptr         ;   //写指针
wire  [$clog2(DEPTH)-1:0]     wr_ptr_true    ;   //写指针-真实地址

reg   [$clog2(DEPTH):0]               rd_ptr         ;   //读指针
wire  [$clog2(DEPTH)-1:0]     rd_ptr_true    ;   //读指针-真实地址

//格雷码读写指针
wire   [$clog2(DEPTH):0]               wr_ptr_g      ;   //格雷码写指针
wire   [$clog2(DEPTH):0]               rd_ptr_g      ;   //格雷码写指针

//本时钟域打拍的格雷码读写时钟
reg   [$clog2(DEPTH):0]               wr_ptr_g_b;   //本地打拍的格雷码写指针
reg   [$clog2(DEPTH):0]               rd_ptr_g_b;   //本地打拍的格雷码读指针

//跨时钟域打拍的格雷码读写时钟
reg   [$clog2(DEPTH):0]               wr_ptr_g_d1; 
reg   [$clog2(DEPTH):0]               wr_ptr_g_d2;  

reg   [$clog2(DEPTH):0]               rd_ptr_g_d1;  
reg   [$clog2(DEPTH):0]               rd_ptr_g_d2; 

//=========================================================================\\
//*****************************main code***********************************\\
//=========================================================================\\

//二进制转格雷码
assign  wr_ptr_g =(wr_ptr >> 1)^ wr_ptr ;
assign  rd_ptr_g =(rd_ptr >> 1)^ rd_ptr ;

//真实指针
assign wr_ptr_true = wr_ptr[$clog2(DEPTH)-1:0];
assign rd_ptr_true = rd_ptr[$clog2(DEPTH)-1:0];


//写操作
always@(posedge wclk or negedge wrstn)begin
    if(!wrstn)
        wr_ptr <= 'd0           ;
    else if(!wfull && winc)
        wr_ptr <= wr_ptr + 1'b1 ; 
end

//格雷码写指针本域打拍
always@(posedge wclk or negedge wrstn)begin
    if(!wrstn)
        wr_ptr_g_b <= 'd0           ;
    else 
        wr_ptr_g_b <= wr_ptr_g      ;
end

//格雷码写指针同步到读时钟域
always@(posedge rclk or negedge rrstn)begin
    if(!rrstn)begin
        wr_ptr_g_d1 <= 'd0           ;
        wr_ptr_g_d2 <= 'd0           ;
    end
    else begin
        wr_ptr_g_d1 <= wr_ptr_g_b    ;
        wr_ptr_g_d2 <= wr_ptr_g_d1   ;
    end
end


//读操作
always@(posedge rclk or negedge rrstn)begin
    if(!rrstn)
        rd_ptr <= 'd0           ;
    else if(!rempty && rinc)
        rd_ptr <= rd_ptr + 1'b1 ; 
end

//格雷码读指针本域打拍
always@(posedge rclk or negedge rrstn)begin
    if(!rrstn)
        rd_ptr_g_b <= 'd0           ;
    else 
        rd_ptr_g_b <= rd_ptr_g      ;
end

//格雷码读指针同步到写时钟域
always@(posedge wclk or negedge wrstn)begin
    if(!wrstn)begin
        rd_ptr_g_d1 <= 'd0           ;
        rd_ptr_g_d2 <= 'd0           ;
    end
    else begin
        rd_ptr_g_d1 <= rd_ptr_g_b    ;
        rd_ptr_g_d2 <= rd_ptr_g_d1   ;
    end
end

//写满判断  本地打拍的格雷码写指针 与异域打拍的格雷码读指针最高位和次高位相反 其余为相同
assign  wfull =  (wr_ptr_g_b =={~rd_ptr_g_d2[$clog2(DEPTH):$clog2(DEPTH)-1],rd_ptr_g_d2[$clog2(DEPTH)-2:0]})?1'b1:1'b0 ;

//读空判断   本地打拍的格雷码读指针 与异域打拍的格雷码写指针相等
assign  rempty = (rd_ptr_g_b == wr_ptr_g_d2)?1'b1:1'b0 ;

dual_port_RAM 
#(
	. DEPTH (DEPTH ) ,
    . WIDTH (WIDTH )
)
dual_port_RAM_inst
(
	. wclk  (wclk             ),   
	. wenc  (!wfull && winc   ),   
	. waddr (wr_ptr_true      ),          //深度对2取对数,得到地址的位宽。
	. wdata (wdata            ),       	//数据写入
	. rclk  (rclk             ),   
	. renc  (!rempty && rinc  ),   
	. raddr (rd_ptr_true      ),   //深度对2取对数,得到地址的位宽。
	. rdata (rdata            )		//数据输出
);

endmodule