- 实验类型
- 本实验为设计型实验。
- 实验目的
-
① 具有多周期控制器的设计能力。
② 掌握用 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]