diff --git a/modules/common/Manifest.py b/modules/common/Manifest.py index b7491d706099b737ea5c6ff80079d14060381ac6..2d4ad63c8558c0df32274bdf7de8801fc18f80aa 100644 --- a/modules/common/Manifest.py +++ b/modules/common/Manifest.py @@ -18,6 +18,7 @@ files = [ "gencores_pkg.vhd", "gc_i2c_slave.vhd", "gc_glitch_filt.vhd", "gc_dyn_glitch_filt.vhd", + "gc_comparator.vhd", "gc_big_adder.vhd", "gc_fsm_watchdog.vhd", "gc_bicolor_led_ctrl.vhd", diff --git a/modules/common/gc_comparator.vhd b/modules/common/gc_comparator.vhd new file mode 100644 index 0000000000000000000000000000000000000000..02c541220f16046824f4e65bbec843336b259c58 --- /dev/null +++ b/modules/common/gc_comparator.vhd @@ -0,0 +1,187 @@ +-------------------------------------------------------------------------------- +-- CERN (BE-CO-HT) +-- General-Purpose Comparator +-- https://www.ohwr.org/projects/general-cores +-------------------------------------------------------------------------------- +-- +-- unit name: gc_comparator +-- +-- description: +-- +-- This unit implements a general-purpose comparator with optional hysteresis. +-- The unit offers two outputs, one is the "traditional" comparator output +-- (output is high as long as positive input is greater than negative input), +-- while the other one is a monostable (one clock tick long) output. +-- +-- Inputs have configurable width and they will be treated as signed vectors +-- internally. The Hysteresis input, if used, will be treated as an unsigned +-- vector. +-- +-- A "polarity inversion" input bit is also available to make the comparator +-- output active when the positive input is below the negative input. If not +-- used, this input defaults to "non-inverted". +-- +-- A "comparator enable" bit is also available to disable the outputs of the +-- comparator. This is useful when changing settings (threshold, polarity) to +-- avoid glitches on the outputs. +-- +-- Pleas note that after reset (or after re-enabling the input), the unit will +-- wait for the positive input to drop below (above if inverted) the negative +-- input before performing any comparisons. This is done to avoid output +-- glitches when switching on and/or when changing settings. +-- +-------------------------------------------------------------------------------- +-- Copyright (c) 2017 CERN / BE-CO-HT +-------------------------------------------------------------------------------- +-- GNU LESSER GENERAL PUBLIC LICENSE +-------------------------------------------------------------------------------- +-- This source file is free software; 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 2.1 of the License, or (at your +-- option) any later version. This source 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 along with this +-- source; if not, download it from http://www.gnu.org/licenses/lgpl-2.1.html +-------------------------------------------------------------------------------- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity gc_comparator is + + generic ( + -- Width of input vectors (in bits). + g_IN_WIDTH : natural := 32); + port ( + clk_i : in std_logic; + rst_n_i : in std_logic := '1'; + -- Polarity inversion + pol_inv_i : in std_logic := '0'; + -- Comparator enable + enable_i : in std_logic := '1'; + -- Positive input (treated as signed) + inp_i : in std_logic_vector(g_IN_WIDTH-1 downto 0); + -- Negative input (treated as signed) + inn_i : in std_logic_vector(g_IN_WIDTH-1 downto 0); + -- Hysteresis (treated as unsigned) + hys_i : in std_logic_vector(g_IN_WIDTH-1 downto 0) := (others => '0'); + -- Comparator output + out_o : out std_logic; + out_p_o : out std_logic); + +end entity gc_comparator; + +architecture arch of gc_comparator is + + type t_FSM_STATE is (S_IDLE, S_BELOW_THRES, S_ABOVE_THRES1, S_ABOVE_THRES2); + + signal fsm_regin, fsm_regout : t_FSM_STATE := S_IDLE; + + signal inp_signx : signed(g_IN_WIDTH downto 0) := (others => '0'); + signal inn_signx : signed(g_IN_WIDTH downto 0) := (others => '0'); + signal hys_signx : signed(g_IN_WIDTH downto 0) := (others => '0'); + signal u_thres : signed(g_IN_WIDTH downto 0) := (others => '0'); + signal l_thres : signed(g_IN_WIDTH downto 0) := (others => '0'); + + signal l_below : std_logic := '0'; + signal u_above : std_logic := '0'; + + signal thr_cross : std_logic := '0'; + signal hys_cross : std_logic := '0'; + + signal comp_out : std_logic := '0'; + signal comp_out_p : std_logic := '0'; + +begin -- architecture arch + + -- Convert inputs to signed and perform comparisons, taking into account + -- optional hysteresis and polarity inversion. See also: + -- https://en.wikipedia.org/wiki/Comparator#Hysteresis + inp_signx <= resize(signed(inp_i), g_IN_WIDTH+1); + inn_signx <= resize(signed(inn_i), g_IN_WIDTH+1); + hys_signx <= signed('0' & hys_i); + u_thres <= inn_signx when pol_inv_i = '0' else inn_signx + hys_signx; + l_thres <= inn_signx when pol_inv_i = '1' else inn_signx - hys_signx; + u_above <= '1' when inp_signx > u_thres else '0'; + l_below <= '1' when inp_signx < l_thres else '0'; + thr_cross <= u_above when pol_inv_i = '0' else l_below; + hys_cross <= l_below when pol_inv_i = '0' else u_above; + + p_fsm_seq : process (clk_i) is + begin + if rising_edge(clk_i) then + if rst_n_i = '0' or enable_i = '0' then + fsm_regout <= S_IDLE; + else + fsm_regout <= fsm_regin; + end if; + end if; + end process p_fsm_seq; + + p_fsm_comb : process (fsm_regout, hys_cross, thr_cross) is + -- Variable to hold changes to our FSM register + -- within the current clock cycle. + variable v_fsm_reg : t_FSM_STATE := S_IDLE; + begin + + -- Default values for FSM combinatorial outputs. Overriden + -- when necessary by each state. + comp_out <= '0'; + comp_out_p <= '0'; + + -- First load register values from previous cycle. + v_fsm_reg := fsm_regout; + + case v_fsm_reg is + + -- We can only end here after a reset or enable_i = '0'. + -- Wait for input to drop below lower threshold + -- before we start tracking it. + when S_IDLE => + if hys_cross = '1' then + v_fsm_reg := S_BELOW_THRES; + end if; + + -- Signal is below lower threshold. + when S_BELOW_THRES => + if thr_cross = '1' then + v_fsm_reg := S_ABOVE_THRES1; + end if; + + -- Signal is above upper threshold, pulse the + -- monostable output and change state. + when S_ABOVE_THRES1 => + comp_out <= '1'; + comp_out_p <= '1'; + if hys_cross = '1' then + v_fsm_reg := S_BELOW_THRES; + else + v_fsm_reg := S_ABOVE_THRES2; + end if; + + -- Signal is still below threshold, wait for it. + when S_ABOVE_THRES2 => + comp_out <= '1'; + if hys_cross = '1' then + v_fsm_reg := S_BELOW_THRES; + end if; + + -- Re-init the FSM if something unexpected happens. + when others => + v_fsm_reg := S_IDLE; + + end case; + + -- Last step, update register values for next cycle + fsm_regin <= v_fsm_reg; + + end process p_fsm_comb; + + -- Assign comparator outputs + out_o <= comp_out; + out_p_o <= comp_out_p; + +end architecture arch; diff --git a/testbench/common/gc_comparator/.gitignore b/testbench/common/gc_comparator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..511c45ea152c643db1356464b3ada39c4b58b383 --- /dev/null +++ b/testbench/common/gc_comparator/.gitignore @@ -0,0 +1,4 @@ +* +!.gitignore +!Manifest.py +!*_tb.vhd diff --git a/testbench/common/gc_comparator/Manifest.py b/testbench/common/gc_comparator/Manifest.py new file mode 100644 index 0000000000000000000000000000000000000000..9b23bd68bd0aa5890ea64cfb1aed1bb49d5a8bf1 --- /dev/null +++ b/testbench/common/gc_comparator/Manifest.py @@ -0,0 +1,18 @@ +action = "simulation" +sim_tool = "ghdl" + +target = "xilinx" +syn_device = "xc6slx45t" + +top_module = "gc_comparator_tb" # for hdlmake2 +sim_top = "gc_comparator_tb" # for hdlmake3 + +files = [ + "gc_comparator_tb.vhd", + ] + +modules = { + "local" : [ + "../../../", + ], +} diff --git a/testbench/common/gc_comparator/gc_comparator_tb.gtkwave b/testbench/common/gc_comparator/gc_comparator_tb.gtkwave new file mode 100644 index 0000000000000000000000000000000000000000..df259be80b9b33f660a355854a6b62f94c180fd4 --- /dev/null +++ b/testbench/common/gc_comparator/gc_comparator_tb.gtkwave @@ -0,0 +1,52 @@ +[*] +[*] GTKWave Analyzer v3.3.79 (w)1999-2017 BSI +[*] Fri Dec 15 08:55:54 2017 +[*] +[dumpfile] "/home/dimitris/repos/ohwr/general-cores/testbench/common/gc_comparator/gc_comparator_tb.ghw" +[dumpfile_mtime] "Fri Dec 15 08:53:15 2017" +[dumpfile_size] 4321 +[savefile] "/home/dimitris/repos/ohwr/general-cores/testbench/common/gc_comparator/gc_comparator_tb.gtkwave" +[timestart] 0 +[size] 1916 1156 +[pos] -1 -1 +*-27.910774 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 +[treeopen] top. +[treeopen] top.gc_comparator_tb. +[treeopen] top.gc_comparator_tb.uut. +[sst_width] 229 +[signals_width] 134 +[sst_expanded] 1 +[sst_vpaned_height] 340 +@28 +top.gc_comparator_tb.uut.clk_i +top.gc_comparator_tb.uut.rst_n_i +@200 +- +@28 +top.gc_comparator_tb.uut.pol_inv_i +top.gc_comparator_tb.uut.enable_i +@200 +- +@22 +#{top.gc_comparator_tb.uut.inp_i[15:0]} top.gc_comparator_tb.uut.inp_i[15] top.gc_comparator_tb.uut.inp_i[14] top.gc_comparator_tb.uut.inp_i[13] top.gc_comparator_tb.uut.inp_i[12] top.gc_comparator_tb.uut.inp_i[11] top.gc_comparator_tb.uut.inp_i[10] top.gc_comparator_tb.uut.inp_i[9] top.gc_comparator_tb.uut.inp_i[8] top.gc_comparator_tb.uut.inp_i[7] top.gc_comparator_tb.uut.inp_i[6] top.gc_comparator_tb.uut.inp_i[5] top.gc_comparator_tb.uut.inp_i[4] top.gc_comparator_tb.uut.inp_i[3] top.gc_comparator_tb.uut.inp_i[2] top.gc_comparator_tb.uut.inp_i[1] top.gc_comparator_tb.uut.inp_i[0] +#{top.gc_comparator_tb.uut.inn_i[15:0]} top.gc_comparator_tb.uut.inn_i[15] top.gc_comparator_tb.uut.inn_i[14] top.gc_comparator_tb.uut.inn_i[13] top.gc_comparator_tb.uut.inn_i[12] top.gc_comparator_tb.uut.inn_i[11] top.gc_comparator_tb.uut.inn_i[10] top.gc_comparator_tb.uut.inn_i[9] top.gc_comparator_tb.uut.inn_i[8] top.gc_comparator_tb.uut.inn_i[7] top.gc_comparator_tb.uut.inn_i[6] top.gc_comparator_tb.uut.inn_i[5] top.gc_comparator_tb.uut.inn_i[4] top.gc_comparator_tb.uut.inn_i[3] top.gc_comparator_tb.uut.inn_i[2] top.gc_comparator_tb.uut.inn_i[1] top.gc_comparator_tb.uut.inn_i[0] +#{top.gc_comparator_tb.uut.hys_i[15:0]} top.gc_comparator_tb.uut.hys_i[15] top.gc_comparator_tb.uut.hys_i[14] top.gc_comparator_tb.uut.hys_i[13] top.gc_comparator_tb.uut.hys_i[12] top.gc_comparator_tb.uut.hys_i[11] top.gc_comparator_tb.uut.hys_i[10] top.gc_comparator_tb.uut.hys_i[9] top.gc_comparator_tb.uut.hys_i[8] top.gc_comparator_tb.uut.hys_i[7] top.gc_comparator_tb.uut.hys_i[6] top.gc_comparator_tb.uut.hys_i[5] top.gc_comparator_tb.uut.hys_i[4] top.gc_comparator_tb.uut.hys_i[3] top.gc_comparator_tb.uut.hys_i[2] top.gc_comparator_tb.uut.hys_i[1] top.gc_comparator_tb.uut.hys_i[0] +@200 +- +@28 +top.gc_comparator_tb.uut.fsm_regout +@22 +#{top.gc_comparator_tb.uut.u_thres[16:0]} top.gc_comparator_tb.uut.u_thres[16] top.gc_comparator_tb.uut.u_thres[15] top.gc_comparator_tb.uut.u_thres[14] top.gc_comparator_tb.uut.u_thres[13] top.gc_comparator_tb.uut.u_thres[12] top.gc_comparator_tb.uut.u_thres[11] top.gc_comparator_tb.uut.u_thres[10] top.gc_comparator_tb.uut.u_thres[9] top.gc_comparator_tb.uut.u_thres[8] top.gc_comparator_tb.uut.u_thres[7] top.gc_comparator_tb.uut.u_thres[6] top.gc_comparator_tb.uut.u_thres[5] top.gc_comparator_tb.uut.u_thres[4] top.gc_comparator_tb.uut.u_thres[3] top.gc_comparator_tb.uut.u_thres[2] top.gc_comparator_tb.uut.u_thres[1] top.gc_comparator_tb.uut.u_thres[0] +#{top.gc_comparator_tb.uut.l_thres[16:0]} top.gc_comparator_tb.uut.l_thres[16] top.gc_comparator_tb.uut.l_thres[15] top.gc_comparator_tb.uut.l_thres[14] top.gc_comparator_tb.uut.l_thres[13] top.gc_comparator_tb.uut.l_thres[12] top.gc_comparator_tb.uut.l_thres[11] top.gc_comparator_tb.uut.l_thres[10] top.gc_comparator_tb.uut.l_thres[9] top.gc_comparator_tb.uut.l_thres[8] top.gc_comparator_tb.uut.l_thres[7] top.gc_comparator_tb.uut.l_thres[6] top.gc_comparator_tb.uut.l_thres[5] top.gc_comparator_tb.uut.l_thres[4] top.gc_comparator_tb.uut.l_thres[3] top.gc_comparator_tb.uut.l_thres[2] top.gc_comparator_tb.uut.l_thres[1] top.gc_comparator_tb.uut.l_thres[0] +@28 +top.gc_comparator_tb.uut.u_above +top.gc_comparator_tb.uut.l_below +top.gc_comparator_tb.uut.thr_cross +top.gc_comparator_tb.uut.hys_cross +@200 +- +@28 +top.gc_comparator_tb.uut.out_o +top.gc_comparator_tb.uut.out_p_o +[pattern_trace] 1 +[pattern_trace] 0 diff --git a/testbench/common/gc_comparator/gc_comparator_tb.vhd b/testbench/common/gc_comparator/gc_comparator_tb.vhd new file mode 100644 index 0000000000000000000000000000000000000000..de7c1fb2120ddca3648ff44dfcd1c0a8d414e001 --- /dev/null +++ b/testbench/common/gc_comparator/gc_comparator_tb.vhd @@ -0,0 +1,217 @@ +-------------------------------------------------------------------------------- +-- CERN (BE-CO-HT) +-- General-Purpose Comparator Testbench +-- https://www.ohwr.org/projects/general-cores +-------------------------------------------------------------------------------- +-- +-- unit name: gc_comparator_tb +-- +-- description: +-- +-- Simple testbench for the gc_comparator unit. +-- +-------------------------------------------------------------------------------- +-- Copyright (c) 2017 CERN / BE-CO-HT +-------------------------------------------------------------------------------- +-- GNU LESSER GENERAL PUBLIC LICENSE +-------------------------------------------------------------------------------- +-- This source file is free software; 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 2.1 of the License, or (at your +-- option) any later version. This source 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 along with this +-- source; if not, download it from http://www.gnu.org/licenses/lgpl-2.1.html +-------------------------------------------------------------------------------- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +-- A testbench has no ports +entity gc_comparator_tb is + generic ( + g_IN_WIDTH : natural := 16); +end entity gc_comparator_tb; + +architecture tb of gc_comparator_tb is + + signal tb_end_sim : std_logic := '0'; + + signal tb_rst_n_i : std_logic := '1'; + signal tb_clk_i : std_logic := '0'; + + signal tb_pol_inv_i : std_logic; + signal tb_inp_i : std_logic_vector(g_IN_WIDTH-1 downto 0) := (others => '0'); + signal tb_inn_i : std_logic_vector(g_IN_WIDTH-1 downto 0) := (others => '0'); + signal tb_hys_i : std_logic_vector(g_IN_WIDTH-1 downto 0) := (others => '0'); + signal tb_enable_i : std_logic; + signal tb_out_o : std_logic; + signal tb_out_p_o : std_logic; + +begin -- architecture tb + + -- Instantiate the Unit Under Test (UUT) + uut : entity work.gc_comparator + generic map ( + g_IN_WIDTH => g_IN_WIDTH) + port map ( + clk_i => tb_clk_i, + rst_n_i => tb_rst_n_i, + pol_inv_i => tb_pol_inv_i, + enable_i => tb_enable_i, + inp_i => tb_inp_i, + inn_i => tb_inn_i, + hys_i => tb_hys_i, + out_o => tb_out_o, + out_p_o => tb_out_p_o); + + -- Clock process definitions + clk_i_process : process + begin + while tb_end_sim /= '1' loop + tb_clk_i <= '0'; + wait for 5 NS; + tb_clk_i <= '1'; + wait for 5 NS; + end loop; + wait; + end process; + + + -- Stimulus process + stim_proc : process + + procedure wait_clock_rising ( + constant cycles : in integer) is + begin + for i in 1 to cycles loop + wait until rising_edge(tb_clk_i); + end loop; + end procedure wait_clock_rising; + + procedure assign_inp ( + constant value : in integer; + constant wait_cycles : in integer) is + begin + wait_clock_rising(wait_cycles); + wait until falling_edge(tb_clk_i); + tb_inp_i <= std_logic_vector(to_signed(value, g_IN_WIDTH)); + end procedure assign_inp; + + procedure check_output ( + constant out_state : in std_logic; + constant out_p_state : in std_logic; + constant wait_cycles : in integer) is + begin + wait_clock_rising(wait_cycles); + assert tb_out_o = out_state and tb_out_p_o = out_p_state + report "CHECK FAIL" severity FAILURE; + end procedure check_output; + + begin + -- initial values + tb_end_sim <= '0'; + tb_pol_inv_i <= '0'; + tb_enable_i <= '1'; + tb_inp_i <= std_logic_vector(to_signed(256, g_IN_WIDTH)); + tb_inn_i <= std_logic_vector(to_signed(128, g_IN_WIDTH)); + tb_hys_i <= std_logic_vector(to_signed(16, g_IN_WIDTH)); + + -- hold reset state for 1 us. + tb_rst_n_i <= '0'; + wait_clock_rising(10); + tb_rst_n_i <= '1'; + + -- Input is above threshold but all outputs + -- should remain low after reset. + check_output('0', '0', 0); + + -- Set input to a value below threshold but above + -- hysteresis. All outputs should remain low. + assign_inp (118, 10); + check_output('0', '0', 2); + + -- Set input to a value below threshold and hysteresis. + -- All outputs should remain low. + assign_inp (100, 10); + check_output('0', '0', 2); + + -- Set input to a value above threshold. + -- out_p should pulse and normal out should stay high. + assign_inp (512, 10); + check_output('1', '1', 2); + check_output('1', '0', 1); + + -- Set input to a value below threshold but above + -- hysteresis. Normal out should remain high. + assign_inp (116, 10); + check_output('1', '0', 2); + + -- Set input to a value below threshold and hysteresis. + -- All outputs should remain low. + assign_inp (32, 10); + check_output('0', '0', 2); + + -- Set input to a value above threshold but only for one + -- cycle.both outputs should pulse for one cycle. + assign_inp (512, 10); + assign_inp (0, 1); + check_output('1', '1', 1); + check_output('0', '0', 1); + + -- Set input to a value above threshold. + -- out_p should pulse and normal out should stay high. + assign_inp (512, 10); + check_output('1', '1', 2); + check_output('1', '0', 1); + + -- Disable comparator. Both outputs should remain low. + wait_clock_rising(10); + tb_enable_i <= '0'; + check_output('0', '0', 2); + + -- Switch polarity and enable comparator again + wait_clock_rising(10); + tb_pol_inv_i <= '1'; + wait_clock_rising(10); + tb_enable_i <= '1'; + check_output('0', '0', 0); + + -- Set input to a value above threshold. + -- out_p should pulse and normal out should stay high. + assign_inp (0, 10); + check_output('1', '1', 2); + check_output('1', '0', 1); + + -- Set input to a value below threshold but above + -- hysteresis. Normal out should remain high. + assign_inp (132, 10); + check_output('1', '0', 2); + + -- Set input to a value below threshold and hysteresis. + -- All outputs should remain low. + assign_inp (256, 10); + check_output('0', '0', 2); + + -- Set input to a value above threshold but only for one + -- cycle.both outputs should pulse for one cycle. + assign_inp (0, 10); + assign_inp (512, 1); + check_output('1', '1', 1); + check_output('0', '0', 1); + + report "PASS" severity NOTE; + + wait_clock_rising(10); + + tb_end_sim <= '1'; + + wait; + + end process; + + +end tb; diff --git a/testbench/common/gc_comparator/run_tb.sh b/testbench/common/gc_comparator/run_tb.sh new file mode 100755 index 0000000000000000000000000000000000000000..0c6fd5908e36a379982301df450525b50d10b488 --- /dev/null +++ b/testbench/common/gc_comparator/run_tb.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +make + +./gc_comparator_tb --wave=gc_comparator_tb.ghw + +gtkwave gc_comparator_tb.ghw gc_comparator_tb.gtkwave