Last active
January 30, 2024 23:05
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
------------------------------------------------------------------------------- | |
-- 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 | |
------------------------------------------------------------------------------- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How to Instantiate and Use This Module:
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)
FLOP_LATCH
FLOP_LATCH
LUT
LUT
CARRY
DMEM
Resulting RTL & Synthetized Logics From This SRLC32E Prescaler:
Compare it With the Resulting RTL & Synthetized Logics From a Naive 27-Bit Tick Counter: