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

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


写测试文件
测试文件一般包含以下几块功能
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
`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) );
endmodule1.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;end2、基于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;endassign #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;end1.2.2 仿真卡壳
参照Vivado仿真,卡在executing analysis and compilation step阶段
取消勾选增量编译,然后进行仿真,可以保持,也可以再勾选,之后就都可以完成仿真了。

2.时钟分频
占空比=高电平时间/总周期时间×100%
n分频 = n个clk上升沿占据的周期对应clk_n一个周期
2.1 偶数倍时钟分频
触发器级联法
(时钟上升沿)
上升沿取反,实现二分频;将二分频作为clk输入,同样处理可得四分频。


计数法分频
思路1:看某位,比如这里盯着cnt[1],需要2个时钟周期(2个上升沿)才会变化。

思路二:直接根据计数值。计数器法可以实现任意偶数分频。在计数周期达到分频系数中间数值 (N/2-1) 时进行时钟翻转,可保证分频后时钟的占空比为 50%。
Tips:中间数值(N/2-1) 需要减1是因为从0开始计数
实现:计数器从0开始计数至2,计数器到0时信号翻转
(图由TimeGen绘制)

//偶数分频电路设计(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分频时钟 endend
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分频时钟 endend
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分频时钟 endend
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 ; endend
//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; endend
assign clk_div6 = clk_div6_r; //延时输出,消除亚稳态
endmoduleTestbench文件
`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仿真结果

思路三:思路二的另一种实现思路,使用2进制的进位机制,希望实现分频,即到达,cnt[n-1]变成1,再过,cnt[n-1]变成0,即每隔,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 取与。

占空比非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时且在上升沿信号清零。
占空比50%奇数分频
信号的翻转对应的源时钟信号应该分别是上升沿和下降沿,但是双边沿触发在电路设计的时候是不允许的。
所以设计两个计数cnt分别是上升沿加1和下降沿加1,这样由cnt决定翻转的clk_div也是上升沿和下降沿翻转的,取或即可实现上升沿和下降沿都翻转,从而是奇数分频。
对于50%占空比奇数分频,就是分别利用待分频时钟的上升沿触发生成一个时钟,然后用下降沿触发生成另一个时钟,然后将两个时钟信号进行或/与运算得到占空比为50%的奇数分频。
以三分频为例,电路需要实现的是:设计2个分别用上升、下降沿触发的计数器cnt_p和cnt_n,设计2个分别用上升、下降沿触发的计数器clk_p和clk_n,利用clk_p和clk_n通过或逻辑运算生成占空比为50%的分频时钟。
//奇数分频电路设计(占空比非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分频时钟信号
endmoduleTestbench文件
`timescale 1ns/1ps //时间刻度:单位1ns,精度1psmodule 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仿真结果

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 endend
// 借用寄存器延迟输出diff_cnt_ralways @(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; endend
//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分频 endend
//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; endend
//延时输出,消除亚稳态assign clk_frac = clk_frac_r;
endmoduleTestbench文件
`timescale 1ns/1ps //时间刻度:单位1ns,精度1psmodule 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|仿真结果)
七段码
八位共阳极七段码

| 显示字符 | 共阳极段选码 | 共阴极段选码 | 显示字符 | 共阳极段选码 | 共阴极段选码 | |
| 0 | C0H | 3FH | A | 88H | 77H | |
| 1 | F9H | 06H | B | 83H | ||
| 2 | A4H | C | C6H | |||
| 3 | B0H | D | A1H | |||
| 4 | 99H | E | 86H | |||
| 5 | 92H | F | 8EH | |||
| 6 | 82H | 全黑 | FFH | |||
| 7 | F8H | |||||
| 8 | 80H | |||||
| 9 | 90H |

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

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

NPN型,基极为高电平1导通;
PNP型,基极为低电平0导通。
NPN







PNP


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


其中箭头指的是流入基极和流出基极
参考资料:极速入门三极管NPN与PNP放大原理【极速入门数模电路P05】
截止状态

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

饱和状态


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

实验
实验一
跑马灯 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;endassign clk_div29 = clk_cnt[div_num];
// led splashalways @(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; endendassign 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;endendmodule仿真时间修改:默认1000ns,导航栏-Tools-settings,如下图。
Vivado不支持.tb文件中增加initial修改仿真时间。
Vivado的Tcl Console不方便使用(无法输入 run 1000ns)。

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;endmoduleTestbench文件
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分频,分频系数
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 endend
// 数字/字符发生器:控制所有数码管同步循环显示0-9和A-Freg [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 endend
// 七段数码管译码器:将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 endend
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选1reg [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;endmoduleTestbench文件
(使用前需将综合代码中的cnt改为[2:0],以及seg7_clk = cnt[2],原因如下)
在你的设计中:
- 主时钟
clk:100MHz(周期10ns) - 扫描时钟
seg7_clk:通过对主时钟进行 分频得到,频率约为 100MHz/32768 ≈ 3.05kHz(分频则周期为clk周期的倍)
但在之前的仿真中:
- 仿真时间只有 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进制 |
| 1 | C6F6F6F0 | |
| 1111_0000 | F0 | |
| 1111_0110 | F6 | |
| 1111_0110 | F6 | |
| 1100_10110 | C6 | |
| 2 | F9F6F6CF | |
| 1100_1111 | CF | |
| 11110110 | F6 | |
| 11110110 | F6 | |
| 1111_1001 | F9 | |
| 3 | FFC6F0FF | |
| 1111_1111 | FF | |
| 1111_0000 | F0 | |
| 1100_0110 | C6 | |
| 1111_1111 | FF | |
| 4 | FFC0FFFF | |
| 1111_1111 | FF | |
| 1111_1111 | FF | |
| 1100_0000 | C0 | |
| 1111_1111 | FF | |
| 5 | FFA3FFFF | |
| 1111_1111 | FF | |
| 1111_1111 | FF | |
| 1010_0011 | A3 | |
| 1111_1111 | FF | |
| 6 | FFFFA3FF | |
| 1111_1111 | FF | |
| 1010_0011 | A3 | |
| 1111_1111 | FF | |
| 1111_1111 | FF | |
| 7 | FFFF9CFF | |
| 1111_1111 | FF | |
| 1001_1100 | 9C | |
| 1111_1111 | FF | |
| 1111_1111 | FF |
矩形变换7段码编码表二
| 编号 | 8段码DPGFEDCBA | 16进制 |
| 8 | FF9EBCFF | |
| 1111_1111 | FF | |
| 1011_1100 | BC | |
| 1001_1110 | 9E | |
| 1111_1111 | FF | |
| 9 | FF9CFFFF | |
| 1111_1111 | FF | |
| 1111_1111 | FF | |
| 1001_1100 | 9C | |
| 1111_1111 | FF | |
| 10 | FFC0FFFF | |
| 1111_1111 | FF | |
| 1111_1111 | FF | |
| 1100_0000 | C0 | |
| 1111_1111 | FF | |
| 11 | FFA3FFFF | |
| 1111_1111 | FF | |
| 1111_1111 | FF | |
| 1010_0011 | A3 | |
| 1111_1111 | FF | |
| 12 | FFA7B3FF | |
| 1111_1111 | FF | |
| 1011_0011 | B3 | |
| 1010_0111 | A7 | |
| 1111_1111 | FF | |
| 13 | FFC6F0FF | |
| 1111_1111 | FF | |
| 1111_0000 | F0 | |
| 1100_0110 | C6 | |
| 1111_1111 | FF | |
| 14 | F9F6F6CF | |
| 1100_1111 | CF | |
| 1111_0110 | F6 | |
| 1111_0110 | F6 | |
| 1111_1001 | F9 | |
采用自然独立编码方法,一个数码管8位,有8个数码管,总字长64位,采用一个64位单元存储,或两个32位单元存储。高位数码管放高位,低位数码管放低位。每个数码管内部,采用7段码编码顺序(DpGFEDCBA)存放。如下图所示。![]() | ||
| 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;endassign 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; endcaseend
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));endmoduleseg7x16
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选1reg [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;endmoduleAnother 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此代码导致板子会有全亮的原因
主要问题在于时序逻辑的竞争条件:
- 地址越界时的处理:当
led_data_addr == 19时,代码试图重置地址并设置led_disp_data <= 64'b1 - 但立即被覆盖:在同一个时钟边沿,下一行
led_disp_data <= LED_DATA[led_data_addr]会覆盖前面的赋值 - 关键问题:当
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



FE

DE

CE

C2

C0

F1

FC

FD

F8

F7

F3

FB

F9

BC

BD

BF

AF

27

37

77

71

70

C8

E7

C7

CF

DF

EF

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));endmoduleseg7_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选1reg [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;endmodule3.ROM(RAM)模块设计流程
3.1 FPGA概念及关联
LUT
一个N输入查找表LUT,可以实现N个输入变量的任何逻辑功能:通过输入和输出的真值表设定输入,使输出与输入符合真值表。
输入多于N个,则需分开用多个LUT实现。


- 实现组合逻辑:
比如我希望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值可以实现各种门电路的功能。
- 实现时序逻辑(添加CLK和D触发器)

当用户通过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的基本存储单位。

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:可配置逻辑块

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是不同的,其他结构完全一样。

两种SLICE,分别为SLICEL,和SLICEM
放大上图后我们可以看出区别

图: 两种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实现图

上图64x1单端口DRAM

上图128x1单端口DRAM

图 双端口DRAM结构
3.6 分布式ROM
SLICEL和SLICEM的每一个LUT都可以实现64*1bit的ROM。和RAM实现的方式原理是一样,就是取消了写地址和写使能,只需要对地址信号的查找即可。
3.7 Vivado ROM IP核的建立
1、查找 ROM IP核
1)如下图单击ipcatalog 选项(IP:设计好的,有专利的硬件功能模块)

2)查找distributed memory
在ip calalog 页面 输入查找关键字memory,并点击结果distributed memory 选项。

2、配置和初始化 ROM IP核
1)Main Screen设置
配置成ROM,设置名称、数据位宽和深度。

关注左图的输入和输出端口位数以及与右边数据的关系
Depth:64 -> 64个地址,5位即可选择,a[5:0]。
Data width:32 -> 数据为32位,所以输出,spo[31:0]。
Depth深度可以理解为按地址顺序往下排,可以排64行,理解为深度。
2)Port Configuration Screen
输入、输出端口配置成直通方式。

增加寄存器会有时延,这里是单周期CPU,所以不添加寄存器。
3)初始化ROM IP核
3.1设置coe文件路径。coe文件里包含ROM初始化信息(这里包含的信息为:数据进制和数据)

3.2验证COE文件的有效性,并保存。

3.2.1 验证该COE文件

3.2.2 COE文件通过验证

3.3 系统生成ROM IP核对应文件

3.3.1 确认生成ROM核文件

“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
这个程序的功能
这是一个简单的数据搬运程序:
- 初始化寄存器:x1=1, x2=2, x3=3
- 存储操作:将x1,x2,x3的值存入内存地址1,3,5
- 加载操作:从内存地址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段数据); // 主程序endmodule3.8.2 主板开关定义

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

例化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
endmoduleseg7x16
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选1reg [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;endmodule4.RegisterFile(RF)模块设计
4.1 Analysis

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


下面代码实现通过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]; endend
endmoduleRF
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;
endmoduleseg7x16.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选1reg [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
5.ALU模块设计

运算指令码长度[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
说明:
SW[12]=1 :循环显示ALU中的A,B,C,Zero的内容。
SW[2]= 1 :C=A+B
SW[2]= 0 :C=A-B

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]; endend
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]};
// 例化alualu 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; endcaseend
endmodulealu
`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算出来,而不是同步endendmoduleseg7x16.v和RF.v没有更改
5.2 实现RF和ALU的关联
1)实现RF部件和ALU部件的关联,使得指定寄存器的数值送入ALU部件参与运算,具体参见下图:

2)通过SW_i输入双端口要读出的寄存器编号A1,A2,并将读出的数值RD1,RD2送入ALU的A,B输入端,根据设定的ALUOp,计算结果,并显示。
3)通过SW_i输入要写入寄存器编号和数值A3,WD,再通过步骤2),将计算结果显示出来。

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;
// ROMreg [5:0] rom_addr;
// RFwire RegWrite;wire [4:0] rs1,rs2,rd;wire [31:0] WD;wire [31:0] RD1,RD2;
reg [5:0] reg_addr;
// ALUwire [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]; endend
// 例化alualu 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; endcaseend
endmodulealu
`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算出来,而不是同步endendmodule6.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

宏定义
// 宏定义如下:`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'b1006.2 Code
SCPU_TOP
// 添加Data Memorymodule 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;
// ROMreg [5:0] rom_addr;
// RFwire RegWrite;wire [4:0] rs1,rs2,rd;wire [31:0] WD;wire [31:0] RD1,RD2;
reg [5:0] reg_addr;
// ALUwire [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]; endend
// 例化alualu 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; endcaseend
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 endend
endmoduledm
`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 endend
// 读操作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]; endcaseendendmodule7.模块拼接

模拟串行运行一段代码
| add x1,x0,x0 | X1=x0+x0; x1=0 | rs1=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,1 | X1=X1+1; X1=1 | rs1=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,x0 | x2=x0+x0; x2=0 | rs1=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,2 | x2=x2+2; x2=2 | rs1=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)=x2 | rs1=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,x0 | x3=x0+x0; x3=0 | rs1 = 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,3 | x3=x3+3; x3=3 | rs1 = 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)=x3 | rs1 = 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 指令集


3.2 ARM 指令集


3.3 RISC-V 指令集
1、模块化的指令集满足不同的应用
| 基本指令集名称 | 指令数 | 特 点 |
| RV32I | 47 | 整数指令,32位地址空间,32个通用寄存器 |
| RV32E | 47 | 整数指令,RV32E的子集,支持16个通用寄存器 |
| RV64I | 59 | 整数指令,64位地址空间(含少数32位整数指令) |
| RV128I | 71 | 整数指令,128位地址空间 |
| 扩展指令集名称 | 指令数 | 特 点 |
| M | 8 | 整数乘除指令 |
| A | 11 | 原子内存操作指令(AMO用于处理器间同步的读-修改-写操作) |
| F | 26 | 单精度浮点运算指令 |
| D | 26 | 双精度浮点运算指令 |
| C | 46 | 压缩指令(16位) |
2、指令格式分为六类

备注:jal指令中立即数(或label)为20位, jalr指令中立即数为12位。
3.4 RISC-V 指令集举例:


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; endcaseend当前缺失信号
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
四大模块详细连接框图

注释:
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的原码

add x1 x0 x0addi x1 x1 1add x2 x0 x0addi x2 x2 2add x3 x0 x0addi x3 x3 3sw 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分析


根据小端法,低地址数据对应低位,高地址数据对应高位
代码(拼接,执行简单指令流)
代码如下:
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;
// ROMreg [5:0] rom_addr;
// RFwire RegWrite;reg [31:0] WD;wire [31:0] RD1,RD2;
reg [5:0] reg_addr;
// ALUwire [31:0] A,B;wire [4:0] ALUOp;wire [31:0] aluout;wire [7:0] Zero;
reg [2:0] alu_addr;
// DMwire 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
//Decodewire [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_WDalways @(*)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; endcaseend
// RF->ALU ALU_A_Bassign A = RD1;assign B = (ALUSrc == 1'b0)?RD2:immout;
// ALU->DM DM_addr_dinassign 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]; endend
/////////////////////////////////////////////////////
// 例化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; endcaseend
//////////////////////////////////////////////////////
// 例化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 endend
endmoduleseg7x16
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选1reg [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;endmoduleRF
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寄存器,写rdend
// 读rs1和rs2assign RD1 = (A1 != 0)?rf[A1]:0;assign RD2 = (A2 != 0)?rf[A2]:0;
endmodulealu
`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算出来,而不是同步endendmoduledm
`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 endend
// 读操作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]}; endcaseendendmoduleCtrl
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_typewire rtype = ~Op[6] & Op[5] & Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //0110011wire 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 000wire 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 loadwire itype_l = ~Op[6] & ~Op[5] & ~Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //0000011wire i_lb = itype_l & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; //lb 000wire i_lh = itype_l & ~Funct3[2] & ~Funct3[1] & Funct3[0]; //lh 001wire i_lw = itype_l & ~Funct3[2] & Funct3[1] & ~Funct3[0]; //lw 010wire i_lbu = itype_l & Funct3[2] & ~Funct3[1] & ~Funct3[0]; //lbu 100wire i_lhu = itype_l & Funct3[2] & ~Funct3[1] & Funct3[0]; //lh 101
// i_r type 涉及Reg,ALU with immediatewire itype_r = ~Op[6] & ~Op[5] & Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0]; //0010011wire i_addi = itype_r & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // addi 000 func3
// s format storewire stype = ~Op[6] & Op[5] & ~Op[4] & ~Op[3] & ~Op[2] & Op[1] & Op[0];//0100011wire i_sw = ~Funct3[2] & Funct3[1] & ~Funct3[0]; // sw 010wire i_sb = stype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // sb 000wire i_sh = stype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // sh 001
// 生成控制信号assign RegWrite = rtype | itype_r | itype_l; // register writeassign MemWrite = stype; // memory writeassign ALUSrc = itype_r | stype | itype_l; // ALU B is from instruction immediate// assign mem2reg = wdsel// WDSel_FromALU 2'b00 WDSel_FromMEM 2'b01assign WDSel[0] = itype_l;assign WDSel[1] = 1'b0;
//ALUOp_nop 5'b00000//ALUOp_lui 5'b00001//ALUOp_auipc 5'b00010//ALUOp_add 5'b00011assign 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'b100assign DMType[2] = i_lbu; // buassign DMType[1] = i_lb | i_sb | i_lhu; // hu和bassign DMType[0] = i_lh | i_sh | i_lb | i_sb; // h和b
endmoduleEXT
// 立即数扩展`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; endcaseend
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;// endendmodule
// Testbench ROM override: dist_mem_immodule 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 endendmoduletb_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 endendmodulecoe文件内容:
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)发生时,仿真器会分几个阶段执行:
- 事件触发阶段 所有
always @(posedge clk)块被触发,右边的表达式(如WD)被计算。 - 非阻塞赋值调度阶段 对于
rf[A3] <= WD;这样的非阻塞赋值,结果不会立刻更新,而是放入一个“更新队列”。 - 打印阶段 如果你在 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; endend这样你就能看到写入后的值,而不是旧值。
方法二:打印写入前后的值对比
你可以在 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]=0 且 RegWrite=1 → 在下一个时钟上升沿,寄存器更新为 WD,所以在下一条指令暂停时,reg显示的就是之前指令执行后reg结果。
sw_i[12]为1,显示alu的运算,显示A,B,C,Zero,是实时当前指令的alu过程
sw_i[11]为1,显示DM数据存储器的内容,是当前指令执行后的结果
8.单周期CPU(37条指令)
37条指令图(列清下图很好写Control生成)

转移指令

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;
// ROMreg [5:0] rom_addr;
// RFwire RegWrite;reg [31:0] WD;wire [31:0] RD1,RD2;
reg [5:0] reg_addr;
// ALUwire [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;
// DMwire 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 + immoutalways @(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 endend
// 每个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
//Decodewire [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<<12always @(*)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; endcaseend//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_Bassign A = RD1;assign B = (ALUSrc == 1'b0)?RD2:immout;
// ALU->DM DM_addr_dinassign 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]; endend
/////////////////////////////////////////////////////
// 例化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; endcaseend
//////////////////////////////////////////////////////
// 例化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 endend
endmoduleseg7x16
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选1reg [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;endmodulerom(如果在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];
endmoduleRF
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有效,写rdend
// 读rs1和rs2assign RD1 = (A1 != 0)?rf[A1]:0;assign RD2 = (A2 != 0)?rf[A2]:0;
endmodulealu
`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算出来,而不是同步endendmoduledm
`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 endend
// 读操作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]}; endcaseendendmoduleCtrl
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]; //0110011wire i_add = rtype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0] & ~Funct7[5]; // add 0000000 000wire i_sub = rtype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0] & Funct7[5]; // sub 0100000 000wire i_sll = rtype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // sll 0000000 001wire i_slt = rtype & ~Funct3[2] & Funct3[1] & ~Funct3[0]; // slt 0000000 010wire i_sltu = rtype & ~Funct3[2] & Funct3[1] & Funct3[0]; // sltu 0000000 011wire i_xor = rtype & Funct3[2] & ~Funct3[1] & ~Funct3[0]; // xor 0000000 100wire i_srl = rtype & Funct3[2] & ~Funct3[1] & Funct3[0] & ~Funct7[5]; // srl 0000000 101wire i_sra = rtype & Funct3[2] & ~Funct3[1] & Funct3[0] & Funct7[5]; // sra 0100000 101wire i_or = rtype & Funct3[2] & Funct3[1] & ~Funct3[0]; // or 0000000 110wire 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]; //0000011wire i_lb = itype_l & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; //lb 000wire i_lh = itype_l & ~Funct3[2] & ~Funct3[1] & Funct3[0]; //lh 001wire i_lw = itype_l & ~Funct3[2] & Funct3[1] & ~Funct3[0]; //lw 010wire i_lbu = itype_l & Funct3[2] & ~Funct3[1] & ~Funct3[0]; //lbu 100wire 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]; //0010011wire i_addi = itype_r & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // addi 000 func3wire i_slti = itype_r & ~Funct3[2] & Funct3[1] & ~Funct3[0]; // slti 010 func3wire i_sltiu = itype_r & ~Funct3[2] & Funct3[1] & Funct3[0]; // sltiu 011 func3wire i_xori = itype_r & Funct3[2] & ~Funct3[1] & ~Funct3[0]; // xori 100 func3wire i_ori = itype_r & Funct3[2] & Funct3[1] & ~Funct3[0]; // ori 110 func3wire 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 001wire i_srli = itype_r & Funct3[2] & ~Funct3[1] & Funct3[0] & ~Funct7[5]; // srli 101 0000000wire 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];//0100011wire i_sb = stype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // sb 000wire i_sh = stype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // sh 001wire 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]; //1100011wire i_beq = btype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // beq 000wire i_bne = btype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // bne 001wire i_blt = btype & Funct3[2] & ~Funct3[1] & ~Funct3[0]; // blt 100wire i_bge = btype & Funct3[2] & ~Funct3[1] & Funct3[0]; // bge 101wire i_bltu = btype & Funct3[2] & Funct3[1] & ~Funct3[0]; // bltu 110wire 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]; //0X10111wire i_lui = utype & Op[5]; // lui 0110111wire 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]; //1101111wire 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'b101assign 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 = 0wire 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<<12assign 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'b100assign DMType[2] = i_lbu; // buassign DMType[1] = i_lb | i_sb | i_lhu; // hu和bassign DMType[0] = i_lh | i_sh | i_lb | i_sb; // h和b
endmoduleEXT
// 立即数扩展`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; endcaseend
endmoduletb文件代码
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条指令,其余填充nopmodule 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 endendmodule现在的 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 endendmodule注:这些机器码是示例,具体编码要根据你希望测试的寄存器和立即数来写。关键是覆盖到
ctrl模块里定义的所有 37 条指令。
✅ 2. 在 testbench 中打印完整信息
保持之前的 $display 打印逻辑不变,这样每条指令都会显示:
- 指令编码和地址
- 解码字段
- 控制信号
- ALU 输入输出
- WD 和寄存器写入结果
- DM 访问情况
9.纠错篇
第7、8节卡住了很久,改动后成功,最后整理出改动点如下:
- RF的clk输出误写成clk,应该为Clk_CPU
- RF的写入操作条件加上 A3 != 5’b0 即不写x0寄存器
- Decode的代码左边赋值的位宽要明确为好
- WD选择前赋上默认值
- 尽量用非阻塞赋值 <=(比如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 endend
// 非阻塞赋值// 循环显示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 endend10.总结
项目越大,代码越多,越要用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;
// PCwire [31:0] PC;wire [31:0] NPC;wire PCwr = ~sw_i[1];wire [2:0] NPCOp;
// ROMreg [5:0] rom_addr;
// RFwire RegWrite;wire [31:0] WD;wire [31:0] RD1,RD2;
reg [5:0] reg_addr;
// ALUwire [31:0] A,B;wire [4:0] ALUOp;wire [31:0] aluout;wire Zero;
reg [2:0] alu_addr;
// DMwire 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;
// Ctrlwire [5:0] EXTOp;wire [1:0] WDSel;wire ALUSrc;
// EXTwire [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 modulePC_Unit U_PC(.clk(Clk_CPU),.rst(~rstn),.NPC(NPC),.PCwr(PCwr),.PC(PC));
// 例化NPC_Unit moduleNPC_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
//Decodewire [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_Bassign A = RD1;assign B = (ALUSrc == 1'b0)?RD2:immout;
// ALU->DM DM_addr_dinassign 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]; endend
/////////////////////////////////////////////////////
// 例化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; endcaseend
//////////////////////////////////////////////////////
// 例化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 endend
endmodulePC
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;endendmoduleNPC
`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; endcaseendendmodulectrl
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]; //0110011wire i_add = rtype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0] & ~Funct7[5]; // add 0000000 000wire i_sub = rtype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0] & Funct7[5]; // sub 0100000 000wire i_sll = rtype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // sll 0000000 001wire i_slt = rtype & ~Funct3[2] & Funct3[1] & ~Funct3[0]; // slt 0000000 010wire i_sltu = rtype & ~Funct3[2] & Funct3[1] & Funct3[0]; // sltu 0000000 011wire i_xor = rtype & Funct3[2] & ~Funct3[1] & ~Funct3[0]; // xor 0000000 100wire i_srl = rtype & Funct3[2] & ~Funct3[1] & Funct3[0] & ~Funct7[5]; // srl 0000000 101wire i_sra = rtype & Funct3[2] & ~Funct3[1] & Funct3[0] & Funct7[5]; // sra 0100000 101wire i_or = rtype & Funct3[2] & Funct3[1] & ~Funct3[0]; // or 0000000 110wire 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]; //0000011wire i_lb = itype_l & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; //lb 000wire i_lh = itype_l & ~Funct3[2] & ~Funct3[1] & Funct3[0]; //lh 001wire i_lw = itype_l & ~Funct3[2] & Funct3[1] & ~Funct3[0]; //lw 010wire i_lbu = itype_l & Funct3[2] & ~Funct3[1] & ~Funct3[0]; //lbu 100wire 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]; //0010011wire i_addi = itype_r & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // addi 000 func3wire i_slti = itype_r & ~Funct3[2] & Funct3[1] & ~Funct3[0]; // slti 010 func3wire 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 001wire i_srli = itype_r & Funct3[2] & ~Funct3[1] & Funct3[0] & ~Funct7[5]; // srli 101 0000000wire i_srai = itype_r & Funct3[2] & ~Funct3[1] & Funct3[0] & Funct7[5]; // srai 101 0100000wire itype_shamt = i_slli | i_srli | i_srai;
// jalrwire 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];//0100011wire i_sb = stype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // sb 000wire i_sh = stype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // sh 001wire 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]; //1100011wire i_beq = btype & ~Funct3[2] & ~Funct3[1] & ~Funct3[0]; // beq 000wire i_bne = btype & ~Funct3[2] & ~Funct3[1] & Funct3[0]; // bne 001wire i_blt = btype & Funct3[2] & ~Funct3[1] & ~Funct3[0]; // blt 100wire i_bge = btype & Funct3[2] & ~Funct3[1] & Funct3[0]; // bge 101wire i_bltu = btype & Funct3[2] & Funct3[1] & ~Funct3[0]; // bltu 110wire 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]; //1101111wire i_jal = jtype; // jal
// U_type 2条wire utype = ~Op[6] & Op[4] & ~Op[3] & Op[2] & Op[1] & Op[0]; //0X10111wire i_lui = utype & Op[5]; // lui 0110111wire 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'b000001assign 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'b11000assign 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'b100assign 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'b10assign 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'b100assign DMType[2] = i_lbu; // buassign DMType[1] = i_lb | i_sb | i_lhu; // hu和bassign DMType[0] = i_lh | i_sh | i_lb | i_sb; // h和b
endmodulealu
`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算出来,而不是同步endendmoduleEXT
// 立即数扩展`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; endcaseend
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选1reg [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;endmoduleRF
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有效,写rdend
// 读rs1和rs2assign RD1 = (A1 != 0)?rf[A1]:0;assign RD2 = (A2 != 0)?rf[A2]:0;
endmoduledm
`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 endend
// 读操作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]}; endcaseendendmoduleinstr.coe(dist_mem_im module 导入)
先用记事本编辑,每个机器码一行并加上逗号
memory_initialization_radix=16; # 16进制memory_initialization_vector=005005130015159300255593ffe006134016569300500513001515b3002555b3ffe00613401656b300a0051300b0059300c0061300d006930103031300000463ff03031301030313008000efff0303130103031300080167ff030313;对应汇编代码
addi x10,x0,5slli x11,x10,1srli x11,x10,2addi x12,x0,-2srai x13,x12,1addi x10,x0,5sll x11,x10,x1srl x11,x10,x2addi x12,x0,-2sra x13,x12,x1addi x10 x0 10addi x11 x0 11addi x12 x0 12addi x13 x0 13addi x6 x6 16beq x0 x0 labeladdi x6 x6 -16label:addi x6 x6 16jal x1 label1addi x6 x6 -16label1:addi x6 x6 16jalr 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 endendmodule为什么使用上升沿第一条指令被跳过了?
原因: 在单周期 CPU 中,PC 在时钟上升沿(posedge clk)更新。Testbench 逻辑是:先等待 posedge clk,然后延时 #1,再打印。 当第一个时钟上升沿到来时:
-
CPU 瞬间执行了 PC=0 的指令,并将 PC 更新为 4。
-
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条指令,支持冒险,转发,不支持冒险



