MCDF_svlab4 代码解读
lab4组件结构与lab3一样,但验证的DUT更大了,mcdt→mcdf。
lab4文件数目增加,为了模拟多个人验证同一个DUT,各个模块构建各自的package,假定模块验证完毕,现在子系统要集成各个模块的package,目前lab4不需要arbiter的package,最顶层环境交给mcdf的package。
与设计相关的文件:arbiter、formatter、reg、slave.fifo要先编译;然后再编译mcdf。
chnl_agent 的 stimlator 是initiator (个人看法:主动的体现在 valid ,可⾃⼰决定data是否有效) ⽽ fmt_agent被动,只能等fmt的req,看能不能放下再给出grant。
一、tb.sv
tb.sv⽂件中有各种接⼝⽂件,其端⼝都跟随DUT端⼝⽽设计,如cmd读写端⼝。
param_def.v来自设计,从复用角度考虑,保持统一。
arb.intf:arbiter接⼝暂时不⽤,各接⼝都在设计内部,不需要激励。
fmt_intf里,fmt_req拉高至少一拍后fmt_grant拉高(posedge信号保持一个周期),fmt_grant拉低后fmt_req跟着拉低(对照之前视频)。
fmt_req拉高,fmt_chid、fmt_length也变化,fmt_grant拉高后,fmt_start拉高,开始发送数据(连续发送fmt_data),发送完后fmt_end保持一拍,与最后一个数据同时变化。
目前来看lab4中mcdf_intf不用,channel、req、fmt的intf都直接与mcdf端口连接。有时想监测内部信号,会把信号给mcdf_intf,因此又可被验证盒子里的组件拿到,这意味着checker可拿到进而可以监测内部信号。
二、arb_pkg.sv、chnl_pkg.sv
不再细谈,可以回头看lab3的介绍。
三、reg_pkg.sv
class reg_trans:
数据成员:addr地址、cmd读写指令、data数据、rsp response 看读写回来的数据是否正常。
constraint cstr默认cmd为读、写、IDLE。宏仍是复用的param_def,说白了就是对一些参数赋值,设计验证两边对齐,可以通用。
为何要有这些constraint:
0x00、0x04、0x08为读写寄存器,转换为二进制分别为:0000 0000,0000 0100,0000 1000;
0x10、0x14、0x18为只读寄存器,转换为二进制分别为:0001 0000,0001 0100,0001 1000。
addr [7:4] ==0 && cmd == 'WRITE -> soft data [31:6] ==0;
第7-4位为0表示该地址是读写寄存器,命令cmd为写状态时(cmd=’WRITE),读写寄存器低6位可配置,高26位bit(31:6)为保留位无法写入,所以约束令高26位为0。
对于寄存器的描述可回看lab0:
soft addr[7:5] == 0;
//因为目前最高地址为0x10、0x14、0x18,转换为二进制分别为0001 0000、0001 0100、0001 1000,可看到7到5位均为0。
soft adddr[4] == 1 -> cmd != ‘READ;
//第4位为1表示只读寄存器(0x10、0x14、0x18转换为二进制分别为0001 0000、0001 0100、0001 1000),cmd为READ命令。
class reg_driver:
注:driver就是lab3的initiator,为了向UVM过渡改名了。
功能:发送激励。
task do_reset();forever begin@(negedge intf.rstn);intf.cmd_addr <= 0;intf.cmd <= `IDLE;intf.cmd_data_m2s <= 0;endendtask
task do_reset():复位时,register读写为设为0,cmd设为IDLE,写的数据设为0。
task do_drive();reg_trans req, rsp;@(posedge intf.rstn);forever beginthis.req_mb.get(req);this.reg_write(req);rsp = req.clone();rsp.rsp = 1;this.rsp_mb.put(rsp);endendtask
task do_drive():do_reset()复位后,从generator拿到一个req,调用req_write操作,clone后返回去rsp。
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; end`IDLE: begin this.reg_idle(); enddefault: $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
task reg_write :
cmd为’WRITE,拿到 reg_trans 类型的req后,若cmd为写操作,把req的addr和cmd发到接口intf上,把数据data发到总线上。
cmd为IDLE,调用reg_idle等一拍,把addr设为0,cmd设为IDLE,数据data设为0。
cmd为READ,先把req的addr和cmd发到接口intf上,告诉接口我要读谁,然后等两个下降沿(当前clk的上升沿过来,第一个下降沿还在当前周期,再等到下一个周期下降沿)采样intf的数据cmd_data_s2m(读的数据,表示对应channel下行的FIFO余量)。
为什么等两个下降沿?
这里想借鉴一下@Hardworking_IC_boy的解读:
reg_driver里是不通过时钟块采样,就需要避免竞争问题。reg_monitor是通过时钟块进行采样的,所以就不用再考虑竞争的问题。mon_trans里是通过mon_ck时钟块采的信号,会默认在上升沿后1ns才去采样,这样也可以准确地采样到D2稳定后的值。
class reg_generator:
task send_trans();reg_trans req, rsp;req = new();assert(req.randomize with {local::addr >= 0 -> addr == local::addr;local::cmd >= 0 -> cmd == local::cmd;local::data >= 0 -> data == local::data;})else $fatal("[RNDFAIL] register packet randomization failure!");$display(req.sprint());this.req_mb.put(req);this.rsp_mb.get(rsp);$display(rsp.sprint());if(req.cmd == `READ) this.data = rsp.data;assert(rsp.rsp)else $error("[RSPERR] %0t error response received!", $time);endtask
this.rsp_mb.get(rsp); generator从driver拿到rsp。
(reg_driver:从generator拿到req后看给的cmd是什么(write/read/idle),然后在reg_write对应操作。如果是读,就会把intf总线数据写进当前req里边,clong req给rsp交给generator。)
所以上边this.data = rsp.data; generator就可以拿到总线上的data。
class reg_monitor:
task mon_trans();reg_trans m;forever begin@(posedge intf.clk iff (intf.rstn && intf.mon_ck.cmd != `IDLE));//clk上升沿监测合理数据,cmd不为idle。m = new();m.addr = intf.mon_ck.cmd_addr;m.cmd = intf.mon_ck.cmd;//新生成一个对象,先把addr和cmd交进来。if(intf.mon_ck.cmd == `WRITE) beginm.data = intf.mon_ck.cmd_data_m2s;//Write:把当前总线上数据放进来。endelse if(intf.mon_ck.cmd == `READ) begin@(posedge intf.clk);m.data = intf.mon_ck.cmd_data_s2m;//READ:等下一个时钟周期,把总线上读回来的写进data。endmon_mb.put(m);//读写都会把数据放入mon_mb,然后交给checker。$display("%0t %s monitored addr %2x, cmd %2b, data %8x", $time, this.name, m.addr, m.cmd, m.data);endendtask
class reg_agent:略。
脉络:
class reg_trans是reg_driver和DUT的reg之间发送的对象类型(包含addr、cmd、data、rsp成员变量),会先根据各个成员变量的特点进行约束,clone函数会新创建一个同类型的对象并把各变量值赋给其。
class reg_driver相当于之前的initiator,会声明两个mailbox,task run()会调用do_drive()和do_reset(),do_reset()就是用来复位的。do_drive()和之前的chnl_pkg很像,从reg_generator通过req_mb接收到req后,会执行reg_write,然后克隆一份req给rsp,然后通过rsp_mb发给reg_generator。
其中执行reg_write是一个难点,对于不同cmd指令操作不同:
写:把req的addr和cmd给reg_driver和reg之间接口的addr和cmd,把req的data给接口的cmd_data_m2s(对于验证环境是output);其实就是把req的数据写到接口总线上。
读:先把req的addr和cmd给reg_driver和reg之间接口的addr和cmd,然后要等待两个下降沿*,把接口的数据cmd_data_s2m(表示对应channel下行的FIFO余量,对于验证环境是input)给req的data。
在读的时候,cmd_data_s2m先给req的data,然后克隆req给rsp传到generator,rsp.data又给generator的this.data,所以generator也会拿到接口总线上的数据。
四、fmt_pkg.sv
stimulator可分为两种:①initiator主动的②responder被动的
channel和register的driver是主动发起请求的initiator。fmt的driver是被动的responder。
fmt响应的过程:fmt会发起req,fmt_agent给grant信号。
fmt_agent里的driver要模拟一个下行FIFO:
下⾏数据如果buffer⽐较⼩,消化buffer⽐较慢,⼀般 grant信号给的就⽐较慢(余量不太易满⾜);若上⾯给的数据⼩,下⾯缓存⼤,只要data一进来下一周期grant立即拉高。(如下图)
class fmt_trans:
function bit compare(fmt_trans t);string s;compare = 1;s = "\n=======================================\n";s = {s, $sformatf("COMPARING fmt_trans object at time %0d \n", $time)};if(this.length != t.length) begincompare = 0;s = {s, $sformatf("sobj length %0d != tobj length %0d \n", this.length, t.length)};endif(this.ch_id != t.ch_id) begincompare = 0;s = {s, $sformatf("sobj ch_id %0d != tobj ch_id %0d\n", this.ch_id, t.ch_id)};endforeach(this.data[i]) beginif(this.data[i] != t.data[i]) begincompare = 0;s = {s, $sformatf("sobj data[%0d] %8x != tobj data[%0d] %8x\n", i, this.data[i], i, t.data[i])};endendif(compare == 1) s = {s, "COMPARED SUCCESS!\n"};else s = {s, "COMPARED FAILURE!\n"};s = {s, "=======================================\n"};rpt_pkg::rpt_msg("[CMPOBJ]", s, rpt_pkg::INFO, rpt_pkg::MEDIUM);endfunction
关注一下compare函数:
比较当前对象和另一个对象的成员(length、id、data),只要有一个不一样,compare返回0并报告错误。返回1则报告比较成功。
class fmt_driver:
class fmt_driver;local string name;local virtual fmt_intf intf;mailbox #(fmt_trans) req_mb;mailbox #(fmt_trans) rsp_mb;
两个mailbox(req_mb和rsp_mb)和generator通信。
local mailbox #(bit[31:0]) fifo;local int fifo_bound;local int data_consum_peroid;
第三个mailbox-fifo模拟下行数据buffer,之前的channel和register的driver没有fifo这个mailbox,数据发出去就不管了,但fmt模拟的从端,不但要接收数据消化掉,还要模拟消化数据的快慢。
fifo_bound:FIFO固定长度;
data_consum_peroid:数据消耗时间,侧面反映带宽,带宽越大时间消耗越少。
function new(string name = "fmt_driver");this.name = name;this.fifo = new();this.fifo_bound = 4096;this.data_consum_peroid = 1;endfunctionfunction void set_interface(virtual fmt_intf intf);if(intf == null)$error("interface handle is NULL, please check if target interface has been intantiated");elsethis.intf = intf;endfunctiontask run();forkthis.do_receive();this.do_consume();this.do_config();this.do_reset();joinendtask
四个方法:do_receive();do_consume();do_config();do_reset();
task do_config();fmt_trans req, rsp;forever beginthis.req_mb.get(req);case(req.fifo)SHORT_FIFO: this.fifo_bound = 64;MED_FIFO: this.fifo_bound = 256;LONG_FIFO: this.fifo_bound = 512;ULTRA_FIFO: this.fifo_bound = 2048;endcasethis.fifo = new(this.fifo_bound);case(req.bandwidth)LOW_WIDTH: this.data_consum_peroid = 8;MED_WIDTH: this.data_consum_peroid = 4;HIGH_WIDTH: this.data_consum_peroid = 2;ULTRA_WIDTH: this.data_consum_peroid = 1;endcasersp = req.clone();rsp.rsp = 1;this.rsp_mb.put(rsp);endendtask
do_config():开始要配置当前的driver,要配置其行为表现的更像一个buffer。
配置过程:this.req_mb.get(req);driver从generator拿到激励req,根据case(req.fifo)配置driver类对象的数据长度fifo_bound,对第三个mailbox——fifo重新例化并指定为相同长度。根据case(req.bandwidth)对driver类对象的data_consum_peroid(配置消耗时长)做配置。然后把req克隆一份给rsp,再传回给generator。
注:这里插入一下mailbox的性质:[Systemverilog学习笔记] Thread Communication-Event、Semaphore、mailbox_hjd西瓜瓜瓜的博客-CSDN博客_mailbox uvm通过下文了解Event、Semaphore、mailbox三种对象的概念、使用方法及应用场景https://blog.csdn.net/qq_36917568/article/details/122153924mailbox 是一种允许不同进程相互交换数据的方法,mailbox是一个内置类,本质上类似于队列,但和queue队列的数据类型有很大不同,使用semaphore来控制存储队列中的push和pull。无法访问邮箱队列中的给定索引,只能按照fifo的顺序检索项目。
mailbox 可以被创建为两种:
mailbox mailbox_name = new(mailbox_space_number);//有界队列,只能存储有限个数据量。当一个进程试图将多个消息存入一个满的mailbox中时,将会被挂起直到mailbox中有足够的空间。mailbox mailbox_name = new();//无界队列,可以存储无限个数据量。
task do_reset();forever begin@(negedge intf.rstn) intf.fmt_grant <= 0;endendtask
do_reset():接口复位信号到来fmt_grant驱动为0。
task do_consume();bit[31:0] data;forever begin//不断消耗数据void'(this.fifo.try_get(data));//尝试从FIFO拿一个数据(不管有没有都尝试拿)repeat($urandom_range(1, this.data_consum_peroid)) @(posedge intf.clk);//拿到一个数据后等待若干个周期。endendtask
do_consume():模拟fmt_driver消耗数据,把fifo中的数据排出去,根据data_consum_peroid(消耗周期)的快慢,每隔几个上升沿就从fifo里try_get一个bit [31:0] data类型的数据。
task do_receive();forever begin@(posedge intf.fmt_req);forever begin@(posedge intf.clk);if((this.fifo_bound-this.fifo.num()) >= intf.fmt_length)//bound不变,随着数据消耗fifo.num减小,余量增大。break;endintf.drv_ck.fmt_grant <= 1;@(posedge intf.fmt_start);forkbegin@(posedge intf.clk);intf.drv_ck.fmt_grant <= 0;//当余量超过length,grant变为1endjoin_nonerepeat(intf.fmt_length) begin@(negedge intf.clk);this.fifo.put(intf.fmt_data);//从fmt_start开始,重复fmt_length次采集数据放入fifo这个mailbox。接收数据完毕。endendendtask
do_receive():模拟从fmt接收数据再消化的过程。当fmt有发出需求,在接口上fmt_req的上升沿来临时,同时还要保证fifo总容量-fifo现存数据量>=接口上给的fmt_length即要到来的数据长度,如果满足就会把接口上的fmt_grant拉高,紧接着接口上的fmt_start上升沿也到来了,等待一个时钟上升沿再把fmt_grant拉低,然后重复fmt_length次把fmt_data放入fifo的操作,等把数据接受完后又开始等待下一个fmt的req发送请求。
do_receice、do_config、do_reset、do_consume都是硬件行为,是并列的。随时接受、配置、复位、消化。
class fmt_generator:
略。
class fmt_monitor:
task mon_trans();fmt_trans m;string s;forever begin@(posedge intf.mon_ck.fmt_start);//不需等待req、grant,假定协议没问题,等待fmt_start到来m = new();//例化数据包m.length = intf.mon_ck.fmt_length;m.ch_id = intf.mon_ck.fmt_chid;//把length、chid、fmt_length存放到fmt_trans对象里。m.data = new[m.length];//给动态数组开辟空间foreach(m.data[i]) begin@(posedge intf.clk);m.data[i] = intf.mon_ck.fmt_data;end//重复length次,每次intf.clk上升沿,把fmt_data存入m.data。mon_mb.put(m);s = $sformatf("=======================================\n");s = {s, $sformatf("%0t %s monitored a packet: \n", $time, this.name)};s = {s, $sformatf("length = %0d: \n", m.length)};s = {s, $sformatf("chid = %0d: \n", m.ch_id)};foreach(m.data[i]) s = {s, $sformatf("data[%0d] = %8x \n", i, m.data[i])};s = {s, $sformatf("=======================================\n")};$display(s);//存好后把m放入mailbox,打印出来。endendtask
fmt_monitor类包含一个fmt_intf类型接口intf。会声明并创建fmt_trans类型的mailbox(mon_mb)与checker通信。run()会调用mon_trans():不需等待req和grant,假定协议没问题,等待fmt_start上升沿,例化一个fmt_trans对象(一个完整的数据包),把fmt_length和fmt_chid存放进该对象里,根据length对动态数组开辟空间,然后把每个fmt_data放进对象。存好后把整个对象放入mon_mb。
class fmt_agent:
一个盒子,各种agent都差不多。
五、mcdf_pkg.sv
对上图的解释:
register 把监测到的data放⼊ checker的mailbox(reg_mb),3个channel 放⼊ chl 的mailbox(chnl_mb0、chnl_mb1、chnl_mb2),fmt把data放⼊fmt的mailbox(fmt_mb)。
所有数据开始都放在自己已例化的mailbox中。
lab3中checker直接数据比较,因为input和output端的数据格式一致。
lab4中(mcdf)3个chnl监测到的数据都是单个的,每次监测到的都是位宽32bits的数,但fmt监测到的是一个数据包(包括ch_id、length),说明fmt的mailbox和3个channel的mailbox内数据格式不一样,为比较带来困难。所以比较之前,先把chnl和register进来的数据(对于mcdf是input)整型转化,做一个参考模型。
mcdf_refmod模拟真实硬件:
1.从reg_mb得到寄存器的读写,用task do_reg_updata更新内部寄存器模型一些信号的值,模拟mcdf中reg的行为。
2.从chnl_mb 0、1、2接收到channel的input,对数据打包(do_package方法)。3个channel的数据打包后分别放入refmod内的3个缓存out_mbs[ 0.2.]。模拟slave→FIFO→arbiter→fmt的行为。
do_compare从out_mbs[ 0.1.2.]拿到数据包,与fmt_mb拿到的数据包比较。与lab3相似,先看fmt_mb数据包id,再从out_mbs拿对应的数据包比较。
mcdf_pkg.sv代码:
import chnl_pkg::*;import reg_pkg::*;import arb_pkg::*;import fmt_pkg::*;import rpt_pkg::*;typedef struct packed {bit[2:0] len;bit[1:0] prio;bit en;bit[7:0] avail;} mcdf_reg_t;
先import各个pkg。
为了模拟硬件的寄存器,定义一个结构体mcdf_reg_t,存放len、prio、en(读写)avail(只读),寄存器的32位数据包含了上述变量的信息,mcdf_reg_t则是把这些信息拿出来分开存放。
class mcdf_refmod:
这种结构例化3个,分别模拟三个channel的reg。
task run();forkdo_reset();this.do_reg_update();do_packet(0);do_packet(1);do_packet(2);joinendtask
do_packet:模拟三个channel对数据打包;do_reset:模拟寄存器的复位,缓存的复位。
do_reg_update:reg_driver如果给reg写,monitor也会捕捉到,把消息给mcdf_checker—mcdf_refmod,通过reg_mb捕捉到reg_trans对象(addr、cmd、data、rsp)。
写操作:
this.regs[t.addr[3:2]].en = t.data[0];
this.regs[t.addr[3:2]].prio = t.data[2:1];
this.regs[t.addr[3:2]].len = t.data[5:3];
不得不说这里的代码很巧妙t.addr[3:2]恰好就是0、1、2,通过这个索引对三个regs的成员变量赋值。
读操作:
this.regs[t.addr[3:2]].avail = t.data[7:0];
如果reg_driver是mcdf的状态寄存器作读操作,就把fifo available的值更新到对应channel的regs.avail。
function get_field_value:
通过id(channel的编号)、mcdf_field_t(枚举类型),拿到对应channel和reg的field。模拟硬件环境中reg把配置给channel、arb、fmt。
do_packet:do_packet (0、1、2)分别表示对来自channel0、1、2的数据进行打包。
task do_packet(int id);fmt_trans ot;//数据包格式,data是动态数组。mon_data_t it;//单一数据格式,data是32位宽的数。bit[2:0] len;forever beginthis.in_mbs[id].peek(it);ot = new();len = this.get_field_value(id, RW_LEN);ot.length = len > 3 ? 32 : 4 << len;ot.data = new[ot.length];ot.ch_id = id;foreach(ot.data[m]) beginthis.in_mbs[id].get(it);ot.data[m] = it.data;endthis.out_mbs[id].put(ot);endendtask
ot里的动态数组data的内容长度取决于当前的length(通过get_field_value得到)。
先确定动态数组的数据个数,再通过in_mbs从chnl_mon拿数据再填进数据包ot的data,填满打包好后整个放入out_mbs。
这里假定arbiter不丢数且优先级功能正常,把三个channel的数据都打包到对应的mailbox里,当arbiter的仲裁功能还未检查。
class mcdf_checker:
do_compare从out_mbs[ 0.1.2.]拿到数据包,与fmt_mb拿到的数据包比较。与lab3相似,先看fmt_mb数据包id,再从out_mbs拿对应的数据包比较。
class mcdf_env:
例化agent和checker。
所有mailbox例化都在checker中,将checker内的mailbox句柄赋给各个agent,monitor内mailbox句柄,先例化再连接。
这里do_config没有存在必要,不用它去配置generator了,转到based_test配置。
env的report嵌套checker的report去报告。
最后一层:class mcdf_base_test:
例化generator和env,连接句柄。
run():
让env.run在后台run(fork-joinnone),不阻碍其他步骤。
this.do_reg();
this.do_formatter();
this.do_data();
相当于配置硬件,想让其工作要先配置寄存器功能模式,利用reg_generator通过reg_driver进去配置寄存器,接下来do_ formatter,让fmt的下行可以发送出去再do_data。
MCDF_svlab4 代码解读相关推荐
- 200行代码解读TDEngine背后的定时器
作者 | beyondma来源 | CSDN博客 导读:最近几周,本文作者几篇有关陶建辉老师最新的创业项目-TdEngine代码解读文章出人意料地引起了巨大的反响,原以为C语言已经是昨日黄花,不过从读 ...
- 装逼一步到位!GauGAN代码解读来了
↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:游璐颖,福州大学,Datawhale成员 AI神笔马良 如何装逼一 ...
- Unet论文解读代码解读
论文地址:http://www.arxiv.org/pdf/1505.04597.pdf 论文解读 网络 架构: a.U-net建立在FCN的网络架构上,作者修改并扩大了这个网络框架,使其能够使用很少 ...
- Lossless Codec---APE代码解读系列(二)
APE file 一些概念 APE代码解读系列(一) APE代码解读系列(三) 1. 先要了解APE compression level APE主要有5level, 分别是: CompressionL ...
- RT-Thread 学习笔记(五)—— RTGUI代码解读
---恢复内容开始--- RT-Thread 版本:2.1.0 RTGUI相关代码解读,仅为自己学习记录,若有错误之处,请告知maoxudong0813@163.com,不胜感激! GUI流程: ma ...
- vins 解读_代码解读 | VINS 视觉前端
AI 人工智能 代码解读 | VINS 视觉前端 本文作者是计算机视觉life公众号成员蔡量力,由于格式问题部分内容显示可能有问题,更好的阅读体验,请查看原文链接:代码解读 | VINS 视觉前端 v ...
- BERT:代码解读、实体关系抽取实战
目录 前言 一.BERT的主要亮点 1. 双向Transformers 2.句子级别的应用 3.能够解决的任务 二.BERT代码解读 1. 数据预处理 1.1 InputExample类 1.2 In ...
- shfflenetv2代码解读
shufflenetv2代码解读 目录 shufflenetv2代码解读 概述 shufflenetv2网络结构图 shufflenetv2架构参数 shufflenetv2代码细节分析 概述 shu ...
- GoogLeNet代码解读
GoogLeNet代码解读 目录 GoogLeNet代码解读 概述 GooLeNet网络结构图 1)从输入到第一层inception 2)从第2层inception到第4层inception 3)从第 ...
最新文章
- CTFshow php特性 web139
- 组织与科目等维度构建
- ListView的Adapter使用(绑定数据) 之 自定义每一项的布局去绑定数据(二)
- [mybatis]Configuration XML_typeAliases and Alias
- 疑似小米11 Pro保护壳曝光:横向矩阵相机设计
- k8s核心技术-Pod(调度策略)_影响Pod调度(污点和污点容忍)---K8S_Google工作笔记0027
- 获取当前周和前一周周一和周天,下一周周一和周天
- Ubuntu把iso写入U盘
- JAVA基础编程50道练习题
- 信息学奥赛 python 教程_浦东信息学奥赛课程价格表,Python编程课程
- 向大家推荐一本学统计学的书
- 千峰JAVA逆战班Day33
- Web前端满屋花案例框架
- strapi终于装好了,网速太慢了,处理了一下代理,新建了一个.zshrc文件,加入了pon和poff两个函数
- Linux的命令行无法粘贴
- windowxp网络无法发现其他计算机,几个步骤轻松解决win7无法访问XP系统共享问题...
- 磁盘管理找不到新加硬盘,借助DG(DiskGenius)实现新加硬盘初始化分区和之前硬盘的数据迁移
- 软件工程团队项目------Beijing Subway
- 令人炸毛儿的MySQL隐式转换 - 无形之刃,最为致命
- 后端给前端传response下载excel文件