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.