From 30bf24c2dda75663e4086d9a7cff4eb84e81bd5c Mon Sep 17 00:00:00 2001 From: Gyorgy Szombathelyi Date: Sat, 1 Jun 2019 23:53:48 +0200 Subject: [PATCH] Add common MiST components --- common/mist/mist.qip | 6 + common/mist/mist.vhd | 96 +++++++ common/mist/mist_video.v | 115 ++++++++ common/mist/osd.v | 195 +++++++++++++ common/mist/rgb2ypbpr.sv | 55 ++++ common/mist/scandoubler.v | 197 +++++++++++++ common/mist/user_io.v | 572 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 1236 insertions(+) create mode 100644 common/mist/mist.qip create mode 100644 common/mist/mist.vhd create mode 100644 common/mist/mist_video.v create mode 100644 common/mist/osd.v create mode 100644 common/mist/rgb2ypbpr.sv create mode 100644 common/mist/scandoubler.v create mode 100644 common/mist/user_io.v diff --git a/common/mist/mist.qip b/common/mist/mist.qip new file mode 100644 index 00000000..fd63ad20 --- /dev/null +++ b/common/mist/mist.qip @@ -0,0 +1,6 @@ +set_global_assignment -name VHDL_FILE [file join $::quartus(qip_path) mist.vhd] +set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) user_io.v] +set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) mist_video.v] +set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) scandoubler.v] +set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) osd.v] +set_global_assignment -name SYSTEMVERILOG_FILE [file join $::quartus(qip_path) rgb2ypbpr.sv] diff --git a/common/mist/mist.vhd b/common/mist/mist.vhd new file mode 100644 index 00000000..a7c1c2f2 --- /dev/null +++ b/common/mist/mist.vhd @@ -0,0 +1,96 @@ +-- A video pipeline for MiST. Just insert between the core video output and the VGA pins +-- Provides an optional scandoubler, a rotateable OSD and (optional) RGb->YPbPr conversion +library IEEE; +use IEEE.std_logic_1164.all; +use IEEE.numeric_std.all; + +package mist is + +component user_io + generic(STRLEN : integer := 0 ); + port ( + clk_sys : in std_logic; + clk_sd : in std_logic := '0'; + SPI_CLK, SPI_SS_IO, SPI_MOSI :in std_logic; + SPI_MISO : out std_logic; + conf_str : in std_logic_vector(8*STRLEN-1 downto 0); + joystick_0 : out std_logic_vector(31 downto 0); + joystick_1 : out std_logic_vector(31 downto 0); + joystick_2 : out std_logic_vector(31 downto 0); + joystick_3 : out std_logic_vector(31 downto 0); + joystick_4 : out std_logic_vector(31 downto 0); + joystick_analog_0 : out std_logic_vector(15 downto 0); + joystick_analog_1 : out std_logic_vector(15 downto 0); + status: out std_logic_vector(31 downto 0); + switches : out std_logic_vector(1 downto 0); + buttons : out std_logic_vector(1 downto 0); + scandoubler_disable : out std_logic; + ypbpr : out std_logic; + + sd_lba : in std_logic_vector(31 downto 0) := (others => '0'); + sd_rd : in std_logic := '0'; + sd_wr : in std_logic := '0'; + sd_ack : out std_logic; + sd_ack_conf : out std_logic; + sd_conf : in std_logic := '0'; + sd_sdhc : in std_logic := '1'; + img_size : out std_logic_vector(31 downto 0); + img_mounted : out std_logic; + + sd_buff_addr : out std_logic_vector(8 downto 0); + sd_dout : out std_logic_vector(7 downto 0); + sd_din : in std_logic_vector(7 downto 0) := (others => '0'); + sd_dout_strobe : out std_logic; + sd_din_strobe : out std_logic; + + ps2_kbd_clk : out std_logic; + ps2_kbd_data : out std_logic; + key_pressed : out std_logic; + key_extended : out std_logic; + key_code : out std_logic_vector(7 downto 0); + key_strobe : out std_logic; + + ps2_mouse_clk : out std_logic; + ps2_mouse_data : out std_logic; + mouse_x : out signed(8 downto 0); + mouse_y : out signed(8 downto 0); + mouse_flags : out std_logic_vector(7 downto 0); -- YOvfl, XOvfl, dy8, dx8, 1, mbtn, rbtn, lbtn + mouse_strobe : out std_logic + ); +end component user_io; + +component mist_video + generic ( + OSD_COLOR : std_logic_vector(2 downto 0) := "110"; + OSD_X_OFFSET : std_logic_vector(9 downto 0) := (others => '0'); + OSD_Y_OFFSET : std_logic_vector(9 downto 0) := (others => '0'); + SD_HCNT_WIDTH: integer := 9; + COLOR_DEPTH : integer := 6 + ); + port ( + clk_sys : in std_logic; + + SPI_SCK : in std_logic; + SPI_SS3 : in std_logic; + SPI_DI : in std_logic; + + scanlines : in std_logic_vector(1 downto 0); + scandoubler_disable : in std_logic; + ypbpr : in std_logic; + rotate : in std_logic_vector(1 downto 0); + + HSync : in std_logic; + VSync : in std_logic; + R : in std_logic_vector(COLOR_DEPTH-1 downto 0); + G : in std_logic_vector(COLOR_DEPTH-1 downto 0); + B : in std_logic_vector(COLOR_DEPTH-1 downto 0); + + VGA_HS : out std_logic; + VGA_VS : out std_logic; + VGA_R : out std_logic_vector(5 downto 0); + VGA_G : out std_logic_vector(5 downto 0); + VGA_B : out std_logic_vector(5 downto 0) + ); +end component mist_video; + +end package; \ No newline at end of file diff --git a/common/mist/mist_video.v b/common/mist/mist_video.v new file mode 100644 index 00000000..57177b6a --- /dev/null +++ b/common/mist/mist_video.v @@ -0,0 +1,115 @@ +// A video pipeline for MiST. Just insert between the core video output and the VGA pins +// Provides an optional scandoubler, a rotateable OSD and (optional) RGb->YPbPr conversion + +module mist_video +( + // master clock + // it should be 4xpixel clock for the scandoubler + input clk_sys, + + // OSD SPI interface + input SPI_SCK, + input SPI_SS3, + input SPI_DI, + + // scanlines (00-none 01-25% 10-50% 11-75%) + input [1:0] scanlines, + + // 0 = HVSync 31KHz, 1 = CSync 15KHz + input scandoubler_disable, + // YPbPr always uses composite sync + input ypbpr, + // Rotate OSD [0] - rotate [1] - left or right + input [1:0] rotate, + + // video in + input [COLOR_DEPTH-1:0] R, + input [COLOR_DEPTH-1:0] G, + input [COLOR_DEPTH-1:0] B, + + input HSync, + input VSync, + + // MiST video output signals + output [5:0] VGA_R, + output [5:0] VGA_G, + output [5:0] VGA_B, + output VGA_VS, + output VGA_HS +); + +parameter OSD_COLOR = 3'd4; +parameter OSD_X_OFFSET = 10'd0; +parameter OSD_Y_OFFSET = 10'd0; +parameter SD_HCNT_WIDTH = 9; +parameter COLOR_DEPTH = 6; // 3-6 + +wire [5:0] SD_R_O; +wire [5:0] SD_G_O; +wire [5:0] SD_B_O; +wire SD_HS_O; +wire SD_VS_O; + +scandoubler #(SD_HCNT_WIDTH, COLOR_DEPTH) scandoubler +( + .clk_sys ( clk_sys ), + .scanlines ( scanlines ), + .hs_in ( HSync ), + .vs_in ( VSync ), + .r_in ( R ), + .g_in ( G ), + .b_in ( B ), + .hs_out ( SD_HS_O ), + .vs_out ( SD_VS_O ), + .r_out ( SD_R_O ), + .g_out ( SD_G_O ), + .b_out ( SD_B_O ) +); + +wire [5:0] osd_r_o; +wire [5:0] osd_g_o; +wire [5:0] osd_b_o; + +osd #(OSD_X_OFFSET, OSD_Y_OFFSET, OSD_COLOR) osd +( + .clk_sys ( clk_sys ), + .rotate ( rotate ), + .SPI_DI ( SPI_DI ), + .SPI_SCK ( SPI_SCK ), + .SPI_SS3 ( SPI_SS3 ), + .R_in ( scandoubler_disable ? R : SD_R_O ), + .G_in ( scandoubler_disable ? G : SD_G_O ), + .B_in ( scandoubler_disable ? B : SD_B_O ), + .HSync ( scandoubler_disable ? HSync : SD_HS_O ), + .VSync ( scandoubler_disable ? VSync : SD_VS_O ), + .R_out ( osd_r_o ), + .G_out ( osd_g_o ), + .B_out ( osd_b_o ) +); + +wire [5:0] y, pb, pr; + +rgb2ypbpr rgb2ypbpr +( + .red ( osd_r_o ), + .green ( osd_g_o ), + .blue ( osd_b_o ), + .y ( y ), + .pb ( pb ), + .pr ( pr ) +); + +assign VGA_R = ypbpr?pr:osd_r_o; +assign VGA_G = ypbpr? y:osd_g_o; +assign VGA_B = ypbpr?pb:osd_b_o; + +wire cs = scandoubler_disable ? ~(HSync ^ VSync) : ~(SD_HS_O ^ SD_VS_O); +wire hs = scandoubler_disable ? HSync : SD_HS_O; +wire vs = scandoubler_disable ? VSync : SD_VS_O; + +// a minimig vga->scart cable expects a composite sync signal on the VGA_HS output. +// and VCC on VGA_VS (to switch into rgb mode) +assign VGA_HS = (scandoubler_disable || ypbpr)? cs : hs; +assign VGA_VS = (scandoubler_disable || ypbpr)? 1'b1 : vs; + +endmodule diff --git a/common/mist/osd.v b/common/mist/osd.v new file mode 100644 index 00000000..5527e729 --- /dev/null +++ b/common/mist/osd.v @@ -0,0 +1,195 @@ +// A simple OSD implementation. Can be hooked up between a cores +// VGA output and the physical VGA pins + +module osd ( + // OSDs pixel clock, should be synchronous to cores pixel clock to + // avoid jitter. + input clk_sys, + + // SPI interface + input SPI_SCK, + input SPI_SS3, + input SPI_DI, + + input [1:0] rotate, //[0] - rotate [1] - left or right + + // VGA signals coming from core + input [5:0] R_in, + input [5:0] G_in, + input [5:0] B_in, + input HSync, + input VSync, + + // VGA signals going to video connector + output [5:0] R_out, + output [5:0] G_out, + output [5:0] B_out +); + +parameter OSD_X_OFFSET = 10'd0; +parameter OSD_Y_OFFSET = 10'd0; +parameter OSD_COLOR = 3'd0; + +localparam OSD_WIDTH = 10'd256; +localparam OSD_HEIGHT = 10'd128; + +// ********************************************************************************* +// spi client +// ********************************************************************************* + +// this core supports only the display related OSD commands +// of the minimig +reg osd_enable; +(* ramstyle = "no_rw_check" *) reg [7:0] osd_buffer[2047:0]; // the OSD buffer itself + +// the OSD has its own SPI interface to the io controller +always@(posedge SPI_SCK, posedge SPI_SS3) begin + reg [4:0] cnt; + reg [10:0] bcnt; + reg [7:0] sbuf; + reg [7:0] cmd; + + if(SPI_SS3) begin + cnt <= 0; + bcnt <= 0; + end else begin + sbuf <= {sbuf[6:0], SPI_DI}; + + // 0:7 is command, rest payload + if(cnt < 15) cnt <= cnt + 1'd1; + else cnt <= 8; + + if(cnt == 7) begin + cmd <= {sbuf[6:0], SPI_DI}; + + // lower three command bits are line address + bcnt <= {sbuf[1:0], SPI_DI, 8'h00}; + + // command 0x40: OSDCMDENABLE, OSDCMDDISABLE + if(sbuf[6:3] == 4'b0100) osd_enable <= SPI_DI; + end + + // command 0x20: OSDCMDWRITE + if((cmd[7:3] == 5'b00100) && (cnt == 15)) begin + osd_buffer[bcnt] <= {sbuf[6:0], SPI_DI}; + bcnt <= bcnt + 1'd1; + end + end +end + +// ********************************************************************************* +// video timing and sync polarity anaylsis +// ********************************************************************************* + +// horizontal counter +reg [9:0] h_cnt; +reg [9:0] hs_low, hs_high; +wire hs_pol = hs_high < hs_low; +wire [9:0] dsp_width = hs_pol ? hs_low : hs_high; + +// vertical counter +reg [9:0] v_cnt; +reg [9:0] vs_low, vs_high; +wire vs_pol = vs_high < vs_low; +wire [9:0] dsp_height = vs_pol ? vs_low : vs_high; + +wire doublescan = (dsp_height>350); + +reg ce_pix; +always @(negedge clk_sys) begin + integer cnt = 0; + integer pixsz, pixcnt; + reg hs; + + cnt <= cnt + 1; + hs <= HSync; + + pixcnt <= pixcnt + 1; + if(pixcnt == pixsz) pixcnt <= 0; + ce_pix <= !pixcnt; + + if(hs && ~HSync) begin + cnt <= 0; + pixsz <= (cnt >> 9) - 1; + pixcnt <= 0; + ce_pix <= 1; + end +end + +always @(posedge clk_sys) begin + reg hsD, hsD2; + reg vsD, vsD2; + + if(ce_pix) begin + // bring hsync into local clock domain + hsD <= HSync; + hsD2 <= hsD; + + // falling edge of HSync + if(!hsD && hsD2) begin + h_cnt <= 0; + hs_high <= h_cnt; + end + + // rising edge of HSync + else if(hsD && !hsD2) begin + h_cnt <= 0; + hs_low <= h_cnt; + v_cnt <= v_cnt + 1'd1; + end else begin + h_cnt <= h_cnt + 1'd1; + end + + vsD <= VSync; + vsD2 <= vsD; + + // falling edge of VSync + if(!vsD && vsD2) begin + v_cnt <= 0; + vs_high <= v_cnt; + end + + // rising edge of VSync + else if(vsD && !vsD2) begin + v_cnt <= 0; + vs_low <= v_cnt; + end + end +end + +// area in which OSD is being displayed +wire [9:0] h_osd_start = ((dsp_width - OSD_WIDTH)>> 1) + OSD_X_OFFSET; +wire [9:0] h_osd_end = h_osd_start + OSD_WIDTH; +wire [9:0] v_osd_start = ((dsp_height- (OSD_HEIGHT<> 1) + OSD_Y_OFFSET; +wire [9:0] v_osd_end = v_osd_start + (OSD_HEIGHT<= h_osd_start) && ((h_cnt + 1'd1) < h_osd_end) && + (VSync != vs_pol) && (v_cnt >= v_osd_start) && (v_cnt < v_osd_end); + end +end + +assign R_out = !osd_de ? R_in : {osd_pixel, osd_pixel, OSD_COLOR[2], R_in[5:3]}; +assign G_out = !osd_de ? G_in : {osd_pixel, osd_pixel, OSD_COLOR[1], G_in[5:3]}; +assign B_out = !osd_de ? B_in : {osd_pixel, osd_pixel, OSD_COLOR[0], B_in[5:3]}; + +endmodule diff --git a/common/mist/rgb2ypbpr.sv b/common/mist/rgb2ypbpr.sv new file mode 100644 index 00000000..1e1662e8 --- /dev/null +++ b/common/mist/rgb2ypbpr.sv @@ -0,0 +1,55 @@ +module rgb2ypbpr ( + input [5:0] red, + input [5:0] green, + input [5:0] blue, + + output [5:0] y, + output [5:0] pb, + output [5:0] pr +); + +wire [5:0] yuv_full[225] = '{ + 6'd0, 6'd0, 6'd0, 6'd0, 6'd1, 6'd1, 6'd1, 6'd1, + 6'd2, 6'd2, 6'd2, 6'd3, 6'd3, 6'd3, 6'd3, 6'd4, + 6'd4, 6'd4, 6'd5, 6'd5, 6'd5, 6'd5, 6'd6, 6'd6, + 6'd6, 6'd7, 6'd7, 6'd7, 6'd7, 6'd8, 6'd8, 6'd8, + 6'd9, 6'd9, 6'd9, 6'd9, 6'd10, 6'd10, 6'd10, 6'd11, + 6'd11, 6'd11, 6'd11, 6'd12, 6'd12, 6'd12, 6'd13, 6'd13, + 6'd13, 6'd13, 6'd14, 6'd14, 6'd14, 6'd15, 6'd15, 6'd15, + 6'd15, 6'd16, 6'd16, 6'd16, 6'd17, 6'd17, 6'd17, 6'd17, + 6'd18, 6'd18, 6'd18, 6'd19, 6'd19, 6'd19, 6'd19, 6'd20, + 6'd20, 6'd20, 6'd21, 6'd21, 6'd21, 6'd21, 6'd22, 6'd22, + 6'd22, 6'd23, 6'd23, 6'd23, 6'd23, 6'd24, 6'd24, 6'd24, + 6'd25, 6'd25, 6'd25, 6'd25, 6'd26, 6'd26, 6'd26, 6'd27, + 6'd27, 6'd27, 6'd27, 6'd28, 6'd28, 6'd28, 6'd29, 6'd29, + 6'd29, 6'd29, 6'd30, 6'd30, 6'd30, 6'd31, 6'd31, 6'd31, + 6'd31, 6'd32, 6'd32, 6'd32, 6'd33, 6'd33, 6'd33, 6'd33, + 6'd34, 6'd34, 6'd34, 6'd35, 6'd35, 6'd35, 6'd35, 6'd36, + 6'd36, 6'd36, 6'd36, 6'd37, 6'd37, 6'd37, 6'd38, 6'd38, + 6'd38, 6'd38, 6'd39, 6'd39, 6'd39, 6'd40, 6'd40, 6'd40, + 6'd40, 6'd41, 6'd41, 6'd41, 6'd42, 6'd42, 6'd42, 6'd42, + 6'd43, 6'd43, 6'd43, 6'd44, 6'd44, 6'd44, 6'd44, 6'd45, + 6'd45, 6'd45, 6'd46, 6'd46, 6'd46, 6'd46, 6'd47, 6'd47, + 6'd47, 6'd48, 6'd48, 6'd48, 6'd48, 6'd49, 6'd49, 6'd49, + 6'd50, 6'd50, 6'd50, 6'd50, 6'd51, 6'd51, 6'd51, 6'd52, + 6'd52, 6'd52, 6'd52, 6'd53, 6'd53, 6'd53, 6'd54, 6'd54, + 6'd54, 6'd54, 6'd55, 6'd55, 6'd55, 6'd56, 6'd56, 6'd56, + 6'd56, 6'd57, 6'd57, 6'd57, 6'd58, 6'd58, 6'd58, 6'd58, + 6'd59, 6'd59, 6'd59, 6'd60, 6'd60, 6'd60, 6'd60, 6'd61, + 6'd61, 6'd61, 6'd62, 6'd62, 6'd62, 6'd62, 6'd63, 6'd63, + 6'd63 +}; + +wire [18:0] y_8 = 19'd04096 + ({red, 8'd0} + {red, 3'd0}) + ({green, 9'd0} + {green, 2'd0}) + ({blue, 6'd0} + {blue, 5'd0} + {blue, 2'd0}); +wire [18:0] pb_8 = 19'd32768 - ({red, 7'd0} + {red, 4'd0} + {red, 3'd0}) - ({green, 8'd0} + {green, 5'd0} + {green, 3'd0}) + ({blue, 8'd0} + {blue, 7'd0} + {blue, 6'd0}); +wire [18:0] pr_8 = 19'd32768 + ({red, 8'd0} + {red, 7'd0} + {red, 6'd0}) - ({green, 8'd0} + {green, 6'd0} + {green, 5'd0} + {green, 4'd0} + {green, 3'd0}) - ({blue, 6'd0} + {blue , 3'd0}); + +wire [7:0] y_i = ( y_8[17:8] < 16) ? 8'd16 : ( y_8[17:8] > 235) ? 8'd235 : y_8[15:8]; +wire [7:0] pb_i = (pb_8[17:8] < 16) ? 8'd16 : (pb_8[17:8] > 240) ? 8'd240 : pb_8[15:8]; +wire [7:0] pr_i = (pr_8[17:8] < 16) ? 8'd16 : (pr_8[17:8] > 240) ? 8'd240 : pr_8[15:8]; + +assign pr = yuv_full[pr_i - 8'd16]; +assign y = yuv_full[y_i - 8'd16]; +assign pb = yuv_full[pb_i - 8'd16]; + +endmodule diff --git a/common/mist/scandoubler.v b/common/mist/scandoubler.v new file mode 100644 index 00000000..ad582eb8 --- /dev/null +++ b/common/mist/scandoubler.v @@ -0,0 +1,197 @@ +// +// scandoubler.v +// +// Copyright (c) 2015 Till Harbaum +// +// This source file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This source file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// TODO: Delay vsync one line + +module scandoubler +( + // system interface + input clk_sys, + + // scanlines (00-none 01-25% 10-50% 11-75%) + input [1:0] scanlines, + + // shifter video interface + input hs_in, + input vs_in, + input [COLOR_DEPTH-1:0] r_in, + input [COLOR_DEPTH-1:0] g_in, + input [COLOR_DEPTH-1:0] b_in, + + // output interface + output reg hs_out, + output reg vs_out, + output reg [5:0] r_out, + output reg [5:0] g_out, + output reg [5:0] b_out +); + +parameter HCNT_WIDTH = 9; +parameter COLOR_DEPTH = 6; + +// try to detect changes in input signal and lock input clock gate +// it + +reg [1:0] i_div; +wire ce_x1 = (i_div == 2'b01); +wire ce_x2 = i_div[0]; + +always @(posedge clk_sys) begin + reg last_hs_in; + last_hs_in <= hs_in; + if(last_hs_in & !hs_in) begin + i_div <= 2'b00; + end else begin + i_div <= i_div + 2'd1; + end +end + + +// --------------------- create output signals ----------------- +// latch everything once more to make it glitch free and apply scanline effect +reg scanline; +reg [5:0] r; +reg [5:0] g; +reg [5:0] b; + +always @(*) begin + if (COLOR_DEPTH == 6) begin + b = sd_out[5:0]; + g = sd_out[11:6]; + r = sd_out[17:12]; + end else begin + b = { sd_out[COLOR_DEPTH-1:0], sd_out[COLOR_DEPTH-1 -:(6-COLOR_DEPTH)] }; + g = { sd_out[COLOR_DEPTH*2-1:COLOR_DEPTH], sd_out[COLOR_DEPTH*2-1 -:(6-COLOR_DEPTH)] }; + r = { sd_out[COLOR_DEPTH*3-1:COLOR_DEPTH*2], sd_out[COLOR_DEPTH*3-1 -:(6-COLOR_DEPTH)] }; + end +end + +always @(posedge clk_sys) begin + if(ce_x2) begin + hs_out <= hs_sd; + vs_out <= vs_in; + + // reset scanlines at every new screen + if(vs_out != vs_in) scanline <= 0; + + // toggle scanlines at begin of every hsync + if(hs_out && !hs_sd) scanline <= !scanline; + + // if no scanlines or not a scanline + if(!scanline || !scanlines) begin + r_out <= r; + g_out <= g; + b_out <= b; + end else begin + case(scanlines) + 1: begin // reduce 25% = 1/2 + 1/4 + r_out <= {1'b0, r[5:1]} + {2'b00, r[5:2] }; + g_out <= {1'b0, g[5:1]} + {2'b00, g[5:2] }; + b_out <= {1'b0, b[5:1]} + {2'b00, b[5:2] }; + end + + 2: begin // reduce 50% = 1/2 + r_out <= {1'b0, r[5:1]}; + g_out <= {1'b0, g[5:1]}; + b_out <= {1'b0, b[5:1]}; + end + + 3: begin // reduce 75% = 1/4 + r_out <= {2'b00, r[5:2]}; + g_out <= {2'b00, g[5:2]}; + b_out <= {2'b00, b[5:2]}; + end + endcase + end + end +end + +// scan doubler output register +reg [COLOR_DEPTH*3-1:0] sd_out; + +// ================================================================== +// ======================== the line buffers ======================== +// ================================================================== + +// 2 lines of 2**HCNT_WIDTH pixels 3*COLOR_DEPTH bit RGB +(* ramstyle = "no_rw_check" *) reg [COLOR_DEPTH*3-1:0] sd_buffer[2*2**HCNT_WIDTH]; + +// use alternating sd_buffers when storing/reading data +reg line_toggle; + +// total hsync time (in 16MHz cycles), hs_total reaches 1024 +reg [HCNT_WIDTH-1:0] hs_max; +reg [HCNT_WIDTH-1:0] hs_rise; +reg [HCNT_WIDTH-1:0] hcnt; + +always @(posedge clk_sys) begin + reg hsD, vsD; + + if(ce_x1) begin + hsD <= hs_in; + + // falling edge of hsync indicates start of line + if(hsD && !hs_in) begin + hs_max <= hcnt; + hcnt <= 0; + end else begin + hcnt <= hcnt + 1'd1; + end + + // save position of rising edge + if(!hsD && hs_in) hs_rise <= hcnt; + + vsD <= vs_in; + if(vsD != vs_in) line_toggle <= 0; + + // begin of incoming hsync + if(hsD && !hs_in) line_toggle <= !line_toggle; + + sd_buffer[{line_toggle, hcnt}] <= {r_in, g_in, b_in}; + end +end + +// ================================================================== +// ==================== output timing generation ==================== +// ================================================================== + +reg [HCNT_WIDTH-1:0] sd_hcnt; +reg hs_sd; + +// timing generation runs 32 MHz (twice the input signal analysis speed) +always @(posedge clk_sys) begin + reg hsD; + + if(ce_x2) begin + hsD <= hs_in; + + // output counter synchronous to input and at twice the rate + sd_hcnt <= sd_hcnt + 1'd1; + if(hsD && !hs_in) sd_hcnt <= hs_max; + if(sd_hcnt == hs_max) sd_hcnt <= 0; + + // replicate horizontal sync at twice the speed + if(sd_hcnt == hs_max) hs_sd <= 0; + if(sd_hcnt == hs_rise) hs_sd <= 1; + + // read data from line sd_buffer + sd_out <= sd_buffer[{~line_toggle, sd_hcnt}]; + end +end + +endmodule diff --git a/common/mist/user_io.v b/common/mist/user_io.v new file mode 100644 index 00000000..b5f0f69a --- /dev/null +++ b/common/mist/user_io.v @@ -0,0 +1,572 @@ +// +// user_io.v +// +// user_io for the MiST board +// http://code.google.com/p/mist-board/ +// +// Copyright (c) 2014 Till Harbaum +// +// This source file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This source file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +// parameter STRLEN and the actual length of conf_str have to match + +module user_io #(parameter STRLEN=0, parameter PS2DIV=100) ( + input [(8*STRLEN)-1:0] conf_str, + + input clk_sys, // clock for system-related messages (kbd, joy, etc...) + input clk_sd, // clock for SD-card related messages + + input SPI_CLK, + input SPI_SS_IO, + output reg SPI_MISO, + input SPI_MOSI, + + output reg [31:0] joystick_0, + output reg [31:0] joystick_1, + output reg [31:0] joystick_2, + output reg [31:0] joystick_3, + output reg [31:0] joystick_4, + output reg [15:0] joystick_analog_0, + output reg [15:0] joystick_analog_1, + output [1:0] buttons, + output [1:0] switches, + output scandoubler_disable, + output ypbpr, + output reg [31:0] status, + + // connection to sd card emulation + input [31:0] sd_lba, + input sd_rd, + input sd_wr, + output reg sd_ack, + output reg sd_ack_conf, + input sd_conf, + input sd_sdhc, + output reg [7:0] sd_dout, // valid on rising edge of sd_dout_strobe + output reg sd_dout_strobe, + input [7:0] sd_din, + output reg sd_din_strobe, + output reg [8:0] sd_buff_addr, + + output reg img_mounted, //rising edge if a new image is mounted + output reg [31:0] img_size, // size of image in bytes + + // ps2 keyboard/mouse emulation + output ps2_kbd_clk, + output reg ps2_kbd_data, + output ps2_mouse_clk, + output reg ps2_mouse_data, + + // keyboard data + output reg key_pressed, // 1-make (pressed), 0-break (released) + output reg key_extended, // extended code + output reg [7:0] key_code, // key scan code + output reg key_strobe, // key data valid + + // mouse data + output reg [8:0] mouse_x, + output reg [8:0] mouse_y, + output reg [7:0] mouse_flags, // YOvfl, XOvfl, dy8, dx8, 1, mbtn, rbtn, lbtn + output reg mouse_strobe, // mouse data is valid on mouse_strobe + + // serial com port + input [7:0] serial_data, + input serial_strobe +); + +reg [6:0] sbuf; +reg [7:0] cmd; +reg [2:0] bit_cnt; // counts bits 0-7 0-7 ... +reg [9:0] byte_cnt; // counts bytes +reg [7:0] but_sw; +reg [2:0] stick_idx; + +assign buttons = but_sw[1:0]; +assign switches = but_sw[3:2]; +assign scandoubler_disable = but_sw[4]; +assign ypbpr = but_sw[5]; + +// this variant of user_io is for 8 bit cores (type == a4) only +wire [7:0] core_type = 8'ha4; + +// command byte read by the io controller +wire [7:0] sd_cmd = { 4'h5, sd_conf, sd_sdhc, sd_wr, sd_rd }; + +wire spi_sck = SPI_CLK; + +// ---------------- PS2 --------------------- +// 8 byte fifos to store ps2 bytes +localparam PS2_FIFO_BITS = 3; + +reg ps2_clk; +always @(negedge clk_sys) begin + integer cnt; + cnt <= cnt + 1'd1; + if(cnt == PS2DIV) begin + ps2_clk <= ~ps2_clk; + cnt <= 0; + end +end + +// keyboard +reg [7:0] ps2_kbd_fifo [(2**PS2_FIFO_BITS)-1:0]; +reg [PS2_FIFO_BITS-1:0] ps2_kbd_wptr; +reg [PS2_FIFO_BITS-1:0] ps2_kbd_rptr; + +// ps2 transmitter state machine +reg [3:0] ps2_kbd_tx_state; +reg [7:0] ps2_kbd_tx_byte; +reg ps2_kbd_parity; + +assign ps2_kbd_clk = ps2_clk || (ps2_kbd_tx_state == 0); + +// ps2 transmitter +// Takes a byte from the FIFO and sends it in a ps2 compliant serial format. +reg ps2_kbd_r_inc; +always@(posedge clk_sys) begin + reg ps2_clkD; + + ps2_clkD <= ps2_clk; + if (~ps2_clkD & ps2_clk) begin + ps2_kbd_r_inc <= 1'b0; + + if(ps2_kbd_r_inc) + ps2_kbd_rptr <= ps2_kbd_rptr + 1'd1; + + // transmitter is idle? + if(ps2_kbd_tx_state == 0) begin + // data in fifo present? + if(ps2_kbd_wptr != ps2_kbd_rptr) begin + // load tx register from fifo + ps2_kbd_tx_byte <= ps2_kbd_fifo[ps2_kbd_rptr]; + ps2_kbd_r_inc <= 1'b1; + + // reset parity + ps2_kbd_parity <= 1'b1; + + // start transmitter + ps2_kbd_tx_state <= 4'd1; + + // put start bit on data line + ps2_kbd_data <= 1'b0; // start bit is 0 + end + end else begin + + // transmission of 8 data bits + if((ps2_kbd_tx_state >= 1)&&(ps2_kbd_tx_state < 9)) begin + ps2_kbd_data <= ps2_kbd_tx_byte[0]; // data bits + ps2_kbd_tx_byte[6:0] <= ps2_kbd_tx_byte[7:1]; // shift down + if(ps2_kbd_tx_byte[0]) + ps2_kbd_parity <= !ps2_kbd_parity; + end + + // transmission of parity + if(ps2_kbd_tx_state == 9) + ps2_kbd_data <= ps2_kbd_parity; + + // transmission of stop bit + if(ps2_kbd_tx_state == 10) + ps2_kbd_data <= 1'b1; // stop bit is 1 + + // advance state machine + if(ps2_kbd_tx_state < 11) + ps2_kbd_tx_state <= ps2_kbd_tx_state + 4'd1; + else + ps2_kbd_tx_state <= 4'd0; + end + end +end + +// mouse +reg [7:0] ps2_mouse_fifo [(2**PS2_FIFO_BITS)-1:0]; +reg [PS2_FIFO_BITS-1:0] ps2_mouse_wptr; +reg [PS2_FIFO_BITS-1:0] ps2_mouse_rptr; + +// ps2 transmitter state machine +reg [3:0] ps2_mouse_tx_state; +reg [7:0] ps2_mouse_tx_byte; +reg ps2_mouse_parity; + +assign ps2_mouse_clk = ps2_clk || (ps2_mouse_tx_state == 0); + +// ps2 transmitter +// Takes a byte from the FIFO and sends it in a ps2 compliant serial format. +reg ps2_mouse_r_inc; +always@(posedge clk_sys) begin + reg ps2_clkD; + + ps2_clkD <= ps2_clk; + if (~ps2_clkD & ps2_clk) begin + ps2_mouse_r_inc <= 1'b0; + + if(ps2_mouse_r_inc) + ps2_mouse_rptr <= ps2_mouse_rptr + 1'd1; + + // transmitter is idle? + if(ps2_mouse_tx_state == 0) begin + // data in fifo present? + if(ps2_mouse_wptr != ps2_mouse_rptr) begin + // load tx register from fifo + ps2_mouse_tx_byte <= ps2_mouse_fifo[ps2_mouse_rptr]; + ps2_mouse_r_inc <= 1'b1; + + // reset parity + ps2_mouse_parity <= 1'b1; + + // start transmitter + ps2_mouse_tx_state <= 4'd1; + + // put start bit on data line + ps2_mouse_data <= 1'b0; // start bit is 0 + end + end else begin + + // transmission of 8 data bits + if((ps2_mouse_tx_state >= 1)&&(ps2_mouse_tx_state < 9)) begin + ps2_mouse_data <= ps2_mouse_tx_byte[0]; // data bits + ps2_mouse_tx_byte[6:0] <= ps2_mouse_tx_byte[7:1]; // shift down + if(ps2_mouse_tx_byte[0]) + ps2_mouse_parity <= !ps2_mouse_parity; + end + + // transmission of parity + if(ps2_mouse_tx_state == 9) + ps2_mouse_data <= ps2_mouse_parity; + + // transmission of stop bit + if(ps2_mouse_tx_state == 10) + ps2_mouse_data <= 1'b1; // stop bit is 1 + + // advance state machine + if(ps2_mouse_tx_state < 11) + ps2_mouse_tx_state <= ps2_mouse_tx_state + 4'd1; + else + ps2_mouse_tx_state <= 4'd0; + end + end +end + +// fifo to receive serial data from core to be forwarded to io controller + +// 16 byte fifo to store serial bytes +localparam SERIAL_OUT_FIFO_BITS = 6; +reg [7:0] serial_out_fifo [(2**SERIAL_OUT_FIFO_BITS)-1:0]; +reg [SERIAL_OUT_FIFO_BITS-1:0] serial_out_wptr; +reg [SERIAL_OUT_FIFO_BITS-1:0] serial_out_rptr; + +wire serial_out_data_available = serial_out_wptr != serial_out_rptr; +wire [7:0] serial_out_byte = serial_out_fifo[serial_out_rptr] /* synthesis keep */; +wire [7:0] serial_out_status = { 7'b1000000, serial_out_data_available}; + +// status[0] is reset signal from io controller and is thus used to flush +// the fifo +always @(posedge serial_strobe or posedge status[0]) begin + if(status[0] == 1) begin + serial_out_wptr <= 0; + end else begin + serial_out_fifo[serial_out_wptr] <= serial_data; + serial_out_wptr <= serial_out_wptr + 1'd1; + end +end + +always@(negedge spi_sck or posedge status[0]) begin + if(status[0] == 1) begin + serial_out_rptr <= 0; + end else begin + if((byte_cnt != 0) && (cmd == 8'h1b)) begin + // read last bit -> advance read pointer + if((bit_cnt == 7) && !byte_cnt[0] && serial_out_data_available) + serial_out_rptr <= serial_out_rptr + 1'd1; + end + end +end + + +// SPI bit and byte counters +always@(posedge spi_sck or posedge SPI_SS_IO) begin + if(SPI_SS_IO == 1) begin + bit_cnt <= 0; + byte_cnt <= 0; + end else begin + if((bit_cnt == 7)&&(~&byte_cnt)) + byte_cnt <= byte_cnt + 8'd1; + + bit_cnt <= bit_cnt + 1'd1; + end +end + +// SPI transmitter FPGA -> IO +reg [7:0] spi_byte_out; + +always@(negedge spi_sck or posedge SPI_SS_IO) begin + if(SPI_SS_IO == 1) begin + SPI_MISO <= 1'bZ; + end else begin + SPI_MISO <= spi_byte_out[~bit_cnt]; + end +end + +always@(posedge spi_sck or posedge SPI_SS_IO) begin + reg [31:0] sd_lba_r; + + if(SPI_SS_IO == 1) begin + spi_byte_out <= core_type; + end else begin + // read the command byte to choose the response + if(bit_cnt == 7) begin + if(!byte_cnt) cmd <= {sbuf, SPI_MOSI}; + + spi_byte_out <= 0; + case({(!byte_cnt) ? {sbuf, SPI_MOSI} : cmd}) + // reading config string + 8'h14: if(byte_cnt < STRLEN) spi_byte_out <= conf_str[(STRLEN - byte_cnt - 1)<<3 +:8]; + + // reading sd card status + 8'h16: if(byte_cnt == 0) begin + spi_byte_out <= sd_cmd; + sd_lba_r <= sd_lba; + end + else if(byte_cnt < 5) spi_byte_out <= sd_lba_r[(4-byte_cnt)<<3 +:8]; + + // reading sd card write data + 8'h18: spi_byte_out <= sd_din; + 8'h1b: + // send alternating flag byte and data + if(byte_cnt[0]) spi_byte_out <= serial_out_status; + else spi_byte_out <= serial_out_byte; + endcase + end + end +end + +// SPI receiver IO -> FPGA + +reg spi_receiver_strobe_r = 0; +reg spi_transfer_end_r = 1; +reg [7:0] spi_byte_in; + +// Read at spi_sck clock domain, assemble bytes for transferring to clk_sys +always@(posedge spi_sck or posedge SPI_SS_IO) begin + + if(SPI_SS_IO == 1) begin + spi_transfer_end_r <= 1; + end else begin + spi_transfer_end_r <= 0; + + if(bit_cnt != 7) + sbuf[6:0] <= { sbuf[5:0], SPI_MOSI }; + + // finished reading a byte, prepare to transfer to clk_sys + if(bit_cnt == 7) begin + spi_byte_in <= { sbuf, SPI_MOSI}; + spi_receiver_strobe_r <= ~spi_receiver_strobe_r; + end + end +end + +// Process bytes from SPI at the clk_sys domain +always @(posedge clk_sys) begin + + reg spi_receiver_strobe; + reg spi_transfer_end; + reg spi_receiver_strobeD; + reg spi_transfer_endD; + reg [7:0] acmd; + reg [7:0] abyte_cnt; // counts bytes + + reg [7:0] mouse_flags_r; + reg [7:0] mouse_x_r; + + reg key_pressed_r; + reg key_extended_r; + + //synchronize between SPI and sys clock domains + spi_receiver_strobeD <= spi_receiver_strobe_r; + spi_receiver_strobe <= spi_receiver_strobeD; + spi_transfer_endD <= spi_transfer_end_r; + spi_transfer_end <= spi_transfer_endD; + + key_strobe <= 0; + mouse_strobe <= 0; + + if (~spi_transfer_endD & spi_transfer_end) begin + abyte_cnt <= 8'd0; + end else if (spi_receiver_strobeD ^ spi_receiver_strobe) begin + + if(~&abyte_cnt) + abyte_cnt <= abyte_cnt + 8'd1; + + if(abyte_cnt == 0) begin + acmd <= spi_byte_in; + end else begin + case(acmd) + // buttons and switches + 8'h01: but_sw <= spi_byte_in; + 8'h60: if (abyte_cnt < 5) joystick_0[(abyte_cnt-1)<<3 +:8] <= spi_byte_in; + 8'h61: if (abyte_cnt < 5) joystick_1[(abyte_cnt-1)<<3 +:8] <= spi_byte_in; + 8'h62: if (abyte_cnt < 5) joystick_2[(abyte_cnt-1)<<3 +:8] <= spi_byte_in; + 8'h63: if (abyte_cnt < 5) joystick_3[(abyte_cnt-1)<<3 +:8] <= spi_byte_in; + 8'h64: if (abyte_cnt < 5) joystick_4[(abyte_cnt-1)<<3 +:8] <= spi_byte_in; + 8'h04: begin + // store incoming ps2 mouse bytes + ps2_mouse_fifo[ps2_mouse_wptr] <= spi_byte_in; + ps2_mouse_wptr <= ps2_mouse_wptr + 1'd1; + if (abyte_cnt == 1) mouse_flags_r <= spi_byte_in; + else if (abyte_cnt == 2) mouse_x_r <= spi_byte_in; + else if (abyte_cnt == 3) begin + // flags: YOvfl, XOvfl, dy8, dx8, 1, mbtn, rbtn, lbtn + mouse_flags <= mouse_flags_r; + mouse_x <= { mouse_flags_r[4], mouse_x_r }; + mouse_y <= { mouse_flags_r[5], spi_byte_in }; + mouse_strobe <= 1; + end + end + 8'h05: begin + // store incoming ps2 keyboard bytes + ps2_kbd_fifo[ps2_kbd_wptr] <= spi_byte_in; + ps2_kbd_wptr <= ps2_kbd_wptr + 1'd1; + if (abyte_cnt == 1) begin + key_extended_r <= 0; + key_pressed_r <= 1; + end + if (spi_byte_in == 8'he0) key_extended_r <= 1'b1; + else if (spi_byte_in == 8'hf0) key_pressed_r <= 1'b0; + else begin + key_extended <= key_extended_r; + key_pressed <= key_pressed_r || abyte_cnt == 1; + key_code <= spi_byte_in; + key_strobe <= 1'b1; + end + end + + // joystick analog + 8'h1a: begin + // first byte is joystick index + if(abyte_cnt == 1) + stick_idx <= spi_byte_in[2:0]; + else if(abyte_cnt == 2) begin + // second byte is x axis + if(stick_idx == 0) + joystick_analog_0[15:8] <= spi_byte_in; + else if(stick_idx == 1) + joystick_analog_1[15:8] <= spi_byte_in; + end else if(abyte_cnt == 3) begin + // third byte is y axis + if(stick_idx == 0) + joystick_analog_0[7:0] <= spi_byte_in; + else if(stick_idx == 1) + joystick_analog_1[7:0] <= spi_byte_in; + end + end + + 8'h15: status <= spi_byte_in; + + // status, 32bit version + 8'h1e: if(abyte_cnt<5) status[(abyte_cnt-1)<<3 +:8] <= spi_byte_in; + + endcase + end + end +end + +// Process SD-card related bytes from SPI at the clk_sd domain +always @(posedge clk_sd) begin + + reg spi_receiver_strobe; + reg spi_transfer_end; + reg spi_receiver_strobeD; + reg spi_transfer_endD; + reg sd_wrD; + reg [7:0] acmd; + reg [7:0] abyte_cnt; // counts bytes + + //synchronize between SPI and sd clock domains + spi_receiver_strobeD <= spi_receiver_strobe_r; + spi_receiver_strobe <= spi_receiver_strobeD; + spi_transfer_endD <= spi_transfer_end_r; + spi_transfer_end <= spi_transfer_endD; + + if(sd_dout_strobe) begin + sd_dout_strobe<= 0; + if(~&sd_buff_addr) sd_buff_addr <= sd_buff_addr + 1'b1; + end + + sd_din_strobe<= 0; + sd_wrD <= sd_wr; + // fetch the first byte immediately after the write command seen + if (~sd_wrD & sd_wr) begin + sd_buff_addr <= 0; + sd_din_strobe <= 1; + end + + img_mounted <= 0; + + if (~spi_transfer_endD & spi_transfer_end) begin + abyte_cnt <= 8'd0; + sd_ack <= 1'b0; + sd_ack_conf <= 1'b0; + sd_dout_strobe <= 1'b0; + sd_din_strobe <= 1'b0; + sd_buff_addr <= 0; + end else if (spi_receiver_strobeD ^ spi_receiver_strobe) begin + + if(~&abyte_cnt) + abyte_cnt <= abyte_cnt + 8'd1; + + if(abyte_cnt == 0) begin + acmd <= spi_byte_in; + + if(spi_byte_in == 8'h18) begin + sd_din_strobe <= 1'b1; + if(~&sd_buff_addr) sd_buff_addr <= sd_buff_addr + 1'b1; + end + + if((spi_byte_in == 8'h17) || (spi_byte_in == 8'h18)) + sd_ack <= 1'b1; + + end else begin + case(acmd) + + // send sector IO -> FPGA + 8'h17: begin + // flag that download begins + sd_dout_strobe <= 1'b1; + sd_dout <= spi_byte_in; + end + + // send sector FPGA -> IO + 8'h18: begin + sd_din_strobe <= 1'b1; + if(~&sd_buff_addr) sd_buff_addr <= sd_buff_addr + 1'b1; + end + + // send SD config IO -> FPGA + 8'h19: begin + // flag that download begins + sd_dout_strobe <= 1'b1; + sd_ack_conf <= 1'b1; + sd_dout <= spi_byte_in; + end + + 8'h1c: img_mounted <= 1; + + // send image info + 8'h1d: if(abyte_cnt<5) img_size[(abyte_cnt-1)<<3 +:8] <= spi_byte_in; + endcase + end + end +end + +endmodule