其实跟使用logisim搭建CPU基本一致,甚至更简单,由于彻底能够照着logisim的电路图来写,各个模块和模块间的链接在logisim中很是清楚。惟一改变了的只有GRF和DM要多一个input PC端口,用来display的时候输出PC值;IFU同理多了一个output PC,用来把PC的值传给GRF和DM。其余的模块我都是直接对着logisim原封不动地用Verilog从新实现了一遍。目前支持指令集{addu、subu、ori、lw、sw、beq、jal、jr、nop、lui、sb、lb、sh、lh、jalr、addi}。ui
端口以下图所示,仅多了一个output PC,实现应该是很是简单的。3d
可是要注意一点,若是进行了初始化(以下),那么必定不能用非阻塞赋值,不然你会发现你的IM根本读不进code.txt里的内容(非阻塞赋值会在initial后进行赋值0,读的code.txt又被清成0了,因此啥也读不到)code
initial begin pc = 32'h00003000; for(i=0;i<1024;i = i + 1) begin im[i] = 32'h00000000;//正确写法 //im[i] <= 32'h00000000;//错误写法 end $readmemh("code.txt",im); end
端口多了一个input PC,用来display的时候能够获取到PC值以输出。blog
这个须要注意的是0号寄存器,他不能被写入,特判一下就能够。也能够和我同样reg [31:0] rf[31:1];
,根本没有0号寄存器天然也写不了他,而后输出寄存器值的时候assign RD1 = (A1 == 0) ? 32'b0 : rf[A1];
,判断是否为输出0号寄存器的值。ip
用位拼接写,很是简单,符号扩展就将最高位复制就能够了。input
assign ext_imm16 = (EXTOp == 2'b00) ? {{16{1'b0}},imm16[15:0]} : (EXTOp == 2'b01) ? {{16{imm16[15]}},imm16[15:0]} : (EXTOp == 2'b10) ? {imm16[15:0],{16{1'b0}}} : {{16{1'b0}},imm16[15:0]};
可是注意不要在位拼接里出现没位数的数,Verilog会默认成32位,而不是你想象中的1位。错误示范以下:it
assign ext_imm16 = (EXTOp == 2'b00) ? {{16{0}},imm16[15:0]} : (EXTOp == 2'b01) ? {{16{imm16[15]}},imm16[15:0]} : (EXTOp == 2'b10) ? {imm16[15:0],{16{0}}} : {{16{0}},imm16[15:0]};
总之写Verilog的时候养成好习惯吧,数字都加上位数和进制,防止在奇怪的地方出错还找不到bug。class
这个就更简单了。没啥写的就说说上次那个奇偶校验跳转的指令吧。原理
首先Verilog里是有异或运算的:^
,这个就是异或,不要课上用|
和~
手写一个异或出来。而后咱们知道缩减运算符^A
就等于A[31]^A[30]^A[29]^...^A[1]^A[0]
。那么A中有奇数个1就至关于^A == 1
,A中有偶数个1就至关于^A == 0
,而后就跟beq同样跳转就能够了(beq的Zero是A == B
,这个指令的Zero是^A
)扩展
这个也比较简单,我依然加了lb、sb、lh、sh指令,在Verilog里写只须要用位拼接来写就能够了,比logisim方便很多。
对写入数据进行处理:(SSel为2'b00即原数据,SSel为2'b01即sb时,SSel为2'b10即sh时)
always @(*) begin if(SSel == 2'b00) begin WData = WD; end else if(SSel == 2'b01) begin WData = (A[1:0] == 2'b00) ? {dm[A[11:2]][31:8],WD[7:0]} : (A[1:0] == 2'b01) ? {dm[A[11:2]][31:16],WD[7:0],dm[A[11:2]][7:0]} : (A[1:0] == 2'b10) ? {dm[A[11:2]][31:24],WD[7:0],dm[A[11:2]][15:0]} : {WD[7:0],dm[A[11:2]][23:0]}; end else if(SSel == 2'b10) begin WData = (A[1] == 1'b0) ? {dm[A[11:2]][31:16],WD[15:0]} : {WD[15:0],dm[A[11:2]][31:16]}; end end
对读出数据进行处理:(LSel为2'b00即原数据,为2'b01即lb时,2'b10即lh时)
always @(*) begin case(LSel) 2'b00:begin RD = dm[A[11:2]]; end 2'b01:begin RD = (A[1:0] == 2'b00) ? {{24{dm[A[11:2]][7]}},dm[A[11:2]][7:0]} : (A[1:0] == 2'b01) ? {{24{dm[A[11:2]][15]}},dm[A[11:2]][15:8]} : (A[1:0] == 2'b10) ? {{24{dm[A[11:2]][23]}},dm[A[11:2]][23:16]} : {{24{dm[A[11:2]][31]}},dm[A[11:2]][31:24]}; end 2'b10:begin RD = (A[1] == 1'b0) ? {{16{dm[A[11:2]][15]}},dm[A[11:2]][15:0]} : {{16{dm[A[11:2]][31]}},dm[A[11:2]][31:16]}; end default:RD = 32'h00000000; endcase end
话说回来个人测评点生成机好像忘记了测lb、sb、lh、sh(逃
这个必定不要按高老板ppt里的那个写。我一开始按他的写而后de了半天才找到原来是MUX的错误。
高老板写法:
后来改为了三目运算符就AC了。。。如今也没看懂他的是什么原理(也多是对的?
assign Out = (S0 == 0 && S1 == 0) ? D0 : (S0 == 1 && S1 == 0) ? D1 : (S0 == 0 && S1 == 1) ? D2 : D3 ;
先写宏定义
`define ADDU 6'b100001 `define SUBU 6'b100011 `define ORI 6'b001101 `define LW 6'b100011 `define SW 6'b101011 `define BEQ 6'b000100 `define JAL 6'b000011 `define JR 6'b001000 `define LUI 6'b001111 `define LB 6'b100000 `define SB 6'b101000 `define LH 6'b100001 `define SH 6'b101001 `define RTYPE 6'b000000 `define ADDI 6'b001000 `define JALR 6'b001001 `define J 6'b000010
以后每个指令都用一个wire表示,注意用宏定义加`,以及R型指令是Rtype与funct的与。
wire addu,subu,ori,lw,sw,beq,jal,jr,lui,lb,sb,lh,sh,addi,jalr,j; assign RType = (opcode == `RTYPE); assign addu = RType&(funct == `ADDU); assign subu = RType&(funct == `SUBU); assign ori = (opcode == `ORI ); assign lw = (opcode == `LW); assign sw = (opcode == `SW); assign beq = (opcode == `BEQ); assign jal = (opcode == `JAL); assign jr = RType&(funct == `JR); assign lui = (opcode == `LUI); assign lb = (opcode == `LB); assign sb = (opcode == `SB); assign sh = (opcode == `SH); assign lh = (opcode == `LH); assign addi = (opcode == `ADDI); assign jalr = RType&(funct == `JALR); assign j = (opcode == `J);
以后对每一个控制信号根据真值表加指令,两位的和一位的各举了一个例子。
assign NPCOp[0] = beq | jr | jalr; assign NPCOp[1] = jal | jr | jalr | j; assign RFWr = addu | subu | ori | lw | jal | lui | lb | lh | addi | jalr ;
这个和Controller做为mips的子模块,datapath用来把全部除了controller的模块链接起来,而后在mips里与controller链接。
结构图以下
跟P3同样分析便可。eg:加addi(不考虑溢出)
判断是否须要增长新的通路以实现该指令,如ALU是否要增长计算功能之类的。addi不须要所以直接改控制信号便可。
对于NPCOp,这不是一个跳转指令,所以NPCOp取00
对于RFWr,要回写到R[rt],所以RFWr为1
对于EXTOp,要进行符号扩展,因此取01
对于ALUOp,加法,因此取00
对于DMWr,不用写入DM,因此取0
对于WRSel,因为写入的是R[rt],因此取01
对于WDSel,因为写入的数据来自ALU的计算结果,因此取00
对于BSel,因为参与ALU计算的第二个数来自EXT,因此取1
对于SSel和LSel,因为不涉及半字或字节,都取00
先定义ADDI
`define ADDI 6'b001000
再添加wire addi
assign addi = (opcode == `ADDI);
在addi控制信号为1的地方加上addi。
如RFWr为1,则在 assign RFWr = addu | subu | ori | lw | jal | lui | lb | lh ;
最后或addi。
即变成 assign RFWr = addu | subu | ori | lw | jal | lui | lb | lh | addi;
其余控制信号依次添加便可,加完全部控制信号后,addi的添加完成。