如何制作一个简易单周期16位CPU
本文最后更新于 982 天前,其中的信息可能已经有所发展或是发生改变。

设计思路与部分图纸参考 简单CPU设计实践 – kingduan

如果你对CPU的运行过程完全不懂,建议观看以下视频(适合所有人观看):

【计算机科学速成课】Crash Course Computer Science

前言

也许你曾对这个问题感兴趣:计算机是怎么运行的?它是如何处理各种运算的?

在查阅相关资料之后,我也来试着自己制作一个简易CPU。

CPUx16

简单介绍

RAM设计

RAM的设计是数据段和程序段,由于指令能控制的RAM地址最长只有8位,所以前256个RAM位置作为数据段,剩下的为程序段。

CPU开始工作时,首先将ROM中的程序写入RAM的程序段,从内存地址为0000000100000000的位置开始写入。

写入程序之后就开始逐个执行在程序段的指令。

寄存器

一共有6个寄存器,分别为PC、IR、R0、R1、R2、R3。

其中PC程序计数器IR指令寄存器除外,剩下的4个都可以被指令操作。

整体结构

上面的结构看起来很复杂,接下来我们按流程来分析这个结构。

运行流程

如上图所示:最上面有4个寄存器,右边是RAM内存,中间CONTROL UNIT(控制单元)负责控制各种信号,ALU执行各种运算。

控制单元中右下角的是程序计数器(PC),右上角是指令寄存器(IR)

第一阶段:读取(FETCH)

程序计数器将要读取的内存地址输入RAM,并且使RAM可读,RAM输出的指令存储到指令寄存器中。

第二阶段:解码(DECODE)

控制单元解码指令寄存器中的指令,决定要开启/关闭哪些寄存器、是否开启ALU、RAM可写/可读、ALU的执行指令等。

第三阶段:执行(EXECUTE)

若是要写入寄存器,那么上一阶段就应该已经把目标寄存器的可写打开了,现在只需要把写入的内容输入寄存器即可。

若是要使用ALU执行运算,那么上一阶段应该已经启用了ALU并且输入了ALU指令,并且有两个寄存器将存储的内容输入了ALU。

……

具体流程

上述三个阶段为一个周期,一个周期结束,代表一条指令运行完毕。

这里我的CPU设计和上面不太一样,使用了5个时钟周期。(应该可以优化成4个……)

从ROM载入指令

首先我们要从ROM载入需要的指令,这里我设计了一个载入模块,利用时钟控制。

如上图CTR是计数器,左边ROM右边RAM,还有一个ROM_2_RAM的时钟。

这个时钟经过一个周期,计数器的值+1之后传给ROM和RAM选择内存地址,ROM输出的值再输入RAM中,RAM接收到上升沿信号将值写入。

这样不断循环下去,当ROM读取到0000000000000000时,表示加载完成,下面有一个数值比较器用于判断。

若加载完成,则 输出信号 去阻止ROM_2_RAM的时钟信号,以免干扰后续程序运行。

图中还有一个ReloadProgram的按钮,按下激活之后将清空计数器、寄存器、程序计数器、指令寄存器。

时钟周期01

第一个时钟周期,只是给程序计数器一个上升沿,使它的数值+1。

时钟周期02

第二个周期,从RAM中读取指令,根据图中的数字1 2 3:第一步打开RAM的可读,第二步读取指令,第三步将指令输入指令寄存器IR,但是此时的IR并没有接收到CLK时钟信号,所以指令还没有被存进去。

时钟周期03

第三周期,首先IR接收到CLK信号后将指令存入,再将存入的指令输出给指令解码器(ID),接下来看一下ID部分。

图中的ID有3个输入,很多输出,下面逐个介绍一下它们的作用。

先看三个输入端:指令(16)、条件A(16)、条件B(16)

一条指令被输入进去之后就可以得到右边所有的输出,而两个条件输入用于一些特殊的指令,例如:JUMP_GT (若A>B则跳转)

Data8、ALUCode、AS1、AS2、S1、S2、S3、S4都来源于指令,是指令的部分截取内容

Data8就是输入的指令取后8位,S1、S2、S3、S4就是把Data8平分为4部分,每部分位宽为2。例如:01101101 -> 01 10 11 01

ALUCode用于控制ALU的计算方式(加减乘除或其它运算),其实就是指令前8位的后4位,例如:前八位是00100001,那么ALUCode就是0001。

AS1、AS2就是把ALUCode平分成两部分,同上。

REG_R_W用于控制寄存器组的读写模式,0读取1写入。

RAM_Write/RAM_Read控制内存可写/可读。

EnableALU控制ALU的开启/关闭。

R_I控制输入到RAM地址或ALU输入端的是寄存器组的B输出还是指令中的立即数。

S1_AS1控制输入到寄存器写入地址的是S1还是AS1。

HALT表示程序运行完毕。

JUMP表示程序计数器的跳转,例如现在正在执行第0011(3)条指令,可以用JUMP向前或向后跳转一定距离,此时将会改变程序计数器的当前数值。

JUMP_Offset表示跳转的距离,可正可负。

CLK_ADDR表示在RAM_Write输出为1的情况下,在时钟周期3、4内保持这种可写状态。因为一般情况下,只有第三周期RAM才可以设置为可写,第四周期只是给RAM提供CLK信号而没有任何其他的操作。

时钟周期04

第四周期只是给RAM一个上升沿,没有任何其他的操作。

时钟周期05

第五周期给寄存器组一个上升沿,若寄存器组的R_W为1,则给指定的寄存器写入输入的数据。

至此我们试着运行了一条指令,这条指令是MOV R0, 3,也就是给寄存器R0写入立即数0000000000000011(3)。

结构分析

CLK时钟

这个没什么好说的,就是一个循环计数器+译码器。

ProgramLoaded用于启用计数器,也就是说只有ROM的程序已经全部加载到RAM才会开始CPU的5个时钟周期。

程序计数器PC

不断把寄存器内的值+1,但当IsOffset输入为1时,寄存器内的值不再是+1而是加上Offset。

指令寄存器

这个就更简单了,就是一个普通的寄存器。

寄存器组

这个结构稍微复杂一点,先介绍一下各输入输出的含义。

InputData:输入寄存器的内容。

W_RegADDR:欲写入寄存器的地址。

R_W:寄存器读取/写入,0读取1写入。

CLK:时钟信号。

OA_ADDR:右侧OA输出的是哪个寄存器的值。

OB_ADDR:同上。

RST:重置RST_ADDR输入值对应的寄存器

RSTAll:重置所有寄存器

ALU模块

这个也很简单,就是通过一个复用器来选择不同模式。这个复用器有16个输出,因此可以有16种运算,这里我只做了加法和减法两种。

指令结构分析

所有指令前四位是指令代码,也就是说这个结构的CPU的指令集中最多只有15条指令。
本CPU只有4个寄存器,故只用两位二进制数就可以表示所有寄存器。

情况1

MOV R0, 3 给R0写入数字3

0011 00(寄存器) 00(无意义) 00000011(立即数)

STORE R2, 11111111 将寄存器R2的内容存在内存地址为11111111的位置上

0010 10(寄存器) 00(无意义) 11111111(内存地址)

LOAD R3, 11111111 将内存地址为11111111的值输入寄存器R3

0001 11(寄存器) 00(无意义) 11111111(内存地址)

通过上面的例子,我们知道后八位可以用来表示内存地址,而本来是ALUCode的位置,由于后八位已经被占用,因此只能用AS1表示寄存器。

情况2

ALU 0001, R1, R1, R0 进行ALU运算,模式为0001(加法),表达式R1 = R1 + R0

0100 0001(ALU) 01(赋值目标寄存器) 01(输出寄存器A)00(输出寄存器B) 00(无意义)

通过这个ALU运算可以发现,ALUCode要输入到ALU,AS1、AS2不能再表示寄存器了,因此只能用后8位表示寄存器,但是只有S1、S2、S3有实际意义。

情况3

JUMP_GT R1, R2, 0001, 1, 1 意思就是 if (R2 > R1) JUMP_S 0001

1000 000001(位移Offset) 01(条件A寄存器)10(条件B寄存器) 1(若为1则交换条件AB的位置) 1(若为1则向后位移 [-] ,反之向前 [+])

这里仍然是后八位表示寄存器,但是把最后两位也一起用上了。

情况4

HALT

0101 000000000000(无意义)

HALT只需要识别前四位即可,表示程序停止。

指令集

具体的指令上面已经介绍过了,这里就不再赘述了。

LOAD 0001 # 从内存加载值
STORE 0010 # 将寄存器的值存入内存
MOV 0011 # 给寄存器写入一个值
ALU 01000001 # 加法
ALU 01000010 # 减法
HALT 0101 # 程序终止
JUMP_A 0110 # 向前跳转
JUMP_S 0111 # 向后跳转
JUMP_GT 1000 # 当大于条件成立时跳转
JUMP_LT 1001 # 当大于条件成立时跳转
JUMP_EQ 1010 # 当等于条件成立时跳转

完整的程序

先用高级语言来表达一下

int R0 = 3;
int R1 = 4;
int R2;
R2 = 9;
int R3 = R2;
while(R2 > R1) {
    R1 = R1 + R0;
}

下面是指令

     二进制       十六进制     表达式
0011000000000011   3003    MOV R0, 3
0011010000000100   3404    MOV R1, 4
0011100000001001   3809    MOV R2, 9
0010100011111111   28ff    STORE R2, 11111111
0001110011111111   1cff    LOAD R3, 11111111
0100000101010000   4150    ALU 0001, R1, R1, R0
1000000001011011   805b    JUMP_GT R1, R2, 0001, 1, 1
0101000000000000   5000    HALT

ROM里面我已经输入好了指令,试着来运行一下

利用Logisim自带的模拟时钟运行完这个程序,可以看到已经读取到了5000并且提示程序结束。

最后

花了一天时间做了这个CPU,遇到了很多问题,经过很多测试才有了这个成品。

因为我没有学过任何相关的专业课程,如果图中设计或上文语言表达中有不合理的地方,欢迎指出批评!

未经允许禁止转载本站内容,经允许转载后请严格遵守CC-BY-NC-ND知识共享协议4.0,代码部分则采用GPL v3.0协议
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇