RISC-V项目(2)---增加B型指令和UART

注意:这里的路径 ../../../Common/… 是比赛环境的相对路径,你需要根据你服务器的实际位置修改

1、增加B型指令的译码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
`timescale 1ns / 1ps                                                                                     
/****************************************Copyright (c)**************************************************
**----------------------------------------File Info-----------------------------------------------------
** File name : decode
** Last modified Date : 2025-11-01
** Last Version : 1.0
** Descriptions : decode
**------------------------------------------------------------------------------------------------------
** Created by : FPGACoreCG
** Created date : 2025-11-01
** Version : 1.0
** Descriptions : The original version
**------------------------------------------------------------------------------------------------------
** Modified by:
** Modified date:
** Version:
** Descriptions:
**
**------------------------------------------------------------------------------------------------------
*******************************************************************************************************/

`include "define.sv"

module decode #(
parameter AW = 32 ,
parameter DW = 32
)(
input logic [AW-1:0] instr_addr_in ,
input logic [DW-1:0] instr_in ,
//to register
output logic [4:0] rd_rs1_addr ,
output logic [4:0] rd_rs2_addr ,
//from register
input logic [DW-1:0] rd_rs1_data ,
input logic [DW-1:0] rd_rs2_data ,
//to instr execute
output logic [DW-1:0] op1_out ,
output logic [DW-1:0] op2_out
);

logic [6:0] opcode ;
logic [4:0] rd ;
logic [2:0] func3 ;
logic [4:0] rs1 ;
logic [4:0] rs2 ;
logic [6:0] func7 ;
logic [31:0] imm ;


assign opcode = instr_in[6:0] ;
assign rd = instr_in[11:7] ;
assign func3 = instr_in[14:12] ;
assign rs1 = instr_in[19:15] ;
assign rs2 = instr_in[24:20] ;
assign func7 = instr_in[31:25] ;

always_comb begin
case(opcode)
`INST_TYPE_I,`INST_TYPE_L,`INST_JALR:
imm = {{20{instr_in[31]}},instr_in[31:20]};
`INST_TYPE_S:
imm = {{20{instr_in[31]}},instr_in[31:25],instr_in[11:7]};
`INST_TYPE_B:
imm = {{20{instr_in[31]}},instr_in[7],instr_in[30:25],instr_in[11:8],1'b0};
`INST_JAL:
imm = {{12{instr_in[31]}},instr_in[19:12],instr_in[20],instr_in[30:21], 1'b0};
`INST_LUI,`INST_LUIPC:
imm = {instr_in[31:12],12'h0};
default:imm = 32'h0;
endcase
end

always_comb begin
case(opcode)
`INST_TYPE_I:begin
case(func3)
`INST_ADDI:begin
rd_rs1_addr = rs1 ;
rd_rs2_addr = 5'h0 ;
op1_out = rd_rs1_data ;
op2_out = imm ;
end
default:begin
rd_rs1_addr = 'h0 ;
rd_rs2_addr = 'h0 ;
op1_out = 'h0 ;
op2_out = 'h0 ;
end
endcase
end
`INST_TYPE_R_M:begin
case(func3)
`INST_ADD_SUB:begin
rd_rs1_addr = rs1 ;
rd_rs2_addr = rs2 ;
op1_out = rd_rs1_data ;
op2_out = rd_rs2_data ;
end
default:begin
rd_rs1_addr = 'h0 ;
rd_rs2_addr = 'h0 ;
op1_out = 'h0 ;
op2_out = 'h0 ;
end
endcase
end
`INST_TYPE_B:begin
case(func3)
`INST_BEQ,`INST_BNE,`INST_BLT,`INST_BGE,`INST_BLTU,`INST_BGEU:begin
rd_rs1_addr = rs1 ;
rd_rs2_addr = rs2 ;
op1_out = rd_rs1_data ;
op2_out = rd_rs2_data ;
end
default:begin
rd_rs1_addr = 'h0 ;
rd_rs2_addr = 'h0 ;
op1_out = 'h0 ;
op2_out = 'h0 ;
end
endcase
end
`INST_JAL:begin
rd_rs1_addr = 5'h0 ;
rd_rs2_addr = 5'h0 ;
op1_out = 32'h0 ;
op2_out = imm ;
end
`INST_JALR:begin
rd_rs1_addr = rs1 ;
rd_rs2_addr = 5'h0 ;
op1_out = rd_rs1_data ;
op2_out = imm ;
end
`INST_LUI:begin
rd_rs1_addr = 5'h0 ;
rd_rs2_addr = 5'h0 ;
op1_out = 32'h0 ;
op2_out = imm ;
end
`INST_LUIPC:begin
rd_rs1_addr = 5'h0 ;
rd_rs2_addr = 5'h0 ;
op1_out = instr_addr_in ;
op2_out = imm ;
end
`INST_NOP_OP: begin
rd_rs1_addr = 5'h0 ;
rd_rs2_addr = 5'h0 ;
op1_out = 32'h0 ;
op2_out = 32'h0 ;
end
default:begin
rd_rs1_addr = 'h0 ;
rd_rs2_addr = 'h0 ;
op1_out = 'h0 ;
op2_out = 'h0 ;
end
endcase
end

endmodule

其中最主要的是下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
`INST_TYPE_B:begin
case(func3)
`INST_BEQ,`INST_BNE,`INST_BLT,`INST_BGE,`INST_BLTU,`INST_BGEU:begin
rd_rs1_addr = rs1 ;
rd_rs2_addr = rs2 ;
op1_out = rd_rs1_data ;
op2_out = rd_rs2_data ;
end
default:begin
rd_rs1_addr = 'h0 ;
rd_rs2_addr = 'h0 ;
op1_out = 'h0 ;
op2_out = 'h0 ;
end
endcase
end

输入:指令中的 rs1(源寄存器1索引)和 rs2(源寄存器2索引)。

动作:去寄存器堆(RegFile)里把这两个寄存器的值读出来。

输出:把读出来的值赋给 op1_out 和 op2_out,送给下一级(ALU)。

2、执行单元代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
`timescale 1ns / 1ps
/****************************************Copyright (c)**************************************************
**----------------------------------------File Info-----------------------------------------------------
** File name : execute.v
** Last modified Date : 2025-11-01
** Last Version : 1.0
** Descriptions : RISC-V 五级流水线 —— 执行阶段 (Execute Stage)
** 负责 ALU 运算、分支判断 (Branch) 和跳转地址计算 (Jump)
**------------------------------------------------------------------------------------------------------
********************************************************************************************************/

// 包含宏定义文件,里面定义了操作码 (e.g., `INST_TYPE_I) 和功能码 (e.g., `INST_ADDI)
`include "define.sv"

module execute #(
parameter AW = 32, // Address Width: 地址位宽 (PC宽度)
parameter DW = 32 // Data Width: 数据位宽 (寄存器宽度)
)(
//-------------------------------------------------------------------------
// 输入信号:来自 Decode (译码) 阶段
//-------------------------------------------------------------------------
input logic [AW-1:0] instr_addr, // 当前指令的 PC 地址 (用于计算相对跳转的目标地址)
input logic [DW-1:0] instr, // 当前指令的原始机器码 (用于提取 funct3/funct7)
input logic [DW-1:0] op1, // 操作数 1 (通常来自 rs1)
input logic [DW-1:0] op2, // 操作数 2 (来自 rs2 或 扩展后的立即数 Imm)

//-------------------------------------------------------------------------
// 输出信号:发送给 Control / IF 阶段 (用于控制跳转)
//-------------------------------------------------------------------------
output logic wr_reg_en, // 写寄存器使能信号 (1: 写, 0: 不写)
output logic [4:0] wr_reg_addr, // 写回的目标寄存器索引 (rd)
output logic [DW-1:0] wr_reg_data, // 写回的数据 (ALU 计算结果)

//-------------------------------------------------------------------------
// 输出信号:发送给 Control / IF 阶段 (用于控制跳转)
//-------------------------------------------------------------------------
output logic jump_en, // 跳转使能 (High有效,表示需要跳转)
output logic [AW-1:0] jump_addr, // 跳转的目标地址 (Target PC)
output logic jump_hold // 流水线暂停请求 (预留信号,目前逻辑中全为 0)
);

//=========================================================================
// 1. 内部信号定义 & 指令字段切片
//=========================================================================
logic [6:0] opcode; // 操作码 (低7位)
logic [4:0] rd; // 目标寄存器索引 (instr[11:7])
logic [2:0] func3; // 功能码3位 (instr[14:12])
logic [6:0] func7; // 功能码7位 (instr[31:25])
logic [31:0] imm; // 本地解出的立即数 (主要用于 B-Type 分支指令)
logic equal; // 比较标志:相等
logic less_signed; // 比较标志:有符号小于
logic less_unsigned; // 比较标志:无符号小于
logic [AW-1:0] jump_imm; // B-Type 指令计算出的跳转目标地址

//=========================================================================
// 2. 组合逻辑连线 (Assign)
//=========================================================================

// --- 指令解码 (虽然 Decode 阶段做过,但 EX 阶段也需要这些具体位来做判断) ---
assign opcode = instr[6:0];
assign rd = instr[11:7];
assign func3 = instr[14:12];
assign func7 = instr[31:25];

// --- 立即数生成 (专门针对 B-Type 指令) ---
// RISC-V 的 B-Type 立即数是打乱拼接的:imm[12|10:5|4:1|11]
// 这里的拼接逻辑:{{20{符号位}}, bit12, bit10:5, bit4:1, 0}
// 注意:最低位补0,因为跳转地址必须是偶数 (2字节对齐)
assign imm = {{20{instr[31]}}, instr[7], instr[30:25], instr[11:8], 1'b0};

// --- 硬件比较器 (ALU Comparator) ---
// 预先并行计算所有比较结果,供分支指令选择使用
assign equal = (op1 == op2) ? 1'b1 : 1'b0; // 判断相等 (BEQ/BNE)
assign less_signed = ($signed(op1) < $signed(op2)) ? 1'b1 : 1'b0;// 有符号小于 (BLT/BGE)
assign less_unsigned = (op1 < op2) ? 1'b1 : 1'b0; // 无符号小于 (BLTU/BGEU)

// --- 计算分支目标地址 ---
// Target = Current PC + Offset (B-Type 的 offset 就是上面切出来的 imm)
assign jump_imm = instr_addr + imm;

//=========================================================================
// 3. 核心执行逻辑 (ALU & Branch Control)
//=========================================================================
always_comb begin
case(opcode)

//-----------------------------------------------------------------
// I-Type 指令: 立即数运算 (e.g., ADDI x1, x2, 10)
//-----------------------------------------------------------------
`INST_TYPE_I: begin
case(func3)
`INST_ADDI: begin
wr_reg_en = 1'b1; // ADDI 需要写回结果
wr_reg_addr = rd; // 写到 rd
wr_reg_data = op1 + op2; // ALU 动作:加法 (op2 此时是立即数)
jump_en = 1'b0; // 不涉及跳转
jump_addr = 'h0;
jump_hold = 1'b0;
end
// 如有其他 I 型指令 (SLTI, ANDI 等),需在此添加 case
default: begin
wr_reg_en = 1'b0; wr_reg_addr = 5'h0; wr_reg_data = 'h0;
jump_en = 1'b0; jump_addr = 'h0; jump_hold = 1'b0;
end
endcase
end

//-----------------------------------------------------------------
// R-Type / M-Type 指令: 寄存器-寄存器运算 (e.g., ADD, SUB)
//-----------------------------------------------------------------
`INST_TYPE_R_M: begin
case(func3)
`INST_ADD_SUB: begin
// 通过 func7 的第 5 位 (bit 30 in instr) 区分 ADD 和 SUB
// 0000000 -> ADD; 0100000 -> SUB
if(func7 == 7'b000_0000) begin // ADD
wr_reg_en = 1'b1;
wr_reg_addr = rd;
wr_reg_data = op1 + op2; // 加法
jump_en = 1'b0; jump_addr = 'h0; jump_hold = 1'b0;
end
else begin // SUB
wr_reg_en = 1'b1;
wr_reg_addr = rd;
wr_reg_data = op1 - op2; // 减法
jump_en = 1'b0; jump_addr = 'h0; jump_hold = 1'b0;
end
end
// 如有乘除法 (M扩展),需在此添加 case
default: begin
wr_reg_en = 1'b0; wr_reg_addr = 5'h0; wr_reg_data = 'h0;
jump_en = 1'b0; jump_addr = 'h0; jump_hold = 1'b0;
end
endcase
end

//-----------------------------------------------------------------
// B-Type 指令: 条件分支 (e.g., BEQ, BNE)
// 只要判断条件成立,就拉高 jump_en,并给出跳转地址
//-----------------------------------------------------------------
`INST_TYPE_B: begin
case(func3)
`INST_BEQ: begin // Branch if Equal
wr_reg_en = 1'b0; // 分支指令不写寄存器
wr_reg_addr = 5'h0;
wr_reg_data = 'h0;
jump_en = equal; // 如果相等,则跳转
jump_addr = equal ? jump_imm : 'h0;
jump_hold = 1'b0;
end
`INST_BNE: begin // Branch if Not Equal
wr_reg_en = 1'b0;
wr_reg_addr = 5'h0;
wr_reg_data = 'h0;
jump_en = ~equal; // 如果不相等,则跳转
jump_addr = ~equal ? jump_imm : 'h0;
jump_hold = 1'b0;
end
`INST_BLT: begin // Branch if Less Than (Signed)
wr_reg_en = 1'b0; wr_reg_addr = 5'h0; wr_reg_data = 'h0; jump_hold = 1'b0;
jump_en = less_signed;
jump_addr = less_signed ? jump_imm : 'h0;
end
`INST_BGE: begin // Branch if Greater/Equal (Signed)
wr_reg_en = 1'b0; wr_reg_addr = 5'h0; wr_reg_data = 'h0; jump_hold = 1'b0;
jump_en = ~less_signed; // not less than = greater or equal
jump_addr = ~less_signed ? jump_imm : 'h0;
end
`INST_BLTU: begin // Branch if Less Than (Unsigned)
wr_reg_en = 1'b0; wr_reg_addr = 5'h0; wr_reg_data = 'h0; jump_hold = 1'b0;
jump_en = less_unsigned;
jump_addr = less_unsigned ? jump_imm : 'h0;
end
`INST_BGEU: begin // Branch if Greater/Equal (Unsigned)
wr_reg_en = 1'b0; wr_reg_addr = 5'h0; wr_reg_data = 'h0; jump_hold = 1'b0;
jump_en = ~less_unsigned;
jump_addr = ~less_unsigned ? jump_imm : 'h0;
end
default: begin
wr_reg_en = 1'b0; wr_reg_addr = 5'h0; wr_reg_data = 'h0;
jump_en = 1'b0; jump_addr = 'h0; jump_hold = 1'b0;
end
endcase
end

//-----------------------------------------------------------------
// J-Type 指令: JAL (Jump and Link)
// 功能:1. 跳转; 2. 将下一条指令地址 (PC+4) 保存到 rd (通常是 ra)
//-----------------------------------------------------------------
`INST_JAL: begin
wr_reg_en = 1'b1; // 需要写寄存器 (Link操作)
wr_reg_addr = rd;
wr_reg_data = instr_addr + 32'h4; // 保存 PC+4 到 rd
jump_en = 1'b1; // 必须跳转
// JAL 的跳转偏移量较大,通常在 Decode 阶段解出放在 op2,或者此处重新计算
// 代码中使用了 `instr_addr + op2`,暗示 op2 传递了 J-Type 的 offset
jump_addr = instr_addr + op2;
jump_hold = 1'b0;
end

//-----------------------------------------------------------------
// I-Type 跳转: JALR (Jump and Link Register)
// 功能:跳到 (rs1 + offset) 的地址
//-----------------------------------------------------------------
`INST_JALR: begin
wr_reg_en = 1'b1;
wr_reg_addr = rd;
wr_reg_data = instr_addr + 32'h4; // 保存 PC+4 到 rd
jump_en = 1'b1;
// JALR 的目标地址是寄存器值 (op1) + 偏移量 (op2)
jump_addr = op1 + op2;
jump_hold = 1'b0;
end

//-----------------------------------------------------------------
// U-Type 指令: LUI (Load Upper Immediate)
//-----------------------------------------------------------------
`INST_LUI: begin
wr_reg_en = 1'b1;
wr_reg_addr = rd;
wr_reg_data = op2; // op2 已经是 Decode 阶段处理好的 {imm, 12'b0}
jump_en = 1'b0;
jump_addr = 'h0;
jump_hold = 1'b0;
end

//-----------------------------------------------------------------
// U-Type 指令: LUIPC (Load Upper Immediate to PC)
// 结果 = PC + {imm, 12'b0}
//-----------------------------------------------------------------
`INST_LUIPC: begin
wr_reg_en = 1'b1;
wr_reg_addr = rd;
wr_reg_data = op1 + op2; // op1 是 PC,op2 是移位后的立即数
jump_en = 1'b0;
jump_addr = 'h0;
jump_hold = 1'b0;
end

//-----------------------------------------------------------------
// 内部空指令 / 气泡 (NOP)
//-----------------------------------------------------------------
`INST_NOP_OP: begin
wr_reg_en = 1'b0;
wr_reg_addr = 5'h0;
wr_reg_data = 'h0;
jump_en = 1'b0;
jump_addr = 'h0;
jump_hold = 1'b0;
end

//-----------------------------------------------------------------
// 默认情况 (非法指令或未实现指令)
//-----------------------------------------------------------------
default: begin
wr_reg_en = 1'b0;
wr_reg_addr = 5'h0;
wr_reg_data = 'h0;
jump_en = 1'b0; // 注意:原代码此处为 1'b1 可能是为了容错或复位,通常应为 0
jump_addr = 'h0;
jump_hold = 1'b0;
end
endcase
end

endmodule

3、测试文件

这段代码使用了 Xilinx Vivado 专用的 综合属性 (* mark_debug = "true" *)**,将其标记在执行阶段(Execute Stage)的关键信号(如指令地址 instr_addr、操作数 op1/op2、写回数据 wr_reg_data 等)之前。这相当于给这些信号颁发了“免死金牌”“自动挂号证”:它的核心作用是强制综合器保留这些信号(防止被优化删除)**,并使它们在 Vivado 的 Set Up Debug 步骤中能被自动识别,从而方便地添加到 ILA(集成逻辑分析仪) 中。其最终目的是为了在 FPGA 上板调试 时,能够像做“心电图”一样实时抓取并观察 CPU 运算核心的内部状态,快速定位指令执行或逻辑运算的 Bug。

1
2
3
4
5
6
7
(*mark_debug = "true"*)logic	[AW-1:0]								execute_instr_addr		; 
(*mark_debug = "true"*)logic [DW-1:0] execute_instr ;
(*mark_debug = "true"*)logic [DW-1:0] execute_op1 ;
(*mark_debug = "true"*)logic [DW-1:0] execute_op2 ;
(*mark_debug = "true"*)logic wr_reg_en ;
(*mark_debug = "true"*)logic [4:0] wr_reg_addr ;
(*mark_debug = "true"*)logic [DW-1:0] wr_reg_data ;

4、LED

  • LED闪烁代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
`timescale 1ns / 1ps

//`define SIM 这里注释掉SIM的定义,表明不进入测试模式

module blink_led(
input logic clk ,
input logic rst_n ,
output logic led
);
`ifdef SIM
localparam CLK_FRQ = 10;
`else
localparam CLK_FRQ = 100000000;
`endif

logic [31:0] cnt ;

always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
cnt <= 'h0;
else if(cnt == CLK_FRQ - 1)
cnt <= 'h0;
else
cnt <= cnt + 'h1;

always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
led <= 1'b0;
else if(cnt == CLK_FRQ - 1)
led <= ~led;

endmodule
  • LED流水灯
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
module flow_led(
input logic clk ,
input logic rst_n ,
output logic [3:0] led
);
`ifdef SIM
localparam CLK_FRQ = 10;
`else
localparam CLK_FRQ = 100000000;
`endif

logic [31:0] cnt ;

always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
cnt <= 'h0;
else if(cnt == CLK_FRQ - 1)
cnt <= 'h0;
else
cnt <= cnt + 'h1;

always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
led <= 4'b0001;
else if(cnt == CLK_FRQ - 1)
led <= {led[0],led[3:1]};

endmodule

呼吸灯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//`define SIM
module breath_led(
input logic clk ,
input logic rst_n ,
output logic led
);

`ifdef SIM
localparam TOTAL_PWM_NUM = 8 ;
localparam SINGLE_PWM_NUM = 8 ;
localparam CELL_NUM = 2 ;
`else
localparam TOTAL_PWM_NUM = 1000 ;
localparam SINGLE_PWM_NUM = 1000 ;
localparam CELL_NUM = 100 ;
`endif

logic [15:0] cell_cnt ;
logic [15:0] single_pwm_cnt ;
logic [15:0] total_pwm_cnt ;
logic flag ;
logic cell_done ;
logic single_pwm_done ;
logic total_pwm_done ;

assign cell_done = (cell_cnt == CELL_NUM - 1);
assign single_pwm_done = (single_pwm_cnt == SINGLE_PWM_NUM - 1);
assign total_pwm_done = (total_pwm_cnt == TOTAL_PWM_NUM - 1);

always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
cell_cnt <= 'h0;
else if(cell_done)
cell_cnt <= 'h0;
else
cell_cnt <= cell_cnt + 'h1;

always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
single_pwm_cnt <= 'h0;
else if(cell_done) begin
if(single_pwm_done)
single_pwm_cnt <= 'h0;
else
single_pwm_cnt <= single_pwm_cnt + 'h1;
end

always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
total_pwm_cnt <= 'h0;
else if(cell_done && single_pwm_done) begin
if(total_pwm_done)
total_pwm_cnt <= 'h0;
else
total_pwm_cnt <= total_pwm_cnt + 'h1;
end

always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
flag <= 1'b1;
else if(cell_done && single_pwm_done && total_pwm_done)
flag <= ~flag;


always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
led <= 1'b0;
else if(single_pwm_cnt <= total_pwm_cnt)
led <= flag;
else
led <= ~flag;

endmodule

下面这个代码就是在进行不同模式灯的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
module cfg_led(
input logic clk ,
input logic rst_n ,
input logic [3:0] led_pattern ,
input logic blink_led ,
input logic breath_led ,
input logic [3:0] flow_led ,
output logic [3:0] led
);

always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
led <= 1'b0;
else begin
case(led_pattern)
4'd0 : led <= {4{1'b0}};
4'd1 : led <= {4{1'b1}};
4'd2 : led <= {4{blink_led}};
4'd3 : led <= {4{breath_led}};
4'd4 : led <= flow_led;
4'd5 : led <= {1'b0,1'b1,blink_led,breath_led};
4'd6 : led <= {blink_led,breath_led,blink_led,breath_led};
default : led <= flow_led;
endcase
end

endmodule

这是一个LED 子系统的顶层架构文件,扮演着“灯光总管”的角色。它通过实例化三个底层驱动模块(u_blink_ledu_breath_ledu_flow_led),让闪烁、呼吸、流水这三种效果生成器在后台并行工作;同时实例化了一个核心选择器 u_cfg_led_inst,负责根据上级输入的控制指令 led_pattern,从上述三个正在运行的信号源中筛选出特定的效果,最终驱动物理 LED 端口,完美实现了效果生成与控制选择的解耦

LED的顶层文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
module led_top(
input logic clk ,
input logic rst_n ,
input logic [3:0] led_pattern ,
output logic [3:0] led
);

logic blink_led ;
logic breath_led ;
logic [3:0] flow_led ;
blink_led u_blink_led_inst(
.clk (clk ),
.rst_n (rst_n ),
.led (blink_led )
);

breath_led u_breath_led_inst(
.clk (clk ),
.rst_n (rst_n ),
.led (breath_led )
);

flow_led u_flow_led_inst(
.clk (clk ),
.rst_n (rst_n ),
.led (flow_led )
);

cfg_led u_cfg_led_inst(
.clk (clk ),
.rst_n (rst_n ),
.led_pattern (led_pattern ),
.blink_led (blink_led ),
.breath_led (breath_led ),
.flow_led (flow_led ),
.led (led )
);

endmodule

5、按键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
`timescale 1ns / 1ps
//--------------------------------------------------------------------------------
// 模块名称: debounce
// 功能描述:
// 按键消抖模块。利用计数器原理,滤除机械按键输入时的抖动毛刺。
// 只有当输入信号保持稳定达到指定时间(默认20ms)后,输出才会改变。
//--------------------------------------------------------------------------------

module debounce(
input logic clk, // 系统时钟 (假设为 100MHz)
input logic rst_n, // 系统复位 (低电平有效)
input logic key_in, // 物理按键输入 (存在抖动)
output logic key_out // 消抖后的输出 (干净信号)
);

//--------------------------------------------------------------------------------
// 参数定义
//--------------------------------------------------------------------------------
// 20ms 的计数值。计算公式: Time / Clk_Period
// 20ms / 10ns (100MHz) = 2,000,000
localparam CNT_20MS = 2000000;

//--------------------------------------------------------------------------------
// 内部信号
//--------------------------------------------------------------------------------
logic key_in_dly; // 输入信号的延迟打拍 (用于检测边缘变化)
logic [31:0] cnt; // 消抖计数器 (32位宽)

//--------------------------------------------------------------------------------
// 逻辑块 1: 输入打拍 (Input Registering)
//--------------------------------------------------------------------------------
// 目的: 记录“上一个时钟周期”的按键状态
always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
key_in_dly <= 1'b1; // 复位时默认按键未按下 (假设低电平有效)
else
key_in_dly <= key_in; // 每个时钟周期更新一次状态

//--------------------------------------------------------------------------------
// 逻辑块 2: 稳定性计时器 (Stability Timer)
//--------------------------------------------------------------------------------
// 核心逻辑:
// 1. 如果信号在抖动 (当前值 != 上一次的值),立即清零计数器。
// 2. 只有信号“老实”地保持不变,计数器才会累加。
always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
cnt <= 'h0;
else if(key_in != key_in_dly) // 检测到边沿变化 (说明有抖动或按键动作)
cnt <= 'h0; // 计数器清零,重新开始计时
else if(cnt == CNT_20MS - 1) // 如果计数器已经数满了 20ms
cnt <= cnt; // 保持计数器不变 (饱和)
else
cnt <= cnt + 'h1; // 信号稳定,且未满20ms,继续计数

//--------------------------------------------------------------------------------
// 逻辑块 3: 输出更新 (Output Update)
//--------------------------------------------------------------------------------
// 目的: 只有当信号稳定时间足够长 (计数器满了),才更新输出
always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
key_out <= 1'b1; // 复位默认输出高电平
else if(cnt == CNT_20MS - 1) // 只有当由于信号稳定导致计数器数满时
key_out <= key_in_dly; // 才认可这个按键状态,更新输出
// 否则保持 key_out 不变 (滤除短于 20ms 的所有毛刺)

endmodule

这个key_out <= key_in_dly,这句代码当检测到按下时,输出为0,不按下的时候,检测为1.

边缘检测模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
`timescale 1ns / 1ps
//--------------------------------------------------------------------------------
// 模块名称: key_trig (按键触发/边沿检测)
// 功能描述:
// 检测输入信号 key_in 的跳变。
// 1. 当信号从 0 -> 1 时,产生一个时钟周期的 key_redge (上升沿) 脉冲。
// 2. 当信号从 1 -> 0 时,产生一个时钟周期的 key_fedge (下降沿) 脉冲。
// 同时利用移位寄存器实现了打拍同步,防止亚稳态。
//--------------------------------------------------------------------------------

module key_trig(
input logic clk, // 系统时钟
input logic rst_n, // 系统复位
input logic key_in, // 输入信号 (通常是消抖后的按键信号)
output logic key_redge, // 输出: 上升沿脉冲 (Rising Edge)
output logic key_fedge // 输出: 下降沿脉冲 (Falling Edge)
);

//--------------------------------------------------------------------------------
// 内部信号: 移位寄存器
//--------------------------------------------------------------------------------
// 定义一个 5 位宽的寄存器,用来存储 key_in 的"历史状态"
logic [4:0] key_in_dly;

//--------------------------------------------------------------------------------
// 逻辑 1: 移位打拍 (Shift Register)
//--------------------------------------------------------------------------------
// 每个时钟周期,把新进来的信号塞到最低位,旧信号往左移
// key_in(新) -> bit0 -> bit1 -> bit2 -> bit3 -> bit4(最旧)
always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
key_in_dly <= 'h0;
else
key_in_dly <= {key_in_dly[3:0], key_in}; // 左移操作

//--------------------------------------------------------------------------------
// 逻辑 2: 上升沿检测 (Rising Edge Detection)
//--------------------------------------------------------------------------------
// 逻辑: 昨天是 0 (!bit3),今天是 1 (bit2) --> 说明发生了上升沿
// 为什么用 bit3 和 bit2?
// 因为信号经过 bit0->bit1->bit2 已经打了两三拍,消除了亚稳态,比较安全。
always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
key_redge <= 1'b0;
else if(!key_in_dly[3] && key_in_dly[2]) // 旧值为0 且 新值为1
key_redge <= 1'b1; // 产生一个周期的脉冲
else
key_redge <= 1'b0; // 其他时候保持为0

//--------------------------------------------------------------------------------
// 逻辑 3: 下降沿检测 (Falling Edge Detection)
//--------------------------------------------------------------------------------
// 逻辑: 昨天是 1 (bit3),今天是 0 (!bit2) --> 说明发生了下降沿
always_ff @(posedge clk or negedge rst_n)
if(!rst_n)
key_fedge <= 1'b0;
else if(!key_in_dly[2] && key_in_dly[3]) // 新值为0 且 旧值为1
key_fedge <= 1'b1; // 产生一个周期的脉冲
else
key_fedge <= 1'b0;

endmodule

按键顶层文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
module key_top(
//--------------------------------------------------------------------------------
// 系统接口
//--------------------------------------------------------------------------------
input logic clk, // 系统时钟 (100MHz)
input logic rst_n, // 系统复位 (低电平有效)

//--------------------------------------------------------------------------------
// 物理接口
//--------------------------------------------------------------------------------
input logic key_in, // [脏信号] 来自板子上物理引脚的原始按键信号
// 此时信号里全是机械抖动的毛刺

//--------------------------------------------------------------------------------
// 逻辑接口 (提供给后级 RISC-V 或其他模块使用)
//--------------------------------------------------------------------------------
output logic key_out, // [净信号] 经过消抖后的稳定电平 (0或1)
// 可以用来做"长按检测"

output logic key_redge, // [事件] 上升沿脉冲 (按下/松开瞬间触发,取决于电路)
output logic key_fedge // [事件] 下降沿脉冲 (按下/松开瞬间触发,取决于电路)
);

//--------------------------------------------------------------------------------
// 模块 1: 按键消抖 (The Filter)
//--------------------------------------------------------------------------------
// 作用: 负责清洗脏数据。把 key_in 的毛刺滤掉,输出稳定的 key_out。
debounce u_debounce_inst(
.clk (clk),
.rst_n (rst_n),
.key_in (key_in), // 输入: 物理引脚 (脏)
.key_out (key_out) // 输出: 稳定电平 (净) -> 同时连到 key_trig 和模块输出
);

//--------------------------------------------------------------------------------
// 模块 2: 边沿检测 (The Trigger)
//--------------------------------------------------------------------------------
// 作用: 负责捕捉时刻。盯着稳定的 key_out,一旦变脸,立马发脉冲。
key_trig u_key_trig_inst(
.clk (clk),
.rst_n (rst_n),
.key_in (key_out), // 输入: 注意!这里接的是消抖后的 key_out,不是 key_in!
.key_redge (key_redge),// 输出: 上升沿标志
.key_fedge (key_fedge) // 输出: 下降沿标志
);

endmodule

6、项目顶层文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 引入全局定义文件 (通常包含地址位宽 AW, 数据位宽 DW, 固件路径 FILE 等)
`include "./../../src/define.sv"

module top(
//-------- 时钟与复位接口 (Differential Clock) --------
input logic sys_clk_p, // 差分时钟输入 P端 (通常用于高性能板卡)
input logic sys_clk_n, // 差分时钟输入 N端
input logic sys_rst_n, // 外部系统复位 (低电平有效)

//-------- 外设接口 --------
input logic key_in, // 物理按键输入 (用于手动复位 CPU)
input logic uart_rx, // UART 接收 (来自上位机)
output logic uart_tx, // UART 发送 (发往上位机)
output logic [3:0] led // LED 输出 (显示测试状态)
);

//-------- 参数定义 --------
localparam CLK_FRE = 100000000; // 系统频率 100MHz (需与 PLL 输出匹配)
localparam BAUDRATE = 115200; // 串口波特率
localparam PARITY = 0; // 无校验
localparam ODD_EVEN = 1; // 奇偶位 (未启用)

// [关键参数] 发送数据长度: 25字节
// 对应字符串 "risv-v test success !!!!\n" 的长度
localparam TX_DW = 25;
localparam RX_DW = 2; // 接收命令长度 (2字节, 如 0x55AA)

// UART 帧头帧尾 (用于协议校验)
localparam TX_HEAD = 8'h5a;
localparam TX_TAIL = 8'ha5;
localparam RX_HEAD = 8'h5a;
localparam RX_TAIL = 8'ha5;

//-------- 内部信号定义 --------
logic sys_clk; // PLL 输出后的单端主时钟
logic rst_n; // 系统内部复位 (PLL Locked)
logic riscv_rst_n; // CPU 专用复位信号
logic [3:0] led_pattern; // LED 显示模式

// [Debug 信号] 加上 (*mark_debug*) 是为了方便 Vivado ILA 抓波形
(*mark_debug = "true"*) logic key_out; // 消抖后的按键
(*mark_debug = "true"*) logic [31:0] test_case; // 当前运行的测试用例 ID
(*mark_debug = "true"*) logic [31:0] reg_s10; // 监视寄存器 x26 (s10)
(*mark_debug = "true"*) logic [31:0] reg_s11; // 监视寄存器 x27 (s11)

logic [1:0] ts_flag; // 测试状态标志位

// UART 字符串常量 (注意:长度必须等于 TX_DW)
logic [TX_DW*8-1:0] send_success;
logic [TX_DW*8-1:0] send_fail;

logic tx_vld; // 发送使能
logic [TX_DW*8-1:0] tx_data; // 待发送数据
logic tx_done; // 发送完成标志 (未处理)
logic rx_vld; // 接收有效标志
logic [RX_DW*8-1:0] rx_data; // 接收到的数据

//-------- 核心组合逻辑 --------

// 1. 状态提取: 从 RISC-V 寄存器 s10 和 s11 中提取最低位,判断测试结果
assign ts_flag = {reg_s10[0], reg_s11[0]};

// 2. LED 映射: 将状态显示在 LED 上
assign led_pattern = {2'h0, ts_flag};

// 3. CPU 复位逻辑:
// CPU 复位 = (系统复位) AND (物理按键状态)
// 这允许你在不掉电的情况下,通过按下按键单独重启 CPU
assign riscv_rst_n = rst_n & key_out;

// 4. 定义返回信息字符串 (注意: 原文 risv-v 拼写有误,应为 riscv)
assign send_success = "risv-v test success !!!!\n";
assign send_fail = "risv-v test fail !!!!\n";

// 5. 自动回复机制 (Auto-Response Logic):
// 当收到数据有效 (rx_vld) 且内容为命令字 0x55AA 时,触发发送
assign tx_vld = rx_vld && (rx_data == 16'h55aa);

// 6. 回复内容选择:
// 如果状态标志为 2'b11 (即 s10=1, s11=1),发送成功信息,否则发送失败
assign tx_data = (ts_flag == 2'b11) ? send_success : send_fail;

//-------- 模块实例化 --------

// 1. 时钟管理: 差分转单端,生成 100MHz
clk_pll u_clk_pll_inst(
.clk_out1 (sys_clk),
.resetn (sys_rst_n),
.locked (rst_n), // PLL 锁定后释放复位
.clk_in1_p (sys_clk_p),
.clk_in1_n (sys_clk_n)
);

// 2. LED 驱动模块
led_top u_led_top_inst(
.clk (sys_clk),
.rst_n (rst_n),
.led_pattern (led_pattern),
.led (led)
);

// 3. 按键处理模块 (包含消抖 + 边沿检测)
key_top u_key_top_inst(
.clk (sys_clk),
.rst_n (rst_n),
.key_in (key_in),
.key_out (key_out), // 输出稳定的电平用于复位 CPU
.key_redge (), // (此处未使用)
.key_fedge () // (此处未使用)
);

// 4. RISC-V 处理器核心
riscv #(
.FILE (`FILE), // 加载的固件文件 (.hex/.bin)
.AW (`AW), // 地址位宽
.DW (`DW) // 数据位宽
) u_riscv_inst(
.clk (sys_clk),
.rst_n (riscv_rst_n),// 受控复位
.test_case (test_case), // [Debug] 当前测试进度
.reg_s10 (reg_s10), // [Debug] 通用寄存器出口 1
.reg_s11 (reg_s11) // [Debug] 通用寄存器出口 2
);

// 5. UART 通信模块 (包含收发 FIFO 和协议包处理)
uart_top #(
.CLK_FRE (CLK_FRE),
.BAUDRATE (BAUDRATE),
.PARITY (PARITY),
.ODD_EVEN (ODD_EVEN),
.SEND_NBYTE (TX_DW), // 发送长度配置
.RECV_NBYTE (RX_DW), // 接收长度配置
.TX_HEAD (TX_HEAD),
.TX_TAIL (TX_TAIL),
.RX_HEAD (RX_HEAD),
.RX_TAIL (RX_TAIL)
) u_uart_top_inst(
.clk (sys_clk),
.rst_n (rst_n),
.tx_ctrl (4'h0),
.rx_ctrl (4'h0),
.rxd (uart_rx),
.send_en (tx_vld), // 发送触发信号
.send_data (tx_data), // 发送内容
.send_done (tx_done),
.txd (uart_tx),
.recv_vld (rx_vld), // 接收完成中断
.recv_data (rx_data) // 接收到的命令
);

endmodule
ESC 关闭 | 导航 | Enter 打开
输入关键词开始搜索