diff --git a/fpga/arty-lcd-ts.vhdl b/fpga/arty-lcd-ts.vhdl new file mode 100644 index 0000000..614bfa8 --- /dev/null +++ b/fpga/arty-lcd-ts.vhdl @@ -0,0 +1,214 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library work; +use work.wishbone_types.all; + +-- Interface for LCD/touchscreen connected to Arduino-compatible socket on Arty A7 +entity lcd_touchscreen is + port ( + clk : in std_ulogic; + rst : in std_ulogic; + wb_in : in wb_io_master_out; + wb_out : out wb_io_slave_out; + wb_sel : in std_ulogic; + tp : out std_ulogic; + + lcd_din : in std_ulogic_vector(7 downto 0); + lcd_dout : out std_ulogic_vector(7 downto 0); + lcd_doe : out std_ulogic; + lcd_doe0 : out std_ulogic; + lcd_doe1 : out std_ulogic; + lcd_rd : out std_ulogic; -- note active low + lcd_wr : out std_ulogic; -- note active low + lcd_rs : out std_ulogic; + lcd_rsoe : out std_ulogic; + lcd_cs : out std_ulogic; -- note active low + lcd_csoe : out std_ulogic; + lcd_rst : out std_ulogic -- note active low + ); +end entity lcd_touchscreen; + +architecture rtl of lcd_touchscreen is + + type state_t is (idle, prep1, prep2, writing, wr_pause, reading, rd_recovery); + + signal state : state_t; + signal delay : unsigned(5 downto 0); + signal ack : std_ulogic; + signal idle1 : std_ulogic; + signal idle2 : std_ulogic; + + signal rs : std_ulogic; + signal rsoe : std_ulogic; + signal cs : std_ulogic; + signal csoe : std_ulogic; + signal d0 : std_ulogic; + signal doe0 : std_ulogic; + signal doe1 : std_ulogic; + signal d1 : std_ulogic; + signal tsctrl : std_ulogic; + + signal wr_data : std_ulogic_vector(31 downto 0); + signal rd_data : std_ulogic_vector(7 downto 0); + signal wr_sel : std_ulogic_vector(3 downto 0); + signal req_wr : std_ulogic; + + -- Assume touchscreen is connected as follows: + -- X+ -> A5 (D1), X- -> A3 (CS), Y+ -> A2 (RS), Y- -> A4 (D0) + +begin + -- for now; should make sure it is at least 10us wide + lcd_rst <= not rst; + + wb_out.dat <= rd_data & rd_data & rd_data & rd_data; + wb_out.ack <= ack; + wb_out.stall <= '0' when state = idle else '1'; + + lcd_doe0 <= doe0; + lcd_doe1 <= doe1; + lcd_rs <= rs; + lcd_rsoe <= rsoe; + lcd_cs <= cs; + lcd_csoe <= csoe; + + tp <= tsctrl; + + process (clk) + begin + if rising_edge(clk) then + ack <= '0'; + idle2 <= idle1; + if rst = '1' then + state <= idle; + delay <= to_unsigned(0, 6); + rd_data <= (others => '0'); + lcd_rd <= '1'; + lcd_wr <= '1'; + cs <= '1'; + csoe <= '1'; + rs <= '0'; + rsoe <= '1'; + lcd_doe <= '0'; + doe0 <= '0'; + doe1 <= '0'; + d0 <= '0'; + d1 <= '0'; + idle1 <= '0'; + idle2 <= '0'; + tsctrl <= '0'; + elsif delay /= "000000" then + delay <= delay - 1; + else + case state is + when idle => + req_wr <= wb_in.we; + wr_data <= wb_in.dat; + wr_sel <= wb_in.sel; + if idle2 = '1' then + -- delay this one cycle after entering idle + lcd_doe <= '0'; + doe0 <= '0'; + doe1 <= '0'; + end if; + idle1 <= '0'; + if wb_in.cyc = '1' and wb_in.stb = '1' and wb_sel = '1' then + if wb_in.sel = "0000" then + ack <= '1'; + else + if wb_in.we = '1' or wb_in.adr(2) = '1' then + ack <= '1'; + end if; + if wb_in.adr(2) = '0' then + tsctrl <= '0'; + csoe <= '1'; + cs <= '0'; -- active low + rsoe <= '1'; + rs <= wb_in.adr(1); + doe0 <= '0'; + doe1 <= '0'; + state <= prep1; + else + tsctrl <= '1'; + idle2 <= '0'; + rd_data <= rsoe & rs & doe0 & d0 & doe1 & d1 & csoe & cs; + if wb_in.we = '1' and wb_in.sel(0) = '1' then + rsoe <= wb_in.dat(7); + rs <= wb_in.dat(6); + doe0 <= wb_in.dat(5); + d0 <= wb_in.dat(4); + lcd_dout(0) <= wb_in.dat(4); + doe1 <= wb_in.dat(3); + d1 <= wb_in.dat(2); + lcd_dout(1) <= wb_in.dat(2); + csoe <= wb_in.dat(1); + cs <= wb_in.dat(0); + end if; + end if; + end if; + else + if tsctrl = '0' then + cs <= '1'; + end if; + end if; + when prep1 => + lcd_doe <= req_wr; + doe0 <= req_wr; + doe1 <= req_wr; + if req_wr = '1' then + if wr_sel(1 downto 0) /= "00" then + if wr_sel(1) = '1' then + lcd_dout <= wr_data(15 downto 8); + wr_sel(1) <= '0'; + else + lcd_dout <= wr_data(7 downto 0); + wr_sel(0) <= '0'; + end if; + else + if wr_sel(3) = '1' then + lcd_dout <= wr_data(31 downto 24); + wr_sel(3) <= '0'; + else + lcd_dout <= wr_data(23 downto 16); + wr_sel(2) <= '0'; + end if; + end if; + end if; + state <= prep2; + when prep2 => + if req_wr = '1' then + lcd_wr <= '0'; -- active low + state <= writing; + delay <= to_unsigned(1, 6); + else + lcd_rd <= '0'; + state <= reading; + delay <= to_unsigned(35, 6); + end if; + when writing => + -- last cycle of writing state + lcd_wr <= '1'; + if wr_sel = "0000" then + state <= idle; + idle1 <= '1'; + else + state <= wr_pause; + end if; + when wr_pause => + state <= prep1; + when reading => + -- last cycle of reading state + lcd_rd <= '1'; + rd_data <= lcd_din; + ack <= '1'; + state <= rd_recovery; + delay <= to_unsigned(6, 6); + when rd_recovery => + state <= idle; + end case; + end if; + end if; + end process; + +end architecture; diff --git a/fpga/arty_a7.xdc b/fpga/arty_a7.xdc index c52d072..7f6a0a5 100644 --- a/fpga/arty_a7.xdc +++ b/fpga/arty_a7.xdc @@ -166,6 +166,26 @@ set_property IOB true [get_cells -hierarchical -filter {NAME =~*.litesdcard2/sdp set_property -dict { PACKAGE_PIN D2 IOSTANDARD LVCMOS33 PULLUP TRUE } [get_ports { i2c_rtc_d }]; set_property -dict { PACKAGE_PIN H2 IOSTANDARD LVCMOS33 PULLUP TRUE } [get_ports { i2c_rtc_c }]; +################################################################################ +# TFT LCD shield (arduino-compatible) +# hacked to swap the LCD_RST and LCD_D0 lines, and put LCD_D1 on A5 +################################################################################ + +set_property -dict { PACKAGE_PIN N15 IOSTANDARD LVCMOS33 PULLDOWN TRUE } [get_ports { lcd_rst }]; +#set_property -dict { PACKAGE_PIN M16 IOSTANDARD LVCMOS33 } [get_ports { lcd_tp }]; +set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[2] }]; +set_property -dict { PACKAGE_PIN T11 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[3] }]; +set_property -dict { PACKAGE_PIN R12 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[4] }]; +set_property -dict { PACKAGE_PIN T14 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[5] }]; +set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[6] }]; +set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[7] }]; +set_property -dict { PACKAGE_PIN F5 IOSTANDARD LVCMOS33 PULLUP TRUE } [get_ports { lcd_rd }]; # A0 +set_property -dict { PACKAGE_PIN D8 IOSTANDARD LVCMOS33 PULLUP TRUE } [get_ports { lcd_wr }]; # A1 +set_property -dict { PACKAGE_PIN C7 IOSTANDARD LVCMOS33 } [get_ports { lcd_rs }]; # A2 +set_property -dict { PACKAGE_PIN E7 IOSTANDARD LVCMOS33 } [get_ports { lcd_cs }]; # A3 +set_property -dict { PACKAGE_PIN D7 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[0] }]; # A4 +set_property -dict { PACKAGE_PIN D5 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[1] }]; # A5 + ################################################################################ # Arduino/chipKIT shield connector ################################################################################ diff --git a/fpga/top-arty.vhdl b/fpga/top-arty.vhdl index b4ba15a..cf5f775 100644 --- a/fpga/top-arty.vhdl +++ b/fpga/top-arty.vhdl @@ -30,6 +30,7 @@ entity toplevel is HAS_UART1 : boolean := true; USE_LITESDCARD : boolean := false; HAS_GPIO : boolean := true; + USE_LCD : boolean := true; NGPIO : natural := 32 ); port( @@ -140,6 +141,14 @@ entity toplevel is i2c_rtc_d : inout std_ulogic; i2c_rtc_c : inout std_ulogic; + -- LCD display interface + lcd_d : inout std_ulogic_vector(7 downto 0); + lcd_rs : out std_ulogic; + lcd_cs : out std_ulogic; + lcd_rd : out std_ulogic; + lcd_wr : out std_ulogic; + lcd_rst : out std_ulogic; + -- DRAM wires ddram_a : out std_ulogic_vector(13 downto 0); ddram_ba : out std_ulogic_vector(2 downto 0); @@ -185,6 +194,7 @@ architecture behaviour of toplevel is signal wb_ext_is_dram_init : std_ulogic; signal wb_ext_is_eth : std_ulogic; signal wb_ext_is_sdcard : std_ulogic; + signal wb_ext_is_lcd : std_ulogic; -- DRAM main data wishbone connection signal wb_dram_in : wishbone_master_out; @@ -213,6 +223,9 @@ architecture behaviour of toplevel is -- for conversion from non-pipelined wishbone to pipelined signal wb_sddma_stb_sent : std_ulogic; + -- LCD touchscreen connection + signal wb_lcd_out : wb_io_slave_out := wb_io_slave_out_init; + -- Status LED signal led_b_pwm : std_ulogic_vector(3 downto 0) := (others => '0'); signal led_r_pwm : std_ulogic_vector(3 downto 0) := (others => '0'); @@ -289,6 +302,7 @@ begin HAS_UART1 => HAS_UART1, HAS_SD_CARD => USE_LITESDCARD, HAS_SD_CARD2 => USE_LITESDCARD, + HAS_LCD => USE_LCD, HAS_GPIO => HAS_GPIO, NGPIO => NGPIO ) @@ -336,6 +350,7 @@ begin wb_ext_is_dram_init => wb_ext_is_dram_init, wb_ext_is_eth => wb_ext_is_eth, wb_ext_is_sdcard => wb_ext_is_sdcard, + wb_ext_is_lcd => wb_ext_is_lcd, -- DMA wishbone wishbone_dma_in => wb_sddma_in, @@ -828,10 +843,54 @@ begin disk_activity <= sdc0_activity or sdc1_activity; end generate; + -- LCD touchscreen on arduino-compatible pins + has_lcd : if USE_LCD generate + signal lcd_dout : std_ulogic_vector(7 downto 0); + signal lcd_doe : std_ulogic; + signal lcd_doe0 : std_ulogic; + signal lcd_doe1 : std_ulogic; + signal lcd_rso : std_ulogic; + signal lcd_rsoe : std_ulogic; + signal lcd_cso : std_ulogic; + signal lcd_csoe : std_ulogic; + signal tp : std_ulogic; + begin + lcd0 : entity work.lcd_touchscreen + port map ( + clk => system_clk, + rst => soc_rst, + wb_in => wb_ext_io_in, + wb_out => wb_lcd_out, + wb_sel => wb_ext_is_lcd, + tp => tp, + + lcd_din => lcd_d, + lcd_dout => lcd_dout, + lcd_doe => lcd_doe, + lcd_doe0 => lcd_doe0, + lcd_doe1 => lcd_doe1, + lcd_rd => lcd_rd, + lcd_wr => lcd_wr, + lcd_rs => lcd_rso, + lcd_rsoe => lcd_rsoe, + lcd_cs => lcd_cso, + lcd_csoe => lcd_csoe, + lcd_rst => lcd_rst + ); + -- lcd_d(0), lcd_d(1), lcd_rs, lcd_cs are used for the touchscreen + -- interface and hence have individual output enables. + lcd_d(0) <= lcd_dout(0) when lcd_doe0 = '1' else 'Z'; + lcd_d(1) <= lcd_dout(1) when lcd_doe1 = '1' else 'Z'; + lcd_d(7 downto 2) <= lcd_dout(7 downto 2) when lcd_doe = '1' else (others => 'Z'); + lcd_rs <= lcd_rso when lcd_rsoe = '1' else 'Z'; + lcd_cs <= lcd_cso when lcd_csoe = '1' else 'Z'; + end generate; + -- Mux WB response on the IO bus wb_ext_io_out <= wb_eth_out when wb_ext_is_eth = '1' else wb_sdcard_out when wb_ext_is_sdcard = '1' and wb_ext_io_in.adr(13) = '0' else wb_sdcard2_out when wb_ext_is_sdcard = '1' and wb_ext_io_in.adr(13) = '1' else + wb_lcd_out when wb_ext_is_lcd = '1' else wb_dram_ctrl_out; status_led_colour : process(all) diff --git a/microwatt.core b/microwatt.core index c1f6184..b54248e 100644 --- a/microwatt.core +++ b/microwatt.core @@ -139,6 +139,10 @@ filesets: uart16550: depend : [":microwatt:uart16550"] + lcdts: + files: + - fpga/arty-lcd-ts.vhdl : {file_type : vhdlSource-2008} + targets: nexys_a7: default_tool: vivado @@ -315,7 +319,7 @@ targets: arty_a7-100-nodram: default_tool: vivado - filesets: [core, arty_a7, soc, fpga, debug_xilinx, uart16550, xilinx_specific, litesdcard] + filesets: [core, arty_a7, soc, fpga, debug_xilinx, uart16550, xilinx_specific, litesdcard, lcdts] parameters : - memory_size - ram_init_file @@ -336,7 +340,7 @@ targets: arty_a7-100: default_tool: vivado - filesets: [core, arty_a7, soc, fpga, debug_xilinx, litedram, liteeth, uart16550, xilinx_specific, litesdcard] + filesets: [core, arty_a7, soc, fpga, debug_xilinx, litedram, liteeth, uart16550, xilinx_specific, litesdcard, lcdts] parameters: - cpus - memory_size @@ -344,6 +348,7 @@ targets: - use_litedram=true - use_liteeth=true - use_litesdcard + - use_lcd - disable_flatten_core - no_bram - spi_flash_offset=4194304 @@ -570,6 +575,12 @@ parameters: paramtype : generic default : false + use_lcd: + datatype : bool + description : Use LCD touchscreen interface + paramtype : generic + default : false + uart_is_16550: datatype : bool description : Use 16550-compatible UART from OpenCores diff --git a/soc.vhdl b/soc.vhdl index fc30014..539b26e 100644 --- a/soc.vhdl +++ b/soc.vhdl @@ -34,6 +34,7 @@ use work.wishbone_types.all; -- 0xc8020000: LiteEth CSRs (*) -- 0xc8030000: LiteEth MMIO (*) -- 0xc8040000: LiteSDCard CSRs +-- 0xc8050000: LCD touchscreen interface -- (*) LiteEth must be a single aligned 32KB block as the CSRs and MMIOs -- are actually decoded as a single wishbone which LiteEth will @@ -95,6 +96,7 @@ entity soc is DCACHE_TLB_NUM_WAYS : natural := 2; HAS_SD_CARD : boolean := false; HAS_SD_CARD2 : boolean := false; + HAS_LCD : boolean := false; HAS_GPIO : boolean := false; NGPIO : natural := 32 ); @@ -116,6 +118,7 @@ entity soc is wb_ext_is_dram_init : out std_ulogic; wb_ext_is_eth : out std_ulogic; wb_ext_is_sdcard : out std_ulogic; + wb_ext_is_lcd : out std_ulogic; -- external DMA wishbone with 32-bit data/address wishbone_dma_in : out wb_io_slave_out := wb_io_slave_out_init; @@ -686,6 +689,7 @@ begin wb_ext_is_dram_csr <= '0'; wb_ext_is_eth <= '0'; wb_ext_is_sdcard <= '0'; + wb_ext_is_lcd <= '0'; end if; if do_cyc = '1' then -- Decode I/O address @@ -715,6 +719,10 @@ begin slave_io := SLAVE_IO_EXTERNAL; io_cycle_external <= '1'; wb_ext_is_sdcard <= '1'; + elsif std_match(match, x"--05-") and HAS_LCD then + slave_io := SLAVE_IO_EXTERNAL; + io_cycle_external <= '1'; + wb_ext_is_lcd <= '1'; else io_cycle_none <= '1'; end if;