由于本IP核定制选择了:因此,程序加入了一些debug的IP核例化。
如下:
而对于我们要仿真而言,这些都是没有必要的。
对于我们用户应用来说,最最重要的模块为:
一个check,一个gen。
gen代表发数据的模块,而check则为收数据的模块。
如数据手册:而如何对二者进行仿真呢?
形成一个回环,示意图如下:一方发,另一方收,反之亦然!
而testbench的作用就是将二者联系起来:
下图清晰说明:例子程序中带有仿真文件:
可见,例化了两次例子程序,就是为了将一方的tx送给另一方的rx,同时,另一方的tx送入一方的rx,形成一个闭环。
我们知道收模块与aurora IP streaming用户接口之间的关系是:
因此,用户接口很简单,就两个信号进来就好,m_axi_rx_data以及m_axi_rx_valid即可,valid有效,则data数据为有效数据,至于收到的数据做什么处理,随你!例子程序的处理,我也不想深究,收过来按照自己的需求搞就完事了。
给出源码:
`timescale 1 ns / 1 ps
`define DLY #1
module aurora_8b10b_streaming_FRAME_CHECK
(
// User Interface
RX_D,
RX_SRC_RDY_N,
// System Interface
USER_CLK,
RESET,
CHANNEL_UP,
ERR_COUNT
);
//***********************************Port Declarations*******************************
// User Interface
input [0:15] RX_D;
input RX_SRC_RDY_N;
// System Interface
input USER_CLK;
input RESET;
input CHANNEL_UP;
output [0:7] ERR_COUNT;
//***************************Internal Register Declarations***************************
// Slack registers
reg [0:15] RX_D_SLACK;
reg RX_SRC_RDY_N_SLACK;
reg [0:8] err_count_r = 9'd0;
// RX Data registers
reg [0:15] data_lfsr_r;
//*********************************Wire Declarations**********************************
wire reset_c;
wire [0:15] data_lfsr_concat_w;
wire data_valid_c;
wire data_err_detected_c;
reg data_err_detected_r;
//*********************************Main Body of Code**********************************
//Generate RESET signal when Aurora channel is not ready
assign reset_c = RESET;
// SLACK registers
always @ (posedge USER_CLK)
begin
RX_D_SLACK <= `DLY RX_D;
RX_SRC_RDY_N_SLACK <= `DLY RX_SRC_RDY_N;
end
//______________________________ Capture incoming data ___________________________
//Data is valid when RX_SRC_RDY_N is asserted
assign data_valid_c = !RX_SRC_RDY_N_SLACK;
//generate expected RX_D using LFSR
always @(posedge USER_CLK)
if(reset_c)
begin
data_lfsr_r <= `DLY 16'hD5E6; //random seed value
end
else if(CHANNEL_UP)
begin
if(data_valid_c)
data_lfsr_r <= `DLY {!{data_lfsr_r[3]^data_lfsr_r[12]^data_lfsr_r[14]^data_lfsr_r[15]},
data_lfsr_r[0:14]};
end
else
begin
data_lfsr_r <= `DLY 16'hD5E6; //random seed value
end
assign data_lfsr_concat_w = {1{data_lfsr_r}};
//___________________________ Check incoming data for errors __________________________
//An error is detected when LFSR generated RX data from the data_lfsr_concat_w register,
//does not match valid data from the RX_D port
assign data_err_detected_c = (data_valid_c && (RX_D_SLACK != data_lfsr_concat_w));
//We register the data_err_detected_c signal for use with the error counter logic
always @(posedge USER_CLK)
data_err_detected_r <= `DLY data_err_detected_c;
//Compare the incoming data with calculated expected data.
//Increment the ERROR COUNTER if mismatch occurs.
//Stop the ERROR COUNTER once it reaches its max value (i.e. 255)
always @(posedge USER_CLK)
if(CHANNEL_UP)
begin
if(&err_count_r)
err_count_r <= `DLY err_count_r;
else if(data_err_detected_r)
err_count_r <= `DLY err_count_r + 1;
end
else
begin
err_count_r <= `DLY 9'd0;
end
//Here we connect the lower 8 bits of the count (the MSbit is used only to check when the counter reaches
//max value) to the module output
assign ERR_COUNT = err_count_r[1:8];
endmodule
仿真预告:
对了,程序中的这个输入变量:
RX_SRC_RDY_N
就是valid的反而已:
//______________________________ Capture incoming data ___________________________
//Data is valid when RX_SRC_RDY_N is asserted
assign data_valid_c = !RX_SRC_RDY_N_SLACK;
其他的不言而喻!
发要比收需要的信号多一个,那就是ready信号,具体为:
// User Interface
output [0:15] TX_D;
output TX_SRC_RDY_N;
input TX_DST_RDY_N;
根据streaming格式的关系:我们用户逻辑需要得到一个ready有效信号,然后置位valid的同时,发送有效数据data。
例子发送程序根据lsfr产生随机数据发送:
//______________________________ Transmit Data __________________________________
//Transmit data when TX_DST_RDY_N is asserted.
//Random data is generated using XNOR feedback LFSR
//TX_SRC_RDY_N is asserted on every cycle with data
always @(posedge USER_CLK)
if(reset_c)
begin
data_lfsr_r <= `DLY 16'hABCD; //random seed value
TX_SRC_RDY_N <= `DLY 1'b1;
end
else if(!TX_DST_RDY_N)
begin
data_lfsr_r <= `DLY {!{data_lfsr_r[3]^data_lfsr_r[12]^data_lfsr_r[14]^data_lfsr_r[15]},
data_lfsr_r[0:14]};
TX_SRC_RDY_N <= `DLY 1'b0;
end
//Connect TX_D to the DATA LFSR register
assign TX_D = {1{data_lfsr_r}};
不需要多言!
下面给出完整发送程序:
`timescale 1 ns / 1 ps
`define DLY #1
module aurora_8b10b_streaming_FRAME_GEN
(
// User Interface
TX_D,
TX_SRC_RDY_N,
TX_DST_RDY_N,
// System Interface
USER_CLK,
RESET,
CHANNEL_UP
);
//*****************************Parameter Declarations****************************
//***********************************Port Declarations*******************************
// User Interface
output [0:15] TX_D;
output TX_SRC_RDY_N;
input TX_DST_RDY_N;
// System Interface
input USER_CLK;
input RESET;
input CHANNEL_UP;
//***************************External Register Declarations***************************
reg TX_SRC_RDY_N;
//***************************Internal Register Declarations***************************
reg [0:15] data_lfsr_r;
wire reset_c;
wire dly_data_xfer;
reg [4:0] channel_up_cnt;
//*********************************Main Body of Code**********************************
always @ (posedge USER_CLK)
begin
if(RESET)
channel_up_cnt <= `DLY 5'd0;
else if(CHANNEL_UP)
if(&channel_up_cnt)
channel_up_cnt <= `DLY channel_up_cnt;
else
channel_up_cnt <= `DLY channel_up_cnt + 1'b1;
else
channel_up_cnt <= `DLY 5'd0;
end
assign dly_data_xfer = (&channel_up_cnt);
//Generate RESET signal when Aurora channel is not ready
assign reset_c = RESET || !dly_data_xfer;
//______________________________ Transmit Data __________________________________
//Transmit data when TX_DST_RDY_N is asserted.
//Random data is generated using XNOR feedback LFSR
//TX_SRC_RDY_N is asserted on every cycle with data
always @(posedge USER_CLK)
if(reset_c)
begin
data_lfsr_r <= `DLY 16'hABCD; //random seed value
TX_SRC_RDY_N <= `DLY 1'b1;
end
else if(!TX_DST_RDY_N)
begin
data_lfsr_r <= `DLY {!{data_lfsr_r[3]^data_lfsr_r[12]^data_lfsr_r[14]^data_lfsr_r[15]},
data_lfsr_r[0:14]};
TX_SRC_RDY_N <= `DLY 1'b0;
end
//Connect TX_D to the DATA LFSR register
assign TX_D = {1{data_lfsr_r}};
endmodule
仿真预警:
先给出仿真文件(然后在简单分析仿真文件):
`timescale 1 ns / 1 ps
module aurora_8b10b_streaming_TB;
//*************************Parameter Declarations**************************
parameter SIM_MAX_TIME = 9500000; //To quit the simulation
//125.0MHz GT Reference clock
parameter CLOCKPERIOD_1 = 8.0 ;
parameter CLOCKPERIOD_2 = 8.0 ;
//parameter CLOCKPERIOD_1 = 8.0;
//parameter CLOCKPERIOD_2 = 8.0;
parameter DRP_CLOCKPERIOD = 20.000 ; //GT DRP Clock
parameter INIT_CLOCKPERIOD = 20.0 ; // Board/System Clock
//************************Internal Register Declarations*****************************
//Freerunning Clock
reg reference_clk_1_n_r;
reg reference_clk_2_n_r;
reg drp_clk_r;
reg init_clk_p;
//Global signals
reg gt_reset_in;
reg gsr_r;
reg gts_r;
reg reset_i;
//********************************Wire Declarations**********************************
//Freerunning Clock
wire reference_clk_1_p_r;
wire reference_clk_2_p_r;
wire init_clk_n;
//Dut1
//Error Detection Interface
wire hard_err_1_i;
wire soft_err_1_i;
//Status
wire channel_up_1_i;
wire lane_up_1_i;
//GT Serial I/O
wire rxp_1_i;
wire rxn_1_i;
wire txp_1_i;
wire txn_1_i;
// Error signals from the Local Link packet checker
wire [0:7] err_count_1_i;
//Dut2
//Error Detection Interface
wire hard_err_2_i;
wire soft_err_2_i;
//Status
wire channel_up_2_i;
wire lane_up_2_i;
//GT Serial I/O
wire rxp_2_i;
wire rxn_2_i;
wire txp_2_i;
wire txn_2_i;
// Error signals from the Local Link packet checker
wire [0:7] err_count_2_i;
//*********************************Main Body of Code**********************************
//_________________________Serial Connections________________
assign rxn_1_i = txn_2_i;
assign rxp_1_i = txp_2_i;
assign rxn_2_i = txn_1_i;
assign rxp_2_i = txp_1_i;
//__________________________Global Signals_____________________________
//Simultate the global reset that occurs after configuration at the beginning
//of the simulation. Note that both GT smart models use the same global signals.
assign glbl.GSR = gsr_r;
assign glbl.GTS = gts_r;
initial
begin
gts_r = 1'b0;
gsr_r = 1'b1;
gt_reset_in = 1'b1;
#5000;
gsr_r = 1'b0;
gt_reset_in = 1'b0;
repeat(10) @(posedge init_clk_p);
gt_reset_in = 1'b1;
repeat(10) @(posedge init_clk_p);
gt_reset_in = 1'b0;
end
//____________________________Clocks____________________________
initial
reference_clk_1_n_r = 1'b0;
always
#(CLOCKPERIOD_1 / 2) reference_clk_1_n_r = !reference_clk_1_n_r;
assign reference_clk_1_p_r = !reference_clk_1_n_r;
initial
reference_clk_2_n_r = 1'b0;
always
#(CLOCKPERIOD_2 / 2) reference_clk_2_n_r = !reference_clk_2_n_r;
assign reference_clk_2_p_r = !reference_clk_2_n_r;
initial
drp_clk_r = 1'b0;
always
#(DRP_CLOCKPERIOD / 2) drp_clk_r = !drp_clk_r;
initial
init_clk_p = 1'b0;
always
#(INIT_CLOCKPERIOD / 2) init_clk_p = !init_clk_p;
assign init_clk_n = !init_clk_p;
//____________________________Resets____________________________
initial
begin
reset_i = 1'b1;
#1000 reset_i = 1'b0;
end
//________________________Instantiate Dut 1 ________________
aurora_8b10b_streaming_exdes example_design_1_i
(
// User IO
.RESET(reset_i),
// Error signals from Aurora
.HARD_ERR(hard_err_1_i),
.SOFT_ERR(soft_err_1_i),
// Status Signals
.LANE_UP(lane_up_1_i),
.CHANNEL_UP(channel_up_1_i),
.INIT_CLK_P(init_clk_p),
.INIT_CLK_N(init_clk_n),
.DRP_CLK_IN(drp_clk_r),
.GT_RESET_IN(gt_reset_in),
// Clock Signals
.GTXQ0_P(reference_clk_1_p_r),
.GTXQ0_N(reference_clk_1_n_r),
// GT I/O
.RXP(rxp_1_i),
.RXN(rxn_1_i),
.TXP(txp_1_i),
.TXN(txn_1_i),
// Error signals from the Local Link packet checker
.ERR_COUNT(err_count_1_i)
);
//________________________Instantiate Dut 2 ________________
aurora_8b10b_streaming_exdes example_design_2_i
(
// User IO
.RESET(reset_i),
// Error signals from Aurora
.HARD_ERR(hard_err_2_i),
.SOFT_ERR(soft_err_2_i),
// Status Signals
.LANE_UP(lane_up_2_i),
.CHANNEL_UP(channel_up_2_i),
.INIT_CLK_P(init_clk_p),
.INIT_CLK_N(init_clk_n),
.DRP_CLK_IN(drp_clk_r),
.GT_RESET_IN(gt_reset_in),
// Clock Signals
.GTXQ0_P(reference_clk_2_p_r),
.GTXQ0_N(reference_clk_2_n_r),
// GT I/O
.RXP(rxp_2_i),
.RXN(rxn_2_i),
.TXP(txp_2_i),
.TXN(txn_2_i),
// Error signals from the Local Link packet checker
.ERR_COUNT(err_count_2_i)
);
分为几个部分,
//GT Serial I/O
wire rxp_1_i;
wire rxn_1_i;
wire txp_1_i;
wire txn_1_i;
但也不尽然如此,例如输入的差分时钟,我们就可以将其中一个声明为reg,至于差分的另一半,声明为wire,之后通过取反操作来实现:
//Freerunning Clock
reg reference_clk_1_n_r;
reg reference_clk_2_n_r;
reg drp_clk_r;
reg init_clk_p;
//********************************Wire Declarations**********************************
//Freerunning Clock
wire reference_clk_1_p_r;
wire reference_clk_2_p_r;
wire init_clk_n;
assign reference_clk_1_p_r = !reference_clk_1_n_r;
assign reference_clk_2_p_r = !reference_clk_2_n_r;
assign init_clk_n = !init_clk_p;
//____________________________Clocks____________________________
initial
reference_clk_1_n_r = 1'b0;
always
#(CLOCKPERIOD_1 / 2) reference_clk_1_n_r = !reference_clk_1_n_r;
assign reference_clk_1_p_r = !reference_clk_1_n_r;
initial
reference_clk_2_n_r = 1'b0;
always
#(CLOCKPERIOD_2 / 2) reference_clk_2_n_r = !reference_clk_2_n_r;
assign reference_clk_2_p_r = !reference_clk_2_n_r;
initial
drp_clk_r = 1'b0;
always
#(DRP_CLOCKPERIOD / 2) drp_clk_r = !drp_clk_r;
initial
init_clk_p = 1'b0;
always
#(INIT_CLOCKPERIOD / 2) init_clk_p = !init_clk_p;
assign init_clk_n = !init_clk_p;
//____________________________Resets____________________________
initial
begin
reset_i = 1'b1;
#1000 reset_i = 1'b0;
end
//________________________Instantiate Dut 1 ________________
aurora_8b10b_streaming_exdes example_design_1_i
(
// User IO
.RESET(reset_i),
// Error signals from Aurora
.HARD_ERR(hard_err_1_i),
.SOFT_ERR(soft_err_1_i),
// Status Signals
.LANE_UP(lane_up_1_i),
.CHANNEL_UP(channel_up_1_i),
.INIT_CLK_P(init_clk_p),
.INIT_CLK_N(init_clk_n),
.DRP_CLK_IN(drp_clk_r),
.GT_RESET_IN(gt_reset_in),
// Clock Signals
.GTXQ0_P(reference_clk_1_p_r),
.GTXQ0_N(reference_clk_1_n_r),
// GT I/O
.RXP(rxp_1_i),
.RXN(rxn_1_i),
.TXP(txp_1_i),
.TXN(txn_1_i),
// Error signals from the Local Link packet checker
.ERR_COUNT(err_count_1_i)
);
//________________________Instantiate Dut 2 ________________
aurora_8b10b_streaming_exdes example_design_2_i
(
// User IO
.RESET(reset_i),
// Error signals from Aurora
.HARD_ERR(hard_err_2_i),
.SOFT_ERR(soft_err_2_i),
// Status Signals
.LANE_UP(lane_up_2_i),
.CHANNEL_UP(channel_up_2_i),
.INIT_CLK_P(init_clk_p),
.INIT_CLK_N(init_clk_n),
.DRP_CLK_IN(drp_clk_r),
.GT_RESET_IN(gt_reset_in),
// Clock Signals
.GTXQ0_P(reference_clk_2_p_r),
.GTXQ0_N(reference_clk_2_n_r),
// GT I/O
.RXP(rxp_2_i),
.RXN(rxn_2_i),
.TXP(txp_2_i),
.TXN(txn_2_i),
// Error signals from the Local Link packet checker
.ERR_COUNT(err_count_2_i)
);
双方如何形成一个通路的呢?
1发接到2的收,2发接到1的收:
//_________________________Serial Connections________________
assign rxn_1_i = txn_2_i;
assign rxp_1_i = txp_2_i;
assign rxn_2_i = txn_1_i;
assign rxp_2_i = txp_1_i;
由于数据是自己的gen模块以及产生的,故在设计文件中设计即可。
下面仿真实践,看看仿真图吧!
先宏观地看第一个仿真图:
可见,一方(简称partner1)和另一方(简称partner2)串行数据完全一致,理所当然如此,因为二者是直接相连的回环。
当然,这种串行数据我们是看不懂的,我们要看的是用户逻辑,模块check以及gen的数据,继续把二者拉出来仿真:
从partner1的gen中提取出valid,data,ready信号;
再从partner2的check中提取出valid和data信号。
二者构成一个通路。
上图中,TX_D就是s_axi_tx_data,而TX_DST_RDY_N取反就是s_axi_tx_tready,同理,TX_SRC_RDY_N取反是s_axi_tx_valid,故而,当TX_SRC_RDY_N和TX_DST_RDY_N都有效的时候,代表发送的数据TX_D为有效数据。
这一点从代码的端口定义可见等价关系:
同理,接收情况也是如此!
按照streaming用户接口的时序图:
当valid有效的时候,接收的数据才有效,因此,我们查看接收数据时刻应该在:
我们通过放大,来看看,这两个时刻上的数据是否一致?
可见,都是d5e6,而且后续数据也一致,可见,发送与接收的通道是没有问题的,至少从仿真上看是没有问题的。(这里提醒一句,这里对齐的时钟肯定是用户时钟,这里没有拉出来。)
从s_axi_tx_valid到s_axi_rx_valid有效的延迟时间,大家也可以算下,延迟了多久?(大概40多个user_clk 时钟周期了)