菜鸟记录
TB1.从Verilog到SV的进场
1.1 修改tb1.sv,比较和tb.v之间的仿真行为差别:如下图所示并无差别,可见sv对Verilog是全兼容的。
1.2 将tb1.sv中的reg或者wire类型修改为logic类型,编译仿真行为无差别,说明,在SV中,reg和wire类型都可以简化为logic类型。
1.3 在1.2的基础上,将rstn的类型由logic变为bit,查看编译仿真行为,可见,一开始时钟信号便被拉低为0,这是以为bit二值逻辑默认为0,而logic四值逻辑默认为x,经过10ns后才被拉低为0。
TB2. 方法task和函数function
2.1 不做修改的情况下, 对 tb2.sv 进行编译仿真, 时钟信号和复位信号还正常吗?为什么?
不正常。
2.2 在两个 initial 块中分别调用产生时钟和复位的 task, 再编译仿真查看时钟信号和复位信号, 是否恢复正常呢?
恢复正常。
2.3 为什么要将两个 task 在两个 initial 块中调用?这是为什么呢?是否可以在一个 initial 块中
调用呢?如果可以, 调用它们的顺序是什么?
因为task和function一样,是一种函数,需要被调用才能被执行,必须被过程块initial或者always调用才能被执行。
不可以放在一个initial块中调用,多个initial块是并行执行的,而initial块中的语句是串行执行的。
2.4 是否可以读出目前时钟的周期和频率呢?该如何测量呢?如果我们想进化 clk_gen()方法, 使其变为可以设置时钟周期的任务 ckgen(int peroid), 那么该怎么修改目前的任务clk_gen()呢?修改成功后, 请在 initial 块中调用任务 cdk_ gen(20), 看看波形中的时钟周期是否变为 20ns 呢?
通过wave菜单栏,可以添加查看时钟频率,周期则可以通过两个bar之间数据读出。
task clk_gen(int period);
clk <= 0;
forever begin
#(period/2) clk<= !clk;
end
endtask
2.5 如果将 timescale 1ns/1ps 修改为 timescale 1ps/1ps, 那么仿真中的时钟周期是否发生变
化?这是为什么呢?
周期发生变化,变为20ps,因为timescale 1ps/1ps,第一个参数为仿真所用时间单位,第二个参数为时间精度。
TB3. 数组的使用
要先生成 100 个数,并对它们按照日前的数值规则进行赋值,创建 3 个动态数组,分别放置要发送给 3 个 slave 的数据。利用之前生成的数组数据, 将它们读取并发送给三个 channel。
// 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
// use the dynamic array, user would send all of data
// data test
initial begin
@(posedge rstn);
repeat(5) @(posedge clk);
// channel 0 test
// TODO use chnl0_arr to send all data
foreach(chnl0_arr[i]) chnl_write(0,chnl0_arr[i]);
// channel 1 test
// TODO use chnl1_arr to send all data
foreach(chnl1_arr[i]) chnl_write(1,chnl1_arr[i]);
// channel 2 test
// TODO use chnl2_arr to send all data
foreach(chnl2_arr[i]) chnl_write(2,chnl2_arr[i]);
end
TB4. 验证结构
例化三个chnl_initiator 实例 chnl0_init、chnl1_ init 和 chnl2_init。它们的作用扮演每个channel_slave 通道对应的 stimulator,发送激励,因此需要在其模块chnl_initiator 中定义它的三个方法,即 set_ name()、chpl_write()和 chnl_idle()。
① chnl_idle() :要实现的一个时钟周期的空闲, 在该周期中, ch. valid 应为低,ch. data 应
为 0。
② chnl_ write():要实现一次有效的写数据,并随后调用 chnl_idle( ),实现一个空闲周期,在实现有效写数据时,需要考虑如何使用 ch_ ready 信号,结合功能描述的 channel_slave接口时序看,只有当 valid 为高且 ready 为高时,数据写入才算成功,如果此时 ready 为低,那么则应该保持数据和 valid 信号,直到 ready 拉高时,数据写入才算成功。
③ set_name( ):设置实例的名称,在 initial 过程块“data_test"中,在发送各个 channel 数据前,设置各个 channel_initiator 的实例名称,这样方便在仿真时各个实例的打印信总可以显示它们各自的名称、 数据发送时间和数据内容, 便于阅读和调试。
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