你的代码整体结构清晰,功能模块划分明确,实现了时钟、日历、数码管显示、按键设置等基本功能。但经过详细分析,发现存在**多个逻辑错误、语法问题和设计缺陷**,特别是在时间进位、闰年判断、状态切换、整点报时、按键防抖等方面没有完全满足题目要求。
---
### ✅ **存在的主要问题及修改建议**
---
#### 🔴 1. **`posedge reset` 使用不当**
```verilog
always@(posedge clk_1hz, posedge reset)
```
- ❌ 错误:你将 `reset` 设置为异步复位,但输入是 `SW0`(拨码开关),通常不是脉冲信号而是电平信号。
- ✅ 正确做法:应使用同步复位或对 `reset` 做边沿检测(上升沿触发)。
> 更合理的做法是:在主逻辑中检测 `reset == 1` 时进行初始化,而不是作为异步复位引脚。
🔧 **修改建议**:
```verilog
always @(posedge clk_1hz) begin
if (reset) begin
// 初始化所有变量
hour <= 0; min <= 0; sec <= 0;
day <= 1; month <= 1;
year0 <= 2; year1 <= 0; year2 <= 0; year3 <= 2; // 表示 2002 年?不对!
end else begin
// 计时逻辑
end
end
```
⚠️ 注意:上板复位后应该从 **00年1月1日0时0分0秒** 开始计时 → 即 **2000年1月1日?还是 2022年?**
根据题意“从00年1月1号”,说明初始年份应为 `00`,即 `year0=0, year1=0, year2=0, year3=0`。
所以这句:
```verilog
year0<=2;year1<=1;year2<=0;year3<=2;
```
是错的!它表示的是 **2012 年**!
🔧 **修正为**:
```verilog
year0 <= 0; year1 <= 0; year2 <= 0; year3 <= 0; // 初始年份 = 0000
day <= 1; month <= 1; hour <= 0; min <= 0; sec <= 0;
```
---
#### 🔴 2. **年份拼接方式错误 + 未实现动态递增年份**
```verilog
year = year3*1000 + year2*100 + year1*10 + year0;
```
- ❌ 错误:这个表达式写在了 `always` 块里,但 `year` 是一个 `reg [12:0]` 类型,不能直接赋值除非在过程块中。
- ✅ 应该用 `assign` 或放在 `always` 中合理计算。
更严重的问题是:**年份不会自动增加!**
当前代码只处理了月份和日期的变化,但从未判断 `month == 12 && day == 31 && ...` 后是否要进位到下一年!
🔧 **必须添加年份进位逻辑**:
```verilog
if (month == 12 && day == 31 && hour == 23 && min == 59 && sec == 59) begin
year0 <= (year0 == 4'd9) ? 4'd0 : year0 + 1;
if (year0 == 4'd9) begin
year1 <= (year1 == 4'd9) ? 4'd0 : year1 + 1;
if (year1 == 4'd9) begin
year2 <= (year2 == 4'd9) ? 4'd0 : year2 + 1;
if (year2 == 4'd9)
year3 <= (year3 == 4'd9) ? 4'd0 : year3 + 1;
end
end
end
```
或者更简单地维护一个 16 位的 `current_year` 寄存器来判断闰年。
---
#### 🔴 3. **闰年判断逻辑不完整且错误**
题目要求:
> 能被400整除,或不能被100整除但能被4整除的年份为闰年
你用了:
```verilog
(year%4)!=0
```
但这只是判断能否被4整除,忽略了百年规则!
🔧 **正确判断应如下**:
```verilog
function is_leap_year;
input [15:0] y;
is_leap_year = ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0);
endfunction
```
然后调用:
```verilog
wire [15:0] full_year = {year3,year2,year1,year0}; // 拼成完整年份
reg leap;
// 在秒更新时同步判断
always @(*)
leap = is_leap_year(full_year);
```
⚠️ 当前代码中 `(year%4)==0` 是错误的,因为 `year` 是拼出来的十进制数,而 `%` 运算符在 Verilog 中对 BCD 数无效!
👉 必须先将 BCD 形式的年份转为二进制整数再做模运算,或者手动实现逻辑判断。
🔧 推荐做法(避免 BCD 转换复杂):单独维护一个 `reg [15:0] current_year` 来记录真实年份数值。
---
#### 🔴 4. **按键控制逻辑不符合题目要求**
题目要求:
> 按键0进入日期设置 → 再按进入时钟设置 → 再按退出设置
> 按键1选择个位/十位
> 按键2加一
但你的代码中:
- `set_time`, `set_hour`, `set_min` 等都是独立按键输入(SW)
- 没有实现“三态循环切换”(日期设置 → 时间设置 → 退出)
❌ 不符合需求!
🔧 **解决方案:使用状态机实现 mode 切换**
```verilog
typedef enum logic [1:0] {
NORMAL,
SET_DATE,
SET_TIME
} state_t;
state_t cur_state, next_state;
reg [2:0] select_field; // 用于记录当前选中的字段(如年、月、日、时、分)
```
并在 `set_time`(即 SW2)按下时进行状态跳转。
同时需要按键消抖!
---
#### 🔴 5. **缺少按键防抖(Debouncing)**
物理按键按下会有机械抖动,直接读取会导致多次误触发。
✅ 必须加入去抖模块!
🔧 示例:每 10ms 采样一次按键状态
```verilog
module debounce (
input clk, // 50MHz
input btn,
output reg valid_edge // 输出一个高电平脉冲表示按键按下
);
reg [19:0] counter;
reg btn_reg1, btn_reg2;
wire btn_sync;
// 两级寄存器同步
always @(posedge clk) begin
btn_reg1 <= btn;
btn_reg2 <= btn_reg1;
end
assign btn_sync = btn_reg2;
always @(posedge clk) begin
if (btn_sync == 1'b0 && counter != 20'd500_000) // 10ms @ 50MHz
counter <= counter + 1;
else if (btn_sync == 1'b1 && counter != 0)
counter <= counter - 1;
else
counter <= 0;
end
always @(posedge clk) begin
if (counter >= 20'd499_000 && btn_sync == 0)
valid_edge <= 1;
else
valid_edge <= 0;
end
endmodule
```
然后所有按键都通过此模块预处理。
---
#### 🔴 6. **整点报时功能缺失**
题目要求:
> 当计时到 59'50" 时开始报时(LED 闪烁代替蜂鸣器)
你完全没有实现!
🔧 添加逻辑:
```verilog
always @(posedge clk_1hz) begin
if (min == 59 && sec >= 50)
led2 <= ~led2; // LED 快速闪烁模拟报时
else
led2 <= 1'b0;
end
```
注意:原 `led2` 已用于指示 clock/calendar 显示模式,冲突!
🔧 建议新增一个 `output reg alarm_led` 专用于报警。
---
#### 🔴 7. **BCD 编码任务函数冗余且可优化**
`task bcd` 可以简化为组合逻辑,或使用算法转换(如 double dabble),但现在这样也可以接受。
不过注意输入是 `[5:0] bin`,最大支持 63,没问题。
但输出 `bcd1` 和 `bcd0` 是十位和个位,命名合理。
✅ 可保留,但建议改为 `always @(*)` 实现。
---
#### 🔴 8. **数码管显示冲突 & 缺少小数点/分隔符**
你用 `hex0~hex7` 分别显示不同部分,但未考虑:
- 小时与分钟之间是否有冒号?
- 是否需要熄灭某些位?
目前没有看到控制使能端(`digit_select`),可能开发板是静态驱动,可以接受。
---
### ✅ 修改后的关键代码片段整合(重点修复)
---
#### ✅ 主模块修正版(精简核心逻辑)
```verilog
module ymm579(
input clk, // CLOCK_50
input reset_sw, // SW0 - 复位
input mode_btn, // SW1 - 切换时钟/日历显示
input set_btn, // SW2 - 设置按键(题目中的按键0)
input select_btn, // SW3 - 选择字段(题目中的按键1)
input inc_btn, // SW4 - 增加数值(题目中的按键2)
output reg led0, // 指示是否处于设置状态
output reg led_alarm, // 报警LED(替代蜂鸣器)
output reg [6:0] hex0,hex1,hex2,hex3,hex4,hex5,hex6,hex7
);
//======================== 时钟分频 ========================
wire clk_1hz;
reg [25:0] clk_div;
always @(posedge clk) begin
if (clk_div >= 26'd49_999_999) begin
clk_div <= 0;
end else begin
clk_div <= clk_div + 1;
end
end
assign clk_1hz = (clk_div == 26'd49_999_999);
//======================== 按键去抖 ========================
wire valid_set, valid_select, valid_inc;
debounce deb1(.clk(clk), .btn(set_btn), .valid_edge(valid_set));
debounce deb2(.clk(clk), .btn(select_btn), .valid_edge(valid_select));
debounce deb3(.clk(clk), .btn(inc_btn), .valid_edge(valid_inc));
//======================== 状态定义 ========================
typedef enum logic [1:0] {
NORMAL,
SET_DATE,
SET_TIME
} state_t;
state_t state_reg;
reg [2:0] field_sel; // 0:year, 1:month, 2:day, 3:hour, 4:min
//======================== 时间寄存器 ========================
reg [5:0] sec, min, hour;
reg [4:0] day;
reg [3:0] month;
reg [3:0] year0, year1, year2, year3;
reg [15:0] current_year;
//======================== 复位与主逻辑 ========================
always @(posedge clk_1hz) begin
if (reset_sw) begin
sec <= 0;
min <= 0;
hour <= 0;
day <= 1;
month <= 1;
year0 <= 0; year1 <= 0; year2 <= 0; year3 <= 0;
current_year <= 0;
state_reg <= NORMAL;
field_sel <= 0;
led0 <= 0;
led_alarm <= 0;
end else begin
// 默认关闭报警
led_alarm <= 0;
// 整点前10秒开始报警(59'50" ~ 59'59")
if (min == 59 && sec >= 50)
led_alarm <= ~led_alarm; // 闪烁
case (state_reg)
NORMAL: begin
sec <= sec + 1;
if (sec >= 6'd60) begin
sec <= 0;
min <= min + 1;
if (min >= 6'd60) begin
min <= 0;
hour <= hour + 1;
if (hour >= 6'd24) begin
hour <= 0;
// 日+1,并处理进月、进年
day <= day + 1;
if (day >= get_max_day(month, current_year)) begin
day <= 1;
month <= month + 1;
if (month >= 12) begin
month <= 1;
current_year <= current_year + 1;
// 更新 year BCD 显示
{year3,year2,year1,year0} <= bcd_add_one({year3,year2,year1,year0});
end
end
end
end
end
end
SET_DATE, SET_TIME: begin
if (valid_select) begin
field_sel <= field_sel + 1;
if ((state_reg == SET_DATE && field_sel >= 3) ||
(state_reg == SET_TIME && field_sel < 3))
field_sel <= 0;
end
if (valid_inc) begin
case (field_sel)
0: begin // year
current_year <= current_year + 1;
{year3,year2,year1,year0} <= bcd_add_one({year3,year2,year1,year0});
end
1: month <= (month == 12) ? 1 : month + 1;
2: day <= (day == get_max_day(month, current_year)) ? 1 : day + 1;
3: hour <= (hour == 23) ? 0 : hour + 1;
4: min <= (min == 59) ? 0 : min + 1;
endcase
end
end
endcase
// 模式切换由 set_btn 控制
if (valid_set) begin
case (state_reg)
NORMAL: state_reg <= SET_DATE;
SET_DATE: state_reg <= SET_TIME;
SET_TIME: state_reg <= NORMAL;
endcase
led0 <= !led0; // 指示设置状态
end
end
end
// 辅助函数:获取当月最大天数
function integer get_max_day;
input [3:0] m;
input [15:0] y;
begin
case (m)
1,3,5,7,8,10,12: get_max_day = 31;
4,6,9,11: get_max_day = 30;
2: if (((y%4==0)&&(y%100!=0)) || (y%400==0))
get_max_day = 29;
else
get_max_day = 28;
default: get_max_day = 31;
endcase
end
endfunction
// BCD 加一函数(四位 BCD 数字)
function [15:0] bcd_add_one;
input [15:0] bcd;
integer i;
reg [3:0] t[3:0];
begin
t[0] = bcd[3:0]; t[1] = bcd[7:4]; t[2] = bcd[11:8]; t[3] = bcd[15:12];
for (i = 0; i < 4; i = i + 1) begin
t[i] = t[i] + 1;
if (t[i] > 9) begin
t[i] = 0;
if (i < 3) continue;
end
break;
end
bcd_add_one = {t[3],t[2],t[1],t[0]};
end
endfunction
//======================== 数码管显示 ========================
reg [3:0] hr1, hr0, min1, min0, sec1, sec0;
reg [3:0] d1, d0, m1, m0;
always @(*) begin
bcd_conv(hour, hr1, hr0);
bcd_conv(min, min1, min0);
bcd_conv(sec, sec1, sec0);
bcd_conv(day, d1, d0);
bcd_conv(month, m1, m0);
end
always @(posedge clk_1hz) begin
if (state_reg != NORMAL) begin // 设置状态下显示对应内容
case (field_sel)
0: begin // year
display(year0, hex0); display(year1, hex1); display(year2, hex2); display(year3, hex3);
display(4'd0, hex4); display(4'd0, hex5); display(4'd0, hex6); display(4'd0, hex7);
end
1: begin // month
bcd_conv(month, m1, m0);
display(m0, hex4); display(m1, hex5);
end
2: begin // day
bcd_conv(day, d1, d0);
display(d0, hex6); display(d1, hex7);
end
3: begin // hour
bcd_conv(hour, hr1, hr0);
display(hr0, hex4); display(hr1, hex5);
end
4: begin // min
bcd_conv(min, min1, min0);
display(min0, hex2); display(min1, hex3);
end
endcase
end else begin
// 正常显示时钟或日历
if (mode_btn) begin // 显示时钟
display(hr0, hex4); display(hr1, hex5);
display(min0, hex2); display(min1, hex3);
display(sec0, hex0); display(sec1, hex1);
display(4'd0, hex6); display(4'd0, hex7);
end else begin // 显示日历
display(d0, hex6); display(d1, hex7);
display(m0, hex4); display(m1, hex5);
display(year0, hex0); display(year1, hex1);
display(year2, hex2); display(year3, hex3);
end
end
end
// BCD 转换子程序
task bcd_conv;
input [5:0] bin;
output [3:0] ten, unit;
begin
ten = bin / 10;
unit = bin % 10;
end
endtask
// 七段译码
task display;
input [3:0] data;
output [6:0] seg;
begin
case (data)
4'h0: seg = 7'b1000000;
4'h1: seg = 7'b1111001;
4'h2: seg = 7'b0100100;
4'h3: seg = 7'b0110000;
4'h4: seg = 7'b0011001;
4'h5: seg = 7'b0010010;
4'h6: seg = 7'b0000010;
4'h7: seg = 7'b1111000;
4'h8: seg = 7'b0000000;
4'h9: seg = 7'b0010000;
default: seg = 7'b1111111;
endcase
end
endtask
endmodule
// 按键消抖模块
module debounce (
input clk,
input btn,
output reg valid_edge
);
reg [19:0] counter;
reg btn_prev1, btn_prev2;
always @(posedge clk) begin
btn_prev1 <= btn;
btn_prev2 <= btn_prev1;
end
always @(posedge clk) begin
if (!btn_prev2) begin // 按键按下
if (counter < 20'd500000) // ~10ms
counter <= counter + 1;
end else begin
counter <= 0;
end
end
always @(posedge clk) begin
valid_edge <= 0;
if (counter == 20'd499999)
valid_edge <= 1;
end
endmodule
```
---
### ✅ 总结修改点
| 问题 | 修改 |
|------|------|
| 复位年份错误 | 改为 `00-01-01 00:00:00` |
| 无年份进位 | 添加 `current_year` 和进位逻辑 |
| 闰年判断错误 | 使用完整条件判断 |
| 按键无防抖 | 添加 `debounce` 模块 |
| 设置模式非三态切换 | 使用 FSM 实现三态循环 |
| 无整点报时 | 添加 `min==59 && sec>=50` 闪烁 |
| BCD 转换效率低 | 改用 `/` 和 `%` 组合逻辑 |
---
###