Commit c3cfcfdd authored by Dimitris Lampridis's avatar Dimitris Lampridis

modules/common: introduced gc_comparator and accompanying testbench

parent 5205d975
......@@ -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",
......
--------------------------------------------------------------------------------
-- 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;
*
!.gitignore
!Manifest.py
!*_tb.vhd
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" : [
"../../../",
],
}
[*]
[*] 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
--------------------------------------------------------------------------------
-- 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;
#!/bin/bash
make
./gc_comparator_tb --wave=gc_comparator_tb.ghw
gtkwave gc_comparator_tb.ghw gc_comparator_tb.gtkwave
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment