Files
risc-v-cpu/pipelined-cpu/srcs/rtl/cpu21_vga.v
T
Alpha df7b3aee6d fix(rtl): 清除被工具反复注入到 .v 末尾的 README 残行; 新增 RTL 文本自检脚本
- cpu21_pointer.v / cpu21_ps2_mouse.v / cpu21_vga.v 末尾多出一行 README 正文
  (Tcl ...), 会导致 Vivado: ERROR: [Synth 8-2715] non-printable character 0xe4
- 新增 pipelined-cpu/tools/check_rtl_text.ps1: 综合前秒级自检, 检出代码位置上
  的非 ASCII 文本/粘贴进来的正文行
2026-09-18 16:02:02 +08:00

242 lines
11 KiB
Verilog
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// cpu21_vga —— 640x480@60 的"格子"显示控制器(显存端口接板级顶层的双口 RAM)。
//
// CPU 不直接操作像素:它只通过 MMIO 往显示缓冲区里写"每格 1 字节"(字形 + 颜色),
// 本模块按像素扫描把格子展开成画面(8x8 字模放大 2 倍填满 16x16 的格子)。
// 这样一格只要 1 次字节写,刷屏开销极小。
//
// 网格:40 列 x 30 行,每格 16x16 像素 = 640x480。
//
// 时钟:本模块工作在 CPU 时钟(clk)下,用 tick(每 2 个 clk 一个使能)产生
// 25 MHz 的像素节拍。**不引入第二个时钟域**,因此显示缓冲区的读(本模块)
// 与写(MMIO,同一时钟)之间没有跨时钟域问题。
//
// 光标:由 CURSOR 寄存器的 (x,y) 决定,在对应格子上画一个白色闪动的方框
// (CTRL[0] 控制显示与否)。缓冲区字节的 bit7 在本模块中不使用(保留)。
// -----------------------------------------------------------------------------
module cpu21_vga #(
parameter integer H_VISIBLE = 640,
parameter integer H_FP = 16,
parameter integer H_SYNC = 96,
parameter integer H_BP = 48,
parameter integer V_VISIBLE = 480,
parameter integer V_FP = 10,
parameter integer V_SYNC = 2,
parameter integer V_BP = 33,
parameter integer COLS = 40,
parameter integer ROWS = 30
) (
input wire clk,
input wire reset,
input wire tick, // 25 MHz 像素使能(每 2 个 clk 一拍)
// 显示缓冲区读端口
output wire [ 9:0] fb_raddr_o,
input wire [31:0] fb_word_i,
// 控制
input wire [31:0] ctrl_i,
input wire [31:0] cursor_i,
// VGA 输出
output reg [ 3:0] vga_r,
output reg [ 3:0] vga_g,
output reg [ 3:0] vga_b,
output reg vga_hs,
output reg vga_vs,
output reg frame_tick_o
);
localparam integer H_TOTAL = H_VISIBLE + H_FP + H_SYNC + H_BP; // 800
localparam integer V_TOTAL = V_VISIBLE + V_FP + V_SYNC + V_BP; // 525
// ---------------------------------------------------------------------------
// 扫描计数
// ---------------------------------------------------------------------------
reg [9:0] hx, vy;
always @(posedge clk or posedge reset) begin
if (reset) begin
hx <= 10'd0;
vy <= 10'd0;
end
else if (tick) begin
if (hx == H_TOTAL - 1) begin
hx <= 10'd0;
vy <= (vy == V_TOTAL - 1) ? 10'd0 : (vy + 10'd1);
end
else begin
hx <= hx + 10'd1;
end
end
end
wire de = (hx < H_VISIBLE) && (vy < V_VISIBLE);
// 同步信号在 tick 上寄存输出
wire hs_n = ~((hx >= (H_VISIBLE + H_FP)) && (hx < (H_VISIBLE + H_FP + H_SYNC)));
wire vs_n = ~((vy >= (V_VISIBLE + V_FP)) && (vy < (V_VISIBLE + V_FP + V_SYNC)));
// ---------------------------------------------------------------------------
// 格子索引与缓冲区读出
// ---------------------------------------------------------------------------
wire [5:0] ccol = hx[9:4]; // 0..39
wire [4:0] crow = vy[8:4]; // 0..29
wire [10:0] cidx = (crow << 5) + (crow << 3) + ccol; // row*40 + col
assign fb_raddr_o = cidx[10:2]; // 字地址(1 字 = 4 格)
wire [1:0] lane = cidx[1:0];
wire [31:0] cell_word = fb_word_i;
wire [7:0] cell_byte = cell_word >> {lane, 3'b000};
wire [3:0] glyph = cell_byte[3:0];
wire [2:0] gcolor = cell_byte[6:4];
// 全局背景色(字模点为 0 时显示的颜色)
wire [2:0] bgcolor = ctrl_i[7:4];
// ---------------------------------------------------------------------------
// 8x8 字模(bit7 = 最左像素),放大 2 倍填满 16x16 的格子
// ---------------------------------------------------------------------------
wire [2:0] fx = hx[3:1];
wire [2:0] fy = vy[3:1];
function [7:0] font_row;
input [3:0] g;
input [2:0] r;
begin
case (g)
4'd0:
font_row = 8'h00; // 空白
4'd1:
font_row = (r == 3'd0) ? 8'h18 : (r == 3'd1) ? 8'h38 : (r == 3'd2) ? 8'h18 :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h18 :
(r == 3'd6) ? 8'h7E : 8'h00; // 1
4'd2:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
(r == 3'd3) ? 8'h0C : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h30 :
(r == 3'd6) ? 8'h7E : 8'h00; // 2
4'd3:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
(r == 3'd3) ? 8'h1C : (r == 3'd4) ? 8'h06 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 3
4'd4:
font_row = (r == 3'd0) ? 8'h0C : (r == 3'd1) ? 8'h1C : (r == 3'd2) ? 8'h3C :
(r == 3'd3) ? 8'h6C : (r == 3'd4) ? 8'h7E : (r == 3'd5) ? 8'h0C :
(r == 3'd6) ? 8'h0C : 8'h00; // 4
4'd5:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h60 : (r == 3'd2) ? 8'h7C :
(r == 3'd3) ? 8'h06 : (r == 3'd4) ? 8'h06 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 5
4'd6:
font_row = (r == 3'd0) ? 8'h1C : (r == 3'd1) ? 8'h30 : (r == 3'd2) ? 8'h60 :
(r == 3'd3) ? 8'h7C : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 6
4'd7:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h0C :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h18 :
(r == 3'd6) ? 8'h18 : 8'h00; // 7
4'd8:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h3C : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 8
4'd9:
font_row = (r == 3'd0) ? 8'hFF : (r == 3'd1) ? 8'h81 : (r == 3'd2) ? 8'hBD :
(r == 3'd3) ? 8'hA5 : (r == 3'd4) ? 8'hA5 : (r == 3'd5) ? 8'hBD :
(r == 3'd6) ? 8'h81 : 8'hFF; // 未揭开的方块
4'd10:
font_row = (r == 3'd0) ? 8'h40 : (r == 3'd1) ? 8'h70 : (r == 3'd2) ? 8'h78 :
(r == 3'd3) ? 8'h70 : (r == 3'd4) ? 8'h40 : (r == 3'd5) ? 8'h40 :
(r == 3'd6) ? 8'h40 : 8'h00; // 旗
4'd11:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h7E : (r == 3'd2) ? 8'hFF :
(r == 3'd3) ? 8'hFF : (r == 3'd4) ? 8'hFF : (r == 3'd5) ? 8'hFF :
(r == 3'd6) ? 8'h7E : 8'h3C; // 雷
4'd12:
font_row = (r == 3'd0) ? 8'h81 : (r == 3'd1) ? 8'h42 : (r == 3'd2) ? 8'h24 :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h24 :
(r == 3'd6) ? 8'h42 : 8'h81; // 爆炸
4'd13:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
(r == 3'd3) ? 8'h0C : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h00 :
(r == 3'd6) ? 8'h18 : 8'h00; // 问号
4'd14:
font_row = (r == 3'd3) ? 8'hFF : (r == 3'd4) ? 8'hFF : 8'h00; // 横线
4'd15:
font_row = 8'hFF; // 实心
default:
font_row = 8'h00;
endcase
end
endfunction
wire [7:0] frow = font_row(glyph, fy);
wire glyph_px = frow[3'd7 - fx]; // 1 = 画字模点
// ---------------------------------------------------------------------------
// 光标框(闪动)
// ---------------------------------------------------------------------------
reg [24:0] blink_q;
always @(posedge clk or posedge reset) begin
if (reset)
blink_q <= 25'd0;
else if (tick)
blink_q <= blink_q + 25'd1;
end
wire blink = blink_q[24];
wire cursor_here = (cursor_i[4:0] == ccol) && (cursor_i[12:8] == crow);
wire on_border = (hx[3:0] == 4'd0) || (hx[3:0] == 4'd15) ||
(vy[3:0] == 4'd0) || (vy[3:0] == 4'd15);
wire cursor_box = cursor_here && blink && ctrl_i[0] && on_border;
// ---------------------------------------------------------------------------
// 颜色(3 位调色板 -> 4:4:4 RGB)
// ---------------------------------------------------------------------------
function [11:0] pal;
input [2:0] c;
begin
case (c)
3'd0:
pal = {4'h0, 4'h0, 4'h0}; // 黑
3'd1:
pal = {4'h0, 4'h0, 4'hA}; // 深蓝
3'd2:
pal = {4'h0, 4'hA, 4'h0}; // 绿
3'd3:
pal = {4'h0, 4'hA, 4'hA}; // 青
3'd4:
pal = {4'hA, 4'h0, 4'h0}; // 红
3'd5:
pal = {4'hA, 4'h0, 4'hA}; // 洋红
3'd6:
pal = {4'hA, 4'hA, 4'h0}; // 黄
default:
pal = {4'hA, 4'hA, 4'hA}; // 浅灰
endcase
end
endfunction
wire [2:0] px_color = glyph_px ? gcolor : bgcolor;
wire [2:0] px_final = cursor_box ? 3'd7 : px_color;
wire [11:0] px_rgb = de ? pal(px_final) : 12'h000;
always @(posedge clk or posedge reset) begin
if (reset) begin
{vga_r, vga_g, vga_b} <= 12'h000;
vga_hs <= 1'b1;
vga_vs <= 1'b1;
frame_tick_o <= 1'b0;
end
else if (tick) begin
{vga_r, vga_g, vga_b} <= px_rgb;
vga_hs <= hs_n;
vga_vs <= vs_n;
frame_tick_o <= (hx == H_TOTAL - 1) && (vy == V_TOTAL - 1);
end
end
endmodule
`default_nettype wire