31980 字
160 分钟
CPU设计笔记

参照资料
FPGA学习笔记-1 FPGA原理与开发流程_fpga开发-CSDN博客
数字分频器设计(偶数分频、奇数分频、小数分频、半整数分频、状态机分频|verilog代码|Testbench|仿真结果)

1.使用教程#

1.1 概述#

200

  1. 创建源文件:用户有两种方式可以创建源文件,以描述电路结构与功能,分别是编写“HDL代码”、创建BD(框图,Block Diagram)。
  2. RTL分析:vivado将上述源文件转换为逻辑门电路。
    此时可进行“行为仿真Behavioral Simulation”。
  3. 综合:vivado将经过“RTL分析”后的门电路映射为FPGA器件内部的物理结构。所以“综合”电路中看不到任何的“门”。“综合”的结果是所使用的特定FPGA器件中实际存在着的物理结构,如“输入缓冲”、“查找表”、“触发器”和“输出缓冲”等。此时可进行“综合后功能仿真”。
  4. 布局布线:需要用户指定时序、布局布线或者其它的设计要求,如时序约束、I/O引脚约束和布局布线约束等。Vivado会自动根据这些约束,将“综合”给出HDL代码与实际FPGA器件进行映射,物理视图与具体的芯片一致。此时可以进行所有类型的仿真。
  5. 生成比特流、下载到板材上。
    注:使用vivado进行后续操作时,会自动检查前面的步骤是否已经完成,比如直接“综合”会自动先进行“RTL分析”、直接“生成比特流”则会自动进行整个过程,若有那个环节有错,vivado会自动停止并给出提示。

1.2 仿真#

Pasted image 20251023225257FPGA学习笔记-1 FPGA原理与开发流程_fpga开发-CSDN博客

Pasted image 20251023225307

Pasted image 20251023225319

写测试文件
测试文件一般包含以下几块功能
1)被测试模块的例化
2)被测模块输入信号的生成和设置初值
3)时钟信号、复位信号生成
4)测试值的输出(调试文件输出和显示输出)

`timescale 1ns / 1ps // 必须放在文件最开头!
module tb_your_module();
// 信号声明
reg clk;
reg rstn;
// ... 其他信号
// 实例化被测模块
your_module uut (
.clk(clk),
.rstn(rstn)
// ... 其他连接
);
// 时钟生成
always #5 clk = ~clk; // 10ns周期 = 100MHz
initial begin
// 测试激励
clk = 0;
rstn = 0;
#100 rstn = 1;
// ... 更多测试
#1000 $finish;
end
endmodule

Pasted image 20251023225426

`timescale 1ns/1ns //时间刻度:单位1ns,精度1ns
module clk_div_even_tb;
//信号申明
reg clk ;
reg rst_n;
wire clk_div2;
wire clk_div4;
wire clk_div6;
wire clk_div8;
//定义源时钟信号一周期时间
parameter DIV_CLK = 10;
//复位信号生成
initial begin
clk = 0; //时钟信号赋初值
rst_n = 1; //复位信号赋初值
#(1.5*DIV_CLK) rst_n = 0;
#DIV_CLK rst_n = 1;
#(30*DIV_CLK);
end
//源时钟信号生成
always #(DIV_CLK/2) clk = ~ clk;
//模块例化
clk_div_even u_clk_div_even
(
.clk (clk),
.rst_n (rst_n),
.clk_div2 (clk_div2),
.clk_div4 (clk_div4),
.clk_div6 (clk_div6),
.clk_div8 (clk_div8)
);
endmodule

1.2.1 Testbench文件编写#

timescale 1ns / 1ps 必须写在testbench文件里

变量初始化的基本原则为:可综合代码中完成内部变量的初始化,Testbench中完成可综合代码所需的各类接口信号的初始化。

初始化的方法有两种:一种是通过initial语句块初始化;另一种是在定义时直接初始化。

一、普通时钟信号:#

1、基于initial语句的方法:

parameter clk_period = 10;
reg clk;
initial begin
clk = 0;
forever
#(clk_period/2) clk = ~clk;
end

2、基于always语句的方法:

parameter clk_period = 10;
reg clk;
initial
clk = 0;
always #(clk_period/2) clk = ~clk;

二、自定义占空比的时钟信号:#

parameter High_time = 5,Low_time = 20;
// 占空比为High_time/(High_time+Low_time)
reg clk;
always begin
clk = 1;
#High_time;
clk = 0;
#Low_time;
end

三、相位偏移的时钟信号:#

parameter High_time = 5,Low_time = 20,pshift_time = 2;
// 相位偏移为360*pshift_time/(High_time+Low_time)
reg clk_a;
wire clk_b;
always begin
clk_a = 1;
#High_time;
clk_a = 0;
#Low_time;
end
assign #pshift_time clk_b = clk_a;

四、固定数目的时钟信号:#

parameter clk_cnt = 5, clk_period = 2;
reg clk;
initial begin
clk = 0;
repeat(clk_cnt)
#(clk_period/2) clk = ~clk;
end

1.2.2 仿真卡壳#

参照Vivado仿真,卡在executing analysis and compilation step阶段
取消勾选增量编译,然后进行仿真,可以保持,也可以再勾选,之后就都可以完成仿真了。
Pasted image 20251025110518

2.时钟分频#

占空比=高电平时间/总周期时间×100%
n分频 = n个clk上升沿占据的周期对应clk_n一个周期

2.1 偶数倍时钟分频#

触发器级联法#

Qn+1=DQ^{n+1}=D(时钟上升沿)
上升沿取反,实现二分频;将二分频作为clk输入,同样处理可得四分频。
Pasted image 20251023210645

Pasted image 20251023203633

计数法分频#

思路1:看某位,比如这里盯着cnt[1],需要2个时钟周期(2个上升沿)才会变化。
Pasted image 20251023203756
思路二:直接根据计数值。计数器法可以实现任意偶数分频。在计数周期达到分频系数中间数值 (N/2-1) 时进行时钟翻转,可保证分频后时钟的占空比为 50%。
Tips:中间数值(N/2-1) 需要减1是因为从0开始计数
实现:计数器从0开始计数至2,计数器到0时信号翻转
(图由TimeGen绘制)
Pasted image 20251023211214

//偶数分频电路设计(2分频、4分频、8分频、6分频)
//触发器法实现2分频、4分频、8分频
//计数器法实现6分频
module clk_div_even(
input rst_n, //复位信号
input clk, //源时钟信号
output clk_div2, //输出2分频
output clk_div4, //输出4分频
output clk_div6, //输出6分频
output clk_div8 //输出8分频
);
//定义4个中间寄存器和1个计数器
reg clk_div2_r;
reg clk_div4_r;
reg clk_div6_r;
reg clk_div8_r;
reg [3:0] cnt;
//2分频时钟输出模块
//源时钟上升沿触发,低电平异步复位
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin //低电平复位
clk_div2_r <= 1'b0;
end
else begin
clk_div2_r <= ~clk_div2_r; //源时钟上升沿信号翻转得到2分频时钟
end
end
assign clk_div2 = clk_div2_r; //延时输出,消除亚稳态
//4分频时钟输出模块
//2分频时钟上升沿触发 低电平异步复位
always @(posedge clk_div2 or negedge rst_n) begin
if (!rst_n) begin
clk_div4_r <= 1'b0;
end
else begin
clk_div4_r <= ~clk_div4_r; //2分频时钟上升沿信号翻转得到4分频时钟
end
end
assign clk_div4 = clk_div4_r; //延时输出,消除亚稳态
//8分频时钟输出模块
//4分频时钟上升沿触发 低电平异步复位
always @(posedge clk_div4 or negedge rst_n) begin
if (!rst_n) begin
clk_div8_r <= 'b0;
end
else begin
clk_div8_r <= ~clk_div8_r; //4分频时钟上升沿信号翻转得到8分频时钟
end
end
assign clk_div8 = clk_div8_r; //延时输出,消除亚稳态
//计数器模块
//源时钟上升沿触发,低电平异步复位
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin //低电平复位
cnt <= 4'b0 ;
end
else if (cnt == 2) begin //计数器从0计数,到2清零
cnt <= 4'b0 ;
end
else begin //计数累加
cnt <= cnt + 1'b1 ;
end
end
//6分频时钟输出模块
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
clk_div6_r <= 1'b0;
end
else if (cnt == 2 ) begin //3个周期信号翻转得到6分频时钟
clk_div6_r <= ~clk_div6_r;
end
end
assign clk_div6 = clk_div6_r; //延时输出,消除亚稳态
endmodule

Testbench文件

`timescale 1ns/1ns //时间刻度:单位1ns,精度1ns
module clk_div_even_tb;
//信号申明
reg clk ;
reg rst_n;
wire clk_div2;
wire clk_div4;
wire clk_div6;
wire clk_div8;
//定义源时钟信号一周期时间
parameter DIV_CLK = 10;
//复位信号生成
initial begin
clk = 0; //时钟信号赋初值
rst_n = 1; //复位信号赋初值
#(1.5*DIV_CLK) rst_n = 0;
#DIV_CLK rst_n = 1;
#(30*DIV_CLK);
end
//源时钟信号生成
always #(DIV_CLK/2) clk = ~ clk;
//模块例化
clk_div_even u_clk_div_even
(
.clk (clk),
.rst_n (rst_n),
.clk_div2 (clk_div2),
.clk_div4 (clk_div4),
.clk_div6 (clk_div6),
.clk_div8 (clk_div8)
);
endmodule

仿真结果
Pasted image 20251023214254
思路三:思路二的另一种实现思路,使用2进制的进位机制,希望实现2n2^n分频,即到达2n12^{n-1},cnt[n-1]变成1,再过2n12^{n-1},cnt[n-1]变成0,即每隔2n12^{n-1},cnt[n-1]即翻转,所以clk_div = cnt[n-1]即可。

// 2^15分频
reg [14:0] cnt;
wire seg7_clk;
always @(posedge clk or negedge rstn) begin
if(!rstn) cnt <= 0;
else cnt <= cnt + 1'b1;
end
assign seg7_clk = cnt[14];

2.2 奇数倍时钟分频#

比如时钟9分频,首先生成clk1分频时钟:对应时钟上升沿产生电平变换,低电平5个周期,高电平4个周期。接着生成clk2分频时钟:对应时钟上升沿产生电平变换,同样低电平5个周期,高电平4个周期。最后对clk1和clk2取或,生成占空比50%的时钟信号。
注意此处也可生成高电平5周期,低电平4周期的clk1和clk2.最后要对clk1和clk2 取与。
Pasted image 20251023204150

占空比非50%奇数分频#

实现N分频(N为奇数),只需将计数器在待分频时钟上升沿触发循环计数,计数到0时输出时钟翻转,当计数到(N-1)/2后再次将输出时钟翻转。
即把N个周期作为一个周期,只需在0翻转,然后在到0之前翻转一次,这里选择(N-1)/2,可以实现高电平和低电平尽可能均匀,比如(N-1)/2个周期为1,(N+1)/2个周期为0。

以三分频为例,电路需要实现的是:
计数器从0开始计数至2,计数器到0时且在上升沿信号翻转,计数器到1时且在上升沿信号清零。
Pasted image 20251023213245

占空比50%奇数分频#

信号的翻转对应的源时钟信号应该分别是上升沿和下降沿,但是双边沿触发在电路设计的时候是不允许的。
所以设计两个计数cnt分别是上升沿加1和下降沿加1,这样由cnt决定翻转的clk_div也是上升沿和下降沿翻转的,取或即可实现上升沿和下降沿都翻转,从而是奇数分频。
对于50%占空比奇数分频,就是分别利用待分频时钟的上升沿触发生成一个时钟,然后用下降沿触发生成另一个时钟,然后将两个时钟信号进行或/与运算得到占空比为50%的奇数分频。

以三分频为例,电路需要实现的是:设计2个分别用上升、下降沿触发的计数器cnt_p和cnt_n,设计2个分别用上升、下降沿触发的计数器clk_p和clk_n,利用clk_p和clk_n通过或逻辑运算生成占空比为50%的分频时钟。
Pasted image 20251023213726

//奇数分频电路设计(占空比非50%的3分频和占空比50%的3分频)
module clk_div_odd (
input clk, //时钟信号
input rst_n, //复位信号
output clk_div3_1, //占空比非50%的3分频时钟信号输出
output clk_div3_2 //占空比50%的3分频时钟信号输出
);
//定义分频的数目
parameter N = 3;
reg [3:0] cnt_p; //上升沿触发计数器计数
reg [3:0] cnt_n; //下降沿触发计数器计数
reg clk_p; //上升沿触发生成的时钟信号
reg clk_n; //下降沿触发生成的时钟信号
//上升沿触发计数器模块
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_p <= 4'b0000;
else if (cnt_p == N-1) //计数器从0计数,到2清零
cnt_p <= 4'b0000;
else
cnt_p <= cnt_p + 1'b1; //计数器累加
end
//下降沿触发计数器模块
always @(negedge clk or negedge rst_n) begin
if(!rst_n)
cnt_n <= 4'b0000;
else if(cnt_n == N-1) //计数器从0计数,到2清零
cnt_n <= 4'b0000;
else
cnt_n <= cnt_n + 1'b1; //计数器累加
end
//上升沿触发生成的时钟信号模块
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
clk_p <= 1'b0;
else if(cnt_p == (N-1)/2) //计数器到1且在上升沿,时钟信号翻转
clk_p <= ~clk_p;
else if (cnt_p <= 0) //计数器到0且在上升沿,时钟信号翻转
clk_p <= ~clk_p;
else
clk_p <= clk_p; //防止latch产生
end
//下降沿触发生成的时钟信号模块
always @(negedge clk or negedge rst_n)
begin
if(!rst_n)
clk_n <= 1'b0;
else if (cnt_n == (N-1)/2) //计数器到1且在上升沿,时钟信号翻转
clk_n <= ~clk_n;
else if (cnt_n == 0) //计数器到0且在上升沿,时钟信号翻转
clk_n <= ~clk_n;
else
clk_n <= clk_n; //防止latch产生
end
//延时输出,消除亚稳态
assign clk_div3_1 = clk_p; //得到占空比非50%的3分频时钟信号
assign clk_div3_2 = clk_p | clk_n; //或逻辑运算得到占空比50%的3分频时钟信号
endmodule

Testbench文件

`timescale 1ns/1ps //时间刻度:单位1ns,精度1ps
module clk_div_odd_tb;
//信号申明
reg clk;
reg rst_n;
wire clk_div3_1; //占空比非50%的3分频时钟信号
wire clk_div3_2; //占空比50%的3分频时钟信号
parameter DIV_CLK = 5; //定义源时钟信号一周期时间
//复位信号生成
initial begin
clk = 0; //时钟信号赋初值
rst_n = 1; //复位信号赋初值
#(3*DIV_CLK)
rst_n = 0;
#(6*DIV_CLK)
rst_n = 1;
#(20*DIV_CLK);
end
//源时钟信号生成
always #DIV_CLK clk = ~clk;
//模块例化
clk_div_odd u_clk_div_odd
(.clk (clk),
.rst_n (rst_n),
.clk_div3_1 (clk_div3_1),
.clk_div3_2 (clk_div3_2)
);
endmodule

仿真结果
Pasted image 20251023214227

2.3 小数分频#

不规整的小数分频不能做到分频后的每个时钟周期都是源时钟周期的小数分频倍,更不能做到分频后的时钟占空比均为 50%,因为 Verilog 不能对时钟进行小数计数。
小数分频是基于可变分频和多次平均的方法实现的。
例如进行5.4倍分频,则保证源时钟54个周期的时间等于分频时 10个周期的时间即可(分频的每个周期对应5.4个原周期)。此时需要在54个源时钟周期内进行6次5分频,4次6分频。(54分频,就是54个时钟周期作为一个周期,平均到10次分频,每次5.4分频)
T = ( Ma+(M+1)b )/ a+b,这里我们发现组成小数分频使用了a个M分频和b个M+1分频的整数分频电路。

以 5.4 倍分频为例:
基本思想是在54个源时钟周期里完成10个5.4分频,根据前面的公式可知:有6的5分频和4个6分频。只要将5分频和6分频插入在54个源时钟周期即可。

同时我们应当考虑分频信号的实现顺序

5分频和6分频的实现顺序一般有以下 4 种:
(1)先进行 6 次 5 分频,再进行 4 次 6 分频;
(2) 先进行 4 次 6分频,再进行 6 次 5 分频;
(3) 将 6 次 5 分频平均的插入到 4 次 6 分频中;
(4) 将 4 次 6 分频平均的插入到 6 次 5 分频中。

前两种方法时钟频率不均匀,相位抖动较大,所以一般会采用后两种平均插入的方法进行小数分频操作。

如何插入

平均插入可以通过分频次数差累计的方法实现,5.4 分频的实现过程如下:
(1) 第一次分频次数差值 54 - 10×5 = 4 < 10,第一次进行 5 分频。
(2) 第二次差值累加结果为 4+4=8 < 10,第二次使用 5 分频,同时差值修改为(54-10×5) + (54 -10×5) = 8 。
(3) 第三次差值累加结果为 4 + 8 = 12 > 10,第三次使用 6分频。
(4) 第四次差值累加结果为 12 + (54-10×6) < 10,第四次使用 5 分频。
以此类推,完成将 6 次 5分频平均插入到 4 次 6分频的过程

每一段并不是严格的5.4分频(因为信号翻转只在边沿触发),而是在54个源时钟周期平均下来有10个分频,而且时序难以保证。且占空比几乎达不到50%。

//小数分频电路设计
//双模前置法实现5.4分频
module clk_div_fraction
(
input rst_n, //复位信号
input clk, //时钟信号
output clk_frac //小数分频输出信号
);
//定义介于5.4分频的5分频和6分频
parameter CLK_DIV_1 = 5;
parameter CLK_DIV_2 = 6;
parameter DIFF = 4; //10个周期内5分频与5.4分频的差值
reg [3:0] cnt_end; //分频插入计数器 (用于判断插入什么分频)
reg [3:0] cnt; //总计数器
reg clk_frac_r; //小数分频中间寄存器信号
reg [4:0] diff_cnt_r; //差值信号
reg [4:0] diff_cnt; //差值信号
wire diff_cnt_en= cnt == cnt_end; //使能信号
//差值累加逻辑模块
always @(*) begin
if(diff_cnt_r >= 10) begin
diff_cnt = diff_cnt_r -10 + DIFF; //差值大于10,插入6分频,差值减6
end
else begin
diff_cnt = diff_cnt_r + DIFF; //差值小于10,插入5分频,差值加4
end
end
// 借用寄存器延迟输出diff_cnt_r
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin //复位差值清零
diff_cnt_r <= 0;
end
else if(diff_cnt_en) begin //使能信号高电平时,差值信号延迟输出
diff_cnt_r <= diff_cnt;
end
end
//5分频和6分频插入逻辑模块
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_end <= CLK_DIV_1-1 ; //复位先插入5分频
end
else if(diff_cnt >= 10) begin
cnt_end <= CLK_DIV_2-1 ; //差值大于10,插入6分频
end
else begin
cnt_end <= CLK_DIV_1-1 ; //差值小于10,插入5分频
end
end
//
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin //总计数器、分频信号信号复位
cnt <= 1'b0;
clk_frac_r <= 1'b0;
end
else if(cnt == cnt_end) begin //计数器到分频插入界限点
cnt <= 1'b0; //总计数器清零
clk_frac_r <= 1'b1; //时钟分频信号电平置"1"
end
else begin //其他情况下,计数器累加计数、时钟分频信号电平保持"0"
cnt <= cnt + 1'b1;
clk_frac_r <= 1'b0;
end
end
//延时输出,消除亚稳态
assign clk_frac = clk_frac_r;
endmodule

Testbench文件

`timescale 1ns/1ps //时间刻度:单位1ns,精度1ps
module clk_div_fraction_tb;
//信号申明
reg clk;
reg rst_n;
wire clk_frac;
parameter DIV_CLK = 5; //定义源时钟信号一周期时间
//复位信号生成
initial begin
clk = 0; //时钟信号赋初值
rst_n = 1; //复位信号赋初值
#(3*DIV_CLK)
rst_n = 0;
#(6*DIV_CLK)
rst_n = 1;
#(20*DIV_CLK);
end
//源时钟信号生成
always #DIV_CLK clk = ~clk;
//模块例化
clk_div_fraction u_clk_div_fraction
(.clk (clk),
.rst_n (rst_n),
.clk_frac (clk_frac)
);
endmodule

参照资料
数字分频器设计(偶数分频、奇数分频、小数分频、半整数分频、状态机分频|verilog代码|Testbench|仿真结果)

七段码#

八位共阳极七段码
Pasted image 20251025000019

显示字符共阳极段选码共阴极段选码显示字符共阳极段选码共阴极段选码
0C0H3FHA88H77H
1F9H06HB83H
2A4HCC6H
3B0HDA1H
499HE86H
592HF8EH
682H全黑FFH
7F8H
880H
990H

Pasted image 20251024213704
共阳极数码管:各段共阳极,根据二极管原理,当共阳极为高电平1,只有阴极为低电平0才导通,所以低电平有效。
共阴极数码滚啊:各段共阳极,根据二极管原理,当共阴极为低电平0,只有阳极为高电平1才导通,所以高电平有效。
此实验使用共阳极数码管,所以低电平有效。
排列顺序为(DP CG CF CE CD CC CB CA)

三极管
Pasted image 20251102091324

  • 基极:用于激活晶体管。(名字的来源,最早的点接触晶体管有两个点接触放置在基材上,而这种基材形成了底座连接。)
  • 集电极:三极管的正极。(因为收集电荷载体)
  • 发射极:三极管的负极。(因为发射电荷载流子)

没有基极,电流只能P->N
Pasted image 20251102092634
NPN型,基极为高电平1导通;
PNP型,基极为低电平0导通。

NPN#

Pasted image 20251102093902

Pasted image 20251102093916

Pasted image 20251102093948

Pasted image 20251102094105

Pasted image 20251102094338

Pasted image 20251102094405

Pasted image 20251102095759

PNP#

Pasted image 20251102104748

Pasted image 20251102104821
图片修改:电流阻止应该在N-P之间

Pasted image 20251102104955

Pasted image 20251102105018
其中箭头指的是流入基极和流出基极

参考资料:极速入门三极管NPN与PNP放大原理【极速入门数模电路P05】

截止状态
Pasted image 20251102092314
截止状态下,三极管各电极的电流几乎为0,集电极和发射极互不相通。
放大状态
Pasted image 20251102092402
饱和状态
Pasted image 20251102092424

Pasted image 20251024213908
U13开始的部分,由于右边采取PNP型三极管,所以U13部分等作为基极是低电平0导通,接通共阳极,即位选。
8个数码显示,分成两个部分,CA要接入两个部分,在各自部分里4个数码共用CA控制。
Pasted image 20251024213918

实验#

实验一#

跑马灯 Led Splash#

`timescale 1ns / 1ps
module test(
input clk,
input rstn,
input [15:0]sw_i,
output [15:0]led_o
);
parameter div_num = 24;
reg [15:0]led_tmp;
reg ledset_flag;
wire clk_div2;
wire clk_div29;
reg [31:0] clk_cnt;
always @(posedge clk or negedge rstn) begin
if(!rstn) clk_cnt <= 32'b0;
else clk_cnt <= clk_cnt + 1'b1;
end
assign clk_div29 = clk_cnt[div_num];
// led splash
always @(posedge clk_div29 or negedge rstn) begin
if(!rstn) begin
ledset_flag = 1'b1;
led_tmp = 16'b0000_0000_0000_0000;
end
else if((ledset_flag == 1'b1) && (sw_i[4:1] == 4'b1010)) begin
ledset_flag = 1'b0;
led_tmp = 16'b1000_0000_0000_0000;
end
else if(sw_i[4:1] == 4'b1010) led_tmp = {led_tmp[14:0],led_tmp[15]}; // 移位寄存器
else begin
led_tmp = 16'b0000_0000_0000_0000;
ledset_flag = 1'b1;
end
end
assign led_o[15:0] = led_tmp;
endmodule

仿真文件

// 测试代码写在模块中,同正常代码需要写在模块中
module test_tb();
// 声明
reg clk,rstn;
reg [15:0]sw_i;
wire [15:0]led_o;
integer counter;
// 例化
test u_test(.clk(clk),.rstn(rstn),.sw_i(sw_i),.led_o(led_o));
// 初始化变量
initial begin
counter = 0;
clk = 1;
rstn = 1;
sw_i = 16'b0000_0000_0001_0100;
#5;
rstn = 0;
#20;
rstn = 1;
end
// 设计
always begin
#50 clk=~clk; // 等待50ns翻转一次
if(clk == 1'b1) begin
$display("counter: %h",counter); // 在底部的Tcl Console(仿真控制台)中以十六进制输出counter
counter = counter + 1;
end
else if (counter > 100) begin
$stop;
end
else counter = counter;
end
endmodule

仿真时间修改:默认1000ns,导航栏-Tools-settings,如下图。
Vivado不支持.tb文件中增加initial修改仿真时间。
Vivado的Tcl Console不方便使用(无法输入 run 1000ns)。

Pasted image 20251024203628

5分频

module clk_div_odd(
input clk,
input rstn,
output clk_p_div,
output clk_n_div,
output clk_div
);
parameter N = 5;
reg [3:0] cnt_p;
reg [3:0] cnt_n;
reg clk_p;
reg clk_n;
always @(posedge clk or negedge rstn) begin
if(!rstn) cnt_p <= 4'b0000;
else if(cnt_p == N - 1) cnt_p <= 4'b0000;
else cnt_p <= cnt_p + 1;
end
always @(negedge clk or negedge rstn) begin
if(!rstn) cnt_n <= 4'b0000;
else if(cnt_n == N - 1) cnt_n <= 4'b0000;
else cnt_n <= cnt_n + 1;
end
always @(posedge clk or negedge rstn) begin
if(!rstn) clk_p <= 1'b0;
else if(cnt_p == (N-1)/2) clk_p <= ~clk_p;
else if(cnt_p == 0) clk_p <= ~clk_p;
else clk_p <= clk_p;
end
always @(negedge clk or negedge rstn) begin
if(!rstn) clk_n <= 1'b0;
else if(cnt_n == (N-1)/2) clk_n <= ~clk_n;
else if(cnt_n == 0) clk_n <= ~clk_n;
else clk_n <= clk_n;
end
assign clk_p_div = clk_p;
assign clk_n_div = clk_n;
assign clk_div = clk_p | clk_n;
endmodule

Testbench文件

module test_tb2();
reg clk,rstn;
wire clk_p,clk_n,clk_div;
parameter CLK_DIV = 5;
clk_div_odd u_clk_div_odd(.clk(clk),.rstn(rstn),.clk_p_div(clk_p),.clk_n_div(clk_n),.clk_div(clk_div));
initial begin
clk = 0;
rstn = 1;
#(3*CLK_DIV);
rstn = 0;
#(6*CLK_DIV);
rstn = 1;
#(20*CLK_DIV);
end
always #CLK_DIV clk = ~clk;
endmodule

实验二#

八位数码管16进制数字显程序#

功能要求:给出要显示的0-F数字串,要求在8位数码管上显示对应的数字串。
编程功能要点:
1)100Mhz分频,分频系数2152^{15}
2)在分频时钟控制下,每个时钟,选中8个数码管中的一个,既获得数码管使能信号。
3)在分频时钟控制下,每个时钟,更新8个数码管要显示的8个(0-F)数字字符(8*4=32位)的内容。
4)组合电路,根据前面选中的数码管,获得该数码管的显示内容,既确定本次要显示的内容。
5)在分频时钟控制下,每个时钟,根据要显示的数字字符,译码成7段码。
6)组合电路,最后输出数码管使能信号和7段码显示内容。

位选译码选择组合逻辑:位选信号本身就是扫描地址的直接映射,并且组合逻辑响应快,适合驱动硬件选择器,组合逻辑简洁,节省资源。(总之,快速响应地址,直接映射)
段选译码选择时序逻辑:防止组合逻辑抖动,稳定显示,配合分频时钟,同步刷新节奏(总之,保证稳定显示,防止抖动)

如果8位数码管每一位的刷新频率取100hz,由于是循环,就是说刷新选中位是1s中100次,那么在刷新的同时还有迅速把其余7个扫描过去(也是刷新),所以相当于1s中要做800次刷新,所以8位总的刷新频率需要 >= 100HZ * 8= 800hz ,那么干脆取1Khz。时钟输入是 100mhz 时钟,需要分频到1khz(分频clk到1kHz,clk上升沿就扫描向右一个),分频系数=2^16左右即可正常显示。既以1Khz的频率分时选中8位数码管之一(disp_an_o[i]=0),同时更新对应数码管的8段显示数据。

检验数码管(AI生成)

module digital_tube_check (
input clk, // 系统时钟,例如50MHz或100MHz
input rstn, // 复位信号,低电平有效
output reg [7:0] disp_seg_o, // 段选信号 (a, b, c, d, e, f, g, dp)
output reg [7:0] disp_an_o // 位选信号,低电平有效,控制哪个数码管点亮
);
// 时钟分频,产生约763Hz的扫描频率(基于50MHz主时钟, 2^16分频)
// 若主频非50MHz,请调整分频系数以获得理想扫描频率(200Hz-1000Hz)
reg [15:0] scan_cnt;
wire scan_clk;
always @(posedge clk or negedge rstn) begin
if (!rstn)
scan_cnt <= 16'd0;
else
scan_cnt <= scan_cnt + 1'b1;
end
assign scan_clk = scan_cnt[15]; // 取高位作为扫描时钟
// 数码管位选扫描计数器(0-7循环,对应8个数码管)
reg [2:0] dig_sel;
always @(posedge scan_clk or negedge rstn) begin
if (!rstn)
dig_sel <= 3'd0;
else
dig_sel <= dig_sel + 1'b1;
end
// 根据位选信号,点亮对应的数码管(低电平有效)
always @(posedge scan_clk or negedge rstn) begin
if (!rstn)
disp_an_o <= 8'hFF; // 复位时所有数码管关闭
else begin
case (dig_sel)
3'd0: disp_an_o <= 8'b11111110; // 点亮第0个数码管
3'd1: disp_an_o <= 8'b11111101; // 点亮第1个数码管
3'd2: disp_an_o <= 8'b11111011; // 点亮第2个数码管
3'd3: disp_an_o <= 8'b11110111; // 点亮第3个数码管
3'd4: disp_an_o <= 8'b11101111; // 点亮第4个数码管
3'd5: disp_an_o <= 8'b11011111; // 点亮第5个数码管
3'd6: disp_an_o <= 8'b10111111; // 点亮第6个数码管
3'd7: disp_an_o <= 8'b01111111; // 点亮第7个数码管
default: disp_an_o <= 8'b11111111; // 默认全灭
endcase
end
end
// 数字/字符发生器:控制所有数码管同步循环显示0-9和A-F
reg [3:0] data_to_show; // 当前要显示的数字/字符(0-F)
reg [27:0] slow_cnt; // 慢速计数器,控制字符变化速度
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
slow_cnt <= 28'd0;
data_to_show <= 4'd0;
end else begin
slow_cnt <= slow_cnt + 1'b1;
// 大约每0.5秒数字自动加1 (基于50MHz时钟,计数25,000,000次)
// 若主频不同,请调整此比较值以改变变化速度
if (slow_cnt == 28'd25_000_000) begin
slow_cnt <= 28'd0;
if (data_to_show == 4'd15) // 0-F循环,F的十六进制值为15
data_to_show <= 4'd0;
else
data_to_show <= data_to_show + 1'b1;
end
end
end
// 七段数码管译码器:将4位二进制数转换为段选信号
always @(*) begin
if (!rstn) begin
disp_seg_o = 8'hFF; // 复位时段选全部熄灭
end else begin
case (data_to_show)
4'h0: disp_seg_o = 8'hC0; // 显示数字 "0"
4'h1: disp_seg_o = 8'hF9; // 显示数字 "1"
4'h2: disp_seg_o = 8'hA4; // 显示数字 "2"
4'h3: disp_seg_o = 8'hB0; // 显示数字 "3"
4'h4: disp_seg_o = 8'h99; // 显示数字 "4"
4'h5: disp_seg_o = 8'h92; // 显示数字 "5"
4'h6: disp_seg_o = 8'h82; // 显示数字 "6"
4'h7: disp_seg_o = 8'hF8; // 显示数字 "7"
4'h8: disp_seg_o = 8'h80; // 显示数字 "8"
4'h9: disp_seg_o = 8'h90; // 显示数字 "9"
4'hA: disp_seg_o = 8'h88; // 显示字母 "A"
4'hB: disp_seg_o = 8'h83; // 显示字母 "b"(小写便于与数字8区分)
4'hC: disp_seg_o = 8'hC6; // 显示字母 "C"
4'hD: disp_seg_o = 8'hA1; // 显示字母 "d"(小写)
4'hE: disp_seg_o = 8'h86; // 显示字母 "E"
4'hF: disp_seg_o = 8'h8E; // 显示字母 "F"
default: disp_seg_o = 8'hFF; // 默认全灭
endcase
end
end
endmodule

八位数码管16进制字符显程序代码

module seg7x16(
input clk,
input rstn,
input [15:0] sw_i,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
reg [31:0] i_data;
always @(posedge clk or negedge rstn) begin
if (!rstn)
i_data <= 32'h00000000;
else
i_data <= {16'h0000, sw_i}; // 每个时钟周期更新
end
// 2^15分频
reg [14:0] cnt;
wire seg7_clk;
always @(posedge clk or negedge rstn) begin
if(!rstn) cnt <= 0;
else cnt <= cnt + 1'b1;
end
assign seg7_clk = cnt[14];
// 8选1
reg [2:0] seg7_addr;
always @(posedge seg7_clk or negedge rstn) begin
if(!rstn) seg7_addr <= 0;
else seg7_addr <= seg7_addr + 1'b1;
end
// 选中的数码管使能信号
reg [7:0] o_sel_r;
always @(*) begin
case(seg7_addr)
7 : o_sel_r = 8'b01111111;
6 : o_sel_r = 8'b10111111;
5 : o_sel_r = 8'b11011111;
4 : o_sel_r = 8'b11101111;
3 : o_sel_r = 8'b11110111;
2 : o_sel_r = 8'b11111011;
1 : o_sel_r = 8'b11111101;
0 : o_sel_r = 8'b11111110;
endcase
end
//
reg [31:0] i_data_store;
always @(posedge clk or negedge rstn) begin
if(!rstn) i_data_store <= 0;
else i_data_store <= i_data;
end
reg [7:0] seg_data_r;
always @(*) begin
case(seg7_addr)
0 : seg_data_r = i_data_store[3:0];
1 : seg_data_r = i_data_store[7:4];
2 : seg_data_r = i_data_store[11:8];
3 : seg_data_r = i_data_store[15:12];
4 : seg_data_r = i_data_store[19:16];
5 : seg_data_r = i_data_store[23:20];
6 : seg_data_r = i_data_store[27:24];
7 : seg_data_r = i_data_store[31:28];
endcase
end
reg [7:0] o_seg_r;
always @(posedge clk or negedge rstn) begin
if(!rstn) o_seg_r <= 8'hFF;
else
case(seg_data_r)
4'h0 : o_seg_r <= 8'hC0;
4'h1 : o_seg_r <= 8'hF9;
4'h2 : o_seg_r <= 8'hA4;
4'h3 : o_seg_r <= 8'hB0;
4'h4 : o_seg_r <= 8'h99;
4'h5 : o_seg_r <= 8'h92;
4'h6 : o_seg_r <= 8'h82;
4'h7 : o_seg_r <= 8'hF8;
4'h8 : o_seg_r <= 8'h80;
4'h9 : o_seg_r <= 8'h90;
4'hA : o_seg_r <= 8'h88;
4'hB : o_seg_r <= 8'h83;
4'hC : o_seg_r <= 8'hC6;
4'hD : o_seg_r <= 8'hA1;
4'hE : o_seg_r <= 8'h86;
4'hF : o_seg_r <= 8'h8E;
endcase
end
assign disp_an_o = o_sel_r;
assign disp_seg_o = o_seg_r;
endmodule

Testbench文件
(使用前需将综合代码中的cnt改为[2:0],以及seg7_clk = cnt[2],原因如下)
在你的设计中:

  • ​​主时钟​​ clk:100MHz(周期10ns)
  • ​扫描时钟​​ seg7_clk:通过对主时钟进行 2152^{15} 分频得到,频率约为 100MHz/32768 ≈ 3.05kHz(2152^{15}分频则周期为clk周期的2152^{15}倍)

但在之前的仿真中:

  • ​仿真时间​只有 1000ns
  • ​​扫描时钟周期​​约为 32768 × 10ns = 327,680ns
    这意味着在 1000ns 的仿真时间内,seg7_clk根本来不及产生一个完整的时钟周期,seg7_addr计数器无法递增,所以位选信号 o_sel_r始终停留在初始值。

Testbench

module tb_seg7x16 (); // 测试模块,无需输入输出端口 [2,5](@ref)
// 1. 定义信号类型 [2,9](@ref)
reg clk; // 时钟信号,定义为reg,用于产生激励
reg rstn; // 复位信号(低有效),定义为reg
reg [15:0] sw_i; // 16位输入开关信号,定义为reg
wire [7:0] disp_seg_o; // 8段数码管段选信号,观察输出,定义为wire
wire [7:0] disp_an_o; // 8位数码管位选信号,观察输出,定义为wire
// 2. 实例化被测模块 (Unit Under Test, UUT) [1,2,3](@ref)
seg7x16 u_seg7x16 (
.clk (clk),
.rstn (rstn),
.sw_i (sw_i),
.disp_seg_o (disp_seg_o),
.disp_an_o (disp_an_o)
);
// 3. 生成时钟信号 [1,3,6,8](@ref)
parameter CLK_PERIOD = 10; // 100MHz时钟,周期为10ns
initial begin
clk = 1'b0;
forever #(CLK_PERIOD/2) clk = ~clk; // 每半个周期翻转一次,产生时钟
end
// 4. 生成测试激励 [1,3,9](@ref)
initial begin
// 4.1 信号初始化 [2,5,9](@ref)
rstn = 1'b0; // 初始时复位有效
sw_i = 16'h0000;
// 4.2 在特定时间撤销复位,并改变输入值,模拟不同场景
#100; // 等待100ns,让仿真稳定
rstn = 1'b1; // 撤销复位,让模块开始工作 [5,9](@ref)
sw_i = 16'h1234; // 赋予一个初始的16位输入值
#300; // 运行300ns后
sw_i = 16'hABCD; // 改变输入值,观察数码管输出变化
#500; // 再运行500ns
sw_i = 16'h5678; // 再次改变输入值
#100; // 再运行100ns,总时间达到1000ns
$stop; // 暂停仿真(或者使用 $finish; 结束仿真)[9](@ref)
end
// 5. (可选)监控信号变化,在终端打印信息 [9,10](@ref)
initial begin
$timeformat(-9, 1, " ns", 12); // 设置时间显示格式(纳秒,小数点后1位)[9](@ref)
$monitor("Time = %t, rstn = %b, sw_i = %h, disp_an_o = %b", $time, rstn, sw_i, disp_an_o);
// 监控关键信号的变化,有助于调试
end
endmodule

八位7段码矩形变换跑马灯#

矩形变换7段码编码表一

编号8段码DPGFEDCBA独立编码16进制
1C6F6F6F0
1111_0000F0
1111_0110F6
1111_0110F6
1100_10110C6
2F9F6F6CF
1100_1111CF
11110110F6
11110110F6
1111_1001F9
3FFC6F0FF
1111_1111FF
1111_0000F0
1100_0110C6
1111_1111FF
4FFC0FFFF
1111_1111FF
1111_1111FF
1100_0000C0
1111_1111FF
5FFA3FFFF
1111_1111FF
1111_1111FF
1010_0011A3
1111_1111FF
6FFFFA3FF
1111_1111FF
1010_0011A3
1111_1111FF
1111_1111FF
7FFFF9CFF
1111_1111FF
1001_11009C
1111_1111FF
1111_1111FF

矩形变换7段码编码表二

编号8段码DPGFEDCBA16进制
8FF9EBCFF
1111_1111FF
1011_1100BC
1001_11109E
1111_1111FF
9FF9CFFFF
1111_1111FF
1111_1111FF
1001_11009C
1111_1111FF
10FFC0FFFF
1111_1111FF
1111_1111FF
1100_0000C0
1111_1111FF
11FFA3FFFF
1111_1111FF
1111_1111FF
1010_0011A3
1111_1111FF
12FFA7B3FF
1111_1111FF
1011_0011B3
1010_0111A7
1111_1111FF
13FFC6F0FF
1111_1111FF
1111_0000F0
1100_0110C6
1111_1111FF
14F9F6F6CF
1100_1111CF
1111_0110F6
1111_0110F6
1111_1001F9
采用自然独立编码方法,一个数码管8位,有8个数码管,总字长64位,采用一个64位单元存储,或两个32位单元存储。高位数码管放高位,低位数码管放低位。每个数码管内部,采用7段码编码顺序(DpGFEDCBA)存放。如下图所示。Pasted image 20251025161552
MyCode
seg7_top
module seg7_top(
input clk,
input rstn,
input [15:0] sw_i,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
parameter div_num = 26;
wire clk_div;
reg [63:0] seg_code;
reg [3:0] index;
reg [31:0] clk_cnt;
always @(posedge clk or negedge rstn) begin
if(!rstn) clk_cnt <= 32'b0;
else clk_cnt <= clk_cnt + 1'b1;
end
assign clk_div = clk_cnt[div_num];
always @(posedge clk_div or negedge rstn) begin
if(!rstn) index <= 4'd1;
else if(sw_i[0] == 1'b1) begin
if(index == 4'd14) index <= 4'd1;
else index <= index + 4'd1;
end
else index <= index;
end
always @(*) begin
case (index)
4'd1: seg_code = 64'hC6F6F6F0C6F6F6F0;
4'd2: seg_code = 64'hF9F6F6CFF9F6F6CF;
4'd3: seg_code = 64'hFFC6F0FFFFC6F0FF;
4'd4: seg_code = 64'hFFC0FFFFFFC0FFFF;
4'd5: seg_code = 64'hFFA3FFFFFFA3FFFF;
4'd6: seg_code = 64'hFFFFA3FFFFFFA3FF;
4'd7: seg_code = 64'hFFFF9CFFFFFF9CFF;
4'd8: seg_code = 64'hFF9EBCFFFF9EBCFF;
4'd9: seg_code = 64'hFF9CFFFFFF9CFFFF;
4'd10: seg_code = 64'hFFC0FFFFFFC0FFFF;
4'd11: seg_code = 64'hFFA3FFFFFFA3FFFF;
4'd12: seg_code = 64'hFFA7B3FFFFA7B3FF;
4'd13: seg_code = 64'hFFC6F0FFFFC6F0FF;
4'd14: seg_code = 64'hF9F6F6CFF9F6F6CF;
default: seg_code = 64'hFFFFFFFFFFFFFFFF;
endcase
end
seg7x16 u_seg7x16(
.clk(clk),
.rstn(rstn),
.disp_mode(sw_i[0]),
.i_data(seg_code),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
endmodule

seg7x16

module seg7x16(
input clk,
input rstn,
input disp_mode,
input [63:0] i_data,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
// 2^15分频
reg [14:0] cnt;
wire seg7_clk;
always @(posedge clk or negedge rstn) begin
if(!rstn) cnt <= 0;
else cnt <= cnt + 1'b1;
end
assign seg7_clk = cnt[14];
// 8选1
reg [2:0] seg7_addr;
always @(posedge seg7_clk or negedge rstn) begin
if(!rstn) seg7_addr <= 0;
else seg7_addr <= seg7_addr + 1'b1;
end
// 选中的数码管使能信号
reg [7:0] o_sel_r;
always @(*) begin
case(seg7_addr)
7 : o_sel_r = 8'b01111111;
6 : o_sel_r = 8'b10111111;
5 : o_sel_r = 8'b11011111;
4 : o_sel_r = 8'b11101111;
3 : o_sel_r = 8'b11110111;
2 : o_sel_r = 8'b11111011;
1 : o_sel_r = 8'b11111101;
0 : o_sel_r = 8'b11111110;
endcase
end
//
reg [63:0] i_data_store;
always @(posedge clk or negedge rstn) begin
if(!rstn) i_data_store <= 0;
else i_data_store <= i_data;
end
reg [7:0] seg_data_r;
always @(*) begin
if(disp_mode == 1'b0) begin // 字符显示模式
case(seg7_addr)
0 : seg_data_r = i_data_store[3:0];
1 : seg_data_r = i_data_store[7:4];
2 : seg_data_r = i_data_store[11:8];
3 : seg_data_r = i_data_store[15:12];
4 : seg_data_r = i_data_store[19:16];
5 : seg_data_r = i_data_store[23:20];
6 : seg_data_r = i_data_store[27:24];
7 : seg_data_r = i_data_store[31:28];
default: seg_data_r = 8'hFF; // 默认全空,防止latch
endcase
end
else begin // 图形显示模式
case(seg7_addr)
0 : seg_data_r = i_data_store[7:0];
1 : seg_data_r = i_data_store[15:8];
2 : seg_data_r = i_data_store[23:16];
3 : seg_data_r = i_data_store[31:24];
4 : seg_data_r = i_data_store[39:32];
5 : seg_data_r = i_data_store[47:40];
6 : seg_data_r = i_data_store[55:48];
7 : seg_data_r = i_data_store[63:56];
default: seg_data_r = 8'hFF; // 默认全空,防止latch
endcase
end
end
reg [7:0] o_seg_r;
always @(posedge clk or negedge rstn) begin
if(!rstn) o_seg_r <= 8'hFF;
else if(disp_mode == 1'b0) begin // 字符模式
case(seg_data_r)
4'h0 : o_seg_r <= 8'hC0;
4'h1 : o_seg_r <= 8'hF9;
4'h2 : o_seg_r <= 8'hA4;
4'h3 : o_seg_r <= 8'hB0;
4'h4 : o_seg_r <= 8'h99;
4'h5 : o_seg_r <= 8'h92;
4'h6 : o_seg_r <= 8'h82;
4'h7 : o_seg_r <= 8'hF8;
4'h8 : o_seg_r <= 8'h80;
4'h9 : o_seg_r <= 8'h90;
4'hA : o_seg_r <= 8'h88;
4'hB : o_seg_r <= 8'h83;
4'hC : o_seg_r <= 8'hC6;
4'hD : o_seg_r <= 8'hA1;
4'hE : o_seg_r <= 8'h86;
4'hF : o_seg_r <= 8'h8E;
default : o_seg_r <= 8'hFF;
endcase
end
else o_seg_r <= seg_data_r;
end
assign disp_an_o = o_sel_r;
assign disp_seg_o = o_seg_r;
endmodule

Another Code
seg7_top

module sccomp(
input clk,
input rstn,
input [15:0] sw_i,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
reg [31:0] clkdiv;
wire Clk_CPU;
always @(posedge clk or negedge rstn) begin
if(!rstn) clkdiv <= 0;
else clkdiv <= clkdiv + 1'b1;
end
assign Clk_CPU = (sw_i[15])?clkdiv[27]:clkdiv[25]; // 2^27 和 2^25分频
reg [63:0] display_data;
reg [5:0] led_data_addr;
reg [63:0] led_disp_data;
parameter LED_DATA_NUM = 19;
reg [63:0] LED_DATA[18:0];
initial begin
LED_DATA[0] = 64'hC6F6F6F0C6F6F6F0;
LED_DATA[1] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[2] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[3] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[4] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[5] = 64'hFFFFA3FFFFFFA3FF;
LED_DATA[6] = 64'hFFFF9CFFFFFF9CFF;
LED_DATA[7] = 64'hFF9EBCFFFF9EBCFF;
LED_DATA[8] = 64'hFF9CFFFFFF9CFFFF;
LED_DATA[9] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[10] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[11] = 64'hFFA7B3FFFFA7B3FF;
LED_DATA[12] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[13] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[14] = 64'h9EBEBEBC9EBEBEBC;
LED_DATA[15] = 64'h2737373327373733;
LED_DATA[16] = 64'h505454EC505454EC;
LED_DATA[17] = 64'h744454F8744454F8;
LED_DATA[18] = 64'h0062080000620800;
end
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
led_data_addr <= 6'd0;
led_disp_data <= 64'b1;
end
else if(sw_i[0] == 1'b1) begin
if(led_data_addr == LED_DATA_NUM) begin
led_data_addr <= 6'd0;
led_disp_data <= 64'b1;
end
led_disp_data <= LED_DATA[led_data_addr];
led_data_addr <= led_data_addr + 1'b1;
end
else led_data_addr <= led_data_addr;
end
wire [31:0] instr;
reg [31:0] reg_data;
reg [31:0] alu_disp_data;
reg [31:0] dmem_data;
always @(sw_i) begin
if(sw_i[0] == 0) begin
case(sw_i[14:11])
4'b1000 : display_data = instr;
4'b0100 : display_data = reg_data;
4'b0010 : display_data = alu_disp_data;
4'b0001 : display_data = dmem_data;
default : display_data = instr;
endcase
end
else display_data = led_disp_data;
end
seg7x16 u_seg7x16(
.clk(clk),
.rstn(rstn),
.disp_mode(sw_i[0]),
.i_data(display_data),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
endmodule

此代码导致板子会有全亮的原因
​主要问题在于时序逻辑的竞争条件:​

  1. ​地址越界时的处理​​:当 led_data_addr == 19时,代码试图重置地址并设置 led_disp_data <= 64'b1
  2. ​但立即被覆盖​​:在同一个时钟边沿,下一行 led_disp_data <= LED_DATA[led_data_addr]会覆盖前面的赋值
  3. ​关键问题​​:当 led_data_addr = 19时,LED_DATA[19]是​​未定义的​​!
    • LED_DATA数组只定义了索引 0-18
    • 访问 LED_DATA[19]会读取到未初始化的内存内容
    • 在仿真中这可能显示为全1(64'hFFFFFFFFFFFFFFFF

Revised-code

module sccomp(
input clk,
input rstn,
input [15:0] sw_i,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
reg [31:0] clkdiv;
wire Clk_CPU;
always @(posedge clk or negedge rstn) begin
if(!rstn) clkdiv <= 0;
else clkdiv <= clkdiv + 1'b1;
end
assign Clk_CPU = (sw_i[15])?clkdiv[27]:clkdiv[25]; // 2^27 和 2^25分频
reg [63:0] display_data;
reg [5:0] led_data_addr;
reg [63:0] led_disp_data;
parameter LED_DATA_NUM = 19;
reg [63:0] LED_DATA[18:0];
initial begin
LED_DATA[0] = 64'hC6F6F6F0C6F6F6F0;
LED_DATA[1] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[2] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[3] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[4] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[5] = 64'hFFFFA3FFFFFFA3FF;
LED_DATA[6] = 64'hFFFF9CFFFFFF9CFF;
LED_DATA[7] = 64'hFF9EBCFFFF9EBCFF;
LED_DATA[8] = 64'hFF9CFFFFFF9CFFFF;
LED_DATA[9] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[10] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[11] = 64'hFFA7B3FFFFA7B3FF;
LED_DATA[12] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[13] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[14] = 64'h9EBEBEBC9EBEBEBC;
LED_DATA[15] = 64'h2737373327373733;
LED_DATA[16] = 64'h505454EC505454EC;
LED_DATA[17] = 64'h744454F8744454F8;
LED_DATA[18] = 64'h0062080000620800;
end
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
led_data_addr <= 6'd0;
led_disp_data <= 64'b1;
end
else if(sw_i[0] == 1'b1) begin
if(led_data_addr == LED_DATA_NUM) begin
led_data_addr <= 6'd0;
led_disp_data <= LED_DATA[0];
end
else begin
led_disp_data <= LED_DATA[led_data_addr];
led_data_addr <= led_data_addr + 1'b1;
end
end
else led_data_addr <= led_data_addr;
end
wire [31:0] instr;
reg [31:0] reg_data;
reg [31:0] alu_disp_data;
reg [31:0] dmem_data;
always @(sw_i) begin
if(sw_i[0] == 0) begin
case(sw_i[14:11])
4'b1000 : display_data = instr;
4'b0100 : display_data = reg_data;
4'b0010 : display_data = alu_disp_data;
4'b0001 : display_data = dmem_data;
default : display_data = instr;
endcase
end
else display_data = led_disp_data;
end
seg7x16 u_seg7x16(
.clk(clk),
.rstn(rstn),
.disp_mode(sw_i[0]),
.i_data(display_data),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
endmodule

贪吃蛇#

1: FFFFFFFEFEFEFEFE
2: FFFEFEFEFEFEFFFF
3: DEFEFEFEFFFFFFFF
4: CEFEFEFFFFFFFFFF
5: C2FFFFFFFFFFFFFF
6: C0FEFFFFFFFFFFFF
7: F1FCFFFFFFFFFFFF
8: FDF8F7FFFFFFFFFF
9: FFF8F3FFFFFFFFFF
10: FFFBF1FEFFFFFFFF
11: FFFFF9F8FFFFFFFF
12: FFFFFDF8F7FFFFFF
13: FFFFFFF9F1FFFFFF
14: FFFFFFFFF1FCFFFF
15: FFFFFFFFF9F8FFFF
16: FFFFFFFFFFF8F3FF
17: FFFFFFFFFFFBF1FE
18: FFFFFFFFFFFFF9BC
19: FFFFFFFFFFFFBDBC
20: FFFFFFFFBFBFBFBD
21: FFFFBFBFBFBFBFFF
22: FFBFBFBFBFBFFFFF
23: AFBFBFBFFFFFFFFF
24: 2737FFFFFFFFFFFF
25: 277777FFFFFFFFFF
26: 7777777777FFFFFF
27: FFFF7777777777FF
28: FFFFFF7777777777
29: FFFFFFFFFF777771
30: FFFFFFFFFFFF7770
31: FFFFFFFFFFFFFFC8
32: FFFFFFFFFFFFE7CE
33: FFFFFFFFFFFFC7CF
34: FFFFFFFFFFDEC7FF
35: FFFFFFFFF7CEDFFF
36: FFFFFFFFC7CFFFFF
37: FFFFFFFEC7EFFFFF
38: FFFFFFCECFFFFFFF
39: FFFFE7CEFFFFFFFF
40: FFFFC7CFFFFFFFFF
41: FFDEC7FFFFFFFFFF
42: F7CEDFFFFFFFFFFF
43: A7CFFFFFFFFFFFFF
44: A7AFFFFFFFFFFFFF
45: AFBFBFBFFFFFFFFF
46: BFBFBFBFBFFFFFFF
47: FFFFBFBFBFBFBFFF
48: FFFFFFFFBFBFBFBD
49: FFFFFFFFFFFFBEBC
50: FFFFFFFFFEFEFEFC

DP CG CF CE CD CC CB CA
Pasted image 20251025201320

Pasted image 20251025201810

Pasted image 20251025204904
FE
Pasted image 20251025204918
DE
Pasted image 20251025204923
CE
Pasted image 20251025204936
C2

Pasted image 20251025205013
C0
Pasted image 20251025205026
F1
Pasted image 20251025205043
FC
Pasted image 20251025205051
FD
Pasted image 20251025205057
F8
Pasted image 20251025205105
F7
Pasted image 20251025205112
F3
Pasted image 20251025205121
FB
Pasted image 20251025205130
F9
Pasted image 20251025205158
BC
Pasted image 20251025205211
BD
Pasted image 20251025205220
BF
Pasted image 20251025205227
AF
Pasted image 20251025205234
27
Pasted image 20251025205240
37
Pasted image 20251025205250
77
Pasted image 20251025205259
71
Pasted image 20251025205306
70
Pasted image 20251025205313
C8
Pasted image 20251025205337
E7
Pasted image 20251025205354
C7
Pasted image 20251025205411
CF

Pasted image 20251025205448
DF
Pasted image 20251025205500
EF
Pasted image 20251025205527
A7

seg7_snake

module seg7_snake(
input clk,
input rstn,
input [15:0] sw_i,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
reg [31:0] clkdiv;
wire Clk_CPU;
always @(posedge clk or negedge rstn) begin
if(!rstn) clkdiv <= 0;
else clkdiv <= clkdiv + 1'b1;
end
assign Clk_CPU = (sw_i[15])?clkdiv[27]:clkdiv[25]; // 2^27 和 2^25分频
reg [5:0] led_data_addr;
reg [63:0] led_disp_data;
parameter LED_DATA_NUM = 50;
reg [63:0] LED_DATA[50:1];
initial begin
LED_DATA[1] = 64'hFFFFFFFEFEFEFEFE;
LED_DATA[2] = 64'hFFFEFEFEFEFEFFFF;
LED_DATA[3] = 64'hDEFEFEFEFFFFFFFF;
LED_DATA[4] = 64'hCEFEFEFFFFFFFFFF;
LED_DATA[5] = 64'hC2FFFFFFFFFFFFFF;
LED_DATA[6] = 64'hC0FEFFFFFFFFFFFF;
LED_DATA[7] = 64'hF1FCFFFFFFFFFFFF;
LED_DATA[8] = 64'hFDF8F7FFFFFFFFFF;
LED_DATA[9] = 64'hFFF8F3FFFFFFFFFF;
LED_DATA[10] = 64'hFFFBF1FEFFFFFFFF;
LED_DATA[11] = 64'hFFFFF9F8FFFFFFFF;
LED_DATA[12] = 64'hFFFFFDF8F7FFFFFF;
LED_DATA[13] = 64'hFFFFFFF9F1FFFFFF;
LED_DATA[14] = 64'hFFFFFFFFF1FCFFFF;
LED_DATA[15] = 64'hFFFFFFFFF9F8FFFF;
LED_DATA[16] = 64'hFFFFFFFFFFF8F3FF;
LED_DATA[17] = 64'hFFFFFFFFFFFBF1FE;
LED_DATA[18] = 64'hFFFFFFFFFFFFF9BC;
LED_DATA[19] = 64'hFFFFFFFFFFFFBDBC;
LED_DATA[20] = 64'hFFFFFFBFBFBFBFBD;
LED_DATA[21] = 64'hFFFFBFBFBFBFBFFF;
LED_DATA[22] = 64'hFFBFBFBFBFBFFFFF;
LED_DATA[23] = 64'hAFBFBFBFFFFFFFFF;
LED_DATA[24] = 64'h2737FFFFFFFFFFFF;
LED_DATA[25] = 64'h277777FFFFFFFFFF;
LED_DATA[26] = 64'h7777777777FFFFFF;
LED_DATA[27] = 64'hFFFF7777777777FF;
LED_DATA[28] = 64'hFFFFFF7777777777;
LED_DATA[29] = 64'hFFFFFFFFFF777771;
LED_DATA[30] = 64'hFFFFFFFFFFF77770;
LED_DATA[31] = 64'hFFFFFFFFFFFFFFC8;
LED_DATA[32] = 64'hFFFFFFFFFFFFE7CE;
LED_DATA[33] = 64'hFFFFFFFFFFFFC7CF;
LED_DATA[34] = 64'hFFFFFFFFFFDEC7FF;
LED_DATA[35] = 64'hFFFFFFFFF7CEDFFF;
LED_DATA[36] = 64'hFFFFFFFFC7CFFFFF;
LED_DATA[37] = 64'hFFFFFFFEC7EFFFFF;
LED_DATA[38] = 64'hFFFFFFCECFFFFFFF;
LED_DATA[39] = 64'hFFFFE7CEFFFFFFFF;
LED_DATA[40] = 64'hFFFFC7CFFFFFFFFF;
LED_DATA[41] = 64'hFFDEC7FFFFFFFFFF;
LED_DATA[42] = 64'hF7CEDFFFFFFFFFFF;
LED_DATA[43] = 64'hA7CFFFFFFFFFFFFF;
LED_DATA[44] = 64'hA7AFFFFFFFFFFFFF;
LED_DATA[45] = 64'hAFBFBFBFFFFFFFFF;
LED_DATA[46] = 64'hBFBFBFBFBFFFFFFF;
LED_DATA[47] = 64'hFFFFBFBFBFBFBFFF;
LED_DATA[48] = 64'hFFFFFFBFBFBFBFBD;
LED_DATA[49] = 64'hFFFFFFFFFFFFBEBC;
LED_DATA[50] = 64'hFFFFFFFFFEFEFEFC;
end
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
led_data_addr <= 6'd1;
led_disp_data <= LED_DATA[1];
end
else if(sw_i[0] == 1'b0) begin
if(led_data_addr == LED_DATA_NUM + 1) begin
led_data_addr <= 6'd1;
led_disp_data <= LED_DATA[1];
end
else begin
led_disp_data <= LED_DATA[led_data_addr];
led_data_addr <= led_data_addr + 1'b1;
end
end
else if(sw_i[0] == 1'b1) begin
if(led_data_addr == 0) begin
led_data_addr <= LED_DATA_NUM;
led_disp_data <= LED_DATA[LED_DATA_NUM];
end
else begin
led_disp_data <= LED_DATA[led_data_addr];
led_data_addr <= led_data_addr - 1'b1;
end
end
else led_data_addr <= led_data_addr;
end
seg7_display u_seg7_display(
.clk(clk),
.rstn(rstn),
.i_data(led_disp_data),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
endmodule

seg7_display

module seg7_display(
input clk,
input rstn,
input [63:0] i_data,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
// 2^15分频
reg [14:0] cnt;
wire seg7_clk;
always @(posedge clk or negedge rstn) begin
if(!rstn) cnt <= 0;
else cnt <= cnt + 1'b1;
end
assign seg7_clk = cnt[14];
// 8选1
reg [2:0] seg7_addr;
always @(posedge seg7_clk or negedge rstn) begin
if(!rstn) seg7_addr <= 0;
else seg7_addr <= seg7_addr + 1'b1;
end
// 选中的数码管使能信号
reg [7:0] o_sel_r;
always @(*) begin
case(seg7_addr)
7 : o_sel_r = 8'b01111111;
6 : o_sel_r = 8'b10111111;
5 : o_sel_r = 8'b11011111;
4 : o_sel_r = 8'b11101111;
3 : o_sel_r = 8'b11110111;
2 : o_sel_r = 8'b11111011;
1 : o_sel_r = 8'b11111101;
0 : o_sel_r = 8'b11111110;
endcase
end
//
reg [63:0] i_data_store;
always @(posedge clk or negedge rstn) begin
if(!rstn) i_data_store <= 0;
else i_data_store <= i_data;
end
reg [7:0] seg_data_r;
always @(*) begin
case(seg7_addr)
0 : seg_data_r = i_data_store[7:0];
1 : seg_data_r = i_data_store[15:8];
2 : seg_data_r = i_data_store[23:16];
3 : seg_data_r = i_data_store[31:24];
4 : seg_data_r = i_data_store[39:32];
5 : seg_data_r = i_data_store[47:40];
6 : seg_data_r = i_data_store[55:48];
7 : seg_data_r = i_data_store[63:56];
endcase
end
reg [7:0] o_seg_r;
always @(posedge clk or negedge rstn) begin
if(!rstn) o_seg_r <= 8'hFF;
else o_seg_r <= seg_data_r;
end
assign disp_an_o = o_sel_r;
assign disp_seg_o = o_seg_r;
endmodule

3.ROM(RAM)模块设计流程#

3.1 FPGA概念及关联#

LUT#

一个N输入查找表LUT,可以实现N个输入变量的任何逻辑功能:通过输入和输出的真值表设定输入,使输出与输入符合真值表。
输入多于N个,则需分开用多个LUT实现。
Pasted image 20251026094816

Pasted image 20251026095147

  1. 实现组合逻辑:
    比如我希望ABCD = 0001时输出0,我设定左边RAM的数据,通过1000的选择输出0。
    比如实现 AND 门,真值表:
    输入组合:00 → 输出 0
    输入组合:01 → 输出 0
    输入组合:10 → 输出 0
    输入组合:11 → 输出 1

实现 OR 门,真值表:
输入组合:00 → 输出 0
输入组合:01 → 输出 1
输入组合:10 → 输出 1
输入组合:11 → 输出 1

总而言之,LUT设置不同的RAM值可以实现各种门电路的功能。

  1. 实现时序逻辑(添加CLK和D触发器)
    Pasted image 20251026095601

当用户通过Verilog或VHDL语言描述了一个逻辑电路以后,EDA开发软件会自动计算逻辑电路的所有可能的结果,并把结果事先写入RAM,这样,每输入一组信号进行逻辑运算就等于输入一个地址进行查表,找出地址对应的内容,然后输出即可,LUT本质上就是一个RAM。

LATCH#

LATCH:一种锁存器。电平触发,非同步控制。在使能信号有效时,LATCH相当于通路(en=1,则 output =input);使能信号无效时,LATCH保存不变。(en=0,则output(当前)=output(之前))

Latch优点:
1、面积比ff小:门电路时构成组合逻辑电路的基础,锁存器和寄存器是时序逻辑电路的基础。构成顺序:晶体管==>门电路==>锁存器==>触发器。
2、latch锁存器速度比ff触发器速度快。
缺点:
1、电平触发,非同步设计,受到布线延迟影响大,容易导致输出产生毛刺

FF#

FF(Flip Flop):触发器,时钟沿触发,可以存储1bit的数据,是reg的基本存储单位。
Pasted image 20251026100240
DFF:D类触发器,是边沿触发,属于时序逻辑。(reg就是由DFF组成,一个8位寄存器,用了8个同步的D触发器)
FF的优点:
1、边沿触发,同步设计,不容易受到毛刺的影响。
2、时序分析简单。
缺点:
1、面积比latch大(锁存器==>寄存器)

LUT、LATCH、FF的相互关系#

LUT与LATCH共同点:属于组合逻辑(不受时钟沿影响) 
不同点:LUT是FPGA最小单元的组成结构,LATCH不是最小组成单元

LATCH和FF不同点:LATCH锁存器可以将输出进行保持不变;触发器FF是指通过时钟触发沿触发的存储单元;由敏感信号(电平,边沿)控制的锁存器就是触发器。

verilog语句与LUT、LATCH、FF的对应关系#

1、always@(*)  或者 assign 时,综合出的为LUT(不受时钟控制)

2、always@(*)时,若if不完整(无else)或者case不完整(无default)时,会综合出latch,导致不稳定。

3、always@(posedge clk)时,综合出的为reg,即使if或case不完整时,也不会综合出latch

逻辑单元LE
用于完成用户逻辑的最小单元,在FPGA内部。(intel叫logic element,xilinx叫logic cell)。一个逻辑单元由以下部件组成:一个四输入的查找表(LookUpTable,LUT),一个可编程的寄存器,一条进位链,一条寄存器级连链。

3.2 FPGA中的块(block) RAM和分布式(distributed)RAM的区别#

distributed RAM就是用LUT实现。
1、块RAM由FPGA板内嵌的RAM资源构成,如M9K memory blocks,而分布式(distributed)RAM由逻辑资源CLB中的SliceM构成;
2、一般使用块RAM,因为FPGA中逻辑资源较少。
3、块RAM的工作一定需要时钟,而分布式RAM由SLICEM,可以工作在组合逻辑或时序逻辑,可用clk或不用。
4、需要较大RAM,一般用RAM,留出LUT实现其他逻辑
CLB:可配置逻辑块
Pasted image 20251026110648

1.存储资源的构成来源

项目块 RAM(Block RAM)分布式 RAM(Distributed RAM)
构成资源FPGA 芯片中专门预留的 嵌入式存储块(如 Xilinx 的 BRAM、Intel 的 M9K、M20K)逻辑单元中的查找表(LUT) 资源构成,特别是支持 RAM 模式的 SliceM
位置分布在 FPGA 芯片的特定区域,通常是垂直排列的存储块分布在整个 FPGA 的逻辑阵列中,靠近逻辑单元

2.使用方式与适用场景

项目块 RAM分布式 RAM
适合场景大容量数据存储,如图像缓存、FIFO、双口 RAM小容量、高速访问的局部存储,如寄存器文件、查找表
容量单个块通常为几 Kbits(如 18Kb、36Kb)每个 LUT 可实现 16~64 bits,容量较小
可配置性支持单口/双口、同步读写、初始化内容等灵活配置,但容量受限,适合小型 RAM
资源消耗占用专用 RAM 资源,不影响逻辑资源占用 LUT 数量,影响逻辑电路实现能力

3.时序特性与时钟依赖

项目块 RAM分布式 RAM
是否需要时钟必须使用时钟,为同步 RAM✅/❌ 可选时钟,既可作为同步 RAM,也可作为组合逻辑(异步查找表)
延迟一般为 1 个时钟周期可配置为 0 延迟(组合逻辑)或 1 个周期(同步)
灵活性固定结构,功能强但不如分布式灵活灵活性高,可实现异步查找表、ROM、FIFO 等

4.设计建议与权衡

  • 优先使用块 RAM:因为它是专用资源,不占用宝贵的逻辑资源(LUT 和 FF),适合大容量存储。
  • 分布式 RAM 用于小型缓存:如寄存器堆、状态表、局部缓存等,适合对速度要求高、容量小的场景。
  • 资源平衡:当块 RAM 不足时,可以考虑用分布式 RAM 补充;反之亦然。

块 RAM 是专为存储设计的“内建存储器”,适合大容量、同步访问的场景;而分布式 RAM 是用 LUT 构建的“逻辑资源变身存储器”,适合小容量、灵活访问的场景

3.3 SLICEL、SLICEM功能的异同#

在XILINX 7系中,FPGA芯片含有可配置逻辑单元CLB(Configurable Logic Block),它又由两种SLICE组成。SLICEL(L:Logic)和SLICEM(M:Memory)。

SLICEL和SLICEM内部都包含以下4种部件,下图为SLICE内部框图:
·4个6输入查找表(Look-Up-Table,LUT6)
·8个触发器(Flip-Flop)
·3个数据选择器(MUX)
·1个进位链(Carry Chain)

SLICEM具备完整的SLICEL功能之外,它还具有写存储数据功能。通过下图进行对比,两者只有红框里的LUT6是不同的,其他结构完全一样。
Pasted image 20251026103344
两种SLICE,分别为SLICEL,和SLICEM

放大上图后我们可以看出区别
Pasted image 20251026103352
图: 两种SLICE结构图
相同点:都具有地址输入线(A1-A6),两个输出口(O5-O6)。
不同点:SLICEM有写地址输入线(WA1-WA8),写数据端(DI1 DI2),写使能端(WE),时钟输入端clk,而SLICEL的LUT6没有。

因此SLICEM的LUT6可以读写存储数据,而SLICEL的LUT6只能读数据不能写。所以SLICEM的LUT6可以做RAM,而SLICEL的LUT6只能做ROM。

3.4 LUT RAM实现原理#

原理:数据选择器
一个SLICE里面有3个数据选择器(MUX),分别为F7AMUX、F7BMUX、F8MUX,可以组合成高位RAM.

F7AMUX 连接两个Lut6,产生类似lut7 输入的功能,输入来源自LUT A 、LUT B。
F7BMUX 连接两个Lut6,产生类似lut7输入的功能,输入源自 LUT C、 LUT D。
F8MUX 连接两个Lut7产生 产生类似lut8 输入的功能,输入来源于 F7AMUX 、F7BMUX 。

其中F7AMUX(LUT A(64*1RAM)+LUT B(64*1RAM))= LUT E(128*1RAM)
同样F7BMUX(LUT C(64*1RAM)+LUT D(64*1RAM))= LUT F(128*1RAM)
最后 F8MUX(LUT E(128*1RAM) + LUT F(128*1RAM)) = LUT G(256*1 RAM)

3.5 常用 RAM实现图#

Pasted image 20251026103558
上图64x1单端口DRAM
Pasted image 20251026103605
上图128x1单端口DRAM
Pasted image 20251026103608
图 双端口DRAM结构

3.6 分布式ROM#

SLICEL和SLICEM的每一个LUT都可以实现64*1bit的ROM。和RAM实现的方式原理是一样,就是取消了写地址和写使能,只需要对地址信号的查找即可。

3.7 Vivado ROM IP核的建立#

1、查找 ROM IP核#

1)如下图单击ipcatalog 选项(IP:设计好的,有专利的硬件功能模块)
Pasted image 20251026103938
2)查找distributed memory
在ip calalog 页面 输入查找关键字memory,并点击结果distributed memory 选项。
Pasted image 20251026104002

2、配置和初始化 ROM IP核#

1)Main Screen设置
配置成ROM,设置名称、数据位宽和深度。
Pasted image 20251026104011
关注左图的输入和输出端口位数以及与右边数据的关系
Depth:64 -> 64个地址,5位即可选择,a[5:0]。
Data width:32 -> 数据为32位,所以输出,spo[31:0]。
Depth深度可以理解为按地址顺序往下排,可以排64行,理解为深度。

2)Port Configuration Screen
输入、输出端口配置成直通方式。
Pasted image 20251026104037
增加寄存器会有时延,这里是单周期CPU,所以不添加寄存器。

3)初始化ROM IP核
3.1设置coe文件路径。coe文件里包含ROM初始化信息(这里包含的信息为:数据进制和数据)
Pasted image 20251026104118
3.2验证COE文件的有效性,并保存。
Pasted image 20251026104123
3.2.1 验证该COE文件
Pasted image 20251026104128
3.2.2 COE文件通过验证
Pasted image 20251026104132
3.3 系统生成ROM IP核对应文件
Pasted image 20251026104138
3.3.1 确认生成ROM核文件
Pasted image 20251026104143
“Out of context per IP” 的含义
Out of context 脱离上下文,即独立综合
这是一个综合策略选项,决定了Vivado如何编译(综合)你的IP核。
两种模式对比:

模式工作方式优点缺点适用场景
Out of context per IP
(为每个IP独立综合)
1. 预综合:先单独把IP核综合成独立的网表文件(.dcp)
2. 黑盒集成:然后将预综合的网表像”积木”一样集成到顶层设计中
• 综合速度快(IP不需要重复综合)
• IP保护
• 增量编译(只改IP时效率高)
• 可能不是最优结果
• 调试时层次结构较深
推荐默认使用
尤其适合大型项目、频繁迭代
Global
(全局综合)
IP核与你的顶层设计代码一起进行综合• 整体优化更好
• 调试更方便
• 每次综合都要重新处理IP
• 综合时间长
对时序要求极苛刻
或需要深度优化时
  • 选择 “Out of context per IP”:Vivado会先把你的ROM单独综合好,生成一个”黑盒子”
  • 当你整体项目综合时,直接使用这个预综合的ROM,大大节省时间

“Number of jobs” 的含义
这是一个并行处理设置,类似于”用几个工人同时干活”。

  • 作用:设置Vivado可以同时运行的并行进程数
  • 基础:你的CPU有多个核心,可以同时处理多个任务

3、ROM初始化文件COE#

coe文件里包含ROM初始化信息(这里包含的信息为:数据进制和数据)

可以用Vivado打开编辑,也可以用普通文本编辑工具
格式如下:
第一行:初始化参数向量采用16进制(也可以2进制)。
第二行:初始化向量名
第三行:初始化向量元素,用空格或逗号分隔,最后以分号结束。
注  释:文件头、尾部可以用“#”号加注释,中间不可以。

memory_initialization_radix=16;
memory_initialization_vector=000000b3,00108093,00000133,00210113, 000001b3,00318193,001020a3,00202123, 003021a3,00102203,00202283,00302303;

解释:里面存储的是12条32位的RISC-V机器指令

memory_initialization_radix=16;// 数据是16进制格式
memory_initialization_vector= // 以下是存储的内容
000000b3, // 地址0:第一条指令
00108093, // 地址1:第二条指令
... // 以此类推

指令详解(翻译成汇编语言)
1.地址0: 000000b3

  • 二进制0000000_00000_00000_000_00000_0110011
  • RISC-V指令add x1, x0, x0(x1 = x0 + x0,即 x1 = 0)
  • 操作: 将零寄存器(x0)的值加到另一个零寄存器,结果存入x1

2.地址1: 00108093

  • 二进制000000000001_00000_000_00001_0010011
  • RISC-V指令addi x1, x0, 1(x1 = x0 + 1,即 x1 = 1)
  • 操作: 将立即数1加到零寄存器,结果存入x1

3.地址2: 00000133

  • 二进制0000000_00000_00010_000_00010_0110011
  • RISC-V指令add x2, x0, x2(x2 = x0 + x2)
  • 操作: 将x2的值与零寄存器相加,结果存回x2

4.地址3: 00210113

  • 二进制000000000010_00010_000_00010_0010011
  • RISC-V指令addi x2, x2, 2(x2 = x2 + 2)
  • 操作: 将x2的值加2,结果存回x2

5.地址4: 000001b3

  • 二进制0000000_00000_00011_000_00011_0110011
  • RISC-V指令add x3, x0, x3(x3 = x0 + x3)
  • 操作: 将x3的值与零寄存器相加,结果存回x3

6.地址5: 00318193

  • 二进制000000000011_00011_000_00011_0010011
  • RISC-V指令addi x3, x3, 3(x3 = x3 + 3)
  • 操作: 将x3的值加3,结果存回x3

7.地址6: 001020a3

  • 二进制000000000001_00001_010_00001_0100011
  • RISC-V指令sw x1, 1(x0)(mem[x0 + 1] = x1)
  • 操作: 将x1的值存储到内存地址1

8.地址7: 00202123

  • 二进制000000000010_00010_010_00011_0100011
  • RISC-V指令sw x2, 3(x0)(mem[x0 + 3] = x2)
  • 操作: 将x2的值存储到内存地址3

9..地址8: 003021a3

  • 二进制000000000011_00011_010_00101_0100011
  • RISC-V指令sw x3, 5(x0)(mem[x0 + 5] = x3)
  • 操作: 将x3的值存储到内存地址5

10.地址9: 00102203

  • 二进制000000000001_00000_010_00100_0000011
  • RISC-V指令lw x4, 1(x0)(x4 = mem[x0 + 1])
  • 操作: 从内存地址1加载数据到x4

11.地址10: 00202283

  • 二进制000000000010_00000_010_00101_0000011
  • RISC-V指令lw x5, 2(x0)(x5 = mem[x0 + 2])
  • 操作: 从内存地址2加载数据到x5

12.地址11: 00302303

  • 二进制000000000011_00000_010_00110_0000011
  • RISC-V指令lw x6, 3(x0)(x6 = mem[x0 + 3])
  • 操作: 从内存地址3加载数据到x6

这个程序的功能
这是一个简单的数据搬运程序

  1. 初始化寄存器:x1=1, x2=2, x3=3
  2. 存储操作:将x1,x2,x3的值存入内存地址1,3,5
  3. 加载操作:从内存地址1,2,3加载数据到x4,x5,x6

3.8 ROM IP核的仿真和上板测试#

3.8.1 主模块接口定义#

module SCPU_TOP( // 模块名自己定义
input clk,  //100MHZ CLK
input rstn,  //reset signal
input [15:0] sw_i, //sw_i[15]---sw_i[0]
output reg [7:0] disp_an_o, //8位数码管位选
output reg[7:0] disp_seg_o //数码管8段数据
);
// 主程序
endmodule

3.8.2 主板开关定义#

Pasted image 20251026104616

3.8.3 主模块功能和仿真模块功能定义#

Pasted image 20251026104638
例化ROM,输入rom_addr 获得 instr,并送到显示模块
显示完成之后,进行instr更新,也即rom_addr更新:rom_addr+1更新

最终代码
SCPU_TOP

module SCPU_TOP(
input clk,
input rstn,
input [15:0] sw_i,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
reg [31:0] clkdiv;
wire Clk_CPU;
always @(posedge clk or negedge rstn) begin
if(!rstn) clkdiv <= 0;
else clkdiv <= clkdiv + 1'b1;
end
assign Clk_CPU = (sw_i[15])?clkdiv[27]:clkdiv[25]; // 2^27 和 2^25分频
reg [63:0] display_data;
reg [5:0] led_data_addr;
reg [63:0] led_disp_data;
parameter LED_DATA_NUM = 19;
reg [63:0] LED_DATA[18:0];
initial begin
LED_DATA[0] = 64'hC6F6F6F0C6F6F6F0;
LED_DATA[1] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[2] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[3] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[4] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[5] = 64'hFFFFA3FFFFFFA3FF;
LED_DATA[6] = 64'hFFFF9CFFFFFF9CFF;
LED_DATA[7] = 64'hFF9EBCFFFF9EBCFF;
LED_DATA[8] = 64'hFF9CFFFFFF9CFFFF;
LED_DATA[9] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[10] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[11] = 64'hFFA7B3FFFFA7B3FF;
LED_DATA[12] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[13] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[14] = 64'h9EBEBEBC9EBEBEBC;
LED_DATA[15] = 64'h2737373327373733;
LED_DATA[16] = 64'h505454EC505454EC;
LED_DATA[17] = 64'h744454F8744454F8;
LED_DATA[18] = 64'h0062080000620800;
end
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
led_data_addr <= 6'd0;
led_disp_data <= 64'b1;
end
else if(sw_i[0] == 1'b1) begin
if(led_data_addr == LED_DATA_NUM) begin
led_data_addr <= 6'd0;
led_disp_data <= LED_DATA[0];
end
else begin
led_disp_data <= LED_DATA[led_data_addr];
led_data_addr <= led_data_addr + 1'b1;
end
end
else led_data_addr <= led_data_addr;
end
wire [31:0] instr;
reg [31:0] reg_data;
reg [31:0] alu_disp_data;
reg [31:0] dmem_data;
reg [5:0] rom_addr;
always @(sw_i) begin
if(sw_i[0] == 0) begin
case(sw_i[14:11])
4'b1000 : display_data = instr;
4'b0100 : display_data = reg_data;
4'b0010 : display_data = alu_disp_data;
4'b0001 : display_data = dmem_data;
default : display_data = instr;
endcase
end
else display_data = led_disp_data;
end
dist_mem_im U_IM(
.a(rom_addr),
.spo(instr)
);
seg7x16 u_seg7x16(
.clk(clk),
.rstn(rstn),
.disp_mode(sw_i[0]),
.i_data(display_data),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) rom_addr <= 6'b0;
else rom_addr <= rom_addr + 1'b1;
end
endmodule

seg7x16

module seg7x16(
input clk,
input rstn,
input disp_mode,
input [63:0] i_data,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
// 2^15分频
reg [14:0] cnt;
wire seg7_clk;
always @(posedge clk or negedge rstn) begin
if(!rstn) cnt <= 0;
else cnt <= cnt + 1'b1;
end
assign seg7_clk = cnt[14];
// 8选1
reg [2:0] seg7_addr;
always @(posedge seg7_clk or negedge rstn) begin
if(!rstn) seg7_addr <= 0;
else seg7_addr <= seg7_addr + 1'b1;
end
// 选中的数码管使能信号
reg [7:0] o_sel_r;
always @(*) begin
case(seg7_addr)
7 : o_sel_r = 8'b01111111;
6 : o_sel_r = 8'b10111111;
5 : o_sel_r = 8'b11011111;
4 : o_sel_r = 8'b11101111;
3 : o_sel_r = 8'b11110111;
2 : o_sel_r = 8'b11111011;
1 : o_sel_r = 8'b11111101;
0 : o_sel_r = 8'b11111110;
endcase
end
//
reg [63:0] i_data_store;
always @(posedge clk or negedge rstn) begin
if(!rstn) i_data_store <= 0;
else i_data_store <= i_data;
end
reg [7:0] seg_data_r;
always @(*) begin
if(disp_mode == 1'b0) begin // 字符显示模式
case(seg7_addr)
0 : seg_data_r = i_data_store[3:0];
1 : seg_data_r = i_data_store[7:4];
2 : seg_data_r = i_data_store[11:8];
3 : seg_data_r = i_data_store[15:12];
4 : seg_data_r = i_data_store[19:16];
5 : seg_data_r = i_data_store[23:20];
6 : seg_data_r = i_data_store[27:24];
7 : seg_data_r = i_data_store[31:28];
endcase
end
else begin // 图形显示模式
case(seg7_addr)
0 : seg_data_r = i_data_store[7:0];
1 : seg_data_r = i_data_store[15:8];
2 : seg_data_r = i_data_store[23:16];
3 : seg_data_r = i_data_store[31:24];
4 : seg_data_r = i_data_store[39:32];
5 : seg_data_r = i_data_store[47:40];
6 : seg_data_r = i_data_store[55:48];
7 : seg_data_r = i_data_store[63:56];
endcase
end
end
reg [7:0] o_seg_r;
always @(posedge clk or negedge rstn) begin
if(!rstn) o_seg_r <= 8'hFF;
else if(disp_mode == 1'b0) begin // 字符模式
case(seg_data_r)
4'h0 : o_seg_r <= 8'hC0;
4'h1 : o_seg_r <= 8'hF9;
4'h2 : o_seg_r <= 8'hA4;
4'h3 : o_seg_r <= 8'hB0;
4'h4 : o_seg_r <= 8'h99;
4'h5 : o_seg_r <= 8'h92;
4'h6 : o_seg_r <= 8'h82;
4'h7 : o_seg_r <= 8'hF8;
4'h8 : o_seg_r <= 8'h80;
4'h9 : o_seg_r <= 8'h90;
4'hA : o_seg_r <= 8'h88;
4'hB : o_seg_r <= 8'h83;
4'hC : o_seg_r <= 8'hC6;
4'hD : o_seg_r <= 8'hA1;
4'hE : o_seg_r <= 8'h86;
4'hF : o_seg_r <= 8'h8E;
default : o_seg_r <= 8'hFF;
endcase
end
else o_seg_r <= seg_data_r;
end
assign disp_an_o = o_sel_r;
assign disp_seg_o = o_seg_r;
endmodule

4.RegisterFile(RF)模块设计#

4.1 Analysis#

Pasted image 20251109100856

module RF(
input clk, //100MHZ CLK
input rst, //reset signal
input RFWr, //Rfwrite = mem2reg
input [15:0] sw_i, //sw_i[15]---sw_i[0]
input [4:0] A1, A2, A3, // Register Num
input [31:0] WD, //Write data
output [31:0] RD1, RD2 //Data output port
);
endmodule

Pasted image 20251109101054
说明:
SW[14]=1 :循环显示RF中的32个寄存器的内容,结尾显示FFFFFFFF
SW[1]= 1 调试模式,RF中的32个寄存器内容不能修改
SW[1]= 0 正常模式,RF中的32个寄存器内容可以被程序修改。

Pasted image 20251109101207

Pasted image 20251109104936

下面代码实现通过SW_i输入写入寄存器的相应参数,循环显示32个寄存器,一般RF模块例化为

// 例化RF模块
RF U_RF(.clk(clk),.rstn(rstn),.RFWr(RegWrite),.sw_i(sw_i),.A1(rs1),.A2(rs2),.A3(rd),.WD(WD),.RD1(RD1),.RD2(RD2));

4.2 Code#

SCPU_TOP

module SCPU_TOP(
input clk,
input rstn,
input [15:0] sw_i,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
reg [31:0] clkdiv;
wire Clk_CPU;
always @(posedge clk or negedge rstn) begin
if(!rstn) clkdiv <= 0;
else clkdiv <= clkdiv + 1'b1;
end
assign Clk_CPU = (sw_i[15])?clkdiv[27]:clkdiv[25]; // 2^27 和 2^25分频
reg [63:0] display_data;
reg [5:0] led_data_addr;
reg [63:0] led_disp_data;
parameter LED_DATA_NUM = 19;
reg [63:0] LED_DATA[18:0];
initial begin
LED_DATA[0] = 64'hC6F6F6F0C6F6F6F0;
LED_DATA[1] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[2] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[3] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[4] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[5] = 64'hFFFFA3FFFFFFA3FF;
LED_DATA[6] = 64'hFFFF9CFFFFFF9CFF;
LED_DATA[7] = 64'hFF9EBCFFFF9EBCFF;
LED_DATA[8] = 64'hFF9CFFFFFF9CFFFF;
LED_DATA[9] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[10] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[11] = 64'hFFA7B3FFFFA7B3FF;
LED_DATA[12] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[13] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[14] = 64'h9EBEBEBC9EBEBEBC;
LED_DATA[15] = 64'h2737373327373733;
LED_DATA[16] = 64'h505454EC505454EC;
LED_DATA[17] = 64'h744454F8744454F8;
LED_DATA[18] = 64'h0062080000620800;
end
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
led_data_addr <= 6'd0;
led_disp_data <= 64'b1;
end
else if(sw_i[0] == 1'b1) begin
if(led_data_addr == LED_DATA_NUM) begin
led_data_addr <= 6'd0;
led_disp_data <= LED_DATA[0];
end
else begin
led_disp_data <= LED_DATA[led_data_addr];
led_data_addr <= led_data_addr + 1'b1;
end
end
else led_data_addr <= led_data_addr;
end
wire [31:0] instr;
reg [31:0] reg_data;
reg [31:0] alu_disp_data;
reg [31:0] dmem_data;
reg [5:0] rom_addr;
reg [5:0] reg_addr;
always @(sw_i) begin
if(sw_i[0] == 0) begin
case(sw_i[14:11])
4'b1000 : display_data = instr;
4'b0100 : display_data = reg_data;
4'b0010 : display_data = alu_disp_data;
4'b0001 : display_data = dmem_data;
default : display_data = instr;
endcase
end
else display_data = led_disp_data;
end
// 例化ROM模块
dist_mem_im U_IM(
.a(rom_addr),
.spo(instr)
);
// 例化显示模块
seg7x16 u_seg7x16(
.clk(clk),
.rstn(rstn),
.disp_mode(sw_i[0]),
.i_data(display_data),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
// 每个CLK_CPU获得一个新指令
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) rom_addr <= 6'b0;
else rom_addr <= rom_addr + 1'b1;
end
wire [4:0] rs1,rs2;
wire [31:0] RD1,RD2;
// 例化RF模块
RF U_RF(.clk(clk),.rstn(rstn),.RFWr(sw_i[3]),.sw_i(sw_i),.A1(rs1),.A2(rs2),.A3(sw_i[11:8]),.WD(sw_i[7:4]),.RD1(RD1),.RD2(RD2));
// 循环显示32个寄存器的内容
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
reg_addr = 5'b0;
reg_data = 32'b0;
end
else if(sw_i[13] == 1'b1)begin
reg_addr <= reg_addr + 1'b1;
reg_data <= U_RF.rf[reg_addr];
end
end
endmodule

RF

module RF(
input clk,
input rstn,
input RFWr,
input [15:0] sw_i,
input [4:0] A1,A2,A3,
input [31:0] WD,
output [31:0] RD1,RD2
);
reg [31:0] rf[31:0];
integer i;
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
for(i=0;i<32;i=i+1) rf[i] <= i;
end
else if(RFWr && (!sw_i[1])) rf[A3] <= WD; // 正常模式下且RegWrite有效
end
assign RD1 = (A1 != 0)?rf[A1]:0;
assign RD2 = (A2 != 0)?rf[A2]:0;
endmodule

seg7x16.v(没有改动)

module seg7x16(
input clk,
input rstn,
input disp_mode,
input [63:0] i_data,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
// 2^15分频
reg [14:0] cnt;
wire seg7_clk;
always @(posedge clk or negedge rstn) begin
if(!rstn) cnt <= 0;
else cnt <= cnt + 1'b1;
end
assign seg7_clk = cnt[14];
// 8选1
reg [2:0] seg7_addr;
always @(posedge seg7_clk or negedge rstn) begin
if(!rstn) seg7_addr <= 0;
else seg7_addr <= seg7_addr + 1'b1;
end
// 选中的数码管使能信号
reg [7:0] o_sel_r;
always @(*) begin
case(seg7_addr)
7 : o_sel_r = 8'b01111111;
6 : o_sel_r = 8'b10111111;
5 : o_sel_r = 8'b11011111;
4 : o_sel_r = 8'b11101111;
3 : o_sel_r = 8'b11110111;
2 : o_sel_r = 8'b11111011;
1 : o_sel_r = 8'b11111101;
0 : o_sel_r = 8'b11111110;
endcase
end
//
reg [63:0] i_data_store;
always @(posedge clk or negedge rstn) begin
if(!rstn) i_data_store <= 0;
else i_data_store <= i_data;
end
reg [7:0] seg_data_r;
always @(*) begin
if(disp_mode == 1'b0) begin // 字符显示模式
case(seg7_addr)
0 : seg_data_r = i_data_store[3:0];
1 : seg_data_r = i_data_store[7:4];
2 : seg_data_r = i_data_store[11:8];
3 : seg_data_r = i_data_store[15:12];
4 : seg_data_r = i_data_store[19:16];
5 : seg_data_r = i_data_store[23:20];
6 : seg_data_r = i_data_store[27:24];
7 : seg_data_r = i_data_store[31:28];
endcase
end
else begin // 图形显示模式
case(seg7_addr)
0 : seg_data_r = i_data_store[7:0];
1 : seg_data_r = i_data_store[15:8];
2 : seg_data_r = i_data_store[23:16];
3 : seg_data_r = i_data_store[31:24];
4 : seg_data_r = i_data_store[39:32];
5 : seg_data_r = i_data_store[47:40];
6 : seg_data_r = i_data_store[55:48];
7 : seg_data_r = i_data_store[63:56];
endcase
end
end
reg [7:0] o_seg_r;
always @(posedge clk or negedge rstn) begin
if(!rstn) o_seg_r <= 8'hFF;
else if(disp_mode == 1'b0) begin // 字符模式
case(seg_data_r)
4'h0 : o_seg_r <= 8'hC0;
4'h1 : o_seg_r <= 8'hF9;
4'h2 : o_seg_r <= 8'hA4;
4'h3 : o_seg_r <= 8'hB0;
4'h4 : o_seg_r <= 8'h99;
4'h5 : o_seg_r <= 8'h92;
4'h6 : o_seg_r <= 8'h82;
4'h7 : o_seg_r <= 8'hF8;
4'h8 : o_seg_r <= 8'h80;
4'h9 : o_seg_r <= 8'h90;
4'hA : o_seg_r <= 8'h88;
4'hB : o_seg_r <= 8'h83;
4'hC : o_seg_r <= 8'hC6;
4'hD : o_seg_r <= 8'hA1;
4'hE : o_seg_r <= 8'h86;
4'hF : o_seg_r <= 8'h8E;
default : o_seg_r <= 8'hFF;
endcase
end
else o_seg_r <= seg_data_r;
end
assign disp_an_o = o_sel_r;
assign disp_seg_o = o_seg_r;
endmodule

Pasted image 20251109112230

5.ALU模块设计#

Pasted image 20251109110330
运算指令码长度[4:0]
1.一个32位数输入到A口,或由sw10-sw7输入4位数到A口;
2.一个32位数输入到B口,或由SW6-SW3输入4位数到B口
3.运算器暂只支持”加、减 “2种操作,输入控制信号ALUOp([4:0]);
4.结果输出到C[31:0];
5.由sw[12]控制数码管显示A,B,C,Zero四个数。

module alu(
input signed [31:0] A, B, //alu input num
input [4:0] ALUOp, //alu how to do
output signed [31:0] C, // alu result
output reg [7:0] Zero
);
endmodule

Pasted image 20251109112520
说明:
SW[12]=1 :循环显示ALU中的A,B,C,Zero的内容。
SW[2]= 1  :C=A+B
SW[2]= 0  :C=A-B

Pasted image 20251109121313

5.1 简单实现加法和减法的ALU#

在前面代码基础上的修改,且数据为符号数,含数据扩展

SCPU_TOP

// 简单ALU,无RF关联
module SCPU_TOP(
input clk,
input rstn,
input [15:0] sw_i,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
reg [31:0] clkdiv;
wire Clk_CPU;
always @(posedge clk or negedge rstn) begin
if(!rstn) clkdiv <= 0;
else clkdiv <= clkdiv + 1'b1;
end
assign Clk_CPU = (sw_i[15])?clkdiv[27]:clkdiv[25]; // 2^27 和 2^25分频
reg [63:0] display_data;
reg [5:0] led_data_addr;
reg [63:0] led_disp_data;
parameter LED_DATA_NUM = 19;
reg [63:0] LED_DATA[18:0];
initial begin
LED_DATA[0] = 64'hC6F6F6F0C6F6F6F0;
LED_DATA[1] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[2] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[3] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[4] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[5] = 64'hFFFFA3FFFFFFA3FF;
LED_DATA[6] = 64'hFFFF9CFFFFFF9CFF;
LED_DATA[7] = 64'hFF9EBCFFFF9EBCFF;
LED_DATA[8] = 64'hFF9CFFFFFF9CFFFF;
LED_DATA[9] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[10] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[11] = 64'hFFA7B3FFFFA7B3FF;
LED_DATA[12] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[13] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[14] = 64'h9EBEBEBC9EBEBEBC;
LED_DATA[15] = 64'h2737373327373733;
LED_DATA[16] = 64'h505454EC505454EC;
LED_DATA[17] = 64'h744454F8744454F8;
LED_DATA[18] = 64'h0062080000620800;
end
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
led_data_addr <= 6'd0;
led_disp_data <= 64'b1;
end
else if(sw_i[0] == 1'b1) begin
if(led_data_addr == LED_DATA_NUM) begin
led_data_addr <= 6'd0;
led_disp_data <= LED_DATA[0];
end
else begin
led_disp_data <= LED_DATA[led_data_addr];
led_data_addr <= led_data_addr + 1'b1;
end
end
else led_data_addr <= led_data_addr;
end
wire [31:0] instr;
reg [31:0] reg_data;
reg [31:0] alu_disp_data;
reg [31:0] dmem_data;
always @(sw_i) begin
if(sw_i[0] == 0) begin
case(sw_i[14:11])
4'b1000 : display_data = instr;
4'b0100 : display_data = reg_data;
4'b0010 : display_data = alu_disp_data;
4'b0001 : display_data = dmem_data;
default : display_data = instr;
endcase
end
else display_data = led_disp_data;
end
reg [5:0] rom_addr;
// 例化ROM模块
dist_mem_im U_IM(
.a(rom_addr),
.spo(instr)
);
// 例化显示模块
seg7x16 u_seg7x16(
.clk(clk),
.rstn(rstn),
.disp_mode(sw_i[0]),
.i_data(display_data),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
// 每个CLK_CPU获得一个新指令
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) rom_addr <= 6'b0;
else rom_addr <= rom_addr + 1'b1;
end
wire [4:0] rs1,rs2;
wire [31:0] RD1,RD2;
reg [5:0] reg_addr;
// 例化RF模块
RF U_RF(.clk(clk),.rstn(rstn),.RFWr(sw_i[3]),.sw_i(sw_i),.A1(rs1),.A2(rs2),.A3(sw_i[11:8]),.WD(sw_i[7:4]),.RD1(RD1),.RD2(RD2));
// 循环显示32个寄存器的内容
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
reg_addr = 5'b0;
reg_data = 32'b0;
end
else if(sw_i[13] == 1'b1)begin
reg_addr <= reg_addr + 1'b1;
reg_data <= U_RF.rf[reg_addr];
end
end
wire A,B;
//wire ALUOp;
wire signed [31:0] aluout;
wire [7:0] Zero;
reg [2:0] alu_addr;
// 符号数输入,符号扩展
assign A = {{28{sw_i[10]}},sw_i[10:7]};
assign B = {{28{sw_i[6]}},sw_i[6:3]};
// 例化alu
alu U_alu(.A(A),.B(B),.ALUOp(sw_i[2]),.C(aluout),.Zero(Zero));
// 循环显示A B Zero四个值
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) alu_addr <= 3'b0;
else alu_addr <= alu_addr + 1'b1;
case(alu_addr)
3'b001:alu_disp_data = U_alu.A;
3'b010:alu_disp_data = U_alu.B;
3'b011:alu_disp_data = U_alu.C;
3'b100:alu_disp_data = U_alu.Zero;
default:alu_disp_data = 32'hFFFFFFFF;
endcase
end
endmodule

alu

`define ALUOp_add 5'b00001
`define ALUOp_sub 5'b00000
module alu(
input signed [31:0] A,B,
input [4:0] ALUOp,
output reg signed [31:0] C,
output reg [7:0] Zero
);
always @(*) begin
case(ALUOp)
`ALUOp_add:C = A + B;
`ALUOp_sub:C = A - B;
default:C = 32'b0;
endcase
Zero = (C == 0)?1:0; // 阻塞赋值,需等C算出来,而不是同步
end
endmodule

seg7x16.v和RF.v没有更改

5.2 实现RF和ALU的关联#

1)实现RF部件和ALU部件的关联,使得指定寄存器的数值送入ALU部件参与运算,具体参见下图:
Pasted image 20251109122920
2)通过SW_i输入双端口要读出的寄存器编号A1,A2,并将读出的数值RD1,RD2送入ALU的A,B输入端,根据设定的ALUOp,计算结果,并显示。

3)通过SW_i输入要写入寄存器编号和数值A3,WD,再通过步骤2),将计算结果显示出来。
Pasted image 20251109121346
a) sw[12]=1,循环显示A,B,C,Zero四个数值。
b) sw[10]—sw[8] -----> A1,A3 ,   用来取代rs1,rd   (缩减为3位)
c) sw[7]—sw[5]  -----> A2,WD ,用来取代rs2,wd。(缩减为3位)
d) sw[4]—sw[3]  -----> ALUOp。
e) sw[2]=0 , 写寄存器使能信号无效,不能修改寄存器,只能读寄存器 ,    
sw[10:8] 代表A1寄存器号,
sw[7:5]  代表A2寄存器号。
f) sw[2]=1 , 写寄存器使能信号有效,可以修改寄存器 ,       
sw[10:8] 代表A3写入寄存器号,
sw[7:5]  代表写入寄存器数值。
sw[1]=0, 非调试模式,可以修改对应寄存器数值
sw[1] =1, 调试模式,不能修改对应寄存器数值

注意:
1)通过sw开关输入的A,B,WD运算数,它们是有符号数,位宽不到32位,有的输入的是3位或4位,那么它们的符号位如何处理?(选最高位作为符号位,符号扩展)

{{29{sw_i[10]}}, sw_i[10:8]} //符号扩展原理
// 高位重复29次符号位(sw_i[10]),然后拼接3位数据

2) RF寄存器组件的输入端口A1,A2,A3 的开关输入,需要扩展吗?(3位输入要扩展为5位地址)

修改代码如下:
SCPU_TOP

// ALU与RF关联
module SCPU_TOP(
input clk,
input rstn,
input [15:0] sw_i,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
reg [31:0] clkdiv;
wire Clk_CPU;
always @(posedge clk or negedge rstn) begin
if(!rstn) clkdiv <= 0;
else clkdiv <= clkdiv + 1'b1;
end
assign Clk_CPU = (sw_i[15])?clkdiv[27]:clkdiv[25]; // 2^27 和 2^25分频
reg [63:0] display_data;
reg [5:0] led_data_addr;
reg [63:0] led_disp_data;
parameter LED_DATA_NUM = 19;
reg [63:0] LED_DATA[18:0];
initial begin
LED_DATA[0] = 64'hC6F6F6F0C6F6F6F0;
LED_DATA[1] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[2] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[3] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[4] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[5] = 64'hFFFFA3FFFFFFA3FF;
LED_DATA[6] = 64'hFFFF9CFFFFFF9CFF;
LED_DATA[7] = 64'hFF9EBCFFFF9EBCFF;
LED_DATA[8] = 64'hFF9CFFFFFF9CFFFF;
LED_DATA[9] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[10] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[11] = 64'hFFA7B3FFFFA7B3FF;
LED_DATA[12] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[13] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[14] = 64'h9EBEBEBC9EBEBEBC;
LED_DATA[15] = 64'h2737373327373733;
LED_DATA[16] = 64'h505454EC505454EC;
LED_DATA[17] = 64'h744454F8744454F8;
LED_DATA[18] = 64'h0062080000620800;
end
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
led_data_addr <= 6'd0;
led_disp_data <= 64'b1;
end
else if(sw_i[0] == 1'b1) begin
if(led_data_addr == LED_DATA_NUM) begin
led_data_addr <= 6'd0;
led_disp_data <= LED_DATA[0];
end
else begin
led_disp_data <= LED_DATA[led_data_addr];
led_data_addr <= led_data_addr + 1'b1;
end
end
else led_data_addr <= led_data_addr;
end
wire [31:0] instr;
reg [31:0] reg_data;
reg [31:0] alu_disp_data;
reg [31:0] dmem_data;
// ROM
reg [5:0] rom_addr;
// RF
wire RegWrite;
wire [4:0] rs1,rs2,rd;
wire [31:0] WD;
wire [31:0] RD1,RD2;
reg [5:0] reg_addr;
// ALU
wire [31:0] A,B;
wire [1:0] ALUOp;
wire signed [31:0] aluout;
wire [7:0] Zero;
reg [2:0] alu_addr;
// 赋值
assign RegWrite = sw_i[2];
assign rs1 = {2'b00, sw_i[10:8]};
assign rs2 = {2'b00, sw_i[7:5]};
assign rd = {2'b00, sw_i[10:8]};
assign WD = (sw_i[2] == 1'b1)?{{29{sw_i[7]}},sw_i[7:5]}:aluout;
assign A = RD1;
assign B = RD2;
assign ALUOp = sw_i[4:3];
always @(sw_i) begin
if(sw_i[0] == 0) begin
case(sw_i[14:11])
4'b1000 : display_data = instr;
4'b0100 : display_data = reg_data;
4'b0010 : display_data = alu_disp_data;
4'b0001 : display_data = dmem_data;
default : display_data = instr;
endcase
end
else display_data = led_disp_data;
end
// 例化ROM模块
dist_mem_im U_IM(
.a(rom_addr),
.spo(instr)
);
// 例化显示模块
seg7x16 u_seg7x16(
.clk(clk),
.rstn(rstn),
.disp_mode(sw_i[0]),
.i_data(display_data),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
// 每个CLK_CPU获得一个新指令
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) rom_addr <= 6'b0;
else rom_addr <= rom_addr + 1'b1;
end
// 例化RF模块
RF U_RF(.clk(clk),.rstn(rstn),.RFWr(RegWrite),.sw_i(sw_i),.A1(rs1),.A2(rs2),.A3(rd),.WD(WD),.RD1(RD1),.RD2(RD2));
// 循环显示32个寄存器的内容
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
reg_addr = 5'b0;
reg_data = 32'b0;
end
else if(sw_i[13] == 1'b1)begin
reg_addr <= reg_addr + 1'b1;
reg_data <= U_RF.rf[reg_addr];
end
end
// 例化alu
alu U_alu(.A(A),.B(B),.ALUOp(ALUOp),.C(aluout),.Zero(Zero));
// 循环显示A B Zero四个值
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) alu_addr <= 3'b0;
else alu_addr <= alu_addr + 1'b1;
case(alu_addr)
3'b001:alu_disp_data = U_alu.A;
3'b010:alu_disp_data = U_alu.B;
3'b011:alu_disp_data = U_alu.C;
3'b100:alu_disp_data = U_alu.Zero;
default:alu_disp_data = 32'hFFFFFFFF;
endcase
end
endmodule

alu

`define ALUOp_add 2'b00
`define ALUOp_sub 2'b01
`define ALUOp_and 2'b10
`define ALUOp_or 2'b11
module alu(
input signed [31:0] A,B,
input [4:0] ALUOp,
output reg signed [31:0] C,
output reg [7:0] Zero
);
always @(*) begin
case(ALUOp)
`ALUOp_add:C = A + B;
`ALUOp_sub:C = A - B;
`ALUOp_and:C = A & B;
`ALUOp_or:C = A | B;
default:C = 32'b0;
endcase
Zero = (C == 0)?1:0; // 阻塞赋值,需等C算出来,而不是同步
end
endmodule

6.DM(DATA MEM)模块设计#

6.1 Analysis#

module dm(
input clk, //100MHZ CLK
input DMWr, //write signal
input [5:0] addr,
input [31:0] din,
input [2:0] DMType,
output reg [31:0] dout
);
endmodule

Pasted image 20251109165230

Pasted image 20251109165238

宏定义

// 宏定义如下:
`define dm_word 3'b000
`define dm_halfword 3'b001
`define dm_halfword_unsigned 3'b010
`define dm_byte 3'b011
`define dm_byte_unsigned 3'b100

6.2 Code#

SCPU_TOP

// 添加Data Memory
module SCPU_TOP(
input clk,
input rstn,
input [15:0] sw_i,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
reg [31:0] clkdiv;
wire Clk_CPU;
always @(posedge clk or negedge rstn) begin
if(!rstn) clkdiv <= 0;
else clkdiv <= clkdiv + 1'b1;
end
assign Clk_CPU = (sw_i[15])?clkdiv[27]:clkdiv[25]; // 2^27 和 2^25分频
reg [63:0] display_data;
reg [5:0] led_data_addr;
reg [63:0] led_disp_data;
parameter LED_DATA_NUM = 19;
reg [63:0] LED_DATA[18:0];
initial begin
LED_DATA[0] = 64'hC6F6F6F0C6F6F6F0;
LED_DATA[1] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[2] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[3] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[4] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[5] = 64'hFFFFA3FFFFFFA3FF;
LED_DATA[6] = 64'hFFFF9CFFFFFF9CFF;
LED_DATA[7] = 64'hFF9EBCFFFF9EBCFF;
LED_DATA[8] = 64'hFF9CFFFFFF9CFFFF;
LED_DATA[9] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[10] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[11] = 64'hFFA7B3FFFFA7B3FF;
LED_DATA[12] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[13] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[14] = 64'h9EBEBEBC9EBEBEBC;
LED_DATA[15] = 64'h2737373327373733;
LED_DATA[16] = 64'h505454EC505454EC;
LED_DATA[17] = 64'h744454F8744454F8;
LED_DATA[18] = 64'h0062080000620800;
end
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
led_data_addr <= 6'd0;
led_disp_data <= 64'b1;
end
else if(sw_i[0] == 1'b1) begin
if(led_data_addr == LED_DATA_NUM) begin
led_data_addr <= 6'd0;
led_disp_data <= LED_DATA[0];
end
else begin
led_disp_data <= LED_DATA[led_data_addr];
led_data_addr <= led_data_addr + 1'b1;
end
end
else led_data_addr <= led_data_addr;
end
wire [31:0] instr;
reg [31:0] reg_data;
reg [31:0] alu_disp_data;
reg [31:0] dmem_data;
// ROM
reg [5:0] rom_addr;
// RF
wire RegWrite;
wire [4:0] rs1,rs2,rd;
wire [31:0] WD;
wire [31:0] RD1,RD2;
reg [5:0] reg_addr;
// ALU
wire [31:0] A,B;
wire [1:0] ALUOp;
wire signed [31:0] aluout;
wire [7:0] Zero;
reg [2:0] alu_addr;
// 赋值
assign RegWrite = sw_i[2];
assign rs1 = {2'b00, sw_i[10:8]};
assign rs2 = {2'b00, sw_i[7:5]};
assign rd = {2'b00, sw_i[10:8]};
assign WD = (sw_i[2] == 1'b1)?{{29{sw_i[7]}},sw_i[7:5]}:aluout; // 符号扩展
assign A = RD1;
assign B = RD2;
assign ALUOp = sw_i[4:3];
always @(sw_i) begin
if(sw_i[0] == 0) begin
case(sw_i[14:11])
4'b1000 : display_data = instr;
4'b0100 : display_data = reg_data;
4'b0010 : display_data = alu_disp_data;
4'b0001 : display_data = dmem_data;
default : display_data = instr;
endcase
end
else display_data = led_disp_data;
end
// 例化ROM模块
dist_mem_im U_IM(
.a(rom_addr),
.spo(instr)
);
// 例化显示模块
seg7x16 u_seg7x16(
.clk(clk),
.rstn(rstn),
.disp_mode(sw_i[0]),
.i_data(display_data),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
// 每个CLK_CPU获得一个新指令
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) rom_addr <= 6'b0;
else rom_addr <= rom_addr + 1'b1;
end
// 例化RF模块
RF U_RF(.clk(clk),.rstn(rstn),.RFWr(RegWrite),.sw_i(sw_i),.A1(rs1),.A2(rs2),.A3(rd),.WD(WD),.RD1(RD1),.RD2(RD2));
// 循环显示32个寄存器的内容
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
reg_addr = 5'b0;
reg_data = 32'b0;
end
else if(sw_i[13] == 1'b1)begin
reg_addr <= reg_addr + 1'b1;
reg_data <= U_RF.rf[reg_addr];
end
end
// 例化alu
alu U_alu(.A(A),.B(B),.ALUOp(ALUOp),.C(aluout),.Zero(Zero));
// 循环显示A B Zero四个值
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) alu_addr <= 3'b0;
else alu_addr <= alu_addr + 1'b1;
case(alu_addr) // 下面用普通reg接收signed数据,只会数据位复制
3'b001:alu_disp_data = U_alu.A;
3'b010:alu_disp_data = U_alu.B;
3'b011:alu_disp_data = U_alu.C;
3'b100:alu_disp_data = U_alu.Zero;
default:alu_disp_data = 32'hFFFFFFFF;
endcase
end
wire MemWrite;
wire [5:0] dm_addr;
wire [31:0] dm_din;
wire [2:0] DMType;
wire [31:0] dm_dout;
reg [6:0] dmem_addr;
assign dm_addr = {2'b0,sw_i[10:8]};
assign dm_din = {{29{sw_i[7]}},sw_i[7:5]};
assign DMType = sw_i[4:3];
assign MemWrite = sw_i[2];
dm U_DM(.clk(Clk_CPU),.DMWr(MemWrite),.addr(dm_addr),.din(dm_din),.DMType(DMType),.dout(dm_dout));
parameter DM_DATA_NUM = 16;
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
dmem_addr = 7'b0;
dmem_data = 32'hFFFFFFFF;
end
else if(sw_i[11] == 1'b1) begin
dmem_addr = dmem_addr + 1'b1;
dmem_data = U_DM.dmem[dmem_addr][7:0];
if(dmem_addr == DM_DATA_NUM) begin
dmem_addr = 7'd0;
dmem_data = 32'hFFFFFFFF;
end
end
end
endmodule

dm

`define dm_word 3'b000
`define dm_halfword 3'b001
`define dm_halfword_unsigned 3'b010
`define dm_byte 3'b011
`define dm_byte_unsigned 3'b100
module dm(
input clk,
input DMWr,
input [5:0] addr,
input [31:0] din,
input [2:0] DMType,
output reg [31:0] dout
);
reg [7:0] dmem[127:0]; // 128个单元,每个单元8位
integer i;
initial begin
for(i=0;i<127;i=i+1) dmem[i] = 8'h00;
end
// 写操作
always @(posedge clk) begin
if(DMWr) begin
case(DMType)
`dm_byte:dmem[addr] <= din[7:0];
`dm_halfword:begin
dmem[addr] <= din[7:0];
dmem[addr+1] <= din[15:8];
end
`dm_word:begin
dmem[addr] <= din[7:0];
dmem[addr+1] <= din[15:8];
dmem[addr+2] <= din[23:16];
dmem[addr+3] <= din[31:24];
end
endcase
end
end
// 读操作
always @(*) begin
case(DMType)
`dm_byte:dout = {{24{dmem[addr][7]}},dmem[addr][7:0]};
`dm_halfword:dout = {{16{dmem[addr][7]}},dmem[addr][15:0]};
`dm_word:dout = dmem[addr][31:0];
default:dout = dmem[addr][31:0];
endcase
end
endmodule

7.模块拼接#

Pasted image 20251116175040

模拟串行运行一段代码#

add x1,x0,x0X1=x0+x0; x1=0rs1=X0 ; rs2=X0;

rd=X1; WD=?; RegWrite=1;

A= RD1;AluSrc=上通路; B= RD2 ;

Aluop=ADD;aluout=A+B=0

MemWrite=0;Memread=X;MemtoReg=下通路;WD=aluout=0; WD=0 X1=0
addi x1,x1,1X1=X1+1; X1=1rs1=X1; rs2=XXX;

rd=X1; WD= ?; RegWrite=1;

A= RD1;AluSrc=下通路; B= IMM ;
Aluop=ADD;c=A+imm=0+1=1

MemWrite=0;Memread=X;
MemtoReg=下通路;WD=aluout=1; WD=1 X1= 1
sw x1,1(x0)X1=>MEM(X0+1)rs1=X0; rs2=X1;

rd= ; WD= XXX ; RegWrite=0;

A= RD1=0;AluSrc=下通路; B= IMM=1 ;
Aluop=ADD;c=A+imm=1;

MemWrite=1;Memread=X; WriteData=RD2=X1
MemtoReg=XXX; MEM(1)=1 ?
lw x4,1(x0)X4=MEM(x0+1)rs1=X0; rs2=;

rd= X4 ; WD= ?; RegWrite=1

A= RD1=0;AluSrc=下通路; B= IMM=1 ;
Aluop=ADD;c=A+imm=1;

MemWrite=0;Memread=XXX; WriteData=XXX
MemtoReg=上通道; WD= MEM(1)=1
add x2,x0,x0x2=x0+x0; x2=0rs1=x0; rs2=x0

rd=x2; WD=; RegWriet=1

A=RD1=0; AluSrc=上通路; B=RD2=0
Aluop=ADD; C=A+B=0;

MemWrite=0;Memread=X;MemtoReg=下通路;WD=aluout=0; WD=0 x2=0
addi x2,x2,2x2=x2+2; x2=2rs1=x2; rs2=

rd=x2; WD=; RegWriet=1

A=RD1=2; AluSrc=下通路; B=Imm=2
Aluop=ADD; C=A+imm=2;

MemWrite=0;Memread=X;MemtoReg=下通路;WD=aluout=2; WD=2 x2=2
sw x2,2(x0)MEM(x0+2)=x2rs1=x0; rs2=x2;

rd= ; WD= ; RegWrite=0;

A= RD1=0;AluSrc=下通路; B= IMM=2 ;
Aluop=ADD;c=A+imm=2;

MemWrite=1;Memread=X; WriteData=RD2=X2
MemtoReg=XXX; RF_WD = MEM(2)=2 ?
lw x4,1(x0)x4=MEM(x0+1)rs1=x0; rs2=;

rd=x4 ; WD= ?; RegWrite=1

A=RD1=0;AluSrc=下通路; B=IMM=1 ;
Aluop=ADD;c=A+imm=1;

MemWrite=0;Memread=XXX; WriteData=XXX
MemtoReg=上通道; WD = MEM(1)=1
add x3,x0,x0x3=x0+x0; x3=0rs1 = x0, rs2 = x0
rd = x3, WD = , RegWrite = 1
A = RD1 = 0, AluSrc = 上通路, B = RD2 = 0
Aluop = ADD, C = A + B
MemWrite = 0, Memread = X, MemtoReg = 下通路, WD = aluout = 0, x3 = 0
addi x3,x3,3x3=x3+3; x3=3rs1 = x3, rs2 =
rd = x3, WD = , RegWrite = 1
A = RD1 = x3, AluSrc = 下通路, B = 3
Aluop = ADD, C = A + imm
MemWrite = 0, Memread = X, MemtoReg = 下通路, WD = aluout = 3, x3 = 3
sw x3,3(x0)MEM(x0+3)=x3rs1 = x0; rs2 = x3;

rd = ; WD = ; RegWrite = 0;

A = RD1 = 0;AluSrc = 下通路; B= IMM = 3 ;
Aluop = ADD;c = A + imm = 3;

MemWrite = 1;Memread=X; WriteData = RD2 = X3
MemtoReg=XXX; MEM(2) = 3
lw x6,3(x0)x6=MEM(x0+3)rs1 = x0; rs2 = ;

rd = x6 ; WD = ; RegWrite = 1

A = RD1 = 0;AluSrc = 下通路; B = IMM = 3 ;
Aluop = ADD;c = A + imm = 3;

MemWrite = 0;Memread = XXX; WriteData = XXX
MemtoReg = 上通道; WD = MEM(3)

指令集ISA的分类#

3.1 Mips 指令集#

Pasted image 20251116193243

Pasted image 20251116193250

3.2 ARM 指令集#

Pasted image 20251116193256

Pasted image 20251116193306

3.3 RISC-V 指令集#

1、模块化的指令集满足不同的应用#
基本指令集名称指令数   
RV32I47整数指令,32位地址空间,32个通用寄存器
RV32E47整数指令,RV32E的子集,支持16个通用寄存器
RV64I59整数指令,64位地址空间(含少数32位整数指令)
RV128I71整数指令,128位地址空间
扩展指令集名称指令数   
M8整数乘除指令
A11原子内存操作指令(AMO用于处理器间同步的读-修改-写操作)
F26单精度浮点运算指令
D26双精度浮点运算指令
C46压缩指令(16位)
2、指令格式分为六类#

Pasted image 20251116193315
备注:jal指令中立即数(或label)为20位, jalr指令中立即数为12位。

3.4 RISC-V 指令集举例:#

Pasted image 20251116193321

Pasted image 20251116193327

4.4 多路选择器
4.4.1 alu多路选择器

wire[31:0] B ;
assign B = (ALUSrc) ? immout : RD2;

4.4.2 RF寄存器wd多路选择器

always @(*) begin
case(WDSel)
`WDSel_FromALU: WD<=aluout;
`WDSel_FromMEM: WD<=dout;
//`WDSel_FromPC: WD<=PC_out+4;
endcase
end

当前缺失信号
RF: RegWrite
ALU: ALUOp
DM: MemWrite DMType
其余:MemtoReg ALUSrc

缺失模块:
多选器 ImmGen Ctrl

整体逻辑:
1. 显示模块(只是用于后期查看Reg等信息来检测CPU正确性)
2. 引入Instr Mem的ROM文件,例化(循环显示来检测)
3. RF模块 (时序逻辑)(clk rstn RegWrite rs1 rs2 rd WriteData RD1 RD2)
4. ALU模块 (组合逻辑)(A B C Zero)
5. MEM模块 (时序逻辑)(clk MemWrite addr din DMType dout)
6. 拼接:
1. Control生成:输入指令各字段,生成各信号(组合逻辑assign
2. Imm Gen:
3. Mux:
1. ALU-Bin-Mux (组合逻辑) assign
2. RF-WD-Mux (组合逻辑) always @(*)+case

四大模块详细连接框图#

Pasted image 20251117220610
注释:
1)Op,funct3,funct7,rs1,rs2,rd,iim,sim 都是由instr译码过来。它们的值各是多少?

2)拨动sw_i[14:11]查看各部件显示内容,系统处要处于调试模式,此时sw_i[1] =1’b1,此时rom_addr =rom_addr,不会移动,只有sw_i[1]=1’b0时候,rom_addr = rom_addr + 1;

修改 rom_addr显示模块如下:if (sw_i[1] == 1’b0) rom_addr = rom_addr + 1;

3)如果rf,dm模块的写信号是posedeg 时钟周期有效,那么wd,din信号的写入是下个周期开始的上升沿。

4)附上12条指令coe的原码
Pasted image 20251116190815

add x1 x0 x0
addi x1 x1 1
add x2 x0 x0
addi x2 x2 2
add x3 x0 x0
addi x3 x3 3
sw x1 1(x0)
sw x2 2(x0)
sw x3 3(x0)
lw x4 1(x0)
lw x5 2(x0)
lw x6 3(x0)

Venus网站分析指令流#

关于x4,x5,x6的值
借助venus分析
Pasted image 20251117221603

Pasted image 20251117221728
根据小端法,低地址数据对应低位,高地址数据对应高位

代码(拼接,执行简单指令流)#

代码如下:
SCPU_TOP

// 拼接
module SCPU_TOP(
input clk,
input rstn,
input [15:0] sw_i,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
reg [31:0] clkdiv;
wire Clk_CPU;
always @(posedge clk or negedge rstn) begin
if(!rstn) clkdiv <= 0;
else clkdiv <= clkdiv + 1'b1;
end
assign Clk_CPU = (sw_i[15])?clkdiv[27]:clkdiv[25]; // 2^27 和 2^25分频
//assign Clk_CPU = clk; // 2^27 和 2^25分频
reg [63:0] display_data;
reg [5:0] led_data_addr;
reg [63:0] led_disp_data;
parameter LED_DATA_NUM = 19;
reg [63:0] LED_DATA[18:0];
initial begin
LED_DATA[0] = 64'hC6F6F6F0C6F6F6F0;
LED_DATA[1] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[2] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[3] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[4] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[5] = 64'hFFFFA3FFFFFFA3FF;
LED_DATA[6] = 64'hFFFF9CFFFFFF9CFF;
LED_DATA[7] = 64'hFF9EBCFFFF9EBCFF;
LED_DATA[8] = 64'hFF9CFFFFFF9CFFFF;
LED_DATA[9] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[10] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[11] = 64'hFFA7B3FFFFA7B3FF;
LED_DATA[12] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[13] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[14] = 64'h9EBEBEBC9EBEBEBC;
LED_DATA[15] = 64'h2737373327373733;
LED_DATA[16] = 64'h505454EC505454EC;
LED_DATA[17] = 64'h744454F8744454F8;
LED_DATA[18] = 64'h0062080000620800;
end
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
led_data_addr <= 6'd0;
led_disp_data <= 64'b1;
end
else if(sw_i[0] == 1'b1) begin
if(led_data_addr == LED_DATA_NUM) begin
led_data_addr <= 6'd0;
led_disp_data <= LED_DATA[0];
end
else begin
led_disp_data <= LED_DATA[led_data_addr];
led_data_addr <= led_data_addr + 1'b1;
end
end
else led_data_addr <= led_data_addr;
end
wire [31:0] instr;
reg [31:0] reg_data;
reg [31:0] alu_disp_data;
reg [31:0] dmem_data;
// ROM
reg [5:0] rom_addr;
// RF
wire RegWrite;
reg [31:0] WD;
wire [31:0] RD1,RD2;
reg [5:0] reg_addr;
// ALU
wire [31:0] A,B;
wire [4:0] ALUOp;
wire [31:0] aluout;
wire [7:0] Zero;
reg [2:0] alu_addr;
// DM
wire MemWrite;
wire [5:0] dm_addr;
wire [31:0] dm_din;
wire [2:0] DMType;
wire [31:0] dm_dout;
reg [6:0] dmem_addr;
// 其他
wire [1:0] WDSel;
wire [31:0] immout;
wire [5:0] EXTOp;
wire ALUSrc;
always @(sw_i) begin
if(sw_i[0] == 0) begin
case(sw_i[14:11])
4'b1000 : display_data <= instr;
4'b0100 : display_data <= reg_data;
4'b0010 : display_data <= alu_disp_data;
4'b0001 : display_data <= dmem_data;
default : display_data <= instr;
endcase
end
else display_data <= led_disp_data;
end
// 例化显示模块
seg7x16 u_seg7x16(
.clk(clk),
.rstn(rstn),
.disp_mode(sw_i[0]),
.i_data(display_data),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
///////////////////////////////////////////////////////
// 例化ROM模块
dist_mem_im U_IM(
.a(rom_addr),
.spo(instr)
);
// 每个CLK_CPU获得一个新指令
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) rom_addr <= 6'b0;
else if(sw_i[1] == 1'b0) rom_addr <= rom_addr + 1'b1;
else rom_addr <= rom_addr;
end
//Decode
wire [6:0] Op = instr[6:0];
wire [6:0] Funct7 = instr[31:25];
wire [2:0] Funct3 = instr[14:12];
wire [4:0] rs1 = instr[19:15];
wire [4:0] rs2 = instr[24:20];
wire [4:0] rd = instr[11:7];
wire [4:0] iimm_shamt= instr[24:20];
wire [11:0] iimm = instr[31:20];
wire [11:0] simm = {instr[31:25],instr[11:7]};
wire [11:0] bimm = {instr[31],instr[7],instr[30:25],instr[11:8]};
wire [19:0] uimm = instr[31:12];
wire [19:0] jimm = {instr[31],instr[19:12],instr[20],instr[30:21]};
// 例化ctrl模块
ctrl u_ctrl(
.Op(Op),
.Funct7(Funct7),
.Funct3(Funct3),
.Zero(Zero),
.RegWrite(RegWrite),
.MemWrite(MemWrite),
.EXTOp(EXTOp),
.ALUOp(ALUOp),
.ALUSrc(ALUSrc),
.DMType(DMType),
.WDSel(WDSel)
);
// 例化EXT模块
EXT U_EXT(
.iimm_shamt(iimm_shamt),
.iimm(iimm),
.simm(simm),
.bimm(bimm),
.uimm(uimm),
.jimm(jimm),
.EXTOp(EXTOp),
.immout(immout)
);
`define WDSel_FromALU 2'b00
`define WDSel_FromMEM 2'b01
// 赋值
// ALU_DM->RF RF_WD
always @(*)begin
WD <= 32'h0;
case(WDSel)
`WDSel_FromALU: WD <= aluout;
`WDSel_FromMEM: WD <= dm_dout;
//`WDSel_FromPC: WD<=PC_out+4;
default: WD <= 32'h0;
endcase
end
// RF->ALU ALU_A_B
assign A = RD1;
assign B = (ALUSrc == 1'b0)?RD2:immout;
// ALU->DM DM_addr_din
assign dm_addr = aluout;
assign dm_din = RD2;
// 例化RF模块
RF U_RF(
.clk(Clk_CPU),
.rstn(rstn),
.RFWr(RegWrite),
.sw_i(sw_i),
.A1(rs1),
.A2(rs2),
.A3(rd),
.WD(WD),
.RD1(RD1),
.RD2(RD2)
);
// 循环显示32个寄存器的内容
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
reg_addr <= 5'b0;
reg_data <= 32'b0;
end
else if(sw_i[13] == 1'b1)begin
reg_addr <= reg_addr + 1'b1;
reg_data <= U_RF.rf[reg_addr];
end
end
/////////////////////////////////////////////////////
// 例化alu模块
alu U_alu(
.A(A),
.B(B),
.ALUOp(ALUOp),
.C(aluout),
.Zero(Zero)
);
// 循环显示A B Zero四个值
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) alu_addr <= 3'b0;
else alu_addr <= alu_addr + 1'b1;
case(alu_addr) // 下面用普通reg接收signed数据,只会数据位复制
3'b001:alu_disp_data <= U_alu.A;
3'b010:alu_disp_data <= U_alu.B;
3'b011:alu_disp_data <= U_alu.C;
3'b100:alu_disp_data <= U_alu.Zero;
default:alu_disp_data <= 32'hFFFFFFFF;
endcase
end
//////////////////////////////////////////////////////
// 例化dm模块
dm U_DM(
.clk(Clk_CPU),
.DMWr(MemWrite),
.addr(dm_addr),
.din(dm_din),
.DMType(DMType),
.dout(dm_dout)
);
// 循环显示Data Memory内容
parameter DM_DATA_NUM = 16;
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
dmem_addr <= 7'b0;
dmem_data <= 32'hFFFFFFFF;
end
else if(sw_i[11] == 1'b1) begin
dmem_addr <= dmem_addr + 1'b1;
dmem_data <= U_DM.dmem[dmem_addr][7:0];
if(dmem_addr == DM_DATA_NUM) begin
dmem_addr <= 7'd0;
dmem_data <= 32'hFFFFFFFF;
end
end
end
endmodule

seg7x16

module seg7x16(
input clk,
input rstn,
input disp_mode,
input [63:0] i_data,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
// 2^15分频
reg [14:0] cnt;
wire seg7_clk;
always @(posedge clk or negedge rstn) begin
if(!rstn) cnt <= 0;
else cnt <= cnt + 1'b1;
end
assign seg7_clk = cnt[14];
// 8选1
reg [2:0] seg7_addr;
always @(posedge seg7_clk or negedge rstn) begin
if(!rstn) seg7_addr <= 0;
else seg7_addr <= seg7_addr + 1'b1;
end
// 选中的数码管使能信号
reg [7:0] o_sel_r;
always @(*) begin
case(seg7_addr)
7 : o_sel_r = 8'b01111111;
6 : o_sel_r = 8'b10111111;
5 : o_sel_r = 8'b11011111;
4 : o_sel_r = 8'b11101111;
3 : o_sel_r = 8'b11110111;
2 : o_sel_r = 8'b11111011;
1 : o_sel_r = 8'b11111101;
0 : o_sel_r = 8'b11111110;
endcase
end
//
reg [63:0] i_data_store;
always @(posedge clk or negedge rstn) begin
if(!rstn) i_data_store <= 0;
else i_data_store <= i_data;
end
reg [7:0] seg_data_r;
always @(*) begin
if(disp_mode == 1'b0) begin // 字符显示模式
case(seg7_addr)
0 : seg_data_r = i_data_store[3:0];
1 : seg_data_r = i_data_store[7:4];
2 : seg_data_r = i_data_store[11:8];
3 : seg_data_r = i_data_store[15:12];
4 : seg_data_r = i_data_store[19:16];
5 : seg_data_r = i_data_store[23:20];
6 : seg_data_r = i_data_store[27:24];
7 : seg_data_r = i_data_store[31:28];
default: seg_data_r = 8'hFF; // 默认全空,防止latch
endcase
end
else begin // 图形显示模式
case(seg7_addr)
0 : seg_data_r = i_data_store[7:0];
1 : seg_data_r = i_data_store[15:8];
2 : seg_data_r = i_data_store[23:16];
3 : seg_data_r = i_data_store[31:24];
4 : seg_data_r = i_data_store[39:32];
5 : seg_data_r = i_data_store[47:40];
6 : seg_data_r = i_data_store[55:48];
7 : seg_data_r = i_data_store[63:56];
default: seg_data_r = 8'hFF; // 默认全空,防止latch
endcase
end
end
reg [7:0] o_seg_r;
always @(posedge clk or negedge rstn) begin
if(!rstn) o_seg_r <= 8'hFF;
else if(disp_mode == 1'b0) begin // 字符模式
case(seg_data_r)
4'h0 : o_seg_r <= 8'hC0;
4'h1 : o_seg_r <= 8'hF9;
4'h2 : o_seg_r <= 8'hA4;
4'h3 : o_seg_r <= 8'hB0;
4'h4 : o_seg_r <= 8'h99;
4'h5 : o_seg_r <= 8'h92;
4'h6 : o_seg_r <= 8'h82;
4'h7 : o_seg_r <= 8'hF8;
4'h8 : o_seg_r <= 8'h80;
4'h9 : o_seg_r <= 8'h90;
4'hA : o_seg_r <= 8'h88;
4'hB : o_seg_r <= 8'h83;
4'hC : o_seg_r <= 8'hC6;
4'hD : o_seg_r <= 8'hA1;
4'hE : o_seg_r <= 8'h86;
4'hF : o_seg_r <= 8'h8E;
default : o_seg_r <= 8'hFF;
endcase
end
else o_seg_r <= seg_data_r;
end
assign disp_an_o = o_sel_r;
assign disp_seg_o = o_seg_r;
endmodule

RF

module RF(
input clk,
input rstn,
input RFWr,
input [15:0] sw_i,
input [4:0] A1,A2,A3,
input [31:0] WD,
output [31:0] RD1,RD2
);
reg [31:0] rf[31:0]; // 32个寄存器:0到31
integer i;
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
for(i=0;i<32;i=i+1) rf[i] <= i;
end
else if(RFWr && (!sw_i[1]) && (A3 != 5'd0)) rf[A3] <= WD; // 正常模式下且RegWrite有效,且不是x0寄存器,写rd
end
// 读rs1和rs2
assign RD1 = (A1 != 0)?rf[A1]:0;
assign RD2 = (A2 != 0)?rf[A2]:0;
endmodule

alu

`define ALUOp_nop 5'b00000
`define ALUOp_lui 5'b00001
`define ALUOp_auipc 5'b00010
`define ALUOp_add 5'b00011
`define ALUOp_sub 5'b00100
`define ALUOp_and 5'b00101
`define ALUOp_or 5'b00110
module alu(
input signed [31:0] A,B,
input [4:0] ALUOp,
output reg signed [31:0] C,
output reg [7:0] Zero
);
always @(*) begin
case(ALUOp)
`ALUOp_add:C = A + B;
`ALUOp_sub:C = A - B;
`ALUOp_and:C = A & B;
`ALUOp_or:C = A | B;
default:C = 32'b0;
endcase
Zero = (C == 0)?1:0; // 阻塞赋值,需等C算出来,而不是同步
end
endmodule

dm

`define dm_word 3'b000
`define dm_halfword 3'b001
`define dm_halfword_unsigned 3'b010
`define dm_byte 3'b011
`define dm_byte_unsigned 3'b100
module dm(
input clk,
input DMWr,
input [5:0] addr,
input [31:0] din,
input [2:0] DMType,
output reg [31:0] dout
);
reg [7:0] dmem[15:0]; // 16个单元
integer i;
initial begin
for(i=0;i<16;i=i+1) dmem[i] = 8'h00;
end
// 写操作
always @(posedge clk) begin
if(DMWr) begin
case(DMType)
`dm_byte:dmem[addr] <= din[7:0];
`dm_halfword:begin
dmem[addr] <= din[7:0];
dmem[addr+1] <= din[15:8];
end
`dm_word:begin
dmem[addr] <= din[7:0];
dmem[addr+1] <= din[15:8];
dmem[addr+2] <= din[23:16];
dmem[addr+3] <= din[31:24];
end
endcase
end
end
// 读操作
always @(*) begin
case(DMType)
`dm_byte:dout = {{24{dmem[addr][7]}},dmem[addr][7:0]};
`dm_byte_unsigned:dout = {24'b0,dmem[addr][7:0]};
`dm_halfword:dout = {{16{dmem[addr+1][7]}},dmem[addr+1][7:0],dmem[addr][7:0]};
`dm_halfword_unsigned:dout = {16'b0,dmem[addr+1][7:0],dmem[addr][7:0]};
`dm_word:dout = {dmem[addr+3][7:0],dmem[addr+2][7:0],dmem[addr+1][7:0],dmem[addr][7:0]};
default:dout = {dmem[addr+3][7:0],dmem[addr+2][7:0],dmem[addr+1][7:0],dmem[addr][7:0]};
endcase
end
endmodule

Ctrl

module ctrl(
input [6:0] Op,
input [6:0] Funct7,
input [2:0] Funct3,
input Zero,
output RegWrite,
output MemWrite,
output [5:0] EXTOp,
output [4:0] ALUOp,
// output [2:0] NPCOp,
output ALUSrc,
output [2:0] DMType,
output [1:0] WDSel // MemtoReg
);
// R_type
wire rtype = ~Op[6] & Op[5] & Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //0110011
wire i_add = rtype & ~Funct7[6] & ~Funct7[5] & ~Funct7[4] & ~Funct7[3] & ~Funct7[2] & ~Funct7[1] & ~Funct7[0] & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // add 0000000 000
wire i_sub = rtype & ~ Funct7[6] & Funct7[5] & ~Funct7[4] & ~Funct7[3] & ~Funct7[2] & ~Funct7[1] & ~Funct7[0] & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // sub 0100000 000
// i_l type load
wire itype_l = ~Op[6] & ~Op[5] & ~Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //0000011
wire i_lb = itype_l & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; //lb 000
wire i_lh = itype_l & ~Funct3[2] & ~Funct3[1] & Funct3[0]; //lh 001
wire i_lw = itype_l & ~Funct3[2] & Funct3[1] & ~Funct3[0]; //lw 010
wire i_lbu = itype_l & Funct3[2] & ~Funct3[1] & ~Funct3[0]; //lbu 100
wire i_lhu = itype_l & Funct3[2] & ~Funct3[1] & Funct3[0]; //lh 101
// i_r type 涉及Reg,ALU with immediate
wire itype_r = ~Op[6] & ~Op[5] & Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //0010011
wire i_addi = itype_r & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // addi 000 func3
// s format store
wire stype = ~Op[6] & Op[5] & ~Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0];//0100011
wire i_sw = ~Funct3[2] & Funct3[1] & ~Funct3[0]; // sw 010
wire i_sb = stype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // sb 000
wire i_sh = stype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // sh 001
// 生成控制信号
assign RegWrite = rtype | itype_r | itype_l; // register write
assign MemWrite = stype; // memory write
assign ALUSrc = itype_r | stype | itype_l; // ALU B is from instruction immediate
// assign mem2reg = wdsel
// WDSel_FromALU 2'b00 WDSel_FromMEM 2'b01
assign WDSel[0] = itype_l;
assign WDSel[1] = 1'b0;
//ALUOp_nop 5'b00000
//ALUOp_lui 5'b00001
//ALUOp_auipc 5'b00010
//ALUOp_add 5'b00011
assign ALUOp[0] = i_add | i_addi | stype | itype_l;
assign ALUOp[1] = i_add | i_addi | stype | itype_l; // 均为加法
assign EXTOp[0] = stype;
assign EXTOp[1] = itype_l | itype_r;
// dm_word 3'b000
// dm_halfword 3'b001
// dm_halfword_unsigned 3'b010
// dm_byte 3'b011
// dm_byte_unsigned 3'b100
assign DMType[2] = i_lbu; // bu
assign DMType[1] = i_lb | i_sb | i_lhu; // hu和b
assign DMType[0] = i_lh | i_sh | i_lb | i_sb; // h和b
endmodule

EXT

// 立即数扩展
`define EXT_CTRL_ITYPE_SHAMT 3'b000
`define EXT_CTRL_ITYPE 3'b010
`define EXT_CTRL_STYPE 3'b001
`define EXT_CTRL_BTYPE 3'b011
`define EXT_CTRL_UTYPE 3'b100
`define EXT_CTRL_JTYPE 3'b101
module EXT(
input [4:0] iimm_shamt,
input [11:0] iimm,
input [11:0] simm,
input [11:0] bimm,
input [19:0] uimm,
input [19:0] jimm,
input [5:0] EXTOp,
output reg [31:0] immout
);
always @(*) begin
case (EXTOp)
`EXT_CTRL_ITYPE_SHAMT: immout <= {27'b0,iimm_shamt[4:0]}; // I-type 立即数 (用于 slli/srli/srai)
`EXT_CTRL_ITYPE: immout <= {{20{iimm[11]}}, iimm[11:0]}; // I-type 立即数 (用于 addi, lw 等)
`EXT_CTRL_STYPE: immout <= {{20{simm[11]}}, simm[11:0]}; // S-type 立即数 (用于 sw 等)
`EXT_CTRL_BTYPE: immout<= {{19{bimm[11]}},bimm[11:0],1'b0}; // B-type 立即数 (用于 beq 等)
`EXT_CTRL_UTYPE: immout <= {uimm[19:0], 12'b0}; // U-type 立即数 (用于 lui, auipc)
`EXT_CTRL_JTYPE: immout<= {{11{jimm[19]}},jimm[19:0],1'b0}; // J-type 立即数 (用于 jal)
default: immout <= 32'b0;
endcase
end
endmodule

还有Rom的coe文件引入dist_mem_imm模块

tb文件代码#

使用tb文件仿真的同时,要修改SCPU_TOP中的Clk_CPU为clk,否则在仿真按27或25分频的clk周期就比较大(27个或25个clk周期为1周期),如果原始 clk周期是20ns(对应50MHz),那么一个 Clk_CPU周期就长达540ns或500ns,效率太低,1000ns里能执行的指令数量少。

assign Clk_CPU = (sw_i[15])?clkdiv[27]:clkdiv[25]; // 2^27 和 2^25分频
// 修改为下面
assign Clk_CPU = clk; // 2^27 和 2^25分频

tb_SCPU_TOP 1.0

`timescale 1ns/1ps
module tb_SCPU_TOP;
reg clk;
reg rstn;
reg [15:0] sw_i;
wire [7:0] disp_seg_o, disp_an_o;
// UUT
SCPU_TOP uut (
.clk(clk),
.rstn(rstn),
.sw_i(sw_i),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
// Clock
initial begin
clk = 0;
forever #5 clk = ~clk; // 100MHz
end
// Reset and switches
initial begin
rstn = 0;
//sw_i = 16'b0000_0000_0000_0010; // 阻止rom_addr自增,同时阻止Reg写入
sw_i = 16'b0000_0000_0000_0000;
#50 rstn = 1;
end
initial begin
@(posedge rstn); // 等待复位释放
repeat (20) begin
@(posedge clk);
$display("t=%0t instr=%h rom_addr=%d", $time, uut.instr, uut.rom_addr);
$display("→ rd=%0d rs1=%0d rs2=%0d Op=%b Funct3=%b Funct7=%b",
uut.rd, uut.rs1, uut.rs2, uut.Op, uut.Funct3, uut.Funct7);
$display("→ RegWrite=%b A3=%0d WD=%h ALUout=%h WDSel=%b ALUSrc=%b",
uut.RegWrite, uut.rd, uut.WD, uut.aluout, uut.WDSel, uut.ALUSrc);
$display("→ rf[%0d] = %h", uut.rd, uut.U_RF.rf[uut.rd]);
$display("--------------------------------------------------");
end
$stop;
end
// // Monitor
// initial begin
// // 等待复位释放后几个时钟
// @(posedge rstn);
// repeat (5) @(posedge clk);
// $display("First instr = %h", uut.instr);
// $display("Decoded rd=%0d rs1=%0d rs2=%0d Op=%b Funct3=%b Funct7=%b",
// uut.rd, uut.rs1, uut.rs2, uut.Op, uut.Funct3, uut.Funct7);
// // 观察一个写周期
// repeat (10) begin
// @(posedge clk);
// $display("t=%0t RegWrite=%b A3=%0d WD=%h ALUout=%h WDSel=%b ALUSrc=%b",
// $time, uut.RegWrite, uut.rd, uut.WD, uut.aluout, uut.WDSel, uut.ALUSrc);
// end
// // 检查 x1
// $display("rf[1] = %h", uut.U_RF.rf[1]);
// if (uut.U_RF.rf[1] == 32'h0000_0000)
// $display("PASS: add x1, x0, x0");
// else
// $display("FAIL: rf[1] != 0, got %h", uut.U_RF.rf[1]);
// $stop;
// end
endmodule
// Testbench ROM override: dist_mem_im
module dist_mem_im (
input [5:0] a,
output reg [31:0] spo
);
always @(*) begin
case (a)
6'd0 : spo = 32'h000000b3; // add x1, x0, x0
6'd1 : spo = 32'h00108093; // addi x1, x1, 1
6'd2 : spo = 32'h00000133; // add x2, x0, x0
6'd3 : spo = 32'h00210113; // addi x2, x2, 2
6'd4 : spo = 32'h000001b3; // add x3, x0, x0
6'd5 : spo = 32'h00318193; // addi x3, x3, 3
6'd6 : spo = 32'h001020a3; // sw x1, 0(x0)
6'd7 : spo = 32'h00202123; // sw x2, 4(x0)
6'd8 : spo = 32'h003021a3; // sw x3, 8(x0)
6'd9 : spo = 32'h00102203; // lw x4, 0(x0)
6'd10: spo = 32'h00202283; // lw x5, 4(x0)
6'd11: spo = 32'h00302303; // lw x6, 8(x0)
6'd12: spo = 32'h00000013; // nop
6'd13: spo = 32'h00000013; // nop
6'd14: spo = 32'h00000013; // nop
6'd15: spo = 32'h00000013; // nop
default: spo = 32'h00000013; // default nop
endcase
end
endmodule

tb_SCPU_TOP 2.0

`timescale 1ns/1ps
module tb_SCPU_TOP;
reg clk;
reg rstn;
reg [15:0] sw_i;
wire [7:0] disp_seg_o, disp_an_o;
SCPU_TOP uut (
.clk(clk),
.rstn(rstn),
.sw_i(sw_i),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
// 时钟生成:100MHz
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 初始化复位和开关
initial begin
rstn = 0;
sw_i = 16'b0000_0000_0000_0000; // sw_i[1]=0 允许写入
#50 rstn = 1;
end
// 指令执行跟踪器
integer i;
reg [4:0] last_rd;
initial begin
@(posedge rstn);
repeat (20) begin
@(posedge clk);
$display("t=%0t instr=%h rom_addr=%d", $time, uut.instr, uut.rom_addr);
$display("→ rd=%0d rs1=%0d rs2=%0d Op=%b Funct3=%b Funct7=%b",
uut.rd, uut.rs1, uut.rs2, uut.Op, uut.Funct3, uut.Funct7);
$display("→ RegWrite=%b WDSel=%b ALUSrc=%b EXTOp=%b",
uut.RegWrite, uut.WDSel, uut.ALUSrc, uut.EXTOp);
$display("→ RD1=%h RD2=%h immout=%h", uut.RD1, uut.RD2, uut.immout);
$display("→ A=%h B=%h → ALUout=%h", uut.A, uut.B, uut.aluout);
$display("→ WD=%h → rf[%0d] = %h", uut.WD, last_rd, uut.U_RF.rf[last_rd]);
$display("--------------------------------------------------");
last_rd <= uut.rd;
end
$stop;
end
endmodule
// ROM模块:dist_mem_im,含16条指令
module dist_mem_im (
input [5:0] a,
output reg [31:0] spo
);
always @(*) begin
case (a)
6'd0 : spo = 32'h000000b3; // add x1, x0, x0
6'd1 : spo = 32'h00108093; // addi x1, x1, 1
6'd2 : spo = 32'h00000133; // add x2, x0, x0
6'd3 : spo = 32'h00210113; // addi x2, x2, 2
6'd4 : spo = 32'h000001b3; // add x3, x0, x0
6'd5 : spo = 32'h00318193; // addi x3, x3, 3
6'd6 : spo = 32'h001020a3; // sw x1, 0(x0)
6'd7 : spo = 32'h00202123; // sw x2, 4(x0)
6'd8 : spo = 32'h003021a3; // sw x3, 8(x0)
6'd9 : spo = 32'h00102203; // lw x4, 0(x0)
6'd10: spo = 32'h00202283; // lw x5, 4(x0)
6'd11: spo = 32'h00302303; // lw x6, 8(x0)
6'd12: spo = 32'h00000013; // nop
6'd13: spo = 32'h00000013; // nop
6'd14: spo = 32'h00000013; // nop
6'd15: spo = 32'h00000013; // nop
default: spo = 32'h00000013;
endcase
end
endmodule

coe文件内容:

memory_initialization_radix=16;
memory_initialization_vector=000000b3 00108093 00000133 00210113 000001b3 00318193 001020a3 00202123 003021a3 00102203 00202283 00302303;

tb文件补充解释:(reg读取更新滞后问题)#

reg的值更新滞后
$display会在 always块执行完毕之前,甚至在块内的非阻塞赋值生效之前就立即执行并打印

在一个时钟上升沿(比如 posedge clk)发生时,仿真器会分几个阶段执行:

  1. 事件触发阶段 所有 always @(posedge clk) 块被触发,右边的表达式(如 WD)被计算。
  2. 非阻塞赋值调度阶段 对于 rf[A3] <= WD; 这样的非阻塞赋值,结果不会立刻更新,而是放入一个“更新队列”。
  3. 打印阶段 如果你在 testbench 里写了:
@(posedge clk);
$display("rf[%0d] = %h", uut.rd, uut.U_RF.rf[uut.rd]);

那么 $display 会在这个时钟边沿触发的 always 块执行完毕之前就读取 rf[...],看到的还是旧值。
4. 更新阶段 当所有触发的 always 块执行完毕后,仿真器才统一更新寄存器的值。此时 rf[A3] 才真正变成新值。

举例:

  • t=100ns 的上升沿,WD 被采样,写入事件排队。
  • 你在 @(posedge clk) 后打印,看到的是旧值。
  • t=100ns 边沿结束时,rf[A3] 才更新。
  • 所以你在 t=110ns 再打印,才看到新值。

如何解决这个“滞后”问题?#

方法一:延迟一拍再打印寄存器值#

在 testbench 中记录 rd,下一拍再打印:

reg [4:0] last_rd;
initial begin
@(posedge rstn);
repeat (20) begin
@(posedge clk);
$display("→ rf[%0d] = %h", last_rd, uut.U_RF.rf[last_rd]);
last_rd <= uut.rd;
end
end

这样你就能看到写入后的值,而不是旧值。

方法二:打印写入前后的值对比#

你可以在 testbench 中加:

$display("→ rf[%0d] before = %h", uut.rd, uut.U_RF.rf[uut.rd]);
@(posedge clk);
$display("→ rf[%0d] after = %h", uut.rd, uut.U_RF.rf[uut.rd]);

这样你能清楚看到写入是否生效。

最终上板子结果检测#

拷到硬件上后拨动sw_i[1]为1暂停rom_addr自增(取下一条指令)
sw_i[14]为1或sw_i[14:11]都为0,显示当前指令编码
sw_i[13]为1,显示reg,然而reg值不会呈现更新内容,因为设定了sw_i[1]=1则不能写入Reg,所以没有更新,如果 sw_i[1]=0RegWrite=1 → 在下一个时钟上升沿,寄存器更新为 WD,所以在下一条指令暂停时,reg显示的就是之前指令执行后reg结果。
sw_i[12]为1,显示alu的运算,显示A,B,C,Zero,是实时当前指令的alu过程
sw_i[11]为1,显示DM数据存储器的内容,是当前指令执行后的结果

8.单周期CPU(37条指令)#

37条指令图(列清下图很好写Control生成)
Pasted image 20251116190550

转移指令#


branch taken系列:beq,bne,blt,bge,bltu,bgeu

  • 比较 -> ALU运算:sub, less than, less than unsigned 获得标志 产生各指令唯一标识的信号
  • 跳转 -> PC = PC + immout(已完成扩展和移位)


jal:

  • 保存返回地址 -> WD = PC + 4
  • 跳转 -> PC = PC + immout(已完成扩展和移位)


jalr:

  • 保存返回地址 -> WD = PC + 4
  • 跳转 -> PC = RD1 + immout(已完成扩展)


shift系列 with shamt(imm):slli,srli,srai#


shift系列 with rs2:srl,sra#

代码(适配37条指令)#

SCPU_TOP

// 37条指令
module SCPU_TOP(
input clk,
input rstn,
input [15:0] sw_i,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
reg [31:0] clkdiv;
wire Clk_CPU;
always @(posedge clk or negedge rstn) begin
if(!rstn) clkdiv <= 0;
else clkdiv <= clkdiv + 1'b1;
end
assign Clk_CPU = (sw_i[15])?clkdiv[27]:clkdiv[25]; // 2^27 和 2^25分频
reg [63:0] display_data;
reg [5:0] led_data_addr;
reg [63:0] led_disp_data;
parameter LED_DATA_NUM = 19;
reg [63:0] LED_DATA[18:0];
initial begin
LED_DATA[0] = 64'hC6F6F6F0C6F6F6F0;
LED_DATA[1] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[2] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[3] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[4] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[5] = 64'hFFFFA3FFFFFFA3FF;
LED_DATA[6] = 64'hFFFF9CFFFFFF9CFF;
LED_DATA[7] = 64'hFF9EBCFFFF9EBCFF;
LED_DATA[8] = 64'hFF9CFFFFFF9CFFFF;
LED_DATA[9] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[10] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[11] = 64'hFFA7B3FFFFA7B3FF;
LED_DATA[12] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[13] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[14] = 64'h9EBEBEBC9EBEBEBC;
LED_DATA[15] = 64'h2737373327373733;
LED_DATA[16] = 64'h505454EC505454EC;
LED_DATA[17] = 64'h744454F8744454F8;
LED_DATA[18] = 64'h0062080000620800;
end
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
led_data_addr <= 6'd0;
led_disp_data <= 64'b1;
end
else if(sw_i[0] == 1'b1) begin
if(led_data_addr == LED_DATA_NUM) begin
led_data_addr <= 6'd0;
led_disp_data <= LED_DATA[0];
end
else begin
led_disp_data <= LED_DATA[led_data_addr];
led_data_addr <= led_data_addr + 1'b1;
end
end
else led_data_addr <= led_data_addr;
end
wire [31:0] instr;
reg [31:0] reg_data;
reg [31:0] alu_disp_data;
reg [31:0] dmem_data;
// ROM
reg [5:0] rom_addr;
// RF
wire RegWrite;
reg [31:0] WD;
wire [31:0] RD1,RD2;
reg [5:0] reg_addr;
// ALU
wire [31:0] A,B;
wire [3:0] ALUOp;
wire [31:0] aluout;
wire Zero;
wire ALU_C_sign;
wire ALU_Cout;
reg [2:0] alu_addr;
// DM
wire MemWrite;
wire [5:0] dm_addr;
wire [31:0] dm_din;
wire [2:0] DMType;
wire [31:0] dm_dout;
reg [6:0] dmem_addr;
// 其他
wire [2:0] WDSel;
wire [31:0] immout;
wire [1:0] NPCOp;
wire [3:0] EXTOp;
wire ALUSrc;
always @(sw_i) begin
if(sw_i[0] == 0) begin
case(sw_i[14:11])
4'b1000 : display_data <= instr;
4'b0100 : display_data <= reg_data;
4'b0010 : display_data <= alu_disp_data;
4'b0001 : display_data <= dmem_data;
default : display_data <= instr;
endcase
end
else display_data = led_disp_data;
end
// 例化显示模块
seg7x16 u_seg7x16(
.clk(clk),
.rstn(rstn),
.disp_mode(sw_i[0]),
.i_data(display_data),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
///////////////////////////////////////////////////////
// 例化ROM模块
dist_mem_im U_IM(
.a(rom_addr),
.spo(instr)
);
reg [5:0] PC_current;
// 每个CLK_CPU获得一个新指令
// 这里是字寻址
// NPCOp: 00:PC+1 01:PC+immout 10:PC = RD1 + immout
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
rom_addr <= 6'b0;
PC_current <= 6'b0;
end
else begin
PC_current <= rom_addr; // 当前执行指令的 PC
if(sw_i[1] == 1'b0) begin
case(NPCOp)
2'b00: rom_addr <= rom_addr + 1'b1;
2'b01: rom_addr <= rom_addr + immout; // PC+offset branch jal
2'b10: rom_addr <= RD1 + immout; // jalr
default: rom_addr <= rom_addr + 1'b1;
endcase
end
end
end
// 每个CLK_CPU获得一个新指令
// 这里是字寻址
// NPCOp: 0:PC+1 1:PC+immout
// always @(posedge Clk_CPU or negedge rstn) begin
// if(!rstn) rom_addr <= 6'b0;
// else if(sw_i[1] == 1'b0) rom_addr <= (NPCOp == 1'b0)?(rom_addr + 1'b1):(rom_addr + immout); // 如果存的offset是字寻址偏移量而不是字节寻址偏移量
// else rom_addr <= rom_addr;
// end
//Decode
wire [6:0] Op = instr[6:0];
wire [6:0] Funct7 = instr[31:25];
wire [2:0] Funct3 = instr[14:12];
wire [4:0] rs1 = instr[19:15];
wire [4:0] rs2 = instr[24:20];
wire [4:0] rd = instr[11:7];
wire [4:0] iimm_shamt= instr[24:20];
wire [11:0] iimm = instr[31:20];
wire [11:0] simm = {instr[31:25],instr[11:7]};
wire [11:0] bimm = {instr[31],instr[7],instr[30:25],instr[11:8]};
wire [19:0] uimm = instr[31:12];
wire [19:0] jimm = {instr[31],instr[19:12],instr[20],instr[30:21]};
// 例化ctrl模块
ctrl u_ctrl(
.Op(Op),
.Funct7(Funct7),
.Funct3(Funct3),
.Zero(Zero),
.ALU_C_sign(ALU_C_sign),
.ALU_Cout(ALU_Cout),
.RegWrite(RegWrite),
.MemWrite(MemWrite),
.EXTOp(EXTOp),
.ALUOp(ALUOp),
.ALUSrc(ALUSrc),
.NPCOp(NPCOp),
.DMType(DMType),
.WDSel(WDSel)
);
// 例化EXT模块
EXT U_EXT(
.iimm_shamt(iimm_shamt),
.iimm(iimm),
.simm(simm),
.bimm(bimm),
.uimm(uimm),
.jimm(jimm),
.EXTOp(EXTOp),
.immout(immout)
);
// 赋值
// ALU_DM->RF RF_WD
// WDSel 000: aluout 001: dm_dout 010: PC+1 011: immout<<12 100: PC + immout<<12
always @(*)begin
WD <= 32'h0;
case(WDSel)
000: WD <= aluout;
001: WD <= dm_dout;
010: WD <= PC_current + 1'b1;
011: WD <= immout << 12;
100: WD <= PC_current + (immout << 12);
default: WD <= 32'h0;
endcase
end
//assign WD = (WDSel == 3'b000) ? aluout : // 普通 ALU 运算
// (WDSel == 3'b001) ? dm_dout : // lw 加载
// (WDSel == 3'b010) ? (PC_current + 1'b1) : // jal/jalr
// (WDSel == 3'b011) ? (immout << 12) : // auipc
// (WDSel == 3'b100) ? (PC_current + (immout << 12)) : 32'b0; // lui // 默认
// RF->ALU ALU_A_B
assign A = RD1;
assign B = (ALUSrc == 1'b0)?RD2:immout;
// ALU->DM DM_addr_din
assign dm_addr = aluout;
assign dm_din = RD2;
// 例化RF模块
RF U_RF(
.clk(Clk_CPU),
.rstn(rstn),
.RFWr(RegWrite),
.sw_i(sw_i),
.A1(rs1),
.A2(rs2),
.A3(rd),
.WD(WD),
.RD1(RD1),
.RD2(RD2)
);
// 循环显示32个寄存器的内容
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
reg_addr <= 5'b0;
reg_data <= 32'b0;
end
else if(sw_i[13] == 1'b1)begin
reg_addr <= reg_addr + 1'b1;
reg_data <= U_RF.rf[reg_addr];
end
end
/////////////////////////////////////////////////////
// 例化alu模块
alu U_alu(
.A(A),
.B(B),
.ALUOp(ALUOp),
.C(aluout),
.Zero(Zero),
.ALU_C_sign(ALU_C_sign),
.ALU_Cout(ALU_Cout)
);
// 循环显示A B Zero四个值
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) alu_addr <= 3'b0;
else alu_addr <= alu_addr + 1'b1;
case(alu_addr) // 下面用普通reg接收signed数据,只会数据位复制
3'b001:alu_disp_data <= U_alu.A;
3'b010:alu_disp_data <= U_alu.B;
3'b011:alu_disp_data <= U_alu.C;
3'b100:alu_disp_data <= U_alu.Zero;
default:alu_disp_data <= 32'hFFFFFFFF;
endcase
end
//////////////////////////////////////////////////////
// 例化dm模块
dm U_DM(
.clk(Clk_CPU),
.DMWr(MemWrite),
.addr(dm_addr),
.din(dm_din),
.DMType(DMType),
.dout(dm_dout)
);
// 循环显示Data Memory内容
parameter DM_DATA_NUM = 16;
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
dmem_addr <= 7'b0;
dmem_data <= 32'hFFFFFFFF;
end
else if(sw_i[11] == 1'b1) begin
dmem_addr <= dmem_addr + 1'b1;
dmem_data <= U_DM.dmem[dmem_addr][7:0];
if(dmem_addr == DM_DATA_NUM) begin
dmem_addr <= 7'd0;
dmem_data <= 32'hFFFFFFFF;
end
end
end
endmodule

seg7x16

module seg7x16(
input clk,
input rstn,
input disp_mode,
input [63:0] i_data,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
// 2^15分频
reg [14:0] cnt;
wire seg7_clk;
always @(posedge clk or negedge rstn) begin
if(!rstn) cnt <= 0;
else cnt <= cnt + 1'b1;
end
assign seg7_clk = cnt[14];
// 8选1
reg [2:0] seg7_addr;
always @(posedge seg7_clk or negedge rstn) begin
if(!rstn) seg7_addr <= 0;
else seg7_addr <= seg7_addr + 1'b1;
end
// 选中的数码管使能信号
reg [7:0] o_sel_r;
always @(*) begin
case(seg7_addr)
7 : o_sel_r = 8'b01111111;
6 : o_sel_r = 8'b10111111;
5 : o_sel_r = 8'b11011111;
4 : o_sel_r = 8'b11101111;
3 : o_sel_r = 8'b11110111;
2 : o_sel_r = 8'b11111011;
1 : o_sel_r = 8'b11111101;
0 : o_sel_r = 8'b11111110;
endcase
end
//
reg [63:0] i_data_store;
always @(posedge clk or negedge rstn) begin
if(!rstn) i_data_store <= 0;
else i_data_store <= i_data;
end
reg [7:0] seg_data_r;
always @(*) begin
if(disp_mode == 1'b0) begin // 字符显示模式
case(seg7_addr)
0 : seg_data_r = i_data_store[3:0];
1 : seg_data_r = i_data_store[7:4];
2 : seg_data_r = i_data_store[11:8];
3 : seg_data_r = i_data_store[15:12];
4 : seg_data_r = i_data_store[19:16];
5 : seg_data_r = i_data_store[23:20];
6 : seg_data_r = i_data_store[27:24];
7 : seg_data_r = i_data_store[31:28];
default: seg_data_r = 8'hFF; // 默认全空,防止latch
endcase
end
else begin // 图形显示模式
case(seg7_addr)
0 : seg_data_r = i_data_store[7:0];
1 : seg_data_r = i_data_store[15:8];
2 : seg_data_r = i_data_store[23:16];
3 : seg_data_r = i_data_store[31:24];
4 : seg_data_r = i_data_store[39:32];
5 : seg_data_r = i_data_store[47:40];
6 : seg_data_r = i_data_store[55:48];
7 : seg_data_r = i_data_store[63:56];
default: seg_data_r = 8'hFF; // 默认全空,防止latch
endcase
end
end
reg [7:0] o_seg_r;
always @(posedge clk or negedge rstn) begin
if(!rstn) o_seg_r <= 8'hFF;
else if(disp_mode == 1'b0) begin // 字符模式
case(seg_data_r)
4'h0 : o_seg_r <= 8'hC0;
4'h1 : o_seg_r <= 8'hF9;
4'h2 : o_seg_r <= 8'hA4;
4'h3 : o_seg_r <= 8'hB0;
4'h4 : o_seg_r <= 8'h99;
4'h5 : o_seg_r <= 8'h92;
4'h6 : o_seg_r <= 8'h82;
4'h7 : o_seg_r <= 8'hF8;
4'h8 : o_seg_r <= 8'h80;
4'h9 : o_seg_r <= 8'h90;
4'hA : o_seg_r <= 8'h88;
4'hB : o_seg_r <= 8'h83;
4'hC : o_seg_r <= 8'hC6;
4'hD : o_seg_r <= 8'hA1;
4'hE : o_seg_r <= 8'h86;
4'hF : o_seg_r <= 8'h8E;
default : o_seg_r <= 8'hFF;
endcase
end
else o_seg_r <= seg_data_r;
end
assign disp_an_o = o_sel_r;
assign disp_seg_o = o_seg_r;
endmodule

rom(如果在Vivado导入了coe文件并创建了rom则不必自建rom模块)

module dist_mem_im (
input wire [5:0] a, // 地址输入
output wire [31:0] spo // 指令输出
);
// 定义 ROM 存储空间,这里假设最多 256 条指令,每条 32 位
reg [31:0] rom [0:63];
// 初始化 ROM 内容
initial begin
// 从 mem 文件加载数据(由 coe 转换而来)
$readmemh("Test_8_Instr1.mem", rom);
end
// 组合逻辑输出
assign spo = rom[a];
endmodule

RF

module RF(
input clk,
input rstn,
input RFWr,
input [15:0] sw_i,
input [4:0] A1,A2,A3,
input [31:0] WD,
output [31:0] RD1,RD2
);
reg [31:0] rf[31:0];
integer i;
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
for(i=0;i<32;i=i+1) rf[i] <= i;
end
else if(RFWr && (!sw_i[1]) && (A3 != 5'd0)) rf[A3] <= WD; // 正常模式下且RegWrite有效,写rd
end
// 读rs1和rs2
assign RD1 = (A1 != 0)?rf[A1]:0;
assign RD2 = (A2 != 0)?rf[A2]:0;
endmodule

alu

`define ALU_NOP 4'b0000
`define ALU_ADD 4'b0001
`define ALU_SUB 4'b0010
`define ALU_AND 4'b0011
`define ALU_OR 4'b0100
`define ALU_XOR 4'b0101
`define ALU_SLL 4'b0110
`define ALU_SRL 4'b0111
`define ALU_SRA 4'b1000
`define ALU_SLT 4'b1001
`define ALU_SLTU 4'b1010
`define ALU_U 4'b1011 // (用于 utype 指令 lui auipc 立即数<<12)
module alu(
input signed [31:0] A,B,
input [3:0] ALUOp,
output reg signed [31:0] C,
output reg Zero,
output reg ALU_C_sign, // 有符号比较结果 SLT
output reg ALU_Cout // 无符号比较结果 SLTU
);
always @(*) begin
C = 32'b0;
case(ALUOp)
`ALU_ADD: C = A + B;
`ALU_SUB: C = A - B;
`ALU_AND: C = A & B;
`ALU_OR: C = A | B;
`ALU_XOR: C = A ^ B;
`ALU_SLL: C = A << B[4:0]; // shamt 只用低5位,5位可以表示0-31的移位量,多的移位没有意义
`ALU_SRL: C = A >> B[4:0];
`ALU_SRA: C = A >>> B[4:0];
`ALU_SLT: begin
C = (A < B) ? 32'b1 : 32'b0;
ALU_C_sign = C; // 高位截断
end
`ALU_SLTU: begin
C = ($unsigned(A) < $unsigned(B)) ? 32'b1 : 32'b0;
ALU_Cout = C;
end
`ALU_U: C = B << 12; // LUI: 把立即数左移12位
default:C = 32'b0;
endcase
Zero = (C == 0)?1:0; // 阻塞赋值,需等C算出来,而不是同步
end
endmodule

dm

`define dm_word 3'b000
`define dm_halfword 3'b001
`define dm_halfword_unsigned 3'b010
`define dm_byte 3'b011
`define dm_byte_unsigned 3'b100
module dm(
input clk,
input DMWr,
input [5:0] addr,
input [31:0] din,
input [2:0] DMType,
output reg [31:0] dout
);
reg [7:0] dmem[6:0]; // 128个地址,128个单元,每个单元8位
integer i;
initial begin
for(i=0;i<7;i=i+1) dmem[i] = 8'h00;
end
// 写操作
always @(posedge clk) begin
if(DMWr) begin
case(DMType)
`dm_byte:dmem[addr] <= din[7:0];
`dm_halfword:begin
dmem[addr] <= din[7:0];
dmem[addr+1] <= din[15:8];
end
`dm_word:begin
dmem[addr] <= din[7:0];
dmem[addr+1] <= din[15:8];
dmem[addr+2] <= din[23:16];
dmem[addr+3] <= din[31:24];
end
endcase
end
end
// 读操作
always @(*) begin
case(DMType)
`dm_byte:dout = {{24{dmem[addr][7]}},dmem[addr][7:0]};
`dm_byte_unsigned:dout = {24'b0,dmem[addr][7:0]};
`dm_halfword:dout = {{16{dmem[addr+1][7]}},dmem[addr+1][7:0],dmem[addr][7:0]};
`dm_halfword_unsigned:dout = {16'b0,dmem[addr+1][7:0],dmem[addr][7:0]};
`dm_word:dout = {dmem[addr+3][7:0],dmem[addr+2][7:0],dmem[addr+1][7:0],dmem[addr][7:0]};
default:dout = {dmem[addr+3][7:0],dmem[addr+2][7:0],dmem[addr+1][7:0],dmem[addr][7:0]};
endcase
end
endmodule

Ctrl

module ctrl(
input [6:0] Op,
input [6:0] Funct7,
input [2:0] Funct3,
input Zero,
input ALU_C_sign, // ALU 结果的符号位 SLT(用于 blt/bge)
input ALU_Cout, // ALU 进位标志 SLTU(用于 bltu/bgeu)
output RegWrite,
output MemWrite,
output [3:0] EXTOp,
output [3:0] ALUOp,
output [1:0] NPCOp,
output ALUSrc,
output [2:0] DMType,
output [2:0] WDSel // MemtoReg
);
// R_type 10条
wire rtype = ~Op[6] & Op[5] & Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //0110011
wire i_add = rtype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0] & ~Funct7[5]; // add 0000000 000
wire i_sub = rtype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0] & Funct7[5]; // sub 0100000 000
wire i_sll = rtype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // sll 0000000 001
wire i_slt = rtype & ~Funct3[2] & Funct3[1] & ~Funct3[0]; // slt 0000000 010
wire i_sltu = rtype & ~Funct3[2] & Funct3[1] & Funct3[0]; // sltu 0000000 011
wire i_xor = rtype & Funct3[2] & ~Funct3[1] & ~Funct3[0]; // xor 0000000 100
wire i_srl = rtype & Funct3[2] & ~Funct3[1] & Funct3[0] & ~Funct7[5]; // srl 0000000 101
wire i_sra = rtype & Funct3[2] & ~Funct3[1] & Funct3[0] & Funct7[5]; // sra 0100000 101
wire i_or = rtype & Funct3[2] & Funct3[1] & ~Funct3[0]; // or 0000000 110
wire i_and = rtype & Funct3[2] & Funct3[1] & Funct3[0]; // and 0000000 111
// i_l type load 5条
wire itype_l = ~Op[6] & ~Op[5] & ~Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //0000011
wire i_lb = itype_l & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; //lb 000
wire i_lh = itype_l & ~Funct3[2] & ~Funct3[1] & Funct3[0]; //lh 001
wire i_lw = itype_l & ~Funct3[2] & Funct3[1] & ~Funct3[0]; //lw 010
wire i_lbu = itype_l & Funct3[2] & ~Funct3[1] & ~Funct3[0]; //lbu 100
wire i_lhu = itype_l & Funct3[2] & ~Funct3[1] & Funct3[0]; //lh 101
// i_r type 涉及Reg与imm运算,ALU with immediate 10条
wire itype_r = ~Op[6] & ~Op[5] & Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //0010011
wire i_addi = itype_r & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // addi 000 func3
wire i_slti = itype_r & ~Funct3[2] & Funct3[1] & ~Funct3[0]; // slti 010 func3
wire i_sltiu = itype_r & ~Funct3[2] & Funct3[1] & Funct3[0]; // sltiu 011 func3
wire i_xori = itype_r & Funct3[2] & ~Funct3[1] & ~Funct3[0]; // xori 100 func3
wire i_ori = itype_r & Funct3[2] & Funct3[1] & ~Funct3[0]; // ori 110 func3
wire i_andi = itype_r & Funct3[2] & Funct3[1] & Funct3[0]; // andi 111 func3
wire i_slli = itype_r & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // slli 001
wire i_srli = itype_r & Funct3[2] & ~Funct3[1] & Funct3[0] & ~Funct7[5]; // srli 101 0000000
wire i_srai = itype_r & Funct3[2] & ~Funct3[1] & Funct3[0] & Funct7[5]; // srai 101 0100000
wire i_jalr = Op[6] & Op[5] & ~Op[4] & ~Op[3] & Op[2] & Op[1] & Op[0]; //1100111
// s format store 3条
wire stype = ~Op[6] & Op[5] & ~Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0];//0100011
wire i_sb = stype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // sb 000
wire i_sh = stype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // sh 001
wire i_sw = stype & ~Funct3[2] & Funct3[1] & ~Funct3[0]; // sw 010
// B_type 6条
wire btype = Op[6] & Op[5] & ~Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //1100011
wire i_beq = btype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // beq 000
wire i_bne = btype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // bne 001
wire i_blt = btype & Funct3[2] & ~Funct3[1] & ~Funct3[0]; // blt 100
wire i_bge = btype & Funct3[2] & ~Funct3[1] & Funct3[0]; // bge 101
wire i_bltu = btype & Funct3[2] & Funct3[1] & ~Funct3[0]; // bltu 110
wire i_bgeu = btype & Funct3[2] & Funct3[1] & Funct3[0]; // bgeu 111
// U_type 2条
wire utype = ~Op[6] & Op[4] & ~Op[3] & Op[2] & Op[1] & Op[0]; //0X10111
wire i_lui = utype & Op[5]; // lui 0110111
wire i_auipc = utype & ~Op[5]; // auipc 0010111
// J_type 1条
wire jtype = Op[6] & Op[5] & ~Op[4] & Op[3] & Op[2] & Op[1] & Op[0]; //1101111
wire i_jal = jtype; // jal
assign RegWrite = rtype | itype_r | itype_l | i_jal | i_jalr; // register write (含跳转返回地址)
assign MemWrite = stype; // memory write
// `define EXT_CTRL_ITYPE_SHAMT 3'b000
// `define EXT_CTRL_ITYPE 3'b001
// `define EXT_CTRL_STYPE 3'b010
// `define EXT_CTRL_BTYPE 3'b011
// `define EXT_CTRL_UTYPE 3'b100
// `define EXT_CTRL_JTYPE 3'b101
assign EXTOp[0] = itype_l | itype_r | btype | jtype;
assign EXTOp[1] = stype | btype;
assign EXTOp[2] = utype | jtype;
localparam ALU_NOP = 4'b0000;
localparam ALU_ADD = 4'b0001;
localparam ALU_SUB = 4'b0010;
localparam ALU_AND = 4'b0011;
localparam ALU_OR = 4'b0100;
localparam ALU_XOR = 4'b0101;
localparam ALU_SLL = 4'b0110; // 左移
localparam ALU_SRL = 4'b0111; // 逻辑右移
localparam ALU_SRA = 4'b1000; // 算术右移
localparam ALU_SLT = 4'b1001; // 有符号比较
localparam ALU_SLTU = 4'b1010; // 无符号比较
localparam ALU_U = 4'b1011; // (用于 utype 指令 lui auipc 立即数<<12)
assign ALUOp =
// 加法相关:ADD 和 ADDI 及其衍生
((i_add | i_addi | itype_l | i_jalr | stype | i_auipc | i_jal) ? ALU_ADD :
// 减法相关:SUB 及分支比较
(i_sub | i_beq | i_bne | i_blt | i_bge | i_bltu | i_bgeu) ? ALU_SUB :
// 逻辑操作
(i_and | i_andi) ? ALU_AND :
(i_or | i_ori) ? ALU_OR :
(i_xor | i_xori) ? ALU_XOR :
// 移位操作
(i_sll | i_slli) ? ALU_SLL :
(i_srl | i_srli) ? ALU_SRL :
(i_sra | i_srai) ? ALU_SRA :
// 比较操作
(i_slt | i_slti) ? ALU_SLT :
(i_sltu | i_sltiu) ? ALU_SLTU :
// LUI 特殊操作
(i_lui | i_auipc) ? ALU_U :
// 默认
ALU_NOP);
// B_type 分支条件判断
// beq: Zero == 1
// bne: Zero == 0
// blt: 有符号比较,A < B 时 ALU_C_sign = 1
// bge: 有符号比较,A >= B 时 ALU_C_sign = 0
// bltu: 无符号比较,A < B 时 ALU_Cout = 1
// bgeu: 无符号比较,A >= B 时 ALU_Cout = 0
wire branch_taken =
(i_beq & Zero) | // beq: 相等时跳转
(i_bne & ~Zero) | // bne: 不相等时跳转
(i_blt & ALU_C_sign) | // blt: 有符号 < 时跳转
(i_bge & ~ALU_C_sign) | // bge: 有符号 >= 时跳转
(i_bltu & ALU_Cout) | // bltu: 无符号 < 时跳转
(i_bgeu & ~ALU_Cout); // bgeu: 无符号 >= 时跳转
// NPCOp: 00:PC+1 01:PC+immout 10:PC = RD1 + immout
// PC + 1 或 PC + offset 或 基址寻址 字寻址
assign NPCOp[0] = branch_taken | i_jal;
assign NPCOp[1] = i_jalr;
assign ALUSrc = itype_r | itype_l | stype; // ALU B is from instruction immediate
// assign mem2reg = wdsel
// WDSel_FromALU 2'b00 WDSel_FromMEM 2'b01
// WDSel 000: aluout 001: dm_dout 010: PC+1 011: immout<<12 100: PC + immout<<12
assign WDSel[0] = itype_l | i_lui;
assign WDSel[1] = i_jal | i_jalr | i_lui;
assign WDSel[2] = i_auipc;
// dm_word 3'b000
// dm_halfword 3'b001
// dm_halfword_unsigned 3'b010
// dm_byte 3'b011
// dm_byte_unsigned 3'b100
assign DMType[2] = i_lbu; // bu
assign DMType[1] = i_lb | i_sb | i_lhu; // hu和b
assign DMType[0] = i_lh | i_sh | i_lb | i_sb; // h和b
endmodule

EXT

// 立即数扩展
`define EXT_CTRL_ITYPE_SHAMT 3'b000
`define EXT_CTRL_ITYPE 3'b001
`define EXT_CTRL_STYPE 3'b010
`define EXT_CTRL_BTYPE 3'b011
`define EXT_CTRL_UTYPE 3'b100
`define EXT_CTRL_JTYPE 3'b101
module EXT(
input [4:0] iimm_shamt,
input [11:0] iimm,
input [11:0] simm,
input [11:0] bimm,
input [19:0] uimm,
input [19:0] jimm,
input [3:0] EXTOp,
output reg [31:0] immout
);
always @(*) begin
case (EXTOp)
`EXT_CTRL_ITYPE_SHAMT: immout <= {27'b0,iimm_shamt[4:0]}; // I-type 立即数 (用于 slli/srli/srai)
`EXT_CTRL_ITYPE: immout <= {{20{iimm[11]}}, iimm[11:0]}; // I-type 立即数 (用于 addi, lw, jalr 等)
`EXT_CTRL_STYPE: immout <= {{20{simm[11]}}, simm[11:0]}; // S-type 立即数 (用于 sw 等)
`EXT_CTRL_BTYPE: immout<= {{19{bimm[11]}},bimm[11:0],1'b0}; // B-type 立即数 (用于 beq 等)
`EXT_CTRL_UTYPE: immout <= {uimm[19:0], 12'b0}; // U-type 立即数 (用于 lui, auipc)
`EXT_CTRL_JTYPE: immout<= {{11{jimm[19]}},jimm[19:0],1'b0}; // J-type 立即数 (用于 jal)
default: immout <= 32'b0;
endcase
end
endmodule

tb文件代码#

tb_SCPU_TOP

`timescale 1ns/1ps
module tb_SCPU_TOP;
reg clk;
reg rstn;
reg [15:0] sw_i;
wire [7:0] disp_seg_o, disp_an_o;
// 实例化顶层CPU
SCPU_TOP uut (
.clk(clk),
.rstn(rstn),
.sw_i(sw_i),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
// 时钟生成:100MHz
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 初始化复位和开关
initial begin
rstn = 0;
sw_i = 16'b0000_0000_0000_0000; // sw_i[1]=0 允许写入
#50 rstn = 1;
end
// 指令执行跟踪器
integer i;
reg [4:0] last_rd;
initial begin
@(posedge rstn);
repeat (40) begin
@(posedge clk);
$display("t=%0t instr=%h rom_addr=%d", $time, uut.instr, uut.rom_addr);
$display("→ rd=%0d rs1=%0d rs2=%0d Op=%b Funct3=%b Funct7=%b",
uut.rd, uut.rs1, uut.rs2, uut.Op, uut.Funct3, uut.Funct7);
$display("→ RegWrite=%b WDSel=%b ALUSrc=%b EXTOp=%b NPCOp=%b",
uut.RegWrite, uut.WDSel, uut.ALUSrc, uut.EXTOp, uut.NPCOp);
$display("→ RD1=%h RD2=%h immout=%h", uut.RD1, uut.RD2, uut.immout);
$display("→ A=%h B=%h → ALUout=%h", uut.A, uut.B, uut.aluout);
$display("→ WD=%h → rf[%0d] = %h", uut.WD, last_rd, uut.U_RF.rf[last_rd]);
$display("→ MemWrite=%b dm_addr=%h dm_din=%h dm_dout=%h",
uut.MemWrite, uut.dm_addr, uut.dm_din, uut.dm_dout);
$display("--------------------------------------------------");
last_rd <= uut.rd;
end
$stop;
end
endmodule
// ROM模块:dist_mem_im,含16条指令,其余填充nop
module dist_mem_im (
input [5:0] a,
output reg [31:0] spo
);
always @(*) begin
case (a)
6'd0 : spo = 32'h000000b3; // add x1, x0, x0
6'd1 : spo = 32'h00108093; // addi x1, x1, 1
6'd2 : spo = 32'h00000133; // add x2, x0, x0
6'd3 : spo = 32'h00210113; // addi x2, x2, 2
6'd4 : spo = 32'h000001b3; // add x3, x0, x0
6'd5 : spo = 32'h00318193; // addi x3, x3, 3
6'd6 : spo = 32'h001020a3; // sw x1, 0(x0)
6'd7 : spo = 32'h00202123; // sw x2, 4(x0)
6'd8 : spo = 32'h003021a3; // sw x3, 8(x0)
6'd9 : spo = 32'h00102203; // lw x4, 0(x0)
6'd10: spo = 32'h00202283; // lw x5, 4(x0)
6'd11: spo = 32'h00302303; // lw x6, 8(x0)
6'd12: spo = 32'h00000013; // nop
6'd13: spo = 32'h00000013; // nop
6'd14: spo = 32'h00000013; // nop
6'd15: spo = 32'h00000013; // nop
default: spo = 32'h00000013; // 其余填充nop
endcase
end
endmodule

现在的 ctrl 模块已经覆盖了 37 条 RISC‑V 基础指令(R/I/S/B/U/J 六大类),每条指令都有对应的 wire 信号。要让这些指令在仿真中真正跑起来,你需要做两件事:

✅ 1. 在 ROM 中添加对应的指令编码#

之前我们只放了 16 条基本指令(add、addi、lw、sw 等)。如果要覆盖到 ctrl 模块的 37 条,你需要在 dist_mem_im 里把这些指令的机器码写进去。

例如:

module dist_mem_im (
input [5:0] a,
output reg [31:0] spo
);
always @(*) begin
case (a)
// R-type
6'd0 : spo = 32'h000000b3; // add x1, x0, x0
6'd1 : spo = 32'h40000033; // sub x0, x0, x0
6'd2 : spo = 32'h00109133; // sll x2, x1, x1
6'd3 : spo = 32'h0020a133; // slt x2, x1, x2
6'd4 : spo = 32'h0030b133; // sltu x2, x1, x3
6'd5 : spo = 32'h0040c133; // xor x2, x1, x4
6'd6 : spo = 32'h0050d133; // srl x2, x1, x5
6'd7 : spo = 32'h4050d133; // sra x2, x1, x5
6'd8 : spo = 32'h0060e133; // or x2, x1, x6
6'd9 : spo = 32'h0070f133; // and x2, x1, x7
// I-type (load)
6'd10: spo = 32'h00012083; // lb x1,0(x2)
6'd11: spo = 32'h00112103; // lh x2,1(x2)
6'd12: spo = 32'h00212183; // lw x3,2(x2)
6'd13: spo = 32'h00412203; // lbu x4,4(x2)
6'd14: spo = 32'h00512283; // lhu x5,5(x2)
// I-type (ALU imm)
6'd15: spo = 32'h00108093; // addi x1,x1,1
6'd16: spo = 32'h00209093; // slti x1,x1,2
6'd17: spo = 32'h0030a093; // sltiu x1,x1,3
6'd18: spo = 32'h0040c093; // xori x1,x1,4
6'd19: spo = 32'h0050e093; // ori x1,x1,5
6'd20: spo = 32'h0060f093; // andi x1,x1,6
6'd21: spo = 32'h00109013; // slli x0,x1,1
6'd22: spo = 32'h00209013; // srli x0,x1,2
6'd23: spo = 32'h40209013; // srai x0,x1,2
6'd24: spo = 32'h00008067; // jalr x0,0(x1)
// S-type (store)
6'd25: spo = 32'h00102023; // sb x1,0(x0)
6'd26: spo = 32'h00202123; // sh x2,4(x0)
6'd27: spo = 32'h00302223; // sw x3,8(x0)
// B-type (branch)
6'd28: spo = 32'h00050063; // beq x10,x0,0
6'd29: spo = 32'h00151063; // bne x10,x1,0
6'd30: spo = 32'h00252063; // blt x10,x2,0
6'd31: spo = 32'h00353063; // bge x10,x3,0
6'd32: spo = 32'h00454063; // bltu x10,x4,0
6'd33: spo = 32'h00555063; // bgeu x10,x5,0
// U-type
6'd34: spo = 32'h000002b7; // lui x5,0x0
6'd35: spo = 32'h00000297; // auipc x5,0x0
// J-type
6'd36: spo = 32'h000000ef; // jal x1,0
default: spo = 32'h00000013; // nop
endcase
end
endmodule

注:这些机器码是示例,具体编码要根据你希望测试的寄存器和立即数来写。关键是覆盖到 ctrl 模块里定义的所有 37 条指令。

✅ 2. 在 testbench 中打印完整信息#

保持之前的 $display 打印逻辑不变,这样每条指令都会显示:

  • 指令编码和地址
  • 解码字段
  • 控制信号
  • ALU 输入输出
  • WD 和寄存器写入结果
  • DM 访问情况

9.纠错篇#

第7、8节卡住了很久,改动后成功,最后整理出改动点如下:

  1. RF的clk输出误写成clk,应该为Clk_CPU
  2. RF的写入操作条件加上 A3 != 5’b0 即不写x0寄存器
  3. Decode的代码左边赋值的位宽要明确为好
  4. WD选择前赋上默认值
  5. 尽量用非阻塞赋值 <=(比如dm循环显示)
// 阻塞赋值
// 循环显示Data Memory内容
parameter DM_DATA_NUM = 16;
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
dmem_addr = 7'b0;
dmem_data = 32'hFFFFFFFF;
end
else if(sw_i[11] == 1'b1) begin
dmem_addr = dmem_addr + 1'b1;
dmem_data = U_DM.dmem[dmem_addr][7:0]; // 这里比如初始addr为0,用阻塞赋值就会用到0+1的地址,0地址就没用到;其次,当addr=15,就会取到地址15+1的处的数据,但16在dm.v属于越界
if(dmem_addr == DM_DATA_NUM) begin
dmem_addr = 7'd0;
dmem_data = 32'hFFFFFFFF;
end
end
end
// 非阻塞赋值
// 循环显示Data Memory内容
parameter DM_DATA_NUM = 16;
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
dmem_addr <= 7'b0;
dmem_data <= 32'hFFFFFFFF;
end
else if(sw_i[11] == 1'b1) begin
dmem_addr <= dmem_addr + 1'b1;
dmem_data <= U_DM.dmem[dmem_addr][7:0];
if(dmem_addr == DM_DATA_NUM) begin
dmem_addr <= 7'd0;
dmem_data <= 32'hFFFFFFFF;
end
end
end

10.总结#

项目越大,代码越多,越要用tb仿真,真的很有效,可以让AI(借助Copilot)根据当前主程序写个tb文件,可以要求在tb文件自己新建一个ROM模块放指令,仿真会优先运行tb里的ROM模块,然后每周期输出值和信号,很好debug。

关于使用什么编辑器来写verilog
最好还是用Vivado,因为可以好写仿真文件并仿真输出运行信息;
日常外面需要轻量级写代码就用VScode

11.期末准备#

容易出错的地方#

  • PC,rom_addr的复位时机要同时(要么都异步复位(写在敏感列表),要么都同步复位(不写在敏感列表)),否则复位会出现部分复位(复位后: A->E->F->…)
  • Ctrl中指令解析错误,要严格检查解析Opcode,Funct是否正确;以及生成信号是否有遗漏
  • WD的赋值采取组合逻辑的呈现:always@(*)(reg) 还是 assign(wire)
    • 前者可能要使用到块内初始化,这引入了多重赋值和时序竞争的可能性
    • 后者直接定义了 WD 与其右侧信号之间的逻辑关系,没有内部的初始化或覆盖步骤,而且连续赋值消除了 always 块内多重赋值带来的所有时序竞争或仿真器对非阻塞赋值的特殊处理,从而确保在任何观察点,WD 都反映了 MUX 逻辑的正确输出
    • 但一般简单情形下都适用,这里也是
  • 一般主模块下首先默认是用wire,wire具有快的性质
  • 模块例化出口端口要用wire连接
  • 指令是否有效,观察alu很容易判断:如果运算结果不应该为0,但为0(因为设置了ALUOp的case:default:C为0),则说明Ctrl模块下该指令的解析出错或生成ALUOp信号遗漏了该指令

代码#

下面代码忽略U型指令:lui, auipc以及除了加法的其他运算(or,ori,slt等)

代码亮点#

  • alu module中把branch指令的各种标志用Zero统一起来
  • 使用 PC和NPC module独立处理PC

核心代码#

SCPU_TOP

`timescale 1ns / 1ps
// 37条指令
module SCPU_TOP(
input clk,
input rstn, // 低电平有效,按下后为0
input [15:0] sw_i,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
reg [31:0] clkdiv;
wire Clk_CPU;
always @(posedge clk or negedge rstn) begin
if(!rstn) clkdiv <= 0;
else clkdiv <= clkdiv + 1'b1;
end
assign Clk_CPU = (sw_i[15])?clkdiv[27]:clkdiv[25]; // 2^27 和 2^25分频
reg [63:0] display_data;
reg [5:0] led_data_addr;
reg [63:0] led_disp_data;
parameter LED_DATA_NUM = 19;
reg [63:0] LED_DATA[18:0];
initial begin
LED_DATA[0] = 64'hC6F6F6F0C6F6F6F0;
LED_DATA[1] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[2] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[3] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[4] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[5] = 64'hFFFFA3FFFFFFA3FF;
LED_DATA[6] = 64'hFFFF9CFFFFFF9CFF;
LED_DATA[7] = 64'hFF9EBCFFFF9EBCFF;
LED_DATA[8] = 64'hFF9CFFFFFF9CFFFF;
LED_DATA[9] = 64'hFFC0FFFFFFC0FFFF;
LED_DATA[10] = 64'hFFA3FFFFFFA3FFFF;
LED_DATA[11] = 64'hFFA7B3FFFFA7B3FF;
LED_DATA[12] = 64'hFFC6F0FFFFC6F0FF;
LED_DATA[13] = 64'hF9F6F6CFF9F6F6CF;
LED_DATA[14] = 64'h9EBEBEBC9EBEBEBC;
LED_DATA[15] = 64'h2737373327373733;
LED_DATA[16] = 64'h505454EC505454EC;
LED_DATA[17] = 64'h744454F8744454F8;
LED_DATA[18] = 64'h0062080000620800;
end
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
led_data_addr <= 6'd0;
led_disp_data <= 64'b1;
end
else if(sw_i[0] == 1'b1) begin
if(led_data_addr == LED_DATA_NUM) begin
led_data_addr <= 6'd0;
led_disp_data <= LED_DATA[0];
end
else begin
led_disp_data <= LED_DATA[led_data_addr];
led_data_addr <= led_data_addr + 1'b1;
end
end
else led_data_addr <= led_data_addr;
end
wire [31:0] instr;
reg [31:0] reg_data;
reg [31:0] alu_disp_data;
reg [31:0] dmem_data;
// PC
wire [31:0] PC;
wire [31:0] NPC;
wire PCwr = ~sw_i[1];
wire [2:0] NPCOp;
// ROM
reg [5:0] rom_addr;
// RF
wire RegWrite;
wire [31:0] WD;
wire [31:0] RD1,RD2;
reg [5:0] reg_addr;
// ALU
wire [31:0] A,B;
wire [4:0] ALUOp;
wire [31:0] aluout;
wire Zero;
reg [2:0] alu_addr;
// DM
wire MemWrite;
wire [5:0] dm_addr;
wire [31:0] dm_din;
wire [2:0] DMType;
wire [31:0] dm_dout;
reg [6:0] dmem_addr;
// Ctrl
wire [5:0] EXTOp;
wire [1:0] WDSel;
wire ALUSrc;
// EXT
wire [31:0] immout;
always @(sw_i) begin
if(sw_i[0] == 0) begin
case(sw_i[14:11])
4'b1000 : display_data <= instr;
4'b0100 : display_data <= reg_data;
4'b0010 : display_data <= alu_disp_data;
4'b0001 : display_data <= dmem_data;
default : display_data <= instr;
endcase
end
else display_data = led_disp_data;
end
// 例化显示模块
seg7x16 u_seg7x16(
.clk(clk),
.rstn(rstn),
.disp_mode(sw_i[0]),
.i_data(display_data),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
///////////////////////////////////////////////////////
// 例化PC_Unit module
PC_Unit U_PC(.clk(Clk_CPU),.rst(~rstn),.NPC(NPC),.PCwr(PCwr),.PC(PC));
// 例化NPC_Unit module
NPC_Unit U_NPC(.PC(PC),.NPCOp(NPCOp),.IMM(immout),.aluout(aluout),.NPC(NPC));
// 例化ROM模块
dist_mem_im U_IM(
.a(rom_addr),
.spo(instr)
);
// 每个CLK_CPU获得一个新指令
// 关注上升沿:上升沿PC更新为NPC,所以rom_addr也应更新为NPC以取下条指令
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) rom_addr <= 6'b0;
else if(sw_i[1] == 1'b0) rom_addr <= NPC >> 2; // rom以1为单位,指令以4为单位,所以除以4以映射rom
else rom_addr <= rom_addr;
end
//Decode
wire [6:0] Op = instr[6:0];
wire [6:0] Funct7 = instr[31:25];
wire [2:0] Funct3 = instr[14:12];
wire [4:0] rs1 = instr[19:15];
wire [4:0] rs2 = instr[24:20];
wire [4:0] rd = instr[11:7];
wire [4:0] iimm_shamt= instr[24:20];
wire [11:0] iimm = instr[31:20];
wire [11:0] simm = {instr[31:25],instr[11:7]};
wire [11:0] bimm = {instr[31],instr[7],instr[30:25],instr[11:8]};
wire [19:0] uimm = instr[31:12];
wire [19:0] jimm = {instr[31],instr[19:12],instr[20],instr[30:21]};
// 例化ctrl模块
ctrl u_ctrl(
.Op(Op),
.Funct7(Funct7),
.Funct3(Funct3),
.Zero(Zero),
.RegWrite(RegWrite),
.MemWrite(MemWrite),
.EXTOp(EXTOp),
.ALUOp(ALUOp),
.ALUSrc(ALUSrc),
.NPCOp(NPCOp),
.DMType(DMType),
.WDSel(WDSel)
);
// 例化EXT模块
EXT U_EXT(
.iimm_shamt(iimm_shamt),
.iimm(iimm),
.simm(simm),
.bimm(bimm),
.uimm(uimm),
.jimm(jimm),
.EXTOp(EXTOp),
.immout(immout)
);
// 赋值
// ALU_DM->RF RF_WD
// WDSel FromALU 2'b00
// WDSel FromMEM 2'b01
// WDSel FromPC 2'b10
//always @(*) begin
// WD <= 32'h0;
// case(WDSel)
// 00: WD <= aluout;
// 01: WD <= dm_dout;
// 10: WD <= PC + 4;
// default: WD <= 32'h0;
// endcase
//end
assign WD = (WDSel == 2'b10) ? (PC + 4) :
(WDSel == 2'b01) ? dm_dout :
(WDSel == 2'b00) ? aluout : 32'h00000000;
// RF->ALU ALU_A_B
assign A = RD1;
assign B = (ALUSrc == 1'b0)?RD2:immout;
// ALU->DM DM_addr_din
assign dm_addr = aluout;
assign dm_din = RD2;
// 例化RF模块
RF U_RF(
.clk(Clk_CPU),
.rstn(rstn),
.RFWr(RegWrite),
.sw_i(sw_i),
.A1(rs1),
.A2(rs2),
.A3(rd),
.WD(WD),
.RD1(RD1),
.RD2(RD2)
);
// 循环显示32个寄存器的内容
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
reg_addr <= 5'b0;
reg_data <= 32'b0;
end
else if(sw_i[13] == 1'b1)begin
reg_addr <= reg_addr + 1'b1;
reg_data <= U_RF.rf[reg_addr];
end
end
/////////////////////////////////////////////////////
// 例化alu模块
alu U_alu(
.A(A),
.B(B),
.ALUOp(ALUOp),
.C(aluout),
.Zero(Zero)
);
// 循环显示A B Zero四个值
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) alu_addr <= 3'b0;
else alu_addr <= alu_addr + 1'b1;
case(alu_addr) // 下面用普通reg接收signed数据,只会数据位复制
3'b001:alu_disp_data <= U_alu.A;
3'b010:alu_disp_data <= U_alu.B;
3'b011:alu_disp_data <= U_alu.C;
3'b100:alu_disp_data <= U_alu.Zero;
default:alu_disp_data <= 32'hFFFFFFFF;
endcase
end
//////////////////////////////////////////////////////
// 例化dm模块
dm U_DM(
.clk(Clk_CPU),
.DMWr(MemWrite),
.addr(dm_addr),
.din(dm_din),
.DMType(DMType),
.dout(dm_dout)
);
// 循环显示Data Memory内容
parameter DM_DATA_NUM = 16;
always @(posedge Clk_CPU or negedge rstn) begin
if(!rstn) begin
dmem_addr <= 7'b0;
dmem_data <= 32'hFFFFFFFF;
end
else if(sw_i[11] == 1'b1) begin
dmem_addr <= dmem_addr + 1'b1;
dmem_data <= U_DM.dmem[dmem_addr][7:0];
if(dmem_addr == DM_DATA_NUM) begin
dmem_addr <= 7'd0;
dmem_data <= 32'hFFFFFFFF;
end
end
end
endmodule

PC

module PC_Unit(
input clk,
input rst,
input [31:0] NPC,
input PCwr,
output reg [31:0] PC
);
// 由于主模块下各rstn复位都是异步复位,所以关于rstn的同时集体复位,这里应该采取异步复位,所以敏感列表里应有rst
// 否则只有在posedge clk下rst为1才复位,则为同步复位
always @(posedge clk or posedge rst) begin
if(rst) PC <= 32'b0;
else if(PCwr) PC <= NPC;
end
endmodule

NPC

`define NPC_PLUS4 3'b000
`define NPC_BRANCH 3'b001
`define NPC_JUMP 3'b010
`define NPC_JALR 3'b100
module NPC_Unit(
input [31:0] PC,
input [2:0] NPCOp,
input [31:0] IMM,
input [31:0] aluout,
output reg [31:0] NPC
);
always @(*) begin
case(NPCOp)
`NPC_PLUS4: NPC = PC + 4; // default
`NPC_BRANCH: NPC = PC + IMM; // branch
`NPC_JUMP: NPC = PC + IMM; // jal
`NPC_JALR: NPC = aluout; // jalr
default: NPC = PC + 4;
endcase
end
endmodule

ctrl

module ctrl(
input [6:0] Op,
input [6:0] Funct7,
input [2:0] Funct3,
input Zero,
output RegWrite,
output MemWrite,
output [5:0] EXTOp,
output [4:0] ALUOp,
output [2:0] NPCOp,
output ALUSrc,
output [2:0] DMType,
output [1:0] WDSel // MemtoReg
);
// R_type 10条
wire rtype = ~Op[6] & Op[5] & Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //0110011
wire i_add = rtype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0] & ~Funct7[5]; // add 0000000 000
wire i_sub = rtype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0] & Funct7[5]; // sub 0100000 000
wire i_sll = rtype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // sll 0000000 001
wire i_slt = rtype & ~Funct3[2] & Funct3[1] & ~Funct3[0]; // slt 0000000 010
wire i_sltu = rtype & ~Funct3[2] & Funct3[1] & Funct3[0]; // sltu 0000000 011
wire i_xor = rtype & Funct3[2] & ~Funct3[1] & ~Funct3[0]; // xor 0000000 100
wire i_srl = rtype & Funct3[2] & ~Funct3[1] & Funct3[0] & ~Funct7[5]; // srl 0000000 101
wire i_sra = rtype & Funct3[2] & ~Funct3[1] & Funct3[0] & Funct7[5]; // sra 0100000 101
wire i_or = rtype & Funct3[2] & Funct3[1] & ~Funct3[0]; // or 0000000 110
wire i_and = rtype & Funct3[2] & Funct3[1] & Funct3[0]; // and 0000000 111
// i_l type load 5条
wire itype_l = ~Op[6] & ~Op[5] & ~Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //0000011
wire i_lb = itype_l & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; //lb 000
wire i_lh = itype_l & ~Funct3[2] & ~Funct3[1] & Funct3[0]; //lh 001
wire i_lw = itype_l & ~Funct3[2] & Funct3[1] & ~Funct3[0]; //lw 010
wire i_lbu = itype_l & Funct3[2] & ~Funct3[1] & ~Funct3[0]; //lbu 100
wire i_lhu = itype_l & Funct3[2] & ~Funct3[1] & Funct3[0]; //lh 101
// i_r type 涉及Reg与imm运算,ALU with immediate 10条
wire itype_r = ~Op[6] & ~Op[5] & Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //0010011
wire i_addi = itype_r & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // addi 000 func3
wire i_slti = itype_r & ~Funct3[2] & Funct3[1] & ~Funct3[0]; // slti 010 func3
wire i_sltiu = itype_r & ~Funct3[2] & Funct3[1] & Funct3[0]; // sltiu 011 func3
wire i_slli = itype_r & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // slli 001
wire i_srli = itype_r & Funct3[2] & ~Funct3[1] & Funct3[0] & ~Funct7[5]; // srli 101 0000000
wire i_srai = itype_r & Funct3[2] & ~Funct3[1] & Funct3[0] & Funct7[5]; // srai 101 0100000
wire itype_shamt = i_slli | i_srli | i_srai;
// jalr
wire i_jalr = Op[6] & Op[5] & ~Op[4] & ~Op[3] & Op[2] & Op[1] & Op[0]; //1100111
// s format store 3条
wire stype = ~Op[6] & Op[5] & ~Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0];//0100011
wire i_sb = stype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // sb 000
wire i_sh = stype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // sh 001
wire i_sw = stype & ~Funct3[2] & Funct3[1] & ~Funct3[0]; // sw 010
// B_type 6条
wire btype = Op[6] & Op[5] & ~Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //1100011
wire i_beq = btype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // beq 000
wire i_bne = btype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // bne 001
wire i_blt = btype & Funct3[2] & ~Funct3[1] & ~Funct3[0]; // blt 100
wire i_bge = btype & Funct3[2] & ~Funct3[1] & Funct3[0]; // bge 101
wire i_bltu = btype & Funct3[2] & Funct3[1] & ~Funct3[0]; // bltu 110
wire i_bgeu = btype & Funct3[2] & Funct3[1] & Funct3[0]; // bgeu 111
// J_type 1条
wire jtype = Op[6] & Op[5] & ~Op[4] & Op[3] & Op[2] & Op[1] & Op[0]; //1101111
wire i_jal = jtype; // jal
// U_type 2条
wire utype = ~Op[6] & Op[4] & ~Op[3] & Op[2] & Op[1] & Op[0]; //0X10111
wire i_lui = utype & Op[5]; // lui 0110111
wire i_auipc = utype & ~Op[5]; // auipc 0010111
assign RegWrite = rtype | itype_r | itype_l | i_jal | i_jalr; // register write (含跳转返回地址)
assign MemWrite = stype; // memory write
//`define EXT_CTRL_ITYPE_SHAMT 6'b100000
//`define EXT_CTRL_ITYPE 6'b010000
//`define EXT_CTRL_STYPE 6'b001000
//`define EXT_CTRL_BTYPE 6'b000100
//`define EXT_CTRL_UTYPE 6'b000010
//`define EXT_CTRL_JTYPE 6'b000001
assign EXTOp[5] = itype_shamt;
assign EXTOp[4] = itype_l | (itype_r & ~itype_shamt) | i_jalr;
assign EXTOp[3] = stype;
assign EXTOp[2] = btype;
assign EXTOp[1] = i_lui | i_auipc;
assign EXTOp[0] = i_jal;
//`define ALUOp_nop 5'b00000
//`define ALUOp_sll 5'b00001
//`define ALUOp_srl 5'b00010
//`define ALUOp_sra 5'b00101
//`define ALUOp_add 5'b00011
//`define ALUOp_beq 5'b00100
//`define ALUOp_bne 5'b01000
//`define ALUOp_blt 5'b01100
//`define ALUOp_bge 5'b10000
//`define ALUOp_bltu 5'b10100
//`define ALUOp_bgeu 5'b11000
assign ALUOp[0] = i_add | i_addi | stype | itype_l | i_jalr | i_sll | i_sra | i_slli | i_srai;
assign ALUOp[1] = i_add | i_addi | stype | itype_l | i_jalr | i_srl | i_srli;
assign ALUOp[2] = i_beq | i_blt | i_bltu | i_sra | i_srai;
assign ALUOp[3] = i_bne | i_blt | i_bgeu;
assign ALUOp[4] = i_bge | i_bltu | i_bgeu;
//`define NPC_PLUS4 3'b000
//`define NPC_BRANCH 3'b001
//`define NPC_JUMP 3'b010
//`define NPC_JALR 3'b100
assign NPCOp[0] = btype & Zero;
assign NPCOp[1] = i_jal;
assign NPCOp[2] = i_jalr;
assign ALUSrc = itype_r | itype_l | stype | i_jal | i_jalr; // ALU B is from instruction immediate
// ALU_DM->RF RF_WD
// WDSel FromALU 2'b00
// WDSel FromMEM 2'b01
// WDSel FromPC 2'b10
assign WDSel[0] = itype_l;
assign WDSel[1] = i_jal | i_jalr;
// dm_word 3'b000
// dm_halfword 3'b001
// dm_halfword_unsigned 3'b010
// dm_byte 3'b011
// dm_byte_unsigned 3'b100
assign DMType[2] = i_lbu; // bu
assign DMType[1] = i_lb | i_sb | i_lhu; // hu和b
assign DMType[0] = i_lh | i_sh | i_lb | i_sb; // h和b
endmodule

alu

`define ALUOp_nop 5'b00000
`define ALUOp_sll 5'b00001
`define ALUOp_srl 5'b00010
`define ALUOp_sra 5'b00101
`define ALUOp_add 5'b00011
`define ALUOp_beq 5'b00100
`define ALUOp_bne 5'b01000
`define ALUOp_blt 5'b01100
`define ALUOp_bge 5'b10000
`define ALUOp_bltu 5'b10100
`define ALUOp_bgeu 5'b11000
module alu(
input signed [31:0] A,B,
input [4:0] ALUOp,
output reg signed [31:0] C,
output reg Zero
);
always @(*) begin
C = 32'b0;
case(ALUOp)
`ALUOp_sll: C = A << B[4:0];
`ALUOp_srl: C = A >> B[4:0];
`ALUOp_sra: C = A >>> B[4:0];
`ALUOp_add: C = A + B;
`ALUOp_beq: C = {31'b0,(A!=B)};
`ALUOp_bne: C = {31'b0,(A==B)};
`ALUOp_blt: C = {31'b0,(A>=B)};
`ALUOp_bge: C = {31'b0,(A<B)};
`ALUOp_bltu: C = {31'b0,($unsigned(A)>=$unsigned(B))};
`ALUOp_bgeu: C = {31'b0,($unsigned(A)<$unsigned(B))};
default:C = 32'b0;
endcase
Zero = (C == 0)?1:0; // 阻塞赋值,需等C算出来,而不是同步
end
endmodule

EXT

// 立即数扩展
`define EXT_CTRL_ITYPE_SHAMT 6'b100000
`define EXT_CTRL_ITYPE 6'b010000
`define EXT_CTRL_STYPE 6'b001000
`define EXT_CTRL_BTYPE 6'b000100
`define EXT_CTRL_UTYPE 6'b000010
`define EXT_CTRL_JTYPE 6'b000001
module EXT(
input [4:0] iimm_shamt,
input [11:0] iimm,
input [11:0] simm,
input [11:0] bimm,
input [19:0] uimm,
input [19:0] jimm,
input [5:0] EXTOp,
output reg [31:0] immout
);
always @(*) begin
case (EXTOp)
`EXT_CTRL_ITYPE_SHAMT: immout <= {27'b0,iimm_shamt[4:0]}; // I-type 立即数 (用于 slli/srli/srai)
`EXT_CTRL_ITYPE: immout <= {{20{iimm[11]}}, iimm[11:0]}; // I-type 立即数 (用于 addi, lw, jalr 等)
`EXT_CTRL_STYPE: immout <= {{20{simm[11]}}, simm[11:0]}; // S-type 立即数 (用于 sw 等)
`EXT_CTRL_BTYPE: immout<= {{19{bimm[11]}},bimm[11:0],1'b0}; // B-type 立即数 (用于 beq 等)
`EXT_CTRL_UTYPE: immout <= {uimm[19:0], 12'b0}; // U-type 立即数 (用于 lui, auipc)
`EXT_CTRL_JTYPE: immout<= {{11{jimm[19]}},jimm[19:0],1'b0}; // J-type 立即数 (用于 jal)
default: immout <= 32'b0;
endcase
end
endmodule

次要代码#

seg7x16

module seg7x16(
input clk,
input rstn,
input disp_mode,
input [63:0] i_data,
output [7:0] disp_seg_o,
output [7:0] disp_an_o
);
// 2^15分频
reg [14:0] cnt;
wire seg7_clk;
always @(posedge clk or negedge rstn) begin
if(!rstn) cnt <= 0;
else cnt <= cnt + 1'b1;
end
assign seg7_clk = cnt[14];
// 8选1
reg [2:0] seg7_addr;
always @(posedge seg7_clk or negedge rstn) begin
if(!rstn) seg7_addr <= 0;
else seg7_addr <= seg7_addr + 1'b1;
end
// 选中的数码管使能信号
reg [7:0] o_sel_r;
always @(*) begin
case(seg7_addr)
7 : o_sel_r = 8'b01111111;
6 : o_sel_r = 8'b10111111;
5 : o_sel_r = 8'b11011111;
4 : o_sel_r = 8'b11101111;
3 : o_sel_r = 8'b11110111;
2 : o_sel_r = 8'b11111011;
1 : o_sel_r = 8'b11111101;
0 : o_sel_r = 8'b11111110;
endcase
end
//
reg [63:0] i_data_store;
always @(posedge clk or negedge rstn) begin
if(!rstn) i_data_store <= 0;
else i_data_store <= i_data;
end
reg [7:0] seg_data_r;
always @(*) begin
if(disp_mode == 1'b0) begin // 字符显示模式
case(seg7_addr)
0 : seg_data_r = i_data_store[3:0];
1 : seg_data_r = i_data_store[7:4];
2 : seg_data_r = i_data_store[11:8];
3 : seg_data_r = i_data_store[15:12];
4 : seg_data_r = i_data_store[19:16];
5 : seg_data_r = i_data_store[23:20];
6 : seg_data_r = i_data_store[27:24];
7 : seg_data_r = i_data_store[31:28];
default: seg_data_r = 8'hFF; // 默认全空,防止latch
endcase
end
else begin // 图形显示模式
case(seg7_addr)
0 : seg_data_r = i_data_store[7:0];
1 : seg_data_r = i_data_store[15:8];
2 : seg_data_r = i_data_store[23:16];
3 : seg_data_r = i_data_store[31:24];
4 : seg_data_r = i_data_store[39:32];
5 : seg_data_r = i_data_store[47:40];
6 : seg_data_r = i_data_store[55:48];
7 : seg_data_r = i_data_store[63:56];
default: seg_data_r = 8'hFF; // 默认全空,防止latch
endcase
end
end
reg [7:0] o_seg_r;
always @(posedge clk or negedge rstn) begin
if(!rstn) o_seg_r <= 8'hFF;
else if(disp_mode == 1'b0) begin // 字符模式
case(seg_data_r)
4'h0 : o_seg_r <= 8'hC0;
4'h1 : o_seg_r <= 8'hF9;
4'h2 : o_seg_r <= 8'hA4;
4'h3 : o_seg_r <= 8'hB0;
4'h4 : o_seg_r <= 8'h99;
4'h5 : o_seg_r <= 8'h92;
4'h6 : o_seg_r <= 8'h82;
4'h7 : o_seg_r <= 8'hF8;
4'h8 : o_seg_r <= 8'h80;
4'h9 : o_seg_r <= 8'h90;
4'hA : o_seg_r <= 8'h88;
4'hB : o_seg_r <= 8'h83;
4'hC : o_seg_r <= 8'hC6;
4'hD : o_seg_r <= 8'hA1;
4'hE : o_seg_r <= 8'h86;
4'hF : o_seg_r <= 8'h8E;
default : o_seg_r <= 8'hFF;
endcase
end
else o_seg_r <= seg_data_r;
end
assign disp_an_o = o_sel_r;
assign disp_seg_o = o_seg_r;
endmodule

RF

module RF(
input clk,
input rstn,
input RFWr,
input [15:0] sw_i,
input [4:0] A1,A2,A3,
input [31:0] WD,
output [31:0] RD1,RD2
);
reg [31:0] rf[31:0];
integer i;
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
for(i=0;i<32;i=i+1) rf[i] <= i;
end
else if(RFWr && (!sw_i[1]) && (A3 != 5'd0)) rf[A3] <= WD; // 正常模式下且RegWrite有效,写rd
end
// 读rs1和rs2
assign RD1 = (A1 != 0)?rf[A1]:0;
assign RD2 = (A2 != 0)?rf[A2]:0;
endmodule

dm

`define dm_word 3'b000
`define dm_halfword 3'b001
`define dm_halfword_unsigned 3'b010
`define dm_byte 3'b011
`define dm_byte_unsigned 3'b100
module dm(
input clk,
input DMWr,
input [5:0] addr,
input [31:0] din,
input [2:0] DMType,
output reg [31:0] dout
);
reg [7:0] dmem[6:0]; // 7个地址,7个单元,每个单元8位
integer i;
initial begin
for(i=0;i<7;i=i+1) dmem[i] = 8'h00;
end
// 写操作
always @(posedge clk) begin
if(DMWr) begin
case(DMType)
`dm_byte:dmem[addr] <= din[7:0];
`dm_halfword:begin
dmem[addr] <= din[7:0];
dmem[addr+1] <= din[15:8];
end
`dm_word:begin
dmem[addr] <= din[7:0];
dmem[addr+1] <= din[15:8];
dmem[addr+2] <= din[23:16];
dmem[addr+3] <= din[31:24];
end
endcase
end
end
// 读操作
always @(*) begin
case(DMType)
`dm_byte:dout = {{24{dmem[addr][7]}},dmem[addr][7:0]};
`dm_byte_unsigned:dout = {24'b0,dmem[addr][7:0]};
`dm_halfword:dout = {{16{dmem[addr+1][7]}},dmem[addr+1][7:0],dmem[addr][7:0]};
`dm_halfword_unsigned:dout = {16'b0,dmem[addr+1][7:0],dmem[addr][7:0]};
`dm_word:dout = {dmem[addr+3][7:0],dmem[addr+2][7:0],dmem[addr+1][7:0],dmem[addr][7:0]};
default:dout = {dmem[addr+3][7:0],dmem[addr+2][7:0],dmem[addr+1][7:0],dmem[addr][7:0]};
endcase
end
endmodule

instr.coe(dist_mem_im module 导入)

先用记事本编辑,每个机器码一行并加上逗号

memory_initialization_radix=16; # 16进制
memory_initialization_vector=
00500513
00151593
00255593
ffe00613
40165693
00500513
001515b3
002555b3
ffe00613
401656b3
00a00513
00b00593
00c00613
00d00693
01030313
00000463
ff030313
01030313
008000ef
ff030313
01030313
00080167
ff030313;

对应汇编代码

addi x10,x0,5
slli x11,x10,1
srli x11,x10,2
addi x12,x0,-2
srai x13,x12,1
addi x10,x0,5
sll x11,x10,x1
srl x11,x10,x2
addi x12,x0,-2
sra x13,x12,x1
addi x10 x0 10
addi x11 x0 11
addi x12 x0 12
addi x13 x0 13
addi x6 x6 16
beq x0 x0 label
addi x6 x6 -16
label:addi x6 x6 16
jal x1 label1
addi x6 x6 -16
label1:addi x6 x6 16
jalr x2 0(x16) // 0x10,即跳转到第5行,
addi x6 x6 -16

借助venus将汇编代码转为机器码(注意:只认jal的label形式,jal x0,-4,会认为label为-4)

testbench文件#

使用force 时钟为仿真时钟,从而无需修改主程序的时钟。

`timescale 1ns / 1ps
// 修改模块名为 SCPU_TOP_TB 以匹配 Vivado 报错中寻找的名称
module SCPU_TOP_TB;
// 输入信号
reg clk;
reg rstn;
reg [15:0] sw_i;
// 输出信号
wire [7:0] disp_seg_o;
wire [7:0] disp_an_o;
// 实例化顶层 CPU 模块
// 请确保 SCPU_TOP.v 以及其调用的子模块 (PC_Unit, NPC_Unit, ctrl, RF, alu, dm 等) 都在工程中
SCPU_TOP uut (
.clk(clk),
.rstn(rstn),
.sw_i(sw_i),
.disp_seg_o(disp_seg_o),
.disp_an_o(disp_an_o)
);
// 时钟生成:100MHz (周期 10ns)
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 重要:仿真时钟旁路逻辑
// SCPU_TOP 内部有巨大的分频器 (2^25),直接仿真会导致 CPU 时钟不动。
// 这里使用 force 强制内部 CPU 时钟跟随仿真时钟。
initial begin
force uut.Clk_CPU = clk;
end
// 记录上一条指令的目标寄存器,用于观察写回结果
reg [4:0] last_rd;
// 主测试逻辑
initial begin
// 1. 初始化
rstn = 0;
sw_i = 16'h0000; // sw_i[1]=0 允许 PC 更新, sw_i[0]=0 选择调试模式
last_rd = 0;
// 2. 复位保持
#50;
rstn = 1; // 释放复位
$display("================== Simulation Start ==================");
$display("Time\tPC\tInstr\tOp");
// 3. 循环执行指令并观察
// 这里的 repeat 次数可以根据指令数量调整
// 关键修改:先等待半个周期,让复位后的 PC=0 状态稳定被捕捉到
// 或者直接在下降沿采样
repeat (30) begin
// 等待时钟下降沿 (指令执行)
@(negedge clk); // 修改为在下降沿打印。
// 单周期CPU在上升沿更新状态,在下降沿时所有组合逻辑(ALU等)都已稳定。
// 如果用上升沿,上升沿瞬间完成PC更新,导致第一条指令被跳过
// 为了等待信号稳定(组合逻辑延迟),稍微延时 1ns 再打印
#1;
// --- 打印第一行:基本状态 ---
$display("------------------------------------------------------------------");
$display("[%0t ns] PC=%h | Instr=%h | ROM_Addr=%d", $time, uut.PC, uut.instr, uut.rom_addr);
// --- 打印第二行:解码信息 ---
$display(" DECODE: Op=%b Funct3=%b Funct7=%b | rs1=x%0d rs2=x%0d rd=x%0d",
uut.Op, uut.Funct3, uut.Funct7, uut.rs1, uut.rs2, uut.rd);
// --- 打印第三行:控制信号 ---
$display(" CTRL : RegWr=%b WDSel=%b ALUSrc=%b EXTOp=%b NPCOp=%b MemWr=%b",
uut.RegWrite, uut.WDSel, uut.ALUSrc, uut.EXTOp, uut.NPCOp, uut.MemWrite);
// --- 打印第四行:数据流 (ALU & Imm) ---
$display(" DATA : RD1(rs1)=%h RD2(rs2)=%h ImmOut=%h",
uut.RD1, uut.RD2, uut.immout);
$display(" ALU : A=%h B=%h -> Result=%h Zero=%b",
uut.A, uut.B, uut.aluout, uut.Zero);
// --- 打印第五行:写回阶段 (Write Back) ---
// 注意:寄存器堆的写入通常发生在时钟沿。这里的 WD 是准备写入的数据。
// 为了验证上一条指令是否成功写入,我们打印上一个周期的 rd 对应的寄存器值
$display(" WB : Will Write Data(WD)=%h -> To Reg x%0d", uut.WD, uut.rd);
if (last_rd != 0) begin
$display(" CHECK : Reg[x%0d] is now %h (Result of previous instr)",
last_rd, uut.U_RF.rf[last_rd]);
end
// --- 打印第六行:访存 (Memory Access) ---
if (uut.MemWrite) begin
$display(" DMEM : WRITE Addr=%h Data=%h", uut.dm_addr, uut.dm_din);
end else if (uut.Op == 7'b0000011) begin // Load 指令 Opcode
$display(" DMEM : READ Addr=%h Data=%h", uut.dm_addr, uut.dm_dout);
end
// 更新 last_rd 以便下一次循环检查
// 如果 RegWrite 为 0,则不应该更新 last_rd 或者将其视为无效,这里简单处理只记录 rd
if (uut.RegWrite) last_rd <= uut.rd;
else last_rd <= 0;
end
$display("================== Simulation End ==================");
$stop;
end
endmodule
// ============================================================
// 用户提供的 ROM 模块 (指令存储器)
// 注意:如果您的工程中已经通过 IP 核生成了 dist_mem_im,
// 请注释掉下面这个模块,否则会报 "Module redefined" 错误。
// ============================================================
module dist_mem_im (
input [5:0] a,
output reg [31:0] spo
);
// RISC-V NOP 指令 (addi x0, x0, 0)
localparam NOP = 32'h00000013;
// 指令列表
always @(*) begin
case (a)
// 算术逻辑运算测试
6'd0 : spo = 32'h00500513; // addi x10, x0, 5 (x10 = 5)
6'd1 : spo = 32'h00151593; // slli x11, x10, 1 (x11 = 10)
6'd2 : spo = 32'h00255593; // srli x11, x10, 2 (x11 = 1)
6'd3 : spo = 32'hffe00613; // addi x12, x0, -2 (x12 = -2)
6'd4 : spo = 32'h40165693; // srai x13, x12, 1 (x13 = -1, 算术右移)
6'd5 : spo = 32'h00500513; // addi x10, x0, 5 (重置 x10)
6'd6 : spo = 32'h001515b3; // sll x11, x10, x1 (x1 此时为0, x11=5)
6'd7 : spo = 32'h002555b3; // srl x11, x10, x2 (x2 此时为0)
6'd8 : spo = 32'hffe00613; // addi x12, x0, -2
6'd9 : spo = 32'h401656b3; // sra x13, x12, x6 (x6 未初始化可能为0)
// 逻辑运算
6'd10: spo = 32'h00a00513; // addi x10, x0, 10
6'd11: spo = 32'h00b00593; // addi x11, x0, 11
6'd12: spo = 32'h00c00613; // addi x12, x0, 12
6'd13: spo = 32'h00d00693; // addi x13, x0, 13
6'd14: spo = 32'h01030313; // addi x6, x6, 16 (测试 addi)
// 分支跳转测试 (Branch)
6'd15: spo = 32'h00000463; // beq x0, x0, 8 (PC = PC + 8 -> 跳到地址 17)
// 如果不跳转,会执行下面这行 (地址 16)
6'd16: spo = 32'hff030313; // addi x6, x6, -16 (如果是这行执行了,说明 beq 失败)
// 跳转目标 (地址 17)
6'd17: spo = 32'h01030313; // addi x6, x6, 16 (x6 增加)
// 无条件跳转 (JAL)
6'd18: spo = 32'h008000ef; // jal x1, 8 (x1=PC+4, PC=PC+8 -> 跳到地址 20)
// 如果不跳转 (地址 19)
6'd19: spo = 32'hff030313; // addi x6, x6, -16
// 跳转目标 (地址 20)
6'd20: spo = 32'h01030313; // addi x6, x6, 16
// 间接跳转 (JALR)
6'd21: spo = 32'h00080167; // jalr x2, 0(x16) (x16 未定义,需注意 x16 的值)
6'd22: spo = 32'hff030313; // addi x6, x6, -16
default: spo = NOP;
endcase
end
endmodule

为什么使用上升沿第一条指令被跳过了?#

原因: 在单周期 CPU 中,PC 在时钟上升沿(posedge clk)更新。Testbench 逻辑是:先等待 posedge clk,然后延时 #1,再打印。 当第一个时钟上升沿到来时:

  1. CPU 瞬间执行了 PC=0 的指令,并将 PC 更新为 4。

  2. Testbench 的 $display 语句在 1ns 后执行,此时它看到的是更新后的 PC(即 PC=4)和对应的指令。因此,PC=0 的执行过程在打印前就已经结束了。

解决方法: 修改 Testbench,将观察点改为 时钟下降沿(negedge)。这样可以在指令执行的半周期处观察信号,此时信号已经稳定,且能看到当前周期的正确状态。

12.大佬的CPU设计:锦恢大大#

ComputerDesignExperiment/multistage_pipeline_final at main · LSTM-Kirigaya/ComputerDesignExperiment

这才是我认为的CPU设计,上方的流程虽然还比较清晰,但时序难以把控,且不够优雅

ComputerDesignExperiment
|-> doc : 实验文档
|-> learning_verilog: 一些有关verilog的基础语法的demo
|-> single_cycle: 单周期CPU,支持14条指令
|-> multistage_pipeline: 五级流水线CPU,支持34条指令,不支持冒险,转发和异常
|-> multistage_pipeline_final: 五级流水线CPU,支持57条指令,支持冒险,转发,不支持冒险

五级流水线

CPU设计笔记
https://fuwari.vercel.app/posts/cpu设计笔记/
作者
Echo_Kang
发布于
2025-11-22
许可协议
CC BY-NC-SA 4.0