官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师
您的当前位置:主页 > 至简设计法 >

至简设计法原理与应用--3.7VGA显示圆--明德扬科教(mdy-edu.com)

发布时间:2020-06-20   作者:admin 浏览量:
第一章 VGA显示圆第1节 项目背景

在一个平面内,一动点以一定点为中心,以一定长度为距离旋转一周所形成的封闭曲线叫做圆,如下图所示,同时其也可以理解为在同一平面内到定点距离为定长的点的集合。圆有无数个点,可以表示为集合
{M||MO|=r}
3.10-1圆的定义

在平面直角坐标系中,以点Oab)为圆心,以r为半径的圆的标准方程是

  

同理,以原点为圆心,半径为rr>0)的圆的标准方程为

  


举个例子,在平面直角坐标系中以坐标(4,4)为圆心画一个半径为2的圆,如下图所示。由于圆的定义即到圆心的距离相等点的集合,因此该坐标系中点到圆心的距离公式为  

如果想要判断一点是否在圆内,可以将该点到圆心的距离与圆的半径进行比较,这一距离如果大于半径该点即在圆外,如果小于半径该点即在圆内。


如下图中的红色点所示,其坐标为(3,5),已知圆心坐标为(4,4),因此红点到圆心的距离则为

  

可知  

半径  

,由于2<4,即红点到圆心的距离小于半径,因此可以判断红点为圆内一点。同理可得蓝点(7,2)到圆心的距离为

  ,可知  

半径r2=22=4,由于13>4,即蓝点到圆心的距离大于半径,因此可以判断蓝点为圆外一点。


可以看出:不论是红点还是蓝点到圆心的距离均需要进行开根号这一步骤,这样会大大增加对比数字大小的难度,因此在实际使用中,可以将距离的平方与半径的平方进行对比。即  

时点在圆上,  

时,点在圆外。本次工程将利用这一公式完成设计。


3.10-2圆内点和圆外点的判断

第2节 设计目标
掌握了圆的产生条件后来进行本次工程的设计。上一章中本书学习了通过VGA显示矩阵图像,这一章将在上一章的基础上进行难度提升,学习通过VGA显示颜色的设计。按照至简设计法的思路,在进行设计之前首先应明确设计目标。明确了设计目标后,后续的每一步操作就是围绕设计目标进行展开的。如果没有牢记设计目标就开始动手进行实践操作,最终的作品也是东拼西凑的产物,一旦在设计过程中出现了问题就需要花费大量的精力进行寻找修复。只有在开始学习时就养成良好的设计习惯,才能在后续的职业生涯中受益。因此建议一定要弄懂设计目标再进行后面部分的学习。

本设计需要通过VGA连接线将显示器和开发板进行连接,FPGA在连接成功后产生640*480分辨率,刷新频率为60HzVGA时序,使得显示器产生图像其中图像中间有一个直径为100像素的圆。圆内显示颜色为绿色,圆外显示颜色是白色,其效果图如3.10-3所示。
3.10-3 VGA显示圆效果图

显示器一般都具有分辨率自适应功能,无须设置就能识别不同分辨率的图像。本设计的相应参数参见表3.10-1中的第一行,这里VGA常用分辨率的对应时序参数并不是随意设定的,而是国际通用标准,每个关于VGA的设计工程都需要遵守这一标准。其中,行的单位为“基准时钟”,即频率为25MHz、周期为40ns的时钟,列的单位则为“行”,请读者朋友们一定区分好。
3.10-1VGA常用分辨率
分辨率
/
同步脉冲
显示后沿
显示区域
显示前沿
帧长
单位
640*480
/60Hz
96
48
640
16
800
基准时钟
2
33
480
10
525
800*600
/72Hz
120
64
800
56
1040
基准时钟
6
23
600
37
666
800*600
/60Hz
128
88
800
40
1056
基准时钟
4
23
600
1
628
1024*768
/60Hz
136
160
1024
24
1344
基准时钟
6
29
768
3
806

设计完成后,通过VGA连接线将显示器和教学板的VGA接口相连,连接示意图如下:
3.10-4教学板连接示意图

上板后显示器展示效果图如下图所示,不同的显示器会有一定的色差,需要以实际显示情况为主。可以看到显示器中央显示圆的直径为100像素,圆内显示颜色为绿色,圆外显示颜色是白色。想要观看连接后的演示视频效果的读者朋友可以登陆至简设计法官网学习:www.mdy-edu.com/xxxx

3.10-5VGA显示圆效果图

第3节 设计实现
确定了设计目标后,本书会逐步分析讲解工程的制作步骤。建议初学者认真学习每一步,因为这里分享给同学们的不仅仅是案例,还有在操作过程中的一些设计理念及原理。当然本书也会分享一些至简设计法的设计技巧,最终希望每一位读者都可以具备独立设计工程的能力。当然已经拥有扎实的功底、只是想要根据步骤完成项目的读者朋友们可以跳过此部分,直接进入第五节中的简略版操作步骤分享。

3.1 顶层接口
新建目录:D:\mdy_book\vga_exec1,在该目录中新建一个名为vga_exec1.v的文件。用GVIM打开后开始编写代码。这里再次强调,初学者一定要按照本书提供的文件路径以及文件名进行设置,避免后面出现未知错误。
首先来确定顶层信号,分析设计目标可知FPGA产生VGA时序,即控制VGA_R4~R0VGA_G5~G0VGA_B4~B0VGA_HSYNCVGA_VSYNC从而使显示器显示图像。其中,FPGA可根据时序产生高低电平从而控制VGA_HSYNCVGA_VSYNC。由于本设计需要显示的颜色是三基色中的绿色,可通过该FPGA自身产生而不需要外部输入图像的数据。因此在FPGA工程设计中可以定义输出信号hys表示行同步,定义输出信号vys表示场同步,定义一个16位的信号lcd_rgb进行RGB输出,其中lcd_rgb[15:11]表示VGA_R4~0lcd_rgb[10:5]表示VGA_G5~0lcd_rgb[4:0]表示VGA_B4~0。当然,本设计中还需要时钟信号clk和复位信号rst_n来进行工程控制。

综上所述,本工程需要五个信号:时钟信号clk,复位信号rst_n,场同步信号vys、行同步信号hysRGB输出信号lcd_rgb。信号与硬件的对应关系如下表所示。
3.10-2信号和管脚关系
器件
电阻网络转换后
信号线
信号线
FPGA管脚
FPGA工程信号
CN1
VGA_RED
VGA_R4
E11
lcd_rgb[15]
VGA_R3
C10
lcd_rgb[14]
VGA_R2
D10
lcd_rgb[13]
VGA_R1
E9
lcd_rgb[12]
VGA_R0
E10
lcd_rgb[11]
VGA_GREEN
VGA_G5
D15
lcd_rgb[10]
VGA_G4
C17
lcd_rgb[9]
VGA_G3
C19
lcd_rgb[8]
VGA_G2
E12
lcd_rgb[7]
VGA_G1
C13
lcd_rgb[6]
VGA_G0
E15
lcd_rgb[5]
VGA_BLUE
VGA_B4
D13
lcd_rgb[4]
VGA_B3
E13
lcd_rgb[3]
VGA_B2
D17
lcd_rgb[2]
VGA_B1
E16
lcd_rgb[1]
VGA_B0
C15
lcd_rgb[0]
VGA_HSYNC
VGA_HSYNC
C20
hys
VGA_VSYNC
VGA_VSYNC
D20
vys
X1

SYS_CLK
G1
clk
K1

SYS_RST
AB12
rst_n

通过以上分析写出顶层信号代码。将module的名称定义为vga_exec1,已经知道该模块有五个信号:clkrst_nlcd_hslcd_vslcd_rgb,将与外部相连接的输入/输出信号列出,从而实现信号与管脚的连接。具体顶层代码如下:
1
2
3
4
5
6
7
module vga_exec1(
clk  ,
rst_n  ,
lcd_hs  ,
lcd_vs  ,
lcd_rgb
    );

随后声明信号的输入输出属性。这里需要声明这一信号对于FPGA来说,属于输入信号还是输出信号。信号若为输入的话则声明为input,若为输出则声明为ouput。在本设计中,由于clk是外部的晶振输入给FPGA的,因此在FPGAclk是输入信号input;同样地,rst_n是外部按键输送给FPGA的,在FPGA中同样为输入信号inputlcd_hslcd_vslcd_rgbFPGA输出给显示器的,因此其为输出信号output,并且其中clkrst_nlcd_hslcd_vs的值都是0或者1,用一根线表示即可,lcd_rgb16位位宽的。根据以上分析补充输入输出端口定义,其具体代码如下:
1
2
3
4
5
input                   clk  ;
input                   rst_n  ;
output                  lcd_hs  ;
output                  lcd_vs  ;
output  [15:0]          lcd_rgb       ;

3.2 信号设计
分析设计目标可知,首先需要设计行同步信号hys,其时序图表示如下:
3.10-6VGA行同步时序

根据时序图可以看到,hys是一个周期性地高低变化的脉冲。根据设计目标可知图像分辨率选定为640*480,因此使用下表中的640*480分辨率的相关参数。即同步脉冲a的时间是96个基准时钟,显示后沿b的时间是48个基准时钟周期,显示时序c的时间是640个基准时钟,显示前沿的时间是16个基准时钟,共计800个基准时钟(800=96+48+640+16)。
3.10-1 VGA常用分辨率
分辨率
/
同步脉冲
显示后沿
显示区域
显示前沿
帧长
单位
640*480
/60Hz
96
48
640
16
800
基准时钟
2
33
480
10
525
800*600
/72Hz
120
64
800
56
1040
基准时钟
6
23
600
37
666
800*600
/60Hz
128
88
800
40
1056
基准时钟
4
23
600
1
628
1024*768
/60Hz
136
160
1024
24
1344
基准时钟
6
29
768
3
806

这里需要注意,一个基准时钟是40ns,而至简设计法开发板的时钟周期是20ns,因此基于至简设计法开发板的VGA工程设计中,采用2个时钟时间代表一个基准时钟时间。在图中补充对应的时间信息,带有时间信息的时序图如下:
3.10-7带时间信息的VGA行同步时序

根据至简设计法的理论,分析波形图和设计目标后可以得到本设计的计数器架构:这里需要使用2个计数器。一个计数器cnt0用来计数一个基准时间,另一个计数器cnt1用来计数hys的行的长度。

先来讨论用于计数基准时间的cnt0。至简设计法的计数器只考虑两个因素:加1条件和计数数量,只要可以确定相应逻辑,就能完成计数器代码设计。首先确定讨论计数器cnt0的加1条件:由于该计数器在不停地计数,永远不停止,因此可以认为其加1条件是始终有效的,可写成:assign add_cnt0==1。

这里可能会有读者会提出疑问:加1条件的概念是什么?这里以停车位来进行比喻,一般情况下对每个停车位置会进行对应编号,但是如果某个位置上放置了一块石头无法作为停车位时,该位置就不能获得对应的编号。

反之则可以认为停车位编号的加1条件就是:对应位置上没有石头,其可以继续的进行编号,即assign add_cnt0 = 没有石头。因此如果在设计中计数器一直没有阻碍地进行计数工作,就可以认为加1条件是一直有效的。
接下来确定计数器cnt0的计数数量,前文分析中可知2个时钟周期等于1个基准时钟,所以计数器cnt0的计数数量是2

确定好了加1条件和计数数量后开始进行代码编写。相信各位往常都是一行行输入代码,但是至简设计法有一个小技巧,可以为大家编写代码省去不少时间,并且一定程度上降低了代码的出错率。至简设计法将日常代码中常用到的固定部分做成了模板,进行代码编程时可以调用相应模板后根据逻辑输入对应设计的变量将代码补充完整。这里就可以用模板编写计数器代码,感受一下这个炫酷的功能。

在命令模式下输入:Mdyjsq”,点击回车,就调出了对应模板,如下图所示。随后再将本案例中的变量填到模板里面,就可以得到完整正确的计数器代码。

3.10-8至简设计法调用计数器代码模板

补充完整后得到计数基准时间的计数器cnt0代码如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt0 <= 0;
    end
    else if(add_cnt0)begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
end
[size=9.5000pt]
assign add_cnt0 = [size=9.5000pt]1[size=9.5000pt];
assign end_cnt0 = add_cnt0 && cnt0== 2 -1[size=9.5000pt];

接着讨论用于计数hys长度的计数器cnt1。根据设计目标可以知道一行占有800个基准时钟,因此其计数数量为800。前文设计中已经确定了一个基准时钟可以用end_cnt0表示,因此计数器cnt1的加1条件为“end_cnt0”,可写成:assign add_cnt1 = end_cnt0。继续调用至简设计法模板,在命令模式下输入“:Mdyjsq”,点击回车后调出对应模板,将“add_cnt1”和“end_cnt1”补充完整,得到该计数器的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end
[size=9.5000pt]
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==800-1 ;

确定了计数器cnt0cnt1hys信号的设计就有了对齐的对象。从时序图可以发现,hys有两个变化点,一个是cnt1数到96个基准时钟时,同步脉冲a结束,信号由01出现一个上升沿;另一个是当cnt1数到800个基准时钟时,信号由10出现下降沿。下面将其翻译成代码,在编辑模式下输入“Shixu2”,调用至简设计法模板,将模板补充完整后得到场同步信号的代码如下:
1
2
3
4
5
6
7
8
9
10
11
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
hys<= 0;
    end
else if(add_cnt1 && cnt1==96-1)begin
hys<= 1;
    end
else if(end_cnt1)begin
hys<= 0;
    end
end

接下来讨论vys信号的设计。根据设计目标可以得到VGA场同步信号的时序图如下所示:

3.10-9VGA场同步时序

可以看出vys也是一个周期性地进行高低变化的脉冲。本设计中图像分辨率选定为640*480,因此使用表3.10- 1中的640*480分辨率的相应参数。查询表可知:同步脉冲a的时间是2行,显示后沿b的时间是33行,显示时序c的时间是480行,显示前沿的时间是10行,共计525行。这里需要注意:行的单位为“基准时钟”,前文设计中使用计数器cnt0表示一个基准时钟,cnt1表示一行。由于场同步信号是的单位是“行”,因此设计中可以使用cnt1来辅助表示场同步信号,即cnt1计数结束则代表一“行”结束。

在场同步信号中补充时间信息,得到带有时间信息的时序图如下所示。

3.10-10带时间信息的VGA场同步时序

分析时序图可以发现若要产生这一时序还需要另1个计数器,将该计数器命名为cnt2,其作用是计数行的数量。前面强调过vys的单位是行,因此加1条件就是一行结束,前面定义了cnt1表示一行,因此计数器cnt2的加1条件一行结束即“end_cnt1”,可写成:assign add_cnt2 = end_cnt1。分析上面的时序图可以知道,该计数器的计数数量为525。将其翻译为代码表示,继续调用至简设计法模板,在命令模式下输入“:Mdyjsq”,点击回车,就调出了对应模板,将“add_cnt1”和“end_cnt1”补充完整,得到该计数器的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt2 <= 0;
    end
    else if(add_cnt2)begin
        if(end_cnt2)
            cnt2 <= 0;
        else
            cnt2 <= cnt2 + 1;
    end
end
[size=9.5000pt]
assign add_cnt2 = end_cnt1;
assign end_cnt2 = add_cnt2 && cnt2==525-1 ;

确定了计数器cnt2,则vys信号的设计就有了对齐的对象。从时序图可以发现:vys有两个变化点,一个是cnt2数到2个时,信号值由01;另一个是当cnt2数到525个时,信号值由10。下面将vys信号翻译成代码,在编辑模式下输入“Shixu2”,调用至简设计法模板,然后补充完整,得到场同步信号的代码如下:
1
2
3
4
5
6
7
8
9
10
11
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
vys<= 1'b0;
    end
    else if(add_cnt2 && cnt2 == 2 - 1)begin
vys<= 1'b1;
    end
    else if(end_cnt2)begin
vys<= 1'b0;
    end
end


最后进行lcd_rgb信号的设计。从设计目标可知在显示器中一共要显示两种颜色:绿色和白色。前面章节中列出了设计使用颜色的相应信号值,当lcd_rgb等于16’b00000_111111_00000时表示绿色;lcd_rgb等于16’b11111_111111_11111时表示白色。这里一定要注意设计目标需要在显示区域才能进行颜色赋值,在非显示区域,lcd_rgb的值要为0,才能正确实现效果显示。

在本设计中可以看到图像分为两部分:圆内和圆外,如下图所示。圆内和圆外的颜色不同,圆外显示白色,圆内显示绿色。因此需要仔细区分,在什么时候输出上面对应的颜色赋值。
3.10-11 VGA显示圆效果图

那么如何区分圆内和圆外呢?在项目背景时讲过圆的概念,回想一下圆的定义公式:file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml10836\wps19.jpg,分析一下file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml10836\wps20.jpg得到的是点到圆心的距离。当这个距离小于file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml10836\wps21.jpg的时候,点在圆内;反之当点到圆心的距离大于file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml10836\wps22.jpg的时候,点在圆外。那么就可以理解为,圆内的区域为file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml10836\wps23.jpg,圆外的区域为file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml10836\wps24.jpg
圆的原理和公式是大家在初中学到的知识,对于FPGA工程师来说圆的基本原理是非常简单的。在这里可以把公式看做一个算法,而本设计的关键则就是如何利用FPGA来实现圆的算法。有很多读者会误认为学习FPGA中工具的使用很简单,而每个工程的算法设计才是最重要的。针对此观点,有时候读者过度的把复杂的东西简单化,简单的东西复杂化。有些读者一听到算法,就觉得很高大上。但是如果真正的掌握了至简设计法的核心思想,用心剖析每一个工程就可以发现算法其实就是一些数学公式,利用简单的有加法运算如求和、求平均数等,或是再复杂一些FFT等公式来解决某个问题。目前能接触到的设计几乎都是在简单公式的基础上,进行组合或者重复使用。根据多年的从业经验以及自身的设计能力,想要创造、发明、改进一套算法很难,只有具有良好数学功底的高材生才可以很好的完成这些具有研究性、创造性的算法,这些算法的开发更多的时候需要的是天赋,并不是单单通过学习训练可以完成的。对于普通设计师来说,应该更多地将精力用于找到算法、读懂算法、实现算法、解决问题。同学们学习FPGA,即是学习如何将各种已经存在的算法利用FPGA进行实现,这也是比较现实且具有可行性的目标。当然,这并不是说要求读者不去创造,不去思考,而是建议同学们灵活运用现有算法,当大家真正扎实的掌握了FPGA后再不断地钻研创造。学习的过程中既要脚踏实地也要仰望星空,哪怕创造这条道路比较艰辛,也要怀揣着崇高的理想坚定不疑地走下去。

回到正题,接下来要讨论如何使用FPGA实现算法file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml10836\wps25.jpg。首先需要搞清楚公式里面的每个元素代表了什么,并且在FPGA中将它们表示出来。
3.10-12显示点与圆心的关系

上图是在显示器中表示点与圆心关系的表示图。

回顾一下工程背景可以知道点到圆心的距离公式可以用坐标轴表示,这里同样将显示器当做坐标轴,横轴x轴为最上面一条线,竖轴y轴为最左边一条线,坐标轴方向与图像显示行进方向一致,单位为像素,x轴以向右为正,y轴以向下为正,第一个像素点即左上角的点为(0,0)。

下面将现有的值通过坐标轴表示出来。

首先确定图像的显示范围,根据设计目标可以知道图像显示分辨率为640*480,因此可以得出显示图像区域的坐标范围,在屏幕中x的范围是0~639y的范围是0~479,最右下角的坐标是(639,479)。有些读者会表示疑问:分辨率命名是640*480,为什么其范围是639*479呢?这是因为每一个像素点是从0开始数的,比如当坐标轴为8时其实数了9个像素点,因此这里的坐标范围需要减1

在用FPGA进行设计时,可以用cnt1cnt2来表示xy。在这里要注意一下,VGA时序中并不是只有显示区域,还存在同步脉冲和显示前沿这一部分,cnt1cnt2表示的是整个VGA时序。因此,x=0y=0的点应该是显示区域的第一个像素点,即当cnt1=96+48cnt2=2+33时,x=0y=0。综上所述用cnt1cnt2来表示xy,则有x = cnt1-96-48y=cnt2-2-33

接下来确定圆心的值,圆心的坐标为(a,b)。根据设计目标可知需要在屏幕正中心显示一个圆,因此中间位置就是行和列的中间值即a=640/2=320,b=480/2=240,即圆心坐标为(320,240)。

前面分析过判断任意点在圆内还是圆外的依据是点到圆心的距离,该距离小于圆的半径即在圆内,距离大于圆的半径即在圆外。那么此时还需要知道一个条件,即圆半径r的值。根据设计目标,需要显示的圆直径为100个像素,半径r=100/2=50,即r50。

至此,可将算法file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml10836\wps26.jpg中的所有值都利用FPGA表示出来。用distance表示距离的平方,可以得出distance = (x-a)*(x-a) + (y-b)*(y-b) = (cnt1-96-48 -320) *(cnt1-96-48 -320) +(cnt2-2-33 -240) *(cnt2-2-33 -240),其代码表示如下:
1
2
3
4
always  @(*)begin
    distance   = ((cnt1 - 96 - 48 - 320) * (cnt1 - 96 - 48 - 320)) + ((cnt2 - 2 - 33 - 240) * (cnt2 - 2 - 33 - 240));
end

根据目标可知如果distance小于r2=(50)2=2500 ,表示点在圆内,否则点在圆外。

确定VGA背景的讲解部分有说明过显示区域如何确定,场同步信号处于显示区域且行同步信号也处于显示区域时才是真正的显示区域,而其他区域中红、绿、蓝基色都应赋值为低电平时,从而实现VGA颜色显示。根据本工程的设计目标可以得出:

显示区域:(cnt1>=96+48&&cnt1<96+48+640)并且(cnt2>=(2+33) &&cnt2<2+33+480)。其中显示区域又可分成绿色区域和白色区域;
绿色区域:点在圆内即distance < 2500时为绿色区域,此时lcd_rgb输出“16’b00000_111111_00000”;
白色区域:在显示区域中非绿色区域的即为白色区域,此时lcd_rgb输出“16’b11111_111111_11111”;
非显示区域:显示区域之外的就是非显示区域,非显示区域lcd_rgb要给低电平,即输出“16’b0”。

其具体代码表示如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
lcd_rgb<= 16'h0;
    end
    else if(cnt1 >=(96+48) && cnt1 <(96+48+640) && cnt2 >=(2+33) && cnt2 < (2+33+480))begin
        if(distance<2500)begin
lcd_rgb<= 16'b00000_111111_00000;
        end
        else begin
lcd_rgb<= 16'b11111_111111_11111;
        end
    end
    else begin
lcd_rgb<= 0;
    end
end

至此,主体程序已经完成。


3.3 信号定义
接下来将module补充完整,首先来定义信号类型。再次强调,在进行regwire的判断的时候,总容易存在多余的联想,比如认为reg就是寄存器,wire是线;或者认为reg会综合成寄存器,wire不会综合成寄存器。但是这些其实和reg型还是wire型都并无关系,在进行信号类型的判断时不需要做任何的联想,只要记住一个规则“用always实现的是reg型,其他都是wire”就可以了。

cnt0是用always产生的信号,因此类型为regcnt0计数的最大值为1,需要用1根线表示,即位宽是1位。

add_cnt0end_cnt0都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可,即位宽为1

打开GVIM,在编辑模式下输入Reg1”“Wire1”可调用至简设计法模板,补充完整后得到代码如下:
1
reg    [0:0]           cnt0  ;
wire                   add_cnt0;
wire                   end_cnt0;

cnt1是用always产生的信号,因此类型为regcnt1计数的最大值为800,那如何确定该值对应的位宽是多少呢?至简设计法在这里分享一个非常实用的技巧,打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入进去,就会获得对应的信号位宽。利用这一方法将cnt1的最大计数器800输入到计算器中,如下图所示,可以看出其位宽为10。本设计的数位比较小,这种方法在后续遇到比较大的数字时会方便很多,也不容易出错。

3.10-13通过计算器获取信号位宽

因此可得cnt1定义代码如下:
1
reg    [9:0]           cnt1  ;

add_cnt1end_cnt1都是用assign方式设计的,因此类型为wire,并且其值是0或者1,用1个线表示即可。编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码如下:
1
2
wire                   add_cnt1;
wire                   end_cnt1;

cnt2是用always产生的信号,因此类型为regcnt2计数的最大值为525,需要用10根线表示,即位宽是10位。其具体代码如下:
1
reg    [9:0]           cnt2  ;

add_cnt2end_cnt2都是用assign方式设计的,因此类型为wire,并且其值是0或者1,用1根线表示即可。编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码如下:
1
2
wire                   add_cnt2;
wire                   end_cnt2;

lcd_rgb是用always方式设计的,因此类型为reg。其位宽是16位,16根线表示即可。编辑模式下输入“Reg16”调用至简设计法模板,补充完整后得到代码如下:
1
reg    [15:0]          lcd_rgb;

hysvys是用always方式设计的,因此类型为reg。并且其值是01,需要1根线表示即可。编辑模式下输入“Reg1”调用至简设计法模板,补充完整后得到代码如下:
1
2
reg                    hys  ;
reg                    vys  ;

distance是用always方式设计的,因此类型为reg。其位宽为20位,需要20根线表示。其具体代码如下:
1
reg  [19:0]            distance   ;

valid_areagreen_area是用always方式设计的,因此类型为reg,并且其值是01,用一根线表示即可。编辑模式下输入“Reg1”调用至简设计法模板,补充完整后得到代码如下:
1
reg                    valid_area ;
reg                    green_area;

至此,整个代码的设计工作已经完成。回顾一下本工程的整个代码设计,可以发现一共需要3个计数器,这里同样分享一个至简设计法代码模板,在“GVIM”中使用快捷命令“Jsq3”可以调出3个计数器的模板,调出的“Jsq3”模板如下图所示,补充完整后可以得到完整的计数器代码。

3.10-14至简设计法调用3个计数器模板

最终得到整个工程的代码如下:
1
2
[size=9.5000pt]
module color_exec1(
clk  ,
rst_n  ,
lcd_hs  ,
lcd_vs  ,
lcd_rgb
    );
[size=9.5000pt]
input                   clk  ;
input                   rst_n  ;
output                  lcd_hs  ;
output                  lcd_vs  ;
output  [15:0]          lcd_rgb       ;
[size=9.5000pt]
reg    [0:0]            cnt0  ;
wire                   add_cnt0;
wire                   end_cnt0;
[size=9.5000pt]
reg    [9:0]            cnt1  ;
wire                   add_cnt1;
wire                   end_cnt1;
reg    [9:0]            cnt2  ;
wire                   add_cnt2;
wire                   end_cnt2;
reg    [15:0]           lcd_rgb;
[size=9.5000pt]
reg                    hys  ;
reg                    vys  ;
[size=9.5000pt]
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt0 <= 0;
    end
    else if(add_cnt0)begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
end
[size=9.5000pt]
assign add_cnt0 = [size=9.5000pt]1[size=9.5000pt];
assign end_cnt0 = add_cnt0 && cnt0== 2 -1[size=9.5000pt];
[size=9.5000pt]
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end
[size=9.5000pt]
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==800-1 ;
[size=9.5000pt]
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt2 <= 0;
    end
    else if(add_cnt2)begin
        if(end_cnt2)
            cnt2 <= 0;
        else
            cnt2 <= cnt2 + 1;
    end
end
[size=9.5000pt]
assign add_cnt2 = end_cnt1;
assign end_cnt2 = add_cnt2 && cnt2==525-1 ;
[size=9.5000pt]
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
hys<= 0;
    end
    else if(add_cnt1 && cnt1==96-1)begin
hys<= 1;
    end
    else if(end_cnt1)begin
hys<= 0;
    end
end
[size=9.5000pt]
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
vys<= 1'b0;
    end
    else if(add_cnt2 && cnt2 == 2 - 1)begin
vys<= 1'b1;
    end
    else if(end_cnt2)begin
vys<= 1'b0;
    end
end
[size=9.5000pt]
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
lcd_rgb<= 16'h0;
    end
    else if(cnt1 >=(96+48) && cnt1 <(96+48+640) && cnt2 >=(2+33) && cnt2 < (2+33+480))begin
        if(distance<2500)begin
lcd_rgb<= 16'b00000_111111_00000;
        end
        else begin
lcd_rgb<= 16'b11111_111111_11111;
        end
    end
    else begin
lcd_rgb<= 0;
    end
end
[size=9.5000pt]
endmodule

下一步是新建工程和上板查看现象。

第4节 综合与上板4.1 新建工程
打开软件Quartus Ⅱ,点击“File”下拉列表中的New Project Wzard...新建工程选项,如下图所示。

3.10-15Quartus新建工程

随后会出现Quartus新建工程介绍,如下图所示,直接点击“Next”。

3.10-16Quartus新建工程介绍

此时会出现工程文件夹、工程名、顶层模块名设置界面,如图3.10- 17所示。设置目录为:D:/mdy_book/vga_exec1,工程名和顶层名为vga_exec1。再次强调,为了避免初学者在后续操作中发生程序跳出未知错误的问题,强烈建议设置的文件目录和工程名称与本书保持一致。设置完成后点击“Next”。

3.10-17QUARTUS新建工程设置名称

新建工程类型设置如下图所示,选择“Empty project”,然后点击“Next”。

3.10-18QUARTUS新建工程类型

接下来进行文件添加,其界面如下图所示。点击右侧的“Add”按钮,选择之前写好的“vga_exec1.v”文件,可以看到界面下方会显示出文件,之后点击“Next”。

3.10-19QUARTUS添加文件

3.10- 20为芯片选择页面,选择“Cyclone ⅣE”,在芯片型号选择处选择“EP4CE15F23C8”,之后点击“Next”。

3.10-20QUARTUS选择芯片型号

3.10- 21QUARTUS设置工具界面,不必做任何修改,直接点击“Next”。

3.10-21QUARTUS设置工具界面

下图为QUARTUS新建工程汇总界面,可以看到新建工程的汇总情况,点击“Finish”,完成新建工程。

3.10-22QUARTUS新建工程汇总界面

4.2 综合
新建工程步骤完成后,就会出现如下图所示的 QUARTUS新建工程后界面

3.10-23QUARTUS新建工程后界面

点击编译按钮,可以对整个工程进行编译。编译成功的界面,如下图所示。

3.10-24QUARTUS编译后界面

4.3 配置管脚
下面需要对相应管脚进行配置。如下图所示,在菜单栏中,选中“Assignments”,然后选择“Pin Planner”,随后就会弹出配置管脚的窗口。

3.10-25QUARTUS配置管脚选项


在配置窗口最下方中的“location”一列,参考信号和管脚关系,按照表3.10- 2中最右两列配置好FPGA管脚,配置管理来源参见管脚配置环节,最终配置的结果如图3.10-26。配置完成后,关闭Pin Planner,软件自动会保存管脚配置信息。
3.10 - 2信号和管脚关系
器件
信号线
信号线
FPGA管脚
内部信号
U6,U7
SEG_E
SEG0
Y6
seg_ment[2]
SEG_DP
SEG1
W6
未用到
SEG_G
SEG2
Y7
seg_ment[0]
SEG_F
SEG3
W7
seg_ment[1]
SEG_D
SEG4
P3
seg_ment[3]
SEG_C
SEG5
P4
seg_ment[4]
SEG_B
SEG6
R5
seg_ment[5]
SEG_A
SEG7
T3
seg_ment[6]
DIG1
DIG_EN1
T4
seg_sel[0]
DIG2
DIG_EN2
V4
seg_sel[1]
DIG3
DIG_EN3
V3
seg_sel[2]
DIG4
DIG_EN4
Y3
seg_sel[3]
DIG5
DIG_EN5
Y8
seg_sel[4]
DIG6
DIG_EN6
W8
seg_sel[5]
DIG7
DIG_EN7
W10
seg_sel[6]
DIG8
DIG_EN8
Y10
seg_sel[7]
X1

SYS_CLK
G1
clk
K1

SYS_RST
AB12
rst_n
3.10-26 QUARTUS配置管脚


4.4 再次综合
再次打开“QUARTUS”软件,在菜单栏中,选中“Processing”,然后选择“Start Compilation”,再次对整个工程进行编译和综合,如图3.10- 27所示。

3.10-27QUARTUS编译选项

当出现如下图所示的QUARTUS编译成功标志,就说明编译综合成功。

3.10-28QUARTUS编译成功标志


4.5 连接开发板
完成编译后开始进行上板调试操作,按照下图的方式将下载器接入电脑USB接口,接上开发板电源,将开发板的VGA口连接到一台显示器上,然后按下下方蓝色开关,硬件连接完毕。

3.10-29开发板连接图


4.6 上板
打开QUARTUS界面,单击界面中的“file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml10836\wps44.jpg”,则会弹出配置界面。在界面中点击“add file”添加“.sof”文件后点击“Start”,会在“Progress”出现显示进度。

3.10-30QUARTUS界面

QUARTUS下载程序界面如下图所示,当进度条到100%提示成功后,即可在显示器上观察到相应的现象。

3.10-31QUARTUS下载程序界面

进度条提示成功后,如果操作无误此时可以在显示器上看到一个绿色的圆,显示器的其余地方为白色。如果没有显示成功,就需要返回检查一下连接是否到位,代码是否编写正确。如果无法自己完成错误排查的话,可以重新按照步骤操作一遍,相信一定会达到想要的效果。



第5节 简化版步骤分享
这里依旧会分享简化版的步骤,方便掌握基础原理后进行反复操作复习。

5.1 设计实现5.1.1 顶层接口
新建目录:D:\mdy_book\vga_exec1在该目录中新建一个名为vga_exec1.v的文件,用GVIM打开后开始编写代码。

确定顶层信号,信号和管脚的对应关系见表3.10- 2。
3.10 - 2信号和管脚关系
器件
电阻网络转换后
信号线
信号线
FPGA管脚
FPGA工程信号
CN1
VGA_RED
VGA_R4
E11
lcd_rgb[15]
VGA_R3
C10
lcd_rgb[14]
VGA_R2
D10
lcd_rgb[13]
VGA_R1
E9
lcd_rgb[12]
VGA_R0
E10
lcd_rgb[11]
VGA_GREEN
VGA_G5
D15
lcd_rgb[10]
VGA_G4
C17
lcd_rgb[9]
VGA_G3
C19
lcd_rgb[8]
VGA_G2
E12
lcd_rgb[7]
VGA_G1
C13
lcd_rgb[6]
VGA_G0
E15
lcd_rgb[5]
VGA_BLUE
VGA_B4
D13
lcd_rgb[4]
VGA_B3
E13
lcd_rgb[3]
VGA_B2
D17
lcd_rgb[2]
VGA_B1
E16
lcd_rgb[1]
VGA_B0
C15
lcd_rgb[0]
VGA_HSYNC
VGA_HSYNC
C20
hys
VGA_VSYNC
VGA_VSYNC
D20
vys
X1

SYS_CLK
G1
clk
K1

SYS_RST
AB12
rst_n


写出顶层信号代码:
1
2
3
4
5
6
7
module vga_exec1(
clk  ,
rst_n  ,
lcd_hs  ,
lcd_vs  ,
lcd_rgb
    );

声明输入输出属性:
1
2
3
4
5
input                   clk  ;
input                   rst_n  ;
output                  lcd_hs  ;
output                  lcd_vs  ;
output  [15:0]          lcd_rgb       ;

5.1.2 信号设计
首先进行架构设计。设计目标中确定显示器中需要显示640*480分辨率的图像,因此使用下表中的第一种分辨率。
3.10-1 VGA常用分辨率
分辨率
/
同步脉冲
显示后沿
显示区域
显示前沿
帧长
单位
640*480
/60Hz
96
48
640
16
800
基准时钟
2
33
480
10
525
800*600
/72Hz
120
64
800
56
1040
基准时钟
6
23
600
37
666
800*600
/60Hz
128
88
800
40
1056
基准时钟
4
23
600
1
628
1024*768
/60Hz
136
160
1024
24
1344
基准时钟
6
29
768
3
806


分析设计目标可得VGA行同步信号,其时序图如下所示。
3.10-7带时间信息的VGA行同步时序

设计计数器架构,表示计数基准时间的计数器cnt0代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt0 <= 0;
    end
    else if(add_cnt0)begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
end
[size=9.5000pt]
assign add_cnt0 = [size=9.5000pt]1[size=9.5000pt];
assign end_cnt0 = add_cnt0 && cnt0== 2 -1[size=9.5000pt];


设计计数hys长度的计数器cnt1代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end
[size=9.5000pt]
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==800-1 ;

设计行同步信号的代码如下:
1
2
3
4
5
6
7
8
9
10
11
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
hys<= 0;
    end
    else if(add_cnt1 && cnt1==96-1)begin
hys<= 1;
    end
    else if(end_cnt1)begin
hys<= 0;
    end
end


设计VGA场同步时序计数器cnt2代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt2 <= 0;
    end
    else if(add_cnt2)begin
        if(end_cnt2)
            cnt2 <= 0;
        else
            cnt2 <= cnt2 + 1;
    end
end
[size=9.5000pt]
assign add_cnt2 = end_cnt1;
assign end_cnt2 = add_cnt2 && cnt2==525-1 ;


设计场同步信号的代码如下:
1
2
3
4
5
6
7
8
9
10
11
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
vys<= 1'b0;
    end
    else if(add_cnt2 && cnt2 == 2 - 1)begin
vys<= 1'b1;
    end
    else if(end_cnt2)begin
vys<= 1'b0;
    end
end


设计lcd_rgb信号的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
lcd_rgb<= 16'h0;
    end
    else if(cnt1 >=(96+48) && cnt1 <(96+48+640) && cnt2 >=(2+33) && cnt2 < (2+33+480))begin
        if(distance<2500)begin
lcd_rgb<= 16'b00000_111111_00000;
        end
        else begin
lcd_rgb<= 16'b11111_111111_11111;
        end
    end
    else begin
lcd_rgb<= 0;
    end
end


至此,主体程序已经完成。接下来将module补充完整。


5.1.3 信号定义
首先定义信号类型,cnt0、add_cnt0 和 end_cnt0的信号定义如下:
1
2
3
reg    [0:0]           cnt0  ;
wire                   add_cnt0;
wire                   end_cnt0;


cnt1的信号定义如下:
1
reg    [9:0]           cnt1  ;


add_cnt1end_cnt1的信号定义如下:
1
2
wire                   add_cnt1;
wire                   end_cnt1;


cnt2的信号定义如下:
1
reg    [9:0]           cnt2  ;


add_cnt2end_cnt2的信号定义如下:
1
2
wire                   add_cnt2;
wire                   end_cnt2;


lcd_rgb的信号定义如下:
1
reg    [15:0]          lcd_rgb;


hysvys的信号定义如下:
1
2
reg                    hys  ;
reg                    vys  ;


distance的信号定义如下:
1
reg  [19:0]            distance   ;


valid_areagreen_area的信号定义如下:
1
reg                    valid_area ;
reg                    green_area;


至此,整个代码的设计工作已经完成。最终得到完整的设计代码如下:
1
2
[size=9.5000pt]
module color_exec1(
clk  ,
rst_n  ,
lcd_hs  ,
lcd_vs  ,
lcd_rgb
    );
[size=9.5000pt]
input                   clk  ;
input                   rst_n  ;
output                  lcd_hs  ;
output                  lcd_vs  ;
output  [15:0]          lcd_rgb       ;
[size=9.5000pt]
reg    [0:0]            cnt0  ;
wire                   add_cnt0;
wire                   end_cnt0;
[size=9.5000pt]
reg    [9:0]            cnt1  ;
wire                   add_cnt1;
wire                   end_cnt1;
reg    [9:0]            cnt2  ;
wire                   add_cnt2;
wire                   end_cnt2;
reg    [15:0]           lcd_rgb;
[size=9.5000pt]
reg                    hys  ;
reg                    vys  ;
[size=9.5000pt]
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt0 <= 0;
    end
    else if(add_cnt0)begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
end
[size=9.5000pt]
assign add_cnt0 = [size=9.5000pt]1[size=9.5000pt];
assign end_cnt0 = add_cnt0 && cnt0== 2 -1[size=9.5000pt];
[size=9.5000pt]
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end
[size=9.5000pt]
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==800-1 ;
[size=9.5000pt]
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt2 <= 0;
    end
    else if(add_cnt2)begin
        if(end_cnt2)
            cnt2 <= 0;
        else
            cnt2 <= cnt2 + 1;
    end
end
[size=9.5000pt]
assign add_cnt2 = end_cnt1;
assign end_cnt2 = add_cnt2 && cnt2==525-1 ;
[size=9.5000pt]
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
hys<= 0;
    end
    else if(add_cnt1 && cnt1==96-1)begin
hys<= 1;
    end
    else if(end_cnt1)begin
hys<= 0;
    end
end
[size=9.5000pt]
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
vys<= 1'b0;
    end
    else if(add_cnt2 && cnt2 == 2 - 1)begin
vys<= 1'b1;
    end
    else if(end_cnt2)begin
vys<= 1'b0;
    end
end
[size=9.5000pt]
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
lcd_rgb<= 16'h0;
    end
    else if(cnt1 >=(96+48) && cnt1 <(96+48+640) && cnt2 >=(2+33) && cnt2 < (2+33+480))begin
        if(distance<2500)begin
lcd_rgb<= 16'b00000_111111_00000;
        end
        else begin
lcd_rgb<= 16'b11111_111111_11111;
        end
    end
    else begin
lcd_rgb<= 0;
    end
end
[size=9.5000pt]
endmodule


下一步是新建工程和上板查看现象。

5.2 综合与上板5.2.1 新建工程
打开软件Quartus Ⅱ,点击“File”下拉列表中的New Project Wzard...新建工程选项。

3.10-15Quartus新建工程


直接点击Next”。
3.10-16 Quartus新建工程介绍

此时会出现的是工程文件夹、工程名、顶层模块名设置界面(目录为:D:/mdy_book/vga_exec1,工程名和顶层名为vga_exec1),完成设置后点击“Next”。

3.10-17 QUARTUS新建工程设置名称


选择“Empty project”后点击“Next”。

3.10-18 QUARTUS新建工程类型


点击右侧的“Add”按钮,选择“vga_exec1.v”文件后点击“Next”,完成文件添加。

3.10-19 QUARTUS添加文件

对芯片型号进行选择,在“Device family”选项中选择“Cyclone ⅣE”,“Available devices”选项中选择“EP4CE15F23C8”,随后点击“Next”。

3.10-20 QUARTUS选择芯片型号


直接点击“Next”。
3.10-21 QUARTUS设置工具界面


点击“Finish”,完成新建工程。

3.10-22 QUARTUS新建工程汇总界面


5.2.2 综合
新建工程后界面如下图所示,点击“编译”。

3.10-23 QUARTUS新建工程后界面


编译成功如下图所示。

3.10-24 QUARTUS编译后界面


5.2.3 配置管脚
进行管脚配置,在菜单栏中点击“Assignments”后点击“Pin Planner”,此时会弹出配置管脚的窗口。

3.10-25 QUARTUS配置管脚选项

在配置窗口“location”根据信号和管脚关系配置管脚,配置完成关闭“Pin Planner”即可自动保存配置信息。

3.10-26 QUARTUS配置管脚


再次“QUARTUS”软件,在菜单栏中选择“Processing”,随后点击“Start Compilation”再次进行综合。

3.10-27QUARTUS编译选项

出现 QUARTUS 编译成功标志时表示此次编译成功。

3.10-28 QUARTUS编译成功标志


5.2.5 连接开发板
下载器接入电脑 USB 接口,将开发板接上电源,开发板的VGA口连接到一台显示器上后按下蓝色开关。

3.10-29开发板连接图


5.2.6 上板
打开 QUARTUS 界面后单击“  ”图标:

3.10-30 QUARTUS界面


点击“add file”,添加.sof文件,完成添加后点击“Start”,在“Progress”中显示进度,当进度条显示“100%”为成功,可观察显示器现象。如果此时开发板连接的显示器显示出了设计目标中需要的画面则代表设计成功。

3.10-31 QUARTUS下载程序界面

第6节 扩展练习
至此,VGA显示圆设计已经完成,相信同学们已经可以完全掌握这一设计。那么在掌握这项工程后可以多做一些思考,尝试在工程原理不变的基础上进行一定的数据调整,试着改变圆的大小和显示区域或者改变显示颜色等参数,挑战一下独立完成多个设计。也欢迎有更多思路和想法的同学前往至简设计法论坛上进行交流讨论。


07.png (4.05 KB, 下载次数: 0)

07.png
  •   
  •   
  •   
  •  
  • FPGA教育领域第一品牌
  • 咨询热线:020-39002701
  • 技术交流Q群:544453837