`timescale 1ns / 1ps `default_nettype none // ----------------------------------------------------------------------------- // MMIO:把外设寄存器映射到数据存储器地址空间。 // // 地址映射(字节地址,都是"字对齐"译码:同一 32 位寄存器内的任意字节偏移都命中, // 具体改哪几个字节由 wstrb_i 决定,与原来 LED 的语义一致): // // 0x0000_1000 LED 数据寄存器(**保持原有语义不变**,led_valid_o 拉高一拍) // 0x0000_1004 七段数码管:8 位十六进制数(每 4 位一个数码管,低 4 位 = 最右) // 0x0000_1100 + 4*k 鼠标寄存器组(见下) // 0x0000_1140 + 4*k 显示控制器寄存器(见下) // 0x0000_2000 - 0x27FF 显存窗口:40x30 格、每格 1 字节(只用到前 1200 字节) // // 鼠标寄存器(基址 0x1100): // +0x00 STATUS RO [0] FIFO 非空 [5:1] 项数 [6] 中断使能 [7] FIFO 满 // +0x04 DATA RO [7:0] dx [15:8] dy [20:16] 按键(读无副作用) // +0x08 ACK WO 写任意值 -> 弹出 FIFO 一项 // +0x0C CTRL RW [0] 中断使能(复位默认 1) // +0x10 INPUT RO [15:0] 拨码开关 [20:16] 按键(原样) // +0x14 TIME RO 自由运行计数器(随机种子) // +0x18 FRAME RO VGA 帧计数 // +0x1C EVCNT RO 累计鼠标事件数 // +0x20 LEVEL RW [0] 写 1 -> 拉起 level_irq_o(软件中断,用作"关卡入口"); // 关卡程序返回前要写 0 清掉,否则下次没有上升沿 // +0x24 MAGIC RO 0xCAFE0002(自检用:读得到说明 MMIO 活着) // // 显示控制器寄存器(基址 0x1140): // +0x00 CTRL RW [0] 光标可见 [6:4] 背景色(3 位调色板) // +0x04 CURSOR RW [5:0] 列(0..39) [13:8] 行(0..29) // +0x08 MAGIC RO 0xCAFE0001 // // 显存本体不在本模块里:写操作通过 fb_* 端口直接送到板级顶层的双口 RAM(VGA 读 // 同一块 RAM),读操作把 fb_rdata_i 组合回读数据,保持"组合读"的时序不变。 // // 两个中断源: // mouse_irq_o = 中断使能 && 事件 FIFO 非空(接 irq_i[0]) // level_irq_o = LEVEL 寄存器(软件中断,接 irq_i[1],用来"进入关卡") // // 以后要增加外设(UART、SD 卡等)时只需在这里扩展地址译码。 // ----------------------------------------------------------------------------- module cpu21_mmio #( parameter [31:0] LED_ADDR = 32'h0000_1000, parameter integer FB_WORDS = 300, parameter integer FIFO_DEPTH = 16 ) ( input wire clk, input wire reset, // MEM 级的访存请求(来自 EX/MEM 流水寄存器) input wire mem_valid_i, input wire mem_write_i, input wire mem_read_i, input wire [31:0] addr_i, input wire [31:0] wdata_i, input wire [ 3:0] wstrb_i, // MMIO 读回 output wire [31:0] rdata_o, output wire rdata_hit_o, // LED 外设(原样保留) output reg [31:0] led_data_o, output reg led_valid_o, // 七段数码管:8 位十六进制数 output reg [31:0] seg_data_o, output reg seg_valid_o, // 显存写端口(板级顶层的双口 RAM) output wire fb_we_o, output wire [ 9:0] fb_waddr_o, output wire [31:0] fb_wdata_o, output wire [ 3:0] fb_wstrb_o, input wire [31:0] fb_rdata_i, // 显示控制器寄存器(给 VGA 用) output wire [31:0] disp_ctrl_o, output wire [31:0] disp_cursor_o, // 鼠标事件源(来自板级顶层:按键去抖 / PS/2 解析) input wire mouse_ev_valid_i, input wire [ 7:0] mouse_dx_i, input wire [ 7:0] mouse_dy_i, input wire [ 4:0] mouse_btn_i, input wire [15:0] sw_i, input wire frame_tick_i, // 中断 output wire mouse_irq_o, output wire level_irq_o ); // --------------------------------------------------------------------------- // 地址译码(都按"字"对齐:同一 32 位寄存器内的任意字节偏移都算命中) // --------------------------------------------------------------------------- localparam [31:0] SEG_ADDR = 32'h0000_1004; localparam [31:0] MOUSE_BASE = 32'h0000_1100; localparam [31:0] DISP_BASE = 32'h0000_1140; localparam integer FIFO_AW = 4; localparam [FIFO_AW:0] FIFO_FULL_V = FIFO_DEPTH[FIFO_AW:0]; wire sel_led = (addr_i[31:2] == LED_ADDR[31:2]); wire sel_seg = (addr_i[31:2] == SEG_ADDR[31:2]); wire sel_mouse = (addr_i[31:8] == MOUSE_BASE[31:8]) && (addr_i[7:6] == 2'b00); wire sel_disp = (addr_i[31:8] == DISP_BASE[31:8]) && (addr_i[7:6] == 2'b01); // 显存窗口 0x2000 - 0x27FF(addr[12]=1 且 addr[11]=0) wire sel_fb = (addr_i[31:13] == 19'd0) && addr_i[12] && !addr_i[11]; wire [3:0] midx = addr_i[5:2]; wire [3:0] didx = addr_i[5:2]; wire [9:0] fb_widx = addr_i[10:2]; wire fb_ok = (fb_widx < FB_WORDS); wire led_wr = mem_valid_i && mem_write_i && sel_led; wire led_rd = mem_valid_i && mem_read_i && sel_led; wire seg_wr = mem_valid_i && mem_write_i && sel_seg; wire seg_rd = mem_valid_i && mem_read_i && sel_seg; wire mo_wr = mem_valid_i && mem_write_i && sel_mouse; wire mo_rd = mem_valid_i && mem_read_i && sel_mouse; wire dp_wr = mem_valid_i && mem_write_i && sel_disp; wire dp_rd = mem_valid_i && mem_read_i && sel_disp; wire fb_wr = mem_valid_i && mem_write_i && sel_fb && fb_ok; wire fb_rd = mem_valid_i && mem_read_i && sel_fb && fb_ok; assign fb_we_o = fb_wr; assign fb_waddr_o = fb_widx; assign fb_wdata_o = wdata_i; assign fb_wstrb_o = wstrb_i; assign rdata_hit_o = led_rd | seg_rd | mo_rd | dp_rd | fb_rd; // 字节写合并:sw 改 4 字节、sb 只改 1 字节(与 RAM 的字节写语义一致) function [31:0] merge_bytes; input [31:0] new_v; input [31:0] old_v; input [ 3:0] strb; begin merge_bytes[7:0] = strb[0] ? new_v[7:0] : old_v[7:0]; merge_bytes[15:8] = strb[1] ? new_v[15:8] : old_v[15:8]; merge_bytes[23:16] = strb[2] ? new_v[23:16] : old_v[23:16]; merge_bytes[31:24] = strb[3] ? new_v[31:24] : old_v[31:24]; end endfunction // --------------------------------------------------------------------------- // LED:保持原有语义(led_valid_o 只拉高一拍) // --------------------------------------------------------------------------- always @(posedge clk or posedge reset) begin if (reset) begin led_data_o <= 32'b0; led_valid_o <= 1'b0; end else begin led_valid_o <= 1'b0; if (led_wr) begin led_data_o <= merge_bytes(wdata_i, led_data_o, wstrb_i); led_valid_o <= 1'b1; end end end // --------------------------------------------------------------------------- // 七段数码管:8 位十六进制数(低 4 位 = 最右边那个数码管) // --------------------------------------------------------------------------- always @(posedge clk or posedge reset) begin if (reset) begin seg_data_o <= 32'b0; seg_valid_o <= 1'b0; end else begin seg_valid_o <= 1'b0; if (seg_wr) begin seg_data_o <= merge_bytes(wdata_i, seg_data_o, wstrb_i); seg_valid_o <= 1'b1; end end end // --------------------------------------------------------------------------- // 显示控制器寄存器(显存本体在板级顶层) // --------------------------------------------------------------------------- reg [31:0] disp_ctrl_q, disp_cursor_q; assign disp_ctrl_o = disp_ctrl_q; assign disp_cursor_o = disp_cursor_q; // --------------------------------------------------------------------------- // 鼠标:事件 FIFO + 寄存器 + 中断 // --------------------------------------------------------------------------- reg [31:0] fifo [0:FIFO_DEPTH-1]; reg [FIFO_AW-1:0] wr_ptr_q, rd_ptr_q; reg [FIFO_AW:0] cnt_q; reg [31:0] mctrl_q, time_q, frame_q, evcnt_q; reg level_q; wire fifo_empty = (cnt_q == {FIFO_AW+1{1'b0}}); wire fifo_full = (cnt_q == FIFO_FULL_V); wire [31:0] fifo_head = fifo[rd_ptr_q]; wire irq_en = mctrl_q[0]; assign mouse_irq_o = irq_en && !fifo_empty; assign level_irq_o = level_q; wire push = mouse_ev_valid_i && !fifo_full; wire pop = mo_wr && (midx == 4'd2) && !fifo_empty; always @(posedge clk or posedge reset) begin if (reset) begin mctrl_q <= 32'h0000_0001; // 鼠标中断默认打开 time_q <= 32'd0; frame_q <= 32'd0; evcnt_q <= 32'd0; level_q <= 1'b0; wr_ptr_q <= {FIFO_AW{1'b0}}; rd_ptr_q <= {FIFO_AW{1'b0}}; cnt_q <= {(FIFO_AW+1){1'b0}}; disp_ctrl_q <= 32'h0000_0001; // 光标可见,背景色 0 disp_cursor_q <= 32'h0000_0000; end else begin time_q <= time_q + 32'd1; if (frame_tick_i) frame_q <= frame_q + 32'd1; if (push) begin fifo[wr_ptr_q] <= {11'b0, mouse_btn_i, mouse_dy_i, mouse_dx_i}; wr_ptr_q <= wr_ptr_q + 1'b1; evcnt_q <= evcnt_q + 32'd1; end if (pop) rd_ptr_q <= rd_ptr_q + 1'b1; cnt_q <= cnt_q + (push ? 1'b1 : 1'b0) - (pop ? 1'b1 : 1'b0); // 鼠标寄存器:CTRL(3) / LEVEL(8) if (mo_wr) begin case (midx) 4'd3: mctrl_q <= merge_bytes(wdata_i, mctrl_q, wstrb_i); 4'd8: level_q <= wdata_i[0]; default: ; endcase end // 显示控制器:CTRL(0) / CURSOR(1) if (dp_wr) begin case (didx) 4'd0: disp_ctrl_q <= merge_bytes(wdata_i, disp_ctrl_q, wstrb_i); 4'd1: disp_cursor_q <= merge_bytes(wdata_i, disp_cursor_q, wstrb_i); default: ; endcase end end end // --------------------------------------------------------------------------- // 读数据(组合,与"指令存储器组合读/数据存储器组合读"的时序一致) // --------------------------------------------------------------------------- reg [31:0] rdata_mux; always @* begin rdata_mux = 32'b0; if (led_rd) begin rdata_mux = led_data_o; end else if (seg_rd) begin rdata_mux = seg_data_o; end else if (fb_rd) begin rdata_mux = fb_rdata_i; end else if (mo_rd) begin case (midx) 4'd0: rdata_mux = {24'b0, fifo_full, irq_en, cnt_q[4:0], !fifo_empty}; 4'd1: rdata_mux = fifo_head; 4'd3: rdata_mux = mctrl_q; 4'd4: rdata_mux = {11'b0, mouse_btn_i, sw_i}; 4'd5: rdata_mux = time_q; 4'd6: rdata_mux = frame_q; 4'd7: rdata_mux = evcnt_q; 4'd8: rdata_mux = {31'b0, level_q}; 4'd9: rdata_mux = 32'hCAFE_0002; default: rdata_mux = 32'b0; endcase end else if (dp_rd) begin case (didx) 4'd0: rdata_mux = disp_ctrl_q; 4'd1: rdata_mux = disp_cursor_q; 4'd2: rdata_mux = 32'hCAFE_0001; default: rdata_mux = 32'b0; endcase end end assign rdata_o = rdata_mux; endmodule `default_nettype wire