Skip to content

Instantly share code, notes, and snippets.

@Thraetaona
Last active January 30, 2024 23:05
Show Gist options
  • Save Thraetaona/ba941e293d36d0f76db6b9f3476b823c to your computer and use it in GitHub Desktop.
Save Thraetaona/ba941e293d36d0f76db6b9f3476b823c to your computer and use it in GitHub Desktop.
A Simple VHDL Abstraction of an Efficient Clock Prescaler Using Cascading Shift Registers
-------------------------------------------------------------------------------
-- SPDX-License-Identifier: LGPL-3.0-or-later or CERN-OHL-W-2.0
--
-- srl_prescaler.vhd: A Simple VHDL Abstraction of an Efficient Clock
-- Prescaler Using Cascading Shift Registers.
--
-- Copyright (C) 2024 Fereydoun Memarzanjany
--
-- This hardware-descriptive model is free hardware design dual-licensed under
-- the GNU LGPL or CERN OHL v2 Weakly Reciprocal: you can redistribute it
-- and/or modify it under the terms of the...
-- * GNU Lesser General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version; OR
-- * CERN Open Hardware Licence Version 2 - Weakly Reciprocal.
--
-- This is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General
-- Public License and the CERN Open Hardware Licence Version
-- 2 - Weakly Reciprocal along with this. If not, see...
-- * <https://spdx.org/licenses/LGPL-3.0-or-later.html>; and
-- * <https://spdx.org/licenses/CERN-OHL-W-2.0.html>.
--
-------------------------------------------------------------------------------
-- Company: N/A
-- Engineer: Fereydoun Memarzanjany
--
-- Create Date: 01/25/2024
-- Design Name: N/A
-- Module Name: srl_prescaler - behavioral
-- Project Name: N/A
-- Target Devices: Artix-A7
-- Tool Versions: Vivado 2023.2.1
-- Description: A template generator for an efficient shift register-based
-- clock divider.
-- Dependencies: Xilxin's unisim.vcomponents.srl32e
-- Revision:
-- Revision 1.00 - Initial Release
-- Additional Comments: Requires VHDL-2008
--
-------------------------------------------------------------------------------
library std;
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
-- Xilinx-specific libraries; TODO: try to "infer" this instead of instantiate.
library unisim;
use unisim.vcomponents.all;
-- How to Instantiate and Use This Entity:
--
-- prescaler : entity work.srl_prescaler
-- generic map (100e6, 1)
-- port map (clk_in_100mhz, ce_out_1hz);
--
entity srl_prescaler is
generic (
INPUT_FREQ : positive;
TARGET_FREQ : positive range positive'low to INPUT_FREQ;
DUTY_CYCLE : real range 0.0000 to 0.9999 := 0.50;
SRL_DEPTH : positive := 32 -- UNIMPLEMENTED!
);
port (
clk_in : in std_ulogic;
ce_out : out std_logic register := '0'
);
end srl_prescaler;
architecture structural of srl_prescaler is
-- A helper function that returns a list of a given integer's
-- _biggest_ divisors, all of which will be less than `bound`.
--
-- NOTE: Slow and intended for compile-time evaluation only!
function factorize(num : integer; bound: positive) return integer_vector is
variable number : integer := num; -- a mutable alias
variable factors : integer_vector(0 to bound-1);
variable count : integer := 0;
begin
per_factor : for factor in bound-1 downto 2 loop -- Big-to-Small
when_divisible : while number mod factor = 0 loop
factors(count) := factor;
number := number / factor;
count := count + 1;
end loop when_divisible;
end loop per_factor;
-- Conditional assignment of initial values requires VHDL-2019.
--return factors(0 to count-1) when number = 1 else (0 downto 1 => 0);
was_factorizable : if number = 1 then
return factors(0 to count-1);
else
return (0 downto 1 => 0); -- Null/0-length integer_vector
end if was_factorizable;
end function;
-- E.g., (100MHz / 1Hz) * 50% duty = (100,000,000 / 100) * 0.5 = 50,000,000
constant COUNTER_TICKS : integer :=
integer( (INPUT_FREQ / TARGET_FREQ) * DUTY_CYCLE );
constant FACTORS_LIST : integer_vector :=
factorize(COUNTER_TICKS, SRL_DEPTH);
constant FACTORS_HIGH : natural := FACTORS_LIST'high;
signal div_clk: std_ulogic_vector(FACTORS_HIGH downto 0) :=
(others => '0');
begin
assert FACTORS_HIGH = 0
report "Unsupported frequency; srl_prescaler can divide only by " &
"numbers that are factorizable by integers 2 to 32 (exclusive)."
severity failure;
-- In VHDL-2019, blocks can appear inside procedures, which
-- should make the generation of this look even cleaner.
srl_chain : for i in 0 to FACTORS_HIGH generate
srl_wrapper : block
signal feedback: std_ulogic; -- NOTE: Local to each iteration.
begin
-- NOTE: If your toolchain does not support conditional when...else
-- assignments for signals or port maps, then you could manually
-- expand/duplicate this using if...generate clauses.
srl_instance : component unisim.vcomponents.SRLC32E
generic map (
INIT => X"0000_0001", -- Initial contents of shift register
IS_CLK_INVERTED => '0' -- Optional inversion for CLK
)
port map (
Q => feedback, -- 1-bit out: SRL Data
Q31 => open, -- 1-bit out: SRL Cascade Data
A => -- 5-bit in: Selects SRL depth
std_logic_vector(to_unsigned(FACTORS_LIST(i), 5) - 1),
CE => -- 1-bit in: Clock enable
'1' when i = 0 else div_clk(i-1),
CLK => clk_in, -- 1-bit in: Clock
D => feedback -- 1-bit in: SRL Data
);
-- First instance's output gets connected with its feedback only;
-- this is because no previous outputs exist before it, and
-- performing `AND` on a constant '1' signal is pointless.
div_clk(i) <= feedback when i = 0 else (div_clk(i-1) and feedback);
end block srl_wrapper;
end generate srl_chain;
-- Unfortunately, some tools (e.g., Vivado) synthetize only a subset of
-- VHDL-2008 and do not provide support for some language features like
-- the below-mentioned `guarded-block` statements; therefore, due to
-- compatibility, the more general `process` clause has been used instead.
/*
-- https://www.edaboard.com/threads/vhdl-register-reseved-word.288096/
toggle_ce_out: block (div_clk(FACTORS_HIGH)) begin
ce_out <= guarded not ce_out; -- ce_out has to be a register.
end block toggle_ce_out;
*/
toggle_ce_out : process (clk_in) begin
if rising_edge( div_clk(FACTORS_HIGH) ) then
ce_out <= not ce_out; -- +- 1% delay
end if;
end process toggle_ce_out;
end structural;
-------------------------------------------------------------------------------
-- END OF FILE: srl_prescaler.vhd
-------------------------------------------------------------------------------
@Thraetaona
Copy link
Author

Thraetaona commented Jan 29, 2024

How to Instantiate and Use This Module:

prescaler : entity work.srl_prescaler
	generic map (100e6, 1)
	port map (clk_in_100mhz, ce_out_1hz);

In the above example, an input clock of 100 MHz (i.e., 100e6 & clk_in_100mhz) gets divided into a clock enable signal of 1 Hz (i.e.,  1 & ce_out_1hz).
Optionally, you could include a custom duty cycle (other than the default of 50%) as the third parameter: generic map (100e6, 1, 0.50)

More information on Y Combinator's Hacker News.


27-Bit Counter SRLC32E Shift Registers
================================= =================================
28 FLOP_LATCH 1 FLOP_LATCH
8 LUT 6 LUT
7 CARRY 6 DMEM

Resulting RTL & Synthetized Logics From This SRLC32E Prescaler:

srl_prescaler_rtl
srl_prescaler_synth

Compare it With the Resulting RTL & Synthetized Logics From a Naive 27-Bit Tick Counter:

naive_counter_rtl
naive_counter_synth

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment