从零开始写RISC-V处理器_第1页
从零开始写RISC-V处理器_第2页
已阅读5页,还剩15页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、RISC-V 处理器从零开始写 RISC-V 处理器前第次听到RISC-V这个词概是两年前,当时觉得它也就是和MIPS这些CPU架构没什么区别,因此也就不以为然了。直到去年,RISC-V这个词开始频繁地出现在微信和其他站上,此时我再也不能动于衷了,于是开始在上搜索有关它的资料,开始知道有SiFive这个站,知道SiFive出了好款RISC-V的开发板。可是最便宜的那块开发板都要700多RMB,最后还是忍痛出了块。由于平时上班较忙,所以玩这块板的时间并不多,也就是晚上下班后和周末玩玩,照着芯册写了个例程在板上跑跑已。再后来发现上已经有如何设计RISC-V处理器的书籍卖了,并且这个处理器是开源的,

2、于是果断买了本来阅读并浏览了它的开源代码,最后表看不懂。从那之后个“从零开始写RISC-V处理器”的想法开始不断地出现在我的脑海。我是很想学习、深研究RISC-V的,但是直以来都没有verilog和FPGA的基础,可以说是CPU设计领域的门外汉,再加上很少业余时间,为此度犹豫不决。但是直觉告诉我已近不能再等了,我决定开始学verilog和FPGA,简单易懂的式写个RISC-V处理器并且把它开源出来,在提的同时希望能帮助到那些想门RISC-V的同学,于是tinyriscv终于在2019年12诞了。tinyriscv是个采三级流线设计,顺序、单发射、单核的32位RISC-V处理器,全部代码都是采v

3、erilog HDL语编写,核设计思想是简单、易懂。绪论2.1 RISC-V是什么RISC,即精简指令集处理器,是相对于X86这种CISC(复杂指令集处理器)来说的。RISC-V中的V是罗马数字,也即阿拉伯数字中的5,就是指第5代RISC。RISC-V是种指令集架构,和ARM、MIPS这些是属于同类东西。RISC-V诞于2010年,最的特点是开源,任何都可以设计RISC-V架构的处理器并且不会有任何版权问题。2.2 既ARM,何RISC-VARM是种很优秀的处理器,这点是可否认的,在RISC处理器中是处于绝对的地位。但是ARM是闭源的,要设计基于ARM的处理器是要交版权费的,或者说要购买ARM

4、的授权,且这授权费是昂贵的。RISC-V的诞并不是偶然的,是必然的,为什么?且由我从以下两领域进说明。先看开源软件领域(或者说是操作系统领域),Windows是闭源的,Linux是开源的,Linux有多成功、对开源软件有多重要的意义,这个不多说了吧。再看机操作系统领域,iOS是闭源的,Android是开源的,Android有多成功,这个也不多说了吧。对于RISC处理器领域,由于有了ARM的闭源,必然就会有另外种开源的RISC处理器。RISC-V之于CPU的意义,就好Linux之于开源软件的意义。或者你会说现在也有好多开源的处理器架构啊,如MIPS等等,为什么偏偏是RISC-V?这个在这我就不细

5、说了,我只想说句:部分能看到的机遇不会是个好的机遇,你懂的。可以说未来年乃更长时间内不会有RISC-V更优秀的开源处理器架构出现。错过RISC-V,你注定要错过个时代。软件篇:浅谈Verilogverilog,确切来说应该是verilog HDL(Hardware Description Language ),从它的名字就可以知道这是种硬件描述语。先它是种语,和C语、C+语样是种编程语,那么verilog描述的是什么硬件呢?描述电阻?描述电容?描述运算放器?都不是,它描述的是数字电路的硬件,如与、门、触发器、锁存器等等。既然是编程语,那定会有它的语法,学过C语的同学再来看verilog得代码,

6、会发现有很多地是相似的。verilog的语法并不难,难的是什么时候该wire类型,什么时候该reg类型,什么时候该assign来描述电路,什么时候该always来描述电路。assign能描述组合逻辑电路,always也能描述组合逻辑电路,两者有什么区别呢?3.1 always描述组合逻辑电路我们知道数字电路有两类型的电路,种是组合逻辑电路,另外种是时序逻辑电路。组合逻辑:输出只是当前输逻辑电平的函数(有延时),与电路的原始状态关。当前电路输信号任何个发改变,输出都将发改变。时序逻辑:输出不仅是当前输电平的函数,还与前电路的状态有关。在verilog中,assign能描述组合逻辑电路,alway

7、s也能描述组合逻辑电路。对于简单的组合逻辑电路的话两者描述起来都较好懂、容易理解,但是旦到了复杂的组合逻辑电路,如果assign描述的话要么是串要么是要好多个assign,不容易弄明。但是always描述起来却是常容易理解的。既然这样,那全部组合逻辑电路都always来描述好了,呵呵,既然assign存在就有它的合理性。always描述组合逻辑电路时要注意避免产锁存器,if和case的分情况要写全。在tinyriscv中了量的always来描述组合逻辑电路,特别是在译码和执阶段。3.2 数字电路设计中的时序问题要分析数字电路中的时序问题,就定要提到以下这个模型。其中对时序影响最的是上图中的组合

8、逻辑电路。所以要避免时序问题,最简单的法减组合逻辑电路的延时。组合逻辑电路的串联级数越多延时就越,实在没办法减串联级数时,可以采流线的式将这些级数触发器隔开。3.3 流线设计要设计处理器的话,流线是绕不开的。当然你也可以抬杠说:”状态机也可以实现处理器啊,不定要流线。”采流线设计式,不但可以提处理器的作频率,还可以提处理器的效率。但是流线并不是越长越好,流线越长要使的资源就越多、积就越。在设计款处理器之前,先要确定好所设计的处理器要达到什么样的性能(或者说主频最是多少),所使的资源的上限是多少,功耗范围是多少。如果味地追求性能不考虑资源和功耗的话,那么所设计出来的处理器估计就只能来玩玩,或者做

9、做学术研究。tinyriscv采的是三级流线,即取指、译码和执,设计的标就是要对标ARM的Cortex-M3系列处理器。3.4 代码风格代码风格其实并没有种标准,但是并不代表代码风格不重要。好的代码风格可以让别看你的代码时有种赏悦的感觉。哪怕代码只是写给看,也定要养成好的代码风格的习惯。tinyriscv的代码风格在很程度上沿了写C语代码所采的风格。下介绍tinyriscv的些主要的代码风格。(1)缩进统使4个空格。(2)if语句不管if语句下有多少语句,if下的语句都由beginend包起来,并且begin在if的最后,如下所:(3)case语句对于每个分情况,不管有多少语句,都由begin

10、end包起来,如下所:(4)always语句always语句后跟begin,如下所:(5)其他=、=、=、+、-、*、/、等符号左右各有个空格。,和:符号后有个空格。对于模块的输信号,不省略wire关键字。每个件的最后留空。if、case、always后都有个空格。硬件篇4.1 tinyriscv整体框架硬件篇主要介绍tinyriscv的verilog代码设计。tinyriscv整体框架如图2-1所。图2-1 tinyriscv整体框架可见前tinyriscv已经不仅仅是个内核了,是个型的SOC,包含些简单的外设,如timer、uart_tx等。tinyriscv SOC输输出信号有两部分,部

11、分是系统时钟clk和复位信号rst,另部分是JTAG调试信号,TCK、TMS、TDI和TDO。上图中的框表个个模块,框的字表模块的名字,箭头则表模块与模块之间的的输输出关系。下简单介绍每个模块的主要作。jtag_top:调试模块的顶层模块,主要有三类型的信号,第种是读写内存的信号,第种是读写寄存器的信号,第三种是控制信号,如复位MCU,暂停MCU等。pc_reg:PC寄存器模块,于产PC寄存器的值,该值会被作指令存储器的地址信号。if_id:取指到译码之间的模块,于将指令存储器输出的指令打拍后送到译码模块。id:译码模块,纯组合逻辑电路,根据if_id模块送进来的指令进译码。当译码出具体的指令

12、(如add指令)后,产是否写寄存器信号,读寄存器信号等。由于寄存器采的是异步读式,因此只要送出读寄存器信号后,会马上得到对应的寄存器数据,这个数据会和写寄存器信号起送到id_ex模块。id_ex:译码到执之间的模块,于将是否写寄存器的信号和寄存器数据打拍后送到执模块。ex:执模块,纯组合逻辑电路,根据具体的指令进相应的操作,如add指令就执加法操作等。此外,如果是lw等访存指令的话,则会进读内存操作,读内存也是采异步读式。最后将是否需要写寄存器、写寄存器地址,写寄存器数据信号送给regs模块,将是否需要写内存、写内存地址、写内存数据信号送给rib总线,由总线来分配访问的模块。div:除法模块,

13、采试商法实现,因此少需要32个时钟才能完成次除法操作。ctrl:控制模块,产暂停流线、跳转等控制信号。clint:核本地中断模块,对输的中断请求信号进总裁,产最终的中断信号。rom:程序存储器模块,于存储程序(bin)件。ram:数据存储器模块,于存储程序中的数据。timer:定时器模块,于计时和产定时中断信号。前持RTOS时需要到该定时器。uart_tx:串发送模块,主要于调试打印。gpio:简单的IO模块,主要于点灯调试。spi:前只有master,于访问spi从机,如spinorflash。4.2 寄存器PC寄存器模块所在的源件:rtl/core/pc_reg.vPC寄存器模块的输输出信

14、号如下表所:PC寄存器模块代码较简单,直接贴出来:第3,PC寄存器的值恢复到原始值(复位后的值)有两种式,第种不说了,就是复位信号有效。第种是收到jtag模块发过来的复位信号。PC寄存器复位后的值为CpuResetAddr,即32h0,可以通过改变CpuResetAddr的值来改变PC寄存器的复位值。第6,判断跳转标志是否有效,如果有效则直接将PC寄存器的值设置为jump_addr_i的值。因此可以知道,所谓的跳转就是改变PC寄存器的值,从使CPU从该跳转地址开始取指。第9,判断暂停标志是否于等于Hold_Pc,该值为3b001。如果是,则保持PC寄存器的值不变。这可能会有疑问,为什么Hold

15、_Pc的值不是个1bit的信号。因为这个暂停标志还会被if_id和id_ex模块使,如果仅仅需要暂停PC寄存器的话,那么if_id模块和id_ex模块是不需要暂停的。当需要暂停if_id模块时,PC寄存器也会同时被暂停。当需要暂停id_ex模块时,那么整条流线都会被暂停。第13,将PC寄存器的值加4。在这可以知道,tinyriscv的取指地址是4字节对齐的,每条指令都是32位的。4.3 通寄存器通寄存器模块所在的源件:rtl/core/regs.v共有32个通寄存器x0 x31,其中寄存器x0是只读寄存器并且其值固定为0。通寄存器的输输出信号如下表所:注意,这的寄存器1不是指x1寄存器,寄存器

16、2也不是指x2寄存器。是指条指令涉及到的两个寄存器(源寄存器1和源寄存器2)。条指令可能会同时读取两个寄存器的值,所以有两个读端。因为jtag模块也会进寄存器的读操作,所以共有三个读端。读寄存器操作来译码模块,并且读出来的寄存器数据也会返回给译码模块。写寄存器操作来执模块。先看读操作的代码,如下:可以看到两个寄存器的读操作乎是样的。因此在这只解析读寄存器1那部分代码。第5,如果是读寄存器0(x0),那么直接返回0就可以了。第8,这涉及到数据相关问题。由于流线的原因,当前指令处于执阶段的时候,下条指令则处于译码阶段。由于执阶段不会写寄存器,是在下个时钟到来时才会进寄存器写操作,如果译码阶段的指令

17、需要上条指令的结果,那么此时读到的寄存器的值是错误的。如下这两条指令:第条指令依赖于第条指令的结果。为了解决这个数据相关的问题就有了第89的操作,即如果读寄存器等于写寄存器,则直接将要写的值返回给读操作。第11,如果没有数据相关,则返回要读的寄存器的值。下看写寄存器操作,代码如下:第56,如果执模块写使能并且要写的寄存器不是x0寄存器,则将要写的值写到对应的寄存器。第78,jtag模块的写操作。CSR寄存器模块(csr_reg.v)和通寄存器模块的读、写操作是类似的,这就不重复了。4.4 取指前tinyriscv所有外设(包括rom和ram)、寄存器的读取都是与时钟关的,或者说所有外设、寄存器

18、的读取采的是组合逻辑的式。这点常重要!tinyriscv并没有具体的取指模块和代码。PC寄存器模块的输出pc_o会连接到外设rom模块的地址输,由于rom的读取是组合逻辑,因此每个时钟上升沿到来之前(时序是满要求的),从rom输出的指令已经稳定在if_id模块的输,当时钟上升沿到来时指令就会输出到id模块。取到的指令和指令地址会输到if_id模块(if_id.v),if_id模块是个时序电路,作是将输的信号打拍后再输出到译码(id.v)模块。4.5译码译码模块所在的源件:rtl/core/id.v译码(id)模块是个纯组合逻辑电路,主要作有以下点:1.根据指令内容,解析出当前具体是哪条指令(如

19、add指令)。2.根据具体的指令,确定当前指令涉及的寄存器。如读寄存器是个还是两个,是否需要写寄存器以及写哪个寄存器。3.访问通寄存器,得到要读的寄存器的值。译码模块的输输出信号如下表所:以add指令为例来说明如何译码。下图是add指令的编码格式:可知,add指令被编码成6部分内容。通过第1、4、6这三部分可以唯确定当前指令是否是add指令。知道是add指令之后,就可以知道add指令需要读两个通寄存器(rs1和rs2)和写个通寄存器(rd)。下看具体的代码:第1,opcode就是指令编码中的第6部分内容。第3,INST_TYPE_R_M的值为7b0110011。第4,funct7是指指令编码中

20、的第1部分内容。第5,funct3是指指令编码中的第4部分内容。第6,到了这,第1、4、6这三部分已经译码完毕,已经可以确定当前指令是add指令了。第7,设置写寄存器标志为1,表执模块结束后的下个时钟需要写寄存器。第8,设置写寄存器地址为rd,rd的值为指令编码的第5部分内容。第9,设置读寄存器1的地址为rs1,rs1的值为指令编码的第3部分内容。第10,设置读寄存器2的地址为rs2,rs2的值为指令编码的第2部分内容。其他指令的译码过程是类似的,这就不重复了。译码模块看起来代码很多,但是部分代码都是类似的。译码模块还有个作是当指令为加载内存指令(如lw等)时,向总线发出请求访问内存的信号。这

21、部分内容将在总线节再分析。译码模块的输出会送到id_ex模块(id_ex.v)的输,id_ex模块是个时序电路,作是将输的信号打拍后再输出到执模块(ex.v)。4.6 执执模块所在的源件:rtl/core/ex.v执(ex)模块是个纯组合逻辑电路,主要作有以下点:1.根据当前是什么指令执对应的操作,如add指令,则将寄存器1的值和寄存器2的值相加。2.如果是内存加载指令,则读取对应地址的内存数据。3.如果是跳转指令,则发出跳转信号。执模块的输输出信号如下表所:下以add指令为例说明,add指令的作就是将寄存器1的值和寄存器2的值相加,最后将结果写的寄存器。代码如下:第24,译码操作。第5,对a

22、dd或sub指令进处理。第612,当前指令不涉及到的操作(如跳转、写内存等)需要将其置回默认值。第13,指令编码中的第30位区分是add指令还是sub指令。0表add指令,1表sub指令。第14,执加法操作。第16,执减法操作。其他指令的执是类似的,需要注意的是没有涉及的信号要将其置为默认值,if和case情况要写全,避免产锁存器。下以beq指令说明跳转指令的执。beq指令的编码如下:beq指令的作就是当寄存器1的值和寄存器2的值相等时发跳转,跳转的的地址为当前指令的地址加上符号扩展的imm的值。具体代码如下:第24,译码出beq指令。第510,没有涉及的信号置为默认值。第11,判断寄存器1的

23、值是否等于寄存器2的值。第12,跳转使能,即发跳转。第13,计算出跳转的的地址。第15、16,不发跳转。其他跳转指令的执是类似的,这就不再重复了。4.7 访存由于tinyriscv只有三级流线,因此没有访存这个阶段,访存的操作放在了执模块中。具体是这样的,在译码阶段如果识别出是内存访问指令(lb、lh、lw、lbu、lhu、sb、sh、sw),则向总线发出内存访问请求,具体代码(位于id.v)如下:第24,译码出内存加载指令,lb、lh、lw、lbu、lhu。第5,需要读寄存器1。第6,不需要读寄存器2。第7,写的寄存器使能。第8,写的寄存器的地址,即写哪个通寄存器。第9,发出访问内存请求。第

24、1921,译码出内存存储指令,sb、sw、sh。第22,需要读寄存器1。第23,需要读寄存器2。第24,不需要写的寄存器。第26,发出访问内存请求。问题来了,为什么在取指阶段发出内存访问请求?这跟总线的设计是相关的,这先不具体介绍总线的设计,只需要知道如果需要访问内存,则需要提前个时钟向总线发出请求。在译码阶段向总线发出内存访问请求后,在执阶段就会得到对应的内存数据。下看执阶段的内存加载操作,以lb指令为例,lb指令的作是访问内存中的某个字节,代码(位于ex.v)如下:第24,译码出lb指令。第510,将没有涉及的信号置为默认值。第11,得到访存的地址。第12,由于访问内存的地址必须是4字节对

25、齐的,因此这的mem_raddr_index的含义就是32位内存数据(4个字节)中的哪个字节,2b00表第0个字节,即最低字节,2b01表第1个字节,2b10表第2个字节,2b11表第3个字节,即最字节。4.8 回写第14、17、20、23,写寄存器数据。由于tinyriscv只有三级流线,因此也没有回写(write back,或者说写回)这个阶段,在执阶段结束后的下个时钟上升沿就会把数据写回寄存器或者内存。需要注意的是,在执阶段,判断如果是内存存储指令(sb、sh、sw),则向总线发出访问内存请求。对于内存加载(lb、lh、lw、lbu、lhu)指令是不需要的。因为内存存储指令既需要加载内存

26、数据需要往内存存储数据。第24,译码出sb指令。第58,将没有涉及的信号置为默认值。第9,写内存使能。第10,发出访问内存请求。第11,内存写地址。第12,内存读地址,读地址和写地址是样的。第13,mem_waddr_index的含义就是写32位内存数据中的哪个字节。第15、18、21、24,写内存数据。sb指令只改变读出来的32位内存数据中对应的字节,其他3个字节的数据保持不变,然后写回到内存中。4.8 跳转和流线暂停跳转就是改变PC寄存器的值。因为跳转与否需要在执阶段才知道,所以当需要跳转时,则需要暂停流线(正确来说是冲刷流线。流线是不可以暂停的,除时钟不跑了)。那怎么暂停流线呢?或者说怎

27、么实现流线冲刷呢?tinyriscv的流线结构如下图所。其中长形表的是时序逻辑电路,云状型表的是组合逻辑电路。在执阶段,当判断需要发跳转时,发出跳转信号和跳转地址给ctrl(ctrl.v)模块。ctrl模块判断跳转信号有效后会给pc_reg、if_id和id_ex模块发出流线暂停信号,并且还会给pc_reg模块发出跳转地址。在时钟上升沿到来时,if_id和id_ex模块如果检测到流线暂停信号有效则送出NOP指令,从使得整条流线(译码阶段、执阶段)流淌的都是NOP指令,已经取出的指令就会效,这就是流线冲刷机制。下看ctrl.v模块是怎么设计的。ctrl.v的输输出信号如下表所:可知,暂停信号来多

28、个模块。对于跳转(跳转包含暂停流线操作),是要冲刷整条流线的,因为跳转后流线上其他阶段的其他操作是效的。对于其他模块的暂停信号,种最简单的设计就是也冲刷整条流线,但是这样的话MCU的效率就会低些。另种设计就是根据不同的暂停信号,暂停不同的流线阶段。如对于总线请求的暂停只需要暂停PC寄存器这阶段就可以了,让流线上的其他阶段继续作。看ctrl.v的代码:第36,复位时赋默认值。第8,输出跳转地址直接等于输跳转地址。第9,输出跳转标志直接等于输跳转标志。第11,默认不暂停流线。第13、14,对于跳转操作、来执阶段的暂停、来中断模块的暂停则暂停整条流线。第1618,对于总线暂停,只需要暂停PC寄存器,

29、让译码和执阶段继续运。第1921,对于jtag模块暂停,则暂停整条流线。跳转时只需要暂停流线个时钟周期,但是如果是多周期指令(如除法指令),则需要暂停流线多个时钟周期。4.9 总线设想下个没有总线的SOC,处理器核与外设之间的连接是怎样的。可能会如下图所:可见,处理器核core直接与每个外设进交互。假设个外设有条地址总线和条数据总线,总共有N个外设,那么处理器核就有N条地址总线和N条数据总线,且每增加个外设就要修改(改动还不)core的代码。有了总线之后(见本章开头的图2_1),处理器核只需要条地址总线和条数据总线,简化了处理器核与外设之间的连接。前已经有不少成熟、标准的总线,如AMBA、wi

30、shbone、AXI等。设计CPU时可以直接使其中某种,以节省开发时间。但是为了追求简单,tinyriscv并没有使这些总线,是主设计了种名为RIB(RISC-V Internal Bus)的总线。RIB总线持多主多从连接,但是同时刻只持主从通信。RIB总线上的各个主设备之间采固定优先级仲裁机制。RIB总线模块所在的源件:rtl/core/rib.vRIB总线模块的输输出信号如下表所(由于各个主、从之间的信号是类似的,所以这只列出其中个主和个从的信号):RIB总线本质上是个多路选择器,从多个主设备中选择其中个来访问对应的从设备。RIB总线地址的最4位决定要访问的是哪个从设备,因此最多持16个从

31、设备。仲裁式采的类似状态机的式来实现,代码如下所:第3,主设备请求信号的组合。第713,切换主设备操作,默认是授权给主设备1的,即取指模块。从这可以知道,从发出总线访问请求后,需要个时钟周期才能完成切换。第1866,通过组合逻辑电路来实现优先级仲裁。第20,默认授权给主设备1。第2435,这是已经授权给主设备0的情况。第25、28、31,分别对应主设备0、主设备2和主设备1的请求,通过if、else语句来实现优先级。第27、30,主设备0和主设备2的请求需要暂停流线,这只需要暂停PC阶段,让译码和执阶段继续执。第3647,这是已经授权给主设备1的情况,和第2435的操作是类似的。第4859,这

32、是已经授权给主设备2的情况,和第2435的操作是类似的。注意:RIB总线上不同的主设备切换是需要个时钟周期的,因此如果想要在执阶段读取到外设的数据,则需要在译码阶段就发出总线访问请求。4.10 中断中断(中断返回)本质上也是种跳转,只不过还需要附加些读写CSR寄存器的操作。RISC-V中断分为两种类型,种是同步中断,即ECALL、EBREAK等指令所产的中断,另种是异步中断,即GPIO、UART等外设产的中断。对于中断模块设计,种简单的法就是当检测到中断(中断返回)信号时,先暂停整条流线,设置跳转地址为中断地址,然后读、写必要的CSR寄存器(mstatus、mepc、mcause等),等读写完这些CSR寄存器后取消流线暂停,这样处理器就可以从中断地址开始取指,进中断服务程序。下看tinyriscv的中断是如何设计的。中断模块所在件:rtl/core/clint.v输输出信号列表如下:先看中断模块是怎样判断有中断信号产的,如下代码:第34,复位后的状态,默认没有中断要处理。第67,判断当前指令是否是ECALL或者EBREAK指令,如果是则设置中断状态为S_INT_SYNC_ASSERT,表有同步中断要处理。第89,判断是否有外设中断信号产,如果是则设置中断状态为S_

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论