其實跟使用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的添加完成。