16位CPU怎么做,DIY大神给你支支招儿

电子工程世界 中字

状态机基本上与系统所有的组件都连接到一起了,因为上面所说的所有动作的执行,都需要状态机的控制,状态机其实就是由一部分触发器构成的记忆电路和另外一部 分组合逻辑构成的次态译码电路构成,还有根据当前状态和输入进行译码的部分用于控制各个部件,下面是教科书上的典型FSM结构:

而我们用的状态机状态转移图如下:

因为这个处理器设计的很简单,所以没有出现很多状态,当处理器经历完以上的状态之后,处理器就执行完了一条指令。

有的CISC的处理器用微指令进行控制,作用和状态机相近,这种结构出现在一些比较古老的处理器上,因为那个时候的设计工具和方法没有现在的先进,所以往往 改动硬件是困难的和高成本的,所以用微指令的话,做好了硬件的结构,要是需要改动只要修改微指令就好了,而现在的电子技术很发达,设计工具也很完备,所以 就有很多直接通过硬连线实现的处理器。

好马配好鞍,有了处理器,我们就得给它配上一个好的程序,下面我们就用自己设计的处理器进行求和,从1加到100,因为我们没有设计编译器,也没有设计汇编器,所以程序只能用机器码写出,示例程序如下:

我们不妨先写出程序的汇编代码:

mov [ADDR],r0;r0 = 0

mov r1,100

lop:add r2,r1

sub r1,1

cmp r1,0

jz ext

mov r4,4

jmp r4(lop)

ext:mov [ADDR],r2

jmp $

先将内存中存放数据的地址清零,这样才能存放等下送来的结果,然后将r1寄存器存入循环次数(也就是求和的上限)。然后再将r1的值加到r2中来,r2其实就是放求和的寄存器,最后我们会将r2中的值送到内存中的某个地址存放的。

然 后将r1减去1,看看是否为0?如果为0则说明求和结束了,如果不是0则说明还要继续,结束后程序就跳到ext部分将结果存放到内存中某个地址(例子中给 的是49152也就是二进制的1100000000000000b),最后jmp $是为了让程序停在这一行,防止程序跑飞(跑飞的程序危害很大!有可能吧数据当代码或者把代码当数据!)

转换成VerilogHDL语言如下:

module memory

input [15:0] addr,

inout [15:0] data,

input rw

);

reg [15:0] data_ram[0:16'b1111_1111_1111_1111];

integer i;

initial begin

for (i = 0; i <= 16'b1111_1111_1111_1111; i = i + 1)

data_ram[i] = $random();

data_ram[0] = 16'b1000000100000000; //mov [ADDR],r0;r0 = 0

data_ram[1] = 16'b1100000000000000; //ADDR

data_ram[2] = 16'b1000000010001000; //mov r1,100

data_ram[3] = 100; //100

//data_ram[2] = 16'b1110011001000000;

data_ram[4] = 16'b0010000100010001; //lop:add r2,r1

data_ram[5] = 16'b1110000011001000; //sub r1,1

data_ram[6] = 16'b0000000000000001; //1

data_ram[7] = 16'b1110000000001000; //cmp r1,0

data_ram[8] = 16'b0000000000000000; //0

data_ram[9] = 16'b1110011010000000; //jz ext

data_ram[10] = 16'b0000000000000011; //+3 offset(ext)

data_ram[11] = 16'b1000000010100000;//mov r4,4

data_ram[12] = 16'b0000000000000100;

data_ram[13] = 16'b0110011001100000;//jmp r4(lop)

data_ram[14] = 16'b1000000100000010;//ext:mov [ADDR],r2

data_ram[15] = 16'b1100000000000000;//ADDR

data_ram[16] = 16'b1110011001000000;//jmp $

data_ram[17] = 16'b1111111111111110;//-2 offset($)

/*data_ram[0] = 16'b1000000010000000; //mov r0,imm

data_ram[1] = 16'b0011111111111111; //imm

data_ram[2] = 16'b0000000001111000; //mov r7,r0

data_ram[3] = 16'b1000000010011000; //mov r3,0

data_ram[4] = 16'b0000000000000000;

data_ram[5] = 16'b1000000010100000; //mov r4,code of jmp r5

data_ram[6] = 16'b0110011001101000; //jmp r5

data_ram[7] = 16'b0000000101011100; //mov [r3],r4

data_ram[8] = 16'b1000000011110000; //mov r6,[0]

data_ram[9] = 16'b0000000000000000; //[0]

data_ram[10]= 16'b1000000100000110; //mov [255],r6

data_ram[11]= 16'b0000000011111111;

data_ram[12]= 16'b0110011001011000; //jmp r3

*/

end

always @ (addr or rw or data)

if (rw)

data_ram[addr] = data;

assign data = rw ? 16'hzzzz : data_ram[addr];

endmodule

设计中CPU外围还需要一个内存设备(Memory),我用HDL对其建模,初始化的时候每个内存地址上对应的数据都初始化为随机的,然后只有从0开始的一系列地址被初始化为我写的代码,机器码对应的汇编指令在注释中已经给出。

然后是结果,结果应该是r2从0变化到5050(1+2+3+......+100=5050)

而r1则从100变化到0,变化到0后程序将进入死循环,停止在jmp $那一条。这是仿真开始的时候:

大家可以看到初始化后,d0~d7都变成了0,这是r0~r7寄存器的Q端,而state_current和state_next则是状态机的现态和状态机 的次态,cpu的各个部件都通过这个状态机受到控制。状态名出现的顺序和上面的FSM Viewer的连线顺序是一样的。

而且大家可以看到,d2从0变化到了0x64也就是十进制100,说明已经执行了第一次加法了。

再来看看仿真结束:

这时候d1变化到了0而d2变化到了0x13ba(十进制的5050),说明程序已经在我们设计的处理器里面运行并且成功的得出了结果!

最后给出一些我用到的指令(跟x86的很像):

add dst,src 将src和dst相加并且送到dst寄存器中

mov [addr],src 将src的值送到以addr位地址的内存单元

sub dst,src 将dst减去src并且送到dst中去

cmp dst,src 将dst减去src 然后不送到dst中 只改变标志位

jz dst 当zf=1时(即上次的算术操作结果为0)则跳转到dst中去

最后再提一下:

我是用synplify综合的电路,然后用debussy+modelsim仿真的。

声明: 本文系OFweek根据授权转载自其它媒体或授权刊载,目的在于信息传递,并不代表本站赞同其观点和对其真实性负责,如有新闻稿件和图片作品的内容、版权以及其它问题的,请联系我们。
侵权投诉

下载OFweek,一手掌握高科技全行业资讯

还不是OFweek会员,马上注册
打开app,查看更多精彩资讯 >
  • 长按识别二维码
  • 进入OFweek阅读全文
长按图片进行保存