HNU 2024 级 电路与电子学 模型机综合设计实验报告

12 minute read

Published:

前言

HNU 信院的电子电路感觉是挑战性很大的一门课,综合设计当中也会遇到很多课上不会讲的知识,网上关于 Quartus II 软件的相关资料也是少之又少。这学期的课程,本人也是在各种跌跌撞撞中查阅资料、向 AI 提问的曲折过程中,最终才实现了模型机的综合设计。这篇博客希望能给以后的同学当作参考,但也希望大家不要抄袭,独立完成之后获得的成就感是无论如何都无法取代的:完成每一个元件的代码和波形测试、把每一个自己编写的元件连接在一起、用简陋的指令集实现一个程序——这其中每一个过程对我来说都是新奇且具有挑战性的。

祝大家顺利完成综合设计。

一、设计目的

以“培养现代数字系统设计能力”为目标,贯彻以 CPU 设计为核心,以层次化、模块化设计方法为抓手的组织思路,培养设计与实现数字系统的能力。

完整、连贯地运用课程所学知识,熟练掌握现代 EDA 工具基本使用方法,为后续课程学习和今后从事相关工作打下良好的基础或做下一些铺垫。

二、设计内容

  1. 按照给定的数据通路、数据格式和指令系统,使用 EDA 工具设计一台用硬连线逻辑控制的简易计算机;
  2. 用所设计的计算机控制模拟通道对外界模拟输入信号进行采样、离散和处理,并显示在数码显示管上。

三、详细设计

3.1 模型机的设计思路

  1. 根据自上到下的设计方式,了解模型机整体的设计思路,根据整体原理设计下层结构功能。
  2. 利用分块设计,使用 Verilog HDL 语言对每个模块进行设计,通过功能波形仿真和时序波形仿真检查功能是否实现,分析元件性能表现。
  3. 根据自下至上的原则,依据整体架构对封装好的各板块进行连接,每连接一个板块进行一次仿真验证,确保无误后再进行下一个组件的连接,直到整个模型机设计完毕。

3.2 模型机的整体架构

参见 AI 麥爾的项目模型机的架构图

3.3 各模块的具体实现

3.3.1. 指令译码器

ins_decode 元件图

接口设计:见上图。模块输入为使能信号 en 和机器码 ir[3..0],输出指令信号 mova, movb, movc, movd, add, sub, jmp, jg, in1, out1, movi, halt 将会接入控制信号发生逻辑上。

输入 en输入 Ir[3:0]movamovbmovcmovdaddsubjmpjgin1out1movihalt
0xxxx000000000000
10100100000000000
10101010000000000
10110001000000000
10111000100000000
11000000010000000
11001000001000000
11010000000100000
11011000000010000
11100000000001000
11101000000000100
11110000000000010
11111000000000001
1其它组合000000000000

功能实现:当译码器的使能信号为 1 时,根据传入的机器码将对应端口输出为 1,其他端口输出 0。具体功能见上表。

Verilog HDL 实现:

module ins_decode(
	input [3:0] ir,
	input en,
	output reg mova,
	output reg movb,
	output reg movc,
	output reg movd,
	output reg add,
	output reg sub,
	output reg jmp,
	output reg jg,
	output reg in1,
	output reg out1,
	output reg movi,
	output reg halt
);

always @(ir, en) begin
	mova = 1'b0;
	movb = 1'b0;
	movc = 1'b0;
	movd = 1'b0;
	add = 1'b0;
	sub = 1'b0;
	jmp = 1'b0;
	jg = 1'b0;
	in1 = 1'b0;
	out1 = 1'b0;
	movi = 1'b0;
	halt = 1'b0;
	
	if (en == 1) begin
		if (ir[3:0] == 4'b0100) mova = 1;
		else if (ir[3:0] == 4'b0101) movb = 1;
		else if (ir[3:0] == 4'b0110) movc = 1;
		else if (ir[3:0] == 4'b0111) movd = 1;
		else if (ir[3:0] == 4'b1000) add = 1;
		else if (ir[3:0] == 4'b1001) sub = 1;
		else if (ir[3:0] == 4'b1010) jmp = 1;
		else if (ir[3:0] == 4'b1011) jg = 1;
		else if (ir[3:0] == 4'b1100) in1 = 1;
		else if (ir[3:0] == 4'b1101) out1 = 1;
		else if (ir[3:0] == 4'b1110) movi = 1;
		else if (ir[3:0] == 4'b1111) halt = 1;
	end
end

endmodule

3.3.2. 算术单元

AU 元件图

接口设计:见上图。共 4 个输入端口,包括来自通用寄存器的数据来源 a[7..0], b[7..0],以及来在控制信号发生逻辑的 ac[3..0] 指令码和使能信号 au_en。输出端口 t[7..0] 表示计算结果,gf 表示 sub 运算得到的标识。

au_enac[3..0]t[7..0]gf
11000t = a + b不影响
11001t = b - aIF(b > a), THEN gf = 1, ELSE gf = 0
10100、0101、1101t = a不影响
1其它组合t = 8’hZZ不影响
0XXXXt = 8’hZZ不影响

功能实现:根据输入的指令码,实现加法运算、减法运算、直接经过三种功能。具体功能见上表。

Verilog HDL 实现:

module au(
	input au_en,
	input [3:0] ac,
	input [7:0] a,
	input [7:0] b,
	output reg [7:0] t,
	output reg gf
);

always @(au_en, ac, a, b) begin
	gf = 1'b0;
	t = 8'b0000_0000;
	if (au_en == 1'b0) begin
		t = 8'hZZ;
	end
	else begin
		if (ac == 4'b1000) t = a + b;
		else if (ac == 4'b1001) begin
			t = b + (~a) + 8'b0000_0001;
			if ((t[7] == 0) & (t[0] | t[1] | t[2] | t[3] | t[4] | t[5] | t[6]) == 1) gf = 1;
			else gf = 0;
		end
		else if (ac == 4'b0100 || ac == 4'b0101 || ac == 4'b1101) t = a;
		else t = 8'hZZ;
	end
end

endmodule

3.3.3. 八重 3-1 多路复用器

八重 MUX3-1 元件图

接口设计:见上图。八重 3-1 多路复用器有 3 个八位输入 a[7:0]、b[7:0]、c[7:0],1 个输出 y[7:0],称之为八重,s[1:0] 选择将哪个输入传至输出。

S[1..0]Y
00a
01b
10c
其他a

功能实现:见上表。

Verilog HDL 实现:

module mux3_1(
	input [7:0] a,
	input [7:0] b,
	input [7:0] c,
	input [1:0] s,
	output reg [7:0] y
);
always @(*) begin
	if (s == 2'b01) y = b;
	else if (s == 2'b10) y = c;
	else y = a;
end
endmodule

3.3.4. 八重 2-1 多路复用器

八重 MUX2-1 元件图

接口设计:见上图。输入包含选择信号 s 和两个 8 位输入 a[7..0], b[7..0]。输出 y[7..0] 由 s 选择。

SY
0a
1b

功能实现:见上表。

Verilog HDL 实现:

module mux2_1(
	input [7:0] a,
	input [7:0] b,
	input s,
	output reg [7:0] y
);
always @(*) begin
	if (s == 1'b0) y = a;
	else y = b;
end
endmodule

3.3.5. 控制信号产生逻辑

con_signal 元件图

接口设计:见上图。包含 15 个输入端口,其中 12 个与指令译码器的输出端口对应,另外 g 由状态寄存器输出,ir[7..0] 为输入指令,sm 信号控制当前为读取还是执行周期。输出包含 16 个控制信号。

功能实现:控制信号产生逻辑接受指令译码器的输出,在 sm 以及状态 g 的配合下产生每个模块需要的控制信号。

Verilog HDL 实现:

module con_signal(
	input mova,
	input movb,
	input movc,
	input movd,
	input add,
	input sub,
	input jmp,
	input jg,
	input g,
	input in1,
	input out1,
	input movi,
	input halt,
	input [7:0] ir,
	input sm,
	output reg sm_en,
	output reg ir_ld,
	output reg ram_re,
	output reg ram_wr,
	output reg pc_ld,
	output reg pc_in,
	output reg [1:0] reg_sr,
	output reg [1:0] reg_dr,
	output reg reg_we,
	output reg [1:0] s,
	output reg au_en,
	output reg [3:0] au_ac,
	output reg gf_en,
	output reg in_en,
	output reg out_en,
	output reg mux_s
);

always @(*) begin
	sm_en = ~halt;
	ir_ld = ~sm;
	ram_re = (~sm) | movc | movi;
	ram_wr = movb;
	pc_ld = jmp | (jg & g);
	pc_in = movi | (~sm);
	reg_sr = ir[1:0];
	reg_dr = ir[3:2];
	reg_we = movi | mova | movc | movd | sub | add | in1;
	if (movb) s = 2'b10;
	else if (movc) s = 2'b01;
	else s = 2'b00;
	au_en = mova | movb | add | out1 | sub;
	au_ac = ir[7:4];
	gf_en = sub;
	in_en = in1;
	out_en = out1;
	mux_s = mova | movi | add | sub | in1 | movc;
end

endmodule

3.3.6. 状态机 SM

SM 元件图

接口设计:见上图。输入时钟 clk 和使能信号 sm_en,输出 sm 指示当前周期信息。

CLKSM_EN功能备注
下降沿1SM \(\Leftarrow\) SM 取反SM 初始值为 0

功能实现:见上表。SM 为 0 是读取指令周期;SM 为 1 是执行指令周期。

Verilog HDL 实现:

module sm(
	input clk,
	input sm_en,
	output sm
);

reg state = 1'b0;

always @(negedge clk) begin
	if (sm_en == 1'b1) state <= ~state;
end

assign sm = state;

endmodule

3.3.7. 指令寄存器 IR

IR 元件图

接口设计:见上图。输入时钟 clk,控制信号 ld_ir 和指令 a[7..0]。输出指令 x[7..0]。

CLKLd_ir功能备注
下降沿1a[7:0] 写入 x[7:0]X 初始值为 00000000

功能实现:见上表。当时钟处在下降沿且控制信号为 1 时将总线数据写入寄存器。

Verilog HDL 实现:

module ir(
	input clk,
	input ld_ir,
	input [7:0] a,
	output reg [7:0] x = 8'b0000_0000
);

always @(negedge clk) begin
	if (ld_ir == 1'b1) x <= a;
end

endmodule

3.3.8. 状态寄存器 PSW

PSW 元件图

接口设计:见上图。输入时钟 clk,使能信号 g_en 和状态信号 g。输出状态信号 gf。

CLKg_en功能备注
下降沿1g 写入 gfgf 初始值为 0

功能实现:见上表。本模型机 PSW 用来存放 SUB 指令执行结果的状态标志。

Verilog HDL 实现:

module psw(
	input clk,
	input g,
	input g_en,
	output reg gf = 1'b0
);

always @(negedge clk) begin
	if (g_en == 1'b1) gf <= g;
end

endmodule

3.3.9. 指令计数器 PC

PC 元件图

接口设计:见上图。输入时钟 clk,控制信号 ld_pc 和 in_pc,以及装载数据 a[7..0]。输出当前地址 c[7..0]。

CLKin_pc功能备注
下降沿1c[7..0] 中数据自加 1PC 初始值为 00000000(第一条指令在 RAM 中的存放地址)
下降沿0a[7..0] 写入 c[7..0] 

功能实现:见上表。根据不同的输入信号使寄存器中的地址自增 1 或是装载新的地址。

Verilog HDL 实现:

module pc(
	input ld_pc,
	input in_pc,
	input clk,
	input [7:0] a,
	output reg [7:0] c = 8'h00
);

always @(negedge clk) begin
	if (in_pc == 1'b1 && ld_pc == 1'b0) c <= c + 8'h01;
	else if (in_pc == 1'b0 && ld_pc == 1'b1) c <= a;
end

endmodule

3.3.10. 通用寄存器组

reg_group 元件图

接口设计:见上图。输入时钟 clk,控制信号 sr[1..0] 和 dr[1..0],以及装载数据 i[7..0]。根据控制信号输出当前寄存器的值 s[7..0], d[7..0]。

操作CLKWE功能
  根据 SR[1..0] 的值从 R0、R1、R2、R3 中选择一个寄存器的值从 S 口输出。根据 DR[1..0] 的值从 R0、R1、R2、R3 中选择一个寄存器的值从 D 口输出
下降沿1控制信号 WE 为 1,根据 DR[1..0] 的值,在 CLK 下降沿将外部输入 A 写入 R0、R1、R2、R3 中的一个寄存器。

功能实现:见上表。根据 sr 和 dr 输出对应寄存器的存储值(组合逻辑)。在时钟下降沿将 i 端口的数据写入 dr 对应的寄存器(时序逻辑)。

Verilog HDL 实现:

module reg_group(
	input we,
	input clk,
	input [1:0] sr,
	input [1:0] dr,
	input [7:0] i,
	output reg [7:0] s,
	output reg [7:0] d
);

reg [7:0] r0 = 8'h01;
reg [7:0] r1 = 8'h00;
reg [7:0] r2 = 8'h00;
reg [7:0] r3 = 8'h07;

always @(negedge clk) begin
	case ({we, dr})
		3'b100: r0 <= i;
		3'b101: r1 <= i;
		3'b110: r2 <= i;
		3'b111: r3 <= i;
	endcase
end

always @(*) begin
	case(sr)
		2'b00: s = r0;
		2'b01: s = r1;
		2'b10: s = r2;
		2'b11: s = r3;
	endcase
	
	case(dr)
		2'b00: d = r0;
		2'b01: d = r1;
		2'b10: d = r2;
		2'b11: d = r3;
	endcase
end

endmodule

3.3.11. 随机存取存储器 RAM

RAM 元件图

接口设计:见上图。输入地址 address[] 和时钟 inclock,控制信号 we 和 outenab。

功能实现:当 we 和 outenab 均为 0 时,dio 端口保持高阻态。当 we 为 1,outenab 为 0 时,在时钟上升沿将把 address 处的数据从 dio 输出。当 we 为 0,outenab 为 1 时,在时钟上升沿将 dio 端口处的数据写入 address 地址。

四、系统测试

4.1 测试环境

开发软件: Quartus II Version 9.0 Build 235 06/17/2009 SJ web Edition

操作系统: Windows 11 家庭中文版 24H2

FPGA 型号: Cyclone II EP2C5T144C8

4.2 测试代码

代码一

该代码系作业提供的示范代码。

RAM 地址汇编指令机器码(RAM 存放内容)说明
0JMP1010 0011R3 的初始值为 07h,指令执行完,PC=07h
1 0001 0001R0 的初始值为 01h,RAM 的 01 地址单元存放 11h
7IN R11100 0100外部引脚输入 01h,指令执行完,R1=01h
8MOVA R2, R10100 1001指令执行完,R2=01h
9MOVC R1, M0110 0100RAM 的 01 单元内容 11h 赋给 R1,指令执行完,R1=11h
10SUB R1, R21001 0110指令执行完,R1=10h 且 G=1
11MOVD R3, PC0111 1100取指后 PC 自加 1,此时 PC=12,那么 R3=12=0Ch
12ADD R3, R11000 1101指令执行完,R3=1Ch=28
13JG1011 0011G=1,满足跳转条件,执行跳转,PC=28
28MOVB M, R30101 0011将 R3 中的数据写入 RAM
29MOVC R1, M0110 0100将 RAM 的数据写入 R1,两条指令执行完,R1=R3=1Ch
30MOVI #91110 0000此为两字节指令,指令执行完后,R0=09H
31 0000 1001 
32SUB R0, R11001 0001指令执行完,R0=EDh 且 G=0
33JG1011 0011G=0,不满足跳转条件,不执行跳转
34OUT R01101 0000指令执行完,输出引脚为 EDh
35HALT1111 0000停机指令
36JMP1010 0011此指令在停机指令后用于检测停机是否正确,正确的停机指令应该是停机后面的指令不执行

代码二

以下代码实现了一个 \(a\times b\) 的乘法功能。其中乘数 \(a\) 从外部读入,乘数 \(b\) 存储在 RAM[2] 位置。要求乘数 \(a\) 以有符号数补码形式输入,乘数 \(b\) 为非负整数。该代码会持续循环运行,每次根据读入的数据更新乘法结果,持续输出。若输入数字小于 \(0\) 则停机。 R3 的初始值设为 0d07

地址(Dec)汇编指令机器码解释(Dec)
0JMP1010 0011Goto 07
1 0000 0000RAM[1] 用来存运算次数
2 0000 0100RAM[2] 用来存乘数 b
3 0000 0000RAM[3] 用来存结果
7IN R21100 1000R2=Input
8MOVI #21110 0000R0=2
9 0000 0010 
10MOVC R1, M0110 0100R1=RAM[2]=b
11MOVI #11110 0000R0=1
12 0000 0001 
13MOVB M, R10101 0001RAM[1]=b
14SUB R0, R11001 0001R0=1-R1, G=1>R1
15MOVI #361110 0000R0=36
16 0010 0100 
17MOVA R3, R00100 1100R3=R0=36
18JG1011 0011If (G) Goto 36
19MOVI #31110 0000R0=3
20 0000 0011 
21MOVC R1, M0110 0100将结果先复制给 R1
22ADD R1, R21000 0110R1=R1+R2
23MOVB M, R10101 0001RAM[3]=R1
24MOVI #11110 0000RAM[1]=RAM[1]-1
25 0000 0001 
26MOVC R1, M0110 0100 
27SUB R1, R01001 0100 
28MOVB M, R10101 0001 
29MOVI #181110 0000R0=18
30 0001 0010 
31MOVD R3, PC0111 1100R3=32
32SUB R3, R01001 1100R3=32-18=14
33MOVI #11110 0000R0=1
34 0000 0001 
35JMP1010 0011Goto 14
36MOVI #01110 0000R0=0
37 0000 0000 
38SUB R0, R21001 0010R0=R0-R2, G=R0>R2
39MOVI #571110 0000R0=57
40 0011 1001 
41MOVA R3, R00100 1100R3=R0=57
42JG1011 0011If (G) Goto 57
43MOVI #31110 0000R0=3
44 0000 0011 
45MOVC R1, M0110 0100R1=RAM[3]
46OUT R11101 0001Output RAM[3]
47MOVI #71110 0000R0=7
48 0000 0111 
49MOVA R3, R00100 1100R3=R0=7
50MOVI #01110 0000R2=0
51 0000 0000 
52MOVA R2, R00100 1000 
53MOVI #31110 0000RAM[3]=R2=0
54 0000 0011 
55MOVB M, R20101 0010 
56JMP1010 0011Goto 7
57HALT1111 0000Quit
58OUT R31101 0011测试是否正常停机

4.3 测试结果

代码一功能仿真(输入 0x01):根据代码,最终应当输出 0xED,波形符合预期,正常停机

代码 1 功能仿真波形

代码一时序仿真(输入 0x01):

代码 1 时序仿真波形

代码二功能仿真(先输入 2,后输入 -1):输出 4 符合预期,寄存器中间数值均符合预期,输入 -1 时正常停机

代码 2 功能仿真波形-I

代码 2 功能仿真波形-II

代码二时序仿真(先输入 5,后输入 -3):

代码 2 功能时序波形-I

代码 2 功能时序波形-II

4.4 模型机性能分析

时序性能

编译情况

综合性能

性能表现良好,时钟周期为 23.35ns,warning 为 13 条。

五、实验总结、心得体会及建议

5.1 从需要掌握的理论、遇到的困难、解决的办法以及经验教训等方面进行总结。

本模型机需要掌握数字电路的基础原理,需要学会使用 Quartus II 软件编写具有对应功能的模块与元件,熟悉计算机数据和信号流动的过程以及模型机的基本结构,掌握如何组装并设计简易的模型机,了解如何在 FPGA 实验板上验证功能。

在实验过程中,本人遇到了编写测试代码的困难,是由于对汇编编程的不熟悉导致的,在了解和学习汇编的基本方法后顺利完成了代码。在测试代码时,还发现部分指令的执行和指导书中的功能描述有细微差别,在后续修改了元件代码实现了功能统一,在解决这一困难的过程中认识到了加强小模块事先测试的重要性。