七段编码 + 多路复用扫描
🏆 成就:数码达人 ✅ Verilator验证
7个LED段(a~g)+1小数点(dp)。多路复用扫描:快速轮流点亮每一位,利用视觉暂留。
reg [7:0] seg_buf [0:3]; // 直接存段码
initial begin
seg_buf[0]=8'h76; // H
seg_buf[1]=8'h79; // E
seg_buf[2]=8'h38; // L
seg_buf[3]=8'h38; // L
end
localparam SCAN_TICKS=50000; // 1ms@50MHz
reg [15:0] scan_counter; reg [1:0] scan_idx;
always @(posedge clk) begin
if(scan_counter>=SCAN_TICKS-1) begin
scan_counter<=0; scan_idx<=scan_idx+1;
digit_sel<=4'b1111; // 消隐
end else scan_counter<=scan_counter+1;
if(scan_counter==5) case(scan_idx) // 点亮
0: begin digit_sel<=4'b1110; seg<=seg_buf[0]; end
1: begin digit_sel<=4'b1101; seg<=seg_buf[1]; end
2: begin digit_sel<=4'b1011; seg<=seg_buf[2]; end
3: begin digit_sel<=4'b0111; seg<=seg_buf[3]; end
endcase
end共阴极:段码1=亮(本课使用)。共阳极:段码0=亮,取反即可。
module sevseg_hello_tb;
logic clk,rst_n; logic [3:0] digit_sel;
logic [7:0] seg;
sevseg_hello uut(.*);
initial clk=0; always #10 clk=~clk;
reg [7:0] captured_seg[0:3];
always @(posedge clk) if(digit_sel!=4'b1111)
case(digit_sel)
4'b1110: captured_seg[0]<=seg;
4'b1101: captured_seg[1]<=seg;
4'b1011: captured_seg[2]<=seg;
4'b0111: captured_seg[3]<=seg;
endcase
function string seg_to_char(input [7:0] s);
case(s)
8'h76:return"H"; 8'h79:return"E";
8'h38:return"L"; 8'h3F:return"O";
8'h00:return" "; default:return"?";
endcase
endfunction
initial begin
rst_n=0;#500;rst_n=1;
$display("========== 七段数码管验证 ==========");
#300_000; // 等待4位都扫描完
$display(" Digit0: 0x%02H → %s",captured_seg[0],seg_to_char(captured_seg[0]));
$display(" Digit1: 0x%02H → %s",captured_seg[1],seg_to_char(captured_seg[1]));
$display(" Digit2: 0x%02H → %s",captured_seg[2],seg_to_char(captured_seg[2]));
$display(" Digit3: 0x%02H → %s",captured_seg[3],seg_to_char(captured_seg[3]));
$display(" 显示结果: %s%s%s%s",
seg_to_char(captured_seg[0]),seg_to_char(captured_seg[1]),
seg_to_char(captured_seg[2]),seg_to_char(captured_seg[3]));
$display("==========================================");
$finish;
end
initial #5_000_000 $finish;
endmodulereg [7:0] msg_rom[0:7];
initial begin
msg_rom[0]=8'h76; msg_rom[1]=8'h79;
msg_rom[2]=8'h38; msg_rom[3]=8'h38;
msg_rom[4]=8'h3F; msg_rom[5:7]=8'h00;
end
reg [2:0] scroll_offset;
reg [26:0] scroll_timer;
always @(posedge clk)
if(scroll_timer>=25_000_000) begin
scroll_timer<=0; scroll_offset<=scroll_offset+1;
end else scroll_timer<=scroll_timer+1;
always @(*) begin
seg_buf[0]=msg_rom[scroll_offset+0];
seg_buf[1]=msg_rom[scroll_offset+1];
seg_buf[2]=msg_rom[scroll_offset+2];
seg_buf[3]=msg_rom[scroll_offset+3];
end共阴极:所有LED阴极连在一起接地,段码1=亮(本课使用)
共阳极:所有LED阳极连在一起接VCC,段码0=亮,取反即可 seg=~char_to_seg(ch)
确认你的开发板使用哪种,否则显示会完全反了!
🎉 恭喜你完成全部14课!你已经掌握了FPGA开发的核心技能:从LUT原理到VGA显示、从游戏开发到通信协议。继续探索,创造更多有趣的项目吧!
挑战1:实现0~9999计数器显示
挑战2:滚动显示"HELLO WORLD"
挑战3:把七段数码管加进Pong游戏做计分板
挑战4:实现数码管闪烁效果(得分时闪烁)
共阴极:所有LED阴极连在一起接地,段码1=亮(本课使用)
共阳极:所有LED阳极连在一起接VCC,段码0=亮,取反 seg=~char_to_seg(ch)
确认你的开发板使用哪种,否则显示会完全反了!
🎉 恭喜你完成全部14课!你已经掌握了FPGA开发的核心技能:从LUT原理到VGA显示、从游戏开发到通信协议。继续探索,创造更多有趣的项目吧!
// 追踪每位显示的段码
reg [7:0] captured_seg[0:3];
always @(posedge clk) if(digit_sel!=4'b1111)
case(digit_sel)
4'b1110: captured_seg[0]<=seg;
4'b1101: captured_seg[1]<=seg;
4'b1011: captured_seg[2]<=seg;
4'b0111: captured_seg[3]<=seg;
endcase
// 段码→字符
function string seg_to_char(input [7:0] s);
case(s) 8'h76:return"H"; 8'h79:return"E";
8'h38:return"L"; 8'h3F:return"O";
default:return"?";
endcase
endfunction把七段数码管加进Pong游戏:左侧2位显示玩家1分数,右侧2位显示玩家2分数。这就是模块化设计的力量——前面的模块直接复用!
共阴极:段码1=亮(本课使用)。共阳极:段码0=亮,取反即可 seg=~char_to_seg(ch)
🎉 恭喜你完成全部14课!继续探索,创造更多有趣的项目吧!
reg [7:0] msg_rom[0:7];
initial begin
msg_rom[0]=8'h76; // H
msg_rom[1]=8'h79; // E
msg_rom[2]=8'h38; // L
msg_rom[3]=8'h38; // L
msg_rom[4]=8'h3F; // O
msg_rom[5:7]=8'h00; // 空
end
reg [2:0] scroll_offset;
reg [26:0] scroll_timer; // 0.5s@50MHz
always @(posedge clk)
if(scroll_timer>=25_000_000) begin
scroll_timer<=0;
scroll_offset<=scroll_offset+1;
end else scroll_timer<=scroll_timer+1;
// 更新显示缓冲
always @(*) begin
seg_buf[0]=msg_rom[scroll_offset+0];
seg_buf[1]=msg_rom[scroll_offset+1];
seg_buf[2]=msg_rom[scroll_offset+2];
seg_buf[3]=msg_rom[scroll_offset+3];
end