From ae22db96407d3c946aecddca465b0b6aa2d608e2 Mon Sep 17 00:00:00 2001 From: Rutherther Date: Fri, 5 Jan 2024 23:57:56 +0100 Subject: [PATCH] fix: many changes --- src/i2c/address_generator.vhd | 2 +- src/i2c/master.vhd | 2 +- src/i2c/master_state.vhd | 2 + src/i2c/scl_generator.vhd | 5 - src/i2c/tx.vhd | 10 +- src/ssd1306/counter.vhd | 299 ++++++++++++++++++++++++++++++++++ src/ssd1306/full_on.vhd | 196 ++++++++++++++++++++++ src/ssd1306/ssd1306_pkg.vhd | 134 +++++++++++++++ src/utils/bcd_counter.vhd | 39 +++++ src/utils/clock_divider.vhd | 36 ++++ src/utils/counter.vhd | 54 ++++++ tb/ssd1306/counter_tb.vhd | 96 +++++++++++ tb/ssd1306/full_on_tb.vhd | 110 +++++++++++++ 13 files changed, 974 insertions(+), 11 deletions(-) create mode 100644 src/ssd1306/counter.vhd create mode 100644 src/ssd1306/full_on.vhd create mode 100644 src/ssd1306/ssd1306_pkg.vhd create mode 100644 src/utils/bcd_counter.vhd create mode 100644 src/utils/clock_divider.vhd create mode 100644 src/utils/counter.vhd create mode 100644 tb/ssd1306/counter_tb.vhd create mode 100644 tb/ssd1306/full_on_tb.vhd diff --git a/src/i2c/address_generator.vhd b/src/i2c/address_generator.vhd index 9e03648..efc6019 100644 --- a/src/i2c/address_generator.vhd +++ b/src/i2c/address_generator.vhd @@ -52,7 +52,7 @@ begin -- architecture a1 curr_scl; done_o <= curr_done and not next_done; - next_done <= '1' when curr_state = ACK and next_state /= ACK else + next_done <= '1' when curr_state = ACK and scl_rising_i = '1' else '1' when curr_done = '1' and scl_falling_delayed_i = '0' else '0'; diff --git a/src/i2c/master.vhd b/src/i2c/master.vhd index d033c31..81ae9cf 100644 --- a/src/i2c/master.vhd +++ b/src/i2c/master.vhd @@ -91,7 +91,6 @@ architecture a1 of master is signal waiting_for_data : std_logic; signal scl_gen_falling : std_logic; begin -- architecture a1 - dev_busy_o <= transmitting or receiving; bus_busy_o <= bus_busy; waiting_for_data <= tx_scl_stretch or rx_scl_stretch; waiting_o <= waiting_for_data; @@ -277,6 +276,7 @@ begin -- architecture a1 address_gen_o => adr_gen, receive_o => receiving, transmit_o => transmitting, + dev_busy_o => dev_busy_o, bus_busy_o => bus_busy); end architecture a1; diff --git a/src/i2c/master_state.vhd b/src/i2c/master_state.vhd index 894070b..187c497 100644 --- a/src/i2c/master_state.vhd +++ b/src/i2c/master_state.vhd @@ -46,6 +46,7 @@ entity master_state is address_gen_o : out std_logic; receive_o : out std_logic; transmit_o : out std_logic; + dev_busy_o : out std_logic; bus_busy_o : out std_logic); end entity master_state; @@ -101,6 +102,7 @@ begin -- architecture a1 req_start_o <= '1' when curr_state = GENERATING_START or curr_state = GENERATED_START else '0'; req_stop_o <= '1' when curr_state = GENERATING_STOP or curr_state = GENERATED_STOP else '0'; cond_gen_o <= cond_gen; + dev_busy_o <= '1' when curr_state /= IDLE and curr_state /= BUS_BUSY and curr_state /= ERR else '0'; address_gen_start_o <= '1' when curr_state = GENERATED_START and next_state = GENERATING_ADDRESS else '0'; diff --git a/src/i2c/scl_generator.vhd b/src/i2c/scl_generator.vhd index cf5890d..db1b39e 100644 --- a/src/i2c/scl_generator.vhd +++ b/src/i2c/scl_generator.vhd @@ -43,9 +43,6 @@ architecture a1 of scl_generator is signal curr_scl_enable : std_logic := '0'; signal next_scl_enable : std_logic; - - signal curr_requested_change : std_logic; - signal next_requested_change : std_logic; begin -- architecture a1 cannot_comply_o <= '1' when curr_state = CHANGING and scl_changing /= '1' else '0'; @@ -90,13 +87,11 @@ begin -- architecture a1 if rst_in = '0' then -- synchronous reset (active low) curr_stable_count <= 0; curr_scl_enable <= '0'; - curr_requested_change <= '0'; curr_state <= OBSERVE; curr_scl <= '1'; else curr_stable_count <= next_stable_count; curr_scl_enable <= next_scl_enable; - curr_requested_change <= next_requested_change; curr_state <= next_state; curr_scl <= next_scl; end if; diff --git a/src/i2c/tx.vhd b/src/i2c/tx.vhd index 897223b..4aa2774 100644 --- a/src/i2c/tx.vhd +++ b/src/i2c/tx.vhd @@ -185,10 +185,6 @@ begin -- architecture a1 curr_saving_buffer_index <= 0; curr_scl <= '1'; -- assume 1 (the default, no one transmitting) curr_done <= next_done; - elsif rst_i2c_i = '1' then - curr_state <= IDLE; - curr_scl <= '1'; -- assume 1 (the default, no one transmitting) - curr_done <= '0'; else curr_state <= next_state; curr_tx_buffers <= next_tx_buffers; @@ -198,6 +194,12 @@ begin -- architecture a1 curr_scl <= next_scl; curr_done <= next_done; end if; + + if rst_i2c_i = '1' then + curr_state <= IDLE; + curr_scl <= '1'; -- assume 1 (the default, no one transmitting) + curr_done <= '0'; + end if; end if; end process set_regs; diff --git a/src/ssd1306/counter.vhd b/src/ssd1306/counter.vhd new file mode 100644 index 0000000..6c7c7fa --- /dev/null +++ b/src/ssd1306/counter.vhd @@ -0,0 +1,299 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library i2c; +library utils; + +use work.ssd1306_pkg.all; + +entity ssd1306_counter is + + generic ( + CLK_FREQ : integer := 100000000; -- Input clock frequency + I2C_CLK_FREQ : integer := 5000000; + COUNT_FREQ : integer := 1; + DELAY : integer := 20; + EXPECT_ACK : std_logic := '1'; + SCL_MIN_STABLE_CYCLES : natural := 50); + + port ( + clk_i : in std_logic; + rst_i : in std_logic; + + start_i : in std_logic; + + err_noack_data_o : out std_logic; + err_noack_address_o : out std_logic; + err_arbitration_o : out std_logic; + err_general_o : out std_logic; + + state_o : out std_logic_vector(3 downto 0); + substate_o : out std_logic_vector(2 downto 0); + + bus_busy_o : out std_logic; + dev_busy_o : out std_logic; + waiting_o : out std_logic; + + sda_io : inout std_logic; + scl_io : inout std_logic); + +end entity ssd1306_counter; + +architecture a1 of ssd1306_counter is + constant ADDRESS : std_logic_vector(6 downto 0) := "0111100"; + + signal rst_n : std_logic; + signal rst_sync : std_logic; + + signal i2c_clk : std_logic; + + signal master_start, master_stop, master_run : std_logic; + signal rw : std_logic; + + signal tx_valid, tx_ready, tx_clear_buffer : std_logic; + signal tx_data : std_logic_vector(7 downto 0); + + signal rx_valid, rx_confirm : std_logic; + signal rx_data : std_logic_vector(7 downto 0); + + signal err_noack_data : std_logic; + signal err_noack_address : std_logic; + signal err_arbitration : std_logic; + signal err_general : std_logic; + signal any_err : std_logic; + + signal waiting : std_logic; + signal dev_busy : std_logic; + + signal sda, scl : std_logic; + signal sda_enable, scl_enable : std_logic; + + type state_t is (IDLE, INIT_DISPLAY, INIT_ADDRESSING_MODE, ZERO_OUT_RAM, SET_ADR_ZERO, DIGIT_N, SEC_DELAY); + signal curr_state : state_t; + signal next_state : state_t; + + signal curr_digit : natural; + signal next_digit : natural; + + type substate_t is (IDLE, START, DATA, STOP, ALT); + signal curr_substate : substate_t; + signal next_substate : substate_t; + + constant DIGITS : natural := 3; + signal count : std_logic_vector(DIGITS * 4 - 1 downto 0); + + signal digit_data : data_arr_t(0 to 8); + + signal curr_index : natural; + signal next_index : natural; + signal max_index : natural; +begin -- architecture a+ + + state_o <= std_logic_vector(to_unsigned(state_t'pos(curr_state), 4)); + substate_o <= std_logic_vector(to_unsigned(substate_t'pos(curr_substate), 3)); + + tx_clear_buffer <= '0'; + master_run <= '1'; + rst_n <= not rst_sync; + waiting_o <= waiting; + dev_busy_o <= dev_busy; + + err_noack_data_o <= err_noack_data; + err_noack_address_o <= err_noack_address; + err_arbitration_o <= err_arbitration; + err_general_o <= err_general; + any_err <= err_general or err_arbitration or err_noack_address or err_noack_data; + + rw <= '0'; + + max_index <= SSD1306_INIT'length when curr_state = INIT_DISPLAY else + SSD1306_HORIZONTAL_ADDRESSING_MODE'length when curr_state = INIT_ADDRESSING_MODE else + 9 when curr_state = DIGIT_N else + SSD1306_CURSOR_TO_ZERO'length when curr_state = SET_ADR_ZERO else + I2C_CLK_FREQ/2 when curr_state = SEC_DELAY else + SSD1306_ZERO_LEN when curr_state = ZERO_OUT_RAM else + 0; + + next_index <= 0 when curr_substate = IDLE else + (curr_index + 1) when curr_substate = ALT and curr_index < max_index else + (curr_index + 1) when tx_ready = '1' and curr_index < max_index and curr_substate = DATA else + curr_index; + + tx_valid <= '1' when tx_ready = '1' and curr_index < max_index and curr_substate = DATA else '0'; + + tx_data <= SSD1306_INIT(curr_index) when curr_state = INIT_DISPLAY and curr_index < max_index else + SSD1306_HORIZONTAL_ADDRESSING_MODE(curr_index) when curr_state = INIT_ADDRESSING_MODE and curr_index < max_index else + SSD1306_ZERO(0) when curr_state = ZERO_OUT_RAM and curr_index = 0 else + SSD1306_ZERO(1) when curr_state = ZERO_OUT_RAM and curr_index /= 0 else + SSD1306_CURSOR_TO_ZERO(curr_index) when curr_state = SET_ADR_ZERO and curr_index < max_index else + + digit_data(curr_index) when curr_state = DIGIT_N and curr_index < max_index else + "00000000"; + + next_digit <= 0 when curr_state /= DIGIT_N else + (curr_digit + 1) when curr_state = DIGIT_N and curr_substate = STOP and curr_digit < DIGITS - 1 else + curr_digit; + + digit_data <= ssd1306_bcd_digit_data(count(((DIGITS - 1 - curr_digit) + 1) * 4 - 1 downto (DIGITS - 1 - curr_digit) * 4)); + + master_start <= '1' when curr_substate = START else '0'; + master_stop <= '1' when curr_substate = STOP else '0'; + + set_next_state: process (all) is + begin -- process set_next_state + next_state <= curr_state; + + if curr_state = IDLE then + next_state <= INIT_DISPLAY; + elsif curr_state = INIT_DISPLAY then + if curr_substate = STOP then + next_state <= INIT_ADDRESSING_MODE; + end if; + elsif curr_state = INIT_ADDRESSING_MODE then + if curr_substate = STOP then + next_state <= ZERO_OUT_RAM; + end if; + elsif curr_state = ZERO_OUT_RAM then + if curr_substate = STOP then + next_state <= SET_ADR_ZERO; + end if; + elsif curr_state = SET_ADR_ZERO then + if curr_substate = STOP then + next_state <= DIGIT_N; + end if; + elsif curr_state = DIGIT_N then + if curr_substate = STOP and curr_digit = DIGITS - 1 then + next_state <= SEC_DELAY; + end if; + elsif curr_state = SEC_DELAY then + if curr_index = max_index then + next_state <= SET_ADR_ZERO; + end if; + end if; + + if any_err = '1' then + next_state <= IDLE; + end if; + end process set_next_state; + + set_next_substate: process (all) is + begin -- process set_next_state + next_substate <= curr_substate; + + if curr_state /= IDLE and curr_state /= SEC_DELAY then + if curr_substate = IDLE then + if dev_busy = '0' then + next_substate <= START; + end if; + elsif curr_substate = START then + next_substate <= DATA; + elsif curr_substate = DATA and curr_index = max_index then + if waiting = '1' then + next_substate <= STOP; + end if; + elsif curr_substate = STOP then + next_substate <= IDLE; + elsif curr_substate = ALT then + next_substate <= IDLE; + end if; + else + next_substate <= ALT; + end if; + end process set_next_substate; + + counter: entity utils.counter + generic map ( + MAX => 9, + DIGITS => DIGITS, + IN_FREQ => CLK_FREQ, + OUT_FREQ => COUNT_FREQ) + port map ( + clk_i => clk_i, + rst_in => rst_n, + value_o => count); + + divider: entity utils.clock_divider + generic map ( + IN_FREQ => CLK_FREQ, + OUT_FREQ => I2C_CLK_FREQ) + port map ( + clk_i => clk_i, + clk_o => i2c_clk); + + i2c_master: entity i2c.master + generic map ( + SCL_FALLING_DELAY => DELAY, + SCL_MIN_STABLE_CYCLES => SCL_MIN_STABLE_CYCLES) + port map ( + clk_i => i2c_clk, + rst_in => rst_n, +-- + slave_address_i => ADDRESS, +-- + generate_ack_i => '1', + expect_ack_i => EXPECT_ACK, +-- + rx_valid_o => rx_valid, + rx_data_o => rx_data, + rx_confirm_i => rx_confirm, +-- + tx_ready_o => tx_ready, + tx_valid_i => tx_valid, + tx_data_i => tx_data, + tx_clear_buffer_i => tx_clear_buffer, +-- + err_noack_data_o => err_noack_data, + err_noack_address_o => err_noack_address, + err_arbitration_o => err_arbitration, + err_general_o => err_general, +-- + stop_i => master_stop, + start_i => master_start, + run_i => master_run, + rw_i => rw, +-- + dev_busy_o => dev_busy, + bus_busy_o => bus_busy_o, + waiting_o => waiting, +-- + sda_i => sda, + scl_i => scl, + sda_enable_o => sda_enable, + scl_enable_o => scl_enable); + + sync_reset: entity utils.metastability_filter + port map ( + clk_i => clk_i, + signal_i => rst_i, + signal_o => rst_sync); + + sda_open_buffer: entity utils.open_drain_buffer + port map ( + pad_io => sda_io, + enable_i => sda_enable, + state_o => sda); + scl_open_buffer: entity utils.open_drain_buffer + port map ( + pad_io => scl_io, + enable_i => scl_enable, + state_o => scl); + + set_regs: process (i2c_clk) is + begin -- process set_regs + if rising_edge(i2c_clk) then -- rising clock edge + if rst_n = '0' then -- synchronous reset (active low) + curr_state <= IDLE; + curr_substate <= IDLE; + curr_digit <= 0; + curr_index <= 0; + else + curr_state <= next_state; + curr_substate <= next_substate; + curr_digit <= next_digit; + curr_index <= next_index; + end if; + end if; + end process set_regs; + +end architecture a1; diff --git a/src/ssd1306/full_on.vhd b/src/ssd1306/full_on.vhd new file mode 100644 index 0000000..4734435 --- /dev/null +++ b/src/ssd1306/full_on.vhd @@ -0,0 +1,196 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library i2c; +library utils; + +entity full_on is + + generic ( + CLK_FREQ : integer := 100000000; -- Input clock frequency + I2C_CLK_FREQ : integer := 5000000; + DELAY : integer := 20; + + SCL_MIN_STABLE_CYCLES : natural := 50); + + port ( + clk_i : in std_logic; + rst_i : in std_logic; + + start_i : in std_logic; + + err_noack_data_o : out std_logic; + err_noack_address_o : out std_logic; + err_arbitration_o : out std_logic; + err_general_o : out std_logic; + + full_on_state_o : out std_logic_vector(2 downto 0); + + bus_busy_o : out std_logic; + dev_busy_o : out std_logic; + waiting_o : out std_logic; + + sda_io : inout std_logic; + scl_io : inout std_logic); + +end entity full_on; + +architecture a1 of full_on is + constant ADDRESS : std_logic_vector(6 downto 0) := "0111100"; + + type data_arr_t is array (natural range <>) of std_logic_vector(7 downto 0); + constant INIT_DATA : data_arr_t := ( + X"80", X"A8", X"80", X"3F", + X"80", X"D3", X"80", X"00", + X"80", X"40", + X"80", X"A0", + X"80", X"C0", + X"80", X"DA", X"80", X"02", + X"80", X"81", X"80", X"7F", + X"80", X"A4", + X"80", X"A6", + X"80", X"D5", X"80", X"80", + X"80", X"8D", X"80", X"14", + X"80", X"AF", -- init done + X"80", X"A5" --entire display on + ); + + signal rst_n : std_logic; + signal rst_sync : std_logic; + + signal i2c_clk : std_logic; + + signal master_start, master_stop, master_run : std_logic; + signal rw : std_logic; + + signal tx_valid, tx_ready, tx_clear_buffer : std_logic; + signal tx_data : std_logic_vector(7 downto 0); + + signal rx_valid, rx_confirm : std_logic; + signal rx_data : std_logic_vector(7 downto 0); + + signal waiting : std_logic; + + signal sda, scl : std_logic; + signal sda_enable, scl_enable : std_logic; + + signal curr_index : natural; + signal next_index : natural; + signal go_next : std_logic; + + type state_t is (IDLE, START, COUNT, STOP, DONE); + signal curr_state : state_t; + signal next_state : state_t; +begin -- architecture a1 + tx_clear_buffer <= '0'; + master_run <= '1'; + rst_n <= not rst_sync; + waiting_o <= waiting; + -- i2c_clk <= clk_i; + + rw <= '0'; + + full_on_state_o <= std_logic_vector(to_unsigned(state_t'pos(curr_state), 3)); + + + next_index <= 0 when curr_state = IDLE else + (curr_index + 1) when go_next = '1' else + curr_index; + + go_next <= '1' when tx_ready = '1' and curr_index < INIT_DATA'length else '0'; + + + tx_valid <= '1' when tx_ready = '1' and curr_index < INIT_DATA'length else '0'; + tx_data <= INIT_DATA(curr_index) when curr_index < INIT_DATA'length else "00000000"; + + next_state <= START when start_i = '1' and curr_state = IDLE else + COUNT when curr_state = START else + STOP when curr_state = COUNT and curr_index = INIT_DATA'length and waiting = '1' else + DONE when curr_state = STOP else + IDLE when curr_state = DONE and start_i = '0' else + curr_state; + + master_start <= '1' when curr_state = START else '0'; + master_stop <= '1' when curr_state = STOP else '0'; + + divider: entity utils.clock_divider + generic map ( + IN_FREQ => CLK_FREQ, + OUT_FREQ => I2C_CLK_FREQ) + port map ( + clk_i => clk_i, + clk_o => i2c_clk); + + i2c_master: entity i2c.master + generic map ( + SCL_FALLING_DELAY => DELAY, + SCL_MIN_STABLE_CYCLES => SCL_MIN_STABLE_CYCLES) + port map ( + clk_i => i2c_clk, + rst_in => rst_n, +-- + slave_address_i => ADDRESS, +-- + generate_ack_i => '1', + expect_ack_i => '1', +-- + rx_valid_o => rx_valid, + rx_data_o => rx_data, + rx_confirm_i => rx_confirm, +-- + tx_ready_o => tx_ready, + tx_valid_i => tx_valid, + tx_data_i => tx_data, + tx_clear_buffer_i => tx_clear_buffer, +-- + err_noack_data_o => err_noack_data_o, + err_noack_address_o => err_noack_address_o, + err_arbitration_o => err_arbitration_o, + err_general_o => err_general_o, +-- + stop_i => master_stop, + start_i => master_start, + run_i => master_run, + rw_i => rw, +-- + dev_busy_o => dev_busy_o, + bus_busy_o => bus_busy_o, + waiting_o => waiting, +-- + sda_i => sda, + scl_i => scl, + sda_enable_o => sda_enable, + scl_enable_o => scl_enable); + + sync_reset: entity utils.metastability_filter + port map ( + clk_i => clk_i, + signal_i => rst_i, + signal_o => rst_sync); + + sda_open_buffer: entity utils.open_drain_buffer + port map ( + pad_io => sda_io, + enable_i => sda_enable, + state_o => sda); + scl_open_buffer: entity utils.open_drain_buffer + port map ( + pad_io => scl_io, + enable_i => scl_enable, + state_o => scl); + + set_regs: process (i2c_clk) is + begin -- process set_regs + if rising_edge(i2c_clk) then -- rising clock edge + if rst_n = '0' then -- synchronous reset (active low) + curr_state <= IDLE; + curr_index <= 0; + else + curr_state <= next_state; + curr_index <= next_index; + end if; + end if; + end process set_regs; + +end architecture a1; diff --git a/src/ssd1306/ssd1306_pkg.vhd b/src/ssd1306/ssd1306_pkg.vhd new file mode 100644 index 0000000..93ec3a1 --- /dev/null +++ b/src/ssd1306/ssd1306_pkg.vhd @@ -0,0 +1,134 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +package ssd1306_pkg is + + constant DISPLAY_WIDTH : unsigned(7 downto 0) := to_unsigned(128, 8); + constant DISPLAY_HEIGHT : unsigned(7 downto 0) := to_unsigned(64, 8); + + type data_arr_t is array (natural range <>) of std_logic_vector(7 downto 0); + + constant SSD1306_INIT : data_arr_t := ( + X"80", X"A8", X"80", X"3F", + X"80", X"D3", X"80", X"00", + X"80", X"40", + X"80", X"A0", + X"80", X"C0", + X"80", X"DA", X"80", X"02", + X"80", X"81", X"80", X"7F", + X"80", X"A4", + X"80", X"A6", + X"80", X"D5", X"80", X"80", + X"80", X"8D", X"80", X"14", + X"80", X"AF" -- init done + ); + + constant SSD1306_HORIZONTAL_ADDRESSING_MODE : data_arr_t := ( + X"80", X"20", -- set addressing mode + X"80", X"00" -- horizontal + ); + + constant SSD1306_CURSOR_TO_ZERO : data_arr_t := ( + X"80", X"21", -- set column address + X"80", X"00", -- set first address + X"80", X"7F", -- set last address + + X"80", X"22", -- set page address + X"80", X"00", -- start + X"80", X"07" -- end + ); + + constant SSD1306_ZERO_LEN : integer := 1025; + constant SSD1306_ZERO : data_arr_t := ( + X"40", X"00" + ); + + ----------------------------------------------------------------------------- + -- 8x8 characters obtained from https://github.com/dhepper/font8x8 + ----------------------------------------------------------------------------- + + -- contains beginning of i2c packet + -- (0x40) to signal only data will follow + +constant SSD1306_CHAR_0 : data_arr_t(0 to 8) := ( + X"40", + X"3E", X"7F", X"71", X"59", + X"4D", X"7F", X"3E", X"00" +); +constant SSD1306_CHAR_1 : data_arr_t(0 to 8) := ( + X"40", + X"40", X"42", X"7F", X"7F", + X"40", X"40", X"00", X"00" +); +constant SSD1306_CHAR_2 : data_arr_t(0 to 8) := ( + X"40", + X"62", X"73", X"59", X"49", + X"6F", X"66", X"00", X"00" +); +constant SSD1306_CHAR_3 : data_arr_t(0 to 8) := ( + X"40", + X"22", X"63", X"49", X"49", + X"7F", X"36", X"00", X"00" +); +constant SSD1306_CHAR_4 : data_arr_t(0 to 8) := ( + X"40", + X"18", X"1C", X"16", X"53", + X"7F", X"7F", X"50", X"00" +); +constant SSD1306_CHAR_5 : data_arr_t(0 to 8) := ( + X"40", + X"27", X"67", X"45", X"45", + X"7D", X"39", X"00", X"00" +); +constant SSD1306_CHAR_6 : data_arr_t(0 to 8) := ( + X"40", + X"3C", X"7E", X"4B", X"49", + X"79", X"30", X"00", X"00" +); +constant SSD1306_CHAR_7 : data_arr_t(0 to 8) := ( + X"40", + X"03", X"03", X"71", X"79", + X"0F", X"07", X"00", X"00" +); +constant SSD1306_CHAR_8 : data_arr_t(0 to 8) := ( + X"40", + X"36", X"7F", X"49", X"49", + X"7F", X"36", X"00", X"00" +); +constant SSD1306_CHAR_9 : data_arr_t(0 to 8) := ( + X"40", + X"06", X"4F", X"49", X"69", + X"3F", X"1E", X"00", X"00" +); + + function ssd1306_bcd_digit_data ( + constant digit : std_logic_vector(3 downto 0)) + return data_arr_t; + +end package ssd1306_pkg; + +package body ssd1306_pkg is + + function ssd1306_bcd_digit_data ( + constant digit : std_logic_vector(3 downto 0)) + return data_arr_t is + variable ret : data_arr_t(0 to 8); + begin + case digit is + when "0000" => ret := SSD1306_CHAR_0; + when "0001" => ret := SSD1306_CHAR_1; + when "0010" => ret := SSD1306_CHAR_2; + when "0011" => ret := SSD1306_CHAR_3; + when "0100" => ret := SSD1306_CHAR_4; + when "0101" => ret := SSD1306_CHAR_5; + when "0110" => ret := SSD1306_CHAR_6; + when "0111" => ret := SSD1306_CHAR_7; + when "1000" => ret := SSD1306_CHAR_8; + when "1001" => ret := SSD1306_CHAR_9; + when others => ret := SSD1306_CHAR_0; + end case; + return ret; + end function ssd1306_bcd_digit_data; + +end package body ssd1306_pkg; diff --git a/src/utils/bcd_counter.vhd b/src/utils/bcd_counter.vhd new file mode 100644 index 0000000..826d3b2 --- /dev/null +++ b/src/utils/bcd_counter.vhd @@ -0,0 +1,39 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity bcd_counter is + + generic ( + MAX : integer := 10); + + port ( + clk_i : in std_logic; + rst_in : in std_logic; + carry_i : in std_logic; + carry_o : out std_logic; + count_o : out std_logic_vector(3 downto 0)); + +end entity bcd_counter; + +architecture a1 of bcd_counter is + signal count_next : unsigned(3 downto 0); + signal count_reg : unsigned(3 downto 0); +begin -- architecture a1 + + count_next <= ((count_reg + 1) mod MAX) when carry_i = '1' else count_reg; + carry_o <= '1' when count_next = "0000" and carry_i = '1' else '0'; + count_o <= std_logic_vector(count_reg); + + set_count: process (clk_i) is + begin -- process set_count + if rising_edge(clk_i) then -- rising clock edge + if rst_in = '0' then -- synchronous reset (active low) + count_reg <= "0000"; + else + count_reg <= count_next; + end if; + end if; + end process set_count; + +end architecture a1; diff --git a/src/utils/clock_divider.vhd b/src/utils/clock_divider.vhd new file mode 100644 index 0000000..99ef72c --- /dev/null +++ b/src/utils/clock_divider.vhd @@ -0,0 +1,36 @@ +library ieee; +use ieee.std_logic_1164.all; + +entity clock_divider is + + generic ( + IN_FREQ : integer; + OUT_FREQ : integer); + + port ( + clk_i : in std_logic; + clk_o : out std_logic); + +end entity clock_divider; + +architecture a1 of clock_divider is + constant MAX : integer := IN_FREQ / OUT_FREQ; + signal counter_next : integer range 0 to MAX; + signal counter_reg : integer range 0 to MAX; +begin -- architecture a1 + keep_max_freq: if IN_FREQ = OUT_FREQ generate + clk_o <= clk_i; + end generate keep_max_freq; + + counter: if IN_FREQ /= OUT_FREQ generate + counter_next <= (counter_reg + 1) when counter_reg < MAX else 0; + clk_o <= '1' when counter_reg >= MAX/2 else '0'; + set_counter: process (clk_i) is + begin -- process set_counter + if rising_edge(clk_i) then -- rising clock edge + counter_reg <= counter_next; + end if; + end process set_counter; + end generate counter; + +end architecture a1; diff --git a/src/utils/counter.vhd b/src/utils/counter.vhd new file mode 100644 index 0000000..88dec86 --- /dev/null +++ b/src/utils/counter.vhd @@ -0,0 +1,54 @@ +library ieee; +use ieee.std_logic_1164.all; + +entity counter is + + generic ( + MAX : natural := 9; + DIGITS : natural; + IN_FREQ : natural; + OUT_FREQ : natural); + + port ( + clk_i : in std_logic; + rst_in : in std_logic; + value_o : out std_logic_vector(DIGITS * 4 - 1 downto 0)); + +end entity counter; + +architecture a1 of counter is + signal count_clk : std_logic; + + signal carries : std_logic_vector(DIGITS downto 0); + signal value : std_logic_vector(DIGITS * 4 - 1 downto 0); +begin -- architecture a1 + value_o <= value; + + pulse_gen: entity work.sync_edge_detector + port map ( + clk_i => clk_i, + signal_i => count_clk, + rising_edge_o => carries(0), + falling_edge_o => open); + + clock_divider: entity work.clock_divider + generic map ( + IN_FREQ => IN_FREQ, + OUT_FREQ => OUT_FREQ) + port map ( + clk_i => clk_i, + clk_o => count_clk); + + counters: for i in 0 to DIGITS - 1 generate + bcd_counter: entity work.bcd_counter + generic map ( + MAX => MAX) + port map ( + clk_i => clk_i, + rst_in => rst_in, + carry_i => carries(i), + carry_o => carries(i + 1), + count_o => value((i + 1) * 4 - 1 downto i * 4)); + end generate counters; + +end architecture a1; diff --git a/tb/ssd1306/counter_tb.vhd b/tb/ssd1306/counter_tb.vhd new file mode 100644 index 0000000..fbba0ce --- /dev/null +++ b/tb/ssd1306/counter_tb.vhd @@ -0,0 +1,96 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library vunit_lib; +context vunit_lib.vunit_context; + +library ssd1306; + +library utils; + +library i2c_tb; +use i2c_tb.tb_pkg.all; +use i2c_tb.tb_i2c_pkg.all; +use i2c_tb.tb_i2c_slave_pkg.all; + +entity counter_tb is + + generic ( + runner_cfg : string); + +end entity counter_tb; + +architecture tb of counter_tb is + constant ADDRESS : std_logic_vector(6 downto 0) := "0111100"; + constant CLK_PERIOD : time := 10 ns; + + signal rst_n : std_logic := '0'; + signal rst : std_logic; + + signal not_scl : std_logic; + + signal err_noack_data, err_noack_address, err_arbitration, err_general : std_logic; + signal bus_busy, dev_busy : std_logic; + + signal one : std_logic := '1'; + constant SCL_MIN_STABLE_CYCLES : natural := 10; + constant TIMEOUT : time := SCL_MIN_STABLE_CYCLES * CLK_PERIOD * 4; +begin -- architecture tb + uut : entity ssd1306.ssd1306_counter + generic map ( + CLK_FREQ => 100, + I2C_CLK_FREQ => 10, + COUNT_FREQ => 5, + DELAY => 1, + EXPECT_ACK => '0', + SCL_MIN_STABLE_CYCLES => SCL_MIN_STABLE_CYCLES) + port map ( + clk_i => clk, + rst_i => rst, + start_i => '1', + err_noack_data_o => err_noack_data, + err_noack_address_o => err_noack_address, + err_arbitration_o => err_arbitration, + err_general_o => err_general, + dev_busy_o => dev_busy, + bus_busy_o => bus_busy, + sda_io => sda, + scl_io => scl + ); + + sda <= 'H'; + scl <= 'H'; + + not_scl <= not scl; + + clk <= not clk after CLK_PERIOD / 2; + rst_n <= '1' after 6 * CLK_PERIOD; + rst <= not rst_n; + + -- TODO: allow conditions from master... + -- sda_stability_check: check_stable(clk, one, scl, not_scl, sda); + + main: process is + begin -- process main + wait until rst_n = '1'; + wait for 2 * CLK_PERIOD; + wait until falling_edge(clk); + test_runner_setup(runner, runner_cfg); + + while test_suite loop + if run("no_check") then + wait for 50 ms; + -- i2c_slave_check_start(ADDRESS, '0', TIMEOUT, scl, sda); + -- for i in 0 to 100 loop + -- i2c_slave_receive(X"ZZ"); + -- end loop; -- i + -- -- stop because no ack + -- i2c_slave_check_stop(TIMEOUT * 10, scl, sda); + end if; + end loop; + + test_runner_cleanup(runner); + end process main; + +end architecture tb; diff --git a/tb/ssd1306/full_on_tb.vhd b/tb/ssd1306/full_on_tb.vhd new file mode 100644 index 0000000..55f8bf3 --- /dev/null +++ b/tb/ssd1306/full_on_tb.vhd @@ -0,0 +1,110 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library vunit_lib; +context vunit_lib.vunit_context; + +library ssd1306; + +library utils; + +library i2c_tb; +use i2c_tb.tb_pkg.all; +use i2c_tb.tb_i2c_pkg.all; +use i2c_tb.tb_i2c_slave_pkg.all; + +entity full_on_tb is + + generic ( + runner_cfg : string); + +end entity full_on_tb; + +architecture tb of full_on_tb is + constant ADDRESS : std_logic_vector(6 downto 0) := "0111100"; + constant CLK_PERIOD : time := 10 ns; + + signal rst_n : std_logic := '0'; + signal rst : std_logic; + + signal not_scl : std_logic; + + signal err_noack_data, err_noack_address, err_arbitration, err_general : std_logic; + signal bus_busy, dev_busy : std_logic; + + signal one : std_logic := '1'; + constant SCL_MIN_STABLE_CYCLES : natural := 10; + constant TIMEOUT : time := SCL_MIN_STABLE_CYCLES * CLK_PERIOD * 4; +begin -- architecture tb + uut : entity ssd1306.full_on + generic map ( + CLK_FREQ => 10000000, + I2C_CLK_FREQ => 10000000, + DELAY => 1, + SCL_MIN_STABLE_CYCLES => SCL_MIN_STABLE_CYCLES) + port map ( + clk_i => clk, + rst_i => rst, + start_i => '1', + err_noack_data_o => err_noack_data, + err_noack_address_o => err_noack_address, + err_arbitration_o => err_arbitration, + err_general_o => err_general, + dev_busy_o => dev_busy, + bus_busy_o => bus_busy, + sda_io => sda, + scl_io => scl + ); + + sda <= 'H'; + scl <= 'H'; + + not_scl <= not scl; + + clk <= not clk after CLK_PERIOD / 2; + rst_n <= '1' after 6 * CLK_PERIOD; + rst <= not rst_n; + + -- TODO: allow conditions from master... + -- sda_stability_check: check_stable(clk, one, scl, not_scl, sda); + + main: process is + begin -- process main + wait until rst_n = '1'; + wait for 2 * CLK_PERIOD; + wait until falling_edge(clk); + test_runner_setup(runner, runner_cfg); + + while test_suite loop + if run("start") then + i2c_slave_check_start(ADDRESS, '0', TIMEOUT, scl, sda); + i2c_slave_receive(X"80", TIMEOUT, scl, sda); + i2c_slave_receive(X"A8", TIMEOUT, scl, sda); + i2c_slave_receive(X"80", TIMEOUT, scl, sda); + i2c_slave_receive(X"3F", TIMEOUT, scl, sda); + i2c_slave_receive(X"80", TIMEOUT, scl, sda); + -- stop because no ack + i2c_slave_check_stop(TIMEOUT * 10, scl, sda); + elsif run("last") then + i2c_slave_check_start(ADDRESS, '0', TIMEOUT, scl, sda); + for i in 0 to 35 loop + i2c_slave_receive(X"ZZ", TIMEOUT, scl, sda); + end loop; -- i + i2c_slave_receive(X"80", TIMEOUT, scl, sda); + i2c_slave_receive(X"A5", TIMEOUT, scl, sda); + i2c_slave_check_stop(TIMEOUT, scl, sda); + elsif run("every_second_X80") then + i2c_slave_check_start(ADDRESS, '0', TIMEOUT, scl, sda); + for i in 0 to 18 loop + i2c_slave_receive(X"80", TIMEOUT, scl, sda); + i2c_slave_receive(X"ZZ", TIMEOUT, scl, sda); + end loop; -- i + i2c_slave_check_stop(TIMEOUT, scl, sda); + end if; + end loop; + + test_runner_cleanup(runner); + end process main; + +end architecture tb; -- 2.49.0