前言
之所以开设“前人种树”专栏,是因为笔者十分推崇某位牛马前辈,当初刚入学对实验一筹莫展时偶然接触到这位前辈分享的材料,且不说对我有没有帮助,反正从那以后我经常看他分享的实验材料。慢慢地看多了之后才发现,原来这位前辈发材料的初衷可能没有我想的那么高尚,不是为了作为前人种树让后来人乘凉的,纯纯为了炫耀。为什么这么说呢?因为基本上他所有的公开分享的材料,都是要么缺斤少两,要么就在某个细节上把原来正确的改成错的。而他收费恰米的材料,都是完全正确可以直接拿来用的。
所以我呢,就把自己的实验材料也做个分享,不缺斤少两,更不故意把对的改成错的让后面的学弟们找半天,故意捉弄人嘛这不是?【楽】希望我这个种树的前人能对后人们有些许帮助,也希望各位是在弄懂我的东西后才去套用甚至自创。
扯远了,计组实验是小组实验,一般三到四个人一组,得上硬件实验机房去烧板子,所谓烧板子就是用一堆线按规定连接电脑和电路板,在电脑的相应软件上编程后烧录进板子。有四个实验,难度依次递增,前两个必做,能拿到及格,后两个选做一个,做第三个能拿良,做第四个能冲优。笔者带了两个组员,做的是前三个,拿了良。
实验报告放下文,至于具体细节和知识,笔者敢打包票,全忘了,没错,全忘光了。。。因为笔者是走纯软件路线的,这些个硬件相关的知识老师不教,做实验纯靠自学,本来就是似懂非懂半桶水功夫,实验做完之后也根本用不到,且笔者如今回首整理资料,时间间隔久远,就忘了。所以各位看官有不懂的地方问笔者大概率是不会的,但可以私信笔者要代码,笔者会尽可能多地把当年的所有资料发你,不过里面的内容良莠不齐,有用的代码烦请各位自己找找吧,肯定是包含在里面的。
实验一 运算器设计实验
- 目的与要求
- 目的
- 熟悉VHDL硬件描述语言及开发环境,了解硬件系统开发的基本过程。
- 掌握ALU基本设计方法和运算器的数据传送通路。
- 掌握内存(RAM)的访问时序和方法。
- 理解总线数据传输的基本原理。
- 理解控制器的功能,加深对多周期CPU的控制器与指令周期的理解。
- 要求
2.1运用所学的知识和方法采用最佳方案完成实验设计。
-
- 编写简洁代码完成实验要求内容。
- 认真书写实验报告,包括实验目的、实验内容、实验设计方案、实验仿真结果等。
二、实验正文
实验内容
-
- 编程实现16位ALU的定点算术运算、逻辑运算等基本功能,ALU的数据输入A、B的长度也为16位,操作码OP位4位,实验班上输入的码是右边到左边顺序为15~0。
- 最终结果能实现一个状态机,根据状态机的状态变化来输入操作码和操作数,并最终实现不同的运算,将结果和标志位在实验板上对应物理位置的灯呈现出来。
实验步骤
-
- 创建空白工程
- 添加源文件及引脚约束文件等
- 编辑源代码
- 在实验板子上有红色大字(七段数码管)“0,1,2,3,4”表示输入的步骤,然后输入操作码后决定运算功能,再输入数据进行演示,最后在红色大字旁边的16个LED灯上有对应的亮或者灭,分别是结果和标志位对应的结果。
部分代码
begin
rst<='0';
wait for clk_period*10;
rst<='1';
clk<='0';
wait for clk_period*10;
clk<='1';
INPUT<="0000000000000001";
wait for clk_period*10;
clk<='0';
wait for clk_period*10;
clk<='1';
INPUT<="0000000000000001";
wait for clk_period*10;
clk<='0';
wait for clk_period*10;
clk<='1';
INPUT<="0000000000000001";
wait for clk_period*10;
clk<='0';
wait for clk_period*10;
clk<='1';
INPUT<="0000000000000000";
wait for clk_period*10;
clk<='0';
wait for clk_period*10;
clk<='1';
wait for clk_period*10;
clk<='0';
wait for clk_period*10;
clk<='1';
wait for clk_period*10;
clk<='0';
wait for clk_period*10;
clk<='1';
wait for clk_period*10;
wait;
end process;
实验结果
输入数据 | 实际输出 | 与预期一致性 | |||
操作码 | 操作数A | 操作数B | 运算结果 | 标志位 | |
0110XOR | 1001010010110100 | 0101001000110101 | 1100011010000001 | 0010 | 一致 |
0111NOT | 1101011010110100 | 0000000000000000 | 0010100101001011 | 0001 | 一致 |
1001SLL | 1001010010110100 | 0000000000000101 | 1001011010000000 | 0010 | 一致 |
思考题
-
- ALU进行算术逻辑运算所使用的电路是组合逻辑电路还是时序逻辑电路?
答:组合逻辑电路。
-
- 如果给定了A和B的初值,且每次运算完后结果都写入到B中,再进行下次运算。这样一个带暂存功能的ALU要增加一些什么电路来实现?
答:增加tmp暂存器和AC累加器。
三、综合实验总结
- 实验难点
-
- 在输出标志位时,如何通过操作数和操作结果判断标志位;
- 在判断标志位cFlags时,需要仔细考虑指令对标志位的影响及其背后原因,尤其是ADC指令和SBB指令,需要记录每一位的进位并利用循环结构得到最终结果和进位标志位(类似全加器原理)。
- 在判断溢出标志位oFlags时,要灵活掌握操作数和运算结果之间符号位的变化和OF标志位的关系,以便正确设置标志位。
- 在进行移位运算时,要将需要被移位的操作数A的数据类型转换为位矢量类型方可移位,将移位操作数转换成整数。
-
- 心得体会
指令种类不少,需要用心学习每一条指令背后的原理才能更好地设计出ALU的逻辑。
- 个人分工
本人在此实验中负责所有工作,代码编写、硬件操作验证等均由本人完成,并且教会其他两位组员实验板的操作及代码原理。
实验二 存储器实验
一、目的与要求
使用教学计算机上的FPGA芯片,设计一个状态机和内存读写逻辑,完成对存储器RAM的访问。
- 写RAM1。将手拨开关上的数据,写入到RAM1的相应存储单元中,在LED灯上分别显示地址和数据。
- 读RAM1。按CLK键,逐个将刚写入的数据从存储单元中读出,送到LED上显示。
二、实验正文
实验内容
使用教学机上的FPGA芯片,设计一个状态机和内存读写逻辑,完成对存储器RAM的访问。
实验步骤
- 输入信号
- 拨码开关决定读写地址或数据;
- RAM的数据线、地址线;
- LED灯,用来显示从内存中读出的数据。
- 状态机
- 读写状态控制的状态机:控制当前控制器是否处于读或写状态;
- 内存读、写周期的状态机:根据时钟进行跳转,控制读写的过程;
- 在实验过程中可以使用七段数码管检测状态及当前的状态,发光二极管只有16个,可以只显示地址的低8位和数据的低8位。
- 验证过程
- 将编译好的内存控制器对应实验板烧入;
- 用手拨动拨码开关,预设一个值,每个写周期,作为写入内存的数据;每个读周期,该值作为内存地址,从该内存位置处读出数据。该预设值在同一个读/写周期内不变;
- 写周期,LED灯显示当前写入的地址,读周期,LED灯显示从内存中读出的数据;
- 注意观察LED灯上地址和数据的变化,是否符合实验的设计要求;
- 记录实验结果;
部分代码
process(rst,clk)
begin
if rst='0' then --复位信号,全部清零
state<="000";
output<="0000000000000000";
add<="000000000000000000";
data<="0000000000000000";
en<='0';
oe<='0';
rw<='0';
else
if clk'event and clk='1' then--时钟进入上升沿
if state="000" then--选择读或者写
output<=input;
case input(2 downto 0) is
when "001" =>state<="001";-- 进入写入状态(00 01 02 03)
when "100" =>state<="101";-- 进入读出状态(11 12)
when others=>state<="000";
end case;
elsif state="001" then state<="010";--写地址
en<='1';
oe<='1';
rw<='1';
add<="00"&input;--“00”凑齐18位
output<=input;
tempadd<=input;--临时存放地址,用于输出地址的八位
elsif state="010" then state<="011";
data<=input;-- 写入数据
output<=input;
elsif state="011" then state<="000";
en<='0';
oe<='1';
rw<='0';
output<=tempadd(7 downto 0)&data(7 downto 0);-- 输出地址的八位和输出数据的八位
elsif state="101" then state<="110";
en<='1';
oe<='1';
rw<='1';
add<="00"&input;--输入要读取的地址
output<=input;
elsif state="110" then state<="101";
en<='0';
oe<='0';
rw<='1';
end if;
end if;
end if;
end process;
process (state)
begin
case state is --两位状态为,第一位表示0为写,1为读,第二位表示读或写第几个状态
when "000"=>seg<="01111110111111"; --00
when "001"=>seg<="01111110100001"; --01
when "010"=>seg<="01111111011011"; --02
when "011"=>seg<="01111111110011"; --03
when "101"=>seg<="01000010100001"; --11
when "110"=>seg<="01000011011011"; --12
when others=>
end case;
end process;
实验结果
思考题:静态存储器的读、写时序各有什么特点?
答:1、静态RAM 用触发器作为存储单元存放1 和0,存取速度快,只要不掉电即可持续保持内容不变。2、与动态RAM相比,静态RAM的集成度较低,并且静态RAM无须考虑保持数据而设置的刷新电路,故扩展电路较简单。3、由于静态RAM是通过有源电路来保持存储器中的数据,因此,要消耗较多功率,价格也较高。
三、综合实验总结
- 实验难点
一开始并没有搞明白这个实验的目的是什么、最后要完成一个什么样的东西,通过反复审题和上网查阅资料终于搞懂了;
并且一开始也不是很清楚到底哪一排(列)应该显示数据还是地址还是随着拨码开关的状态实时变化。
- 心得体会
虽然该实验比实验一复杂,但在实验一的基础上来做实验二,感觉没那么难,有种举一反三的感觉,只是要多花点时间,多用点心。
- 个人分工
本人在此实验中负责所有工作,代码编写、硬件操作验证等均由本人完成,并且教会其他两位组员实验板的操作及代码原理。
实验三 控制器实验
一、目的与要求
- 编写VHDL程序,实现具有七条MIPS指令功能的控制器。
- 七条指令分别为:ADDU SUBU BNEZ JR OR LW SW。
- 7条指令格式及功能详见实验指导书。
二、实验正文
实验内容
本实验通过用VHDL硬件描述语言设计指令控制器,帮助学生学习和掌握THCOMIPS指令系统的指令功能和指令格式。实验材料中提供了一个简单多周期控制器,该控制器视线了7条THCO MIPS指令功能,7条指令分别为ADDU SUBU BNEZ JR OR LW SW,具体功能和格式请参考实验指导书。
实验步骤
(1)现具有七条MIPS指令功能的控制器;
(2)验板验证每条指令的信号正确。
因为此实验是演示性实验,只需要自己编程出来的代码给老师演示一步步的操作,包括四个状态(PC ALU MEM REG)和五个周期(取指、译码、执行、访存、写回)的演示。实验板操作一次,就去对照一次代码,找到对应物理位置的LED灯,查看此时是亮或者灭。
部分代码
- ucf文件
NET "CLK" LOC = U9; #clk键
NET "RST" LOC = U10; #rst键
NET "clk0" LOC = B8; #11.0592MHz时钟晶振
NET "showCtrl" LOC = U11; #电路板右下方微动开关key0
NET "bZero_ctrl" LOC = V16; #电路板右下方微动开关key3
#电路板下方的16个拨码开关
NET "instructions[0]" LOC = J6;
NET "instructions[1]" LOC = J7;
NET "instructions[2]" LOC = K2;
NET "instructions[3]" LOC = K7;
NET "instructions[4]" LOC = M1;
NET "instructions[5]" LOC = N1;
NET "instructions[6]" LOC = N2;
NET "instructions[7]" LOC = R1;
NET "instructions[8]" LOC = R4;
NET "instructions[9]" LOC = U1;
NET "instructions[10]" LOC = V2;
NET "instructions[11]" LOC = V3;
NET "instructions[12]" LOC = V4;
NET "instructions[13]" LOC = U8;
NET "instructions[14]" LOC = R7;
NET "instructions[15]" LOC = T7;
#电路板上方的16个LED灯:
NET "light[0]" LOC = D10;
NET "light[1]" LOC = E10;
NET "light[2]" LOC = A11;
NET "light[3]" LOC = B11;
NET "light[4]" LOC = C11;
NET "light[5]" LOC = D11;
NET "light[6]" LOC = E11;
NET "light[7]" LOC = F11;
NET "light[8]" LOC = A12;
NET "light[9]" LOC = E12;
NET "light[10]" LOC = F12;
NET "light[11]" LOC = A13;
NET "light[12]" LOC = B13;
NET "light[13]" LOC = D13;
NET "light[14]" LOC = E13;
NET "light[15]" LOC = A14;
- vhdl文件
process(rst,clk) -- 控制的核心进程
begin
if rst = '0' then
state <= instruction_fetch;
IorD <= '0'; -- 存储器地址来源:来自PC,即读出来的是指令
IRWrite <= '0'; -- 不写入IR寄存器
MemRead <= '0'; -- 不读存储器
MemtoReg <= "00"; -- 写入寄存器堆的数据来源:00:ALUOut 01:MDR
ALUOp <= "00"; -- 00 : ALU执行加操作 01:ALU执行减操作 10:指令的功能字段决定ALU操作
ALUSrcA <= '0'; -- 1:来自寄存器 0:来自PC
ALUSrcB <= "00"; -- "00":来自寄存器B "01":常数1 "10":符号位扩展后IR的低16位
PCWrite <= '0'; -- 为1时改写PC
PCSource <= '0'; -- 0:直接来源于ALU输出 1:来源于ALUOut寄存器
RegDst <= "00"; -- 选择目的寄存器
RegWrite <= "000"; -- 写寄存器控制
elsif rising_edge(clk) then
case state is
when instruction_fetch => --取指阶段: M -> IR , PC + 1 -> PC
MemRead <= '1'; -- 从存储器中读出数据
ALUSrcA <= '0'; -- ALU源操作数A:来自PC
IorD <= '0'; -- 存储器地址来源:来自PC,即读出来的是指令
ALUSrcB <= "01"; -- 常数1
ALUOp <= "00"; -- ALU执行加操作
PCWrite <= '1'; -- 改写PC的值
PCSource <= '0'; -- 新的PC来源:直接来源于ALU的输出
IRWrite <= '1'; -- 存储器的输出写入IR
RegWrite <= "000"; -- 写寄存器控制:"000" 无
state <= decode; -- 进入解码状态
when decode => --解码阶段
IRWrite <= '0'; -- 不写入IR
MemRead <= '0'; -- 不从寄存器中读数据
PCWrite <= '0'; -- PC不写
ALUSrcA <= '0'; -- A
ALUSrcB <= "10"; -- IR
ALUOp <= "00"; -- 加
state <= execute;
when execute => --执行阶段
case instructions(15 downto 11) is
when "00100" => -- BEQZ:若寄存器rx的值为0,则跳转到目的地址immediate执行;否则顺序执行下一条指令
ALUSrcA <= '1'; -- PC
ALUOp <= "10"; -- 指令的功能字段决定ALU操作
PCSource <= '1'; -- 来源于ALUout寄存器
state <= instruction_fetch;
when "00110" => -- SLL
ALUSrcA <= '1';
ALUSrcB <= "00";
ALUOp <= "10"; -- 逻辑左移
state <= write_reg;
when "01101" => -- LI
ALUSrcA <= '1'; --A
ALUSrcB <= "10"; --0扩展后的IR
ALUOp <= "00";
state <= write_reg;
when "01111" => -- MOVE
ALUSrcA <= '1'; -- A
ALUOp <= "10"; -- MOVE指令决定运算,即不运算
state <= write_reg;
when "10011" => -- LW 从内存读到寄存器
ALUSrcA <= '1'; -- A
ALUSrcB <= "10"; -- IR符号位扩展(立即数immediate)
ALUOp <= "00"; -- 加
state <= mem_control;
when "11011" => -- SW 从寄存器写入内存********
ALUSrcA <= '1'; -- A
ALUSrcB <= "10"; -- IR符号位扩展(立即数immediate)
ALUOp <= "00"; -- 加
state <= mem_control;
when "11100" =>
case instructions(1 downto 0) is
when "01" => -- ADDU
ALUSrcA <= '1'; -- 来自寄存器A
ALUSrcB <= "00"; -- 来自寄存器B
ALUOp <= "00"; -- 加
when "11" => -- SUBU
ALUSrcA <= '1'; --来自寄存器A
ALUSrcB <= "00"; --来自寄存器B
ALUOp <= "01"; --减
when others =>
null;
end case;
state <= write_reg;
when "11101" =>
case instructions(4 downto 0) is
when "01101" => -- OR
ALUSrcA <= '1'; -- 来自寄存器A
ALUSrcB <= "00"; -- 来自寄存器B
ALUOp <= "10"; -- OR指令
state <= write_reg;
when "01111" => -- NOT
ALUSrcA <= '1';
ALUop <= "10"; -- NOT运算
state <= write_reg;
when "01110" => -- XOR
ALUSrcA <= '1';
ALUSrcB <= "00";
ALUOp <= "10";
state <= write_reg;
when "00000" =>
case instructions(7 downto 5) is
when "000" => -- JR
ALUSrcA <= '1';
ALUOp <= "10"; -- 根据指令的功能字段决定操作
PCWrite <= '1'; -- 写PC
PCSource <= '0'; -- 数据:ALU输出
state <= instruction_fetch;
when others =>
null;
end case;
when others =>
null;
end case;
when others =>
null;
end case;
when mem_control => --访存阶段
PCWrite <= '0';
RegWrite <= "000";
case instructions(15 downto 11) is
when "10011" => -- LW
MemRead <= '1'; -- 读寄存器
IorD <= '1'; -- 地址来源于ALUOut
state <= write_reg;
when "11011" => -- SW
MemRead <= '1';
IorD <= '1';
state <= write_reg;
when others =>
null;
end case;
when write_reg => --写回阶段
MemWrite <= '0';
MemRead <= '0';
case instructions(15 downto 11) is
when "01101" => -- LI
RegDst <= "00"; -- 选rx
RegWrite <= "001";-- 写
MemtoReg <= "00"; -- 来自ALUOut
when "01111" => -- MOVE
RegDst <= "00"; -- 选Rx
RegWrite <= "001";
MemtoReg <= "00";
when "10011" => -- LW
RegDst <= "10"; -- 目的寄存器为ry
RegWrite <= "001";-- 写
MemtoReg <= "01"; -- 数据来自于 MDR
when "11011" => -- SW
MemWrite <= '0'; -- 不写
IorD <= '0'; -- 来自PC
when "11100" =>
case instructions(1 downto 0) is
when "01" => -- ADDU
RegDst <= "01"; -- 指定寄存器rz
RegWrite <= "001"; -- 将数据写入寄存器rz
MemtoReg <= "00"; -- 数据来自ALUOut
when "11" => -- SUBU
RegDst <= "01";
RegWrite <= "001";
MemtoReg <= "00";
when others =>
null;
end case;
when "11101" =>
case instructions(4 downto 0) is
when "01101" => -- OR
RegDst <= "00";
RegWrite <= "001";
MemtoReg <= "00";
when "01110" => -- XOR
RegDst <= "00";
RegWrite <= "001";
MemtoReg <= "00";
when "01111" => -- NOT6
RegDst <= "00";
RegWrite <= "001";
MemtoReg <= "00";
when others =>
null;
end case;
when others =>
null;
end case;
state <= instruction_fetch;
end case;
end if;
end process;
三、综合实验总结
- 实验难点
编程部分,ucf文件不好写,引脚的物理位置要对照之前的文件一个个去推理,写到一半才发现指导书上就有。控制信号太多,容易遗漏,四个状态和五个周期的转换也要注意对应的脉冲信号量是哪个;
操作部分,实验板的按键太过灵敏,按一下可能就跳过三四个状态,导致刚开始验证的时候以为是代码出错。
- 心得体会
实践出真知,脑子里的构思做出来不一定有想象中那么容易。
最重要的是,感谢老师和研究生学长学姐的耐心指导,没有他们的帮助,我们组还要走许多弯路,耗费许多精力和时间,由衷感激!
3. 个人分工
本人在此实验中负责全部所有工作,代码编写、硬件操作验证等均由本人完成,并且教会其他两位组员实验板的操作及代码原理。