路科v0实验,这是第五周练习完成代码,
学习内容如下:
定义了接口
使用模块定义了数据发生器gen(),激励器,监视器。
尚未使用类来完成监视器的设计
//`timescale 1ns/10ps
typedef struct{//定义一个结构体变量,用于存放数据信息
bit[3:0] src;//输入数据(din)的通道
bit[3:0] dst;//输出数据(dout)的通道
bit[7:0] data[];//发送的数据
} rt_packet_t;
interface rt_io;
logic clk;
logic rstn;
logic[15:0] din;
logic[15:0] frame_n;
logic[15:0] valid_n;
logic[15:0] dout;
logic[15:0] valido_n;
logic[15:0] busy_n;
logic[15:0] frameo_n;
endinterface
//用于发生激励
module rt_stimulator(
rt_io io
);
rt_packet_t pkts[$];//激励模块的队列
int src_chal_status[int];
function void put_pkt(input rt_packet_t p);//激励模块的队列中加入一个数据
pkts.push_back(p);
endfunction
task drive_reset_proc();
@(negedge io.rstn);//检测到复位信号后,产生一个复位动作
io.din <= 0;
io.frame_n <= '1;
io.valid_n <= '1;
endtask
//saddr-in,daddr-out,
task automatic drive_chal_proc(bit[3:0] saddr,bit[3:0] daddr,byte unsigned data[]);
$display("src_chal[%0d], chal[%0d] started",saddr,daddr);
$display("data = %h",data);
for(int i = 0;i<4;i++) begin//发送四位的daddr(dst),即那个数据通道
@(posedge io.clk);
io.din[saddr] <= daddr[i];
io.valid_n[saddr] <= 1'b0;//暂时先不开启输入?
io.frame_n[saddr] <= 1'b0;
end
//drive pad phase
for(int i = 0;i<5;i++) begin//按照时序,发送五个空拍
@(posedge io.clk);
io.din[saddr] <= 1'b1;
io.valid_n[saddr] <= 1'b0;
io.frame_n[saddr] <= 1'b0;
end
//drive data phase
foreach(data[id]) begin//每个data为一个字节,每个结构体里的data可能有多个字节的数据
for(int i = 0;i<8;i++) begin//开始发送一个字节
@(posedge io.clk);
io.din[saddr] <= data[id][i];
io.valid_n[saddr] <= 1'b0;
if(id == data.size()-1 && i == 7)
io.frame_n[saddr] <= 1'b1;//数据发送完毕
else
io.frame_n[saddr] <= 1'b0;//else继续发送
end
end
@(posedge io.clk);//结束动作
io.din[0] <= 1'b0;
io.valid_n[saddr] <= 1'b1;
io.frame_n[saddr] <= 1'b1;
$display("src_chal[%0d], chal[%0d] finished",saddr,daddr);
endtask
//等待src(输入通道)可用(-1),src_chal_status是一个关联数组,存放了src是否被占用的状态
task automatic wait_src_chanl_avail(rt_packet_t p);
if(!src_chal_status.exists(p.src))
src_chal_status[p.src] = p.dst;
else if(src_chal_status[p.src] >= 0)
wait(src_chal_status[p.src] == -1);
endtask
//将某一输入通道置为空闲
function automatic set_src_chanl_avail(rt_packet_t p);
src_chal_status[p.src] = -1;
endfunction
initial begin
drive_reset_proc();//这个函数的作用:检测到rstn下降沿后进行一个复位动作
end
initial begin
@(negedge io.rstn);//检测到复位信号(rstn的下降沿)
repeat(10) @(posedge io.clk);//等待十个复位信号
forever begin
automatic rt_packet_t p;//声明一个静态结构体
wait(pkts.size()>0);//若队列为空则一直等待,直到队列中存在数据
p = pkts.pop_front();//从队列中取出数据
wait_src_chanl_avail(p);//p中有p.src,src指示了输入通道,这个方法会一直等待这个通道为可用状态,并占用他。
fork //出发线程,但不等待他执行完成(fork-join_none)
begin
drive_chal_proc(p.src,p.dst,p.data);//发送数据
set_src_chanl_avail(p);//发送完成后,将占用的通道置为空闲状态
end
join_none
end
end
endmodule
module rt_monitor(rt_io io);//定义监视器模块
//注 rt_packet_t是前面定义的结构体,用于储存数据信息,包括dut的输入通道信息,输出通道信息,以及data
rt_packet_t in_pkts[16][$];//定义16个队列,每个队列用于存放一个结构体信息
rt_packet_t out_pkts[16][$];
task automatic moni_chal_in(bit[3:0] id);//定义输入数据的monitor
rt_packet_t pkt;//一个结构体,用于储存一次检测的信息
pkt.src = id;//id是监测的通道id。即pkt.src(输入通道)
forever begin
pkt.data.delete();//清空这个给结构体
@(negedge io.frame_n[id]);//检测frame_n下降沿,数据开始发送
$display("[monitor-in]%d start",id);
for(int i=0;i<4;i++) begin
@(posedge io.clk);//每个时钟上升沿接收1bit数据,按照时序,现在接受的是四位的地址数据(这个地址数据其实是dout的输出通道数)
pkt.dst[i] = io.din[id];
end
repeat(5) @(negedge io.clk);//按照时序,这里有五个空的时钟,不关心他的值
do
begin
pkt.data = new[pkt.data.size+1](pkt.data);//这里没明白,可能是分配新的内存空间
for(int i=0;i<8;i++) begin
@(posedge io.clk);//八个时钟上升沿,接收一个字节的data
pkt.data[pkt.data.size-1][i] = io.din[id];
end
end
while(!io.frame_n[id]);//一直重复上面这个过程(接受一字节的data),直到io.frame_n置1,因为按照时序,数据发送结束后,io.frame_n会拉高
in_pkts[id].push_back(pkt);
$display("[monitor-in]%d train finished src = %d, dst = %d, data = %h ",id,pkt.src, pkt.dst, pkt.data);
end
endtask
task automatic moni_chal_out(bit[3:0] id);
rt_packet_t pkt;
forever begin
pkt.data.delete();//清空数据
pkt.src = 0;//dut的输出中,无从得知一个数据的src是哪来的,这里直接定义为0
pkt.dst = id;//监测的通道id
@(negedge io.frameo_n[id]);//io.frameo_n[id]下降沿表明dut要开始输出数据了(参见时序图)
$display("[monitor-out]%d start",id);
do
begin
pkt.data = new[pkt.data.size+1](pkt.data);
for(int i=0;i<8;i++) begin//dut的输出仅输出data,
@(posedge io.clk iff !io.valido_n[id]);//dut输出时会将valido拉低,因此这里的判断是:时钟上升沿 并且 valido为低电平
pkt.data[pkt.data.size-1][i] = io.dout[id];
end
end
while(io.frameo_n[id] == 1);//这里与din的监测逻辑一样,dut的dout输出结束后,会将frameo_n拉高
out_pkts[id].push_back(pkt);
$display("[monitor-out]%d train finished src = %d, dst = %d, data = %h ",id,pkt.src, pkt.dst, pkt.data);
end
endtask
initial begin:moni_chal_in_proc
foreach(in_pkts[i]) begin//in_pkts是16个队列,对应每一个通道,这里对每一个通道都进行监测
automatic int chanl_in_id = i;
fork//出发监测线程,(且不等待他结束)
moni_chal_in(chanl_in_id);//这一个task里面有forever,一直对dut进行监测
join_none
end
end
initial begin:moni_chal_out_proc
foreach(out_pkts[i]) begin
automatic int chanl_out_id = i;
fork
moni_chal_out(chanl_out_id);
join_none
end
end
endmodule
//用于生成数据的模块
module rt_generator;
rt_packet_t pkts[$];
function void put_pkt(input rt_packet_t p);
pkts.push_back(p);//将一条数据放入队列中
endfunction
task get_pkt(output rt_packet_t p);
wait(pkts.size()>0)//等待直到pkts队列里部位空
p = pkts.pop_front();//取出一条数据
endtask
function void gen_pkt(int src = -1, int dst = -1);
endfunction
endmodule
module top;
endmodule
module tb1;//这里就相当于一个顶层设计了
var bit clk,rstn;
//generate clk
initial begin
forever #5 clk = ~clk;
end
//generate rstn
initial begin
#5 rstn <= 1;
#10 rstn <= 0;
#10 rstn <= 1;
end
rt_io io();//例化一个接口
assign io.clk = clk;
assign io.rstn = rstn;
router dut(
.reset_n(io.rstn),
.clock(io.clk),
.frame_n(io.frame_n),
.valid_n(io.valid_n),
.din(io.din),
.dout(io.dout),
.busy_n(io.busy_n),
.valido_n(io.valido_n),
.frameo_n(io.frameo_n)
);
rt_monitor monitor(io);//例化一个检测器,并连接接口:io
rt_stimulator stim(io);//例化一个激励器
rt_generator gen();//例化一个数据生成模块
initial begin:generate_proc//生成数据
rt_packet_t p;
p = '{0, 3, '{8'ha1,8'hf0}};
gen.put_pkt(p);
gen.put_pkt('{1, 3, '{8'haa,8'hff}});
gen.put_pkt('{1, 2, '{8'h0f,8'h0f}});
gen.put_pkt('{0, 3, '{8'h0f,8'h0f,8'haa}});
end
initial begin:transmit_proc
rt_packet_t p;//一个结构体句柄
forever begin
gen.get_pkt(p);//取出一个数据
stim.put_pkt(p);//将该数据送入激励发生器,即发送数据
end
end
endmodule