既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
读写寄存器
- bit(0):通道使能信号。1为打开,0位关闭。复位值为1。 bit(2:1):优先级。0为最高,3为最低。复位值为3。
- bit(5:3):数据包长度,解码对应表为,
0对应长度4,1对应长度8,2对应长度16,3对应长度32,其它数值(4-7)均暂时对应长度32。复位值为0。 - bit(31:6):保留位,无法写入。复位值为0。
只读寄存器
bit(7:0):上行数据从端FIFO的可写余量,同FIFO的数据余量保持同步变化。复位值为FIFO的深度数。
bit(31:8):保留位,复位值为0。
1.12 clone、sprint、
function reg_trans clone();
reg_trans c = new();
c.addr = this.addr;
c.cmd = this.cmd;
c.data = this.data;
c.rsp = this.rsp;
return c;
endfunction
function string sprint();
string s;
s = {s, $sformatf("=======================================\n")};
s = {s, $sformatf("reg\_trans object content is as below: \n")};
s = {s, $sformatf("addr = %2x: \n", this.addr)};
s = {s, $sformatf("cmd = %2b: \n", this.cmd)};
s = {s, $sformatf("data = %8x: \n", this.data)};
s = {s, $sformatf("rsp = %0d: \n", this.rsp)};
s = {s, $sformatf("=======================================\n")};
return s;
endfunction
endclass
1.2 reg_driver
class reg_driver;
local string name;
local virtual reg_intf intf;
mailbox #(reg_trans) req_mb;
mailbox #(reg_trans) rsp_mb;
function new(string name = "reg\_driver");
this.name = name;
endfunction
function void set\_interface(virtual reg_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task run();
fork
this.do\_drive();
this.do\_reset();
join
endtask
task do\_reset(); //复位
forever begin
@(negedge intf.rstn);
intf.cmd_addr <= 0;
intf.cmd <= `IDLE;
intf.cmd_data_m2s <= 0;
end
endtask
task do\_drive();
reg_trans req, rsp;
@(posedge intf.rstn);
forever begin
this.req_mb.get(req);
this.reg\_write(req);
rsp = req.clone();
rsp.rsp = 1;
this.rsp_mb.put(rsp);
end
endtask
task reg\_write(reg_trans t);//给寄存器
@(posedge intf.clk iff intf.rstn);
case(t.cmd)
`WRITE: begin //如果是写操作
intf.drv_ck.cmd_addr <= t.addr; //地址
intf.drv_ck.cmd <= t.cmd;
intf.drv_ck.cmd_data_m2s <= t.data; //数据
end
`READ: begin
intf.drv_ck.cmd_addr <= t.addr; //总线
intf.drv_ck.cmd <= t.cmd;
repeat(2) @(negedge intf.clk);//等两个时钟的下降沿,第一个下降沿还在
t.data = intf.cmd_data_s2m; //采样了intf的数据
end
`IDLE: begin
this.reg\_idle(); //等一拍
end
default: $error("command %b is illegal", t.cmd);
endcase
$display("%0t reg driver [%s] sent addr %2x, cmd %2b, data %8x", $time, name, t.addr, t.cmd, t.data);
endtask
当case为 READ时,等待了两个时钟的下降沿。
第一个下降沿还在当前周期,第二个下降沿就在下一个周期了。此时数据已经驱动到接口cmd_data_s2m处了,这时去采样接口处的数据就一定是要读的数据。
- 在控制寄存器接口上,需要在每一个时钟解析cmd。当cmd为写(WR)指令时,需要把数据cmd_data_in写入到cmd_addr对应的寄存器中;当cmd为读指令(RD)时,即需要从cmd_addr对应的寄存器中读取数据,并在下一个周期,将数据驱动至cmd_data_out接口。
task reg\_idle();
@(posedge intf.clk);
intf.drv_ck.cmd_addr <= 0;
intf.drv_ck.cmd <= `IDLE;
intf.drv_ck.cmd_data_m2s <= 0;
endtask
endclass
1.3 reg_generator
class reg_generator;
rand bit[7:0] addr = -1;
rand bit[1:0] cmd = -1;
rand bit[31:0] data = -1;
mailbox #(reg_trans) req_mb;
mailbox #(reg_trans) rsp_mb;
reg_trans reg_req[$];
constraint cstr{
soft addr == -1;
soft cmd == -1;
soft data == -1;
}
function new();
this.req_mb = new();
this.rsp_mb = new();
endfunction
task start();
send\_trans();
endtask