VHDL 编程的一些心得体会

2024-04-12

VHDL 编程的一些心得体会(共3篇)

篇1:VHDL 编程的一些心得体会

VHDL 编程的一些心得体会(转)http://www.dzjs.net/html/EDAjishu/2007/0131/1504.html

VHDL 是由美国国防部为描述电子电路所开发的一种语言,其全称为(Very High Speed Integrated Circuit)Hardware Description Language。与另外一门硬件描述语言 Verilog HDL 相比,VHDL 更善于描述高层的一些设计,包括系统级(算法、数据通路、控制)和行为级(寄存器传输级),而且 VHDL 具有设计重用、大型设计能力、可读性强、易于编译等优点逐渐受到硬件设计者的青睐。但是,VHDL 是一门语法相当严格的语言,易学性差,特别是对于刚开始接触 VHDL 的设计者而言,经常会因某些小细节处理不当导致综合无法通过。为此本文就其中一些比较典型的问题展开探讨,希望对初学者有所帮助,提高学习进度。

一. 关于端口

VHDL 共定义了 5 种类型的端口,分别是 In, Out,Inout, Buffer及 Linkage,实际设计时只会用到前四种。In 和 Out 端口的使用相对简单。这里,我们主要讲述关于 buffer和inout 使用时的注意事项。

与 Out 端口比,Buffer 端口具有回读功能,也即内部反馈,但在设计时最好不要使用 buffer,因为 buffer类型的端口不能连接到其他类型的端口上,无法把包含该类型端口的设计作为子模块元件例化,不利于大型设计和程序的可读性。若设计时需要实现某个输出的回读功能,可以通过增加中间信号作为缓冲,由该信号完成回读功能。

双向端口 Inout 是四种端口类型中最为特殊的一种,最难以学习和掌握,为此专门提供一个简单程序进行阐述,部分程序如下:...„

① DataB<=Din when CE=’1’ and Rd=’0’ else ②(others=>’Z’);

③ Dout<=DataB when CE=’1’ and Rd=’1’ else ④(others=>’1’);„ „

程序中 DataB 为双向端口,编程时应注意的是,当 DataB 作为输出且空闲时,必须将其设为高阻态挂起,即有类似第②行的语句,否则实现后会造成端口死锁。而当 DataB 作为有效输入时,DataB 输出必须处于高阻态,对于该例子中即,当 CE=’1’ and Rd=’1’时,二.信号和变量

常数、信号和变量是 VHDL 中最主要的对象,分别代表一定的物理意义。常数对应于数字电路中的电源或地;信号对应某条硬件连线;变量通常指临时数据的局部存储。信号和变量功能相近,用法上却有很大不同。

表 1 信号与变量主要区别

信号 变量

赋值延迟 至少有△延时 无,立即变化

相关信息 有,可以形成波形 无,只有当前值 进程敏感 是 否 全局性 具有全局性,可存在于多个进程中 只能在某个进程或子程序中有效 相互赋值关系 信号不能给变量赋值 变量可以给信号赋值

对于变量赋值操作无延迟,初学者认为这个特性对 VHDL 设计非常有利,但这只是理论上的。基于以下几点原因,我们建议,编程时还是应以信号为主,尽量减少变量的使用。

(1)变量赋值无延时是针对进程运行而言的,只是一个理想值,对于变量的操作往往被综合成为组合逻辑的形式,而硬件上的组合逻辑必然存在输入到输出延时。当进程内关于变量的操作越多,其组合逻辑就会变得越大越复杂。假设在一个进程内,有关于变量的 3 个 级连操作,其输出延时 分别为 5ns,6ns,7ns,则其最快的时钟只能达到 18ns。相反,采用信号编程,在时钟控制下,往往综合成触发器的形式,特别是对于 FPGA 芯片而言,具有丰富的触发器结构,易形成流水作业,其时钟频率只受控于延时最大的那一级,而不会与变量一样层层累积。假设某个设计为 3 级流水作业,其每一级延时分别为 10ns,11ns,12ns,则其最快时钟可达 12ns。因此,采用信号反而更能提高设计的速度。

(2)由于变量不具备信息的相关性,只有当前值,因此也无法在仿真时观察其波形和状态改变情况,无法对设计的运行情况有效验证,而测试验证工作量往往会占到整个设计 70%~80%的工作量,采用信号则不会存在这类问题。

(3)变量有效范围只能局限在单个进程或子程序中,要想将其值带出与其余进程、子模块之间相互作用,必须借助信号,这在一定程度上会造成代码不够简洁,可读性下降等缺点。

当然,变量也具有其特殊的优点,特别是用来描述一些复杂的算法,如图像处理,多维数组变换等。

三.位(矢量)与逻辑(矢量)

bit 或其矢量形式 bit_vector只有’0’和’1’两种状态,数字电路中也只有’0’和’1’两种逻辑,因此会给初学者一个误区,认为采用位(矢量)则足够设计之用,而不必像std_logic那样出现’X’,’U’,’W’各种状态,增加编程难度。但实际情况却并非如此,以一个最简单 D型触发器设计为例 „ „

① process(clk)② begin

③ if clk’event and clk=’1’ then ④ Q<=D;⑤ end if;

⑥ end process;„ „

实际中 clk 对数据端 D的输入有一定的时间限制,即在 clk 上升沿附近(建立时间和保持时间之内),D必须保持稳定,否则 Q输出会出现亚稳态,如下图所示。

当 clk 和 D时序关系不满足时,由于 bit 只有’0’或’1’,系统只能随机的从’0’和’1’中给 Q 输出,这样的结果显然是不可信的;而采用 std_logic 类型,则时序仿真时会输出为一个’X’,提醒用户建立保持时间存在问题,应重新安排 D和 clk 之间时序关系。

此外,对于双向总线设计(前面已提及)、FPGA/CPLD上电配置等问题,如果没有’Z’,’X’等状态,根本无法进行设计和有效验证。

四.关于进程

进程(Process)是 VHDL 中最为重要的部分,大部分设计都会用到 Process 结构,因此掌握Process 的使用显得尤为重要。以下是初学和使用 Process 经常会出错的例子。

1.多余时钟的引入

在设计时往往会遇到这种情况,需要对外部某个输入信号进行判断,当其出现上跳或下跳沿时,执行相应的操作,而该信号不像正常时钟那样具有固定占空比和周期,而是很随机,需要程序设计判断其上跳沿出现与否。这时,很容易写出如下程序:

① process(Ctl_a)--Ctl_a即为该输入信号 ② begin

③ if Ctl_a’event and Ctl_a=’1’ then ④ „ „;--执行相应操作 ⑤ end if;⑥ end process;

由于出现第③行这类语句,综合工具自动默认 Ctl_a 为时钟,某些 FPGA 更会强行将该输入约束到时钟引脚上。而设计者的初衷只是想将其作为下位机的状态输入以进行判断。上面的程序容易造成多时钟现象,增加设计的难度。解决的办法可以如下,将 Ctl_a 增加一级状态 Ctl_areg 寄存,通过对 Ctl_a 和Ctl_areg 状态判断上跳与否,改正程序如下: ① process(clk)② begin

③ if clk’event and clk=’1’ then ④ Ctl_areg<=Ctl_a;--产生相邻状态

⑤ if Ctl_areg=’0’ and Ctl_a=’1’ then--上跳判断 ⑥ „ „;--执行相应操作 ⑦ end if;⑧ end if;

⑨ end process;

程序中第④行用以产生两个相邻状态,第⑤行对前后状态进行判断是否有上跳现象发生。其中,需注意的是 clk 的时钟频率应明显快于 Ctl_a信号的变化频率,以保证正确采样。

2.输出多驱动

误用 Process经常会引起输出多驱动源的发生,即在两个以上的进程内对同一信号赋值操作。

以下程序就出现了这类情况: ⑴ Proc_a: process(clk)⑵ begin

⑶ if clk’event and clk=’1’ then ⑷ Dout<=Din_A;⑸ end if

⑹ end process;;⑺

⑻ Proc_b:process(sel_en)⑼ begin

⑽ if sel_en=’1’ then ⑾ Dout<=Din_B;⑿ end if;

⒀ end process;

进程 Proc_a 和 Proc_b 中都出现了对 Dout 的赋值语句,设计者原本的想法是,只要合理控制好 clk 和 sel_en 输入,使其不发生冲突,即 clk上升沿时 sel_en 不为’1’;sel_en 为’1’时,不出现 clk 的上升沿,这样 Proc_a,Proc_b 两个进程就不会发生冲突。但综合时,综合工具会将所有可能情况全部罗列进去,包括第⑶行和第⑽行同时成立的情况,此时对于 Dout就有 Din_A和 Din_B 两个输入驱动,Dout 不知接收哪一个,因此该程序无法综合,改正的方法是只要将两个进程合并成一个即可。

由于进程在 VHDL 中的重要性,对此专门做了一个总结如下:(1)一个进程中不允许出现两个时钟沿触发,(Xilinx 公司 CoolRunner 系列 CPLD 支持单个时双钟的双触发沿除外)(2)对同一信号赋值的语句应出现在单个进程内,不要在时钟沿之后加上 else 语句,如 if clk’event and clk=’1’ then-else „ 的结构,现有综合工具支持不了这种特殊的触发器结构

(3)当出现多层 IF语句嵌套时,最好采用 CASE 语句替代,一是减少多层嵌套带来的延时,二来可以增强程序的可读性

(4)顺序语句如 IF语句、CASE 语句、LOOP 语句、变量赋值语句等必须出现在进程、函数或子程序内部,而不能单独出现在进程之外

(5)进程内部是顺序执行的,进程之间是并行运行的;VHDL 中的所有并行语句都可以理解为特殊的进程,只是不以 Process结构出现,其输入信号和判断信号就是隐含的敏感表

五.关于 VHDL 学习中的几点说明

与软件语言相比,VHDL 最重要的特点就在于它的并行运行特性,当设计好的电路上电后,器件内部所有信号将同时并发工作,而不会以软件方式按照程序顺序执行,即使在进程内部也是趋向并行工作的。例如以下程序: ① process(clk)② begin

③ if clk’event and clk=’1’ then ④ <=;⑤ <=;

⑥ end if;;

⑦ end process;

综合的结果两个独立的 D 型触发器,虽然进程内部应按顺序执行,但是硬件实现后,只要采样到时钟上升沿,和 状态会同时翻转,而不会先执行 的变化,然后才会去执行 的转变。因此,VHDL 学习过程中,应加强硬件概念的理解,没有硬件概念或是硬件概念不强,在设计时,往往会将 VHDL 设计以软件编程的方式来处理,而得出一些不可思议的结果。

作为一门硬件描述语言,VHDL 几乎可以用来描述现有的大型系统数字电路、算法以及其它设计。但是,限于目前综合工具的水平,VHDL 中的许多语法还不能支持,例如:

dout<=din after 5 ns;综合时就无法达到如此精度,因此这条语句主要用来编写测试激励,而很少出现在设计实体中。类似的情况还有很多,目前 VHDL 设计使用的也只是整个标准中的一部分,这也正是VHDL 的“可综合子集”性质,它一定程度上限制了 VHDL 的广泛应用,但是随着综合技术的发展,这种情况会逐渐得以改善,VHDL 也将在各个领域中发挥出愈来愈重要的作用

篇2:VHDL 编程的一些心得体会

VHDL 是由美国国防部为描述电子电路所开发的一种语言,其全称为(Very High Speed Integrated Circuit)Hardware Description Language。与另外一门硬件描述语言 Verilog HDL 相比,VHDL 更善于描述高层的一些设计,包括系统级(算法、数据通路、控制)和行为级(寄存器传输级),而且 VHDL 具有设计重用、大型设计能力、可读性强、易于编译等优点逐渐受到硬件设计者的青睐。但是,VHDL 是一门语法相当严格的语言,易学性差,特别是对于刚开始接触 VHDL 的设计者而言,经常会因某些小细节处理不当导致综合无法通过。为此本文就其中一些比较典型的问题展开探讨,希望对初学者有所帮助,提高学习进度。

一.关于端口

VHDL 共定义了 5 种类型的端口,分别是 In, Out,Inout, Buffer及 Linkage,实际设计时只会用到前四种。In 和 Out 端口的使用相对简单。这里,我们主要讲述关于 buffer和inout 使用时的注意事项。

与 Out 端口比,Buffer 端口具有回读功能,也即内部反馈,但在设计时最好不要使用 buffer,因为 buffer类型的端口不能连接到其他类型的端口上,无法把包含该类型端口的设计作为子模块元件例化,不利于大型设计和程序的可读性。若设计时需要实现某个输出的回读功能,可以通过增加中间信号作为缓冲,由该信号完成回读功能。

双向端口 Inout 是四种端口类型中最为特殊的一种,最难以学习和掌握,为此专门提供一个简单程序进行阐述,部分程序如下:

...„

①DataB<=Din when CE=’1’ and Rd=’0’ else

②(others=>’Z’);

③ Dout<=DataB when CE=’1’ and Rd=’1’ else

④(others=>’1’);

„ „

程序中 DataB 为双向端口,编程时应注意的是,当 DataB 作为输出且空闲时,必须将其设为高阻态挂起,即有类似第②行的语句,否则实现后会造成端口死锁。而当 DataB 作为有效输入时,DataB 输出必须处于高阻态,对于该例子中即,当 CE=’1’ and Rd=’1’时,二.信号和变量

常数、信号和变量是 VHDL 中最主要的对象,分别代表一定的物理意义。常数对应于数字电路中的电源或地;信号对应某条硬件连线;变量通常指临时数据的局部存储。信号和变量功能相近,用法上却有很大不同。

表 1信号与变量主要区别

号变量

赋值延迟至少有△延时无,立即变化

相关信息有,可以形成波形无,只有当前值进程敏

感是否全局性具有全局性,可存在于多个进程中只能在某个进程或子程序中有效相互赋值关系信号不能给变量赋值变量可以给信号赋值

对于变量赋值操作无延迟,初学者认为这个特性对 VHDL 设计非常有利,但这只是理论上的。基于以下几点原因,我们建议,编程时还是应以信号为主,尽量减少变量的使用。

(1)变量赋值无延时是针对进程运行而言的,只是一个理想值,对于变量的操作往往被综合成为组合逻辑的形式,而硬件上的组合逻辑必然存在输入到输出延时。当进程内关于变量的操作越多,其组合逻辑就会变得越大越复杂。假设在一个进程内,有关于变量的 3 个级连操作,其输出延时分别为 5ns,6ns,7ns,则其最快的时钟只能达到 18ns。相反,采用信号编程,在时钟控制下,往往综合成触发器的形式,特别是对于 FPGA 芯片而言,具有丰富的触发器结构,易形成流水作业,其时钟频率只受控于延时最大的那一级,而不会与变量一样层层累积。假设某个设计为 3 级流水作业,其每一级延时分别为 10ns,11ns,12ns,则其最快时钟可达 12ns。因此,采用信号反而更能提高设计的速度。

(2)由于变量不具备信息的相关性,只有当前值,因此也无法在仿真时观察其波形和状态改变情况,无法对设计的运行情况有效验证,而测试验证工作量往往会占到整个设计 70%~80%的工作量,采用信号则不会存在这类问题。

(3)变量有效范围只能局限在单个进程或子程序中,要想将其值带出与其余进程、子模块之间相互作用,必须借助信号,这在一定程度上会造成代码不够简洁,可读性下降等缺点。

当然,变量也具有其特殊的优点,特别是用来描述一些复杂的算法,如图像处理,多维数组变换等。

三.位(矢量)与逻辑(矢量)

bit 或其矢量形式 bit_vector只有’0’和’1’两种状态,数字电路中也只有’0’和’1’两种逻辑,因此会给初学者一个误区,认为采用位(矢量)则足够设计之用,而不必像std_logic那样出现’X’,’U’,’W’各种状态,增加编程难度。但实际情况却并非如此,以一个最简单 D型触发器设计为例

„ „

① process(clk)

② begin

③ if clk’event and clk=’1’ then

④ Q<=D;

⑤ end if;

⑥ end process;

„ „

实际中 clk 对数据端 D的输入有一定的时间限制,即在 clk 上升沿附近(建立时间和保持时间之内),D必须保持稳定,否则 Q输出会出现亚稳态,如下图所示。

当 clk 和 D时序关系不满足时,由于 bit 只有’0’或’1’,系统只能随机的从’0’和’1’中给 Q 输出,这样的结果显然是不可信的;而采用 std_logic 类型,则时序仿真时会输出为一个’X’,提醒用户建立保持时间存在问题,应重新安排 D和 clk 之间时序关系。

此外,对于双向总线设计(前面已提及)、FPGA/CPLD上电配置等问题,如果没有’Z’,’X’等状态,根本无法进行设计和有效验证。

四.关于进程

进程(Process)是 VHDL 中最为重要的部分,大部分设计都会用到 Process 结构,因此掌握Process 的使用显得尤为重要。以下是初学和使用 Process 经常会出错的例子。

1.多余时钟的引入

在设计时往往会遇到这种情况,需要对外部某个输入信号进行判断,当其出现上跳或下跳沿时,执行相应的操作,而该信号不像正常时钟那样具有固定占空比和周期,而是很随机,需要程序设计判断其上跳沿出现与否。这时,很容易写出如下程序:

①process(Ctl_a)--Ctl_a即为该输入信号

② begin

③ if Ctl_a’event and Ctl_a=’1’ then

④„„;--执行相应操作

⑤ end if;

⑥ end process;

由于出现第③行这类语句,综合工具自动默认 Ctl_a 为时钟,某些 FPGA 更会强行将该输入约束到时钟引脚上。而设计者的初衷只是想将其作为下位机的状态输入以进行判断。上面的程序容易造成多时钟现象,增加设计的难度。解决的办法可以如下,将 Ctl_a 增加一级状态

Ctl_areg 寄存,通过对 Ctl_a 和Ctl_areg 状态判断上跳与否,改正程序如下:① process(clk)

② begin

③ if clk’event and clk=’1’ then

④ Ctl_areg<=Ctl_a;--产生相邻状态

⑤ if Ctl_areg=’0’ and Ctl_a=’1’ then--上跳判断

⑥„„;--执行相应操作

⑦ end if;

⑧ end if;

⑨ end process;

程序中第④行用以产生两个相邻状态,第⑤行对前后状态进行判断是否有上跳现

象发生。其中,需注意的是 clk 的时钟频率应明显快于 Ctl_a信号的变化频率,以保证正确采样。

2.输出多驱动

误用 Process经常会引起输出多驱动源的发生,即在两个以上的进程内对同一信号赋值操作。

以下程序就出现了这类情况:

⑴ Proc_a: process(clk)

⑵ begin

⑶ if clk’event and clk=’1’ then

⑷ Dout<=Din_A;

⑸ end if

⑹ end process;;

⑻ Proc_b:process(sel_en)

⑼ begin

⑽ if sel_en=’1’ then

⑾ Dout<=Din_B;

⑿ end if;

⒀ end process;

进程 Proc_a 和 Proc_b 中都出现了对 Dout 的赋值语句,设计者原本的想法是,只要合理控制好 clk 和 sel_en 输入,使其不发生冲突,即 clk上升沿时 sel_en 不为’1’;sel_en 为’1’时,不出现 clk 的上升沿,这样 Proc_a,Proc_b 两个进程就不会发生冲突。但综合时,综合工具会将所有可能情况全部罗列进去,包括第⑶行和第⑽行同时成立的情况,此时对于 Dout就有 Din_A和 Din_B 两个输入驱动,Dout 不知接收哪一个,因此该程序无法综合,改正的方法是只要将两个进程合并成一个即可。

由于进程在 VHDL 中的重要性,对此专门做了一个总结如下:

(1)一个进程中不允许出现两个时钟沿触发,(Xilinx 公司 CoolRunner 系列 CPLD 支持单个时双钟的双触发沿除外)

(2)对同一信号赋值的语句应出现在单个进程内,不要在时钟沿之后加上 else 语句,如 if clk’event and clk=’1’ then-else„的结构,现有综合工具支持不了这种特殊的触发器结构

(3)当出现多层 IF语句嵌套时,最好采用 CASE 语句替代,一是减少多层嵌套带来的延时,二来可以增强程序的可读性

(4)顺序语句如 IF语句、CASE 语句、LOOP 语句、变量赋值语句等必须出现在进程、函数或子程序内部,而不能单独出现在进程之外

(5)进程内部是顺序执行的,进程之间是并行运行的;VHDL 中的所有并行语句都可以理解为特殊的进程,只是不以 Process结构出现,其输入信号和判断信号就是隐含的敏感表

五.关于 VHDL 学习中的几点说明

与软件语言相比,VHDL 最重要的特点就在于它的并行运行特性,当设计好的电路上电后,器件内部所有信号将同时并发工作,而不会以软件方式按照

程序顺序执行,即使在进程内部也是趋向并行工作的。例如以下程序:① process(clk)

② begin

③ if clk’event and clk=’1’ then

④ <=;

⑤ <=;

⑥ end if;;

⑦ end process;

综合的结果两个独立的 D 型触发器,虽然进程内部应按顺序执行,但是硬件实现后,只要采样到时钟上升沿,和 状态会同时翻转,而不会先执行的变化,然后才会去执行的转变。因此,VHDL 学习过程中,应加强硬件概念的理解,没有硬件概念或是硬件概念不强,在设计时,往往会将 VHDL 设计以软件编程的方式来处理,而得出一些不可思议的结果。

作为一门硬件描述语言,VHDL 几乎可以用来描述现有的大型系统数字电路、算法以及其它设计。但是,限于目前综合工具的水平,VHDL 中的许多语法还不能支持,例如:

dout<=din after 5 ns;

篇3:总结了一些比较有趣的编程名言

生命太短暂,不要去做一些根本没有人想要的东西。——Ash Maurya,Running Lean 作者

如果你交给某人一个程序,你将折磨他一整天;如果你教某人如何编写程序,你将折磨他一辈子。——David Leinweber

软件设计有两种方式:一种方式是,使软件过于简单,明显没有缺陷;另一种方式是,使软件过于复杂,没有明显的缺陷。——C.A.R.Hoare

其实,我尝试着使Ruby更自然,而不是简单。Ruby看起来很简单,但内部是非常复杂的,就像我们的身体一样。——松本行弘,Ruby之父

大部分情况下,构建程序的过程本质上是对规范调试的过程。——Fred Brooks,《人月神话》作者

软件开发往往是这样:最开始的90%代码占用了开始的90%的开发时间;剩下10%代码同样需要90%的开发时间。——Tom Cargill

当你试图解决一个你不理解的问题时,复杂化就产成了。——Andy Boothe 用几个小时来制定计划,可以节省几周的编程时间。—— 匿名

控制复杂性是计算机编程的本质。—— Brian Kernighan

计算机科学领域的所有问题都可以通过其他方式间接解决。——David Wheeler

编程是两队人马在竞争:软件工程师努力设计出最大最好的连白痴都会使用的程序;而宇宙在拼命制造最大最好的白痴。到目前为止,宇宙是胜利者。—— Rick Cook

调试一个初次见到的代码比重写代码要困难两倍。因此,按照定义,如果你写代码非常巧妙,那么没有人足够聪明来调试它。—— Brian W.Kernighan

我不是一个伟大的程序员,我只是一个具有良好习惯的优秀程序员。― Kent Beck

你们中大多数人都熟悉程序员的美德,有三种:那就是懒惰、急躁和傲慢。– Larry Wall,Perl語言发明人

任何一个傻瓜都会写能够让机器理解的代码,只有好的程序员才能写出人类可以理解的代码。——Martin Fowler

靠代码行数来衡量开发进度,就像是凭重量来衡量飞机制造的进度。——比尔•盖茨

这不是一个bug,这只是一个未列出来的特性。——匿名

作为一个程序员,郁闷的事情是,面对一个代码块,却不敢去修改。更糟糕的是,这个代码块还是自己写的。—— Peyton Jones

它在我的机器上可以很好运行!——大部分程序员

能说算不上什么,有本事就把你的代码给我看看。——Linus Torvalds,Linux之父

我认为对象就像是生物学里的细胞,或者网络中的一台计算机,只能够通过消息来通信——Alan Kay,Smalltalk的发明人,面向对象之父

当你选择了一种语言,意味着你还选择了一组技术、一个社区。——Joshua Bloch 质量、速度、廉价,选择其中两个。——匿名

过早的优化是罪恶之源。——Donald Knuth

没有什么代码的执行速度比空代码更快。——Merb核心原则

如果你是房间里最聪明的人,那么你走错房间了。——匿名

如果只需更改一个单一的代码行,你的部门需要花费多长时间?——Mary Poppendieck 九个人不能让一个孩子在一个月内出生。——Fred Brooks,《人月神话》作者

好代码本身就是最好的文档。当你需要添加一个注释时,你应该考虑如何修改代码才能不需要注释。——Steve McConnell,Code Complete 作者

一个人在教会电脑之前,别说他真正理解这个东西了。——Donald Knuth

杭州网络营销培训

杭州java软件培训 你是否也有自己喜欢的名言,欢迎分享。

上一篇:九年级上册五单元作文范文下一篇:b开头的形容词表示不愉快的