写在前面
具体实验code可通过以下链接下载:
链接:https://pan.baidu.com/s/1E731af2LgIZzyOzlYZi94Q?pwd=qkxu
提取码:qkxu
code:
`timescale 1ns/1ps
module tb1;
logic clk;
logic rstn;
logic [31:0] ch0_data;
logic ch0_valid;
logic ch0_ready;
logic [ 5:0] ch0_margin;
logic [31:0] ch1_data;
logic ch1_valid;
logic ch1_ready;
logic [ 5:0] ch1_margin;
logic [31:0] ch2_data;
logic ch2_valid;
logic ch2_ready;
logic [ 5:0] ch2_margin;
logic [31:0] mcdt_data;
logic mcdt_val;
logic [ 1:0] mcdt_id;
mcdt dut(
.clk_i(clk)
,.rstn_i(rstn)
,.ch0_data_i(ch0_data)
,.ch0_valid_i(ch0_valid)
,.ch0_ready_o(ch0_ready)
,.ch0_margin_o(ch0_margin)
,.ch1_data_i(ch1_data)
,.ch1_valid_i(ch1_valid)
,.ch1_ready_o(ch1_ready)
,.ch1_margin_o(ch1_margin)
,.ch2_data_i(ch2_data)
,.ch2_valid_i(ch2_valid)
,.ch2_ready_o(ch2_ready)
,.ch2_margin_o(ch2_margin)
,.mcdt_data_o(mcdt_data)
,.mcdt_val_o(mcdt_val)
,.mcdt_id_o(mcdt_id)
);
// clock generation
initial begin
clk <= 0;
forever begin
#5 clk <= !clk;
end
end
// reset trigger
initial begin
#10 rstn <= 0;
repeat(10) @(posedge clk);
rstn <= 1;
end
// data test
initial begin
@(posedge rstn);
repeat(5) @(posedge clk);
// channel 0 test
chnl_write(0, 'h00C0_0000);
chnl_write(0, 'h00C0_0001);
chnl_write(0, 'h00C0_0002);
chnl_write(0, 'h00C0_0003);
chnl_write(0, 'h00C0_0004);
chnl_write(0, 'h00C0_0005);
chnl_write(0, 'h00C0_0006);
chnl_write(0, 'h00C0_0007);
chnl_write(0, 'h00C0_0008);
chnl_write(0, 'h00C0_0009);
// channel 1 test
chnl_write(1, 'h00C1_0000);
chnl_write(1, 'h00C1_0001);
chnl_write(1, 'h00C1_0002);
chnl_write(1, 'h00C1_0003);
chnl_write(1, 'h00C1_0004);
chnl_write(1, 'h00C1_0005);
chnl_write(1, 'h00C1_0006);
chnl_write(1, 'h00C1_0007);
chnl_write(1, 'h00C1_0008);
chnl_write(1, 'h00C1_0009);
// channel 2 test
chnl_write(2, 'h00C2_0000);
chnl_write(2, 'h00C2_0001);
chnl_write(2, 'h00C2_0002);
chnl_write(2, 'h00C2_0003);
chnl_write(2, 'h00C2_0004);
chnl_write(2, 'h00C2_0005);
chnl_write(2, 'h00C2_0006);
chnl_write(2, 'h00C2_0007);
chnl_write(2, 'h00C2_0008);
chnl_write(2, 'h00C2_0009);
end
// channel write task
task chnl_write(input logic[1:0] id, input logic[31:0] data);
case(id)
0: begin
@(posedge clk);
ch0_valid <= 1;
ch0_data <= data;
@(posedge clk);
ch0_valid <= 0;
ch0_data <= 0;
end
1: begin
@(posedge clk);
ch1_valid <= 1;
ch1_data <= data;
@(posedge clk);
ch1_valid <= 0;
ch1_data <= 0;
end
2: begin
@(posedge clk);
ch2_valid <= 1;
ch2_data <= data;
@(posedge clk);
ch2_valid <= 0;
ch2_data <= 0;
end
default: $error("channel id %0d is invalid", id);
endcase
endtask
endmodule
方法task和函数function的练习
tb1之前产生时钟和发起复位的两个initial 过程块语句都被tb2两个task即clk_gen()和rstn_gen()取代了。 以下为对比代码:
数组的使用
使用tb1的赋值方法太过于繁琐,如果写入数据较少还可以接受,但是例如要写入100,200个较多的数据时,这样重复性的操作会使代码量增大,所以这部分赋值可以采用动态数组,需要写入多少数据就为其开辟相应的空间,并对开辟的空间进行赋值,最后通过chnl_write方法写入channel。
tb3波形:
验证结构
为了实现清晰的验证结构, 我们希望将DUT 和激励发生器(stimulator)之间划分。 因此, 我们可
以将激励方法chnl_write()封装在新的模块chnl_initiator中,如下:
可以发现之前的 initial语句块"channel write task"已经不见了, 在其位置上的变为了三个例化的chnl_initiator实例chnl0_init 、chnl1_init 和chnl2_init。 它们的作用扮演每个channel slave通道对应的 stimulator, 发送激励 , 因此我们在其模块chnl_initiator中定义了它的三个方法 , set_name() 、chnl_write()和chnl_idle()。
完整code:
`timescale 1ns/1ps
module chnl_initiator(
input clk,
input rstn,
output logic [31:0] ch_data,
output logic ch_valid,
input ch_ready,
input [ 5:0] ch_margin
);
string name;
function void set_name(string s);
name = s;
endfunction
task chnl_write(input logic[31:0] data);
// USER TODO
// drive valid data
// ...
@(posedge clk);
ch_valid <= 1;
ch_data <= data;
@(negedge clk);
wait(ch_ready === 'b1);
$display("%t channel initial [%s] sent data %x", $time, name, data);
chnl_idle();
endtask
task chnl_idle();
// USER TODO
// drive idle data
// ...
@(posedge clk);
ch_valid <= 0;
ch_data <= 0;
endtask
endmodule
module tb4_ref;
logic clk;
logic rstn;
logic [31:0] ch0_data;
logic ch0_valid;
logic ch0_ready;
logic [ 5:0] ch0_margin;
logic [31:0] ch1_data;
logic ch1_valid;
logic ch1_ready;
logic [ 5:0] ch1_margin;
logic [31:0] ch2_data;
logic ch2_valid;
logic ch2_ready;
logic [ 5:0] ch2_margin;
logic [31:0] mcdt_data;
logic mcdt_val;
logic [ 1:0] mcdt_id;
mcdt dut(
.clk_i(clk)
,.rstn_i(rstn)
,.ch0_data_i(ch0_data)
,.ch0_valid_i(ch0_valid)
,.ch0_ready_o(ch0_ready)
,.ch0_margin_o(ch0_margin)
,.ch1_data_i(ch1_data)
,.ch1_valid_i(ch1_valid)
,.ch1_ready_o(ch1_ready)
,.ch1_margin_o(ch1_margin)
,.ch2_data_i(ch2_data)
,.ch2_valid_i(ch2_valid)
,.ch2_ready_o(ch2_ready)
,.ch2_margin_o(ch2_margin)
,.mcdt_data_o(mcdt_data)
,.mcdt_val_o(mcdt_val)
,.mcdt_id_o(mcdt_id)
);
// clock generation
initial begin
clk <= 0;
forever begin
#5 clk <= !clk;
end
end
// reset trigger
initial begin
#10 rstn <= 0;
repeat(10) @(posedge clk);
rstn <= 1;
end
logic [31:0] chnl0_arr[];
logic [31:0] chnl1_arr[];
logic [31:0] chnl2_arr[];
// USER TODO
// generate 100 data for each dynamic array
initial begin
chnl0_arr = new[100];
chnl1_arr = new[100];
chnl2_arr = new[100];
foreach(chnl0_arr[i]) begin
chnl0_arr[i] = 'h00C0_00000 + i;
chnl1_arr[i] = 'h00C1_00000 + i;
chnl2_arr[i] = 'h00C2_00000 + i;
end
end
// USER TODO
// use the dynamic array, user would send all of data
// data test
initial begin
@(posedge rstn);
repeat(5) @(posedge clk);
// USER TODO
// Give unique names to each channel initiator
// ...
chnl0_init.set_name("chnl0_init");
chnl1_init.set_name("chnl0_init");
chnl2_init.set_name("chnl0_init");
// channel 0 test
// TODO use chnl0_arr to send all data
foreach(chnl0_arr[i]) chnl0_init.chnl_write(chnl0_arr[i]);
// channel 1 test
// TODO use chnl1_arr to send all data
foreach(chnl1_arr[i]) chnl1_init.chnl_write(chnl1_arr[i]);
// channel 2 test
// TODO use chnl2_arr to send all data
foreach(chnl2_arr[i]) chnl2_init.chnl_write(chnl2_arr[i]);
end
chnl_initiator chnl0_init(
.clk (clk),
.rstn (rstn),
.ch_data (ch0_data),
.ch_valid (ch0_valid),
.ch_ready (ch0_ready),
.ch_margin(ch0_margin)
);
chnl_initiator chnl1_init(
.clk (clk),
.rstn (rstn),
.ch_data (ch1_data),
.ch_valid (ch1_valid),
.ch_ready (ch1_ready),
.ch_margin(ch1_margin)
);
chnl_initiator chnl2_init(
.clk (clk),
.rstn (rstn),
.ch_data (ch2_data),
.ch_valid (ch2_valid),
.ch_ready (ch2_ready),
.ch_margin(ch2_margin)
);
endmodule
波形:
从仿真波形可以看出,chnl_ready信号一直是拉高的状态, 也就是说FIFO一直有空间可以写入数据,处于未满状态。那有没有什么方法可以让ready拉低,换言之,只有写入的数据比读出的数据快就可能出现ready拉低的情况,分析以上代码,我们发现:1.在每次写入数据之后紧跟一个write_idle(),这样实际上两拍才会写入一个数据;2.一共有3个slave,但是他们的chnl_write()在initial块中是顺序执行的,每次只有一个slave在往FIFO写入数据,如果换成并行执行,是不是也可以使chnl_ready拉低。
结合以上分析,我们对刚才的代码进行修改,如下:
`timescale 1ns/1ps
module chnl_initiator(
input clk,
input rstn,
output logic [31:0] ch_data,
output logic ch_valid,
input ch_ready,
input [ 5:0] ch_margin
);
string name;
function void set_name(string s);
name = s;
endfunction
task chnl_write(input logic[31:0] data);
// USER TODO
// drive valid data
// ...
@(posedge clk)
ch_valid <= 1;
ch_data<=data;
@(negedge clk);
wait(ch_ready == 1);
$display("%t channel initial [%s] sent data %x", $time, name, data);
//chnl_idle();
endtask
task chnl_idle();
// USER TODO
// drive idle data
//¡ª...
@(posedge clk)
ch_valid<=0;
ch_data<=0;
endtask
endmodule
module tb4;
logic clk;
logic rstn;
logic [31:0] ch0_data;
logic ch0_valid;
logic ch0_ready;
logic [ 5:0] ch0_margin;
logic [31:0] ch1_data;
logic ch1_valid;
logic ch1_ready;
logic [ 5:0] ch1_margin;
logic [31:0] ch2_data;
logic ch2_valid;
logic ch2_ready;
logic [ 5:0] ch2_margin;
logic [31:0] mcdt_data;
logic mcdt_val;
logic [ 1:0] mcdt_id;
mcdt dut(
.clk_i(clk)
,.rstn_i(rstn)
,.ch0_data_i(ch0_data)
,.ch0_valid_i(ch0_valid)
,.ch0_ready_o(ch0_ready)
,.ch0_margin_o(ch0_margin)
,.ch1_data_i(ch1_data)
,.ch1_valid_i(ch1_valid)
,.ch1_ready_o(ch1_ready)
,.ch1_margin_o(ch1_margin)
,.ch2_data_i(ch2_data)
,.ch2_valid_i(ch2_valid)
,.ch2_ready_o(ch2_ready)
,.ch2_margin_o(ch2_margin)
,.mcdt_data_o(mcdt_data)
,.mcdt_val_o(mcdt_val)
,.mcdt_id_o(mcdt_id)
);
// clock generation
initial begin
clk <= 0;
forever begin
#5 clk <= !clk;
end
end
// reset trigger
initial begin
#10 rstn <= 0;
repeat(10) @(posedge clk);
rstn <= 1;
end
logic [31:0] chnl0_arr[];
logic [31:0] chnl1_arr[];
logic [31:0] chnl2_arr[];
// USER TODO
// generate 100 data for each dynamic array
initial begin
chnl0_arr = new[100];
chnl1_arr = new[100];
chnl2_arr = new[100];
foreach(chnl0_arr[i]) begin
chnl0_arr[i] = 'h00C0_00000 + i;
chnl1_arr[i] = 'h00C1_00000 + i;
chnl2_arr[i] = 'h00C2_00000 + i;
end//...//...
end
// USER TODO
// use the dynamic array, user would send all of data
// data test
initial begin
@(posedge rstn);
repeat(5) @(posedge clk);
// USER TODO
// Give unique names to each channel initiator
// ...
fork
begin
chnl0_init.set_name("chnl0_init");
foreach(chnl0_arr[i]) chnl0_init.chnl_write(chnl0_arr[i]);
chnl0_init.chnl_idle();
end
// channel 1 test
// TODO use chnl1_arr to send all data
begin
chnl1_init.set_name("chnl1_init");
foreach(chnl1_arr[i]) chnl1_init.chnl_write(chnl1_arr[i]);
chnl1_init.chnl_idle();
end
// channel 2 test
// TODO use chnl2_arr to send all datsa
begin
chnl2_init.set_name("chnl2_init");
foreach(chnl2_arr[i]) chnl2_init.chnl_write(chnl2_arr[i]);
chnl2_init.chnl_idle();
end
join
// channel 0 test
// TODO use chnl0_arr to send all data
/*chnl0_init.chnl_write('h00C0_0000);
chnl0_init.chnl_write('h00C0_0001);
chnl0_init.chnl_write('h00C0_0002);
chnl0_init.chnl_write('h00C0_0003);
// channel 1 test
// TODO use chnl1_arr to send all data
chnl1_init.chnl_write('h00C1_0000);
chnl1_init.chnl_write('h00C1_0001);
chnl1_init.chnl_write('h00C1_0002);
chnl1_init.chnl_write('h00C1_0003);
// channel 2 test
// TODO use chnl2_arr to send all data
chnl2_init.chnl_write('h00C2_0000);
chnl2_init.chnl_write('h00C2_0001);
chnl2_init.chnl_write('h00C2_0002);
chnl2_init.chnl_write('h00C2_0003);*/
end
chnl_initiator chnl0_init(
.clk (clk),
.rstn (rstn),
.ch_data (ch0_data),
.ch_valid (ch0_valid),
.ch_ready (ch0_ready),
.ch_margin(ch0_margin)
);
chnl_initiator chnl1_init(
.clk (clk),
.rstn (rstn),
.ch_data (ch1_data),
.ch_valid (ch1_valid),
.ch_ready (ch1_ready),
.ch_margin(ch1_margin)
);
chnl_initiator chnl2_init(
.clk (clk),
.rstn (rstn),
.ch_data (ch2_data),
.ch_valid (ch2_valid),
.ch_ready (ch2_ready),
.ch_margin(ch2_margin)
);
endmodule
波形: