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

时间轴网站代码开发公司对物业公司的补贴怎么开票

时间轴网站代码,开发公司对物业公司的补贴怎么开票,东莞建设一个网站,小程序代理是什么意思目录 前言 interface: sequence item : driver: monitor: sequence: 注释: sequencer: agent: agent_config : agent_packet: 总结 前言 这篇文章主要讲述agent中各个组件的模板的通用写法,具体的环境还是要依靠DUT的环境。读者可以细致阅读代码注释以…

目录

前言

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://www.dtcms.com/a/543699.html

相关文章:

  • 企业网站网站建设wordpress 分享
  • 娱乐城网站建设自己做网站翻译服务器 - 添加网站
  • 起飞页怎么做网站爱企查 免费
  • 网上商店的优势和劣势seo基本概念
  • 网站建设目录结构doc杭州钱塘区
  • 长沙市天心区建设局网站常用网站后缀
  • 河南网站建设哪家公司好皮具网站建设策划书
  • 做免费视频网站违法吗做加盟正规网站
  • 青州网站定制数据分析
  • 对比色网站设计网站源码哪个好
  • 吉安公司做网站延安做网站电话
  • 公司做网站 需要准备什么电子设计全国网站建设
  • 上海网站建设的网深圳网络推广公司有哪些
  • 地方门户网站如何宣传莱芜房产网站
  • 网站建设维护费合同范本企业关键词优化专业公司
  • 网站设计与管理教程罗湖网站设计开发
  • 常州建设网站公司哪家好和生活爱辽宁免费下载安装
  • 网站索引量是什么意思欧美网站建设案例
  • 阿里巴巴做网站的电话号码图书馆网站建设研究
  • 石嘴山市建设局网站mvc 网站建设
  • 苏州微网站建设公司哪家好百度网盘电脑版登录入口
  • 网站建设人工费网站怎么做的支付宝
  • 网站建设报价表格哪个网站做的w7系统好
  • 个人主题网站7块钱建购物网站
  • 深圳网站建设 信科网络佛山百度关键词推广
  • xml网站模板深圳外贸公司名单
  • 怎么在网站上面做悬浮广告百度网站怎么提升排名
  • cms网站群电脑版h5制作软件
  • 哪里 教做网站带维护wordpress 图片自述
  • 宁波网站设计相信荣胜网络微信小程序公司