~ruther/vhdl-i2c

ae22db96407d3c946aecddca465b0b6aa2d608e2 — Rutherther 1 year, 5 months ago 0545a32
fix: many changes
M src/i2c/address_generator.vhd => src/i2c/address_generator.vhd +1 -1
@@ 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';


M src/i2c/master.vhd => src/i2c/master.vhd +1 -1
@@ 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;

M src/i2c/master_state.vhd => src/i2c/master_state.vhd +2 -0
@@ 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';


M src/i2c/scl_generator.vhd => src/i2c/scl_generator.vhd +0 -5
@@ 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;

M src/i2c/tx.vhd => src/i2c/tx.vhd +6 -4
@@ 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;


A src/ssd1306/counter.vhd => src/ssd1306/counter.vhd +299 -0
@@ 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;

A src/ssd1306/full_on.vhd => src/ssd1306/full_on.vhd +196 -0
@@ 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;

A src/ssd1306/ssd1306_pkg.vhd => src/ssd1306/ssd1306_pkg.vhd +134 -0
@@ 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;

A src/utils/bcd_counter.vhd => src/utils/bcd_counter.vhd +39 -0
@@ 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;

A src/utils/clock_divider.vhd => src/utils/clock_divider.vhd +36 -0
@@ 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;

A src/utils/counter.vhd => src/utils/counter.vhd +54 -0
@@ 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;

A tb/ssd1306/counter_tb.vhd => tb/ssd1306/counter_tb.vhd +96 -0
@@ 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;

A tb/ssd1306/full_on_tb.vhd => tb/ssd1306/full_on_tb.vhd +110 -0
@@ 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;

Do not follow this link