- 實驗類型
- 本實驗為設計型實驗。
- 實驗目的
-
① 具有多周期控制器的設計能力。
② 掌握用 Verilog HDL 實作有限狀态機的常用方法。
③ 熟悉 Vivado 的設計流程,具備硬體的設計仿真和測試能力。
④ 測試多周期控制器的功能。
- 實驗原理
控制器是CPU 的重要組成部分,是整個計算機的控制核心。控制器的功能是按照程式預定的順序執行每條指令。每條指令都是在控制器的控制下按照取指令、分析指令和執行指令的步驟依次完成的, 這就需要控制器必須在正确的時間準确地産生各部件的控制信号, 使計算機能夠有條不紊地完成所有指令的功能。
-
多周期 CPU 把指令執行分成多個階段,每個階段在一個時鐘周期内完成,如取指、譯碼、執行、訪存、寫回。此時,不同指令所用周期數可以不同,每個周期隻做一部分操作。将 CPU 劃分為多周期的優勢在于,每個時鐘周期内 CPU 需要做的工作就變少,是以時鐘周期短、時鐘頻率高;每個部件做的事情單一,如取指部件隻負責從指令存儲器中取出指令,是以 CPU 可以進行流水工作,相當于一個時鐘周期完成一條指令, CPU 可以更快地運作。
多周期 MIPS CPU 的控制部件的狀态轉移如下圖 所示,每個狀态對應一個周期。
- 本實驗根據狀态及指令直接對控制信号指派,中間變量 next_state 意為下一狀态。在目前狀态中,根據指令對 next_state 指派,并在每個時鐘上升沿把 next_state 存入狀态寄存器,這是用 Verilog HDL 實作有限狀态機時常用的方法。
-
實驗内容和要求
① 學習 MIPS 指令集,深入了解常用指令的功能和編碼,并進行歸納确定處理器各部件的控制碼,如使用何種運算、是否寫寄存器堆等。
② 參考附錄 E,根據本實驗準備實作的 20 條 MIPS 指令和最後一行跳轉指令的示例,将下表補充填寫完整。
指令類型 彙編指令 指令碼 源操作數1 源操作數2 目的寄存器 功能描述
R型指令 add rd, rs, rt 000000 rs | rt | rd | 00000|100000
I型指令 addi rt, rs, imm 001000 rs|rt|imm
J型指令 j target 000010 | target PC target PC 跳轉
PC={PC[31:28],target,2’b00}
③ 自行設計本實驗的方案,多周期 MIPS CPU 的控制部件的大緻結構如下圖所示。
④ 根據設計的實驗方案,使用 Verilog HDL 編寫相應代碼。
⑤ 對編寫的代碼進行仿真,得到正确的波形圖。
⑥ 将以上設計作為一個單獨的子產品,設計一個外圍子產品去調用該子產品,如下圖所示。
外圍子產品中需調用封裝好的 LCD 觸摸屏子產品, 觀察多周期 CPU 的控制器的狀态和各輸出控制信号的值等,并且需要利用觸摸功能輸入指令的操作碼、功能碼,以達到實時觀察控制信号變化的效果。通過這些手段,可以在實驗箱上充分驗證 CPU 的正确性。
源代碼
mccu.v:
module mccu (op,func,z,clock,resetn,wpc,wir,wmem,wreg,iord,regrt,m2reg,aluc,shift
,alusrca,alusrcb,pcsource,jal,sext,state);
input [5:0] op,func;
input z,clock,resetn;
output reg wpc,wir,wmem,wreg,iord,regrt,m2reg;
output reg [3:0] aluc;
output reg [1:0] alusrcb,pcsource;
output reg shift,alusrca,jal,sext;
output reg [2:0] state;
reg [2:0] next_state;
parameter [2:0] sif = 3'b000,
sid = 3'b001,
sexe = 3'b010,
smem = 3'b011,
swb = 3'b100;
wire r_type,i_add,i_sub,i_and,i_or,i_xor,i_sll,i_srl,i_sra,i_jr;
wire i_addi,i_andi,i_ori,i_xori,i_lw,i_sw,i_beq,i_bne,i_lui,i_j,i_jal;
//and(r_type,~op[5],~op[4],~op[3],~op[2],~op) ;
and(r_type,~op[5],~op[4],~op[3],~op[2],~op[1],~op[0]);
and(i_add,r_type,func[5],~func[4],~func[3],~func[2],~func[1],~func[0]);
and(i_sub,r_type,func[5],~func[4],~func[3],~func[2],func[1],~func[0]);
and(i_and,r_type,func[5],~func[4],~func[3],func[2],~func[1],~func[0]);
and(i_or,r_type,func[5],~func[4],~func[3],func[2],~func[1],func[0]);
and(i_xor,r_type,func[5],~func[4],~func[3],func[2],func[1],~func[0]);
and(i_sll,r_type,~func[5],~func[4],~func[3],~func[2],~func[1],~func[0]);
and(i_srl,r_type,~func[5],~func[4],~func[3],~func[2],func[1],~func[0]);
and(i_sra,r_type,~func[5],~func[4],~func[3],~func[2],func[1],func[0]);
and(i_jr,r_type,~func[5],~func[4],func[3],~func[2],~func[1],~func[0]);
and(i_addi,~op[5],~op[4],op[3],~op[2],~op[1],~op[0]);
and(i_andi,~op[5],~op[4],op[3],op[2],~op[1],~op[0]);
and(i_ori,~op[5],~op[4],op[3],op[2],~op[1],op[0]);
and(i_xori,~op[5],~op[4],op[3],op[2],op[1],~op[0]);
and(i_lw,op[5],~op[4],~op[3],~op[2],op[1],op[0]);
and(i_sw,op[5],~op[4],op[3],~op[2],op[1],op[0]);
and(i_beq,~op[5],~op[4],~op[3],op[2],~op[1],~op[0]);
and(i_bne,~op[5],~op[4],~op[3],op[2],~op[1],op[0]);
and(i_lui,~op[5],~op[4],~op[3],op[2],op[1],op[0]);
and(i_j,~op[5],~op[4],~op[3],~op[2],op[1],~op[0]);
and(i_jal,~op[5],~op[4],~op[3],~op[2],op[1],op[0]);
wire i_shift;
or (i_shift,i_sll,i_srl,i_sra);
always @ * begin
wpc = 0;
wir = 0;
wmem = 0;
wreg = 0;
iord = 0;
aluc = 4'bx000;
alusrca = 0;
alusrcb = 2'h0;
regrt = 0;
m2reg = 0;
shift = 0;
pcsource = 2'h0;
jal = 0;
sext = 1;
case (state)
sif:begin
wpc = 1;
wir = 1;
alusrca = 1;
alusrcb = 2'h1;
next_state = sid;
end
sid:begin
if(i_j)begin
pcsource = 2'h3;
wpc = 1;
next_state = sif;
end
else if(i_jal)begin
pcsource = 2'h3;
wpc = 1;
jal = 1;
wreg = 1;
next_state = sif;
end
else if(i_jr)begin
pcsource = 2'h2;
wpc = 1;
next_state = sif;
mccu_display.v:
//*************************************************************************
// > 檔案名: mccu_display.v
// > 描述 :mccu 顯示子產品,調用 FPGA 闆上的 IO 接口和觸摸屏
//*************************************************************************
module mccu_display(
//時鐘與複位信号
input clk,
input resetn, //字尾"n"代表低電平有效
//脈沖開關,用于産生脈沖 clk,實作單步執行
input btn_clk,
//撥碼開關,用于選擇輸入數
input [1:0] input_sel, //00:輸入為控制信号(op)
//10:輸入為源操作數 1(func)
//11:輸入為源操作數 2(z)
//觸摸屏相關接口,不需要更改
output lcd_rst,
output lcd_cs,
output lcd_rs,
output lcd_wr,
output lcd_rd,
inout[15:0] lcd_data_io,
output lcd_bl_ctr,
inout ct_int,
inout ct_sda,
output ct_scl,
output ct_rstn
);
//-----{時鐘和複位信号}begin
//不需要更改,用于單步調試
wire cpu_clk; //多周期 CPU 裡使用脈沖開關作為時鐘,以實作單步執行
reg btn_clk_r1;
reg btn_clk_r2;
always @(posedge clk)
begin
if (!resetn)
begin
btn_clk_r1<= 1'b0;
end
else
begin
btn_clk_r1 <= ~btn_clk;
end
btn_clk_r2 <= btn_clk_r1;
end
wire clk_en;
assign clk_en = !resetn || (!btn_clk_r1 && btn_clk_r2);
BUFGCE cpu_clk_cg(.I(clk),.CE(clk_en),.O(cpu_clk));
//-----{時鐘和複位信号}end
//-----{調用 mccu 子產品}begin
reg [5:0] op; // 操作碼
reg [5:0] func; // 功能碼
//-----{此處省略,請自己編寫} ALU 結果為零标志
reg z;
wire [3:0] aluc; // ALU 控制信号
wire [1:0] alusrcb, pcsource;
wire shift,alusrca,jal,sext,wpc,wir,wmem,wreg,iord,regrt,m2reg;
wire [2:0] state;
mccu m(
.op(op),
.func(func),
.z(z),
.clock(cpu_clk),
.resetn(resetn),
.wpc(wpc),
.wir(wir),
.wmem(wmem),
.wreg(wreg),
.iord(iord),
.regrt(regrt),
.m2reg(m2reg),
.aluc(aluc),
.shift(shift),
.alusrca(alusrca),
.alusrcb(alusrcb),
.pcsource(pcsource),
.jal(jal),
.sext(sext),
.state(state)
//-----{此處省略,請自己編寫}
);
//-----{調用 mccu 子產品}end
//---------------------{調用觸摸屏子產品}begin--------------------//
//-----{執行個體化觸摸屏}begin
//此小節不需要更改
reg display_valid;
reg [39:0] display_name;
reg [31:0] display_value;
wire [5 :0] display_number;
wire input_valid;
wire [31:0] input_value;
lcd_module lcd_module(
.clk (clk ), //10Mhz
.resetn (resetn ),
//調用觸摸屏的接口
.display_valid (display_valid ),
.display_name (display_name ),
.display_value (display_value ),
.display_number (display_number),
.input_valid (input_valid ),
.input_value (input_value ),
//lcd 觸摸屏相關接口,不需要更改
.lcd_rst (lcd_rst ),
.lcd_cs (lcd_cs ),
.lcd_rs (lcd_rs ),
.lcd_wr (lcd_wr ),
.lcd_rd (lcd_rd ),
.lcd_data_io (lcd_data_io ),
.lcd_bl_ctr (lcd_bl_ctr ),
.ct_int (ct_int ),
.ct_sda (ct_sda ),
.ct_scl (ct_scl ),
.ct_rstn (ct_rstn )
);
//-----{執行個體化觸摸屏}end
//-----{從觸摸屏擷取輸入}begin
//根據實際需要輸入的數修改此小節,
//建議對每一個數的輸入,編寫單獨一個 always 塊
//當 input_sel 為 00 時,表示輸入 op
always @(posedge clk)
begin
if (!resetn)
begin
op <= 4'd0;
end
else if (input_valid && input_sel==2'b00)
begin
op <= input_value[5:0];
end
end
always @(posedge clk)
begin
if (!resetn)
begin
func <= 4'd0;
end
else if (input_valid && input_sel==2'b01)
begin
func <= input_value[5:0];
end
end
always @(posedge clk)
begin
if (!resetn)
begin
z <= 4'd0;
end
else if (input_valid && input_sel==2'b11)
begin
z <= input_value[0];
end
end
//-----{此處省略,請自己編寫}
//-----{從觸摸屏擷取輸入}end
//-----{輸出到觸摸屏顯示}begin
//根據需要顯示的數修改此小節,
//觸摸屏上共有 44 塊顯示區域,可顯示 44 組 32 位資料
//44 塊顯示區域從 1 開始編号,編号為 1~44,
always @(posedge clk)
begin
case(display_number)
6'd1 :
begin
display_valid <= 1'b1;
display_name <= "OP";
display_value <= op;
end
6'd2 :
begin
display_valid <= 1'b1;
display_name <= "FUNC";
display_value <= func;
end
6'd3 :
begin
display_valid <= 1'b1;
display_name <= "Z";
display_value <= z;
end
6'd4 :
begin
display_valid <= 1'b1;
display_name <= "STATE";
display_value <= state;
end
6'd5 :
begin
display_valid <= 1'b1;
display_name <= "WPC";
display_value <= wpc;
end
6'd6 :
begin
display_valid <= 1'b1;
display_name <= "WIR";
display_value <= wir;
end
6'd7 :
begin
display_valid <= 1'b1;
display_name <= "WMEM";
display_value <= wmem;
end
6'd8 :
begin
display_valid <= 1'b1;
display_name <= "WREG";
display_value <= wreg;
end
6'd9 :
begin
display_valid <= 1'b1;
display_name <= "IORG";
display_value <= iord;
end
6'd10 :
begin
display_valid <= 1'b1;
display_name <= "REGRT";
display_value <= regrt;
end
6'd11 :
begin
display_valid <= 1'b1;
display_name <= "M2REG";
display_value <= m2reg;
end
6'd12 :
begin
display_valid <= 1'b1;
display_name <= "ALUC";
display_value <= aluc;
end
6'd13 :
begin
display_valid <= 1'b1;
display_name <= "SHIFT";
display_value <= shift;
end
6'd14 :
begin
display_valid <= 1'b1;
display_name <= "ALUSRCA";
display_value <= alusrca;
end
6'd15 :
begin
display_valid <= 1'b1;
display_name <= "ALUSRVB";
display_value <= alusrcb;
end
6'd16 :
begin
display_valid <= 1'b1;
display_name <= "PCSOURCE";
display_value <= pcsource;
end
6'd17 :
begin
display_valid <= 1'b1;
display_name <= "JAL";
display_value <= jal;
end
6'd18 :
begin
display_valid <= 1'b1;
display_name <= "SEXT";
display_value <= sext;
end
//-----{此處省略,請自己編寫}
default :
begin
display_valid <= 1'b0;
display_name <= 40'd0;
display_value <= 32'd0;
end
endcase
end
//-----{輸出到觸摸屏顯示}end
//----------------------{調用觸摸屏子產品}end---------------------//
mccu.xdc:
#時鐘信号連接配接
set_property PACKAGE_PIN AC19 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
#脈沖開關,用于輸入作為複位信号,低電平有效
set_property PACKAGE_PIN Y3 [get_ports resetn]
set_property IOSTANDARD LVCMOS33 [get_ports resetn]
#脈沖開關,用于輸入作為單步執行的clk
set_property PACKAGE_PIN Y5 [get_ports btn_clk]
set_property IOSTANDARD LVCMOS33 [get_ports btn_clk]
#撥碼開關連接配接,用于輸入
set_property PACKAGE_PIN AC21 [get_ports input_sel[1]]
set_property PACKAGE_PIN AD24 [get_ports input_sel[0]]
set_property IOSTANDARD LVCMOS33 [get_ports input_sel[1]]
set_property IOSTANDARD LVCMOS33 [get_ports input_sel[0]]
#觸摸屏引腳連接配接
set_property PACKAGE_PIN J25 [get_ports lcd_rst]
set_property PACKAGE_PIN H18 [get_ports lcd_cs]
set_property PACKAGE_PIN K16 [get_ports lcd_rs]
set_property PACKAGE_PIN L8 [get_ports lcd_wr]
set_property PACKAGE_PIN K8 [get_ports lcd_rd]
set_property PACKAGE_PIN J15 [get_ports lcd_bl_ctr]
set_property PACKAGE_PIN H9 [get_ports {lcd_data_io[0]}]
set_property PACKAGE_PIN K17 [get_ports {lcd_data_io[1]}]
set_property PACKAGE_PIN J20 [get_ports {lcd_data_io[2]}]
set_property PACKAGE_PIN M17 [get_ports {lcd_data_io[3]}]
set_property PACKAGE_PIN L17 [get_ports {lcd_data_io[4]}]
set_property PACKAGE_PIN L18 [get_ports {lcd_data_io[5]}]
set_property PACKAGE_PIN L15 [get_ports {lcd_data_io[6]}]
set_property PACKAGE_PIN M15 [get_ports {lcd_data_io[7]}]
set_property PACKAGE_PIN M16 [get_ports {lcd_data_io[8]}]
set_property PACKAGE_PIN L14 [get_ports {lcd_data_io[9]}]
set_property PACKAGE_PIN M14 [get_ports {lcd_data_io[10]}]
set_property PACKAGE_PIN F22 [get_ports {lcd_data_io[11]}]
set_property PACKAGE_PIN G22 [get_ports {lcd_data_io[12]}]
set_property PACKAGE_PIN G21 [get_ports {lcd_data_io[13]}]
set_property PACKAGE_PIN H24 [get_ports {lcd_data_io[14]}]
set_property PACKAGE_PIN J16 [get_ports {lcd_data_io[15]}]
set_property PACKAGE_PIN L19 [get_ports ct_int]
set_property PACKAGE_PIN J24 [get_ports ct_sda]
set_property PACKAGE_PIN H21 [get_ports ct_scl]
set_property PACKAGE_PIN G24 [get_ports ct_rstn]
set_property IOSTANDARD LVCMOS33 [get_ports lcd_rst]
set_property IOSTANDARD LVCMOS33 [get_ports lcd_cs]
set_property IOSTANDARD LVCMOS33 [get_ports lcd_rs]
set_property IOSTANDARD LVCMOS33 [get_ports lcd_wr]
set_property IOSTANDARD LVCMOS33 [get_ports lcd_rd]
set_property IOSTANDARD LVCMOS33 [get_ports lcd_bl_ctr]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[8]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[9]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[10]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[11]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[12]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[13]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[14]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[15]}]
set_property IOSTANDARD LVCMOS33 [get_ports ct_int]
set_property IOSTANDARD LVCMOS33 [get_ports ct_sda]
set_property IOSTANDARD LVCMOS33 [get_ports ct_scl]
set_property IOSTANDARD LVCMOS33 [get_ports ct_rstn]