For smaller, performance-critical saturation counters this pattern might be of use, as it doesn't use the carry chain and only has a single level of logic:

`library IEEE;`

use IEEE.STD_LOGIC_1164.ALL;

use IEEE.NUMERIC_STD.ALL;

entity sat_counter is

Port ( clk : in STD_LOGIC;

inc : in STD_LOGIC;

dec : in STD_LOGIC;

count : out STD_LOGIC_VECTOR (3 downto 0));

end sat_counter;

architecture Behavioral of sat_counter is

type a_mem is array(0 to 63) of std_logic_vector(3 downto 0);

signal mem : a_mem := (x"0",x"1",x"2",x"3",x"4",x"5",x"6",x"7",x"8",x"9",x"A",x"B",x"C",x"D",x"E",x"F", -- Nnee

x"1",x"2",x"3",x"4",x"5",x"6",x"7",x"8",x"9",x"A",x"B",x"C",x"D",x"E",x"F",x"F", -- Inc

x"0",x"0",x"1",x"2",x"3",x"4",x"5",x"6",x"7",x"8",x"9",x"A",x"B",x"C",x"D",x"E", -- Dec

x"0",x"1",x"2",x"3",x"4",x"5",x"6",x"7",x"8",x"9",x"A",x"B",x"C",x"D",x"E",x"F"); -- inc & dec

signal index : std_logic_vector(5 downto 0);

signal state : std_logic_vector(3 downto 0) := (others => '0');

begin

count <= std_logic_vector(state);

index <= dec & inc & state;

process(clk)

begin

if rising_edge(clk) then

state <= mem(to_integer(unsigned(index)));

end if;

end process;

end Behavioral;

For example, this four bit counter needs four 6-input LUTs and 4FFs.

BRAM blocks can also be used for counters of moderate widths (8 or so bits).

You can sometimes add add an "enable" and reset input at no extra cost (see second schematic):

`library IEEE;`

use IEEE.STD_LOGIC_1164.ALL;

use IEEE.NUMERIC_STD.ALL;

entity sat_counter is

Port ( clk : in STD_LOGIC;

inc : in STD_LOGIC;

dec : in STD_LOGIC;

enable : in STD_LOGIC;

reset : in STD_LOGIC;

count : out STD_LOGIC_VECTOR (3 downto 0));

end sat_counter;

architecture Behavioral of sat_counter is

type a_mem is array(0 to 63) of std_logic_vector(3 downto 0);

signal mem : a_mem := (x"0",x"1",x"2",x"3",x"4",x"5",x"6",x"7",x"8",x"9",x"A",x"B",x"C",x"D",x"E",x"F", -- Nnee

x"1",x"2",x"3",x"4",x"5",x"6",x"7",x"8",x"9",x"A",x"B",x"C",x"D",x"E",x"F",x"F", -- Inc

x"0",x"0",x"1",x"2",x"3",x"4",x"5",x"6",x"7",x"8",x"9",x"A",x"B",x"C",x"D",x"E", -- Dec

x"0",x"1",x"2",x"3",x"4",x"5",x"6",x"7",x"8",x"9",x"A",x"B",x"C",x"D",x"E",x"F"); -- inc & dec

signal index : std_logic_vector(5 downto 0);

signal state : std_logic_vector(3 downto 0) := (others => '0');

begin

count <= std_logic_vector(state);

index <= dec & inc & state;

process(clk)

begin

if rising_edge(clk) then

if reset = '1' then

state <= (others => '0');

elsif enable = '1' then

state <= mem(to_integer(unsigned(index)));

end if;

end if;

end process;

end Behavioral;

You can push the ROM+state register into a Block RAM, making longer fast counters possible, and with Dual-port RAMs you can implement two counters using the same memory block.