当前位置: 首页 > news >正文

IP验证学习之agent编写

目录

前言

interface:

sequence item :

driver:

monitor:

sequence:

注释:

sequencer:

agent:

agent_config : 

agent_packet:

总结


前言

这篇文章主要讲述agent中各个组件的模板的通用写法,具体的环境还是要依靠DUT的环境。读者可以细致阅读代码注释以体会其中的设计思想。

以编写一个agent为例,简单的描述一下agent的内容,其基本概念已经在 UVM入门基础 文章里做了简单介绍,这里不在赘述了。

若其中含有错误还恳请读者指正。

参考:绿皮书,验证公众号


一个成熟的agent里面,势必包含了很多可重用性组件,不过这些组件通常也是照搬其他环境的组件然后自己做改动的。

下面做一些简单的组件编写介绍。

interface:

接口的编写很简单,通常是直接看DUT的接口,然后进行分类后就可以写出来,因为很简单,可以先写接口。

interface op_in_if#(DATA_WIDTH = 4                //参数申明
) (input clk, input rst_n);logic    start;logic    ed;logic[DATA_WIDTH-1 : 0]    data1;logic[DATA_WIDTH-1 : 0]    data2;logic                      data_en;   parameter     setup_time    = 0.1ns;
parameter     hold_time     = 0.1ns;clocking drv_cb  @(posedge clk);            //为了能从波形上看出延时default input #setup_time    output #hold_time;output    start;output    ed;                    //接口方向是从driver视角看的output    data1;output    data2;output    data_en;input     rst_n;endclocking: drv_cbclocking mon_cb  @(posedge clk);default input #setup_time    output #hold_time;input    start;input    ed;                    //接口方向是从monitor视角看的input    data1;input    data2;input    data_en;input    rst_n;endclocking: mon_cbmodoport drv(clocking drv_cb);
modoport mon(clocking mon_cb);endinterface: op_in_if

sequence item :

在编写该组件时,通常会排除掉interface里的控制信号以及使能信号,只是单纯的提取数据和配置接口。这样做的原因是DUT的控制信号以及使能信号在整个环境中流通其实没有太大意义,这些控制信号可以在tb里直接配置,或者是由其他VIP控制的。

如果当前的transacion还需要用于对比,那就需要注意将其注册变量,以及添加一些相关约束,(如果不需要对比则可以将变量加入 UVM_NOCOMPARE 内)。

class op_in_seq_item#(DATA_WIDTH = 4
) extends uvm_sequence_item;//-------------Data Member--------------rand bit[DATA_WIDTH-1:0]    data1;rand bit[DATA_WIDTH-1:0]    data2;            //monitor采样,采样到数据后存放到队列中rand bit[DATA_WIDTH-1:0]    data1_q[$];rand bit[DATA_WIDTH-1:0]    data2_q[$];        //用于临时存储数据rand bit    data_num;                        //用于计数数据有多少个,通常用来控制数据要发多少`uvm_object_param_utils_begin(op_in_seq_item`PARAM_LIST)`uvm_field_int(data1 , UVM_ALL_ON)`uvm_field_int(data2 , UVM_ALL_ON)`uvm_field_queue_int(data1_q , UVM_ALL_ON)`uvm_field_queue_int(data1_q , UVM_ALL_ON)`uvm_field_int(data_num , UVM_ALL_ON)`uvm_object_utils_end                        //存在生命周期的用object注册//-------------------------------------------------//Methods//-------------------------------------------------extern function new(string name = "op_in_seq_item");endclass:op_in_seq_itemfunction op_in_seq_item::new(string name = "op_in_seq_item");    //在类的外面申明函数以供类调用super.new(name);`uvm_info("TRACE",$sformatf(%m), UVM_HIGH)endfunction:new

driver:

编写driver通常是根据已有的时序图编写,这个时序图来自设计文档,也可以是自己对DUT的理解,通常在main_phase里执行发送流程,而具体的数据内容则是自己定义函数编写实现。

`define DRV_VIF    vif.drv_cb        //为了方面书写使用宏定义来替代接口的始终块
typedef class op_in_driver;class op_in_driver_callback#(DATA_WIDTH = 4
)extends uvm_callback;            //为了在driver中不修改原值的情况下进行额外的逻辑操作,通常使用callback机制function new(string name = "op_in_driver_callback");super.new(name);endfunctionvirtual task pre_send(op_in_driver`PARAM_LIST    drv, op_in_seq_item`PARAM_LIST    tr);        endtask    //定义具体的回调方法virtual task post_send(op_in_driver`PARAM_LIST    drv, op_in_seq_item`PARAM_LIST    tr);        endtask    //同样是在回调基类中定义了具体的回调方法endclass    //定义了回调基类,也就是定义了回调函数的入口,在里面可以找到具体的回调方法class op_in_driver_example_callback#(DATA_WIDTH = 4
)extends op_in_driver_callback`PARAM_LIST;    //继承上一个callback类,这只是个具体的使用示例virtual task pre_send(op_in_driver`PARAM_LIST    drv, op_in_seq_item`PARAM_LIST    tr);    `uvm_info("OP_IN_DRIVER_CALLBACK", "callback test", UVM_MEDIUM)endtask
endclass    //这里演示的只是一个追踪环境功能,读者可以自行修改逻辑。class op_in_driver#(DATA_WIDTH = 4
) extends uvm_driver #(op_in_seq_item`PARAM_LIST);`uvm_component_param_utils(op_in_driver`PARAM_LIST)`uvm_register_cb(op_in_driver,    op_in_driver_callback)virtual op_in_if `PARAM_LIST    vif ;//------------------------------------------------------------//Methods//------------------------------------------------------------extern function new(string name,uvm_component parent);extern function void build_phase(uvm_phase phase);extern virtual function void start_of_simulation_phase(uvm_phase phase);extern virtual task main_phase(uvm_phase phase);extern virtual task pre_reset_phase(uvm_phase phase);extern virtual task reset_phase(uvm_phase phase);task shutdown_phase(uvm_phase phase);    //为了将最后一笔数据发出去,这里就是构造一个脉冲发出去super.shutdown_phase(phase);phase.raise_objection(this);@(`DRV_VIF)`DRV_VIF.ed <= 1;@(`DRV_VIF)`DRV_VIF.ed <= 0;phase.drop_objection(this);
endtask//用户自定义的任务extern virtual task send(op_in_seq_item`PARAM_LIST    tr);endclass: op_in_driverfunction op_in_driver::new(string name, uvm_component parent)super.new(name , parent);`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction : newfunction void op_in_driver::build_phase(uvm_phase phase);    //也可以在build_phase里面实例化super.build_phase(phase)`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction: build_phasefunction void op_in_driver::start_of_simulation_phase(uvm_phase phase);super.start_of_simulation_phase(phase);`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction: start_of_simulation_phase//在main_phase里正常的发驱动即可,上面的phase主要是用来追踪环境的,可以写也可以不写
task op_in_driver::main_phase(uvm_phase phase);op_in_seq_item`PARAM_LIST    req ;`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)forever beginseq_item_port.get_next_item(req) ;`uvm_info("OP_IN_DRIVER_TRANS", req.sprint(), UVM_MEDIUM )`uvm_do_callbacks(op_in_driver`PARAM_LIST, op_in_driver_callback`PARAM_LIST, pre_send(this,req));    //调用回调方法,也就是调用之前定义的回调函数pre_send,将回调函数里的值赋值给当前的对象req。send(req);repeat(20) @(`DRV_VIF);`uvm_do_callbacks(op_in_driver`PARAM_LIST, op_in_driver_callback`PARAM_LIST, pre_send(this,req));seq_item_port.item_done();endendtask : main_phase//模拟reset之前的信号输入X态
task op_in_driver::pre_reset_phase(uvm_phase phase);super.pre_reset_phase(phase)`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)phase.raise_objection(this);`DRV_IF.start   <= 'bx;`DRV_IF.data_en <= 'bx;`DRV_IF.data1   <= 'bx;`DRV_IF.data2   <= 'bx;`DRV_IF.ed      <= 'bx;phase.drop_objection(this);
endtask: pre_reset_phase//等待复位后X态变成复位值
task op_in_driver::reset_phase(uvm_phase phase);super.reset_phase(phase);`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)phase.raise_objection(this);wait(vif.rst_n = 1) ;            //等待事件触发`DRV_IF.start   <=   'b0;`DRV_IF.data_en <=   'b0;`DRV_IF.data1   <=   'b0;`DRV_IF.data2   <=   'b0;`DRV_IF.ed      <=   'b0;wait(vif.rst_n = 0) ;while(!vif.rst_n) begin@(`DRV_VIF);endphase.drop_objection(this) ;
endtask : reset_phase//根据设计文档中的时序图逻辑编写即可。
task op_in_driver:: send(op_in_seq_item`PARAM_LIST    tr);@(`DRV_VIF);`DRV_VIF.start    <=  1;@(`DRV_VIF);`DRV_VIF.start    <=  0;//repeat(2) @(`DRV_VIF)for(int i=0 ; i< tr.data_num; i++)    begin`DRV_VIF.data1    <=  tr.data1_q[i] ;`DRV_VIF.data2    <=  tr.data2_q[i] ;@(`DRV_VIF);end`DRV_VIF.data_en    <=  'b0;`DRV_VIF.ed         <=   1;@(`DRV_VIF);`DRV_VIF.ed         <=   0;endtask :send`undef    DRV_VIF

上述的driver描写的各个phase算得上详细了,实际上的driver编写通常也懒得追加环境,读者阅读代码时注意后面的注释语句就好。

monitor:

编写时要对应着driver的驱动时序,同样是根据已有的时序图来编写,不过相对而言会更复杂一点,因为它需要使用forever语句一直保持采样,原因在于monitor不知道何时采样才能停止。

`define MON_VIF    vif.mon_cb ;    //同driver一样,定义宏代替时钟块class op_in_monitor#(DATA_WIDTH = 4
)extends uvm_monitor;`uvm_component_param_utils(op_in_monitor`PARAM_LIST)    //组件注册virtual op_in_if`PARAM_LIST    vif;                    //声明接口句柄uvm_analysis_port #(op_in_seq_item`PARAM_LIST)     analysis_port;    //端口声明//-----------------------------------------------------------//Methods//-----------------------------------------------------------//standard UVM methods
extern function new(string name, uvm_component parent);
extern function build_phase(uvm_phase phase);
extern virtual task run_phase(uvm_phase phase);//用户自定义函数endclass: op_in_monitorfunction op_in_monitor::new(string name, uvm_component parent)super.new(name, parent) ;`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH)
endfunction: newfunction void op_in_monitor::build_phase(uvm_phase phase)super.build_phase(phase) ;`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH)analysis_port = new("analysis_port", this );
endfunction:build_phase//为了向读者展示另一种常见的写法,这里就不像driver一样细分很多phase,而是统一使用run_phasse写task op_in_monitor::run_phase(uvm_phase phase);op_in_seq_item`PARAM_LIST    tr_clone ;        //采样后赋值op_in_seq_item`PARAM_LIST    tr ;bit    mon_flag = 0  ;    //采样标志`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH)tr = op_in_seq_item`PARAM_LIST::type_id::create("tr", this);forkforever beginwhile(`MON_VIF.rst_n !== 1) begin@(`MON_VIF);end@(`MON_VIF);if(`MON_VIF.start) begintr.begin_tr();    //记录事务的开始时间 ,begin_tr() 和 end_tr() 是sequence类自带的方法mon_flag = 1;endelse if(`MON_VIF.ed) begintr.end_tr();    //记录事务的结束时间mon_flag = 0;if(tr.data1_q.size()!=0) begin `uvm_info("MONITOR_TRANS" ,{"\n", tr.sprint()} , UVM_MEDIUM)$cast(tr_clone,tr.clone);     //采样赋值后类型转换tr_clone.begin_tr(tr.get_begin_time);tr_clone.end_tr(tr.get_end_time);        //获取事务的持续时间,用于分析时序analysis_port.write(tr_clone);tr = op_in_seq_item`PARAM_LIST::type_id::create("tr", this);   //发出去后更新数值endendendforever beginif(mon_flag && ~`MON_VIF.start && `MON_VIF.data_en) begintr.data1 = `MON_VIF.data1 ;                  //采样到数据tr.data2 = `MON_VIF.data2;tr.data1_q.push_back(`MON_VIF.data1);        //压入队列tr.data2_q.push_back(`MON_VIF.data2);@(`MON_VIF);endelse begin@(`MON_VIF);endendjoin
endtask :  run_phase`undef MON_VIF

sequence:

sequence可以当作是sequence_item(transaction) 的延伸,transaction 是只包含事务性信息的包,而sequence是对包的具体事务做划分,规定了其变量要做什么动作,以及怎样划分等。

在编写sequence通常是对应着要操作的那个transaction,对transaction内的信号进行一些列的操作,在将数值赋值给transaction。

不过有些代码设计时也会先编写一个base_seq出来,所有其他的seq都从这个base_seq扩展出来,共用一套基层代码,类似于base_teset一样。

class op_in_seq_base#(DATA_WIDTH = 4
) extends uvm_sequence #(op_in_seq_item`PARAM_LIST);`uvm_object_param_utils_begin(op_in_seq_base`PARAM_LIST)// ......`uvm_object_utils_end//constrainsconstrain vaild{//data inside {[8'h0: 8'hf0]};
};// ----------------------------------------------------------// Methods//-----------------------------------------------------------//standard UVM Methods
extern function new(string name = "op_in_seq_base");
extern function void pre_randomize();
extern task pre_body();        //uvm_sequence自带的任务 , 用于控制sequence的行为
extern task post_body();       //uvm_sequence自带的任务 ,用于控制sequence的行为endclass: op_in_seq_basefunction op_in_seq_base::new(string name = "op_in_seq_base");super.new(name);`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH ) 
endfunctionfunction void op_in_seq_base::pre_randomize();//......空载endfunctiontask op_in_seq_base::pre_body();`ifdef    UVM_VERSION_1_1if(get_parent_sequence() == null && starting_phase != null) begin    //如果是顶层序列并且是在某个phase里运行uvm_objection objection = starting_phase.get_objection();    //获取当前序列启动phase的objectionobjection.set_drain_time(this,25ns);    //设置objection的排水时间为25nsstarting_phase.raise_objection(this);end`endif
endtasktask op_in_seq_base::post_body();`ifdef UVM_VERSION_1_1if(get_parent_sequence() == null && starting_phase != null) beginstarting_phase.drop_objection(this);end`endif
endtaskclass op_in_seq#(DATA_WIDTH = 4
) extends op_in_seq_base`PARAM_LIST;            //扩展出子sequence`uvm_objection_param_utils(op_in_seq`PARAM_LIST)//data member//......//constrain//......//---------------------------------------------//Methods
//----------------------------------------------//standard UVM Methods
extern function new(string name = " op_in_seq");
extern virtual task body();endclass: op_in_seqfunction op_in_seq::new(string name = "op_in_seq");super.new(name)`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH)
endfunction : newtask op_in_seq::body();op_in_seq_item`PARAM_LIST    req;`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH)`uvm_do(req)//如果需要细分sequence,可以写成uvm_do_with(req,{data_num inside {[2:30]}; })
endtask

注释:

if (get_parent_sequence() == null && starting_phase != null) 这段代码是检查当前当前序列是否有父序列,如果为null则表明是顶层序列启动,也就是直接由sequencer启动,否则就表明是由其他序列调用,后面的是检查当前序列是否关联了UVM的phase,如果非null,则表明 它是在某个phase 里启动的。

因此这行代码的解释为:检查该序列是否为顶层启动且关联了phase。

task my_sequence::body();// 如果是顶层序列且在某个phase中运行if (get_parent_sequence() == null && starting_phase != null) begin`uvm_info("SEQ_START", "This is a top-level sequence running in a phase", UVM_LOW)// 执行仅顶层序列需要的操作(如配置DUT、启动监控等)end// 正常序列逻辑forever beginmy_transaction tr;`uvm_create(tr)// ... 填充事务并发送 ...end
endtask

uvm_objection objection = starting_phase.get_objection();这段代码是获取该序列的对应的objection对象,简单的说,如果序列内置的starting_phase是耗时的,那么就会获取到活动的objection对象,该phase就不会结束。

task my_sequence::body();// 获取当前sequence中关联的phase的objection对象uvm_objection objection = starting_phase.get_objection();// 如果有objection对象(即当前序列关联了phase)if (objection != null) begin// 发起objection,阻止phase结束objection.raise_objection(this, "Sequence started");// 执行序列逻辑(例如发送多个事务)repeat (10) begin`uvm_do(req)end// 撤销objection,允许phase结束objection.drop_objection(this, "Sequence completed");end
endtask

sequencer:

sequencer通常不需要我们加任何东西进去,因此它和interface一样很好编写,为了避免后续还有某些需求,也可以写一个框架出来。

class op_in_sequencer#(DATA_WIDTH = 4
) extends uvm_sequencer #(op_in_seq_item`PARAM_LIST);`uvm_component_param_utils(op_in_sequencer`PARAM_LIST)//-------------------------------------------------------//Methods//-------------------------------------------------------//Standard UVM Methodsextern function new(string name = "op_in_sequencer,uvm_component parent = null);endclassfunction op_in_sequencer::new(string name = "op_in_sequencer,uvm_component parent = null);super.new(name,parent)
endfunction: new

agent:

agent的编写相信读者已经很熟悉了,其内部也只是单纯的例化一下driver , monitor, sequencer组件,再get一下接口配置连接一下罢了。

每个人的写法都不一定相同,个人的写法是集成和连接外面的内容都放到 agent 的uvm_config_db里。

class op_in_agent #(DATA_WIDTH = 4
) extends uvm_agent;`uvm_component_param_utils(op_in_agent`PARAM_LIST)//virtual op_in_if    vif;     //获取接口的方式是从顶层get到,不是实例化得到,因此我用了注释//Component Membersop_in_sequencer`PARAM_LIST    seqr;op_in_driver`PARAM_LIST       drv;op_in_monitor`PARAM_LIST      mon;uvm_analysis_port#(op_in_seq_item`PARAM_LIST)    analysis_port;op_in_agent_config`PARAM_LIST    agt_cfg;        //获取配置//-----------------------------------------------------------//Methods//-----------------------------------------------------------//Standard UVM Methodsextern function new(string name,uvm_component parent);extern function void build_phase(uvm_phase phase);extern virtual function void connect_phase(uvm_phase phase);extern virtual function void start_of_simulation_phase(uvm_phase phase);endclass: op_in_agentfunction op_in_agent::new(string name, uvm_component parent);super.new(name, parent);`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction: newfunction void op_in_agent::build_phase(uvm_phase phase);super.build_phase(phase);`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)//从上级环境中获取agent_config来使用if(!uvm_config_db#(op_in_agent_config`PARAM_LIST)::get(this, "", "op_in_agent_config", agt_cfg))begin`uvm_error("build_phase",$sformatf("agent config not found"))end//另一种常用做法,从上一级 的env_config里面获取接口到agent_config当中// uvm_config_db#(virtual op_in_if)::get(this, "" ,"op_in_if" , vif);// if(vif == null) begin//   `uvm_fatal("cfg_err",$sformatf(" interface for agent config not found"))// end// agt_cfg.vif = vif;if(agt_cfg.active == UVM_ACTIVE) beginseqr = op_in_sequencer`PARAM_LIST::type_id::create("seqr", this);drv = op_in_sequencer`PARAM_LIST::type_id::create("drv", this);drv.vif = agt_cfg.vif;endmon = op_in_monitor`PARAM_LIST::type_id::create("mon", this);mon.vif = agt_cfg.vif;endfunction: build_phasefunction void op_in_agent::connect_phase(uvm_phase phase);super.connect_phase(phase);`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)if(agt_cfg.active == UVM_ACTIVE) begindrv.seq_item_port.connect(seqr.seq_item_export);endthis.analysis_port = mon.analysis_port;
endfunction: connect_phasefunction void op_in_agent::start_of_simulation_phase(uvm_phase phase);super.start_of_simulation_phase(phase);`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction: start_of_simulation_phase

agent_config : 

里面通常加入一些可配置的变量和接口,用于灵活的配置当前的agent模式和接口。

class op_in_agent_config #(DATA_WIDTH = 4
) extends uvm_object;`uvm_object_param_utils(op_in_agent_config`PARAM_LIST)//Virtual Interfacevirtual op_in_if`PARAM_LIST    vif ;//Is the agent active or  passsiveuvm_active_passive_enum active = UVM_PASSIVE ;     //定义了一个枚举变量active,初始化配置为passive模式。//-------------------------------------------------------//Methods//--------------------------------------------------------//Standard UVM Methodsextern function new(string name = "op_in_agent_config");endclass: op_in_agent_configfunction op_in_agent_config::new(string name = "op_in_agent_config");super.new(name);
endfunction

agent_packet:

agent_packet主要是为了集成而做的,用到的地方只需要import即可。即使当前agent有新文件添加或者代码改动也不会影响所有环境的编译,因为所有的改动都存放于agent_packet里了。

//为了参数化使用,定义了参数列表
`define PARAM_LIST #(        //定义了一个宏  PARAM_LIST ,传递DATA_WIDTHDATA_WIDTH \            //宏定义以 \ 换行
)package op_in_agent_pkg ;        //包定义import uvm_pkg::* ;            //导入基础库`include "op_in_agent_config.svh"`include "op_in_seq_item.svh"`include "op_in_driver.svh"`include "op_in_monitor.svh"`include "op_in_sequencer.svh"`include "op_in_agent.svh"//adapter here`include "op_in_seq_lib.svh" endpackage`include "op_in_if.sv"`undef PARAM_LIST        //取消宏定义,避免污染全局


总结

通用的UVM环境编写有很多种方式,但是思想不变,读者在编写agent时可以主要关注driver ,monitor 和 sequence的编写思想上。


文章转载自:

http://dp8f6QBR.kfLpf.cn
http://k09qUa6K.kfLpf.cn
http://Mmr1WqvQ.kfLpf.cn
http://O48PyrED.kfLpf.cn
http://8umoseH4.kfLpf.cn
http://EXR5mZaf.kfLpf.cn
http://g6o47Bmt.kfLpf.cn
http://SAIKNgZj.kfLpf.cn
http://9t0fpkAC.kfLpf.cn
http://BuPrbhA9.kfLpf.cn
http://1qGB1Gsr.kfLpf.cn
http://VODkTfDc.kfLpf.cn
http://L4qhOtA5.kfLpf.cn
http://tlfqew4V.kfLpf.cn
http://8NdeLyul.kfLpf.cn
http://QU2XoasY.kfLpf.cn
http://z1X31Fd4.kfLpf.cn
http://T4tyPUKi.kfLpf.cn
http://Un66UagK.kfLpf.cn
http://x7RgP9pe.kfLpf.cn
http://sNClLkm2.kfLpf.cn
http://5nC73o8V.kfLpf.cn
http://8zCuTiqN.kfLpf.cn
http://OO07lBxE.kfLpf.cn
http://8uT8Wz0X.kfLpf.cn
http://v8Rx3ml6.kfLpf.cn
http://icsREr5Y.kfLpf.cn
http://XE7WzCvV.kfLpf.cn
http://6Y5EjCeE.kfLpf.cn
http://bjP08gZf.kfLpf.cn
http://www.dtcms.com/a/381177.html

相关文章:

  • Redis 安全机制:从漏洞防御到生产环境加固
  • Linux多线程概念
  • 笛卡尔参数化直线霍夫变换 Hough Transform for lines with cartesian parameterisation
  • 动态代理1
  • 《2025年AI产业发展十大趋势报告》五十三
  • 高系分二,数学与工程基础
  • 9-15、AI大模型数学基础知识手册与记忆宫殿
  • DataCollatorForLanguageModeling 标签解析(92)
  • 系统编程day08-存储映射与共享内存
  • 【Webpack】模块联邦
  • 研发踩坑实录
  • 广东省省考备考(第九十八天9.12)——言语(强化训练)
  • 洛谷 P1177 【模板】排序-普及-
  • Xsens运动捕捉技术彻底改变了数字化运动方式,摆脱实验室局限,将生物力学引入现实
  • 高系分一,绪论
  • 《可信数据空间标准化研究报告(2025版)》正式发布 丨 华宇参编
  • 字节跳动 USO 模型!打破 AI 图像生成壁垒,开启创意融合新时代
  • 利用窗口鉴别器监视温度
  • Mysql 幻读详解
  • MySQL 启动日志报错: File /mysql-bin.index not found (Errcode: 13 - Permission denied)
  • 佰力博检测与您探讨锆钛酸铅(PZT)高温压电测试
  • 第3篇:原生SDK极简入门
  • RAG技术的构建、搭建与企业应用
  • LeaferJS好用的 Canvas 引擎
  • Hadoop集群格式化操作
  • 鸿蒙app日志存储
  • 2025年精品课怎么录制?传课目录下载、录制教程、评分标准下载~
  • 项目帮助文档的实现
  • Spring Boot 中 StringRedisTemplate 与 RedisTemplate 的区别与使用陷阱(附 getBean 为何报错
  • 继承相关介绍