一个牛人的Systemverilog总结.docx

上传人:小飞机 文档编号:3190372 上传时间:2023-03-11 格式:DOCX 页数:27 大小:51.65KB
返回 下载 相关 举报
一个牛人的Systemverilog总结.docx_第1页
第1页 / 共27页
一个牛人的Systemverilog总结.docx_第2页
第2页 / 共27页
一个牛人的Systemverilog总结.docx_第3页
第3页 / 共27页
一个牛人的Systemverilog总结.docx_第4页
第4页 / 共27页
一个牛人的Systemverilog总结.docx_第5页
第5页 / 共27页
亲,该文档总共27页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

《一个牛人的Systemverilog总结.docx》由会员分享,可在线阅读,更多相关《一个牛人的Systemverilog总结.docx(27页珍藏版)》请在三一办公上搜索。

1、一个牛人的Systemverilog总结Systemverilog 数据类型 l 合并数组和非合并数组 1)合并数组: 存储方式是连续的,中间没有闲置空间。 例如,32bit的寄存器,可以看成是4个8bit的数据,或者也可以看成是1个32bit的数据。 表示方法: 数组大小和位,必须在变量名前指定,数组大小必须是 Bit3:0 7:0 bytes ; 2)二维数组和合并数组识别: 合并数组: bit 3:0 7:0 arrys; 大小在变量名前面放得,且降序 二维数组: int arrays0:7 0:3 ; 大小在变量名后面放得,可降序可升序 位宽在变量名前面,用于识别合并和非合并数组,位宽

2、在后面,用于识别数组中元素个数。 3)非合并数组 一般仿真器存放数组元素时使用32bit的字边界,byte、shortint、int都放在一个字中。 非合并数组:字的地位存放变量,高位不用。 表示方法: Bit 7:0 bytes; 4)合并数组和非合并数组的选择 当需要以字节或字为单位对存储单元操作。 当需要等待数组中变化的,则必须使用合并数组。例如测试平台需要通过存储器数据的变化来唤醒,需要用到,只能用于标量或者合并数组。 Bit3:0 7:0 barray3 ; 表示合并数组,合并数组中有3个元素,每个元素时8bit,4个元素可以组成合并数组 可以使用barry0作敏感信号。 l 动态数

3、组 随机事物不确定大小。 使用方法:数组在开始是空的,同时使用new来分配空间,在newn指定元素的个数。 Int dyn; Dyn = new5; /分配5个元素空间 Dyn.delete ; /释放空间 l 队列 在队列中增加或删除元素比较方便。 l 关联数组 当你需要建立一个超大容量的数组。关联数组,存放稀疏矩阵中的值。 表示方法: 采用在方括号中放置数据类型的形式声明: Bit63:0 assocbit63:0; l 常量: 1)Verilog 推荐使用文本宏。 好处:全局作用范围,且可以用于位段或类型定义 缺点:当需要局部常量时,可能引起冲突。 2)Parameter 作用范围仅限于

4、单个module 3)Systemverilog: 参数可以在多个模块里共同使用,可以用typedef 代替单调乏味的宏。 过程语句 l 可以在for循环中定义变量,作用范围仅在循环内部 for(int i=0;i10;i+) arrayi =i; l 任务、函数及void函数 1)区别: Verilog中task 和function最重要的区别是:task可以消耗时间而函数不能。函数中不能使用#100的延时或的阻塞语句,也不能调用任务; Systemverilog中函数可以调用任务,但只能在fork joinnone生成的线程中。 2)使用: 如果有一个不消耗时间的systemverilog

5、任务,应该把它定义成void函数;这样它可以被任何函数或任务调用。 从最大灵活性角度考虑,所有用于调用的子程序都应该被定义成函数而非任务,以便被任何其它任务或函数调用。 l 类静态变量 作用: 1)类的静态变量,可以被这个类的对象实例所共享。 当你想使用全局变量的时候,应该先想到创建一个类的静态变量 静态变量在声明的时候初始化。 2) 类的每一个实例都需要从同一个对象获取信息。 l 静态方法 作用: 当静态变量很多的时候,操作它们的代码是一个很大的程序,可以用在类中创建一个静态方法读写静态变量,但是静态方法不能读写非静态变量。 l ref高级的参数类型 Ref 参数传递为引用而不是复制。Ref

6、比 input 、output、inout更好用。 Function void print_checksum(const ref bit 31:0 a ); 1) 也可以不用ref进行数组参数传递,这时数组会被复制到堆栈区,代价很高。 2) 用带ref 进行数组参数传递,仅仅是引用,不需要复制;向子程序传递数组时,应尽量使用ref以获得最佳性能,如果不希望子程序改变数组的值,可以使用const ref。 3) Ref参数,用ref 传递变量;可以在任务里修改变量而且,修改结果对调用它的函数可见,相对于指针的功能。 l Return语句 增加了return语句。Task任务由于发现了错误而需要提

7、前返回,如果不这样,那么任务中剩下的语句就必须被放到一个else条件语句中。体会下 Task load_array(int len. Ref int array ); If(len0) begin $display(Bad len); Returun; /任务中其它代码 endtask l 局部数据存储 automatic作用 Verilog中由于任务中局部变量会使静态存储区,当在多个地方调用同一个任务时,不同线程之间会窜用这些局部变量。 Systemverilog中,module和program块中,缺省使用静态存储;如果想使用自动存储,需加入automatic关键词。 测试平台 l Int

8、erface 背景 : 一个信号可能连接几个设计层次,如果增加一个信号,必须在多个文件中定义和连接。接口可以解决这些问题。 好处: 如果希望在接口中增加一个信号,不需要改变其他模块,如TOP模块。 使用方法: 接口中去掉信号的方向类型; DUT 和测试平台中,信号列表中采用接口名,例化一个名字 注意: 因为去掉了方向类型,接口中不需要考虑方向信号,简单的接口,可以看做 是一组双向信号的集合。这些信号使用logic类型d1 。 双向信号为何可以使用logic呢? 这里的双向,只是概念上的双向,不想verilog中databus多驱动的双向。 双向信号如何做接口? 仲裁器的简单接口 Interfa

9、ce arb_if( input bit clk); Logic 1:0 grant,request; Logic rst; Endinterface DUT 使用接口: Module arb(arb_if arbif); Always (posedge arbif.clk or negedge arbif.rst) endmodule DUT 不采用接口,测试平台中使用接口 DUT 中源代码不需要修改,只需要再top中,将接口连接到端口上。 Module top; Bit clk; Always #2 clk =clk; Arb_if arbif(clk); Arb_port al(.gra

10、nt(arbif.grant), .request(arbif.grant), .rst(arbif.rst), .clk(arbif.clk) ); Test t1(arbif); Endmodule l Modport 背景: 端口的连接方式包含了方向信息,编译器依次来检查连续错误;接口使用无信号的连接方式。Modport将接口中信号分组并指定方向。 例子: l 在总线设计中使用modport 并非接口中每个信号都必须连接。Data总线接口中就解决不了,个人觉得? 因为data是一个双驱动 l 时钟块 作用: 一旦定义了时钟块,测试平台就可以采用arbif.cb等待时钟,而不需要描述确切的

11、时钟信号和边沿,即使改变了时钟块中的时钟或边沿,也不需要修改测试代码 应用: 将测试平台中的信号,都放在clocking 中,并指定方向。并且在modprot test; endinterface program test; initial begin mif.cb.data = z; mif.cb; $display(mif.cb.data); /总线中读数据 mif.cb; Mif.cb.data = 8h5a; /驱动总线 mif.cb; Mif.cb.data = z; /释放总线 注: interface 列表中clk 采用的是input bit clk;为什么要用bit? 时钟块

12、clocking cb 中,一般将testbench中需要的信号,方向指定在这里; 而在modprot 指定test信号方向的时候,采用clocking cb。 interface中信号,不一定都用logic,也可采用wire;systemverilog 中如果采用C代码的风格,必须采用logic类型 现在的风格,DUT 没才用clocking cb ,测试平台和DUT的时钟如何统一? l 激励时序 DUT和测试平台之间时序必须密切配合。 l 测试平台和设计间的竞争状态 好的风格: 使用非阻塞赋值可以减少竞争。 systemverilog验证中initial 中都采用= 赋值,而等待延迟采用a

13、rbif.cb等待一个周期来实现。 而verilog中采用的风格时,initial 中采用 =阻塞赋值,沿时可以采用#2,等实现。 因此时钟发生器,只能放在module 中,而不能放在program中 l Program中不能使用always块 测试平台可以使用initial 但不能使用always,使用always 模块不能正常工作。 原因:测试平台的执行过程是进过初始化、驱动和响应等步骤后结束仿真。 如果确实需要一个always块,可以使用initial forever 来完成。比如:在产生时钟时。 类 l 类中static变量 背景: 如果一个变量需要被其他对象所共享,如果没有OPP,就

14、需要创建全局变量,这样会污染全局名字空间,导致你想定义局部变量,但变量对每个人都是可见的。 1)作用: 类中static变量,将被这个类的所有实例所共享,使用范围仅限于这个类。 例:class transaction; Static int count=0; Int id; Endclass Trasaction tr1,tr2; Id不是静态变量,所以每个trasaction对象都有自己的id;count 是静态变量,所有对象只有一个count变量。 如何用? 当你打算创建一个全局变量的时候,首先考虑创建一个类的静态变量。 2)static变量的引用 句柄或类名加: 4) static 变量

15、的初始化 static变量通常在声明时初始化。不能在构造函数中初始化,因为每一个新的对象都会调用构造函数。 l 静态句柄: 背景:当类的每一个对象,都需要从同一个对象中获取信息的时候。如果定义成非静态句柄,则每个对象都会有一份copy,造成内存浪费。 l 静态方法 背景: 当使用更多静态变量的时候,操作他们的代码会很长。 作用: 可以在类中创建一个静态方法用于读写静态变量。 注:systemverilog不允许,静态方法读写非静态变量。 l 类之外的方法 背景:解决类太长的问题。类最好控制在一页内,如果方法很都很长。 l This 背景:如果在类很深的底层作用域,却想引用类一级的对象。在构造函

16、数中最常见。 作用:this指向类一级变量 l 如何做类,类做多大? 上限:类不能太大 当类中存在多处相同的代码,你需要将这段代码做成当前类的一个成员函数或父类的成员函数。 下限:类不能太小 类太小,增加了层次。 方法:如果一个小类只被例化了一次,可以将它合并到父类中去。 l 动态对象 概念区分:方法中修改对象和修改句柄 修改对象将对象的变量重新赋值。 修改句柄在任务中new对象。 1)当你将对象传递给方法 背景:句柄,new后变成对象,在将其作为参数传递给方法。 实质和作用: 传递的是句柄。这个方法可以读取对象中的值;也以改变对象中的值 2)修改标量变量的值 背景:在方法的参数中,前面加re

17、f;。 作用: 方法可以修改变量的值,并将修改的值,传递给主程序。 引申: 方法可以改变对象,即使没有使用ref 修饰句柄。 因为传递的是句柄,句柄是地址。不要将句柄和对象混为一谈,如果传递的是对象,对象是单向的,那方法以外也不能传递回来。可以这样理解吧。 读写对象中的值: 例: Task transmit; Cbbus.rx_data 和ifelse 可产生和case效果类似的语句块,可以用于枚举类型的表达式。 l 双向约束 l 控制多个约束块 作用:可以打开或关闭某个约束 可以使用内建的Handle.constraint.constraint_mode打开或关闭。 l 内嵌约束 背景:很多

18、测试只会在代码的一个地方随机化对象,但是约束越来越复杂时, Systemverilog可以使用randomized with 来增加额外的约束,这和在类里增加的约束是等效的。 l Pre_randomize 和post_randomize函数 有时候需要再调用randomize之前或之后立即执行一些操作。 随机化前:设置类里的一些非随机变量, 随机化后:计算数据的误差矫正值。 l 约束的技巧 1) 约束中使用变量 2) 使用非随机值 如果一套约束在已产生了几乎所有想要的激励向量,但还缺少几种。 可以使用rand_mode把这些变量设置为非随机变量。 l 数组约束 Systemverilog可以

19、用foreach对数组中的每一个元素进行约束。 线程及线程间的通信 l 测试平台使用许多并发执行的线程。测试平台隶属于程序块。 Systemverilog引入两种新的创建线程的方法forkjoin_none和forkjoin_any 1)使用forkjoin_none来产生线程 在调度其内部语句时,父线程继续执行。 2)使用forkjoin_any实现线程同步 在调度块内语句,当第一个语句执行完,父线程才继续执行。 l 动态线程 Systemverilog中可以动态创建线程。 用法: forkjoin_none放在了任务中,而不是包含两个线程。 原因: 主程序中有连个线程:发送和检测线程。但是

20、不能同时启动,发送事物后,才能检测,否则还未产生数据,就开始检测;但是检测又不能阻塞下一次发送事物的线程。所以forkjoin_none 放在了检测task 任务中, 例:测试平台产生随机事物并发送到DUT中,DUT把事物返回到测试平台。测试平台必须等到事物完成,但同时不希望停止随机事物的发送。 Program automatic test; Task check_trans(Transaction tr); Fork Begin Wait(bus.cb.addr = tr.addr); End Join_noe Endtask Initial begin Repreat(10) begin

21、Tr= new; Assert.(tr.randomize); /把事物发送到DUT中 Transmit; /等待DUT的回复 Check_trans(tr); End #100; End endprogram l 并发线程中务必使用自动变量来保持数值。 l #0 延迟,使得当前线程必须等到forkjoin_none语句中产生的线程执行完后,才得以运行。 l 停止线程 1) 停止单个线程 使用fork .join_any 后加disable。 3)停止多个线程 Disable fork 能停止从当前线程中衍生出来得所有子线程。 应该使用fork .join 把目标代码包含起来,以限制Disab

22、le fork的作用范围。 l 事件 背景: Verilog中当一个线程在一个事件上发生阻塞的同时,正好另一个线程触发了这个事件,则竞争就出现了。如果触发线程先于阻塞线程,则触发无效。 解决方法: Systemverilog 引入了triggered函数,用于检测某个事件是否已被触发过,包括正在触发。线程可以等待这个结果,而不用在操作符上阻塞。 例子: Event e1,e2; Initial begin -e1; e2; End Initial begin -e2; e1; End 上面的代码,假设先执行第一个块,再执行第二个块。第一个块会阻塞在e2,直到e2触发,再运行;在执行第二个块时,

23、会阻塞在e1,但是e1已经触发 解决方法:用wait来代替阻塞el,如果先触发,也可以执行。 l 等待多个事件 最好的办法是:采用线程计数器来等待多个线程。 l 旗语 Get可以获取一个或多个钥匙,put可以返回一个或多个钥匙。Try_get获取一个旗语而不被阻塞。 l 信箱 背景:如何在两个线程中传递信息?考虑发生器需要创建很多事物并传递给驱动器的情况。 问题:如果使用发生器的线程去调用驱动器的任务。这样,发生器需要知道驱动器的层次化路径,降低了代码的可重用性;还迫使发生器和驱动器同一速率运行,当一个发生器需控制多个驱动器时会发生同步问题。 解决办法:把驱动器和发生器当成各个处理事物的对象,

24、之间通过信道交换数据。信道允许驱动器和发生器异步操作;引入问题:你可能倾向于仅仅使用一个共享的数据或队列,但这样,编写实现线程间的读写和阻塞代码会很困难。解决办法:可以使用systemverilog中的信箱。把信箱看出一个具有源端和收端的FIFO. 操作: 1)信箱的容量可以指定,new,size限制信箱中的条目,size为0,或没指定,则信箱是无限大。 2)Put放数据,get可以移出数据。Peek可以获取信箱中数据的copy而不移出。 3)信箱中可以放句柄,而不是对象。 漏洞:在循环外只创建一个对象,然后使用循环对对象随机化,信箱中是句柄,最终得到的是一个含有多个句柄的信箱,多个句柄都指向

25、同一个对象。 解决办法:在循环中,创建多个对象。 l 异步线程间使用信箱 背景: 很多情况下,由信箱连接的两个线程应该步调一致,这样生产方才不至于跑到消费方前。好处:最好层的generator需要等待低层的数据发完后才能结束。测试平台能精确知道所有激励发出去的时间。 两种情况 两个线程同步,需要额外的握手信号。否则,出现生产方运行到结束,消费方还启动。 1) 信箱容量为1,两个线程同步 因阻塞,连个线程不需要握手信箱 3) 容量不为1,线程间同步 需要使用握手信号,以使producer不超前于consumer;如果consumer超前于prodecer会阻塞。 解决办法 1) 使用定容信箱和p

26、eek实现线程同步: 消费方:consumer 使用信箱方法peek获取信箱里的数据的copy而不将其移出,当consumer处理完数据后,便使用get移出数据。 特点:信箱容量定义为1,不需要握手信号。 Calss consumer Repeatbegin Mbx.peek(i); $display(consumer:after get( ),i); Mbx.get(i); End endcalss 如果直接使用get替代peek,那么事务会被立刻移出,这样可能会在consumer完成事务前,producer生成新的数据。 - 2)使用信箱和事件实现线程同步 使用边沿敏感的阻塞语句hands

27、hake 代替电平触发wait(handshake.triggered)。 因为:线程中任务run使用循环,事件阻塞只能使用handshake。 局限:如果遇到producer线程的阻塞和consumer线程的触发同时发生,则可能出现次序上的问题。 3)使用两个信箱实现线程同步 使用另一个信箱把consumer的完成信息发回给producer。 目的:在producer线程中,处理完事物后,用一个get来阻塞。 特点:信箱容量大于1. Maibox mbx,rtn; Class prodecer For(int i=0; i4;i+) begin . Mbx.put(i); Rtn.get(i

28、); End Endclass Class consumer Repeat(3) begin . Mbx.get(i); Rtn.put(-i); End Endclass 说明:信箱的构造函数中Mbx =new;Rtn =new,信箱容量为无穷大。如何实现同步? 虽然信箱容量为无穷大,producer线程发完一个数据后遇到get会阻塞,不能放入第二个数据;等到consumer得到第一个数据并且处理完后,通过另一个信箱返回一个数据,producer才继续放第二个数据。 因为get得到数据后,将信箱中数据取出。表象:信箱容量定义为无穷大,但是实际上也是producer放一个数据,consumer

29、取一个数据;然后producer再放第二个数据,依次类推。 这样确保producer不会超前于consumer线程,而将数据都写入信箱。 4)其他的同步技术 通过变量或旗语阻塞也可以实现握手。事件是最简单的结构,其次是通过变量阻塞。旗语相当于第2个信箱,但是没有交换信息。Systemverilog中的信箱比其他技术要差,原因是无法在producer放入第一个事务时,让它阻塞。Producer一直比consumer提前一个事务的时间。 l Wait(handshake.triggered)和handshake 使用范围 1) Wait(handshake.triggered),用于等待一个事件;

30、 2) 循环中等待事件,只能用handshake 3) 两个线程的同步,一般任务run使用循环,所以只能使用handshake。 注意事项: 1) 在循环中,等待事件不能用Wait(handshake.triggered),因为如果事件触发一次,wait语句一直为真,进入不断的循环。下一次循环中,不会阻塞。 2) handshake 如果触发事件,先于等待事件。会等不到事件,因为 OPP的高级编程技巧 l 继承 背景: 为总线事务增加一个错误功能并带可变延时的复杂类。方法如下: 1) 使用合成,即在类中例化另一个类型的类。有时候很难将功能分成独立的部分。如果使用合成,则需要为正确和错误事务分别

31、创建不同的类,正确类的测试平台需要重写以处理错误类的对象。 2)使用扩展类 作用: 当需要增加事务,而对现有的测试代码修改越少越好,。例如增加错误注入功能。 扩展类和类合成区别: 扩展类解决,增加新事务,使用类合成中,大量修改代码的麻烦。 如何使用: 扩展类共享基类的变量和子程序。 1)基本类中的方法,需标记为virtual,这样扩展类中才可以重新定义。扩展类中函数,和基类中函数名一样时,通过supper.函数名,调用基类中函数。Systemverilog中不允许supper.supper.new方式经行多层调用。 2)如果基类构造函数new有参数,那么扩展类,必须有一个构造函数,并在构造函数

32、的第一行调用基类的构造函数。 Class basel Function new; this.var = var; endfunction endclass class extended extends basel function new(input int var); super.new(var); endfunction endclass 3)OPP规则指出:基类的句柄,也可以指向扩展类的对象。 l 蓝图模式 1)背景:一个简单的发生器,通过信箱将数据传递给驱动器。 class generator mailbox gen2drv; transaction tr; function new(

33、input mailbox gen2drv) this.gen2drv = gen2drv; endfunction task run; forever begin tr = new; assert(tr.randmize); gen2drv.put(tr); /mail.put(x) end endtask endclass 存在问题:这个例子在循环内部创建事务对象,而不是在循环外部,避免了测试平台常见的错误。New放在循环外部,错误原因是,mailbox中放入的是句柄,而不能是对象,所有的句柄都指向同一个对象。任务Run创建了一个事物并立即随机化,意味着事务使用了默认的所有约束。要修改,必

34、须要修改transaction类。(2)无法使用扩展 解决办法:将tr的创建和初始化分开,使用蓝图模式。 另一个问题:如果简单的把创建和初始化分开,而放在循环外部,而避免测试平台错误,如何解决?蓝图模式如何解决 2)蓝图模式概念: 首先构建一个对象蓝图,然后修改它的约束,甚至可以用扩展对象替换它,随机化这个蓝图时,就得到想赋予的随机值;然后复制这个对象,将copy发给下游。 蓝图:是一个钩子,允许你改变发生器类的行为而无需修改其类代码。蓝图对象在一个地方构建,在另一个地方使用 3)P200与P221相对比分析:重要 蓝图模式,也就比new在循环外地generator多了一个copy函数。问题蓝

35、图模式,new在循环外,也只有一个对象,而mailbox中放入的只能是句柄,如何解决常见的平台错误? 因为copy,是对象的复制,而不是句柄的复制。这样蓝图模式只有一个句柄,但是随机化后,copy,相当于再循环中创建了许多对象。而测试平台常见错误的本质是,只创建了一个对象。这样就避免了问题。 蓝图模式下,因为只有一个ID号,那么任务run循环中,下发了许多数据,这些只有一个ID号了? 因为copy是对象的复制,所以在copy中ID号也会增加。下发的每个数据,都有各自的ID号。 l 使用扩展的transaction 为了注入错误,需要将蓝图对象transaction变成Badtransactio

36、n。必须在环境的创建和运行阶段之间完成这个操作。注意:所有的badTr引用都在这一个文件中,这样就不需要改变environment类或generator类。 Env.build; Begin Badtr bad = new; Env.gen.blueprint = bad; End Env.run 目的是:将一个对象取代另一个对象。New后都是对象了,将对象赋值给对象,这是什么写法?不是复制呀?复制本质是将一个句柄指向一个对象。 解释:上述是句柄的复制,将扩展类句柄bad赋值给基类句柄blueprint,这样基类句柄指向扩展类对象,后面的代码调用的时候,就直接指向扩展类bad了,改变了蓝图。

37、l Env.new和nev.build区别 Env.new仅仅new函数 nev.build是将各个模块new,并传达一些参数,通过这些参数将环境的各个模块,连接起来。P213 l $cast 作类型向下转换 背景:基类句柄可以指向扩展类对象,不需要额外的代码;扩展类句柄指向基类对象,一般情况下会出错,但有时候是可以的,前提是基类句柄指向了它的一个扩展类对象。 作用:扩展类句柄指向基类对象时,使用$cast函数。在非法的情况下,不会编译报错,会返回了一个0. $cast做任务使用时,systemverilog会在运行时,检查源对象类型和目的对象类型不匹配,会报错; $cast 做函数使用时,运

38、行时,仍做类型检查,在不匹配时,不会报错,$函数返回0. 前面所述:基类句柄可以指向任何它的扩展类的对象、 1)基类句柄指向扩展类对象出现情况:修改蓝图,不改过多代码,增加功能 Transaction tr; /基类句柄 BadTr bad; /扩展类句柄 Bad = new; Tr = bad; / 基类句柄指向扩展类对象 tr.display; /掉用的是扩展类的方法 2)扩展类句柄指向基类对象出现情况:基类virtual 方法copy函数,它的继承类中copy函数 将基类句柄赋值给扩展类句柄,使扩展类句柄指向基类对象,一般编译器会出错,不能运行,所以非常小心;只有基类句柄指向扩展类对象时,再将扩展类句柄指向基类对象时,不出错。为了检测基类句柄是否指向了扩展对象,并且不让编译器报错,可以使用$cast函数检测。 当把扩展类句柄指向基类对象时,发生什么? Tr= new; Bad = tr; /扩展类句柄指向基类句柄 上述会发生错误,编译不会被通过。因为有些属性在基类中不存在;但是扩展类句柄指向基类句柄不总是

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号