Date de création 2020-08-21Date d’expiration 2020-11-22module video_controller #( // Video parameters parameter HORZ_BACK_PORCH = 88, parameter HORZ_VISIBLE = 800, parameter HORZ_FRONT_PORCH = 40, parameter HORZ_SYNC = 128, parameter VERT_BACK_PORCH = 23, parameter VERT_VISIBLE = 600, parameter VERT_FRONT_PORCH = 1, parameter VERT_SYNC = 4, // Extended registers ports parameter EXT_DISPLAY_MODE = 8'h80, parameter EXT_COLOR_MODE_0 = 8'h81, parameter EXT_COLOR_MODE_1 = 8'h82, parameter EXT_VIDEO_ROLLER = 8'h85, parameter EXT_VIDEO_ORIGIN = 8'h86, // Standard registers ports parameter PCW_VIDEO_ROLLER = 8'hF5, parameter PCW_VIDEO_ORIGIN = 8'hF6, parameter PCW_VIDEO_CONTROL = 8'hF7 ) ( input wire clk, input wire reset, input wire [15:0] z80_address, input wire [7:0] z80_data, input wire iorq_n, input wire wr_n, input wire [7:0] lom_data, output reg [16:0] lom_address, output reg video_request, output wire hsync, output wire vsync, output reg [2:0] pixel_red, output reg [2:0] pixel_green, output reg [2:0] pixel_blue ); // ============================================================================= // Constants // ============================================================================= localparam DISPLAY_MODE_STANDARD = 2'd0, DISPLAY_MODE_PROGRESSIVE = 2'd1, DISPLAY_MODE_INTERLACED = 2'd2, DISPLAY_MODE_ALTERNATE = 2'd3, // 3 bit colors -> BGR // ||| COLOR_BLACK = 3'b000, COLOR_RED = 3'b001, COLOR_GREEN = 3'b010, COLOR_YELLOW = 3'b011, COLOR_BLUE = 3'b100, COLOR_MAGENTA = 3'b101, COLOR_CYAN = 3'b110, COLOR_WHITE = 3'b111, TRUE = 1, FALSE = 0, TRUE_n = 0, FALSE_n = 1; // ============================================================================= // Video controller registers // ============================================================================= // Roller RAM address and color mode address can span over 128 kilobytes. // There are two sets of registers to allow 512 lines instead of the 256 // standard. reg [16:0] roller_ram_address [1:0]; reg [16:0] color_mode_address [1:0]; // Color mode is enabled if the first color mode address register is not zero. wire color_mode_enabled = color_mode_address[0] != 17'd0; reg [7:0] vertical_origin [1:0]; reg video_invert = FALSE; reg blank_screen = FALSE; reg [2:0] foreground_color = COLOR_WHITE; reg [2:0] background_color = COLOR_BLACK; reg [1:0] display_mode = DISPLAY_MODE_STANDARD; always @(posedge clk) if (reset) begin foreground_color <= COLOR_WHITE; background_color <= COLOR_BLACK; display_mode <= DISPLAY_MODE_STANDARD; color_mode_address[0] <= 'd0; color_mode_address[1] <= 'd0; roller_ram_address[0] <= 'd0; roller_ram_address[1] <= 'd0; vertical_origin[0] <= 'd0; vertical_origin[1] <= 'd0; video_invert <= FALSE; blank_screen <= FALSE; end else if (iorq_n == TRUE_n && wr_n == TRUE_n) case (z80_address[7:0]) // Extended ports EXT_DISPLAY_MODE: begin foreground_color <= z80_data[2:0]; background_color <= z80_data[5:3]; display_mode <= z80_data[7:6]; end EXT_COLOR_MODE_0: begin color_mode_address[0] <= { z80_data[7:0], 9'b0 }; if (display_mode == DISPLAY_MODE_STANDARD) color_mode_address[1] <= { z80_data[7:0], 9'b0 }; else color_mode_address[1] <= color_mode_address[1]; end EXT_COLOR_MODE_1: color_mode_address[1] <= { z80_data[7:0], 9'b0 }; EXT_VIDEO_ROLLER: roller_ram_address[1] <= { z80_data[7:0], 9'b0 }; EXT_VIDEO_ORIGIN: vertical_origin[1] <= z80_data; // Standard ports PCW_VIDEO_ROLLER: begin roller_ram_address[0] <= { z80_data[7:0], 9'b0 }; if (display_mode == DISPLAY_MODE_STANDARD) roller_ram_address[1] <= { z80_data[7:0], 9'b0 }; else roller_ram_address[1] <= roller_ram_address[1]; end PCW_VIDEO_ORIGIN: begin vertical_origin[0] <= z80_data; if (display_mode == DISPLAY_MODE_STANDARD) vertical_origin[1] <= z80_data; else vertical_origin[1] <= vertical_origin[1]; end PCW_VIDEO_CONTROL: begin video_invert <= z80_data[7]; blank_screen <= ~z80_data[6]; end endcase // ============================================================================= // Video timings (800×600@60Hz) // ============================================================================= localparam HORZ_TOTAL = HORZ_BACK_PORCH + HORZ_VISIBLE + HORZ_FRONT_PORCH + HORZ_SYNC, VERT_TOTAL = VERT_BACK_PORCH + VERT_VISIBLE + VERT_FRONT_PORCH + VERT_SYNC, HORZ_VISIBLE_START = HORZ_BACK_PORCH, HORZ_SYNC_START = HORZ_BACK_PORCH + HORZ_VISIBLE + HORZ_FRONT_PORCH, VERT_VISIBLE_START = VERT_BACK_PORCH, VERT_SYNC_START = VERT_BACK_PORCH + VERT_VISIBLE + VERT_FRONT_PORCH, HORZ_PCW_VISIBLE = 720, VERT_PCW_VISIBLE = 512; // ============================================================================= // Horizontal pixel counter // ============================================================================= reg [10:0] xpos = 0; always @(posedge clk) if (reset) xpos <= 'd0; else if (xpos == HORZ_TOTAL - 1) xpos <= 'd0; else xpos <= xpos + 'd1; // ============================================================================= // Vertical line counter // ============================================================================= reg [9:0] ypos = 0; always @(posedge clk) if (reset) ypos <= 'd0; else if (xpos == HORZ_TOTAL - 1) begin if (ypos == VERT_TOTAL - 1) ypos <= 'd0; else ypos <= ypos + 'd1; end else ypos <= ypos; // ============================================================================= // Synchronization signals // ============================================================================= assign hsync = xpos < HORZ_SYNC_START; assign vsync = ypos < VERT_SYNC_START; wire pcw_x_visible = xpos >= HORZ_VISIBLE_START && xpos < HORZ_VISIBLE_START + HORZ_PCW_VISIBLE; wire pcw_y_visible = ypos >= VERT_VISIBLE_START && ypos < VERT_VISIBLE_START + VERT_PCW_VISIBLE; // ============================================================================= // Roller RAM index and pixels address // ============================================================================= localparam // Worst case for reading memory MEM_READ_DURATION = 7, LINE_START = 0, COMPUTE_ROLLER_RAM_INDEX = 1 + LINE_START, REQ_ROLLER_INDEX_LO = 1 + COMPUTE_ROLLER_RAM_INDEX, REQ_ROLLER_INDEX_LO_E = 1 + REQ_ROLLER_INDEX_LO, READ_ROLLER_INDEX_LO = MEM_READ_DURATION + REQ_ROLLER_INDEX_LO, REQ_ROLLER_INDEX_HI = 1 + READ_ROLLER_INDEX_LO, REQ_ROLLER_INDEX_HI_E = 1 + REQ_ROLLER_INDEX_HI, READ_ROLLER_INDEX_HI = MEM_READ_DURATION + REQ_ROLLER_INDEX_HI, REQ_PALETTE_INDEX_LO = 1 + READ_ROLLER_INDEX_HI, REQ_PALETTE_INDEX_LO_E = 1 + REQ_PALETTE_INDEX_LO, READ_PALETTE_INDEX_LO = MEM_READ_DURATION + REQ_PALETTE_INDEX_LO, REQ_PALETTE_INDEX_HI = 1 + READ_PALETTE_INDEX_LO, REQ_PALETTE_INDEX_HI_E = 1 + REQ_PALETTE_INDEX_HI, READ_PALETTE_INDEX_HI = MEM_READ_DURATION + REQ_PALETTE_INDEX_HI, REQ_FIRST_8_PIXELS = 1 + READ_PALETTE_INDEX_HI, REQ_FIRST_8_PIXELS_E = 1 + REQ_FIRST_8_PIXELS, READ_FIRST_8_PIXELS = MEM_READ_DURATION + REQ_FIRST_8_PIXELS, REQ_8_PIXELS = 0, REQ_8_PIXELS_E = 1 + REQ_8_PIXELS, READ_8_PIXELS = MEM_READ_DURATION + REQ_8_PIXELS; reg [7:0] roller_ram_index [1:0]; reg [16:0] pixels_address = 17'd0; reg [7:0] pixels = 8'd0; reg [1:0] dot = 2'd0; reg [8:0] line = 9'd0; reg [15:0] palette; wire mode_4_color = palette[12]; always @(posedge clk) if (pcw_y_visible) begin case (xpos) LINE_START: if (ypos == VERT_VISIBLE_START) line <= 9'd0; else line <= line + 9'd1; // Compute roller RAM index COMPUTE_ROLLER_RAM_INDEX: case (display_mode) DISPLAY_MODE_INTERLACED, DISPLAY_MODE_STANDARD: if (line[8:1] == 8'd0) roller_ram_index[line[0]] <= vertical_origin[line[0]]; else roller_ram_index[line[0]] <= roller_ram_index[line[0]] + 8'd1; DISPLAY_MODE_PROGRESSIVE: if (line[7:0] == 8'd0) roller_ram_index[line[8]] <= vertical_origin[line[8]]; else roller_ram_index[line[8]] <= roller_ram_index[line[8]] + 8'd1; endcase // Retrieve address of first 8 pixels using the roller RAM (lo byte) REQ_ROLLER_INDEX_LO: begin video_request <= TRUE; case (display_mode) DISPLAY_MODE_INTERLACED, DISPLAY_MODE_STANDARD: lom_address <= roller_ram_address[line[0]] + { 8'b0, roller_ram_index[line[0]], 1'b0 }; DISPLAY_MODE_PROGRESSIVE: lom_address <= roller_ram_address[line[8]] + { 8'b0, roller_ram_index[line[8]], 1'b0 }; endcase end REQ_ROLLER_INDEX_LO_E: video_request <= FALSE; READ_ROLLER_INDEX_LO: // In order to span over 128 kilobytes of memory, a 0 bit is // inserted at 4th position to get a 17 bit address. pixels_address[8:0] <= { lom_data[7:3], 1'b0, lom_data[2:0] }; // Retrieve address of first 8 pixels using the roller RAM (hi byte) REQ_ROLLER_INDEX_HI: begin video_request <= TRUE; lom_address <= lom_address + 17'd1; end REQ_ROLLER_INDEX_HI_E: video_request <= FALSE; READ_ROLLER_INDEX_HI: pixels_address[16:9] <= lom_data; // Retrieve address of line palette (low byte) REQ_PALETTE_INDEX_LO: begin video_request <= TRUE; case (display_mode) DISPLAY_MODE_INTERLACED, DISPLAY_MODE_STANDARD: lom_address <= color_mode_address[line[0]] + { 8'b0, roller_ram_index[line[0]], 1'b0 }; DISPLAY_MODE_PROGRESSIVE: lom_address <= color_mode_address[line[8]] + { 8'b0, roller_ram_index[line[8]], 1'b0 }; endcase end REQ_PALETTE_INDEX_LO_E: video_request <= FALSE; READ_PALETTE_INDEX_LO: palette[7:0] <= lom_data; // Retrieve address of line palette (high byte) REQ_PALETTE_INDEX_HI: begin video_request <= TRUE; lom_address <= lom_address + 'd1; end REQ_PALETTE_INDEX_HI_E: video_request <= FALSE; READ_PALETTE_INDEX_HI: palette[15:8] <= lom_data; // Read 8 pixels REQ_FIRST_8_PIXELS: begin video_request <= TRUE; lom_address <= pixels_address; end REQ_FIRST_8_PIXELS_E: video_request <= FALSE; READ_FIRST_8_PIXELS: pixels <= { lom_data[0], lom_data[1], lom_data[2], lom_data[3], lom_data[4], lom_data[5], lom_data[6], lom_data[7] }; endcase if (pcw_x_visible) begin if (color_mode_enabled & mode_4_color) case (xpos[2:1]) 'd0: dot <= pixels[1:0]; 'd1: dot <= pixels[3:2]; 'd2: dot <= pixels[5:4]; 'd3: dot <= pixels[7:6]; endcase else dot <= { 1'b0, pixels[xpos[2:0]] }; // Read next 8 pixels case (xpos[2:0]) REQ_8_PIXELS: begin pixels_address <= pixels_address + 17'd8; lom_address <= pixels_address + 17'd8; video_request <= TRUE; end REQ_8_PIXELS_E: video_request <= FALSE; READ_8_PIXELS: pixels <= { lom_data[0], lom_data[1], lom_data[2], lom_data[3], lom_data[4], lom_data[5], lom_data[6], lom_data[7] }; endcase end end // ============================================================================= // Pixel generation // ============================================================================= task set_pixel_3; input red; input green; input blue; begin pixel_red <= { red, red, red }; pixel_green <= { green, green, green }; pixel_blue <= { blue, blue, blue }; end endtask always @(posedge clk) if (pcw_y_visible && pcw_x_visible && ~blank_screen) begin if (color_mode_enabled) case (dot) 2'd0: set_pixel_3(palette[2], palette[1], palette[0]); 2'd1: set_pixel_3(palette[5], palette[4], palette[3]); 2'd2: set_pixel_3(palette[8], palette[7], palette[6]); 2'd3: set_pixel_3(palette[11], palette[10], palette[9]); endcase else if (dot == video_invert) set_pixel_3( background_color[2], background_color[1], background_color[0] ); else set_pixel_3( foreground_color[2], foreground_color[1], foreground_color[0] ); end else set_pixel_3(0, 0, 0); endmodule