将前面28课实现的所有模块集成为一台完整的8位复古电脑!CPU、内存、VGA、键盘、UART、定时器、GPIO、中断控制器、监控程序、BASIC解释器——所有部件连接在一起,通过系统总线协同工作。这是最激动人心的时刻:散落的零件终于组装成一台真正的电脑。
// retro8_top.v - Retro8 顶层集成模块
module retro8_top (
input wire clk_25mhz, // 25MHz主时钟
input wire rst_n,
// VGA接口
output wire [3:0] vga_r, vga_g, vga_b,
output wire vga_hs, vga_vs,
// PS/2键盘
input wire ps2_clk, ps2_data,
// UART串口
output wire uart_tx,
input wire uart_rx,
// GPIO
inout wire [7:0] gpio_pins
);
// ============================================================
// 系统总线信号
// ============================================================
wire [15:0] cpu_addr;
wire [7:0] cpu_data_out;
wire [7:0] cpu_data_in;
wire cpu_rw;
wire cpu_irq;
wire cpu_int_ack;
wire [2:0] cpu_int_vector;
// ============================================================
// 地址解码器
// ============================================================
wire cs_ram = (cpu_addr < 16'h7800);
wire cs_vga_buf = (cpu_addr >= 16'h7800) & (cpu_addr < 16'h8000);
wire cs_rom = (cpu_addr >= 16'h8000) & (cpu_addr < 16'hFA00);
wire cs_char_rom = (cpu_addr >= 16'hFA00) & (cpu_addr < 16'hFF00);
wire cs_uart = (cpu_addr >= 16'hFF00) & (cpu_addr < 16'hFF02);
wire cs_kbd = (cpu_addr >= 16'hFF02) & (cpu_addr < 16'hFF04);
wire cs_timer = (cpu_addr >= 16'hFF04) & (cpu_addr < 16'hFF07);
wire cs_gpio = (cpu_addr >= 16'hFF10) & (cpu_addr < 16'hFF14);
wire cs_int_ctl = (cpu_addr >= 16'hFF20) & (cpu_addr < 16'hFF24);
wire cs_io = cs_uart | cs_kbd | cs_timer | cs_gpio | cs_int_ctl;
// ============================================================
// CPU核心(第4-8课实现)
// ============================================================
// cpu_core uut_cpu ( ... );
// ============================================================
// 内存子系统
// ============================================================
// RAM: 30KB $0000-$77FF
wire [7:0] ram_rdata;
// ram_module uut_ram ( ... );
// ROM: 30KB $8000-$F9FF
wire [7:0] rom_rdata;
// rom_module uut_rom ( ... );
// ============================================================
// VGA文本模式控制器(第20课)
// ============================================================
wire [7:0] vga_char_data, vga_attr_data, vga_font_data;
wire [11:0] vga_char_addr, vga_attr_addr;
wire [10:0] vga_font_addr;
wire [11:0] vga_cursor_pos;
vga_text_mode uut_vga (
.clk(clk_25mhz),
.rst_n(rst_n),
.vga_r(vga_r), .vga_g(vga_g), .vga_b(vga_b),
.vga_hs(vga_hs), .vga_vs(vga_vs),
.char_data(vga_char_data), .char_addr(vga_char_addr),
.attr_data(vga_attr_data), .attr_addr(vga_attr_addr),
.font_data(vga_font_data), .font_addr(vga_font_addr),
.cursor_pos(vga_cursor_pos),
.cursor_on(1'b1)
);
// ============================================================
// PS/2键盘控制器(第19课)
// ============================================================
wire [7:0] kbd_scan_code;
wire kbd_scan_ready, kbd_scan_error;
wire kbd_irq;
ps2_keyboard uut_kbd (
.clk(clk_25mhz), .rst_n(rst_n),
.ps2_clk(ps2_clk), .ps2_data(ps2_data),
.scan_code(kbd_scan_code),
.scan_ready(kbd_scan_ready),
.scan_error(kbd_scan_error),
.scan_ack(1'b0)
);
// ============================================================
// UART控制器(第21课)
// ============================================================
wire [7:0] uart_rx_data;
wire uart_rx_ready, uart_tx_done;
wire uart_irq;
uart uut_uart (
.clk(clk_25mhz), .rst_n(rst_n),
.baud_div(16'd217), // 115200 @ 25MHz
.tx_data(cpu_data_out), .tx_start(1'b0),
.tx_busy(), .tx_done(uart_tx_done),
.rx_data(uart_rx_data), .rx_ready(uart_rx_ready),
.rx_error(), .rx_ack(1'b0),
.uart_tx(uart_tx), .uart_rx(uart_rx)
);
// ============================================================
// 定时器(第22课)
// ============================================================
wire timer_irq_out;
timer uut_timer (
.clk(clk_25mhz), .rst_n(rst_n),
.cs(cs_timer), .rw(cpu_rw),
.reg_addr(cpu_addr[0]),
.cpu_data_in(cpu_data_out),
.cpu_data_out(),
.timer_irq(timer_irq_out),
.timer_out()
);
// ============================================================
// GPIO控制器(第23课)
// ============================================================
wire gpio_irq_out;
gpio uut_gpio (
.clk(clk_25mhz), .rst_n(rst_n),
.cs(cs_gpio), .rw(cpu_rw),
.reg_addr(cpu_addr[1:0]),
.cpu_data_in(cpu_data_out),
.cpu_data_out(),
.gpio_pins(gpio_pins),
.gpio_irq(gpio_irq_out)
);
// ============================================================
// 中断控制器(第24课)
// ============================================================
wire [7:0] irq_sources;
assign irq_sources = {
1'b0, // IRQ7: NMI
1'b0, // IRQ6: 保留
1'b0, // IRQ5: 保留
gpio_irq_out, // IRQ4: GPIO
1'b0, // IRQ3: VGA
uart_irq, // IRQ2: UART
kbd_irq, // IRQ1: 键盘
timer_irq_out // IRQ0: 定时器
};
interrupt_controller uut_int_ctl (
.clk(clk_25mhz), .rst_n(rst_n),
.cs(cs_int_ctl), .rw(cpu_rw),
.reg_addr(cpu_addr[1:0]),
.cpu_data_in(cpu_data_out),
.cpu_data_out(),
.irq_sources(irq_sources),
.int_request(cpu_irq),
.int_vector(cpu_int_vector),
.int_ack(cpu_int_ack)
);
endmodule
| 地址范围 | 大小 | 用途 | 读写 |
|---|---|---|---|
| $0000-$77FF | 30KB | RAM(用户程序+数据) | R/W |
| $7800-$7FFF | 2KB | VGA文本+属性缓冲 | R/W |
| $8000-$F9FF | 30KB | ROM(监控+BASIC) | R |
| $FA00-$FEFF | 1.25KB | 字符ROM | R |
| $FF00 | 1B | UART数据 | R/W |
| $FF01 | 1B | UART状态/控制 | R/W |
| $FF02 | 1B | 键盘数据(FIFO) | R |
| $FF03 | 1B | 键盘状态 | R |
| $FF04 | 1B | 定时器计数低 | R/W |
| $FF05 | 1B | 定时器计数高 | R/W |
| $FF06 | 1B | 定时器控制 | R/W |
| $FF10 | 1B | GPIO方向 | R/W |
| $FF11 | 1B | GPIO数据 | R/W |
| $FF12 | 1B | GPIO中断使能 | R/W |
| $FF13 | 1B | GPIO边沿选择 | R/W |
| $FF20 | 1B | 中断屏蔽寄存器 | R/W |
| $FF21 | 1B | 中断挂起寄存器 | R/W1C |
| $FF22 | 1B | 中断服务寄存器 | R/W1C |
| $FF23 | 1B | 中断模式寄存器 | R/W |
; init.asm - Retro8 系统初始化
.ORG $8000
RESET:
; 1. 初始化栈指针
LDX #$FF
TXS
; 2. 初始化UART - 115200波特率
LDA #$C9 ; 分频低字节 (217 = 0x00C9)
STA $FF01
LDA #$00
STA $FF02 ; 分频高字节
; 3. 清空VGA屏幕
LDA #$20 ; 空格字符
LDX #$00
CLEAR_LOOP:
STA $7800,X ; 字符缓冲
LDA #$07 ; 白字黑底属性
STA $7800+256,X
LDA #$20
INX
BNE CLEAR_LOOP
; 4. 初始化定时器 - 10Hz心跳
LDA #$08 ; reload低字节 ($3D08)
STA $FF04
LDA #$3D ; reload高字节
STA $FF05
LDA #$07 ; EN+循环+IRQ+/64
STA $FF06
; 5. 使能中断
LDA #$07 ; 使能IRQ0(定时器)+IRQ1(键盘)+IRQ2(UART)
STA $FF20
CLI ; CPU开中断
; 6. 进入BASIC解释器
JMP BASIC_START
实现上电自检(Power-On Self Test):检查RAM完整性(写/读每个地址)、验证ROM校验和、检测各I/O设备是否存在。如果自检失败,通过VGA显示错误码(如"RAM ERR @ $2A3F"),通过UART发送详细信息。
VGA控制器需要持续读取显存来刷新屏幕。使用DMA通道自动从显存搬运数据到VGA控制器,避免CPU访问显存时与VGA竞争。设计DMA请求/应答握手协议。
利用10Hz定时器中断,实现最简单的协作式多任务:两个BASIC程序交替执行,每次中断切换任务。每个任务有独立的变量空间和PC。这需要上下文保存/恢复机制。
编写完整的Verilator测试平台:实例化retro8_top,模拟PS/2键盘输入和UART数据,验证VGA输出、中断响应和程序执行的完整流程。这是最接近真实硬件行为的仿真。
你完成了8位复古电脑的完整系统集成!这包括:
所有零件终于组装在一起——你的8位电脑真正"活"了!