简易CPU设计入门:顶层模块概览

在前面的章节中,我讲解了Quartus II 13.1的基本使用方法,也介绍了ModelSim软件的基本使用方法,也讲解了整个项目的基本执行流程。从本节开始,我要来讲解各个代码文件里面的内容了。

在学习本节之前,还需要大家先将我们的这个CPU的项目代码给下载回去。如果你是跟着本专栏来从头学习的话,那么,这个项目代码,你就已经下载好了。如果,你是直接看到的本节文章,那么,你还需要先去下载本专栏的配套的CPU项目代码。

下载的方法,请参考下方链接所示的博客。

下载项目代码,了解本专栏CPU项目

下载好了代码,并且将项目导入了你本机的Quartus以后,我们接着来学习下面的内容。

首先呢,我要来讲解的,是顶层模块,它的代码文件的名字,是cpu_top.v。它的路径,从本项目的顶层文件夹【cpu_me01】算起,路径为:【cpu_me01\code\cpu_top.v】。

接下来,请大家打开这个顶层文件。我也将整个的代码放在下面的代码块中。

module cpu_top
(
	input wire sys_clk,
	input wire sys_rst_n,
	
	output wire [15:0] test_ip,
	output wire [15:0] test_instruct
);

wire init_done;
wire get_inst_en;
wire [15:0] instruct_pointer;
wire decode_en;
wire [15:0] instruct_word;
wire decode_done;
wire [4:0] op_code;
wire [2:0] reserve_bit;
wire [7:0] op_rand;

assign test_ip = instruct_pointer;
assign test_instruct = instruct_word;

sys_init sys_init_inst
(
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),
	.init_done(init_done)
);

ctrl_center ctrl_center_inst
(
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),
	.init_done(init_done),
	.decode_done(decode_done),
	.op_code_in(op_code),
	.reserve_bit_in(reserve_bit),
	.op_rand_in(op_rand),	
	.ip(instruct_pointer),	//instruction pointer
	.get_inst_en(get_inst_en)
);

get_instruct get_instruct_inst
(
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),
	.get_inst_en(get_inst_en),
	.ip(instruct_pointer),
	.decode_en(decode_en),
	.instruct_code(instruct_word)
);

decode_unit decode_unit_inst
(
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),
	.decode_en(decode_en),
	.instruct_word(instruct_word),
	.decode_done(decode_done),
	.op_code(op_code),
	.reserve_bit(reserve_bit),
	.op_rand(op_rand)
);

endmodule

上面的代码块,仅供大家浏览代码之用。实际讲解的时候,我还是采用截图的方式,一块一块地,来进行着讲解。

一.   顶层模块的信号列表

图1

图1里面,我所截图的部分,是代码文件的前8行的代码。它主要是包含了顶层模块cpu_top的信号列表。在Verilog HDL里面,想要书写信号列表,可以采取两种方式,一种为ANSI-C的方式,另一种为非ANSI-C方式。

在我这里,我采用的是ANSI-C的方式。之所以采用这种方式,那是因为,我在学习FPGA开发板的配套教程的时候,教程里面,都是用的这种方式,我也习惯了这种书写方式,所以就这么写了。

但是,大家自己在学习的时候,我还是建议,两种书写方式,你都是需要学会的。因为,我们有可能会需要学习他人的代码。那么,你也不知道,他人采用的是什么格式的代码。对于和我们一起来学习FPGA开发板的同学来讲,大家的写法,应该都是一样的。

可是呢,指不定哪一天,你会需要去学习某一位大神的代码。大神的话,人家的代码,可能就包含着各种我们不常用的代码了。那么,在这里,无论是ANSI-C风格的信号列表,还是非ANSI-C风格的信号列表,我们都是需要会的。至少,你得会看。

在我的这个顶层模块中,它包含了四种信号。前两种,是sys_clk信号和sys_rst_n信号。

其中,sys_clk信号,为系统时钟信号。而sys_rst_n,为系统复位信号。

sys,为system的简写。clk,为clock的简写。rst,为reset的简写。n,就是negative的简写,在这里它表示的是【非】的含义。

关于系统复位信号,我们可以设置为:在复位信号为高电平时复位,也可以设置为:在系统复位信号为低电平时复位。如果我们想要让复位的事件发生在复位信号为高电平时,那么,我们就可以将复位信号的信号名设置为【sys_rst】。但是呢,在本项目里面,我是将复位事件设置为低电平复位,所以,信号名为【sys_rst_n】。当然了,你也可以将其取名为【sys_rst_bar】,也可以。但是呢,在这里,我还是用【n】后缀来表示【非】的概念。

这种命名方式,采用了正点原子与野火电子的FPGA开发板配套教程的那种命名方式。

如果你学的是这两家的FPGA教程,那么,我这里的命名方式,你应该很熟悉才对。

在我们的这个顶层模块里面,其实有用的,正是这俩信号,时钟与复位信号。它们都是输入信号。

可是,在图1里面,我还设置了两个输出信号,它们的名字分别是【test_ip】和【test_instruct】,位宽都是16位的。

之所以设置这俩信号,其实是为了在顶层模块中进行观察。其实也可以不设置这俩输出信号。但是呢,我还是设置了这俩信号。如果你觉得我的这俩信号设置得冗余了,那么,就请自行修改吧。

其实呢,在我的这个专栏的学习中,你可能会发现,冗余的地方,还不止是一个地方。好多的代码,其实都重复了,写得不太好。如果有兴趣修改代码的话,就请大家来自行完成吧。

我这里呢,我是第一次写CPU代码,而且写出来的还是一个仿真CPU代码。写代码的时候呢,也算是调试了好多次。有的代码bug,调试起来,真的是挺费劲的。当我把这个项目写完了,调试通过了的时候,我是觉得挺累的。一时半会儿,我都不想再去写这个CPU代码了。

我自己在讲解本专栏的时候,之所以没有从零开始写代码和给大家讲解,也是因为,我觉得调试代码挺难的,那个过程,也容易让人心烦。所以呢,我就没有去从零开始地来写代码和给大家讲解。而是,直接把整个的项目,列出来,然后呢,我再一块一块地,来给大家讲解。

二.   连接信号与连续赋值语句

我们接着看代码。

图2

图2所示的代码,为9到22行的代码。

其中,第10行到第18行,为连接用的信号。我将它们都设置为了wire(线网)类型的变量。第20到和第21行,是链条连续赋值语句,这两条连续赋值语句,分别给顶层模块的两个输出信号【test_ip】和【test_instruct】赋值。

在学习Verilog的时候,我们知道,Verilog HDL有四种抽象级别。它们分别为:

  1. 行为级,也叫做算法级。
  2. 数据流级。
  3. 门极。
  4. 开关级。

其中呢,第1级和第2级的混合代码,也就是行为级与数据流级的混合代码,被称作是RTL级。RTL的中文翻译为【寄存器传输级】。

其实就是我们在图2中所见到的assign语句,就是一种数据流级语句。用assign关键字,来给线网变量进行赋值的语句,就叫做连续赋值语句,它是数据流级语句。

并非所有的带有【assign】关键字的语句,都是连续赋值语句。在Verilog HDL中,还存在着一种叫做过程赋值语句的语法,也是带有【assign】关键字。这种过程赋值语句,它是给reg型变量赋值,而不是给wire类型的变量赋值。而所有的连续赋值语句,都属于是数据流级语句。

在这里,我是给大家略微 讲了讲Verilog的语法小知识,略微领着大家复习了一下基础语法。

目前呢,我其实也是在复习着Verilog HDL基本语法。我在学习这个Verilog HDL的时候,我的一个感觉就是,这门语言呢,语法实在是太琐碎了。一下子呢,很难把它给记住。好多东西呢,都是说,学习起来,很容易搞混。

所以呢,我建议,大家最好是平时能买一本专门地Verilog语法教材来看一看。随时去看,随时去复习着Verilog语法知识。之所以我建议大家买专业的语法书去看,那是因为,在你学习完了本专栏以后,慢慢地,你还需要去写更加复杂的CPU的代码。那么,为了更好地学习CPU知识,一个必要的准备,应该就是好好地去学习硬件编程语言。

书到用时方恨少,多些准备,总还是好的。

在这里呢,对于图2的10到18行的变量,我暂时不打算细讲。以后呢,讲到相应的模块的时候,我倒是会去讲解。在这里,大家只需要浏览一下子即可。

不过呢,我还是略微地来谈一谈这些个信号的基本含义吧。具体含义,等到我去讲相应的模块的模块的时候,再去详细展开了。

init_done:初始化完成信号,主要是用在系统初始化模块里面。

get_inst_en:取指令使能信号。这个信号的展开的英文名称为【get instruct enable】。本信号值为1时,可以进行取指令的操作。

instruct_pointer:指令指针。英特尔8086处理器里面,有一个寄存器叫做IP寄存器,它是指令指针寄存器。而我的这个项目里面的instruct_pointer信号,就是用来模仿着8086里面的IP寄存器的功能的。当它有效的时候,它的值为本次执行的指令的地址。

8086的指令地址分为两个部分,一个是段地址,存放在CS寄存器里面。另一个是偏移地址,存放在IP寄存器里面。

在我这里呢,没有段地址,也没有段寄存器。只有一个指令指针,用来表示指令的地址。

decode_en:译码使能信号。本信号为高电平时,可以对得到的指令码进行译码操作了。

instruct_word:指令字,其实就是整个的16位的指令码。在流行的CPU中,不同的指令,可以有不同的长度。有的是1字节,有的是2字节,有的也肯能是3字节或4字节,或者更长。而在我的这个项目里面呢,所有的指令,都统一地设置为2字节,也就是16比特。

decode_done:译码完成信号。

op_code,reserve_bit,op_rand:译码工作,是将16位的指令字,分解为三个部分。分别是操作码、操作数、保留位。op_code就是操作码,reserve_bit为保留位,oprand为操作数。正常来讲,在计算机英语里面,操作码的英文是opcode,操作数的英文是oprand。在我这里呢,我是在op两个字母的后面加上了下划线。你觉得我这么写不好看的话,你也可以自己改信号名。

这些个连接信号讲完了之后,其实本节的内容,差不多也就可以了。不过呢,的确是还有几行,是一些个模块的实例化代码。在这里呢,我还是将那些个实例化代码贴一下子。

三.   子功能模块的实例化

图3
图4
图5
图6

图3是系统初始化模块的实例化代码,图4是控制中心模块的实例化代码,图5是取指令模块的实例化代码,图6是译码模块的实例化代码。

在这里呢,图6模块,是本项目中的译码器。我这里实现的译码器很简单。对于实际的CPU,我不知道它的译码器是怎么样的。反正,我这里的译码器,设计的是比较简单的。

话说,我这里的整个的代码项目,在我看来,都是特别简单的。因为,当前,我也暂时写不出来多么地复杂的逻辑。只能是搞出来这种思路比较简单的CPU了。

至于更加地复杂的CPU,我想要写出来,那么,我还需要很多的学习啊。

上面的几个实例化模块连接,我就不去细讲了。以后呢,我们再慢慢地来研究着不同的实例模块代码。

结束语

在这一节,大家只需要浏览一下这几个实例模块连接代码。如果再细致一些,你可以先浏览一下实例连接代码与顶层模块的10到18行的链接信号的连接关系,还有与顶层模块的信号列表的连接关系。只需要大致地浏览一下就可以了。暂时你不需要去深究。

一般来讲,刚刚接触一个东西的时候,最好呢,都不去太过于深究。因为,刚开始的时候,你的基础不足,学习经验还不足。在这种,你的个人还不足的时候,你就去深入研究一些个东西,那么,你会很累。很可能会是说,你学得很累,还没有把东西给研究透彻,然后呢,你还觉得很有挫败感。如果总是有着这种挫败感,那么,你很难把一个复杂的学习任务给完成。很可能学习到某一块的时候,你就不愿意继续往下学习了。

中庸里面讲,【君子之道,譬若行远必自迩,譬若登高必自卑】。想要修炼成为一个君子,这就好比,你想要走到一个很远的地方。为了走到很远的地方,那一定是从近处开始走,一步一步地,走到远方。又好比你想要攀登到一个很高的地方,那么,你也是要从很低的地方作为起点,一点一点地,由低到高地,攀登上去。

《论语-宪问》里面讲,【下学而上达】。你想要学习到很高深的学问与智慧,那么,你也一定是从很基础的地方开始学起,逐步地达到高深的境界。

这个过程呢,它需要由浅入深,循序渐进。如果是说,你自己急于求成,急于达到很高的地步,一开始就深研究,深琢磨,这个不是一件好事。

对于当前的教育,让一个三四岁的小孩子,就去幼儿园里面学习东西,我不觉得这是一件好事。我觉得,六七岁,开始步入幼儿园,还是比较好的。太小了,就让他去学习各种文化知识,反而有可能扼杀孩子的兴趣。

又比如说,在我们国家的中学教育里面,让初中与高中的学生,承担着过重和过大的学习压力,然后呢,还没有太多的时间,用来和探索。在这种成长期,青春期,就把大量的时间,耗费在学业的焦虑上,我不觉得这是一件好事。

这些东西,在我看来,都属于是一种,在刚开始的阶段,就往深了去研究和琢磨的现象。

在《周易-恒卦》里面,有如下所示的一段爻辞。

恒-初六:浚恒,贞凶,无攸利。

初六,是恒卦的六个爻中的一个。浚,就是深的意思。初六,在整个的恒卦里面,它是开始的阶段。恒,就是恒久,长久的意思。【贞凶,无攸利】是占断评语。无攸利,就是【没有什么好处】、【没有什么利益】的意思。贞凶,它的意思是,坚持着某一种道路,会收到凶险的结果。

整个的这条爻辞的意思是,在刚开始的阶段里,就一味地求深,一味地往高大,往完美,往深入的方向,去作要求,去追求,去采取行动。如果有人坚持着这样的一种方式,采用着这样的一种道路,去做事情的话,其结果是凶险的,没有什么好处。

如果呢,你真的是想要学习某一门学问,想要将其学习到很精通,很高深的地步,最好呢,还是爻循序渐进,不可以想着一步登天,不可以在一开始的时候,自己根基还不稳呢,就一下子想要达到很高深的地步。

有的人可能觉得,我有着高一些的追求,这是我的上进的表现。实际上呢,不是那么回事。急功近利,总想要一口气吃撑一个胖子,这就是一种胡乱作为,是一种妄动的表现。

很多的事情,我们想要做好它,其实都需要我们去有序地开展。总是一味地存有过高的、不切实际的追求,存在着苛刻与完美的要求,那是有害的。

本节结束。

  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值