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 代码解读相关推荐

  1. 200行代码解读TDEngine背后的定时器

    作者 | beyondma来源 | CSDN博客 导读:最近几周,本文作者几篇有关陶建辉老师最新的创业项目-TdEngine代码解读文章出人意料地引起了巨大的反响,原以为C语言已经是昨日黄花,不过从读 ...

  2. 装逼一步到位!GauGAN代码解读来了

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:游璐颖,福州大学,Datawhale成员 AI神笔马良 如何装逼一 ...

  3. Unet论文解读代码解读

    论文地址:http://www.arxiv.org/pdf/1505.04597.pdf 论文解读 网络 架构: a.U-net建立在FCN的网络架构上,作者修改并扩大了这个网络框架,使其能够使用很少 ...

  4. Lossless Codec---APE代码解读系列(二)

    APE file 一些概念 APE代码解读系列(一) APE代码解读系列(三) 1. 先要了解APE compression level APE主要有5level, 分别是: CompressionL ...

  5. RT-Thread 学习笔记(五)—— RTGUI代码解读

    ---恢复内容开始--- RT-Thread 版本:2.1.0 RTGUI相关代码解读,仅为自己学习记录,若有错误之处,请告知maoxudong0813@163.com,不胜感激! GUI流程: ma ...

  6. vins 解读_代码解读 | VINS 视觉前端

    AI 人工智能 代码解读 | VINS 视觉前端 本文作者是计算机视觉life公众号成员蔡量力,由于格式问题部分内容显示可能有问题,更好的阅读体验,请查看原文链接:代码解读 | VINS 视觉前端 v ...

  7. BERT:代码解读、实体关系抽取实战

    目录 前言 一.BERT的主要亮点 1. 双向Transformers 2.句子级别的应用 3.能够解决的任务 二.BERT代码解读 1. 数据预处理 1.1 InputExample类 1.2 In ...

  8. shfflenetv2代码解读

    shufflenetv2代码解读 目录 shufflenetv2代码解读 概述 shufflenetv2网络结构图 shufflenetv2架构参数 shufflenetv2代码细节分析 概述 shu ...

  9. GoogLeNet代码解读

    GoogLeNet代码解读 目录 GoogLeNet代码解读 概述 GooLeNet网络结构图 1)从输入到第一层inception 2)从第2层inception到第4层inception 3)从第 ...

最新文章

  1. CTFshow php特性 web139
  2. 组织与科目等维度构建
  3. ListView的Adapter使用(绑定数据) 之 自定义每一项的布局去绑定数据(二)
  4. [mybatis]Configuration XML_typeAliases and Alias
  5. 疑似小米11 Pro保护壳曝光:横向矩阵相机设计
  6. k8s核心技术-Pod(调度策略)_影响Pod调度(污点和污点容忍)---K8S_Google工作笔记0027
  7. 获取当前周和前一周周一和周天,下一周周一和周天
  8. Ubuntu把iso写入U盘
  9. JAVA基础编程50道练习题
  10. 信息学奥赛 python 教程_浦东信息学奥赛课程价格表,Python编程课程
  11. 向大家推荐一本学统计学的书
  12. 千峰JAVA逆战班Day33
  13. Web前端满屋花案例框架
  14. strapi终于装好了,网速太慢了,处理了一下代理,新建了一个.zshrc文件,加入了pon和poff两个函数
  15. Linux的命令行无法粘贴
  16. windowxp网络无法发现其他计算机,几个步骤轻松解决win7无法访问XP系统共享问题...
  17. 磁盘管理找不到新加硬盘,借助DG(DiskGenius)实现新加硬盘初始化分区和之前硬盘的数据迁移
  18. 软件工程团队项目------Beijing Subway
  19. 令人炸毛儿的MySQL隐式转换 - 无形之刃,最为致命
  20. 后端给前端传response下载excel文件

热门文章

  1. 华为帐号助力金融服务体验 中信银行成鸿蒙生态先行者
  2. linux如何设置串口参数,串口通信的参数如何设置
  3. 全国表彰|达观数据创始人陈运文博士荣获第十一届“中国青年创业奖”,是上海唯一获奖者
  4. 常见的管道命令:sort、uniq、tr、cut
  5. 使用xlnt库读取excel中文乱码
  6. 自动驾驶开发中的地图
  7. 全球与中国萃取塔市场现状及未来发展趋势(2022)
  8. 新畅行业商城软件v1.3.9
  9. 使用docker安装拼音分词器
  10. 用c语言实现字符大小写转化